Merge remote-tracking branch 'upstream/main' into haryr/aug25-rebase

# Conflicts:
#	src/libslic3r/Support/TreeSupport.cpp
This commit is contained in:
harrierpigeon
2026-08-30 23:31:48 -05:00
111 changed files with 3633 additions and 2156 deletions
+1 -1
View File
@@ -149,7 +149,7 @@ jobs:
working-directory: ${{ github.workspace }}
run: |
if [ -z "${{ vars.SELF_HOSTED }}" ]; then
brew install automake texinfo libtool
brew install automake texinfo libtool pkgconf yasm nasm
fi
./build_release_macos.sh -dx ${{ !vars.SELF_HOSTED && '-1' || '' }} -a ${{ inputs.arch }} -t 10.15
(cd "${{ github.workspace }}/deps/build/${{ inputs.arch }}" && \
+7
View File
@@ -385,6 +385,13 @@ jobs:
dir "C:/Program Files (x86)/Windows Kits/10/Include"
choco install nsis
- name: Install pkg-config
# FFmpeg is discovered via pkg-config (pkg_check_modules LIBAV in
# src/slic3r/CMakeLists.txt); the Windows runners don't ship it.
if: runner.os == 'Windows' && !vars.SELF_HOSTED
run: |
choco install pkgconfiglite -y
- name: Build slicer Win
if: runner.os == 'Windows'
working-directory: ${{ github.workspace }}
+70 -3
View File
@@ -298,6 +298,8 @@ if (APPLE)
SET(CMAKE_XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "com.orcaslicer.OrcaSlicer")
message(STATUS "Orca: IS_CROSS_COMPILE: ${IS_CROSS_COMPILE}")
elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(CMAKE_INSTALL_RPATH "$ORIGIN")
endif ()
# Proposal for C++ unit tests and sandboxes
@@ -474,7 +476,8 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
# WIN10SDK_PATH is used to point CMake to the WIN10 SDK installation directory.
# We pick it from environment if it is not defined in another way
# ORCA: Removed Netfabb STL fixing service support in favor of CGAL.
# if(WIN32)
if(WIN32)
find_package(PkgConfig REQUIRED)
# if(NOT DEFINED WIN10SDK_PATH)
# if(DEFINED ENV{WIN10SDK_PATH})
# set(WIN10SDK_PATH "$ENV{WIN10SDK_PATH}")
@@ -510,7 +513,7 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
# else()
# message("Building without Win10 Netfabb STL fixing service support")
# endif()
# endif()
endif()
if (APPLE)
message("OS X SDK Path: ${CMAKE_OSX_SYSROOT}")
@@ -1065,6 +1068,10 @@ function(orcaslicer_copy_dlls target config postfix output_dlls)
${CMAKE_PREFIX_PATH}/bin/occt/TKXDESTEP.dll
${CMAKE_PREFIX_PATH}/bin/occt/TKXSBase.dll
${CMAKE_PREFIX_PATH}/bin/freetype.dll
${CMAKE_PREFIX_PATH}/bin/avcodec-61.dll
${CMAKE_PREFIX_PATH}/bin/swresample-5.dll
${CMAKE_PREFIX_PATH}/bin/swscale-8.dll
${CMAKE_PREFIX_PATH}/bin/avutil-59.dll
DESTINATION ${_out_dir})
set(${output_dlls}
@@ -1100,12 +1107,58 @@ function(orcaslicer_copy_dlls target config postfix output_dlls)
${_out_dir}/TKXSBase.dll
${_out_dir}/freetype.dll
${_out_dir}/avcodec-61.dll
${_out_dir}/swresample-5.dll
${_out_dir}/swscale-8.dll
${_out_dir}/avutil-59.dll
PARENT_SCOPE
)
endfunction()
function(orcaslicer_copy_sos target config postfix output_sos)
get_property(_is_multi GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
get_target_property(_alt_out_dir ${target} RUNTIME_OUTPUT_DIRECTORY)
if (_alt_out_dir)
set(_out_dir "${_alt_out_dir}")
elseif (_is_multi)
set(_out_dir "${CMAKE_CURRENT_BINARY_DIR}/${config}")
else ()
set(_out_dir "${CMAKE_CURRENT_BINARY_DIR}")
endif ()
file(COPY ${CMAKE_PREFIX_PATH}/lib/libavcodec.so
${CMAKE_PREFIX_PATH}/lib/libavcodec.so.61
${CMAKE_PREFIX_PATH}/lib/libavcodec.so.61.3.100
${CMAKE_PREFIX_PATH}/lib/libavutil.so
${CMAKE_PREFIX_PATH}/lib/libavutil.so.59
${CMAKE_PREFIX_PATH}/lib/libavutil.so.59.8.100
${CMAKE_PREFIX_PATH}/lib/libswscale.so
${CMAKE_PREFIX_PATH}/lib/libswscale.so.8
${CMAKE_PREFIX_PATH}/lib/libswscale.so.8.1.100
${CMAKE_PREFIX_PATH}/lib/libswresample.so
${CMAKE_PREFIX_PATH}/lib/libswresample.so.5
${CMAKE_PREFIX_PATH}/lib/libswresample.so.5.1.100
DESTINATION ${_out_dir})
set(${output_sos}
${_out_dir}/libavcodec.so
${_out_dir}/libavcodec.so.61
${_out_dir}/libavcodec.so.61.3.100
${_out_dir}/libavutil.so
${_out_dir}/libavutil.so.59
${_out_dir}/libavutil.so.59.8.100
${_out_dir}/libswscale.so
${_out_dir}/libswscale.so.8
${_out_dir}/libswscale.so.8.1.100
${_out_dir}/libswresample.so
${_out_dir}/libswresample.so.5
${_out_dir}/libswresample.so.5.1.100
PARENT_SCOPE
)
endfunction()
# Bundled sources set their own warning flags, and a plain -Wall there means /Wall
# (= -Weverything) under clang-cl. Target options are applied after the ones a target
@@ -1217,6 +1270,20 @@ else ()
endif()
endif ()
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(LIBRARY_FILES
${LIBDIR_BIN}/libavcodec.so.61
${LIBDIR_BIN}/libavcodec.so.61.3.100
${LIBDIR_BIN}/libavutil.so.59
${LIBDIR_BIN}/libavutil.so.59.8.100
${LIBDIR_BIN}/libswresample.so.5
${LIBDIR_BIN}/libswresample.so.5.1.100
${LIBDIR_BIN}/libswscale.so.8
${LIBDIR_BIN}/libswscale.so.8.1.100
)
install(FILES ${LIBRARY_FILES} DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
endif ()
install(FILES ${CMAKE_SOURCE_DIR}/LICENSE.txt DESTINATION ".")
configure_file(${LIBDIR}/dev-utils/platform/unix/fhs.hpp.in ${LIBDIR_BIN}/dev-utils/platform/unix/fhs.hpp)
+16 -3
View File
@@ -20,6 +20,18 @@ for %%a in (%*) do (
if "%%a"=="-x" set USE_NINJA=1
)
@REM Check for clang-cl option (-l). Combined with -x it also builds the deps with
@REM clang-cl; on the Visual Studio generator it applies to the slicer only, because
@REM the dependency sub-builds have no toolset to inherit and stay on MSVC.
set CLANG_ARG=
set TOOLSET_ARG=
for %%a in (%*) do (
if "%%a"=="-l" (
set CLANG_ARG=-DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl
set TOOLSET_ARG=-T ClangCL
)
)
@REM Check for unit-tests option ("tests")
set BUILD_TESTS=OFF
for %%a in (%*) do (
@@ -127,12 +139,13 @@ if "%1"=="slicer" (
GOTO :slicer
)
echo "building deps.."
if defined CLANG_ARG if "%USE_NINJA%"=="0" echo Note: -l needs -x for the dependencies; building them with MSVC.
echo on
REM Set minimum CMake policy to avoid <3.5 errors
set CMAKE_POLICY_VERSION_MINIMUM=3.5
if "%USE_NINJA%"=="1" (
cmake ../ -G %CMAKE_GENERATOR% -DCMAKE_BUILD_TYPE=%build_type%
cmake ../ -G %CMAKE_GENERATOR% %CLANG_ARG% -DCMAKE_BUILD_TYPE=%build_type%
cmake --build . --config %build_type% --target deps
) else (
cmake ../ -G %CMAKE_GENERATOR% -A %arch% -DCMAKE_BUILD_TYPE=%build_type%
@@ -151,10 +164,10 @@ cd %build_dir%
echo on
set CMAKE_POLICY_VERSION_MINIMUM=3.5
if "%USE_NINJA%"=="1" (
cmake .. -G %CMAKE_GENERATOR% -DORCA_TOOLS=ON %SIG_FLAG% -DBUILD_TESTS=%BUILD_TESTS% -DCMAKE_BUILD_TYPE=%build_type%
cmake .. -G %CMAKE_GENERATOR% %CLANG_ARG% -DORCA_TOOLS=ON %SIG_FLAG% -DBUILD_TESTS=%BUILD_TESTS% -DCMAKE_BUILD_TYPE=%build_type%
cmake --build . --config %build_type% --target all
) else (
cmake .. -G %CMAKE_GENERATOR% -A %arch% -DORCA_TOOLS=ON %SIG_FLAG% -DBUILD_TESTS=%BUILD_TESTS% -DCMAKE_BUILD_TYPE=%build_type%
cmake .. -G %CMAKE_GENERATOR% -A %arch% %TOOLSET_ARG% -DORCA_TOOLS=ON %SIG_FLAG% -DBUILD_TESTS=%BUILD_TESTS% -DCMAKE_BUILD_TYPE=%build_type%
cmake --build . --config %build_type% --target ALL_BUILD -- -m
)
@echo off
+3
View File
@@ -21,6 +21,9 @@ orcaslicer_add_cmake_project(Assimp
URL ${_assimp_url}
URL_HASH ${_assimp_hash}
CMAKE_ARGS
# Assimp's ccache support sets the global RULE_LAUNCH_COMPILE, which breaks
# the Ninja RC rule. The superbuild forwards CMAKE_<LANG>_COMPILER_LAUNCHER.
-DASSIMP_BUILD_USE_CCACHE=OFF
-DASSIMP_BUILD_TESTS=OFF
-DASSIMP_BUILD_SAMPLES=OFF
-DASSIMP_BUILD_ASSIMP_TOOLS=OFF
+9 -1
View File
@@ -24,6 +24,13 @@ if (MSVC AND DEP_DEBUG)
set(_options "FORWARD_CONFIG")
endif ()
# Boost.Container's bundled dlmalloc passes int* where the Win32 Interlocked API
# takes volatile long*; cl compiles that with a warning, clang errors out.
set(_boost_c_flags_line "")
if (MSVC AND CMAKE_C_COMPILER_ID STREQUAL "Clang")
set(_boost_c_flags_line "-DCMAKE_C_FLAGS:STRING=-Wno-incompatible-pointer-types")
endif ()
orcaslicer_add_cmake_project(Boost
${_options}
URL "https://github.com/boostorg/boost/releases/download/boost-1.84.0/boost-1.84.0.tar.gz"
@@ -38,6 +45,7 @@ orcaslicer_add_cmake_project(Boost
"${_context_abi_line}"
"${_context_arch_line}"
"${_context_impl_line}"
"${_boost_c_flags_line}"
)
set(DEP_Boost_DEPENDS ZLIB)
set(DEP_Boost_DEPENDS ZLIB)
+13 -4
View File
@@ -157,8 +157,16 @@ endif ()
function(orcaslicer_add_cmake_project projectname)
cmake_parse_arguments(P_ARGS "FORWARD_CONFIG" "INSTALL_DIR;BUILD_COMMAND;INSTALL_COMMAND" "CMAKE_ARGS" ${ARGN})
# MSVC is true for clang-cl as well, so the sub-build toolchain has to key on the
# generator. A non-Visual-Studio superbuild passes its own generator down, and with
# it the CMAKE_C_COMPILER / CMAKE_CXX_COMPILER forwarded below.
set(_dep_msvc_gen FALSE)
if (MSVC AND CMAKE_GENERATOR MATCHES "Visual Studio")
set(_dep_msvc_gen TRUE)
endif ()
set(_configs_line -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE})
if (_is_multi OR MSVC)
if (_is_multi OR _dep_msvc_gen)
if (P_ARGS_FORWARD_CONFIG)
set(_configs_line -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE})
elseif (ORCA_INCLUDE_DEBUG_INFO AND NOT DEP_DEBUG)
@@ -174,7 +182,7 @@ function(orcaslicer_add_cmake_project projectname)
set(_target_config "Release")
endif()
if (MSVC)
if (_dep_msvc_gen)
set(_gen CMAKE_GENERATOR "${DEP_MSVC_GEN}" CMAKE_GENERATOR_PLATFORM "${DEP_PLATFORM}")
else()
set(_gen "")
@@ -182,7 +190,7 @@ function(orcaslicer_add_cmake_project projectname)
if ($ENV{CMAKE_BUILD_PARALLEL_LEVEL})
set(_build_j "") # assume environment will control --build parallel setting
elseif(MSVC)
elseif(_dep_msvc_gen)
set(_build_j "/m")
else()
set(_build_j "-j${NPROC}")
@@ -367,7 +375,7 @@ include(libnoise/libnoise.cmake)
include(Draco/Draco.cmake)
# Assimp: glTF/GLB/FBX import for the texture-to-color feature.
include(FFMPEG/FFMPEG.cmake)
include(Assimp/Assimp.cmake)
@@ -451,6 +459,7 @@ set(_dep_list
dep_libnoise
dep_python3
dep_wxInspector
dep_FFMPEG
dep_Assimp
)
+14
View File
@@ -56,6 +56,18 @@ else()
set(_curl_static ON)
endif()
# curl 7.75's configure probes and code rely on C laxness cl allows but clang
# errors on (implicit function declarations, int* vs u_long* in ioctlsocket),
# which flips probe results and misconfigures nonblock.c into the AmigaOS
# IoctlSocket branch. Relax both diagnostics so the probes behave like cl, and
# pin the camel-case probes off since they only "pass" by implicit declaration.
set(_curl_c_flags_line "")
set(_curl_probe_overrides "")
if (MSVC AND CMAKE_C_COMPILER_ID STREQUAL "Clang")
set(_curl_c_flags_line "-DCMAKE_C_FLAGS:STRING=-Wno-implicit-function-declaration -Wno-incompatible-pointer-types")
set(_curl_probe_overrides -DHAVE_IOCTLSOCKET_CAMEL=0 -DHAVE_IOCTLSOCKET_CAMEL_FIONBIO=0)
endif ()
orcaslicer_add_cmake_project(CURL
# GIT_REPOSITORY https://github.com/curl/curl.git
# GIT_TAG curl-7_75_0
@@ -69,6 +81,8 @@ orcaslicer_add_cmake_project(CURL
-DBUILD_CURL_EXE:BOOL=OFF
-DCMAKE_POSITION_INDEPENDENT_CODE=ON
-DCURL_STATICLIB=${_curl_static}
"${_curl_c_flags_line}"
${_curl_probe_overrides}
${_curl_platform_flags}
)
+15
View File
@@ -7,5 +7,20 @@ orcaslicer_add_cmake_project(Eigen
URL https://gitlab.com/libeigen/eigen/-/archive/5.0.1/eigen-5.0.1.zip
URL_HASH SHA256=0dbb1f9e3aaad66f352c03227d8c983f6f0b49e0b07e71a7300f4abcc01aee12
CMAKE_ARGS "${_eigen_extra_flags}"
# Only the headers are consumed here. Everything below builds nothing we
# use, and all three enable_language(Fortran): test/CMakeLists.txt:9,
# lapack/CMakeLists.txt:6 and blas/testing/CMakeLists.txt:2. They default
# to ON because the dependency configures as its own top-level project.
#
# Whether that probe is harmless depends on what CMake finds. The Visual
# Studio generator supports no Fortran, so it finds nothing; clang-cl sits
# next to the LLVM toolset's flang, which works. MSVC with Ninja finds
# Strawberry Perl's MinGW gfortran instead, which the deps build already
# requires for OpenSSL, and hands it the MSVC-style /machine:x64 that
# MinGW's ld reads as a missing input file. The configure dies there and
# takes the rest of the superbuild with it.
-DEIGEN_BUILD_TESTING=OFF
-DEIGEN_BUILD_BLAS=OFF
-DEIGEN_BUILD_LAPACK=OFF
DEPENDS dep_Boost dep_GMP dep_MPFR
)
+87
View File
@@ -0,0 +1,87 @@
set(_conf_cmd ./configure)
if (MSVC)
set(_source_dir "${CMAKE_BINARY_DIR}/dep_FFMPEG-prefix/src/dep_FFMPEG")
set(PREBUILD_URL_arm64 "https://github.com/Noisyfox/FFmpeg-Builds-Orca/releases/download/autobuild-2026-07-17-14-28/ffmpeg-n7.0.3-31-g9b6ffd74b5-winarm64-orca-shared-7.0.zip")
set(PREBUILD_HASH_arm64 "12f4140279f2f8469885e1b5b2e8be9d788882914c21523cacd56989f3548054")
set(PREBUILD_URL_x64 "https://github.com/Noisyfox/FFmpeg-Builds-Orca/releases/download/autobuild-2026-07-17-14-28/ffmpeg-n7.0.3-31-g9b6ffd74b5-win64-orca-shared-7.0.zip")
set(PREBUILD_HASH_x64 "e65916020ddb9ef84b2666dfbcbfc9b1d67f69d15b4a66db53754637bf2d498c")
ExternalProject_Add(dep_FFMPEG
URL ${PREBUILD_URL_${DEPS_ARCH}}
URL_HASH SHA256=${PREBUILD_HASH_${DEPS_ARCH}}
DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/FFMPEG
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND
COMMAND ${CMAKE_COMMAND} -E copy_directory "${_source_dir}/bin" "${DESTDIR}/bin"
COMMAND ${CMAKE_COMMAND} -E copy_directory "${_source_dir}/lib" "${DESTDIR}/lib"
COMMAND ${CMAKE_COMMAND} -E copy_directory "${_source_dir}/include" "${DESTDIR}/include"
)
else ()
if (APPLE)
set(_minos_cmd
"--extra-cflags=-mmacosx-version-min=${DEP_OSX_TARGET}"
"--extra-ldflags=-mmacosx-version-min=${DEP_OSX_TARGET}"
)
# Static FFmpeg: nothing to bundle into the .app, no rpath handling.
# Disable the VideoToolbox/AudioToolbox HW-accel paths: the player decodes
# in software (swscale), and the auto-detected HW objects would drag in
# system frameworks that the static libs would then depend on.
set(_link_cmd --enable-static --disable-shared --disable-videotoolbox --disable-audiotoolbox)
if (IS_CROSS_COMPILE)
set(_cross_cmd --enable-cross-compile)
set(_pic_cmd --enable-pic)
if (${CMAKE_SYSTEM_PROCESSOR} MATCHES "x86_64")
set(_arch_cmd --arch=arm64)
set(_cc_cmd "--cc=clang -arch arm64")
else()
set(_arch_cmd --arch=x86_64)
set(_cc_cmd "--cc=clang -arch x86_64")
endif()
endif()
else ()
set(_link_cmd --enable-shared)
endif ()
set(_build_j -j)
if(DEFINED ENV{CMAKE_BUILD_PARALLEL_LEVEL})
set(_build_j "-j$ENV{CMAKE_BUILD_PARALLEL_LEVEL}")
endif()
ExternalProject_Add(dep_FFMPEG
URL https://github.com/FFmpeg/FFmpeg/archive/refs/tags/n7.0.3.tar.gz
URL_HASH SHA256=DEEDCABE339165214A3637DF4C86A507AEF0D793CF8774FF68735F4737E8DDBC
DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/FFMPEG
CONFIGURE_COMMAND ${_conf_cmd}
${_cross_cmd}
${_pic_cmd}
${_arch_cmd}
${_cc_cmd}
"--prefix=${DESTDIR}"
${_link_cmd}
${_minos_cmd}
--disable-doc
--enable-small
--disable-outdevs
--disable-filters
--enable-filter=*null*,afade,*fifo,*format,*resample,aeval,allrgb,allyuv,atempo,pan,*bars,color,*key,crop,draw*,eq*,framerate,*_qsv,*_vaapi,*v4l2*,hw*,scale,volume,test*
--disable-protocols
--enable-protocol=file,fd,pipe,rtp,udp
--disable-muxers
--enable-muxer=rtp
--disable-encoders
--disable-decoders
--enable-decoder=*aac*,h264*,mp3*,mjpeg,rv*
--disable-demuxers
--enable-demuxer=h264,mp3,mov
--disable-zlib
--disable-avdevice
BUILD_IN_SOURCE ON
BUILD_COMMAND make ${_build_j}
INSTALL_COMMAND make install
)
endif()
+43
View File
@@ -1,3 +1,20 @@
diff --git a/adm/cmake/occt_defs_flags.cmake b/adm/cmake/occt_defs_flags.cmake
index 00000000..00000001 100644
--- a/adm/cmake/occt_defs_flags.cmake
+++ b/adm/cmake/occt_defs_flags.cmake
@@ -134,7 +134,11 @@
set (CMAKE_CXX_FLAGS "-std=c++0x ${CMAKE_CXX_FLAGS}")
endif()
# Optimize size of binaries
- set (CMAKE_SHARED_LINKER_FLAGS "-Wl,-s ${CMAKE_SHARED_LINKER_FLAGS}")
+ # clang-cl reports the Clang compiler ID, and OCCT builds shared on Windows,
+ # where the MSVC-style linker gets this flag as an argument it does not know.
+ if (NOT WIN32)
+ set (CMAKE_SHARED_LINKER_FLAGS "-Wl,-s ${CMAKE_SHARED_LINKER_FLAGS}")
+ endif()
elseif(MINGW)
add_definitions(-D_WIN32_WINNT=0x0601)
# _WIN32_WINNT=0x0601 (use Windows 7 SDK)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d98acc0f..28eb8eb4 100644
--- a/CMakeLists.txt
@@ -168,6 +185,32 @@ index d98acc0f..28eb8eb4 100644
endforeach()
if (BUILD_SAMPLES_QT)
diff --git a/adm/cmake/occt_macros.cmake b/adm/cmake/occt_macros.cmake
index 224c96b1..8c94a1c5 100644
--- a/adm/cmake/occt_macros.cmake
+++ b/adm/cmake/occt_macros.cmake
@@ -608,7 +608,7 @@ macro (OCCT_INSERT_CODE_FOR_TARGET)
install(CODE "if (\"\${CMAKE_INSTALL_CONFIG_NAME}\" MATCHES \"^([Rr][Ee][Ll][Ee][Aa][Ss][Ee])$\")
set (OCCT_INSTALL_BIN_LETTER \"\")
elseif (\"\${CMAKE_INSTALL_CONFIG_NAME}\" MATCHES \"^([Rr][Ee][Ll][Ww][Ii][Tt][Hh][Dd][Ee][Bb][Ii][Nn][Ff][Oo])$\")
- set (OCCT_INSTALL_BIN_LETTER \"i\")
+ set (OCCT_INSTALL_BIN_LETTER \"\")
elseif (\"\${CMAKE_INSTALL_CONFIG_NAME}\" MATCHES \"^([Dd][Ee][Bb][Uu][Gg])$\")
set (OCCT_INSTALL_BIN_LETTER \"d\")
endif()")
diff --git a/adm/cmake/occt_toolkit.cmake b/adm/cmake/occt_toolkit.cmake
index 550e0e2f..7ac1a3b8 100644
--- a/adm/cmake/occt_toolkit.cmake
+++ b/adm/cmake/occt_toolkit.cmake
@@ -241,7 +241,7 @@
else()
set (aReleasePdbConf)
endif()
- install (FILES ${CMAKE_BINARY_DIR}/${OS_WITH_BIT}/${COMPILER}/bin\${OCCT_INSTALL_BIN_LETTER}/${PROJECT_NAME}.pdb
+ install (FILES $<TARGET_PDB_FILE:${PROJECT_NAME}>
CONFIGURATIONS Debug ${aReleasePdbConf} RelWithDebInfo
DESTINATION "${INSTALL_DIR_BIN}\${OCCT_INSTALL_BIN_LETTER}")
endif()
diff --git a/src/Font/Font_FTFont.cxx b/src/Font/Font_FTFont.cxx
index 5ae9899f..0a17372b 100644
--- a/src/Font/Font_FTFont.cxx
+13 -3
View File
@@ -17,10 +17,20 @@ else()
endif()
if(WIN32)
set(_conf_cmd perl Configure )
set(_openssl_msvc_env CC=cl CXX=cl RC=rc CL=/FS)
# OpenSSL's perl Configure honors the CC environment variable, but the
# VC-WIN64A makefile only works with cl (an unquoted clang-cl path with
# spaces, e.g. exported by CLion, silently produces no .obj files and the
# lib step fails with LNK1181). Pin the upstream toolchain.
# Keep rc.exe resolved from the MSVC developer environment as well. The
# absolute Windows SDK path contains spaces and OpenSSL 1.1.1 writes it to
# the generated nmake file without quoting, which skips .res generation.
# /FS serializes access to OpenSSL's shared generated PDB when cl is
# driven through nmake from a Ninja configure step.
set(_conf_cmd ${CMAKE_COMMAND} -E env ${_openssl_msvc_env} perl Configure )
set(_cross_comp_prefix_line "")
set(_make_cmd nmake)
set(_install_cmd nmake install_sw )
set(_make_cmd ${CMAKE_COMMAND} -E env ${_openssl_msvc_env} nmake)
set(_install_cmd ${CMAKE_COMMAND} -E env ${_openssl_msvc_env} nmake install_sw )
else()
if(APPLE)
set(_conf_cmd export MACOSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET} && ./Configure -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET})
+24
View File
@@ -1,3 +1,26 @@
# wxInspector finds wxWidgets through CMake's FindwxWidgets module, which only
# searches lib/vc*_lib because _WX_TOOL is hardcoded to "vc". A superbuild driven
# by clang-cl installs wxWidgets into lib/clang_x64_lib, so hand the module the
# directory wxWidgets actually used, derived the same way wxWidgetsConfig.cmake
# derives it.
set(_wxinspector_wx_hints "")
if (MSVC)
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set(_wx_compiler_prefix "clang")
else ()
set(_wx_compiler_prefix "vc")
endif ()
set(_wx_arch_suffix "")
if (CMAKE_GENERATOR_PLATFORM AND NOT CMAKE_GENERATOR_PLATFORM STREQUAL "Win32")
string(TOLOWER "_${CMAKE_GENERATOR_PLATFORM}" _wx_arch_suffix)
elseif (CMAKE_SIZEOF_VOID_P EQUAL 8)
set(_wx_arch_suffix "_x64")
endif ()
set(_wxinspector_wx_hints
"-DwxWidgets_ROOT_DIR=${DESTDIR}"
"-DwxWidgets_LIB_DIR=${DESTDIR}/lib/${_wx_compiler_prefix}${_wx_arch_suffix}_lib")
endif ()
orcaslicer_add_cmake_project(
wxInspector
URL https://github.com/Noisyfox/wxInspector/archive/refs/tags/v1.0.0.zip
@@ -6,6 +29,7 @@ orcaslicer_add_cmake_project(
CMAKE_ARGS
-DCMAKE_CXX_FLAGS="-DwxDEBUG_LEVEL=0"
-DCMAKE_POSITION_INDEPENDENT_CODE=ON
${_wxinspector_wx_hints}
)
if (MSVC)
-28
View File
@@ -1,28 +0,0 @@
---
build/cmake/wxWidgetsConfig.cmake.in | 10 +++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/build/cmake/wxWidgetsConfig.cmake.in b/build/cmake/wxWidgetsConfig.cmake.in
index 1a83f36..70ad8a4 100644
--- a/build/cmake/wxWidgetsConfig.cmake.in
+++ b/build/cmake/wxWidgetsConfig.cmake.in
@@ -58,7 +58,16 @@ if(WIN32_MSVC_NAMING)
endif()
endif()
-include("${CMAKE_CURRENT_LIST_DIR}${wxPLATFORM_LIB_DIR}/@PROJECT_NAME@Targets.cmake")
+if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
+ if (CMAKE_GENERATOR_PLATFORM STREQUAL "ARM64" OR CMAKE_VS_PLATFORM_NAME STREQUAL "ARM64" OR CMAKE_SYSTEM_PROCESSOR MATCHES "^(ARM64|arm64|aarch64)$")
+ set(_wx_clang_msvc_lib_dir "vc_arm64_lib")
+ else()
+ set(_wx_clang_msvc_lib_dir "vc_x64_lib")
+ endif()
+ include("${CMAKE_CURRENT_LIST_DIR}${wxPLATFORM_LIB_DIR}/${_wx_clang_msvc_lib_dir}/@PROJECT_NAME@Targets.cmake")
+else()
+ include("${CMAKE_CURRENT_LIST_DIR}${wxPLATFORM_LIB_DIR}/@PROJECT_NAME@Targets.cmake")
+endif()
macro(wx_inherit_property source dest name)
# property name without _<CONFIG>
--
2.43.0
-1
View File
@@ -28,7 +28,6 @@ orcaslicer_add_cmake_project(
GIT_SHALLOW ON
GIT_SUBMODULES 3rdparty/catch 3rdparty/pcre 3rdparty/libwebp
DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} ${JPEG_PKG}
PATCH_COMMAND git apply --verbose --ignore-space-change --whitespace=fix ${CMAKE_CURRENT_LIST_DIR}/0001-Clang-CL-fix.patch
CMAKE_ARGS
-DwxBUILD_PRECOMP=ON
${_wx_toolkit}
@@ -0,0 +1,376 @@
# macOS FFmpeg Media Player Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Make macOS use the same FFmpeg-based media player (`wxMediaCtrl3` + `AVVideoDecoder`) as Windows/Linux, linking the static FFmpeg libraries from the deps build, and remove the old `wxMediaCtrl2.mm` BambuPlayer-based player.
**Architecture:** The new player is platform-neutral C++ already used on Linux/Windows. Enabling it on macOS is pure build wiring: compile `wxMediaCtrl3.cpp` + `AVVideoDecoder.cpp` on macOS, drop the `__WXMAC__` alias that redirects `wxMediaCtrl3` to the old `wxMediaCtrl2`, and link static FFmpeg (`libavcodec.a`/`libswscale.a`/`libavutil.a`) from the deps install. The Bambu stream API is dlsym'd at runtime from the network plugin (`libBambuSource.dylib`), which already exports it — no plugin changes needed. Rendering reuses the existing `wxImage``DrawBitmap` paint path (same as Linux).
**Tech Stack:** C++17, wxWidgets, CMake, FFmpeg 7.0.3 (libavcodec/libswscale/libavutil), macOS (Xcode generator), `deps/` ExternalProject build system.
## Global Constraints
- Branch: `dev/ffmpeg-player-macos`. Commit after every task.
- **Linux and Windows builds must not change** — the FFmpeg deps flag change is guarded by `APPLE`; Linux keeps `--enable-shared`, Windows keeps its prebuilt DLL zips.
- Static FFmpeg only on macOS: deps produce `libavcodec.a`/`libswscale.a`/`libavutil.a`; the app links those explicitly — the app binary must have **no** `libav*` dylib references (`otool -L` check).
- Follow existing code style: PascalCase classes, snake_case functions, C++17.
- No changes to `StatusPanel.cpp`, `MediaPlayCtrl.*`, or the BambuTunnel interface — the app already creates `wxMediaCtrl3` and uses only its public interface.
- The player cannot be unit-tested (hardware/plugin-dependent GUI code); verification is build-level, link-level, and manual runtime on a Mac.
- `localization/i18n/list.txt` references only `wxMediaCtrl2.cpp` (Win/Linux, stays) — no translation-list changes needed.
- Build dirs on the dev machine: main app = `build_arm64/` (Xcode generator, multi-config), deps = `deps/build/arm64/` (Unix Makefiles). App target name: `OrcaSlicer`. Substitute your own configured build dirs where noted.
---
### Task 1: Enable wxMediaCtrl3 on macOS and link static FFmpeg
**Files:**
- Modify: `src/slic3r/GUI/wxMediaCtrl3.h` (lines 1822: the `#ifdef __WXMAC__` alias branch)
- Modify: `src/slic3r/GUI/wxMediaCtrl3.cpp:13` (uncomment the event define)
- Modify: `src/slic3r/GUI/wxMediaCtrl2.cpp:101` (remove the event define)
- Modify: `src/slic3r/CMakeLists.txt` (APPLE source list ~lines 779792; FFmpeg link block ~lines 905910)
**Interfaces:**
- Consumes: nothing new (all classes already exist).
- Produces: `wxMediaCtrl3` class compiled on macOS with the same interface as Linux/Windows — `Load(wxURI)`, `Play()`, `Stop()`, `SetIdleImage(wxString)`, `GetState()`, `GetLastError()`, `GetVideoSize()`, event `EVT_MEDIA_CTRL_STAT` defined once in the lib (from `wxMediaCtrl3.cpp`).
- [ ] **Step 1: Remove the macOS alias in wxMediaCtrl3.h**
Current (lines 1623 of `src/slic3r/GUI/wxMediaCtrl3.h`):
```cpp
void wxMediaCtrl_OnSize(wxWindow * ctrl, wxSize const & videoSize, int width, int height);
#ifdef __WXMAC__
#include "wxMediaCtrl2.h"
#define wxMediaCtrl3 wxMediaCtrl2
#else
#define BAMBU_DYNAMIC
```
New:
```cpp
void wxMediaCtrl_OnSize(wxWindow * ctrl, wxSize const & videoSize, int width, int height);
#define BAMBU_DYNAMIC
```
Also remove the matching `#endif` that closed the `#else` branch (the one before the final `#endif /* wxMediaCtrl3_h */`), so the file's `#ifndef`/`#endif` guard pair stays balanced.
- [ ] **Step 2: Move the EVT_MEDIA_CTRL_STAT definition into wxMediaCtrl3.cpp**
In `src/slic3r/GUI/wxMediaCtrl3.cpp:13`, uncomment:
```cpp
//wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent);
```
becomes:
```cpp
wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent);
```
In `src/slic3r/GUI/wxMediaCtrl2.cpp:101`, delete:
```cpp
wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent);
```
(One definition total in the lib — `MediaPlayCtrl.cpp:59` binds this event on the media ctrl.)
- [ ] **Step 3: Update the APPLE source list in CMakeLists.txt**
In `src/slic3r/CMakeLists.txt`, the APPLE branch (currently compiles `wxMediaCtrl2.mm`, which becomes dead on macOS):
```cmake
GUI/wxMediaCtrl2.mm
GUI/wxMediaCtrl2.h
GUI/wxMediaCtrl3.h
)
```
becomes:
```cmake
GUI/AVVideoDecoder.cpp
GUI/AVVideoDecoder.hpp
GUI/wxMediaCtrl3.cpp
GUI/wxMediaCtrl3.h
)
```
(The `else ()` branch — Win/Linux — stays exactly as it is.)
- [ ] **Step 4: Link static FFmpeg on macOS**
In `src/slic3r/CMakeLists.txt`, the FFmpeg block (currently `if (NOT APPLE)`):
```cmake
if (NOT APPLE)
pkg_check_modules(LIBAV REQUIRED IMPORTED_TARGET
libavcodec
libswscale
libavutil
)
target_link_libraries(libslic3r_gui PkgConfig::LIBAV)
endif()
```
becomes:
```cmake
if (APPLE)
# Static FFmpeg from the deps install: nothing to bundle into the .app,
# no rpath/install_name handling. Order matters: avcodec -> swscale -> avutil.
find_library(LIBAVCODEC_LIBRARY NAMES libavcodec.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
find_library(LIBSWSCALE_LIBRARY NAMES libswscale.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
find_library(LIBAVUTIL_LIBRARY NAMES libavutil.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
target_link_libraries(libslic3r_gui ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY})
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_PREFIX_PATH}/include)
else ()
pkg_check_modules(LIBAV REQUIRED IMPORTED_TARGET
libavcodec
libswscale
libavutil
)
target_link_libraries(libslic3r_gui PkgConfig::LIBAV)
endif()
```
The deps install (`${CMAKE_PREFIX_PATH}/lib`) already contains the three `.a` files from the existing arm64 deps build — no deps rebuild needed for this task.
- [ ] **Step 5: Reconfigure and build the app**
Run (Xcode generator; `cmake` re-runs automatically on build):
```bash
cmake --build build_arm64 --config RelWithDebInfo --target OrcaSlicer
```
Expected: configure succeeds (no `pkg_check_modules` errors on macOS, `find_library` finds all three `.a` files), compile succeeds (`wxMediaCtrl3.cpp` and `AVVideoDecoder.cpp` compile on macOS without changes), link succeeds.
If CMake complains that `wxMediaCtrl3.h` is included but not in the source list or similar IDE-only warnings — ignore; headers in the list are cosmetic.
- [ ] **Step 6: Verify no dynamic FFmpeg dependency**
```bash
otool -L build_arm64/src/RelWithDebInfo/OrcaSlicer.app/Contents/MacOS/OrcaSlicer | grep -i "libav" || echo "OK: no dynamic FFmpeg"
```
Expected: prints `OK: no dynamic FFmpeg` (empty grep output). This is the whole point of static linking — nothing to bundle into the `.app`.
- [ ] **Step 7: Quick sanity — macOS unit tests still pass**
```bash
ctest --test-dir build_arm64/tests/libslic3r --output-on-failure
```
Expected: passes (add `-C RelWithDebInfo` if the multi-config generator requires it). If no tests were built in this build dir, build target `tests` first (`cmake --build build_arm64 --config RelWithDebInfo --target tests`).
- [ ] **Step 8: Commit**
```bash
git add src/slic3r/CMakeLists.txt src/slic3r/GUI/wxMediaCtrl3.h src/slic3r/GUI/wxMediaCtrl3.cpp src/slic3r/GUI/wxMediaCtrl2.cpp
git commit -m "feat: use FFmpeg media player on macOS with static FFmpeg"
```
---
### Task 2: Static-only FFmpeg in the macOS deps build
**Files:**
- Modify: `deps/FFMPEG/FFMPEG.cmake` (non-MSVC branch, APPLE section and CONFIGURE_COMMAND)
**Interfaces:**
- Consumes: nothing.
- Produces: a deps install on macOS containing only `libavcodec.a`, `libswscale.a`, `libavutil.a` (+ headers) — no `libav*` dylibs, so no bundling/rpath machinery is ever needed on macOS. Linux and Windows output are unchanged.
- [ ] **Step 1: Add the static flag variable**
In `deps/FFMPEG/FFMPEG.cmake`, inside the non-MSVC `else ()` branch, in the existing `if (APPLE)` block:
```cmake
if (APPLE)
set(_minos_cmd
"CFLAGS=-mmacosx-version-min=${DEP_OSX_TARGET}"
"LDFLAGS=-mmacosx-version-min=${DEP_OSX_TARGET}"
)
```
add after the `_minos_cmd` set:
```cmake
# Static FFmpeg: nothing to bundle into the .app, no rpath handling.
# Shared flags must come AFTER --enable-shared below so they win.
set(_link_cmd --enable-static --disable-shared)
```
and add a matching `else ()` after the `if (IS_CROSS_COMPILE) ... endif()` block inside that `if (APPLE)`, so non-Apple Unix keeps shared:
```cmake
else ()
set(_link_cmd --enable-shared)
endif ()
```
(If the existing `if (IS_CROSS_COMPILE)` block is the last thing inside `if (APPLE)`, the new `else ()` closes the `if (APPLE)` itself.)
- [ ] **Step 2: Use the variable in CONFIGURE_COMMAND**
In the `ExternalProject_Add(dep_FFMPEG ...)` configure command:
```cmake
"--prefix=${DESTDIR}"
--enable-shared
```
becomes:
```cmake
"--prefix=${DESTDIR}"
--enable-shared
${_link_cmd}
```
Order matters: `--enable-shared` comes first, then `--enable-static --disable-shared` (APPLE) or `--enable-shared` (Linux) — the last flag wins in FFmpeg configure.
- [ ] **Step 3: Rebuild the FFmpeg dep (slow — several minutes, run in background)**
The changed CONFIGURE_COMMAND invalidates the ExternalProject stamp, so this re-configures and rebuilds FFmpeg:
```bash
cmake --build deps/build/arm64 --target dep_FFMPEG
```
For a fully clean static-only check (removes the previous shared build tree, which can leave stale `.dylib` files behind in the in-source build):
```bash
rm -rf deps/build/arm64/dep_FFMPEG-prefix
cmake --build deps/build/arm64 --target dep_FFMPEG
```
- [ ] **Step 4: Verify the artifacts**
```bash
ls deps/build/arm64/dep_FFMPEG-prefix/src/dep_FFMPEG/libavcodec/*.a
ls deps/build/arm64/dep_FFMPEG-prefix/src/dep_FFMPEG/libavcodec/*.dylib 2>/dev/null || echo "OK: no dylibs"
```
Expected: `libavcodec.a` present, second command prints `OK: no dylibs`. Check `libavutil` and `libswscale` the same way.
- [ ] **Step 5: Verify the app still links against the static libs**
```bash
cmake --build build_arm64 --config RelWithDebInfo --target OrcaSlicer
otool -L build_arm64/src/RelWithDebInfo/OrcaSlicer.app/Contents/MacOS/OrcaSlicer | grep -i "libav" || echo "OK: no dynamic FFmpeg"
```
Expected: build succeeds, `OK: no dynamic FFmpeg`.
- [ ] **Step 6: Commit**
```bash
git add deps/FFMPEG/FFMPEG.cmake
git commit -m "build: build static-only FFmpeg for macOS deps"
```
---
### Task 3: Remove the old macOS player
**Files:**
- Delete: `src/slic3r/GUI/wxMediaCtrl2.mm`
- Delete: `src/slic3r/GUI/BambuPlayer/BambuPlayer.h` (and the empty `BambuPlayer/` dir)
- Modify: `src/slic3r/GUI/wxMediaCtrl2.h` (remove the `#ifdef __WXMAC__` section, lines 2260)
**Interfaces:**
- Consumes: Task 1 (macOS no longer references `wxMediaCtrl2` — nothing includes `wxMediaCtrl2.h` on macOS anymore; `wxMediaCtrl2` is never instantiated on any platform).
- Produces: a clean tree where the old BambuPlayer-based player is gone from macOS. The `BambuPlayer` ObjC class itself remains inside the network plugin (external prebuilt binary) — only the GUI-side consumer is removed.
- [ ] **Step 1: Delete the old player files**
```bash
git rm src/slic3r/GUI/wxMediaCtrl2.mm
git rm src/slic3r/GUI/BambuPlayer/BambuPlayer.h
rmdir src/slic3r/GUI/BambuPlayer 2>/dev/null || true
```
- [ ] **Step 2: Strip the __WXMAC__ section from wxMediaCtrl2.h**
In `src/slic3r/GUI/wxMediaCtrl2.h`, remove the entire macOS branch of the `#ifdef __WXMAC__` guard — from `#ifdef __WXMAC__` (line 22) through the closing `};` of the mac class (line 60), and the `#else` marker — leaving only the non-mac `class wxMediaCtrl2 : public wxMediaCtrl { ... };` definition followed by the final `#endif /* wxMediaCtrl2_h */`. The resulting file keeps its `#ifndef`/`#endif` include guard pair balanced.
The file stays on disk because Win/Linux compile `wxMediaCtrl2.cpp`, which includes it.
- [ ] **Step 3: Grep for leftover references**
```bash
grep -rn "wxMediaCtrl2.mm\|BambuPlayer/BambuPlayer.h\|BambuPlayer" src/slic3r --include="*.cpp" --include="*.h" --include="*.mm" --include="*.txt"
```
Expected: no hits in `src/slic3r/GUI` (ignore `localization/i18n/list.txt:196`, which lists the Win/Linux `wxMediaCtrl2.cpp` and stays).
- [ ] **Step 4: Rebuild the app**
```bash
cmake --build build_arm64 --config RelWithDebInfo --target OrcaSlicer
```
Expected: configure + compile + link succeed with the deleted files gone.
- [ ] **Step 5: Commit**
```bash
git add -A src/slic3r/GUI
git commit -m "refactor: remove old BambuPlayer-based media player from macOS"
```
---
### Task 4: Runtime verification on hardware
**Files:** none — manual verification.
**Interfaces:** consumes all prior tasks. Final gate: the new player must actually stream on a Mac.
- [ ] **Step 1: Launch the freshly built app**
```bash
open build_arm64/src/RelWithDebInfo/OrcaSlicer.app
```
Expected: app launches normally; no crash in the network/device subsystem.
- [ ] **Step 2: Load the network plugin and open the Device tab**
Log in / ensure the network plugin (`libBambuSource.dylib`) loads, select a printer, open the Device tab (camera monitoring panel).
Expected: the camera preview area shows the idle image initially (no crash — this exercises `wxMediaCtrl3::SetIdleImage` and the `wxImage` load path on macOS for the first time).
- [ ] **Step 3: Start the stream and watch it render**
Click play / wait for `MediaPlayCtrl` to start the stream.
Expected: live video renders in the panel. Check the console/log output (`BOOST_LOG` goes to the terminal if run from it, or check the log file):
- `stat_log ...` lines appear (the `EVT_MEDIA_CTRL_STAT` path is live — proves the Bambu C API dlsym worked from `libBambuSource.dylib`);
- no repeated decode/error messages like `AVVideoDecoder: ...` or `can not find function ...` (proves `StaticBambuLib::get` resolved all Bambu functions);
- Stop/Play toggle works; idle image reappears on stop;
- window resize keeps aspect ratio (exercises `DoSetSize`/`adjust_frame_size`/`paintEvent`).
- [ ] **Step 4: Confirm the old player is really gone**
Expected: nothing in the logs references `BambuPlayer` (the ObjC class is no longer dlsym'd); the video path is entirely `wxMediaCtrl3` + `AVVideoDecoder`.
If a printer is unavailable, at minimum verify Steps 12 (launch + idle image) and note in the PR that live-stream verification needs hardware.
- [ ] **Step 5: Final review pass**
```bash
git log --oneline -6
git show --stat HEAD # and each of the three task commits
```
Expected: the last 4 commits are the design doc + the 3 implementation tasks (each task commit touches only its listed files). Review the diff for scope: no Linux/Windows changes beyond the two `EVT_MEDIA_CTRL_STAT` lines in Task 1, no `StatusPanel`/`MediaPlayCtrl` changes.
@@ -0,0 +1,110 @@
# FFmpeg Media Player for macOS — Design
Date: 2026-08-14
Branch: `dev/ffmpeg-player-macos`
## Problem
The branch's new FFmpeg-based media player (`wxMediaCtrl3` + `AVVideoDecoder`) is used on
Windows and Linux, but macOS still runs the old player: `wxMediaCtrl2.mm`, an ObjC
`BambuPlayer` class dlsym'd from the Bambu network plugin that renders via CALayer.
On macOS, `wxMediaCtrl3` is currently aliased to `wxMediaCtrl2` and FFmpeg is not linked
into the app at all.
Goal: make macOS use the same FFmpeg player as Windows/Linux, linking the **static**
FFmpeg libraries from the deps build instead of dynamic ones.
## Current state (verified)
- New player (Win/Linux): `GUI/wxMediaCtrl3.cpp` + `GUI/AVVideoDecoder.cpp`. Decodes with
FFmpeg (libavcodec/libswscale/libavutil), renders frames into `wxImage` (non-Windows) /
`wxBitmap` (Windows) drawn in a `paintEvent`, feeds via the `Bambu_*` C API
(`BambuTunnel.h`, `BAMBU_DYNAMIC`) dlsym'd from the network plugin through
`StaticBambuLib::get()` (`GUI/Printer/PrinterFileSystem.cpp`, compiled on all platforms).
- Old player (macOS): `GUI/wxMediaCtrl2.mm` uses the ObjC `BambuPlayer` class found via
`dlsym(module, "OBJC_CLASS_$_BambuPlayer")` in `libBambuSource.dylib`.
- The macOS network plugin `libBambuSource.dylib` already exports the full Bambu C API
(verified with `nm`), so the new player needs zero plugin changes.
- FFmpeg linking in `src/slic3r/CMakeLists.txt` is guarded by `if (NOT APPLE)`
macOS currently does not link FFmpeg.
- `deps/FFMPEG/FFMPEG.cmake`: non-MSVC branch builds FFmpeg from source with
`--enable-shared`. The existing arm64 deps build on the dev machine happened to be
configured with both static and shared enabled, so `libavcodec.a` / `libswscale.a` /
`libavutil.a` are already present at
`deps/build/arm64/OrcaSlicer_dep/usr/local/lib/`.
- `EVT_MEDIA_CTRL_STAT` is `wxDEFINE_EVENT`'d in `wxMediaCtrl2.cpp` (Win/Linux) and
`wxMediaCtrl2.mm` (macOS); the define in `wxMediaCtrl3.cpp` is commented out.
- `wxMediaCtrl2` is never instantiated anywhere on any platform — dead code.
- `StatusPanel` already creates `wxMediaCtrl3`; `MediaPlayCtrl` only uses the
`wxMediaCtrl3` interface (`Load/Play/Stop/GetState/GetVideoSize/GetLastError/SetIdleImage`),
so no UI-side changes are needed.
## Approach (approved)
**Reuse the shared player on macOS.** Compile the existing `wxMediaCtrl3.cpp` +
`AVVideoDecoder.cpp` on macOS so all three platforms run one implementation.
Rendering uses the existing `wxImage``DrawBitmap` paint path, identical to Linux.
Known trade-off: frames are scaled to the widget's logical (1x) size, so Retina is
slightly soft compared to the old CALayer player. Accepted for now; a Retina-aware
scaling follow-up is possible later.
Rejected alternative: a native CGImage/CALayer renderer for macOS — faster and
Retina-crisp, but adds a second render implementation to maintain.
## Changes
### 1. Enable the FFmpeg player on macOS (source)
- `GUI/wxMediaCtrl3.h`: remove the `#ifdef __WXMAC__` branch (lines 1822) that aliases
`wxMediaCtrl3``wxMediaCtrl2`. macOS then compiles the real `wxMediaCtrl3` class,
including the `BAMBU_DYNAMIC` BambuTunnel path used on Linux.
- Event symbol fix: move `wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent)` into
`wxMediaCtrl3.cpp` (uncomment the existing line) and remove it from
`wxMediaCtrl2.cpp`. One definition total in the lib; all three platforms resolve it.
### 2. Static FFmpeg linking (deps + app)
- `deps/FFMPEG/FFMPEG.cmake`: in the non-MSVC branch, pass
`--disable-shared --enable-static` when `APPLE`. Linux keeps `--enable-shared`;
Windows keeps its prebuilt shared DLL zips. Fresh macOS deps builds install only
`libavcodec.a` / `libswscale.a` / `libavutil.a` — no dylibs to bundle, no
rpath/install_name handling. (The existing local arm64 deps build already contains
the `.a` files, so no deps rebuild is strictly needed to try the change locally,
but a fresh CI deps build must produce them.)
- `src/slic3r/CMakeLists.txt`:
- APPLE branch of `SLIC3R_GUI_SOURCES`: add `GUI/wxMediaCtrl3.cpp`,
`GUI/wxMediaCtrl3.h`, `GUI/AVVideoDecoder.cpp`, `GUI/AVVideoDecoder.hpp`;
remove `GUI/wxMediaCtrl2.mm` and `GUI/wxMediaCtrl2.h` (the `.h` stays on
disk for the Win/Linux build of `wxMediaCtrl2.cpp`, but nothing on macOS
includes it after this change).
- Add an APPLE mirror of the `NOT APPLE` FFmpeg block: `find_library` for
`libavcodec.a`, `libswscale.a`, `libavutil.a` under `${CMAKE_PREFIX_PATH}/lib`
with `NO_DEFAULT_PATH`, link them (order avcodec → swscale → avutil), and add
`${CMAKE_PREFIX_PATH}/include` as a SYSTEM include directory. Deps are built with
`--disable-zlib` and no external codecs, so the three static libs link cleanly.
### 3. Remove the old player
- Delete `GUI/wxMediaCtrl2.mm` and `GUI/BambuPlayer/BambuPlayer.h` (header used only
by the `.mm`; the real `BambuPlayer` lives inside the network plugin).
- Remove the now-dead `__WXMAC__` section of `GUI/wxMediaCtrl2.h`.
- `wxMediaCtrl2.cpp` (Win/Linux) stays in the build as-is (dead but harmless; out of
scope to remove on this branch).
### 4. Verification
- Build on macOS: `cmake --build build_arm64` (or `build/arm64`).
- Confirm no dynamic FFmpeg dependency: `otool -L` on the app binary shows no `libav*`
dylib references.
- Runtime: with the network plugin loaded, the Device tab camera preview streams via
the FFmpeg player (check the device page / `MediaPlayCtrl`).
- macOS `ctest` still passes — static linking means no test-executable `.so` copying
hacks (unlike the Linux shared-lib setup).
## Out of scope
- Linux (shared libs, AppImage/flatpak bundling) and Windows (prebuilt DLL zips)
keep their current FFmpeg setup.
- Retina-aware frame scaling / native CGImage rendering (follow-up if visual quality
is judged insufficient).
- Audio streaming (neither player plays audio in this UI path).
-1
View File
@@ -193,7 +193,6 @@ src/slic3r/GUI/ObjColorDialog.cpp
src/slic3r/GUI/SyncAmsInfoDialog.cpp
src/slic3r/GUI/WipeTowerDialog.cpp
src/slic3r/GUI/wxExtensions.cpp
src/slic3r/GUI/wxMediaCtrl2.cpp
src/slic3r/GUI/WebUserLoginDialog.cpp
src/slic3r/GUI/WebGuideDialog.cpp
src/slic3r/GUI/KBShortcutsDialog.hpp
Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 579 KiB

@@ -318,6 +318,12 @@ modules:
sha256: 0ba163956f2d468b19a91b96c5aba66ee9610843ea41dda628ea44cdafde7db7
dest: external-packages/wxInspector
# FFmpeg n7.0.3
- type: file
url: https://github.com/FFmpeg/FFmpeg/archive/refs/tags/n7.0.3.tar.gz
sha256: deedcabe339165214a3637df4c86a507aef0d793cf8774ff68735f4737e8ddbc
dest: external-packages/FFMPEG
# ---------------------------------------------------------------
# Fallback archives for deps normally provided by the GNOME SDK.
# These are only used if find_package() fails to locate them.
+3
View File
@@ -25,6 +25,9 @@ export REQUIRED_DEV_PACKAGES=(
wayland-protocols
webkit2gtk-4.1
wget
pkgconf
yasm
nasm
)
if [[ -n "$UPDATE_LIB" ]]
+3
View File
@@ -25,6 +25,9 @@ export REQUIRED_DEV_PACKAGES=(
wayland-protocols
webkit2gtk
wget
pkgconf
yasm
nasm
)
if [[ -n "$UPDATE_LIB" ]]
+1
View File
@@ -20,6 +20,7 @@ export REQUIRED_BUNDLES=(
perl-basic
texinfo
wget
nasm
)
if [[ -n "$UPDATE_LIB" ]]
+3
View File
@@ -27,6 +27,9 @@ REQUIRED_DEV_PACKAGES=(
ninja-build
texinfo
wget
pkgconf
yasm
nasm
)
if [[ -n "$UPDATE_LIB" ]]
+3
View File
@@ -31,6 +31,9 @@ REQUIRED_DEV_PACKAGES=(
webkit2gtk4.1-devel
wget
libcurl-devel
pkgconf
yasm
nasm
)
if [[ -n "$UPDATE_LIB" ]]
+3
View File
@@ -32,6 +32,9 @@ REQUIRED_DEV_PACKAGES=(
sys-devel/m4
virtual/libudev
x11-libs/gtk+:3
dev-util/pkgconf
dev-lang/yasm
dev-lang/nasm
)
if [[ -n "$UPDATE_LIB" ]]
+3
View File
@@ -30,6 +30,9 @@ REQUIRED_DEV_PACKAGES=(
webkit2gtk4-devel
wget
libcurl-devel
pkgconf
yasm
nasm
)
if [[ -n "$UPDATE_LIB" ]]
+15 -1
View File
@@ -75,7 +75,7 @@ if (SLIC3R_GUI)
list(FILTER wxWidgets_LIBRARIES EXCLUDE REGEX expat)
list(APPEND wxWidgets_LIBRARIES ${EXPAT_LIBRARIES})
endif ()
# This is an issue in the new wxWidgets cmake build, doesn't deal with librt
find_library(LIBRT rt)
if(LIBRT)
@@ -300,6 +300,16 @@ if (WIN32)
endif()
else ()
if (NOT APPLE)
set(output_sos_Release "")
set(output_sos_Debug "")
add_custom_target(OrcaSlicerSosCopy ALL DEPENDS OrcaSlicer)
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
orcaslicer_copy_sos(OrcaSlicerSosCopy "Debug" "d" output_sos_Debug)
else()
orcaslicer_copy_sos(OrcaSlicerSosCopy "Release" "" output_sos_Release)
endif()
endif()
if (APPLE AND NOT CMAKE_MACOSX_BUNDLE)
# On OSX, the name of the binary matches the name of the Application.
add_custom_command(TARGET OrcaSlicer POST_BUILD
@@ -378,5 +388,9 @@ if (WIN32)
install(FILES ${output_dlls_${build_type}} DESTINATION ".")
install(DIRECTORY "${CMAKE_PREFIX_PATH}/libpython/" DESTINATION "python")
else ()
if (APPLE)
else()
install(FILES ${output_sos_${build_type}} DESTINATION "${CMAKE_INSTALL_PREFIX}")
endif()
install(TARGETS OrcaSlicer RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR})
endif ()
@@ -83,6 +83,10 @@ copy_shared_object_to_dir() {
src_real="$(readlink -f "$src")"
dst_name="$(basename "$src_real")"
mkdir -p "$dst_dir"
if [ "$src_real" = "$dst_dir/$dst_name" ]; then
# Already bundled; the dependency resolved from the bundle directory.
return 0
fi
cp -fL "$src_real" "$dst_dir/$dst_name"
if [ -L "$src" ]; then
@@ -96,12 +100,23 @@ copy_shared_object_to_dir() {
}
bundle_dependency_closure() {
local dst_dir="$1"
local dst_dir
dst_dir="$(cd -- "$1" && pwd)"
shift
local -a queue=("$@")
local target dep dep_real copied_path
local target dep dep_real dep_key copied_path
declare -A seen=()
# Dependencies are resolved with ldd, which only searches the default
# loader path. Deps-built shared libraries (e.g. the FFmpeg stack) are not
# installed there and carry no RUNPATH of their own, so once copied into
# the bundle ldd can no longer resolve one sibling from another
# (libavcodec -> libavutil) and reports it as missing. Extend the loader
# path with the bundle directory plus the source directories of files
# already bundled, so every library that was resolved once keeps resolving
# for its own dependencies. The audit script does the same
# (scripts/check_appimage_libs.sh).
local -a search_dirs=("$dst_dir")
while [ ${#queue[@]} -gt 0 ]; do
target="${queue[0]}"
@@ -122,17 +137,24 @@ bundle_dependency_closure() {
continue
fi
if [ -n "${seen[$dep_real]}" ]; then
# Key dedup on the bundled file rather than the source path: once
# ldd resolves a library from the bundle directory (via the
# LD_LIBRARY_PATH above) its path is a dst_dir path, which differs
# from the source path the first resolution returned. Keying on
# the source path would re-copy the file onto itself.
dep_key="$dst_dir/$(basename "$dep_real")"
if [ -n "${seen[$dep_key]}" ]; then
continue
fi
seen[$dep_real]=1
seen[$dep_key]=1
copy_shared_object_to_dir "$dep" "$dst_dir"
search_dirs+=("$(dirname "$dep_real")")
copied_path="$dst_dir/$(basename "$dep_real")"
if [ -e "$copied_path" ]; then
queue+=("$copied_path")
fi
done < <(appimage_list_direct_dependencies "$target")
done < <(LD_LIBRARY_PATH="$(IFS=:; printf '%s' "${search_dirs[*]}")${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" appimage_list_direct_dependencies "$target")
done
}
+1 -1
View File
@@ -229,7 +229,7 @@ public:
m_bbox(bbox.min - Point(SCALED_EPSILON, SCALED_EPSILON), bbox.max + Point(SCALED_EPSILON, SCALED_EPSILON)) {}
size_t idx() const { return m_idx; }
const BoundingBox& bbox() const { return m_bbox; }
Point centroid() const { return (m_bbox.min() + m_bbox.max() / 2); }
Point centroid() const { return (m_bbox.min() + m_bbox.max()) / 2; }
private:
size_t m_idx;
BoundingBox m_bbox;
+6 -6
View File
@@ -57,24 +57,24 @@ void ArcFitter::do_arc_fitting(const Points& points, std::vector<PathFittingData
//BBS: can be fit as arc, then save arc data temperarily
last_arc = target_arc;
if (back_index == points.size() - 1) {
result.emplace_back(std::move(PathFittingData{ front_index,
result.emplace_back(PathFittingData{ front_index,
back_index,
last_arc.direction == ArcDirection::Arc_Dir_CCW ? EMovePathType::Arc_move_ccw : EMovePathType::Arc_move_cw,
last_arc }));
last_arc });
front_index = back_index;
}
} else {
if (back_index - front_index > 2) {
//BBS: althought current point_stack can't be fit as arc,
//but previous must can be fit if removing the top in stack, so save last arc
result.emplace_back(std::move(PathFittingData{ front_index,
result.emplace_back(PathFittingData{ front_index,
back_index - 1,
last_arc.direction == ArcDirection::Arc_Dir_CCW ? EMovePathType::Arc_move_ccw : EMovePathType::Arc_move_cw,
last_arc }));
last_arc });
} else {
//BBS: save the first segment as line move when 3 point-line can't be fit as arc move
if (result.empty() || result.back().path_type != EMovePathType::Linear_move)
result.emplace_back(std::move(PathFittingData{front_index, front_index + 1, EMovePathType::Linear_move, ArcSegment()}));
result.emplace_back(PathFittingData{front_index, front_index + 1, EMovePathType::Linear_move, ArcSegment()});
else if(result.back().path_type == EMovePathType::Linear_move)
result.back().end_point_index = front_index + 1;
}
@@ -87,7 +87,7 @@ void ArcFitter::do_arc_fitting(const Points& points, std::vector<PathFittingData
//BBS: handle the remain data
if (front_index != back_index) {
if (result.empty() || result.back().path_type != EMovePathType::Linear_move)
result.emplace_back(std::move(PathFittingData{front_index, back_index, EMovePathType::Linear_move, ArcSegment()}));
result.emplace_back(PathFittingData{front_index, back_index, EMovePathType::Linear_move, ArcSegment()});
else if (result.back().path_type == EMovePathType::Linear_move)
result.back().end_point_index = back_index;
}
+8 -8
View File
@@ -13,8 +13,8 @@ Slic3r::Polylines Paths64_to_polylines(const Clipper2Lib::Paths64& in)
Slic3r::Points points;
points.reserve(path64.size());
for (const Clipper2Lib::Point64& point64 : path64)
points.emplace_back(std::move(Slic3r::Point(point64.x, point64.y)));
out.emplace_back(std::move(Slic3r::Polyline(points)));
points.emplace_back(Slic3r::Point(point64.x, point64.y));
out.emplace_back(Slic3r::Polyline(points));
}
return out;
}
@@ -29,7 +29,7 @@ Clipper2Lib::Paths64 Slic3rPoints_to_Paths64(const Container& in)
Clipper2Lib::Path64 path;
path.reserve(item.size());
for (const Slic3r::Point& point : item.points)
path.emplace_back(std::move(Clipper2Lib::Point64(point.x(), point.y())));
path.emplace_back(Clipper2Lib::Point64(point.x(), point.y()));
out.emplace_back(std::move(path));
}
return out;
@@ -44,7 +44,7 @@ Points Path64ToPoints(const Clipper2Lib::Path64& path64)
{
Points points;
points.reserve(path64.size());
for (const Clipper2Lib::Point64 &point64 : path64) points.emplace_back(std::move(Slic3r::Point(point64.x, point64.y)));
for (const Clipper2Lib::Point64 &point64 : path64) points.emplace_back(Slic3r::Point(point64.x, point64.y));
return points;
}
@@ -99,7 +99,7 @@ Clipper2Lib::Paths64 Slic3rPolygons_to_Paths64(const Polygons &in)
for (const Polygon &poly : in) {
Clipper2Lib::Path64 path;
path.reserve(poly.points.size());
for (const Slic3r::Point &point : poly.points) path.emplace_back(std::move(Clipper2Lib::Point64(point.x(), point.y())));
for (const Slic3r::Point &point : poly.points) path.emplace_back(Clipper2Lib::Point64(point.x(), point.y()));
out.emplace_back(std::move(path));
}
return out;
@@ -114,7 +114,7 @@ Clipper2Lib::Paths64 Slic3rExPolygons_to_Paths64(const ExPolygons& in)
const auto &poly = expolygon.contour_or_hole(i);
Clipper2Lib::Path64 path;
path.reserve(poly.points.size());
for (const Slic3r::Point &point : poly.points) path.emplace_back(std::move(Clipper2Lib::Point64(point.x(), point.y())));
for (const Slic3r::Point &point : poly.points) path.emplace_back(Clipper2Lib::Point64(point.x(), point.y()));
out.emplace_back(std::move(path));
}
}
@@ -134,8 +134,8 @@ Polylines _clipper2_pl_open(Clipper2Lib::ClipType clipType, const Slic3r::Polyli
Slic3r::Polylines out;
out.reserve(solution.size() + solution_open.size());
polylines_append(out, std::move(Paths64_to_polylines(solution)));
polylines_append(out, std::move(Paths64_to_polylines(solution_open)));
polylines_append(out, Paths64_to_polylines(solution));
polylines_append(out, Paths64_to_polylines(solution_open));
return out;
}
@@ -108,6 +108,22 @@ const std::vector<Vec2d>& CornerSmoother::curve_coefficients(
return m_cached_coefficients;
}
bool CornerSmoother::is_on_straight_run(const Vec2d &previous, const Vec2d &vertex, const Vec2d &next)
{
const Vec2d incoming_leg = vertex - previous;
const Vec2d outgoing_leg = next - vertex;
const double incoming_length = incoming_leg.norm();
const double outgoing_length = outgoing_leg.norm();
// A vertex repeating one of its neighbours carries no direction of its own.
if (incoming_length < EPSILON || outgoing_length < EPSILON)
return true;
const Vec2d incoming = incoming_leg / incoming_length;
const Vec2d outgoing = outgoing_leg / outgoing_length;
return incoming.dot(outgoing) > 0. &&
std::abs(incoming.x() * outgoing.y() - incoming.y() * outgoing.x()) < EPSILON;
}
void CornerSmoother::round_corner(const Vec2d &previous, const Vec2d &corner, const Vec2d &next)
{
m_corner_points.clear();
+41 -18
View File
@@ -1,6 +1,7 @@
#pragma once
#include <algorithm>
#include <array>
#include <cmath>
#include <functional>
#include <vector>
@@ -47,36 +48,57 @@ public:
template<typename Emit> void push(const Vec2d &point, Emit &emit)
{
if (m_pending == 0) {
if (m_held == 0) {
// The first point of a path is an end, not a corner, and stays where it is.
emit(point);
m_previous = point;
} else if (m_pending > 1) {
round_corner(m_previous, m_corner, point);
for (const Vec2d &corner_point : m_corner_points)
emit(corner_point);
m_previous = m_corner;
m_window[m_held++] = point;
return;
}
m_corner = point;
m_pending = std::min(m_pending + 1, 2);
if (m_held > 1 && is_on_straight_run(m_window[m_held - 2], m_window[m_held - 1], point)) {
// The newest vertex only splits a straight leg, so the leg runs on to this point instead.
m_window[m_held - 1] = point;
return;
}
if (m_held < 3) {
m_window[m_held++] = point;
return;
}
// Both legs of the middle vertex are complete now, so its curve can no longer grow.
emit_corner(m_window[0], m_window[1], m_window[2], emit);
m_window[0] = m_window[1];
m_window[1] = m_window[2];
m_window[2] = point;
}
// Emits the last point of the path and prepares the smoother for a new one.
template<typename Emit> void flush(Emit &emit)
{
if (m_pending > 1)
emit(m_corner);
m_pending = 0;
if (m_held > 2)
emit_corner(m_window[0], m_window[1], m_window[2], emit);
if (m_held > 1)
emit(m_window[m_held - 1]);
m_held = 0;
}
private:
template<typename Emit> void emit_corner(const Vec2d &previous, const Vec2d &corner, const Vec2d &next, Emit &emit)
{
round_corner(previous, corner, next);
for (const Vec2d &corner_point : m_corner_points)
emit(corner_point);
}
// Tells a vertex that only continues a straight leg (or repeats its predecessor) from a corner.
// A path doubling back on itself is not one, that vertex is a hairpin and stays where it is.
static bool is_on_straight_run(const Vec2d &previous, const Vec2d &vertex, const Vec2d &next);
// Fills m_corner_points with the points replacing the corner vertex.
void round_corner(const Vec2d &previous, const Vec2d &corner, const Vec2d &next);
// Flattens the canonical corner curve of the given size and turn into coordinates of the
// (incoming, outgoing) basis of the corner. Cached, as an infill path repeats the same corner.
const std::vector<Vec2d>& curve_coefficients(double corner_distance, const Vec2d &incoming, const Vec2d &outgoing);
// Fraction of the shorter adjoining segment consumed on each side of a corner. Half of a segment
// is the maximum, otherwise the curves of two adjacent corners would overlap.
// Fraction of the shorter adjoining leg consumed on each side of a corner. Half of a leg is the
// maximum, otherwise the curves of two adjacent corners would overlap.
const double m_corner_distance_ratio;
const double m_tolerance;
const double m_max_corner_distance;
@@ -88,10 +110,11 @@ private:
double m_cached_cosine { 0. };
bool m_has_cached_coefficients { false };
Vec2d m_previous { Vec2d::Zero() };
Vec2d m_corner { Vec2d::Zero() };
// Number of points held back: none, the first point of a path, or a corner candidate.
int m_pending { 0 };
// The corners seen last, kept free of vertices that merely split a straight leg. The middle one
// is rounded once the third arrives, which is what makes its outgoing leg final.
std::array<Vec2d, 3> m_window { Vec2d::Zero(), Vec2d::Zero(), Vec2d::Zero() };
// How many of them are filled in.
int m_held { 0 };
};
// Rounds the corners of already scaled paths in place. Paths of less than three points are left alone.
+2 -2
View File
@@ -55,9 +55,9 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
boost::filesystem::path temp_mtl_path(mtl_file);
mtl_path = temp_mtl_path;
}
auto _mtl_path = mtl_name_is_path ? mtl_abs_path.string().c_str() : mtl_path.string().c_str();
const std::string _mtl_path = (mtl_name_is_path ? mtl_abs_path : mtl_path).string();
if (boost::filesystem::exists(mtl_name_is_path ? mtl_abs_path : mtl_path)) {
if (!ObjParser::mtlparse(_mtl_path, mtl_data)) {
if (!ObjParser::mtlparse(_mtl_path.c_str(), mtl_data)) {
BOOST_LOG_TRIVIAL(error) << "load_obj:load_mtl: failed to parse " << _mtl_path;
message = _L("load mtl in obj: failed to parse");
return false;
+22 -12
View File
@@ -111,14 +111,19 @@ bool StepPreProcessor::isUtf8File(const char* path)
bool StepPreProcessor::isUtf8(const std::string str)
{
size_t num = 0;
int i = 0;
size_t i = 0;
while (i < str.length()) {
if ((str[i] & 0x80) == 0x00) {
const unsigned char lead = static_cast<unsigned char>(str[i]);
if ((lead & 0x80) == 0x00) {
i++;
} else if ((num = preNum(str[i])) > 2) {
// preNum() counts the leading 1 bits, and a multi-byte sequence is 2 to 4
// bytes long, so anything outside that range is not a lead byte.
} else if ((num = preNum(lead)) >= 2 && num <= 4) {
if (i + num > str.length())
return false;
i++;
for (int j = 0; j < num - 1; j++) {
if ((str[i] & 0xc0) != 0x80)
for (size_t j = 0; j < num - 1; j++) {
if ((static_cast<unsigned char>(str[i]) & 0xc0) != 0x80)
return false;
i++;
}
@@ -132,15 +137,20 @@ bool StepPreProcessor::isUtf8(const std::string str)
bool StepPreProcessor::isGBK(const std::string str) {
size_t i = 0;
while (i < str.length()) {
if (str[i] <= 0x7f) {
// char is signed here, so every byte compares <= 0x7f unless widened first.
const unsigned char lead = static_cast<unsigned char>(str[i]);
if (lead <= 0x7f) {
i++;
continue;
} else {
if (str[i] >= 0x81 &&
str[i] <= 0xfe &&
str[i + 1] >= 0x40 &&
str[i + 1] <= 0xfe &&
str[i + 1] != 0xf7) {
if (i + 1 >= str.length())
return false;
const unsigned char trail = static_cast<unsigned char>(str[i + 1]);
if (lead >= 0x81 &&
lead <= 0xfe &&
trail >= 0x40 &&
trail <= 0xfe &&
trail != 0xf7) {
i += 2;
continue;
}
@@ -586,7 +596,7 @@ Step::Step_Status Step::mesh(Model* model,
for (Standard_Integer aNodeIter = 1; aNodeIter <= aTriangulation->NbNodes(); ++aNodeIter) {
gp_Pnt aPnt = aTriangulation->Node(aNodeIter);
aPnt.Transform(aTrsf);
points.emplace_back(std::move(Vec3f(aPnt.X(), aPnt.Y(), aPnt.Z())));
points.emplace_back(Vec3f(aPnt.X(), aPnt.Y(), aPnt.Z()));
}
// BBS: copy triangles
const TopAbs_Orientation anOrientation = anExpSF.Current().Orientation();
+1 -1
View File
@@ -8963,7 +8963,7 @@ private:
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " inital and interval = " << m_interval;
m_next_backup = boost::get_system_time() + boost::posix_time::seconds(m_interval);
boost::unique_lock lock(m_mutex);
m_thread = std::move(boost::thread(boost::ref(*this)));
m_thread = boost::thread(boost::ref(*this));
}
~_BBS_Backup_Manager() {
+1 -1
View File
@@ -352,7 +352,7 @@ bool load_svg(const char *path, Model *model, std::string &message)
for (Standard_Integer aNodeIter = 1; aNodeIter <= aTriangulation->NbNodes(); ++aNodeIter) {
gp_Pnt aPnt = aTriangulation->Node(aNodeIter);
aPnt.Transform(aTrsf);
points.emplace_back(std::move(Vec3f(aPnt.X(), aPnt.Y(), aPnt.Z())));
points.emplace_back(Vec3f(aPnt.X(), aPnt.Y(), aPnt.Z()));
}
// BBS: copy triangles
const TopAbs_Orientation anOrientation = anExpSF.Current().Orientation();
+1 -1
View File
@@ -9562,7 +9562,7 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role, LiftTyp
continue;
Polygons temp;
temp.emplace_back(std::move(instance_bbox.polygon()));
temp.emplace_back(instance_bbox.polygon());
if (intersection_pl(travel, temp).empty())
continue;
+2 -2
View File
@@ -352,7 +352,7 @@ void segment(CGALMesh& src, std::vector<CGALMesh>& dst, double smoothing_alpha =
//}
//else
{
dst.emplace_back(std::move(CGALMesh(out)));
dst.emplace_back(CGALMesh(out));
}
}
//if (mesh_merged.is_empty() == false) {
@@ -371,7 +371,7 @@ std::vector<TriangleMesh> segment(const TriangleMesh& src, double smoothing_alph
std::vector<TriangleMesh> out_meshes;
for (auto& outf_cgal_mesh: out_cgal_meshes)
{
out_meshes.emplace_back(std::move(cgal_to_triangle_mesh(outf_cgal_mesh.m)));
out_meshes.emplace_back(cgal_to_triangle_mesh(outf_cgal_mesh.m));
}
return out_meshes;
+1 -1
View File
@@ -261,7 +261,7 @@ static void add_textured_mesh_to_model(Model& model, const TexturedMesh& tex_mes
its_remove_degenerate_faces(its);
its_compactify_vertices(its);
model.add_object(object_name.c_str(), input_file.c_str(), std::move(TriangleMesh(std::move(its))));
model.add_object(object_name.c_str(), input_file.c_str(), TriangleMesh(std::move(its)));
}
Model Model::read_from_file(const std::string& input_file,
+1 -1
View File
@@ -408,7 +408,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
ExtrusionRole role = is_external ? erExternalPerimeter : erPerimeter;
const bool is_contour = !extrusion->is_closed || pg_extrusion.is_contour;
apply_fuzzy_skin(extrusion, perimeter_generator, is_contour);
apply_fuzzy_skin(extrusion, perimeter_generator, is_contour, extrusion->is_closed);
ExtrusionPaths paths;
// detect overhanging/bridging perimeters
+20 -64
View File
@@ -3190,63 +3190,6 @@ void PresetBundle::export_selections(AppConfig &config)
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": printer %1%, print %2%, filaments[0] %3% ")%printers.get_selected_preset_name() % prints.get_selected_preset_name() %filament_presets[0];
}
// BBS
void PresetBundle::set_num_filaments(unsigned int n, std::vector<std::string> new_colors) {
int old_filament_count = this->filament_presets.size();
if (n > old_filament_count && old_filament_count != 0)
filament_presets.resize(n, filament_presets.back());
else {
filament_presets.resize(n);
}
ConfigOptionStrings* filament_color = project_config.option<ConfigOptionStrings>("filament_colour");
ConfigOptionStrings *filament_multi_color = project_config.option<ConfigOptionStrings>("filament_multi_colour");
ConfigOptionStrings* filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type");
ConfigOptionInts* filament_map = project_config.option<ConfigOptionInts>("filament_map");
ConfigOptionInts* filament_nozzle_map = project_config.option<ConfigOptionInts>("filament_nozzle_map");
ConfigOptionInts* filament_volume_map = project_config.option<ConfigOptionInts>("filament_volume_map");
filament_color->resize(n);
// Sync filament multi colour
filament_multi_color->values.resize(n);
for (size_t i = 0; i < n; i++) {
filament_multi_color->values[i] = filament_color->values[i];
}
filament_color_type->resize(n);
filament_map->values.resize(n, 1);
filament_nozzle_map->values.resize(n, 0);
filament_volume_map->values.resize(n, static_cast<int>(NozzleVolumeType::nvtStandard));
ams_multi_color_filment.resize(n);
// Mixed-color metadata is a parallel per-filament array set, so it has to grow and shrink
// with the filament count exactly like filament_colour above.
if (auto* opt = project_config.option<ConfigOptionBools>("filament_is_mixed"))
opt->values.resize(n, false);
if (auto* opt = project_config.option<ConfigOptionStrings>("filament_mixed_components"))
opt->values.resize(n, std::string{});
if (auto* opt = project_config.option<ConfigOptionStrings>("filament_mixed_sublayer_ratios"))
opt->values.resize(n, std::string{});
if (auto* opt = project_config.option<ConfigOptionBools>("filament_mixed_gradient"))
opt->values.resize(n, false);
if (auto* opt = project_config.option<ConfigOptionStrings>("filament_mixed_gradient_range"))
opt->values.resize(n, std::string{});
if (auto* opt = project_config.option<ConfigOptionStrings>("filament_mixed_gradient_curve"))
opt->values.resize(n, std::string{});
if (auto* opt = project_config.option<ConfigOptionBools>("filament_mixed_gradient_per_part"))
opt->values.resize(n, false);
// BBS set new filament color to new_color
if (old_filament_count < n) {
if (!new_colors.empty()) {
for (int i = old_filament_count; i < n; i++) {
filament_color->values[i] = new_colors[i - old_filament_count];
filament_multi_color->values[i] = new_colors[i - old_filament_count];
filament_color_type->values[i] = "1"; // default color type
}
}
}
update_multi_material_filament_presets();
}
void PresetBundle::set_num_filaments(unsigned int n, std::string new_color)
{
unsigned old_filament_count = this->filament_presets.size();
@@ -3262,6 +3205,11 @@ void PresetBundle::set_num_filaments(unsigned int n, std::string new_color)
ConfigOptionInts* filament_nozzle_map = project_config.option<ConfigOptionInts>("filament_nozzle_map");
ConfigOptionInts* filament_volume_map = project_config.option<ConfigOptionInts>("filament_volume_map");
// Which slots are new is a fact about the arrays below, not about filament_presets:
// update_multi_material_filament_presets() tops that list up to the nozzle count on its own,
// so it can already sit at the new size while every array below is still at the old one.
const size_t old_slot_count = filament_color->values.size();
filament_color->resize(n);
// Sync filament multi colour
filament_multi_color->values.resize(n);
@@ -3292,13 +3240,11 @@ void PresetBundle::set_num_filaments(unsigned int n, std::string new_color)
opt->values.resize(n, false);
//BBS set new filament color to new_color
if (old_filament_count < n) {
if (!new_color.empty()) {
for (unsigned i = old_filament_count; i < n; i++) {
filament_color->values[i] = new_color;
filament_multi_color->values[i] = new_color;
filament_color_type->values[i] = "1"; // default color type
}
if (!new_color.empty()) {
for (size_t i = old_slot_count; i < n; i++) {
filament_color->values[i] = new_color;
filament_multi_color->values[i] = new_color;
filament_color_type->values[i] = "1"; // default color type
}
}
@@ -3407,6 +3353,16 @@ size_t PresetBundle::num_mixed_filaments() const
return opt == nullptr ? 0 : size_t(std::count(opt->values.begin(), opt->values.end(), true));
}
// Counted off the mixed flags, not filament_presets: that list is topped up to the nozzle count on
// its own, so it can sit a slot ahead of the arrays that describe slots. Unlike the sibling
// physical_filament_config_indices(), which bounds by filament_presets, this ignores that top-up.
size_t PresetBundle::num_physical_filaments() const
{
const auto *opt = project_config.option<ConfigOptionBools>("filament_is_mixed");
return opt == nullptr ? filament_presets.size()
: size_t(std::count(opt->values.begin(), opt->values.end(), false));
}
std::vector<size_t> PresetBundle::physical_filament_config_indices() const
{
std::vector<size_t> indices;
+5 -2
View File
@@ -326,8 +326,9 @@ public:
// Export selections (current print, current filaments, current printer) into config.ini
void export_selections(AppConfig &config);
// BBS
void set_num_filaments(unsigned int n, std::vector<std::string> new_colors);
// n is the total slot count, and growth appends at the raw tail - which is where the mixed
// slots live. A caller adding physical filaments has to add num_mixed_filaments() on top and
// then move the new slots ahead of the mixed tail, as Sidebar::add_custom_filament does.
void set_num_filaments(unsigned int n, std::string new_col = "");
void update_num_filaments(unsigned int to_del_flament_id);
@@ -503,6 +504,8 @@ public:
// How many slots are mixed. They sit at the tail of the filament list and have no nozzle of
// their own, so any resize driven by the printer's extruder count has to add this on top.
size_t num_mixed_filaments() const;
// How many slots hold a real filament, i.e. everything ahead of the mixed tail.
size_t num_physical_filaments() const;
void on_extruders_count_changed(int extruder_count);
+2 -2
View File
@@ -4039,7 +4039,7 @@ std::vector<Polygons> Print::get_extruder_printable_polygons() const
Polygons ploys = {Polygon::new_scale(e_printable_area)};
extruder_printable_polys.emplace_back(ploys);
}
return std::move(extruder_printable_polys);
return extruder_printable_polys;
}
std::vector<Polygons> Print::get_extruder_unprintable_polygons() const
@@ -4052,7 +4052,7 @@ std::vector<Polygons> Print::get_extruder_unprintable_polygons() const
Polygons ploys = diff(printable_poly, Polygon::new_scale(e_printable_area));
extruder_unprintable_polys.emplace_back(ploys);
}
return std::move(extruder_unprintable_polys);
return extruder_unprintable_polys;
}
size_t Print::get_extruder_id(unsigned int filament_id) const
+1 -1
View File
@@ -915,7 +915,7 @@ void PrintObject::detect_overhangs_for_lift()
Layer& lower_layer = *layer.lower_layer;
ExPolygons overhangs = diff_ex(layer.lslices, offset_ex(lower_layer.lslices, scale_(min_overlap)));
layer.loverhangs = std::move(offset2_ex(overhangs, -0.1f * scale_(line_width), 0.1f * scale_(line_width)));
layer.loverhangs = offset2_ex(overhangs, -0.1f * scale_(line_width), 0.1f * scale_(line_width));
layer.loverhangs_bbox = get_extents(layer.loverhangs);
}
});
+1 -1
View File
@@ -199,7 +199,7 @@ static void MakeMesh(TopoDS_Shape& theSolid, TriangleMesh& theMesh)
for (Standard_Integer aNodeIter = 1; aNodeIter <= aTriangulation->NbNodes(); ++aNodeIter) {
gp_Pnt aPnt = aTriangulation->Node(aNodeIter);
aPnt.Transform(aTrsf);
points.emplace_back(std::move(Vec3f(aPnt.X(), aPnt.Y(), aPnt.Z())));
points.emplace_back(Vec3f(aPnt.X(), aPnt.Y(), aPnt.Z()));
}
//BBS: copy triangles
const TopAbs_Orientation anOrientation = anExpSF.Current().Orientation();
+14 -14
View File
@@ -867,7 +867,7 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/)
// normal overhang
ExPolygons lower_layer_offseted = offset_ex(effective_lower, support_offset_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS);
overhangs_all_layers[layer_nr] = std::move(diff_ex(curr_polys, lower_layer_offseted));
overhangs_all_layers[layer_nr] = diff_ex(curr_polys, lower_layer_offseted);
double duration{ std::chrono::duration_cast<second_>(clock_::now() - t0).count() };
if (duration > 30 || overhangs_all_layers[layer_nr].size() > 100) {
@@ -1421,7 +1421,7 @@ void TreeSupport::generate_toolpaths()
raft_areas.push_back(expoly);
}
raft_areas = std::move(offset_ex(raft_areas, scale_(object_config.raft_first_layer_expansion)));
raft_areas = offset_ex(raft_areas, scale_(object_config.raft_first_layer_expansion));
size_t layer_nr = 0;
for (; layer_nr < m_slicing_params.base_raft_layers; layer_nr++) {
@@ -1557,9 +1557,9 @@ void TreeSupport::generate_toolpaths()
erSupportMaterialInterface : erSupportMaterial;
make_perimeter_and_inner_brim(ts_layer->support_fills.entities, poly, wall_count, flow,
brim_role);
polys = std::move(offset_ex(poly, -flow.scaled_spacing()));
polys = offset_ex(poly, -flow.scaled_spacing());
} else if (area_group.type == SupportLayer::Roof1stLayer) {
polys = std::move(offset_ex(poly, 0.5*support_flow.scaled_width()));
polys = offset_ex(poly, 0.5*support_flow.scaled_width());
}
else {
polys.push_back(poly);
@@ -2465,7 +2465,7 @@ void TreeSupport::draw_circles()
// Inside the gap: remove only the part overlapping the contact surface, keep the rest.
if (bottom_gap_height > EPSILON && layer_bottom_z < band_gap_top - EPSILON) {
any_gap_cleared = true;
comp_poly = std::move(diff_ex(comp_poly, band.surfaces));
comp_poly = diff_ex(comp_poly, band.surfaces);
}
// Overlaps interface band
@@ -2500,7 +2500,7 @@ void TreeSupport::draw_circles()
ExPolygons comp_interface = band_ex.empty() ? ExPolygons {} : intersection_ex(comp_poly, band_ex);
if (!comp_interface.empty()) {
append(new_floor_areas, comp_interface);
comp_poly = std::move(diff_ex(comp_poly, offset_ex(comp_interface, 10)));
comp_poly = diff_ex(comp_poly, offset_ex(comp_interface, 10));
}
}
@@ -2592,7 +2592,7 @@ void TreeSupport::draw_circles()
ts_layer->lslices.emplace_back(*expoly);
}
ts_layer->lslices = std::move(union_ex(ts_layer->lslices));
ts_layer->lslices = union_ex(ts_layer->lslices);
//Must update bounding box which is used in avoid crossing perimeter
ts_layer->lslices_bboxes.clear();
ts_layer->lslices_bboxes.reserve(ts_layer->lslices.size());
@@ -2670,7 +2670,7 @@ void TreeSupport::draw_circles()
if (global_lightning_infill)
{
//search overhangs globally
overhang = std::move(diff_ex(offset_ex(base_areas_lower, -2.0 * scale_(support_extrusion_width)), base_areas));
overhang = diff_ex(offset_ex(base_areas_lower, -2.0 * scale_(support_extrusion_width)), base_areas);
}
else
{
@@ -2681,13 +2681,13 @@ void TreeSupport::draw_circles()
Polygon rev_hole = hole;
rev_hole.make_counter_clockwise();
ExPolygons ex_hole;
ex_hole.emplace_back(std::move(ExPolygon(rev_hole)));
ex_hole.emplace_back(ExPolygon(rev_hole));
for (auto& other_area : base_areas)
//if (&other_area != &base_area)
ex_hole = std::move(diff_ex(ex_hole, other_area));
overhang = std::move(union_ex(overhang, ex_hole));
ex_hole = diff_ex(ex_hole, other_area);
overhang = union_ex(overhang, ex_hole);
}
overhang = std::move(intersection_ex(overhang, offset_ex(base_areas_lower, -0.5 * scale_(support_extrusion_width))));
overhang = intersection_ex(overhang, offset_ex(base_areas_lower, -0.5 * scale_(support_extrusion_width)));
}
overhangs.emplace_back(to_polygons(overhang));
@@ -2945,7 +2945,7 @@ void TreeSupport::drop_nodes()
m_object->print()->set_status(60 + int(10 * (1 - float(layer_nr) / contact_nodes.size())), _u8L("Generating support"));// (boost::format(_u8L("Support: propagate branches at layer %d")) % layer_nr).str());
Polygons layer_contours = std::move(m_ts_data->get_contours_with_holes(obj_layer_nr));
Polygons layer_contours = m_ts_data->get_contours_with_holes(obj_layer_nr);
//std::unordered_map<Line, bool, LineHash>& mst_line_x_layer_contour_cache = m_mst_line_x_layer_contour_caches[layer_nr];
tbb::concurrent_unordered_map<Line, bool, LineHash> mst_line_x_layer_contour_cache;
auto is_line_cut_by_contour = [&mst_line_x_layer_contour_cache,&layer_contours](Point a, Point b)
@@ -4007,7 +4007,7 @@ const ExPolygons& TreeSupportData::calculate_avoidance(const RadiusLayerPair& ke
}
const ExPolygons &collision = get_collision(radius, layer_nr);
avoidance_areas.insert(avoidance_areas.end(), collision.begin(), collision.end());
avoidance_areas = std::move(union_ex(avoidance_areas));
avoidance_areas = union_ex(avoidance_areas);
auto ret = m_avoidance_cache.insert({key, std::move(avoidance_areas)});
//assert(ret.second);
return ret.first->second;
+108 -17
View File
@@ -146,6 +146,85 @@ public:
using IntersectionLines = std::vector<IntersectionLine>;
// Orca: A planar face is commonly represented by multiple triangles. A slicing plane then crosses
// their shared edges and creates intermediate 2D points which are not part of the model contour.
// Track only edges whose two incident triangles lie in the same geometric plane within the slicing
// coordinate precision, so those artificial junctions can be omitted without simplifying genuine,
// nearly-collinear geometry.
using CoplanarEdges = std::vector<bool>;
static CoplanarEdges coplanar_edges(const indexed_triangle_set &mesh, const std::vector<Vec3i32> &face_edge_ids,
const Transform3d &trafo)
{
struct FacePlane {
Vec3d origin { Vec3d::Zero() };
Vec3d normal { Vec3d::Zero() };
bool valid { false };
};
// Orca: Edge IDs are dense but may include boundary edges referenced by just one face.
int num_edges = 0;
for (const Vec3i32 &edge_ids : face_edge_ids)
num_edges = std::max(num_edges, edge_ids.maxCoeff() + 1);
CoplanarEdges coplanar(num_edges, false);
std::vector<int> first_face(num_edges, -1);
std::vector<int> first_face_edge(num_edges, -1);
std::vector<FacePlane> face_planes(face_edge_ids.size());
std::vector<bool> face_plane_computed(face_edge_ids.size(), false);
auto transformed_vertex = [&mesh, &trafo](int vertex_idx) {
return trafo * mesh.vertices[vertex_idx].cast<double>();
};
// Orca: Compute planes lazily. The single-plane slicer masks most faces, so eagerly calculating
// every plane would defeat part of that optimization.
auto face_plane = [&mesh, &face_planes, &face_plane_computed, &transformed_vertex](int face_idx) -> const FacePlane& {
if (! face_plane_computed[face_idx]) {
const Vec3i32 &face = mesh.indices[face_idx];
const Vec3d a = transformed_vertex(face(0));
const Vec3d b = transformed_vertex(face(1));
const Vec3d c = transformed_vertex(face(2));
FacePlane &plane = face_planes[face_idx];
plane.origin = a;
plane.normal = (b - a).cross(c - a);
const double normal_length = plane.normal.norm();
if (normal_length > 0.) {
plane.normal /= normal_length;
plane.valid = true;
}
face_plane_computed[face_idx] = true;
}
return face_planes[face_idx];
};
const double plane_distance_tolerance = SCALING_FACTOR;
for (int face_idx = 0; face_idx < int(face_edge_ids.size()); ++ face_idx) {
for (int edge_idx = 0; edge_idx < 3; ++ edge_idx) {
const int edge_id = face_edge_ids[face_idx](edge_idx);
if (edge_id < 0)
continue;
if (first_face[edge_id] == -1) {
first_face[edge_id] = face_idx;
first_face_edge[edge_id] = edge_idx;
} else {
const int first_face_idx = first_face[edge_id];
const FacePlane &first_plane = face_plane(first_face_idx);
const FacePlane &second_plane = face_plane(face_idx);
const int first_opposite_idx = mesh.indices[first_face_idx]((first_face_edge[edge_id] + 2) % 3);
const int second_opposite_idx = mesh.indices[face_idx]((edge_idx + 2) % 3);
const Vec3d first_opposite = transformed_vertex(first_opposite_idx);
const Vec3d second_opposite = transformed_vertex(second_opposite_idx);
// Orca: A shared edge guarantees that the planes intersect, but not that they coincide.
// Check both opposite vertices against the neighboring plane using one coord_t as the
// distance tolerance. The normal dot product only preserves face orientation; it does
// not classify a shallow angle as coplanar (see #15364).
coplanar[edge_id] = first_plane.valid && second_plane.valid && first_plane.normal.dot(second_plane.normal) > 0. &&
std::abs(first_plane.normal.dot(second_opposite - first_plane.origin)) <= plane_distance_tolerance &&
std::abs(second_plane.normal.dot(first_opposite - second_plane.origin)) <= plane_distance_tolerance;
}
}
}
return coplanar;
}
enum class FacetSliceType {
NoSlice = 0,
Slicing = 1,
@@ -1057,7 +1136,8 @@ struct OpenPolyline {
// called by make_loops() to connect sliced triangles into closed loops and open polylines by the triangle connectivity.
// Only connects segments crossing triangles of the same orientation.
static void chain_lines_by_triangle_connectivity(IntersectionLines &lines, Polygons &loops, std::vector<OpenPolyline> &open_polylines)
static void chain_lines_by_triangle_connectivity(IntersectionLines &lines, const CoplanarEdges &coplanar_edges,
Polygons &loops, std::vector<OpenPolyline> &open_polylines)
{
// Build a map of lines by edge_a_id and a_id.
std::vector<IntersectionLine*> by_edge_a_id;
@@ -1134,6 +1214,11 @@ static void chain_lines_by_triangle_connectivity(IntersectionLines &lines, Polyg
(first_line->a_id != -1 && first_line->a_id == last_line->b_id)) {
// The current loop is complete. Add it to the output.
assert(first_line->a == last_line->b);
// Orca: The seed point is also a triangle junction. Handle it explicitly because it
// is never visited through the next_line branch below when the loop closes.
if (first_line->edge_a_id >= 0 && first_line->edge_a_id < int(coplanar_edges.size()) &&
coplanar_edges[first_line->edge_a_id])
loop_pts.erase(loop_pts.begin());
loops.emplace_back(std::move(loop_pts));
#ifdef SLIC3R_TRIANGLEMESH_DEBUG
printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size());
@@ -1153,7 +1238,12 @@ static void chain_lines_by_triangle_connectivity(IntersectionLines &lines, Polyg
next_line->a.x, next_line->a.y, next_line->b.x, next_line->b.y);
*/
assert(last_line->b == next_line->a);
loop_pts.emplace_back(next_line->a);
// Orca: Skip only junctions introduced by triangulating one planar face. Unlike a generic
// collinearity cleanup, this preserves intentional shallow corners used when comparing
// adjacent layers for bridges and overhang perimeters (see #15364).
if (next_line->edge_a_id < 0 || next_line->edge_a_id >= int(coplanar_edges.size()) ||
! coplanar_edges[next_line->edge_a_id])
loop_pts.emplace_back(next_line->a);
last_line = next_line;
next_line->set_skip();
}
@@ -1382,7 +1472,8 @@ static void chain_open_polylines_close_gaps(std::vector<OpenPolyline> &open_poly
static Polygons make_loops(
// Lines will have their flags modified.
IntersectionLines &lines)
IntersectionLines &lines,
const CoplanarEdges &coplanar_edges)
{
Polygons loops;
#if 0
@@ -1412,7 +1503,7 @@ static Polygons make_loops(
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
std::vector<OpenPolyline> open_polylines;
chain_lines_by_triangle_connectivity(lines, loops, open_polylines);
chain_lines_by_triangle_connectivity(lines, coplanar_edges, loops, open_polylines);
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
{
@@ -1484,6 +1575,7 @@ template<typename ThrowOnCancel>
static std::vector<Polygons> make_loops(
// Lines will have their flags modified.
std::vector<IntersectionLines> &lines,
const CoplanarEdges &coplanar_edges,
const MeshSlicingParams &params,
ThrowOnCancel throw_on_cancel)
{
@@ -1491,20 +1583,13 @@ static std::vector<Polygons> make_loops(
layers.resize(lines.size());
tbb::parallel_for(
tbb::blocked_range<size_t>(0, lines.size()),
[&lines, &layers, &params, throw_on_cancel](const tbb::blocked_range<size_t> &range) {
[&lines, &layers, &coplanar_edges, &params, throw_on_cancel](const tbb::blocked_range<size_t> &range) {
for (size_t line_idx = range.begin(); line_idx < range.end(); ++ line_idx) {
if ((line_idx & 0x0ffff) == 0)
throw_on_cancel();
Polygons &polygons = layers[line_idx];
polygons = make_loops(lines[line_idx]);
// Orca: A planar quad represented by two triangles contributes a point where the
// slicing plane crosses the shared diagonal. After rounding to coord_t this
// point may be very slightly off the otherwise straight contour edge. Apart
// from being redundant, such points make the subsequent contour
// simplification depend on the slice height (and may move seam candidates).
remove_collinear(polygons);
polygons = make_loops(lines[line_idx], coplanar_edges);
auto this_mode = line_idx < params.slicing_mode_normal_below_layer ? params.mode_below : params.mode;
if (! polygons.empty()) {
@@ -1633,7 +1718,7 @@ static std::vector<Polygons> make_slab_loops(
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
Polygons &loops = layers[line_idx];
std::vector<OpenPolyline> open_polylines;
chain_lines_by_triangle_connectivity(in, loops, open_polylines);
chain_lines_by_triangle_connectivity(in, {}, loops, open_polylines);
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
{
SVG svg(debug_out_path("make_slab_loops-out-%d-%d-%s.svg", iRun, line_idx, ProjectionFromTop ? "top" : "bottom").c_str(), bbox_svg);
@@ -1673,7 +1758,7 @@ static ExPolygons make_expolygons_simple(std::vector<IntersectionLine> &lines)
ExPolygons slices;
Polygons holes;
for (Polygon &loop : make_loops(lines))
for (Polygon &loop : make_loops(lines, {}))
if (loop.area() >= 0.)
slices.emplace_back(std::move(loop));
else
@@ -1878,6 +1963,7 @@ std::vector<Polygons> slice_mesh(
BOOST_LOG_TRIVIAL(debug) << "slice_mesh to polygons";
std::vector<IntersectionLines> lines;
CoplanarEdges coplanar;
{
//FIXME facets_edges is likely not needed and quite costly to calculate.
@@ -1885,6 +1971,8 @@ std::vector<Polygons> slice_mesh(
// However facets_edges assigns a single edge ID to two triangles only, thus when factoring facets_edges out, one will have
// to make sure that no code relies on it.
std::vector<Vec3i32> face_edge_ids = its_face_edge_ids(mesh);
// Orca: Keep the coplanarity classification aligned with the edge IDs used to chain this slice.
coplanar = coplanar_edges(mesh, face_edge_ids, params.trafo);
if (zs.size() <= 1) {
// It likely is not worthwile to copy the vertices. Apply the transformation in place.
if (is_identity(params.trafo)) {
@@ -1906,7 +1994,7 @@ std::vector<Polygons> slice_mesh(
throw_on_cancel();
std::vector<Polygons> layers = make_loops(lines, params, throw_on_cancel);
std::vector<Polygons> layers = make_loops(lines, coplanar, params, throw_on_cancel);
#ifdef SLIC3R_DEBUG
{
@@ -1952,6 +2040,7 @@ Polygons slice_mesh(
const MeshSlicingParams &params)
{
std::vector<IntersectionLines> lines;
CoplanarEdges coplanar;
{
bool trafo_identity = is_identity(params.trafo);
@@ -1987,6 +2076,8 @@ Polygons slice_mesh(
// 3) Calculate face neighbors for just the faces in face_mask.
std::vector<Vec3i32> face_edge_ids = its_face_edge_ids(mesh, face_mask);
// Orca: The single-plane path has its own masked edge-ID space, so classify that space separately.
coplanar = coplanar_edges(mesh, face_edge_ids, params.trafo);
// 4) Slice "face_mask" triangles, collect line segments.
// It likely is not worthwile to copy the vertices. Apply the transformation in place.
@@ -2002,7 +2093,7 @@ Polygons slice_mesh(
}
// 5) Chain the line segments.
std::vector<Polygons> layers = make_loops(lines, params, [](){});
std::vector<Polygons> layers = make_loops(lines, coplanar, params, [](){});
assert(layers.size() == 1);
return layers.front();
}
+24 -18
View File
@@ -38,6 +38,8 @@ set(SLIC3R_GUI_SOURCES
GUI/AuxiliaryDataViewModel.hpp
GUI/AuxiliaryDialog.cpp
GUI/AuxiliaryDialog.hpp
GUI/AVVideoDecoder.cpp
GUI/AVVideoDecoder.hpp
GUI/Auxiliary.hpp
GUI/BackgroundSlicingProcess.cpp
GUI/BackgroundSlicingProcess.hpp
@@ -613,6 +615,8 @@ set(SLIC3R_GUI_SOURCES
GUI/WipeTowerDialog.cpp
GUI/wxExtensions.cpp
GUI/wxExtensions.hpp
GUI/wxMediaCtrl3.cpp
GUI/wxMediaCtrl3.h
plugin/PythonInterpreter.cpp
plugin/PythonInterpreter.hpp
plugin/PythonPluginBridge.cpp
@@ -801,21 +805,8 @@ if (APPLE)
GUI/DeepLinkHandlerMac.mm
GUI/DeepLinkHandlerMac.h
GUI/GUI_UtilsMac.mm
GUI/wxMediaCtrl2.mm
GUI/wxMediaCtrl2.h
)
FIND_LIBRARY(DISKARBITRATION_LIBRARY DiskArbitration)
else ()
list(APPEND SLIC3R_GUI_SOURCES
GUI/wxMediaCtrl2.cpp
GUI/wxMediaCtrl2.h
)
endif ()
if (UNIX AND NOT APPLE)
list(APPEND SLIC3R_GUI_SOURCES
GUI/Printer/gstbambusrc.c
)
endif ()
set(ORCA_UPDATER_SIG_KEY_B64 "${ORCA_UPDATER_SIG_KEY}")
@@ -915,6 +906,26 @@ if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
add_precompiled_header(libslic3r_gui pchheader.hpp FORCEINCLUDE)
endif ()
if (APPLE)
# Static FFmpeg from the deps install: nothing to bundle into the .app,
# no rpath/install_name handling. Order matters: avcodec -> swscale -> avutil.
find_library(LIBAVCODEC_LIBRARY NAMES libavcodec.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
find_library(LIBSWSCALE_LIBRARY NAMES libswscale.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
find_library(LIBAVUTIL_LIBRARY NAMES libavutil.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
if (NOT LIBAVCODEC_LIBRARY OR NOT LIBSWSCALE_LIBRARY OR NOT LIBAVUTIL_LIBRARY)
message(FATAL_ERROR "Static FFmpeg (libavcodec.a/libswscale.a/libavutil.a) not found under ${CMAKE_PREFIX_PATH}/lib. Rebuild the deps — FFMPEG builds static-only on macOS.")
endif ()
target_link_libraries(libslic3r_gui ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY})
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_PREFIX_PATH}/include)
else ()
pkg_check_modules(LIBAV REQUIRED IMPORTED_TARGET
libavcodec
libswscale
libavutil
)
target_link_libraries(libslic3r_gui PkgConfig::LIBAV)
endif()
# We need to implement some hacks for wxWidgets and touch the underlying GTK
# layer and sub-libraries. This forces us to use the include locations and
# link these libraries.
@@ -941,11 +952,6 @@ if (UNIX AND NOT APPLE)
target_compile_definitions(libslic3r_gui PRIVATE wxHAVE_GDK_WAYLAND)
endif ()
# We add GStreamer for bambu:/// support.
pkg_check_modules(GSTREAMER REQUIRED gstreamer-1.0)
pkg_check_modules(GST_BASE REQUIRED gstreamer-base-1.0)
target_link_libraries(libslic3r_gui ${GSTREAMER_LIBRARIES} ${GST_BASE_LIBRARIES})
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${GSTREAMER_INCLUDE_DIRS} ${GST_BASE_INCLUDE_DIRS})
endif ()
# Add a definition so that we can tell we are compiling slic3r.
+170
View File
@@ -0,0 +1,170 @@
#include "AVVideoDecoder.hpp"
#include <assert.h>
extern "C"
{
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
}
AVVideoDecoder::AVVideoDecoder()
{
codec_ctx_ = avcodec_alloc_context3(nullptr);
}
AVVideoDecoder::~AVVideoDecoder()
{
if (sws_ctx_)
sws_freeContext(sws_ctx_);
if (frame_)
av_frame_free(&frame_);
if (codec_ctx_)
avcodec_free_context(&codec_ctx_);
}
int AVVideoDecoder::open(Bambu_StreamInfo const &info)
{
auto codec_id = info.sub_type == AVC1 ? AV_CODEC_ID_H264 : AV_CODEC_ID_MJPEG;
auto codec = avcodec_find_decoder(codec_id);
if (codec == nullptr) {
fprintf(stderr, "AVVideoDecoder: unsupported codec!\n");
return -1; // Codec not found
}
/* open the coderc */
if (avcodec_open2(codec_ctx_, codec, nullptr) < 0) {
fprintf(stderr, "AVVideoDecoder: could not open codec\n");
return -1;
}
// Allocate an AVFrame structure
frame_ = av_frame_alloc();
if (frame_ == nullptr)
return -1;
return 0;
}
int AVVideoDecoder::decode(const Bambu_Sample &sample)
{
int ret = -1;
AVPacket *pkt = av_packet_alloc();
if (!pkt) {
return ret;
}
ret = av_new_packet(pkt, sample.size);
if (ret != 0) {
av_packet_free(&pkt);
return ret;
}
memcpy(pkt->data, sample.buffer, size_t(sample.size));
ret = avcodec_send_packet(codec_ctx_, pkt);
if (ret == 0) {
got_frame_ = avcodec_receive_frame(codec_ctx_, frame_) == 0;
}
av_packet_unref(pkt);
av_packet_free(&pkt);
return ret;
}
int AVVideoDecoder::flush()
{
int ret = avcodec_send_packet(codec_ctx_, nullptr);
got_frame_ = avcodec_receive_frame(codec_ctx_, frame_) == 0;
return ret;
}
void AVVideoDecoder::close()
{
}
bool AVVideoDecoder::toWxImage(wxImage &image, wxSize const &size2)
{
if (!got_frame_)
return false;
auto size1 = size2;
if (!size1.IsFullySpecified())
size1 = {frame_->width, frame_->height };
auto size = size1;
if (size.GetWidth() & 0x0f) {
size.SetWidth((size.GetWidth() & ~0x0f) + 0x10);
if (size.GetWidth() != width_) {
std::fill(bits_.begin(), bits_.end(), 0);
width_ = size.GetWidth();
}
}
AVPixelFormat wxFmt = AV_PIX_FMT_RGB24;
sws_ctx_ = sws_getCachedContext(sws_ctx_,
frame_->width, frame_->height, AVPixelFormat(frame_->format),
size1.GetWidth(), size1.GetHeight(), wxFmt,
SWS_GAUSS,
nullptr, nullptr, nullptr);
if (sws_ctx_ == nullptr)
return false;
int length = size.GetWidth() * size.GetHeight() * 3;
if (bits_.size() < length)
bits_.resize(length);
uint8_t * datas[] = { bits_.data() };
int strides[] = { size.GetWidth() * 3 };
int result_h = sws_scale(sws_ctx_, frame_->data, frame_->linesize, 0, frame_->height, datas, strides);
if (result_h != size.GetHeight()) {
return false;
}
// Copy: the frame outlives this decoder and is painted by the GUI thread while the
// next sws_scale is already overwriting bits_, so it must own its pixels. The Windows
// path below needs no equivalent, wxBitmap copies the bits into GDI.
image = wxImage(size.GetWidth(), size.GetHeight(), bits_.data(), true).Copy();
if (!image.IsOk()) {
fprintf(stderr, "AVVideoDecoder: image not ok %dx%d\n", size.GetWidth(), size.GetHeight());
return false;
}
return true;
}
bool AVVideoDecoder::toWxBitmap(wxBitmap &bitmap, wxSize const &size2)
{
if (!got_frame_)
return false;
auto size1 = size2;
if (!size1.IsFullySpecified())
size1 = {frame_->width, frame_->height };
auto size = size1;
if (size.GetWidth() & 0x0f) {
size.SetWidth((size.GetWidth() & ~0x0f) + 0x10);
if (size.GetWidth() != width_) {
std::fill(bits_.begin(), bits_.end(), 0);
width_ = size.GetWidth();
}
}
AVPixelFormat wxFmt = AV_PIX_FMT_RGB32;
sws_ctx_ = sws_getCachedContext(sws_ctx_,
frame_->width, frame_->height, AVPixelFormat(frame_->format),
size1.GetWidth(), size1.GetHeight(), wxFmt,
SWS_GAUSS,
nullptr, nullptr, nullptr);
if (sws_ctx_ == nullptr)
return false;
int length = size.GetWidth() * size.GetHeight() * 4;
if (bits_.size() < length)
bits_.resize(length);
uint8_t *datas[] = { bits_.data() };
int strides[] = { size.GetWidth() * 4 };
int result_h = sws_scale(sws_ctx_, frame_->data, frame_->linesize, 0, frame_->height, datas, strides);
if (result_h != size.GetHeight()) {
fprintf(stderr, "AVVideoDecoder: result_h %d %d\n", result_h, size.GetHeight());
return false;
}
bitmap = wxBitmap((char const *) bits_.data(), size.GetWidth(), size.GetHeight(), 32);
assert(bitmap.IsOk());
if (!bitmap.IsOk()) {
fprintf(stderr, "AVVideoDecoder: bitmap not ok %dx%d\n", size.GetWidth(), size.GetHeight());
return false;
}
return true;
}
+46
View File
@@ -0,0 +1,46 @@
#ifndef AVVIDEODECODER_HPP
#define AVVIDEODECODER_HPP
#include "Printer/BambuTunnel.h"
extern "C" {
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
}
#include <vector>
#include <wx/bitmap.h>
#include <wx/gdicmn.h>
#include <wx/image.h>
class wxBitmap;
class AVVideoDecoder
{
public:
AVVideoDecoder();
~AVVideoDecoder();
public:
int open(Bambu_StreamInfo const &info);
int decode(Bambu_Sample const &sample);
int flush();
void close();
bool toWxImage(wxImage &image, wxSize const &size);
bool toWxBitmap(wxBitmap &bitmap, wxSize const & size);
private:
AVCodecContext *codec_ctx_ = nullptr;
AVFrame * frame_ = nullptr;
SwsContext * sws_ctx_ = nullptr;
bool got_frame_ = false;
int width_ { 0 }; // scale result width
std::vector<uint8_t> bits_;
};
#endif // AVVIDEODECODER_HPP
-30
View File
@@ -1,30 +0,0 @@
//
// BambuPlayer.h
// BambuPlayer
//
// Created by cmguo on 2021/12/6.
//
#import <Foundation/Foundation.h>
#import <AVFoundation/AVSampleBufferDisplayLayer.h>
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
@interface BambuPlayer : NSObject
+ (void) initialize;
- (instancetype) initWithDisplayLayer: (AVSampleBufferDisplayLayer*) layer;
- (instancetype) initWithImageView: (NSView*) view;
- (int) open: (char const *) url;
- (NSSize) videoSize;
- (int) play;
- (void) stop;
- (void) close;
- (void) setLogger: (void (*)(void const * context, int level, char const * msg)) logger withContext: (void const *) context;
@end
NS_ASSUME_NONNULL_END
@@ -1015,7 +1015,7 @@ wxBoxSizer* CalibrationPresetPage::create_ams_items_sizer(MachineObject* obj, wx
auto ams_items_sizer = new wxBoxSizer(wxHORIZONTAL);
for (auto &info : ams_info) {
auto preview_ams_item = new AMSPreview(ams_preview_panel, wxID_ANY, info, info.ams_type);
preview_ams_item->Update(info);
preview_ams_item->UpdateInfo(info);
preview_ams_item->Open();
ams_preview_list.push_back(preview_ams_item);
std::string ams_id = preview_ams_item->get_ams_id();
+1 -1
View File
@@ -64,7 +64,7 @@ public:
DevFirmware(MachineObject* obj) : m_owner(obj) {}
private:
MachineObject* m_owner = nullptr;
[[maybe_unused]] MachineObject* m_owner = nullptr;
};
} // namespace Slic3r
@@ -2,7 +2,7 @@
/* File: uiAMSBestPositionPopup.hpp
* Description: The popup with suggest best ams position
*
//**********************************************************/
************************************************************/
#include "uiAMSBestPositionPopup.hpp"
@@ -2,7 +2,7 @@
/* File: uiAMSBestPositionPopup.hpp
* Description: The popup with suggest best ams position
*
//**********************************************************/
************************************************************/
#pragma once
#include "slic3r/GUI/Widgets/AMSItem.hpp"
@@ -6,7 +6,7 @@
* \n class wgtDeviceNozzleRackNozzleItem;
* \n class wgtDeviceNozzleRackToolHead;
* \n class wgtDeviceNozzleRackPos;
//**********************************************************/
************************************************************/
#include "wgtDeviceNozzleRack.h"
#include "wgtDeviceNozzleRackUpdate.h"
@@ -6,7 +6,7 @@
* \n class wgtDeviceNozzleRackNozzleItem;
* \n class wgtDeviceNozzleRackToolHead;
* \n class wgtDeviceNozzleRackPos;
//**********************************************************/
************************************************************/
#pragma once
#include "slic3r/GUI/DeviceCore/DevNozzleRack.h"
@@ -3,7 +3,7 @@
* Description: The panel with rack updating
*
* \n class wgtDeviceNozzleRackUpdate
//**********************************************************/
************************************************************/
#include "wgtDeviceNozzleRackUpdate.h"
@@ -3,7 +3,7 @@
* Description: The panel for updating hotends
*
* \n class wgtDeviceNozzleRackUpdate
//**********************************************************/
************************************************************/
#pragma once
#include "slic3r/GUI/DeviceCore/DevNozzleRack.h"
@@ -3,7 +3,7 @@
* Description: The panel to select nozzle
*
* \n class wgtDeviceNozzleSelect;
//**********************************************************/
************************************************************/
#include "wgtDeviceNozzleSelect.h"
#include "wgtDeviceNozzleRack.h"
@@ -3,7 +3,7 @@
* Description: The panel to select nozzle
*
* \n class wgtDeviceNozzleSelect;
//**********************************************************/
************************************************************/
#pragma once
+10 -4
View File
@@ -8906,10 +8906,16 @@ void GUI_App::load_current_presets(bool active_preset_combox/*= false*/, bool ch
auto* nozzle_diameter = edited_printer_preset.config.option<ConfigOptionFloats>("nozzle_diameter");
if (nozzle_diameter) {
// Mixed-color slots are virtual filaments kept at the tail of the list, so they have no
// nozzle of their own. Sizing to the nozzle count alone would silently drop the mixes of
// a just-loaded project, and update_extruder_count() would then strip the facets painted
// with them.
preset_bundle->set_num_filaments(nozzle_diameter->values.size() + preset_bundle->num_mixed_filaments());
// nozzle of their own and the count has to allow for them. Only ever grow: this sizes
// the list so the combo boxes have something to bind to, and set_num_filaments() trims
// at the raw tail, so shrinking here would eat the mixes rather than the surplus
// physical slots. A list longer than the nozzle count is a state the app reaches
// legitimately - raising the extruder count and not saving the printer preset leaves
// exactly that on the next start - and losing the project's mixes to it is worse than
// carrying a filament the printer has no nozzle for until the count is next changed.
const size_t target = nozzle_diameter->values.size() + preset_bundle->num_mixed_filaments();
if (target > preset_bundle->filament_presets.size())
preset_bundle->set_num_filaments(target);
}
}
this->plater()->set_printer_technology(printer_technology);
+3
View File
@@ -155,6 +155,9 @@ public:
update_dark_config();
on_sys_color_changed();
event.Skip();
#else
// Not calling Skip() is what stops the event propagating on Windows.
(void) this;
#endif // __WINDOWS__
});
+34 -10
View File
@@ -2482,6 +2482,19 @@ static const ImWchar ranges_keyboard_shortcuts[] =
};
#endif // __APPLE__
// Names drawn through the atlas come from file names and CAD data, not from the UI language.
// GetGlyphRangesDefault() already gives every language the CJK ideographs, which is why a
// Chinese file name renders under an English UI; these are the alphabetic scripts it omits.
// Codepoints the font lacks are skipped at build time, so only existing glyphs cost anything.
static const ImWchar ranges_language_independent[] =
{
0x0100, 0x024F, // Latin Extended-A and Extended-B
0x0370, 0x03FF, // Greek and Coptic
0x0400, 0x04FF, // Cyrillic
0x1E00, 0x1EFF, // Latin Extended Additional (Vietnamese)
0,
};
std::vector<unsigned char> ImGuiWrapper::load_svg(const std::string& bitmap_name, unsigned target_width, unsigned target_height, unsigned *outwidth, unsigned *outheight)
{
@@ -2792,6 +2805,7 @@ void ImGuiWrapper::init_font(bool compress)
ImFontAtlas::GlyphRangesBuilder builder;
builder.AddRanges(m_glyph_ranges);
builder.AddRanges(ImGui::GetIO().Fonts->GetGlyphRangesDefault());
builder.AddRanges(ranges_language_independent);
#ifdef __APPLE__
if (m_font_cjk)
// Apple keyboard shortcuts are only contained in the CJK fonts.
@@ -2813,12 +2827,17 @@ void ImGuiWrapper::init_font(bool compress)
// Orca: temp fix for Korean font
auto font_name_regular = "HarmonyOS_Sans_SC_Regular.ttf";
auto font_name_bold = "HarmonyOS_Sans_SC_Bold.ttf";
// The Korean and Thai fonts cover their own script and little else, so they need the
// default font merged in behind them to reach the full range.
bool needs_glyph_fallback = false;
if(m_glyph_ranges == ImGui::GetIO().Fonts->GetGlyphRangesKorean()) {
font_name_regular = "NanumGothic-Regular.ttf";
font_name_bold = "NanumGothic-Bold.ttf";
needs_glyph_fallback = true;
} else if (m_glyph_ranges == ImGui::GetIO().Fonts->GetGlyphRangesThai()) {
font_name_regular = "Sarabun-Medium.ttf";
font_name_bold = "Sarabun-SemiBold.ttf";
needs_glyph_fallback = true;
}
default_font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/" + font_name_regular).c_str(), m_font_size, &cfg, ranges.Data);
if (default_font == nullptr) {
@@ -2828,11 +2847,12 @@ void ImGuiWrapper::init_font(bool compress)
}
}
if (m_glyph_ranges == ImGui::GetIO().Fonts->GetGlyphRangesThai()) {
// A merged font only supplies glyphs the font ahead of it lacks, so this fills the gaps
// without restyling anything the script font already covers.
if (needs_glyph_fallback) {
ImFontConfig fallback_cfg = cfg;
fallback_cfg.MergeMode = true;
static constexpr ImWchar celsius_range[] = { 0x2103, 0x2103, 0 };
io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/HarmonyOS_Sans_SC_Regular.ttf").c_str(), m_font_size, &fallback_cfg, celsius_range);
io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/HarmonyOS_Sans_SC_Regular.ttf").c_str(), m_font_size, &fallback_cfg, ranges.Data);
}
bold_font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/" + font_name_bold).c_str(), m_font_size, &cfg, ranges.Data);
@@ -2841,11 +2861,10 @@ void ImGuiWrapper::init_font(bool compress)
if (bold_font == nullptr) { throw Slic3r::RuntimeError("ImGui: Could not load deafult font"); }
}
if (m_glyph_ranges == ImGui::GetIO().Fonts->GetGlyphRangesThai()) {
if (needs_glyph_fallback) {
ImFontConfig fallback_cfg = cfg;
fallback_cfg.MergeMode = true;
static constexpr ImWchar celsius_range[] = { 0x2103, 0x2103, 0 };
io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/HarmonyOS_Sans_SC_Bold.ttf").c_str(), m_font_size, &fallback_cfg, celsius_range);
io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/HarmonyOS_Sans_SC_Bold.ttf").c_str(), m_font_size, &fallback_cfg, ranges.Data);
}
if (m_glyph_ranges == ImGui::GetIO().Fonts->GetGlyphRangesThai()) {
@@ -2897,13 +2916,18 @@ void ImGuiWrapper::init_font(bool compress)
glsafe(::glGetIntegerv(GL_MAX_TEXTURE_SIZE, &gl_max_tex_size));
constexpr int max_retries = 6;
for (int attempt = 0; attempt < max_retries && io.Fonts->TexHeight > gl_max_tex_size; ++attempt) {
io.Fonts->TexDesiredWidth = (io.Fonts->TexDesiredWidth > 0 ? io.Fonts->TexDesiredWidth : io.Fonts->TexWidth) * 2;
const int width = io.Fonts->TexDesiredWidth > 0 ? io.Fonts->TexDesiredWidth : io.Fonts->TexWidth;
// Both dimensions share the same limit, so widening past it would only trade an
// illegal height for an illegal width.
if (width * 2 > gl_max_tex_size)
break;
io.Fonts->TexDesiredWidth = width * 2;
io.Fonts->Build();
}
if (io.Fonts->TexHeight > gl_max_tex_size) {
// Shouldn't really happen
BOOST_LOG_TRIVIAL(error) << "Font atlas height " << io.Fonts->TexHeight
<< " still exceeds GL_MAX_TEXTURE_SIZE (" << gl_max_tex_size << ")"
// Needs both a very large glyph set and a small GL_MAX_TEXTURE_SIZE.
BOOST_LOG_TRIVIAL(error) << "Font atlas " << io.Fonts->TexWidth << "x" << io.Fonts->TexHeight
<< " does not fit GL_MAX_TEXTURE_SIZE (" << gl_max_tex_size << ")"
<< " after " << max_retries << " attempts; rendering may be incomplete";
}
+1 -1
View File
@@ -74,7 +74,7 @@ bool ImageDPIFrame::Show(bool show)
}
void ImageDPIFrame::set_bitmap(const wxBitmap &bit_map) {
if (&bit_map && bit_map.IsOk()) {
if (bit_map.IsOk()) {
m_bitmap->SetBitmap(bit_map);
}
}
+22 -37
View File
@@ -39,13 +39,14 @@ static std::map<int, std::string> error_messages = {
namespace Slic3r {
namespace GUI {
MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl2 *media_ctrl, const wxPoint &pos, const wxSize &size)
MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl3 *media_ctrl, const wxPoint &pos, const wxSize &size)
: wxPanel(parent, wxID_ANY, pos, size)
, m_media_ctrl(media_ctrl)
{
SetLabel("MediaPlayCtrl");
SetBackgroundColour(*wxWHITE);
m_media_ctrl->Bind(wxEVT_MEDIA_STATECHANGED, &MediaPlayCtrl::onStateChanged, this);
m_media_ctrl->SetIdleImage(from_u8(resources_dir() + "/images/live_stream_default.png"));
m_button_play = new Button(this, "", "media_play", wxBORDER_NONE);
m_button_play->SetCanFocus(false);
@@ -177,13 +178,6 @@ void MediaPlayCtrl::SetMachineObject(MachineObject* obj)
if (machine == m_machine) {
if (m_last_state == MEDIASTATE_IDLE && IsEnabled())
Play();
else if (m_last_state == MEDIASTATE_LOADING && m_tutk_state == "disable"
&& m_last_user_play + wxTimeSpan::Seconds(3) < wxDateTime::Now()) {
// resend ttcode to printer
if (auto agent = wxGetApp().getAgent())
agent->get_camera_url(machine, [](auto) {}, wxGetApp().get_printer_cloud_provider());
m_last_user_play = wxDateTime::Now();
}
return;
}
m_machine = machine;
@@ -313,7 +307,7 @@ void MediaPlayCtrl::Play()
// !m_lan_mode && !m_remote_proto && m_lan_proto == LVL_None (x)
if (m_lan_proto <= MachineObject::LVL_Disable && (m_lan_mode || !m_remote_proto)) {
Stop(m_lan_proto == MachineObject::LVL_None
Stop(m_lan_proto == MachineObject::LVL_None
? _L("A problem occurred. Please update the printer firmware and try again.")
: _L("LAN Only Liveview is off. Please turn on the liveview on printer screen."));
return;
@@ -351,7 +345,7 @@ void MediaPlayCtrl::Play()
url += "&cli_id=" + wxGetApp().app_config->get("slicer_uuid");
url += "&cli_ver=" + std::string(SLIC3R_VERSION);
}
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: " << hide_passwd(url,
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: " << hide_passwd(url,
{"?uid=", "authkey=", "passwd=", "license=", "token="});
CallAfter([this, m, url] {
if (m != m_machine) {
@@ -426,7 +420,7 @@ void MediaPlayCtrl::Stop(wxString const &msg, wxString const &msg2)
auto tunnel = m_url.empty() ? "" : into_u8(wxURI(m_url).GetPath()).substr(1);
if (auto n = tunnel.find_first_of("/_"); n != std::string::npos)
tunnel = tunnel.substr(0, n);
if (last_state != wxMEDIASTATE_PLAYING && m_failed_code != 0
if (last_state != wxMEDIASTATE_PLAYING && m_failed_code != 0
&& m_last_failed_codes.find(m_failed_code) == m_last_failed_codes.end()
&& (m_user_triggered || m_failed_retry > 3)) {
m_last_failed_codes.insert(m_failed_code);
@@ -560,7 +554,7 @@ void MediaPlayCtrl::ToggleStream()
url += "&cli_id=" + wxGetApp().app_config->get("slicer_uuid");
url += "&cli_ver=" + std::string(SLIC3R_VERSION);
}
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::ToggleStream: " << hide_passwd(url,
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::ToggleStream: " << hide_passwd(url,
{"?uid=", "authkey=", "passwd=", "license=", "token="});
CallAfter([this, m, url] {
if (m != m_machine) return;
@@ -580,8 +574,8 @@ void MediaPlayCtrl::ToggleStream()
}, wxGetApp().get_printer_cloud_provider());
}
void MediaPlayCtrl::msw_rescale() {
m_button_play->Rescale();
void MediaPlayCtrl::msw_rescale() {
m_button_play->Rescale();
}
void MediaPlayCtrl::jump_to_play()
@@ -715,7 +709,9 @@ void MediaPlayCtrl::media_proc()
break;
}
else if (url == "<play>") {
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start play";
m_media_ctrl->Play();
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: end play";
}
else {
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start load";
@@ -771,15 +767,15 @@ bool MediaPlayCtrl::start_stream_service(bool *need_install)
if (!boost::filesystem::exists(file_dll) || boost::filesystem::last_write_time(file_dll) != boost::filesystem::last_write_time(file_dll2))
boost::filesystem::copy_file(file_dll2, file_dll, boost::filesystem::copy_options::overwrite_existing);
}
boost::process::child process_source(file_source, file_url2.ToStdWstring(), boost::process::start_dir(tools_dir),
boost::process::windows::create_no_window,
boost::process::child process_source(file_source, file_url2.ToStdWstring(), boost::process::start_dir(tools_dir),
boost::process::windows::create_no_window,
boost::process::std_out > intermediate, boost::process::limit_handles);
boost::process::child process_ffmpeg(file_ffmpeg, configss, boost::process::windows::create_no_window,
boost::process::child process_ffmpeg(file_ffmpeg, configss, boost::process::windows::create_no_window,
boost::process::std_in < intermediate, boost::process::limit_handles);
#else
boost::filesystem::permissions(file_source, boost::filesystem::owner_exe | boost::filesystem::add_perms);
boost::filesystem::permissions(file_ffmpeg, boost::filesystem::owner_exe | boost::filesystem::add_perms);
boost::process::child process_source(file_source, file_url2.data().AsInternal(), boost::process::start_dir(start_dir),
boost::process::child process_source(file_source, file_url2.data().AsInternal(), boost::process::start_dir(start_dir),
boost::process::std_out > intermediate, boost::process::limit_handles);
boost::process::child process_ffmpeg(file_ffmpeg, configss, boost::process::std_in < intermediate, boost::process::limit_handles);
#endif
@@ -830,27 +826,16 @@ bool MediaPlayCtrl::get_stream_url(std::string *url)
}}
void wxMediaCtrl2::DoSetSize(int x, int y, int width, int height, int sizeFlags)
void wxMediaCtrl_OnSize(wxWindow * ctrl, wxSize const & videoSize, int width, int height)
{
#ifdef __WXMAC__
wxWindow::DoSetSize(x, y, width, height, sizeFlags);
#else
wxMediaCtrl::DoSetSize(x, y, width, height, sizeFlags);
#endif
#if defined(__LINUX__) && defined(__WXGTK__)
if (m_gtk_video_window) {
const wxSize client_size = GetClientSize();
m_gtk_video_window->SetSize(0, 0, client_size.GetWidth(), client_size.GetHeight());
}
#endif
if (sizeFlags & wxSIZE_USE_EXISTING) return;
wxSize size = m_video_size;
wxSize size = videoSize;
if (!size.IsFullySpecified()) size = {16, 9};
int maxHeight = (width * size.GetHeight() + size.GetHeight() - 1) / size.GetWidth();
if (maxHeight != GetMaxHeight()) {
// BOOST_LOG_TRIVIAL(info) << "wxMediaCtrl2::DoSetSize: width: " << width << ", height: " << height << ", maxHeight: " << maxHeight;
SetMaxSize({-1, maxHeight});
CallAfter([this] {
if (auto p = GetParent()) {
if (maxHeight != ctrl->GetMaxHeight()) {
// BOOST_LOG_TRIVIAL(info) << "wxMediaCtrl_OnSize: width: " << width << ", height: " << height << ", maxHeight: " << maxHeight;
ctrl->SetMaxSize({-1, maxHeight});
ctrl->CallAfter([ctrl] {
if (auto p = ctrl->GetParent()) {
p->Layout();
p->Refresh();
}
+4 -4
View File
@@ -8,7 +8,7 @@
#ifndef MediaPlayCtrl_h
#define MediaPlayCtrl_h
#include "wxMediaCtrl2.h"
#include "wxMediaCtrl3.h"
#include <wx/panel.h>
@@ -30,7 +30,7 @@ namespace GUI {
class MediaPlayCtrl : public wxPanel
{
public:
MediaPlayCtrl(wxWindow *parent, wxMediaCtrl2 *media_ctrl, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize);
MediaPlayCtrl(wxWindow *parent, wxMediaCtrl3 *media_ctrl, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize);
~MediaPlayCtrl();
@@ -75,7 +75,7 @@ private:
// token
std::shared_ptr<int> m_token = std::make_shared<int>(0);
wxMediaCtrl2 * m_media_ctrl;
wxMediaCtrl3 * m_media_ctrl;
wxMediaState m_last_state = MEDIASTATE_IDLE;
std::string m_machine;
int m_lan_proto = 0;
@@ -90,7 +90,7 @@ private:
bool m_device_busy = false;
bool m_disable_lan = false;
wxString m_url;
std::deque<wxString> m_tasks;
boost::mutex m_mutex;
boost::condition_variable m_cond;
-1
View File
@@ -34,7 +34,6 @@
#include "Widgets/AxisCtrlButton.hpp"
#include "Widgets/TextInput.hpp"
#include "Widgets/StaticLine.hpp"
#include "wxMediaCtrl2.h"
#include "MediaPlayCtrl.h"
///////////////////////////////////////////////////////////////////////////
+17 -8
View File
@@ -1468,12 +1468,12 @@ void ExtruderGroup::update_ams()
size_t left = 4;
size_t index = 0;
for (size_t i = i4; i < ams_n4 && left > 0; ++i, ++index, left -= 2) {
ams[index]->Update(i < ams_4.size() ? ams_4[i] : info4);
ams[index]->UpdateInfo(i < ams_4.size() ? ams_4[i] : info4);
ams[index]->Refresh();
ams[index]->Open();
}
for (size_t i = i1; i < ams_n1 && left > 0; ++i, ++index, --left) {
ams[index]->Update(i < ams_1.size() ? ams_1[i] : info1);
ams[index]->UpdateInfo(i < ams_1.size() ? ams_1[i] : info1);
ams[index]->Refresh();
ams[index]->Open();
}
@@ -3871,7 +3871,7 @@ bool Sidebar::reset_bed_type_combox_choices(bool is_sidebar_init)
}
}
m_last_combo_bedtype_count = p->combo_printer_bed->GetCount();
if (!is_sidebar_init && &p->plater->get_partplate_list()) {
if (!is_sidebar_init) {
p->plater->get_partplate_list().check_all_plate_local_bed_type(m_cur_combox_bed_types);
}
return true;
@@ -4915,7 +4915,10 @@ void Sidebar::edit_mixed_filament(size_t panel_idx)
if (multi_colour_opt && cfg_idx < multi_colour_opt->values.size())
multi_colour_opt->values[cfg_idx] = blended;
// The edited slot keeps its index, so nothing else refreshes the per-feature filament
// lists - and its blended colour and type are what they show for it.
update_mixed_filament_list();
update_dynamic_filament_list();
wxGetApp().plater()->update_project_dirty_from_presets();
wxPostEvent(this, SimpleEvent(EVT_SCHEDULE_BACKGROUND_PROCESS, this));
}
@@ -5287,8 +5290,11 @@ void Sidebar::on_filament_count_change(size_t num_filaments)
if (num_physical == choices.size()) {
// The ctor pre-creates one combo, so a single-filament project hits this guard before
// any layout pass has sized the scroll areas; refresh them here as well.
// Adding a mixed slot also lands here, since only the virtual count changed, so the
// per-feature filament lists - which do list mixed slots - have to be refreshed too.
recalc_filament_scroll_sizes();
update_mixed_filament_list();
update_dynamic_filament_list();
return;
}
@@ -5501,12 +5507,15 @@ void Sidebar::add_custom_filament(wxColour new_col, const std::string& preset_na
// Mixed-color slots are kept at the tail of the filament arrays, so a new physical
// filament has to be inserted just after the last physical one rather than appended.
// total == every slot (physical + mixed); insert_pos == the physical slot count.
size_t total = wxGetApp().preset_bundle->filament_presets.size();
size_t insert_pos = p->combos_filament.size();
// Count off filament_is_mixed, not filament_presets or the combos: the extruder-count spinner
// reaches this before the sidebar has rebuilt, and update_multi_material_filament_presets()
// can have grown filament_presets alone.
auto *bundle = wxGetApp().preset_bundle;
size_t insert_pos = bundle->num_physical_filaments();
size_t total = insert_pos + bundle->num_mixed_filaments();
int filament_count = (int)(total + 1);
std::string new_color = new_col.GetAsString(wxC2S_HTML_SYNTAX).ToStdString();
wxGetApp().preset_bundle->set_num_filaments(filament_count, new_color);
bundle->set_num_filaments(filament_count, new_color);
// Maintain physical-first ordering: rotate the new slot from end to insert_pos.
// No mixed slots -> insert_pos == total -> every rotate below is a no-op.
@@ -22077,7 +22086,7 @@ void Plater::show_object_info()
auto mesh_errors = p->sidebar->obj_list()->get_mesh_errors_info(&info_manifold, &non_manifold_edges);
if (non_manifold_edges > 0) {
info_manifold += into_u8("\n" + _L("Tips:") + "\n" + _L("Use \"Fix Model\" to repair the mesh."));
info_manifold += "\n" + _L("Tips:") + "\n" + _L("Use \"Fix Model\" to repair the mesh.");
}
info_manifold = "<Error>" + info_manifold + "</Error>";
+2 -2
View File
@@ -662,10 +662,10 @@ PrinterFileSystem::File const &PrinterFileSystem::GetFile(size_t index, bool &se
void PrinterFileSystem::Attached()
{
boost::unique_lock lock(m_mutex);
m_recv_thread = std::move(boost::thread([w = weak_from_this()] {
m_recv_thread = boost::thread([w = weak_from_this()] {
boost::shared_ptr<PrinterFileSystem> s = w.lock();
if (s) s->RecvMessageThread();
}));
});
}
void PrinterFileSystem::Start()
-657
View File
@@ -1,657 +0,0 @@
/* bambusrc for gstreamer
* integration with proprietary Bambu Lab blob for getting raw h.264 video
*
* Copyright (C) 2023 Joshua Wise <joshua@accelerated.tech>
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* Alternatively, the contents of this file may be used under the
* GNU Lesser General Public License Version 2.1 (the "LGPL"), in
* which case the following provisions apply instead of the ones
* mentioned above:
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <gst/gst.h>
#include "gstbambusrc.h"
#include <stdio.h>
#include <unistd.h>
#ifndef EXTERNAL_GST_PLUGIN
#define BAMBU_DYNAMIC
#endif
#include "BambuTunnel.h"
#ifdef BAMBU_DYNAMIC
// From PrinterFileSystem.
#ifdef __cplusplus
extern "C"
#else
extern
#endif
BambuLib *bambulib_get();
BambuLib *_lib = NULL;
#define BAMBULIB(x) (_lib->x)
#else
#define BAMBULIB(x) (x)
#endif
GST_DEBUG_CATEGORY_STATIC (gst_bambusrc_debug);
#define GST_CAT_DEFAULT gst_bambusrc_debug
static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS_ANY);
//GST_STATIC_CAPS("video/x-h264,framerate=0/1,parsed=(boolean)false,stream-format=(string)byte-stream"));
enum
{
PROP_0,
PROP_LOCATION,
};
static void gst_bambusrc_uri_handler_init (gpointer g_iface,
gpointer iface_data);
static void gst_bambusrc_finalize (GObject * gobject);
static void gst_bambusrc_dispose (GObject * gobject);
static void gst_bambusrc_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_bambusrc_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static GstStateChangeReturn gst_bambusrc_change_state (GstElement *
element, GstStateChange transition);
static GstFlowReturn gst_bambusrc_create (GstPushSrc * psrc,
GstBuffer ** outbuf);
static gboolean gst_bambusrc_start (GstBaseSrc * bsrc);
static gboolean gst_bambusrc_stop (GstBaseSrc * bsrc);
static gboolean gst_bambusrc_is_seekable (GstBaseSrc * bsrc);
static gboolean gst_bambusrc_query (GstBaseSrc * bsrc, GstQuery * query);
static gboolean gst_bambusrc_unlock (GstBaseSrc * bsrc);
static gboolean gst_bambusrc_unlock_stop (GstBaseSrc * bsrc);
static gboolean gst_bambusrc_set_location (GstBambuSrc * src,
const gchar * uri, GError ** error);
#define gst_bambusrc_parent_class parent_class
G_DEFINE_TYPE_WITH_CODE (GstBambuSrc, gst_bambusrc, GST_TYPE_PUSH_SRC,
G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER,
gst_bambusrc_uri_handler_init));
static void
gst_bambusrc_class_init (GstBambuSrcClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *gstelement_class;
GstBaseSrcClass *gstbasesrc_class;
GstPushSrcClass *gstpushsrc_class;
gobject_class = (GObjectClass *) klass;
gstelement_class = (GstElementClass *) klass;
gstbasesrc_class = (GstBaseSrcClass *) klass;
gstpushsrc_class = (GstPushSrcClass *) klass;
gobject_class->set_property = gst_bambusrc_set_property;
gobject_class->get_property = gst_bambusrc_get_property;
gobject_class->finalize = gst_bambusrc_finalize;
gobject_class->dispose = gst_bambusrc_dispose;
g_object_class_install_property (gobject_class,
PROP_LOCATION,
g_param_spec_string ("location", "Location",
"URI to pass to Bambu Lab blobs", "",
(GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
gst_element_class_add_static_pad_template (gstelement_class, &srctemplate);
gst_element_class_set_static_metadata (gstelement_class, "Bambu Lab source",
"Source/Network",
"Receive data as a client over the network using the proprietary Bambu Lab blobs",
"Joshua Wise <joshua@accelerated.tech>");
gstelement_class->change_state =
GST_DEBUG_FUNCPTR (gst_bambusrc_change_state);
gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_bambusrc_start);
gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_bambusrc_stop);
gstbasesrc_class->unlock = GST_DEBUG_FUNCPTR (gst_bambusrc_unlock);
gstbasesrc_class->unlock_stop =
GST_DEBUG_FUNCPTR (gst_bambusrc_unlock_stop);
gstbasesrc_class->is_seekable =
GST_DEBUG_FUNCPTR (gst_bambusrc_is_seekable);
gstbasesrc_class->query = GST_DEBUG_FUNCPTR (gst_bambusrc_query);
gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_bambusrc_create);
GST_DEBUG_CATEGORY_INIT (gst_bambusrc_debug, "bambusrc", 0,
"Bambu Lab src");
}
static void
gst_bambusrc_reset (GstBambuSrc * src)
{
gst_caps_replace (&src->src_caps, NULL);
if (src->tnl) {
BAMBULIB(Bambu_Close)(src->tnl);
BAMBULIB(Bambu_Destroy)(src->tnl);
src->tnl = NULL;
}
}
static void
gst_bambusrc_init (GstBambuSrc * src)
{
src->location = NULL;
src->tnl = NULL;
gst_base_src_set_automatic_eos (GST_BASE_SRC (src), FALSE);
gst_base_src_set_live(GST_BASE_SRC(src), TRUE);
gst_bambusrc_reset (src);
}
static void
gst_bambusrc_dispose (GObject * gobject)
{
GstBambuSrc *src = GST_BAMBUSRC (gobject);
GST_DEBUG_OBJECT (src, "dispose");
G_OBJECT_CLASS (parent_class)->dispose (gobject);
}
static void
gst_bambusrc_finalize (GObject * gobject)
{
GstBambuSrc *src = GST_BAMBUSRC (gobject);
GST_DEBUG_OBJECT (src, "finalize");
g_free (src->location);
if (src->tnl) {
BAMBULIB(Bambu_Close)(src->tnl);
BAMBULIB(Bambu_Destroy)(src->tnl);
}
G_OBJECT_CLASS (parent_class)->finalize (gobject);
}
static void
gst_bambusrc_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstBambuSrc *src = GST_BAMBUSRC (object);
switch (prop_id) {
case PROP_LOCATION:
{
const gchar *location;
location = g_value_get_string (value);
if (location == NULL) {
GST_WARNING ("location property cannot be NULL");
goto done;
}
if (!gst_bambusrc_set_location (src, location, NULL)) {
GST_WARNING ("badly formatted location");
goto done;
}
break;
}
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
done:
return;
}
static void
gst_bambusrc_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstBambuSrc *src = GST_BAMBUSRC (object);
switch (prop_id) {
case PROP_LOCATION:
g_value_set_string (value, src->location);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
int gst_bambu_last_error = 0;
static GstFlowReturn
gst_bambusrc_create (GstPushSrc * psrc, GstBuffer ** outbuf)
{
GstBambuSrc *src;
src = GST_BAMBUSRC (psrc);
(void) src;
GST_DEBUG_OBJECT (src, "create()");
int rv;
Bambu_Sample sample;
if (!src->tnl) {
return GST_FLOW_ERROR;
}
while ((rv = BAMBULIB(Bambu_ReadSample)(src->tnl, &sample)) == Bambu_would_block) {
GST_DEBUG_OBJECT(src, "create would block");
usleep(33333); /* 30Hz */
}
if (rv == Bambu_stream_end) {
return GST_FLOW_EOS;
}
if (rv != Bambu_success) {
gst_bambu_last_error = rv;
return GST_FLOW_ERROR;
}
#if GLIB_CHECK_VERSION(2,68,0)
gpointer sbuf = g_memdup2(sample.buffer, sample.size);
#else
gpointer sbuf = g_memdup(sample.buffer, sample.size);
#endif
*outbuf = gst_buffer_new_wrapped_full(0, sbuf, sample.size, 0, sample.size, sbuf, g_free);
/* Synthesize monotonic timestamps at the announced frame rate, anchored
* to the first frame's arrival time. The X1C's RTSPS server emits
* unreliable decode timestamps (wildly non-monotonic jumps, or sometimes
* none at all); forwarding them directly froze the pipeline after a few
* seconds. Pacing on a synthesized clock the same trick mpv uses when
* it reports "No video PTS! Making something up." gives smooth
* playback regardless of network jitter, and only drops late frames if
* the printer can't keep up. A snap-back resets the anchor if real
* arrival drifts more than two frame periods from the synthesized
* timeline (e.g. announced framerate was wrong).
*/
GstClock *clock = GST_ELEMENT_CLOCK(psrc);
GstClockTime base_time = gst_element_get_base_time((GstElement *)psrc);
GstClockTime running_now = GST_CLOCK_TIME_NONE;
if (clock) {
GstClockTime now = gst_clock_get_time(clock);
if (now != GST_CLOCK_TIME_NONE && now >= base_time)
running_now = now - base_time;
}
/* Adapt the period to actual inter-arrival time via EWMA. The announced
* frame_rate is unreliable on Bambu printers (X1C announces 30 but
* delivers ~28), so trusting it causes the synthesized timeline to drift
* relative to real time, which makes the sink consider frames late and
* skip pacing entirely. Measuring the real rate keeps PTS in step with
* arrival on average, so the sink can pace inside bursts while still
* tracking the printer's actual frame cadence.
*/
if (src->avg_period == 0) {
int fps = src->frame_rate > 0 ? src->frame_rate : 30;
src->avg_period = GST_SECOND / fps;
}
if (running_now != GST_CLOCK_TIME_NONE && src->last_arrival != 0) {
GstClockTimeDiff delta = GST_CLOCK_DIFF(src->last_arrival, running_now);
/* clamp to plausible video frame periods (5..200 ms) so a one-off
* burst-of-zero or long stall doesn't poison the average */
if (delta > 5 * GST_MSECOND && delta < 200 * GST_MSECOND) {
src->avg_period = (src->avg_period * 15 + (GstClockTime)delta) / 16;
}
}
src->last_arrival = (running_now != GST_CLOCK_TIME_NONE) ? running_now : src->last_arrival;
GstClockTime period = src->avg_period;
/* Lead time: schedule frames a few periods in the future of their
* arrival, so the sink has a small jitter buffer. Without this, frames
* arriving slightly later than expected land behind the running clock
* and the sink renders them immediately, producing visible stutter.
* 100ms is invisible for a live print-monitor view.
*/
const GstClockTime LEAD = 100 * GST_MSECOND;
if (!src->sttime) {
src->sttime = (running_now != GST_CLOCK_TIME_NONE) ? running_now + LEAD : LEAD;
src->frame_count = 0;
}
GstClockTime pts = src->sttime + src->frame_count * period;
/* Safety net: with the lead applied, expected drift is roughly -LEAD
* (pts sits LEAD ns ahead of running_now). Re-anchor only if the
* synthesized timeline diverges from that expectation by several frame
* periods, which indicates a real disturbance (printer paused, stream
* resumed, large fps change) rather than ordinary jitter.
*/
if (running_now != GST_CLOCK_TIME_NONE) {
GstClockTimeDiff drift = GST_CLOCK_DIFF(pts, running_now);
GstClockTimeDiff expected = -(GstClockTimeDiff)LEAD;
GstClockTimeDiff slack = (GstClockTimeDiff)(4 * period);
if (drift > expected + slack || drift < expected - slack) {
GST_DEBUG_OBJECT(src, "ts drift %" G_GINT64_FORMAT " ns; re-anchoring", drift);
src->sttime = running_now + LEAD;
src->frame_count = 0;
pts = src->sttime;
}
}
GST_BUFFER_PTS(*outbuf) = pts;
GST_BUFFER_DTS(*outbuf) = pts;
GST_BUFFER_DURATION(*outbuf) = period;
src->frame_count++;
GST_DEBUG_OBJECT(src,
"sttime:%lu, DTS:%lu, PTS: %lu~",
src->sttime, GST_BUFFER_DTS(*outbuf), GST_BUFFER_PTS(*outbuf));
return GST_FLOW_OK;
}
static void _log(void *ctx, int lvl, const char *msg) {
GstBambuSrc *src = (GstBambuSrc *) ctx;
GST_DEBUG_OBJECT(src, "bambu: %s", msg);
BAMBULIB(Bambu_FreeLogMsg)(msg);
}
static gboolean
gst_bambusrc_start (GstBaseSrc * bsrc)
{
GstBambuSrc *src = GST_BAMBUSRC (bsrc);
GST_DEBUG_OBJECT (src, "start(\"%s\")", src->location);
if (src->tnl) {
BAMBULIB(Bambu_Close)(src->tnl);
BAMBULIB(Bambu_Destroy)(src->tnl);
src->tnl = NULL;
}
#ifdef BAMBU_DYNAMIC
if (!_lib) {
_lib = bambulib_get();
if (!_lib->Bambu_Open) {
return FALSE;
}
}
#endif
if (BAMBULIB(Bambu_Create)(&src->tnl, src->location) != Bambu_success) {
return FALSE;
}
int rv = 0;
BAMBULIB(Bambu_SetLogger)(src->tnl, _log, (void *)src);
if ((rv = BAMBULIB(Bambu_Open)(src->tnl)) != Bambu_success) {
BAMBULIB(Bambu_Destroy)(src->tnl);
src->tnl = NULL;
gst_bambu_last_error = rv;
return FALSE;
}
int n = 0;
while ((rv = BAMBULIB(Bambu_StartStream)(src->tnl, 1 /* video */)) == Bambu_would_block) {
usleep(100000);
}
if (rv != Bambu_success) {
BAMBULIB(Bambu_Close)(src->tnl);
BAMBULIB(Bambu_Destroy)(src->tnl);
src->tnl = NULL;
gst_bambu_last_error = rv;
return FALSE;
}
src->video_type = AVC1;
n = BAMBULIB(Bambu_GetStreamCount)(src->tnl);
GST_INFO_OBJECT (src, "Bambu_GetStreamCount returned stream count=%d",n);
for (int i = 0; i < n; ++i) {
Bambu_StreamInfo info;
BAMBULIB(Bambu_GetStreamInfo)(src->tnl, i, &info);
GST_INFO_OBJECT (src, "stream %d type=%d, sub_type=%d", i, info.type, info.sub_type);
if (info.type == VIDE) {
src->video_type = info.sub_type;
src->frame_rate = info.format.video.frame_rate;
GST_INFO_OBJECT (src, " width %d height=%d, frame_rate=%d",
info.format.video.width, info.format.video.height, info.format.video.frame_rate);
}
}
src->sttime = 0;
src->frame_count = 0;
src->last_arrival = 0;
src->avg_period = 0;
return TRUE;
}
static gboolean
gst_bambusrc_stop (GstBaseSrc * bsrc)
{
GstBambuSrc *src;
src = GST_BAMBUSRC (bsrc);
GST_DEBUG_OBJECT (src, "stop()");
if (src->tnl) {
BAMBULIB(Bambu_Close)(src->tnl);
BAMBULIB(Bambu_Destroy)(src->tnl);
src->tnl = NULL;
}
return TRUE;
}
static GstStateChangeReturn
gst_bambusrc_change_state (GstElement * element, GstStateChange transition)
{
GstStateChangeReturn ret;
GstBambuSrc *src;
src = GST_BAMBUSRC (element);
(void) src;
switch (transition) {
case GST_STATE_CHANGE_READY_TO_NULL:
//gst_bambusrc_session_close (src);
break;
default:
break;
}
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
return ret;
}
/* Interrupt a blocking request. */
static gboolean
gst_bambusrc_unlock (GstBaseSrc * bsrc)
{
GstBambuSrc *src;
src = GST_BAMBUSRC (bsrc);
GST_DEBUG_OBJECT (src, "unlock()");
return TRUE;
}
/* Interrupt interrupt. */
static gboolean
gst_bambusrc_unlock_stop (GstBaseSrc * bsrc)
{
GstBambuSrc *src;
src = GST_BAMBUSRC (bsrc);
GST_DEBUG_OBJECT (src, "unlock_stop()");
return TRUE;
}
static gboolean
gst_bambusrc_is_seekable (GstBaseSrc * bsrc)
{
GstBambuSrc *src = GST_BAMBUSRC (bsrc);
(void) src;
return FALSE;
}
static gboolean
gst_bambusrc_query (GstBaseSrc * bsrc, GstQuery * query)
{
GstBambuSrc *src = GST_BAMBUSRC (bsrc);
gboolean ret;
GstSchedulingFlags flags;
gint minsize, maxsize, align;
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_URI:
gst_query_set_uri (query, src->location);
ret = TRUE;
break;
default:
ret = FALSE;
break;
}
if (!ret)
ret = GST_BASE_SRC_CLASS (parent_class)->query (bsrc, query);
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_SCHEDULING:
gst_query_parse_scheduling (query, &flags, &minsize, &maxsize, &align);
flags = (GstSchedulingFlags)((int)flags | (int)GST_SCHEDULING_FLAG_SEQUENTIAL);
gst_query_set_scheduling (query, flags, minsize, maxsize, align);
break;
default:
break;
}
return ret;
}
static gboolean
gst_bambusrc_set_location (GstBambuSrc * src, const gchar * uri,
GError ** error)
{
if (src->location) {
g_free (src->location);
src->location = NULL;
}
if (uri == NULL)
return FALSE;
src->location = g_strdup (uri);
return TRUE;
}
static GstURIType
gst_bambusrc_uri_get_type (GType type)
{
return GST_URI_SRC;
}
static const gchar *const *
gst_bambusrc_uri_get_protocols (GType type)
{
static const gchar *protocols[] = { "bambu", NULL };
return protocols;
}
static gchar *
gst_bambusrc_uri_get_uri (GstURIHandler * handler)
{
GstBambuSrc *src = GST_BAMBUSRC (handler);
/* FIXME: make thread-safe */
return g_strdup (src->location);
}
static gboolean
gst_bambusrc_uri_set_uri (GstURIHandler * handler, const gchar * uri,
GError ** error)
{
GstBambuSrc *src = GST_BAMBUSRC (handler);
return gst_bambusrc_set_location (src, uri, error);
}
static void
gst_bambusrc_uri_handler_init (gpointer g_iface, gpointer iface_data)
{
GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
iface->get_type = gst_bambusrc_uri_get_type;
iface->get_protocols = gst_bambusrc_uri_get_protocols;
iface->get_uri = gst_bambusrc_uri_get_uri;
iface->set_uri = gst_bambusrc_uri_set_uri;
}
static gboolean gstbambusrc_init(GstPlugin *plugin)
{
return gst_element_register(plugin, "bambusrc", GST_RANK_PRIMARY, GST_TYPE_BAMBUSRC);
}
#ifndef EXTERNAL_GST_PLUGIN
// for use inside of Bambu Slicer
void gstbambusrc_register()
{
static int did_register = 0;
if (did_register)
return;
did_register = 1;
gst_plugin_register_static(GST_VERSION_MAJOR, GST_VERSION_MINOR, "bambusrc", "Bambu Lab source", gstbambusrc_init, "0.0.1", "GPL", "BambuStudio", "BambuStudio", "https://github.com/bambulab/BambuStudio");
}
#else
#ifndef PACKAGE
#define PACKAGE "bambusrc"
#endif
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, bambusrc, "Bambu Lab source", gstbambusrc_init, "0.0.1", "GPL", "BambuStudio", "https://github.com/bambulab/BambuStudio")
#endif
-78
View File
@@ -1,78 +0,0 @@
/* bambusrc for gstreamer
* integration with proprietary Bambu Lab blob for getting raw h.264 video
*
* Copyright (C) 2023 Joshua Wise <joshua@accelerated.tech>
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* Alternatively, the contents of this file may be used under the
* GNU Lesser General Public License Version 2.1 (the "LGPL"), in
* which case the following provisions apply instead of the ones
* mentioned above:
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GST_BAMBUSRC_H__
#define __GST_BAMBUSRC_H__
#include <gst/gst.h>
#include <gst/base/gstpushsrc.h>
#include <glib.h>
G_BEGIN_DECLS
#define GST_TYPE_BAMBUSRC (gst_bambusrc_get_type())
G_DECLARE_FINAL_TYPE (GstBambuSrc, gst_bambusrc,
GST, BAMBUSRC, GstPushSrc)
typedef void *Bambu_Tunnel;
struct _GstBambuSrc
{
GstPushSrc element;
GstCaps *src_caps;
gchar *location;
Bambu_Tunnel tnl;
GstClockTime sttime;
int video_type;
int frame_rate;
guint64 frame_count;
GstClockTime last_arrival;
GstClockTime avg_period;
};
extern void gstbambusrc_register();
G_END_DECLS
#endif /* __GST_BAMBUSRC_H__ */
+8 -4
View File
@@ -681,7 +681,7 @@ SearchDialog::SearchDialog(OptionsSearcher *searcher, Preset::Type type, wxWindo
SearchDialog::~SearchDialog() {}
void SearchDialog::Popup(wxPoint position /*= wxDefaultPosition*/)
void SearchDialog::Popup(wxWindow *focus /*= nullptr*/)
{
/* const std::string& line = searcher->search_string();
search_line->SetValue(line.empty() ? default_string : from_u8(line));
@@ -696,17 +696,19 @@ void SearchDialog::Popup(wxPoint position /*= wxDefaultPosition*/)
search_line2->SetValue(wxString(""));
//const std::string &line = searcher->search_string();
//searcher->search(into_u8(line), true);
PopupWindow::Popup();
PopupWindow::Popup(focus);
search_line2->SetFocus();
update_list();
}
#ifdef __WXMSW__
void SearchDialog::MSWDismissUnfocusedPopup()
{
Dismiss();
OnDismiss();
}
#endif // __WXMSW__
void SearchDialog::OnDismiss() { }
@@ -926,7 +928,7 @@ SearchObjectDialog::SearchObjectDialog(GUI::ObjectList* object_list, wxWindow* p
SearchObjectDialog::~SearchObjectDialog() {}
void SearchObjectDialog::Popup(wxPoint position /*= wxDefaultPosition*/)
void SearchObjectDialog::Popup(wxWindow *focus /*= nullptr*/)
{
if (m_is_dismissing || this->IsShown()) {
return;
@@ -937,7 +939,7 @@ void SearchObjectDialog::Popup(wxPoint position /*= wxDefaultPosition*/)
// dropdown list, otherwise the text input won't be usable
m_object_list->SetFocus();
#endif
PopupWindow::Popup();
PopupWindow::Popup(focus);
search_line2->SetFocus();
m_object_list->assembly_plate_object_name();
@@ -945,11 +947,13 @@ void SearchObjectDialog::Popup(wxPoint position /*= wxDefaultPosition*/)
update_list();
}
#ifdef __WXMSW__
void SearchObjectDialog::MSWDismissUnfocusedPopup()
{
Dismiss();
OnDismiss();
}
#endif // __WXMSW__
void SearchObjectDialog::OnDismiss() {}
+12 -8
View File
@@ -216,10 +216,12 @@ public:
SearchDialog(OptionsSearcher *searcher, Preset::Type type, wxWindow *parent, TextInput *input, wxWindow *search_btn);
~SearchDialog();
void MSWDismissUnfocusedPopup();
void Popup(wxPoint position = wxDefaultPosition);
void OnDismiss();
void Dismiss();
#ifdef __WXMSW__
void MSWDismissUnfocusedPopup() override;
#endif // __WXMSW__
void Popup(wxWindow *focus = nullptr) override;
void OnDismiss() override;
void Dismiss() override;
void Die();
void msw_rescale();
@@ -260,10 +262,12 @@ public:
SearchObjectDialog(GUI::ObjectList* object_list, wxWindow* parent, TextInput* input);
~SearchObjectDialog();
void MSWDismissUnfocusedPopup();
void Popup(wxPoint position = wxDefaultPosition);
void OnDismiss();
void Dismiss();
#ifdef __WXMSW__
void MSWDismissUnfocusedPopup() override;
#endif // __WXMSW__
void Popup(wxWindow *focus = nullptr) override;
void OnDismiss() override;
void Dismiss() override;
void Die();
void OnInputText(wxCommandEvent& event);
+2 -2
View File
@@ -483,7 +483,7 @@ SelectMachineDialog::SelectMachineDialog(Plater *plater)
m_link_edit_nozzle->Bind(wxEVT_LEFT_DOWN, [this](auto &e) {
if (this && this->m_is_in_sending_mode) {
if (m_is_in_sending_mode) {
return;
}
@@ -2847,7 +2847,7 @@ void SelectMachineDialog::on_ok_btn(wxCommandEvent &event)
});
// STUDIO-9580
/* use warning color if there are warning and normal messages* /
/* use warning color if there are warning and normal messages*/
/* use indexes if there are several messages*/
/* add header and ending if there are several messages or has none block warnings*/
if (confirm_text.size() > 1 || !is_printing_block)
+9 -11
View File
@@ -978,18 +978,16 @@ void SendToPrinterDialog::on_ok(wxCommandEvent &event)
m_send_job->on_check_ip_address_fail([this, token = std::weak_ptr(m_token)](int result) {
CallAfter([token, this] {
if (token.expired()) { return; }
if (this) {
SendFailedConfirm sfcDlg;
auto res = sfcDlg.ShowModal();
m_status_bar->cancel();
SendFailedConfirm sfcDlg;
auto res = sfcDlg.ShowModal();
m_status_bar->cancel();
if (res == wxYES) {
wxQueueEvent(m_button_ensure, new wxCommandEvent(wxEVT_BUTTON));
} else if (res == wxAPPLY) {
wxCommandEvent *evt = new wxCommandEvent(EVT_CLEAR_IPADDRESS);
wxQueueEvent(this, evt);
wxGetApp().show_ip_address_enter_dialog();
}
if (res == wxYES) {
wxQueueEvent(m_button_ensure, new wxCommandEvent(wxEVT_BUTTON));
} else if (res == wxAPPLY) {
wxCommandEvent *evt = new wxCommandEvent(EVT_CLEAR_IPADDRESS);
wxQueueEvent(this, evt);
wxGetApp().show_ip_address_enter_dialog();
}
});
});
+37 -37
View File
@@ -983,7 +983,7 @@ void PrintingTaskPanel::paint(wxPaintEvent&)
dc.DrawBitmap(m_thumbnail_bmp_display, wxPoint(0, 0));
}
dc.SetFont(Label::Body_12);
if (m_plate_index >= 0) {
wxString plate_id_str = wxString::Format("%d", m_plate_index);
dc.DrawText(plate_id_str, wxPoint(4, 4));
@@ -1271,7 +1271,7 @@ void PrintingTaskPanel::set_plate_index(int plate_idx)
}
void PrintingTaskPanel::market_scoring_show()
{
{
m_score_staticline->Show();
m_score_subtask_info->Show();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " show market scoring page";
@@ -1402,7 +1402,7 @@ void StatusBasePanel::init_bitmaps()
m_bitmap_fan_off = ScalableBitmap(this, "monitor_fan_off", 22);
m_bitmap_speed = ScalableBitmap(this, "monitor_speed", 24);
m_bitmap_speed_active = ScalableBitmap(this, "monitor_speed_active", 24);
m_thumbnail_brokenimg = ScalableBitmap(this, "monitor_brokenimg", 120);
m_thumbnail_sdcard = ScalableBitmap(this, "monitor_sdcard_thumbnail", 120);
//m_bitmap_camera = create_scaled_bitmap("monitor_camera", nullptr, 18);
@@ -1530,7 +1530,7 @@ wxBoxSizer *StatusBasePanel::create_monitoring_page()
// media_ctrl_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize);
// media_ctrl_panel->SetBackgroundColour(*wxBLACK);
// wxBoxSizer *bSizer_monitoring = new wxBoxSizer(wxVERTICAL);
m_media_ctrl = new wxMediaCtrl2(this);
m_media_ctrl = new wxMediaCtrl3(this);
m_media_ctrl->SetMinSize(wxSize(PAGE_MIN_WIDTH, FromDIP(288)));
m_custom_camera_view = WebView::CreateWebView(this, wxEmptyString);
@@ -2458,7 +2458,7 @@ StatusPanel::StatusPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, co
m_project_task_panel->get_pause_resume_button()->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_pause_resume), NULL, this);
m_project_task_panel->get_abort_button()->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_abort), NULL, this);
m_project_task_panel->get_market_scoring_button()->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_scoring), NULL, this);
m_project_task_panel->get_market_retry_buttom()->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_retry), NULL, this);
m_project_task_panel->get_market_retry_buttom()->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_retry), NULL, this);
m_project_task_panel->get_clean_button()->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_print_error_clean), NULL, this);
m_setting_button->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(StatusPanel::on_camera_enter), NULL, this);
@@ -2520,7 +2520,7 @@ StatusPanel::~StatusPanel()
m_project_task_panel->get_pause_resume_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_pause_resume), NULL, this);
m_project_task_panel->get_abort_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_abort), NULL, this);
m_project_task_panel->get_market_scoring_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_scoring), NULL, this);
m_project_task_panel->get_market_retry_buttom()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_retry), NULL, this);
m_project_task_panel->get_market_retry_buttom()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_retry), NULL, this);
m_project_task_panel->get_clean_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_print_error_clean), NULL, this);
m_setting_button->Disconnect(wxEVT_LEFT_DOWN, wxMouseEventHandler(StatusPanel::on_camera_enter), NULL, this);
@@ -2566,7 +2566,7 @@ StatusPanel::~StatusPanel()
if (sdcard_hint_dlg != nullptr)
delete sdcard_hint_dlg;
if (m_score_data != nullptr) {
if (m_score_data != nullptr) {
delete m_score_data;
}
}
@@ -2590,7 +2590,7 @@ void StatusPanel::init_scaled_buttons()
m_bpButton_e_down_10->SetCornerRadius(FromDIP(12));
}
void StatusPanel::on_market_scoring(wxCommandEvent &event) {
void StatusPanel::on_market_scoring(wxCommandEvent &event) {
if (obj && obj->is_makeworld_subtask() && obj->rating_info && obj->rating_info->request_successful) { // model is mall model and has rating_id
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_market_scoring" ;
if (m_score_data && m_score_data->rating_id == obj->rating_info->rating_id) { // current score data for model is same as mall model
@@ -2599,7 +2599,7 @@ void StatusPanel::on_market_scoring(wxCommandEvent &event) {
int ret = m_score_dlg.ShowModal();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": old data";
if (ret == wxID_OK) {
if (ret == wxID_OK) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": old data is upload";
m_score_data->rating_id = -1;
m_project_task_panel->set_star_count_dirty(false);
@@ -2621,11 +2621,11 @@ void StatusPanel::on_market_scoring(wxCommandEvent &event) {
std::string comment = obj->rating_info->content;
if (!comment.empty()) { m_score_dlg.set_comment(comment); }
std::vector<std::string> images_json_array;
images_json_array = obj->rating_info->image_url_paths;
if (!images_json_array.empty()) m_score_dlg.set_cloud_bitmap(images_json_array);
int ret = m_score_dlg.ShowModal();
if (ret == wxID_OK) {
@@ -3651,14 +3651,14 @@ void StatusPanel::update_basic_print_data(bool def)
void StatusPanel::update_model_info()
{
auto get_subtask_fn = [this](BBLModelTask* subtask) {
CallAfter([this, subtask]() {
CallAfter([this, subtask]() {
if (obj && obj->subtask_id_ == subtask->task_id) {
obj->set_modeltask(subtask);
}
});
};
if (wxGetApp().getAgent() && obj) {
BBLSubTask* curr_task = obj->get_subtask();
if (curr_task) {
@@ -3982,7 +3982,7 @@ void StatusPanel::reset_printing_values()
m_project_task_panel->update_left_time(NA_STR);
m_project_task_panel->update_layers_num(true, wxString::Format(_L("Layer: %s"), NA_STR));
update_calib_bitmap();
task_thumbnail_state = ThumbnailState::PLACE_HOLDER;
m_start_loading_thumbnail = false;
m_load_sdcard_thumbnail = false;
@@ -4037,7 +4037,7 @@ bool StatusPanel::check_axis_z_at_home(MachineObject* obj)
}
void StatusPanel::on_axis_ctrl_z_up_10(wxCommandEvent &event)
{
{
if (obj) {
obj->command_axis_control("Z", 1.0, -10.0f, 900);
if (!check_axis_z_at_home(obj))
@@ -5396,7 +5396,7 @@ void StatusPanel::msw_rescale()
m_calibration_btn->Rescale();
m_options_btn->SetMinSize(wxSize(-1, FromDIP(26)));
m_options_btn->Rescale();
m_options_btn->Rescale();
m_safety_btn->SetMinSize(wxSize(-1, FromDIP(26)));
m_safety_btn->Rescale();
@@ -5578,11 +5578,11 @@ ScoreDialog::ScoreDialog(wxWindow *parent, ScoreData *score_data)
, m_upload_status_code(StatusCode::CODE_NUMBER)
{
m_tocken.reset(new int(0));
wxBoxSizer *m_main_sizer = get_main_sizer(score_data->local_to_url_image, score_data->comment_text);
m_image_url_paths = score_data->image_url_paths;
this->SetSizer(m_main_sizer);
Fit();
@@ -5598,16 +5598,16 @@ void ScoreDialog::on_dpi_changed(const wxRect &suggested_rect) {}
void ScoreDialog::OnBitmapClicked(wxMouseEvent &event)
{
wxStaticBitmap *clickedBitmap = dynamic_cast<wxStaticBitmap *>(event.GetEventObject());
if (m_image.find(clickedBitmap) != m_image.end()) {
if (m_image.find(clickedBitmap) != m_image.end()) {
if (!m_image[clickedBitmap].is_selected) {
for (auto panel : m_image[clickedBitmap].image_broad) {
for (auto panel : m_image[clickedBitmap].image_broad) {
panel->Show();
}
m_image[clickedBitmap].is_selected = true;
m_selected_image_list.insert(clickedBitmap);
} else {
for (auto panel : m_image[clickedBitmap].image_broad) {
panel->Hide();
for (auto panel : m_image[clickedBitmap].image_broad) {
panel->Hide();
}
m_image[clickedBitmap].is_selected = false;
m_selected_image_list.erase(clickedBitmap);
@@ -5624,9 +5624,9 @@ void ScoreDialog::OnBitmapClicked(wxMouseEvent &event)
}
std::set <std::pair<wxStaticBitmap * ,wxString>> ScoreDialog::add_need_upload_imgs()
{
{
std::set<std::pair<wxStaticBitmap *, wxString>> need_upload_images;
for (auto bitmap : m_image) {
for (auto bitmap : m_image) {
if (!bitmap.second.is_uploaded) {
wxString &local_image_path = bitmap.second.local_image_url;
if (!local_image_path.empty()) { need_upload_images.insert(std::make_pair(bitmap.first, local_image_path)); }
@@ -5646,7 +5646,7 @@ std::pair<wxStaticBitmap *, ScoreDialog::ImageMsg> ScoreDialog::create_local_thu
cur_image_msg.local_image_url = local_path;
cur_image_msg.img_url_paths = "";
cur_image_msg.is_uploaded = false;
wxStaticBitmap *imageCtrl = new wxStaticBitmap(this, wxID_ANY, wxBitmap(wxImage(local_path, wxBITMAP_TYPE_ANY).Rescale(FromDIP(80), FromDIP(60))), wxDefaultPosition,
wxDefaultSize, 0);
imageCtrl->Bind(wxEVT_LEFT_DOWN, &ScoreDialog::OnBitmapClicked, this);
@@ -5711,7 +5711,7 @@ void ScoreDialog::update_static_bitmap(wxStaticBitmap* static_bitmap, wxImage im
}
wxBoxSizer *ScoreDialog::create_broad_sizer(wxStaticBitmap *bitmap, ImageMsg& cur_image_msg)
{
{
// tb: top and bottom lr: left and right
auto m_image_tb_broad = new wxBoxSizer(wxVERTICAL);
auto line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL);
@@ -5755,7 +5755,7 @@ void ScoreDialog::init() {
fail_image = wxImage(Slic3r::resources_dir() + "/images/oss_picture_load_failed.png", wxBITMAP_TYPE_ANY);
}
wxBoxSizer *ScoreDialog::get_score_sizer() {
wxBoxSizer *ScoreDialog::get_score_sizer() {
wxBoxSizer *score_sizer = new wxBoxSizer(wxHORIZONTAL);
wxStaticText *static_score_text = new wxStaticText(this, wxID_ANY, _L("Rate"), wxDefaultPosition, wxDefaultSize, 0);
static_score_text->Wrap(-1);
@@ -5878,18 +5878,18 @@ wxBoxSizer *ScoreDialog::get_photo_btn_sizer() {
for (int i = 0; i < filePaths.GetCount(); i++) { //It's ugly, but useful
bool is_repeat = false;
for (auto image : m_image) {
if (filePaths[i] == image.second.local_image_url) {
if (filePaths[i] == image.second.local_image_url) {
is_repeat = true;
continue;
}
}
if (!is_repeat) {
local_path.push_back(std::make_pair(filePaths[i], ""));
if (local_path.size() + m_image.size() > m_photo_nums) {
break;
if (local_path.size() + m_image.size() > m_photo_nums) {
break;
}
}
}
load_photo(local_path);
@@ -6007,7 +6007,7 @@ wxBoxSizer *ScoreDialog::get_button_sizer()
}
}
progress_dialog->Hide();
if (progress_dialog) {
if (progress_dialog) {
delete progress_dialog;
progress_dialog = nullptr;
}
@@ -6141,7 +6141,7 @@ wxBoxSizer *ScoreDialog::get_main_sizer(const std::vector<std::pair<wxString, st
m_main_sizer->Add(m_photo_sizer, 0, wxEXPAND | wxTOP, FromDIP(8));
m_image_sizer = new wxGridSizer(5, FromDIP(5), FromDIP(5));
if (!images.empty()) {
if (!images.empty()) {
load_photo(images);
}
m_main_sizer->Add(m_image_sizer, 0, wxEXPAND | wxLEFT, FromDIP(24));
@@ -6153,7 +6153,7 @@ wxBoxSizer *ScoreDialog::get_main_sizer(const std::vector<std::pair<wxString, st
return m_main_sizer;
}
ScoreData ScoreDialog::get_score_data() {
ScoreData ScoreDialog::get_score_data() {
ScoreData score_data;
score_data.rating_id = m_rating_id;
score_data.design_id = m_design_id;
@@ -6164,20 +6164,20 @@ ScoreData ScoreDialog::get_score_data() {
score_data.comment_text = m_comment_text->GetValue();
score_data.image_url_paths = m_image_url_paths;
for (auto img : m_image) { score_data.local_to_url_image.push_back(std::make_pair(img.second.local_image_url, img.second.img_url_paths)); }
return score_data;
}
void ScoreDialog::set_comment(std::string comment)
{
if (m_comment_text) {
if (m_comment_text) {
m_comment_text->SetValue(wxString::FromUTF8(comment));
}
}
void ScoreDialog::set_cloud_bitmap(std::vector<std::string> cloud_bitmaps)
{
{
m_image_url_paths = cloud_bitmaps;
for (std::string &url : cloud_bitmaps) {
if (std::string::npos == url.find(m_model_id)) continue;
+10 -11
View File
@@ -14,7 +14,6 @@
#include <wx/sizer.h>
#include <wx/gbsizer.h>
#include <wx/webrequest.h>
#include "wxMediaCtrl2.h"
#include "MediaPlayCtrl.h"
#include "AMSSetting.hpp"
#include "Calibration.hpp"
@@ -195,11 +194,11 @@ public:
void set_cloud_bitmap(std::vector<std::string> cloud_bitmaps);
protected:
enum StatusCode {
UPLOAD_PROGRESS = 0,
UPLOAD_EXIST_ISSUE,
enum StatusCode {
UPLOAD_PROGRESS = 0,
UPLOAD_EXIST_ISSUE,
UPLOAD_IMG_FAILED,
CODE_NUMBER
CODE_NUMBER
};
std::shared_ptr<int> m_tocken;
@@ -217,7 +216,7 @@ protected:
{
wxString local_image_url; //local image path
std::string img_url_paths; // oss url path
vector<wxPanel *> image_broad;
vector<wxPanel *> image_broad;
bool is_selected;
bool is_uploaded; // load
wxBoxSizer * image_tb_broad = nullptr;
@@ -252,7 +251,7 @@ protected:
std::set<std::pair<wxStaticBitmap *, wxString>> add_need_upload_imgs();
std::pair<wxStaticBitmap *, ImageMsg> create_local_thumbnail(wxString &local_path);
std::pair<wxStaticBitmap *, ImageMsg> create_oss_thumbnail(std::string &oss_path);
};
class PrintingTaskPanel : public wxPanel
@@ -261,7 +260,7 @@ public:
PrintingTaskPanel(wxWindow* parent, PrintingTaskType type);
~PrintingTaskPanel();
void create_panel(wxWindow* parent);
private:
MachineObject* m_obj{nullptr};
@@ -353,7 +352,7 @@ public:
void set_plate_index(int plate_idx = -1);
void market_scoring_show();
void market_scoring_hide();
public:
ScalableButton* get_abort_button() {return m_button_abort;};
ScalableButton* get_pause_resume_button() {return m_button_pause_resume;};
@@ -443,7 +442,7 @@ protected:
wxStaticBitmap* m_camera_switch_button;
wxMediaCtrl2 * m_media_ctrl;
wxMediaCtrl3 * m_media_ctrl;
MediaPlayCtrl * m_media_play_ctrl;
Label * m_staticText_printing;
@@ -567,7 +566,7 @@ protected:
virtual void on_bed_temp_kill_focus(wxFocusEvent &event) { event.Skip(); }
virtual void on_bed_temp_set_focus(wxFocusEvent &event) { event.Skip(); }
virtual void on_nozzle_temp_kill_focus(wxFocusEvent &event) { event.Skip(); }
virtual void on_nozzle_temp_set_focus(wxFocusEvent &event) { event.Skip(); }
virtual void on_nozzle_temp_set_focus(wxFocusEvent &event) { event.Skip(); }
virtual void on_nozzle_fan_switch(wxCommandEvent &event) { event.Skip(); }
virtual void on_printing_fan_switch(wxCommandEvent &event) { event.Skip(); }
virtual void on_axis_ctrl_z_up_10(wxCommandEvent &event) { event.Skip(); }
+19 -15
View File
@@ -2182,21 +2182,25 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
//Orca: sync filament num if it's a multi tool printer
if (opt_key == "extruders_count" && !m_config->opt_bool("single_extruder_multi_material")){
auto num_extruder = boost::any_cast<size_t>(value);
int old_filament_size = wxGetApp().preset_bundle->filament_presets.size();
std::vector<std::string> new_colors;
for (int i = old_filament_size; i < num_extruder; ++i) {
wxColour new_col = Plater::get_next_color_for_filament();
std::string new_color = new_col.GetAsString(wxC2S_HTML_SYNTAX).ToStdString();
new_colors.push_back(new_color);
const size_t num_extruder = boost::any_cast<size_t>(value);
auto *bundle = wxGetApp().preset_bundle;
Sidebar &sidebar = wxGetApp().plater()->sidebar();
// A tool changer feeds filament N from nozzle N, so the extruder count sizes the physical
// run only; mixed slots are virtual and keep the tail. Go one slot at a time through the
// sidebar's own +/- calls: they insert ahead of the mixed tail and renumber filament ids,
// painted facets, custom g-code and mixed components, which a bulk resize clamps away.
// Both also refresh the print tab and export the selections, so nothing to do afterwards.
size_t physical = bundle->num_physical_filaments();
while (physical != num_extruder) {
if (physical < num_extruder)
sidebar.add_custom_filament(Plater::get_next_color_for_filament());
else
sidebar.delete_filament(physical - 1); // physical > num_extruder >= 1
const size_t updated = bundle->num_physical_filaments();
if (updated == physical)
break; // the call declined, e.g. the total slot limit - do not spin
physical = updated;
}
// Mixed-color slots are virtual filaments at the tail of the list with no nozzle of their
// own, so they are carried on top of the new extruder count instead of being truncated.
const size_t total_filaments = num_extruder + wxGetApp().preset_bundle->num_mixed_filaments();
wxGetApp().preset_bundle->set_num_filaments(total_filaments, new_colors);
wxGetApp().plater()->on_filament_count_change(total_filaments);
wxGetApp().get_tab(Preset::TYPE_PRINT)->update();
wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config);
}
//Orca: disable purge_in_prime_tower if single_extruder_multi_material is disabled
@@ -7747,7 +7751,7 @@ void Tab::delete_preset()
for (auto &preset2 : *m_presets)
if (preset2.inherits() == current_preset.name) {
++count;
presets += "\n - " + preset2.name;
presets += "\n - " + from_u8(preset2.name);
}
if (count > 0) {
msg = _L("Presets inherited by other presets cannot be deleted!");
-3
View File
@@ -36,7 +36,6 @@ public:
TabButton* pageButton;
private:
wxWindow* m_parent;
wxFlexGridSizer* m_buttons_sizer;
wxBoxSizer* m_sizer;
ScalableBitmap m_arrow_img;
@@ -400,8 +399,6 @@ private:
unsigned m_showTimeout,
m_hideTimeout;
TabButtonsListCtrl *m_ctrl{nullptr};
};
//#endif // _WIN32
#endif // slic3r_Tabbook_hpp_
+3 -3
View File
@@ -984,7 +984,7 @@ void AMSControl::UpdateAms(const std::string &series_name,
if (cans->get_ams_id() == std::to_string(VIRTUAL_TRAY_MAIN_ID) || cans->get_ams_id() == std::to_string(VIRTUAL_TRAY_DEPUTY_ID)) {
for (auto ifo : m_ext_info) {
if (ifo.ams_id == ams_id) {
cans->Update(ifo);
cans->UpdateInfo(ifo);
cans->show_sn_value(m_ams_model == AMSModel::AMS_LITE ? false : true);
}
}
@@ -992,7 +992,7 @@ void AMSControl::UpdateAms(const std::string &series_name,
else{
for (auto ifo : m_ams_info) {
if (ifo.ams_id == ams_id) {
cans->Update(ifo);
cans->UpdateInfo(ifo);
cans->show_sn_value(m_ams_model == AMSModel::AMS_LITE ? false : true);
}
}
@@ -1015,7 +1015,7 @@ void AMSControl::UpdateAms(const std::string &series_name,
std::string id = ams_prv.second->get_ams_id();
auto item = m_ams_item_list.find(id);
if (item != m_ams_item_list.end())
{ ams_prv.second->Update(item->second->get_ams_info());
{ ams_prv.second->UpdateInfo(item->second->get_ams_info());
}
}
}
+15 -23
View File
@@ -325,7 +325,7 @@ AMSrefresh::AMSrefresh(wxWindow *parent, std::string ams_id, wxString can_id, Ca
m_can_id = can_id.ToStdString();
create(parent, wxID_ANY, pos, size);
Update(ams_id, info);
UpdateInfo(ams_id, info);
}
AMSrefresh::AMSrefresh(wxWindow *parent, std::string ams_id, int can_id, Caninfo info, const wxPoint &pos, const wxSize &size) : AMSrefresh()
@@ -333,7 +333,7 @@ AMSrefresh::AMSrefresh(wxWindow *parent, std::string ams_id, int can_id, Caninfo
m_can_id = wxString::Format("%d", can_id).ToStdString();
create(parent, wxID_ANY, pos, size);
Update(ams_id, info);
UpdateInfo(ams_id, info);
}
AMSrefresh::~AMSrefresh()
@@ -482,7 +482,7 @@ void AMSrefresh::paintEvent(wxPaintEvent &evt)
dc.DrawText(m_refresh_id, pot);
}
void AMSrefresh::Update(std::string ams_id, Caninfo info)
void AMSrefresh::UpdateInfo(std::string ams_id, Caninfo info)
{
if (m_ams_id == ams_id && m_info == info)
{
@@ -945,7 +945,7 @@ AMSLib::AMSLib(wxWindow *parent, std::string ams_idx, Caninfo info, AMSModelOrig
Bind(wxEVT_LEAVE_WINDOW, &AMSLib::on_leave_window, this);
Bind(wxEVT_LEFT_DOWN, &AMSLib::on_left_down, this);
Update(info, ams_idx, false);
UpdateInfo(info, ams_idx, false);
}
AMSLib::~AMSLib()
@@ -1730,7 +1730,7 @@ void AMSLib::on_pass_road(bool pass)
}
}
void AMSLib::Update(Caninfo info, std::string ams_idx, bool refresh)
void AMSLib::UpdateInfo(Caninfo info, std::string ams_idx, bool refresh)
{
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
if (!dev) return;
@@ -1868,7 +1868,7 @@ AMSRoad::AMSRoad(wxWindow *parent, wxWindowID id, Caninfo info, int canindex, in
void AMSRoad::create(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size) { wxWindow::Create(parent, id, pos, size); }
void AMSRoad::Update(AMSinfo amsinfo, Caninfo info, int canindex, int maxcan)
void AMSRoad::UpdateInfo(AMSinfo amsinfo, Caninfo info, int canindex, int maxcan)
{
m_amsinfo = amsinfo;
m_info = info;
@@ -2083,9 +2083,6 @@ void AMSRoad::OnPassRoad(std::vector<AMSPassRoadMode> prord_list)
}
}
/*
/*************************************************
Description:AMSRoadUpPart
**************************************************/
@@ -2124,7 +2121,7 @@ void AMSRoadUpPart::create(wxWindow* parent, wxWindowID id, const wxPoint& pos,
Refresh();
}
void AMSRoadUpPart::Update(AMSinfo amsinfo)
void AMSRoadUpPart::UpdateInfo(AMSinfo amsinfo)
{
if (m_amsinfo != amsinfo)
{
@@ -2616,7 +2613,7 @@ void AMSPreview::Close()
Hide();
}
void AMSPreview::Update(AMSinfo amsinfo)
void AMSPreview::UpdateInfo(AMSinfo amsinfo)
{
if (m_amsinfo == amsinfo)
{
@@ -2954,7 +2951,7 @@ AMSHumidity::AMSHumidity(wxWindow* parent, wxWindowID id, AMSinfo info, const wx
}
});
Update(info);
UpdateInfo(info);
}
void AMSHumidity::create(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size) {
@@ -2963,7 +2960,7 @@ void AMSHumidity::create(wxWindow* parent, wxWindowID id, const wxPoint& pos, co
}
void AMSHumidity::Update(AMSinfo amsinfo)
void AMSHumidity::UpdateInfo(AMSinfo amsinfo)
{
if (m_amsinfo != amsinfo)
{
@@ -3380,7 +3377,7 @@ void AmsItem::AddLiteCan(Caninfo caninfo, int canindex, wxGridSizer* sizer)
//m_can_road_list[caninfo.can_id] = m_panel_road;
}
void AmsItem::Update(AMSinfo info)
void AmsItem::UpdateInfo(AMSinfo info)
{
if (m_info == info)
{
@@ -3392,7 +3389,7 @@ void AmsItem::Update(AMSinfo info)
if (m_humidity)
{
m_humidity->Update(m_info);
m_humidity->UpdateInfo(m_info);
}
for (int i = 0; i < m_can_count; i++) {
@@ -3401,7 +3398,7 @@ void AmsItem::Update(AMSinfo info)
auto refresh = it->second;
if (refresh != nullptr){
refresh->Update(info.ams_id, info.cans[i]);
refresh->UpdateInfo(info.ams_id, info.cans[i]);
refresh->Show();
}
}
@@ -3410,7 +3407,7 @@ void AmsItem::Update(AMSinfo info)
AMSLib* lib = m_can_lib_list[std::to_string(i)];
if (lib != nullptr){
if (i < m_can_count){
lib->Update(info.cans[i], info.ams_id);
lib->UpdateInfo(info.cans[i], info.ams_id);
lib->Show();
}
else{
@@ -3419,12 +3416,7 @@ void AmsItem::Update(AMSinfo info)
}
}
if (m_panel_road != nullptr){
m_panel_road->Update(m_info);
}
if (true || m_ams_model == AMSModel::GENERIC_AMS) {
/*m_panel_road->Update(m_info, info.cans[0]);
m_panel_road->Show();*/
m_panel_road->UpdateInfo(m_info);
}
Layout();
+7 -7
View File
@@ -312,7 +312,7 @@ public:
~AMSrefresh();
public:
void Update(std::string ams_id, Caninfo info);
void UpdateInfo(std::string ams_id, Caninfo info);
std::string GetCanId() const { return m_info.can_id; };
@@ -492,7 +492,7 @@ public:
AMSModel m_ams_model;
AMSModelOriginType m_ext_type = { AMSModelOriginType::GENERIC_EXT };
void Update(Caninfo info, std::string ams_idx, bool refresh = true);
void UpdateInfo(Caninfo info, std::string ams_idx, bool refresh = true);
void UnableSelected() { m_unable_selected = true; };
void EableSelected() { m_unable_selected = false; };
void OnSelected();
@@ -581,7 +581,7 @@ public:
double m_radius = {4};
wxColour m_road_def_color;
wxColour m_road_color;
void Update(AMSinfo amsinfo, Caninfo info, int canindex, int maxcan);
void UpdateInfo(AMSinfo amsinfo, Caninfo info, int canindex, int maxcan);
std::vector<ScalableBitmap> ams_humidity_img;
@@ -614,7 +614,7 @@ public:
void create(wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize);
public:
void Update(AMSinfo amsinfo);
void UpdateInfo(AMSinfo amsinfo);
void OnVamsLoading(bool load, wxColour col = AMS_CONTROL_GRAY500);
void SetPassRoadColour(wxColour col);
@@ -715,7 +715,7 @@ public:
void Open();
void Close();
void Update(AMSinfo amsinfo);
void UpdateInfo(AMSinfo amsinfo);
void create(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size);
void OnEnterWindow(wxMouseEvent &evt);
void OnLeaveWindow(wxMouseEvent &evt);
@@ -768,7 +768,7 @@ public:
int m_canindex = { 0 };
bool m_selected = { false };
double m_radius = { 12 };
void Update(AMSinfo amsinfo);
void UpdateInfo(AMSinfo amsinfo);
std::vector<ScalableBitmap> ams_humidity_imgs;
std::vector<ScalableBitmap> ams_humidity_dark_imgs;
@@ -801,7 +801,7 @@ public:
AmsItem(wxWindow *parent, AMSinfo info, AMSModel model, AMSPanelPos pos);
~AmsItem();
void Update(AMSinfo info);
void UpdateInfo(AMSinfo info);
void create(wxWindow *parent);
void AddCan(Caninfo caninfo, int canindex, int maxcan, wxBoxSizer* sizer);
void AddLiteCan(Caninfo caninfo, int canindex, wxGridSizer* sizer);
+1 -1
View File
@@ -124,7 +124,7 @@ void AxisCtrlButton::SetInnerBackgroundColor(StateColor const& color)
void AxisCtrlButton::SetBitmap(ScalableBitmap &bmp)
{
if (&bmp && (& bmp.bmp()) && (bmp.bmp().IsOk())) {
if (bmp.bmp().IsOk()) {
m_icon = bmp;
}
}
+4 -4
View File
@@ -208,7 +208,7 @@ bool ComboBox::SetFont(wxFont const& font)
int ComboBox::Append(const wxString &item, const wxBitmap &bitmap, int style)
{
if (&bitmap && bitmap.IsOk()) {
if (bitmap.IsOk()) {
return Append(item, bitmap, nullptr, style);
}
return Append(item, wxNullBitmap, nullptr, style);
@@ -219,7 +219,7 @@ int ComboBox::Append(const wxString &text,
void * clientData,
int style)
{
if (&bitmap && bitmap.IsOk()) {
if (bitmap.IsOk()) {
return Append(text, bitmap, wxString{}, clientData, style);
}
return Append(text, wxNullBitmap, wxString{}, clientData, style);
@@ -237,7 +237,7 @@ int ComboBox::Append(const wxString &text,
void *clientData,
int style)
{
auto valid_bit_map = (&bitmap && bitmap.IsOk()) ? bitmap : wxNullBitmap;
auto valid_bit_map = bitmap.IsOk() ? bitmap : wxNullBitmap;
Item item{text, wxEmptyString, valid_bit_map, valid_bit_map, clientData, group_key, group_label};
item.style = style;
items.push_back(item);
@@ -333,7 +333,7 @@ wxBitmap ComboBox::GetItemBitmap(unsigned int n) { return items[n].icon; }
void ComboBox::SetItemBitmap(unsigned int n, wxBitmap const &bitmap)
{
if (n >= items.size()) return;
items[n].icon = (&bitmap && bitmap.IsOk()) ? bitmap : wxNullBitmap;
items[n].icon = bitmap.IsOk() ? bitmap : wxNullBitmap;
drop.Invalidate();
}
+2 -1
View File
@@ -98,7 +98,7 @@ void LabeledStaticBox::SetBorderColor(StateColor const &color)
Refresh();
}
void LabeledStaticBox::SetFont(wxFont set_font)
bool LabeledStaticBox::SetFont(const wxFont &set_font)
{
m_font = set_font;
@@ -109,6 +109,7 @@ void LabeledStaticBox::SetFont(wxFont set_font)
m_label_width = tW;
Refresh();
return true;
}
bool LabeledStaticBox::Enable(bool enable)
+1 -1
View File
@@ -42,7 +42,7 @@ public:
void SetBorderColor(StateColor const &color);
void SetFont(wxFont set_font);
bool SetFont(const wxFont &set_font) override;
bool Enable(bool enable) override;
+5 -13
View File
@@ -21,6 +21,8 @@ ScrolledWindow::ScrolledWindow(wxWindow *parent, wxWindowID id, wxPoint position
m_bottomScrollbar = NULL;
m_verticalSplitter = NULL;
m_horizontalSplitter = NULL;
m_userPanel = NULL;
m_scroll_win = NULL;
m_marginWidth = marginWidth;
@@ -110,23 +112,13 @@ void ScrolledWindow::SetTipColor(wxColour color)
if (m_bottomScrollbar) m_bottomScrollbar->SetTipColor(color);
}
void ScrolledWindow::Refresh()
bool ScrolledWindow::SetBackgroundColour(const wxColour &color)
{
// m_rightScrollbar->SetViewStart(0);
// m_rightScrollbar->Refresh();
// m_rightScrollbar->Update();
// m_userPanel->Refresh();
// m_bottomScrollbar->SetViewStart(0);
// m_rightScrollbar->Refresh();
// m_bottomScrollbar->Refresh();
}
void ScrolledWindow::SetBackgroundColour(wxColour color)
{
wxWindow::SetBackgroundColour(color);
const bool result = wxWindow::SetBackgroundColour(color);
m_verticalSplitter->SetBackgroundColour(color);
m_userPanel->SetBackgroundColour(color);
m_scroll_win->SetBackgroundColour(color);
return result;
}
void ScrolledWindow::SetMarginColor(wxColour color)
+2 -3
View File
@@ -15,8 +15,7 @@ public:
ScrolledWindow(wxWindow *parent, wxWindowID id, wxPoint position, wxSize size, long style, int marginWidth = 0, int scrollbarWidth = 4, int tipLength = 0);
void OnMouseWheel(wxMouseEvent &event);
void SetTipColor(wxColour color);
void Refresh();
void SetBackgroundColour(wxColour color);
bool SetBackgroundColour(const wxColour &color) override;
void SetMarginColor(wxColour color);
void SetScrollbarColor(wxColour color);
@@ -27,7 +26,7 @@ public:
// wxSplitterWindow* GetVerticalSplitter() { return m_verticalSplitter; }
// wxSplitterWindow* GetHorizontalSplitter() { return m_horizontalSplitter; }
bool IsBothDirections() { return m_bothDirections; }
virtual void SetScrollbars(int pixelsPerUnitX, int pixelsPerUnitY, int noUnitsX, int noUnitsY, int xPos = 0, int yPos = 0, bool noRefresh = false);
virtual void SetScrollbars(int pixelsPerUnitX, int pixelsPerUnitY, int noUnitsX, int noUnitsY, int xPos = 0, int yPos = 0, bool noRefresh = false) override;
private:
wxPanel * m_userPanel; // the panel targeted by the scrolled window
+3 -2
View File
@@ -111,7 +111,7 @@ int TabCtrl::AppendItem(const wxString &item,
btns.push_back(btn);
if (btns.size() > 1)
sizer->GetItem(sizer->GetItemCount() - 1)->SetMinSize({0, 0});
sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, TAB_BUTTON_SPACE * 2);
sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, TAB_BUTTON_SPACE);
sizer->AddStretchSpacer(1);
relayout();
return btns.size() - 1;
@@ -225,8 +225,9 @@ bool TabCtrl::IsVisible(unsigned int item) const
void TabCtrl::DoSetSize(int x, int y, int width, int height, int sizeFlags)
{
auto size = GetSize();
wxWindow::DoSetSize(x, y, width, height, sizeFlags);
if (sizeFlags & wxSIZE_USE_EXISTING) return;
if (size == GetSize()) return;
relayout();
}
-633
View File
@@ -1,633 +0,0 @@
#include "wxMediaCtrl2.h"
#include "libslic3r/Time.hpp"
#include "I18N.hpp"
#include "GUI_App.hpp"
#include "LinuxDisplayBackend.hpp"
#include <boost/filesystem/operations.hpp>
#include <string>
#ifdef __WIN32__
#include <winuser.h>
#include <versionhelpers.h>
#include <wx/msw/registry.h>
#include <shellapi.h>
#endif
#ifdef __LINUX__
#include "Printer/gstbambusrc.h"
#include <gst/gst.h> // main gstreamer header
#endif
#if defined(__LINUX__) && defined(__WXGTK__)
#include <gtk/gtk.h>
#include <wx/nativewin.h>
namespace {
bool ensure_gstreamer_initialized_for_liveview()
{
GError* error = nullptr;
if (!gst_init_check(nullptr, nullptr, &error)) {
BOOST_LOG_TRIVIAL(error) << "wxMediaCtrl2: gst_init_check failed before native Wayland liveview setup"
<< (error ? std::string(": ") + error->message : std::string());
if (error)
g_error_free(error);
return false;
}
return true;
}
bool is_gstreamer_feature_available(const char* feature)
{
if (!ensure_gstreamer_initialized_for_liveview())
return false;
GstElementFactory* factory = gst_element_factory_find(feature);
if (!factory)
return false;
gst_object_unref(factory);
return true;
}
void set_gstreamer_feature_rank(const char* feature, guint rank)
{
GstElementFactory* factory = gst_element_factory_find(feature);
if (!factory)
return;
gst_plugin_feature_set_rank(GST_PLUGIN_FEATURE(factory), rank);
gst_object_unref(factory);
}
void configure_wayland_gstreamer_liveview_path()
{
static bool configured = false;
if (configured)
return;
configured = true;
if (!ensure_gstreamer_initialized_for_liveview())
return;
// Prefer software decode for Bambu liveview on Wayland/NVIDIA, where
// zero-copy GL/DMABUF display paths can be fragile. Keep hardware
// decoders available as lower-ranked fallbacks for VAAPI/NVDEC/V4L2-only
// installations instead of passing preflight and then blocking autoplug.
set_gstreamer_feature_rank("avdec_h264", GST_RANK_PRIMARY + 300);
set_gstreamer_feature_rank("openh264dec", GST_RANK_PRIMARY + 100);
set_gstreamer_feature_rank("nvh264dec", GST_RANK_MARGINAL);
set_gstreamer_feature_rank("vaapih264dec", GST_RANK_MARGINAL);
set_gstreamer_feature_rank("vah264dec", GST_RANK_MARGINAL);
set_gstreamer_feature_rank("v4l2h264dec", GST_RANK_MARGINAL);
}
}
#endif // defined(__LINUX__) && defined(__WXGTK__)
#ifdef __LINUX__
extern "C" int gst_bambu_last_error;
class WXDLLIMPEXP_MEDIA
wxGStreamerMediaBackend : public wxMediaBackendCommonBase
{
public:
GstElement *m_playbin; // GStreamer media element
};
#endif
wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent);
wxMediaCtrl2::wxMediaCtrl2(wxWindow *parent)
{
#if defined(__LINUX__) && defined(__WXGTK__)
m_native_wayland = Slic3r::GUI::is_running_on_wayland();
if (m_native_wayland && is_gstreamer_feature_available("gtksink"))
configure_wayland_gstreamer_liveview_path();
else if (m_native_wayland) {
m_gtk_sink_error = _L("Native Wayland liveview requires the GStreamer GTK video sink. Please install the gtksink plugin for GStreamer, then restart OrcaSlicer.");
BOOST_LOG_TRIVIAL(warning) << "wxMediaCtrl2: native Wayland liveview disabled because GStreamer gtksink is unavailable";
}
#endif
#ifdef __WIN32__
auto hModExe = GetModuleHandle(NULL);
// BOOST_LOG_TRIVIAL(info) << "wxMediaCtrl2: GetModuleHandle " << hModExe;
auto NvOptimusEnablement = (DWORD *) GetProcAddress(hModExe, "NvOptimusEnablement");
auto AmdPowerXpressRequestHighPerformance = (int *) GetProcAddress(hModExe, "AmdPowerXpressRequestHighPerformance");
if (NvOptimusEnablement) {
// BOOST_LOG_TRIVIAL(info) << "wxMediaCtrl2: NvOptimusEnablement " << *NvOptimusEnablement;
*NvOptimusEnablement = 0;
}
if (AmdPowerXpressRequestHighPerformance) {
// BOOST_LOG_TRIVIAL(info) << "wxMediaCtrl2: AmdPowerXpressRequestHighPerformance " << *AmdPowerXpressRequestHighPerformance;
*AmdPowerXpressRequestHighPerformance = 0;
}
#endif
#if defined(__LINUX__) && defined(__WXGTK__)
if (m_native_wayland)
wxControl::Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize);
else
#endif
wxMediaCtrl::Create(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxMEDIACTRLPLAYERCONTROLS_NONE);
#ifdef __LINUX__
gstbambusrc_register();
#ifdef __WXGTK__
if (m_native_wayland && m_gtk_sink_error.empty())
m_use_gtk_sink = CreateGtkSinkPlayer();
if (m_native_wayland && !m_use_gtk_sink && m_gtk_sink_error.empty())
m_gtk_sink_error = _L("Failed to initialize the native Wayland GStreamer video sink. Please check your GStreamer GTK plugin installation.");
#endif
if (!m_use_gtk_sink && m_imp) {
auto playbin = reinterpret_cast<wxGStreamerMediaBackend *>(m_imp)->m_playbin;
g_object_set(G_OBJECT(playbin),
"audio-sink", nullptr,
nullptr);
} else if (!m_use_gtk_sink) {
BOOST_LOG_TRIVIAL(warning) << "wxMediaCtrl2: wxMediaCtrl backend is unavailable";
}
Bind(wxEVT_MEDIA_LOADED, [this](auto & e) {
m_loaded = true;
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
event.SetId(0);
event.SetEventObject(this);
wxPostEvent(this, event);
});
#endif
}
wxMediaCtrl2::~wxMediaCtrl2()
{
#if defined(__LINUX__) && defined(__WXGTK__)
DestroyGtkSinkPlayer();
#endif
}
#if defined(__LINUX__) && defined(__WXGTK__)
bool wxMediaCtrl2::CreateGtkSinkPlayer()
{
GstElement *playbin = gst_element_factory_make("playbin", "orca-wayland-gtk-playbin");
if (!playbin)
return false;
GError *error = nullptr;
GstElement *video_sink = gst_parse_bin_from_description(
"videoconvert ! videoscale ! video/x-raw,format=BGRx ! gtksink name=orca_wayland_gtksink sync=false",
TRUE,
&error);
if (!video_sink) {
BOOST_LOG_TRIVIAL(warning) << "wxMediaCtrl2: failed to create gtksink video bin"
<< (error ? std::string(": ") + error->message : std::string());
if (error)
g_error_free(error);
gst_object_unref(playbin);
return false;
}
GstElement *gtk_sink = gst_bin_get_by_name(GST_BIN(video_sink), "orca_wayland_gtksink");
if (!gtk_sink) {
BOOST_LOG_TRIVIAL(warning) << "wxMediaCtrl2: failed to find gtksink in video bin";
gst_object_unref(video_sink);
gst_object_unref(playbin);
return false;
}
GtkWidget *gtk_widget = nullptr;
g_object_get(G_OBJECT(gtk_sink), "widget", &gtk_widget, nullptr);
if (!gtk_widget) {
BOOST_LOG_TRIVIAL(warning) << "wxMediaCtrl2: gtksink did not expose a GtkWidget";
gst_object_unref(gtk_sink);
gst_object_unref(video_sink);
gst_object_unref(playbin);
return false;
}
gtk_widget_show(gtk_widget);
m_gtk_video_window = new wxNativeWindow(this, wxID_ANY, gtk_widget);
m_gtk_video_window->Show();
g_object_unref(gtk_widget);
g_object_set(G_OBJECT(playbin),
"video-sink", video_sink,
"audio-sink", nullptr,
nullptr);
gst_object_unref(video_sink);
m_gtk_playbin = playbin;
m_gtk_sink = gtk_sink;
GstBus *bus = gst_element_get_bus(playbin);
m_gtk_bus_watch_id = gst_bus_add_watch(bus, [](GstBus *, GstMessage *message, gpointer data) -> gboolean {
auto *self = static_cast<wxMediaCtrl2 *>(data);
if (!self || !self->m_gtk_playbin)
return G_SOURCE_REMOVE;
switch (GST_MESSAGE_TYPE(message)) {
case GST_MESSAGE_ERROR:
{
GError *error = nullptr;
gchar *debug = nullptr;
gst_message_parse_error(message, &error, &debug);
BOOST_LOG_TRIVIAL(warning) << "wxMediaCtrl2: gtksink pipeline error"
<< (error ? std::string(": ") + error->message : std::string())
<< (debug ? std::string(" debug: ") + debug : std::string());
if (error)
g_error_free(error);
if (debug)
g_free(debug);
self->m_error = gst_bambu_last_error ? gst_bambu_last_error : 2;
self->m_loaded = false;
self->m_gtk_state = wxMEDIASTATE_STOPPED;
self->PostGtkSinkStateEvent(self->GetId());
break;
}
case GST_MESSAGE_EOS:
self->m_loaded = false;
self->m_gtk_state = wxMEDIASTATE_STOPPED;
self->PostGtkSinkStateEvent(self->GetId());
break;
case GST_MESSAGE_STATE_CHANGED:
if (GST_MESSAGE_SRC(message) == GST_OBJECT(self->m_gtk_playbin)) {
GstState old_state;
GstState new_state;
GstState pending_state;
gst_message_parse_state_changed(message, &old_state, &new_state, &pending_state);
if (new_state == GST_STATE_PLAYING) {
self->m_loaded = true;
self->m_gtk_state = wxMEDIASTATE_PLAYING;
self->PostGtkSinkStateEvent();
} else if (new_state == GST_STATE_PAUSED && old_state < GST_STATE_PAUSED) {
// Treat only upward READY/NULL -> PAUSED as load completion.
// PLAYING -> PAUSED is a normal teardown step before NULL.
self->m_loaded = true;
self->m_gtk_state = wxMEDIASTATE_PAUSED;
self->PostGtkSinkStateEvent();
} else if (new_state <= GST_STATE_READY && old_state >= GST_STATE_PAUSED) {
self->m_loaded = false;
self->m_gtk_state = wxMEDIASTATE_STOPPED;
self->PostGtkSinkStateEvent();
}
}
break;
default:
break;
}
return G_SOURCE_CONTINUE;
}, this);
gst_object_unref(bus);
BOOST_LOG_TRIVIAL(info) << "wxMediaCtrl2: using GTK native Wayland video sink";
return true;
}
void wxMediaCtrl2::DestroyGtkSinkPlayer()
{
if (m_gtk_bus_watch_id) {
g_source_remove(m_gtk_bus_watch_id);
m_gtk_bus_watch_id = 0;
}
if (m_gtk_playbin) {
gst_element_set_state(m_gtk_playbin, GST_STATE_NULL);
}
if (m_gtk_video_window) {
m_gtk_video_window->Destroy();
m_gtk_video_window = nullptr;
}
if (m_gtk_playbin) {
gst_object_unref(m_gtk_playbin);
m_gtk_playbin = nullptr;
}
if (m_gtk_sink) {
gst_object_unref(m_gtk_sink);
m_gtk_sink = nullptr;
}
}
void wxMediaCtrl2::PostGtkSinkStateEvent(int id)
{
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
event.SetId(id);
event.SetEventObject(this);
wxPostEvent(this, event);
}
#endif // defined(__LINUX__) && defined(__WXGTK__)
#define CLSID_BAMBU_SOURCE L"{233E64FB-2041-4A6C-AFAB-FF9BCF83E7AA}"
void wxMediaCtrl2::Load(wxURI url)
{
#ifdef __WIN32__
InvalidateBestSize();
if (m_imp == nullptr) {
static bool notified = false;
if (!notified) CallAfter([] {
auto res = wxMessageBox(_L("Windows Media Player is required for this task! Do you want to enable 'Windows Media Player' for your operation system?"), _L("Error"), wxOK | wxCANCEL);
if (res == wxOK) {
wxString url = IsWindows10OrGreater()
? "ms-settings:optionalfeatures?activationSource=SMC-Article-14209"
: "https://support.microsoft.com/en-au/windows/get-windows-media-player-81718e0d-cfce-25b1-aee3-94596b658287";
wxExecute("cmd /c start " + url, wxEXEC_HIDE_CONSOLE);
}
});
m_error = 100;
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
event.SetId(GetId());
event.SetEventObject(this);
wxPostEvent(this, event);
return;
}
{
wxRegKey key11(wxRegKey::HKCU, L"SOFTWARE\\Classes\\CLSID\\" CLSID_BAMBU_SOURCE L"\\InProcServer32");
wxRegKey key12(wxRegKey::HKCR, L"CLSID\\" CLSID_BAMBU_SOURCE L"\\InProcServer32");
wxString path = key11.Exists() ? key11.QueryDefaultValue()
: key12.Exists() ? key12.QueryDefaultValue() : wxString{};
wxRegKey key2(wxRegKey::HKCR, "bambu");
wxString clsid;
if (key2.Exists())
key2.QueryRawValue("Source Filter", clsid);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": clsid %1% path %2%") % clsid % path;
std::string data_dir_str = Slic3r::data_dir();
boost::filesystem::path data_dir_path(data_dir_str);
auto dll_path = data_dir_path / "plugins" / "BambuSource.dll";
if (path.empty() || !wxFile::Exists(path) || clsid != CLSID_BAMBU_SOURCE) {
if (boost::filesystem::exists(dll_path)) {
CallAfter(
[dll_path] {
int res = wxMessageBox(_L("BambuSource has not correctly been registered for media playing! Press Yes to re-register it. You will be promoted twice"), _L("Error"), wxYES_NO);
if (res == wxYES) {
std::string regContent = R"(Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\bambu]
"Source Filter"="{233E64FB-2041-4A6C-AFAB-FF9BCF83E7AA}"
)";
auto reg_path = (fs::temp_directory_path() / fs::unique_path()).replace_extension(".reg");
std::ofstream temp_reg_file(reg_path.c_str());
if (!temp_reg_file) {
return false;
}
temp_reg_file << regContent;
temp_reg_file.close();
auto sei_params = L"/q /s " + reg_path.wstring();
SHELLEXECUTEINFO sei{sizeof(sei), SEE_MASK_NOCLOSEPROCESS, NULL, L"open",
L"regedit", sei_params.c_str(),SW_HIDE,SW_HIDE};
::ShellExecuteEx(&sei);
wstring quoted_dll_path = L"\"" + dll_path.wstring() + L"\"";
SHELLEXECUTEINFO info{sizeof(info), 0, NULL, L"runas", L"regsvr32", quoted_dll_path.c_str(), SW_HIDE };
::ShellExecuteEx(&info);
fs::remove(reg_path);
}
return true;
});
} else {
CallAfter([] {
wxMessageBox(_L("Missing BambuSource component registered for media playing! Please re-install OrcaSlicer or seek community help."), _L("Error"), wxOK);
});
}
m_error = clsid != CLSID_BAMBU_SOURCE ? 101 : path.empty() ? 102 : 103;
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
event.SetId(GetId());
event.SetEventObject(this);
wxPostEvent(this, event);
return;
}
if (path != dll_path) {
static bool notified = false;
if (!notified) CallAfter([dll_path] {
int res = wxMessageBox(_L("Using a BambuSource from a different install, video play may not work correctly! Press Yes to fix it."), _L("Warning"), wxYES_NO | wxICON_WARNING);
if (res == wxYES) {
auto path = dll_path.wstring();
if (path.find(L' ') != std::wstring::npos)
path = L"\"" + path + L"\"";
SHELLEXECUTEINFO info{sizeof(info), 0, NULL, L"open", L"regsvr32", path.c_str(), SW_HIDE};
::ShellExecuteEx(&info);
}
});
notified = true;
}
wxRegKey keyWmp(wxRegKey::HKCU, "SOFTWARE\\Microsoft\\MediaPlayer\\Player\\Extensions\\.");
keyWmp.Create();
long permissions = 0;
if (keyWmp.HasValue("Permissions"))
keyWmp.QueryValue("Permissions", &permissions);
if ((permissions & 32) == 0) {
permissions |= 32;
keyWmp.SetValue("Permissions", permissions);
}
}
url = wxURI(url.BuildURI().append("&hwnd=").append(boost::lexical_cast<std::string>(GetHandle())).append("&tid=").append(
boost::lexical_cast<std::string>(GetCurrentThreadId())));
#endif
#ifdef __WXGTK3__
GstElementFactory *factory;
int hasplugins = 1;
factory = gst_element_factory_find("h264parse");
if (!factory) {
hasplugins = 0;
} else {
gst_object_unref(factory);
}
factory = gst_element_factory_find("openh264dec");
if (!factory) {
factory = gst_element_factory_find("avdec_h264");
}
if (!factory) {
factory = gst_element_factory_find("vaapih264dec");
}
if (!factory) {
factory = gst_element_factory_find("vah264dec");
}
if (!factory) {
factory = gst_element_factory_find("nvh264dec");
}
if (!factory) {
factory = gst_element_factory_find("v4l2h264dec");
}
if (!factory) {
hasplugins = 0;
} else {
gst_object_unref(factory);
}
if (!hasplugins) {
CallAfter([] {
wxMessageBox(_L("Your system is missing H.264 codecs for GStreamer, which are required to play video. (Try installing the gstreamer1.0-plugins-bad or gstreamer1.0-libav packages, then restart Orca Slicer?)"), _L("Error"), wxOK);
});
m_error = 101;
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
event.SetId(GetId());
event.SetEventObject(this);
wxPostEvent(this, event);
return;
}
wxLog::EnableLogging(false);
#endif
m_error = 0;
m_loaded = false;
#if defined(__LINUX__) && defined(__WXGTK__)
if (m_use_gtk_sink && m_gtk_playbin) {
const std::string uri = std::string(url.BuildURI().ToUTF8().data());
gst_element_set_state(m_gtk_playbin, GST_STATE_NULL);
g_object_set(G_OBJECT(m_gtk_playbin), "uri", uri.c_str(), nullptr);
m_gtk_state = wxMEDIASTATE_STOPPED;
GstStateChangeReturn state = gst_element_set_state(m_gtk_playbin, GST_STATE_PAUSED);
if (state == GST_STATE_CHANGE_FAILURE) {
m_error = gst_bambu_last_error ? gst_bambu_last_error : 2;
PostGtkSinkStateEvent(GetId());
}
return;
}
if (!m_imp) {
m_error = m_native_wayland && !m_gtk_sink_error.empty() ? 104 : 100;
m_loaded = false;
if (m_native_wayland && !m_gtk_sink_error.empty() && !m_gtk_sink_error_notified) {
m_gtk_sink_error_notified = true;
const wxString message = m_gtk_sink_error;
CallAfter([message] {
wxMessageBox(message, _L("Error"), wxOK);
});
}
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
event.SetId(GetId());
event.SetEventObject(this);
wxPostEvent(this, event);
return;
}
#endif
wxMediaCtrl::Load(url);
}
void wxMediaCtrl2::Play()
{
#if defined(__LINUX__) && defined(__WXGTK__)
if (m_use_gtk_sink && m_gtk_playbin) {
GstStateChangeReturn state = gst_element_set_state(m_gtk_playbin, GST_STATE_PLAYING);
if (state == GST_STATE_CHANGE_FAILURE) {
m_error = gst_bambu_last_error ? gst_bambu_last_error : 2;
m_gtk_state = wxMEDIASTATE_STOPPED;
PostGtkSinkStateEvent(GetId());
}
return;
}
if (!m_imp) {
m_error = m_native_wayland && !m_gtk_sink_error.empty() ? 104 : 100;
if (m_native_wayland && !m_gtk_sink_error.empty() && !m_gtk_sink_error_notified) {
m_gtk_sink_error_notified = true;
const wxString message = m_gtk_sink_error;
CallAfter([message] {
wxMessageBox(message, _L("Error"), wxOK);
});
}
PostGtkSinkStateEvent(GetId());
return;
}
#endif
wxMediaCtrl::Play();
}
void wxMediaCtrl2::Stop()
{
#if defined(__LINUX__) && defined(__WXGTK__)
if (m_use_gtk_sink && m_gtk_playbin) {
gst_element_set_state(m_gtk_playbin, GST_STATE_NULL);
m_gtk_state = wxMEDIASTATE_STOPPED;
m_loaded = false;
PostGtkSinkStateEvent(0);
return;
}
if (!m_imp)
return;
#endif
wxMediaCtrl::Stop();
}
wxMediaState wxMediaCtrl2::GetState()
{
#if defined(__LINUX__) && defined(__WXGTK__)
if (m_use_gtk_sink && m_gtk_playbin)
return m_gtk_state;
if (!m_imp)
return wxMEDIASTATE_STOPPED;
#endif
return wxMediaCtrl::GetState();
}
int wxMediaCtrl2::GetLastError() const
{
#ifdef __LINUX__
#ifdef __WXGTK__
if (m_use_gtk_sink && m_error)
return m_error;
#endif
if (m_error)
return m_error;
return gst_bambu_last_error;
#else
return m_error;
#endif
}
wxSize wxMediaCtrl2::GetVideoSize() const
{
#ifdef __LINUX__
// Gstreamer doesn't give us a VideoSize until we're playing, which
// confuses the MediaPlayCtrl into claiming that it is stuck
// "Loading...". Fake it out for now.
return m_loaded ? wxSize(1280, 720) : wxSize{};
#else
wxSize size = m_imp ? m_imp->GetVideoSize() : wxSize(0, 0);
if (size.GetWidth() > 0)
const_cast<wxSize&>(m_video_size) = size;
return size;
#endif
}
wxSize wxMediaCtrl2::DoGetBestSize() const
{
return {-1, -1};
}
#ifdef __WIN32__
WXLRESULT wxMediaCtrl2::MSWWindowProc(WXUINT nMsg,
WXWPARAM wParam,
WXLPARAM lParam)
{
// The stream source sends WM_USER+1000 with a synchronous SendMessage from its own threads,
// so this runs re-entrantly on the UI thread at whatever message-retrieval point the player
// happens to be in - often nested inside an Orca log statement. Never BOOST_LOG_TRIVIAL here:
// boost::log is not re-entrant on one thread, and doing so corrupted its per-thread record
// state, crashing later in unrelated places (the player, the log filter, a plug-in heap free).
// Post the string out (as the stat branch does) and log it on a clean stack instead.
if (nMsg == WM_USER + 1000) {
wxString msg((wchar_t const *) lParam);
if (wParam == 1) {
if (msg.EndsWith("]")) {
int n = msg.find_last_of('[');
if (n != wxString::npos) {
long val = 0;
if (msg.SubString(n + 1, msg.Length() - 2).ToLong(&val))
m_error = (int) val;
}
} else if (msg.Contains("stat_log")) {
wxCommandEvent evt(EVT_MEDIA_CTRL_STAT);
evt.SetEventObject(this);
evt.SetString(msg.Mid(msg.Find(' ') + 1));
wxPostEvent(this, evt);
}
}
return 0;
}
return wxMediaCtrl::MSWWindowProc(nMsg, wParam, lParam);
}
#endif
-117
View File
@@ -1,117 +0,0 @@
//
// wxMediaCtrl2.h
// libslic3r_gui
//
// Created by cmguo on 2021/12/7.
//
#ifndef wxMediaCtrl2_h
#define wxMediaCtrl2_h
#include "wx/uri.h"
#include "wx/mediactrl.h"
wxDECLARE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent);
#if defined(__LINUX__) && defined(__WXGTK__)
typedef struct _GstElement GstElement;
#endif
#ifdef __WXMAC__
class wxMediaCtrl2 : public wxWindow
{
public:
wxMediaCtrl2(wxWindow * parent);
~wxMediaCtrl2();
void Load(wxURI url);
void Play();
void Stop();
void SetIdleImage(wxString const & image);
wxMediaState GetState() const;
wxSize GetVideoSize() const;
int GetLastError() const { return m_error; }
static inline const wxMediaState MEDIASTATE_BUFFERING = static_cast<wxMediaState>(6);
protected:
void DoSetSize(int x, int y, int width, int height, int sizeFlags) override;
static void bambu_log(void const * ctx, int level, char const * msg);
void NotifyStopped();
private:
void create_player();
void * m_player = nullptr;
wxMediaState m_state = wxMEDIASTATE_STOPPED;
int m_error = 0;
wxSize m_video_size{16, 9};
};
#else
class wxMediaCtrl2 : public wxMediaCtrl
{
public:
wxMediaCtrl2(wxWindow *parent);
~wxMediaCtrl2();
void Load(wxURI url);
void Play();
void Stop();
void SetIdleImage(wxString const & image);
wxMediaState GetState();
int GetLastError() const;
wxSize GetVideoSize() const;
protected:
wxSize DoGetBestSize() const override;
void DoSetSize(int x, int y, int width, int height, int sizeFlags) override;
#ifdef __WIN32__
WXLRESULT MSWWindowProc(WXUINT nMsg,
WXWPARAM wParam,
WXLPARAM lParam) override;
#endif
private:
#if defined(__LINUX__) && defined(__WXGTK__)
bool CreateGtkSinkPlayer();
void DestroyGtkSinkPlayer();
void PostGtkSinkStateEvent(int id = 0);
bool m_native_wayland = false;
bool m_use_gtk_sink = false;
wxString m_gtk_sink_error;
bool m_gtk_sink_error_notified = false;
GstElement *m_gtk_playbin = nullptr;
GstElement *m_gtk_sink = nullptr;
unsigned int m_gtk_bus_watch_id = 0;
wxWindow *m_gtk_video_window = nullptr;
wxMediaState m_gtk_state = wxMEDIASTATE_STOPPED;
#endif
wxString m_idle_image;
int m_error = 0;
bool m_loaded = false;
wxSize m_video_size{16, 9};
};
#endif
#endif /* wxMediaCtrl2_h */
-170
View File
@@ -1,170 +0,0 @@
//
// wxMediaCtrl2.m
// OrcaSlicer
//
// Created by cmguo on 2021/12/7.
//
#import "wxMediaCtrl2.h"
#import "wx/mediactrl.h"
#include <boost/log/trivial.hpp>
#import <Foundation/Foundation.h>
#import "BambuPlayer/BambuPlayer.h"
#import "../Utils/NetworkAgent.hpp"
#include <stdlib.h>
#include <dlfcn.h>
wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent);
#define BAMBU_DYNAMIC
void wxMediaCtrl2::bambu_log(void const * ctx, int level, char const * msg)
{
if (level == 1) {
wxString msg2(msg);
if (msg2.EndsWith("]")) {
int n = msg2.find_last_of('[');
if (n != wxString::npos) {
long val = 0;
wxMediaCtrl2 * ctrl = (wxMediaCtrl2 *) ctx;
if (msg2.SubString(n + 1, msg2.Length() - 2).ToLong(&val))
ctrl->m_error = (int) val;
}
} else if (strstr(msg, "stat_log")) {
wxMediaCtrl2 * ctrl = (wxMediaCtrl2 *) ctx;
wxCommandEvent evt(EVT_MEDIA_CTRL_STAT);
evt.SetEventObject(ctrl);
evt.SetString(strchr(msg, ' ') + 1);
wxPostEvent(ctrl, evt);
}
} else if (level < 0) {
wxMediaCtrl2 * ctrl = (wxMediaCtrl2 *) ctx;
ctrl->NotifyStopped();
}
BOOST_LOG_TRIVIAL(info) << msg;
}
wxMediaCtrl2::wxMediaCtrl2(wxWindow * parent)
: wxWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
{
NSView * imageView = (NSView *) GetHandle();
imageView.layer = [[CALayer alloc] init];
CGColorRef color = CGColorCreateGenericRGB(0, 0, 0, 1.0f);
imageView.layer.backgroundColor = color;
CGColorRelease(color);
imageView.wantsLayer = YES;
create_player();
}
wxMediaCtrl2::~wxMediaCtrl2()
{
BambuPlayer * player = (BambuPlayer *) m_player;
[player dealloc];
}
void wxMediaCtrl2::create_player()
{
auto module = Slic3r::NetworkAgent::get_bambu_source_entry();
if (!module) {
//not ready yet
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "Network plugin not ready currently!";
return;
}
Class cls = (__bridge Class) dlsym(module, "OBJC_CLASS_$_BambuPlayer");
if (cls == nullptr) {
m_error = -2;
return;
}
NSView * imageView = (NSView *) GetHandle();
BambuPlayer * player = [cls alloc];
[player initWithImageView: imageView];
[player setLogger: bambu_log withContext: this];
m_player = player;
}
void wxMediaCtrl2::Load(wxURI url)
{
if (!m_player) {
create_player();
if (!m_player) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": create_player failed currently!";
return;
}
}
BambuPlayer * player = (BambuPlayer *) m_player;
if (player) {
[player close];
m_error = 0;
m_error = [player open: url.BuildURI().ToUTF8()];
}
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
event.SetId(GetId());
event.SetEventObject(this);
wxPostEvent(this, event);
}
void wxMediaCtrl2::Play()
{
if (!m_player) {
create_player();
if (!m_player) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": create_player failed currently!";
return;
}
}
BambuPlayer * player2 = (BambuPlayer *) m_player;
[player2 play];
if (m_state != wxMEDIASTATE_PLAYING) {
m_state = wxMEDIASTATE_PLAYING;
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
event.SetId(GetId());
event.SetEventObject(this);
wxPostEvent(this, event);
}
}
void wxMediaCtrl2::Stop()
{
if (!m_player) {
create_player();
if (!m_player) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": create_player failed currently!";
return;
}
}
BambuPlayer * player2 = (BambuPlayer *) m_player;
[player2 close];
NotifyStopped();
}
void wxMediaCtrl2::NotifyStopped()
{
if (m_state != wxMEDIASTATE_STOPPED) {
m_state = wxMEDIASTATE_STOPPED;
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
event.SetId(GetId());
event.SetEventObject(this);
wxPostEvent(this, event);
}
}
wxMediaState wxMediaCtrl2::GetState() const
{
return m_state;
}
wxSize wxMediaCtrl2::GetVideoSize() const
{
BambuPlayer * player2 = (BambuPlayer *) m_player;
if (player2) {
NSSize size = [player2 videoSize];
if (size.width > 0)
const_cast<wxSize&>(m_video_size) = {(int) size.width, (int) size.height};
return {(int) size.width, (int) size.height};
} else {
return {0, 0};
}
}
+313
View File
@@ -0,0 +1,313 @@
#include "wxMediaCtrl3.h"
#include "AVVideoDecoder.hpp"
#include "I18N.hpp"
#include "libslic3r/Utils.hpp"
#include <boost/log/trivial.hpp>
#include <wx/dcclient.h>
#ifdef __WIN32__
#include <versionhelpers.h>
#include <wx/msw/registry.h>
#include <shellapi.h>
#endif
wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent);
BEGIN_EVENT_TABLE(wxMediaCtrl3, wxWindow)
// catch paint events
EVT_PAINT(wxMediaCtrl3::paintEvent)
END_EVENT_TABLE()
struct StaticBambuLib : BambuLib
{
static StaticBambuLib &get(BambuLib *);
};
wxMediaCtrl3::wxMediaCtrl3(wxWindow *parent)
: wxWindow(parent, wxID_ANY)
, BambuLib(StaticBambuLib::get(this))
, m_thread([this] { PlayThread(); })
{
SetBackgroundColour("#000001ff");
}
wxMediaCtrl3::~wxMediaCtrl3()
{
{
std::unique_lock<std::mutex> lk(m_mutex);
m_url.reset(new wxURI);
m_frame = wxImage(m_idle_image);
m_cond.notify_all();
}
m_thread.join();
}
void wxMediaCtrl3::Load(wxURI url)
{
std::unique_lock<std::mutex> lk(m_mutex);
m_video_size = wxDefaultSize;
m_error = 0;
m_url.reset(new wxURI(url));
m_cond.notify_all();
}
void wxMediaCtrl3::Play()
{
std::unique_lock<std::mutex> lk(m_mutex);
if (m_state != wxMEDIASTATE_PLAYING) {
m_state = wxMEDIASTATE_PLAYING;
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
event.SetId(GetId());
event.SetEventObject(this);
wxPostEvent(this, event);
}
}
void wxMediaCtrl3::Stop()
{
std::unique_lock<std::mutex> lk(m_mutex);
m_url.reset();
m_frame = wxImage(m_idle_image);
NotifyStopped();
m_cond.notify_all();
Refresh();
}
void wxMediaCtrl3::SetIdleImage(wxString const &image)
{
if (m_idle_image == image)
return;
m_idle_image = image;
if (m_url == nullptr) {
std::unique_lock<std::mutex> lk(m_mutex);
m_frame = wxImage(m_idle_image);
assert(m_frame.IsOk());
Refresh();
}
}
wxMediaState wxMediaCtrl3::GetState()
{
std::unique_lock<std::mutex> lk(m_mutex);
return m_state;
}
int wxMediaCtrl3::GetLastError()
{
std::unique_lock<std::mutex> lk(m_mutex);
return m_error;
}
wxSize wxMediaCtrl3::GetVideoSize()
{
std::unique_lock<std::mutex> lk(m_mutex);
return m_video_size;
}
wxSize wxMediaCtrl3::DoGetBestSize() const
{
return {-1, -1};
}
static void adjust_frame_size(wxSize & frame, wxSize const & video, wxSize const & window)
{
if (video.x * window.y < video.y * window.x)
frame = { video.x * window.y / video.y, window.y };
else
frame = { window.x, video.y * window.x / video.x };
}
void wxMediaCtrl3::paintEvent(wxPaintEvent &evt)
{
wxPaintDC dc(this);
auto size = GetSize();
if (size.x <= 0 || size.y <= 0)
return;
std::unique_lock<std::mutex> lk(m_mutex);
if (!m_frame.IsOk())
return;
auto size2 = m_frame.GetSize();
if (size2.x != m_frame_size.x && size2.y == m_frame_size.y)
size2.x = m_frame_size.x;
auto size3 = (size - size2) / 2;
if (size2.x != size.x && size2.y != size.y) {
double scale = 1.;
if (size.x * size2.y > size.y * size2.x) {
size3 = {size.x * size2.y / size.y, size2.y};
scale = double(size.y) / size2.y;
} else {
size3 = {size2.x, size.y * size2.x / size.x};
scale = double(size.x) / size2.x;
}
dc.SetUserScale(scale, scale);
size3 = (size3 - size2) / 2;
}
dc.DrawBitmap(m_frame, size3.x, size3.y);
}
void wxMediaCtrl3::DoSetSize(int x, int y, int width, int height, int sizeFlags)
{
wxWindow::DoSetSize(x, y, width, height, sizeFlags);
if (sizeFlags == wxSIZE_USE_EXISTING) return;
wxMediaCtrl_OnSize(this, m_video_size, width, height);
std::unique_lock<std::mutex> lk(m_mutex);
adjust_frame_size(m_frame_size, m_video_size, GetSize());
Refresh();
}
void wxMediaCtrl3::bambu_log(void *ctx, int level, tchar const *msg2)
{
#ifdef _WIN32
wxString msg(msg2);
#else
wxString msg = wxString::FromUTF8(msg2);
#endif
if (level == 1) {
if (msg.EndsWith("]")) {
int n = msg.find_last_of('[');
if (n != wxString::npos) {
long val = 0;
wxMediaCtrl3 *ctrl = (wxMediaCtrl3 *) ctx;
if (msg.SubString(n + 1, msg.Length() - 2).ToLong(&val)) {
std::unique_lock<std::mutex> lk(ctrl->m_mutex);
ctrl->m_error = (int) val;
}
}
} else if (msg.Contains("stat_log")) {
wxCommandEvent evt(EVT_MEDIA_CTRL_STAT);
wxMediaCtrl3 *ctrl = (wxMediaCtrl3 *) ctx;
evt.SetEventObject(ctrl);
evt.SetString(msg.Mid(msg.Find(' ') + 1));
wxPostEvent(ctrl, evt);
}
}
BOOST_LOG_TRIVIAL(info) << msg.ToUTF8().data();
}
void wxMediaCtrl3::PlayThread()
{
using namespace std::chrono_literals;
std::shared_ptr<wxURI> url;
std::unique_lock<std::mutex> lk(m_mutex);
while (true) {
m_cond.wait(lk, [this, &url] { return m_url != url; });
url = m_url;
if (url == nullptr)
continue;
if (!url->HasScheme())
break;
lk.unlock();
Bambu_Tunnel tunnel = nullptr;
int error = Bambu_Create(&tunnel, m_url->BuildURI().ToUTF8());
if (error == 0) {
Bambu_SetLogger(tunnel, &wxMediaCtrl3::bambu_log, this);
error = Bambu_Open(tunnel);
if (error == 0)
error = Bambu_would_block;
}
lk.lock();
while (error == int(Bambu_would_block)) {
m_cond.wait_for(lk, 100ms);
if (m_url != url) {
error = 1;
break;
}
lk.unlock();
error = Bambu_StartStream(tunnel, true);
lk.lock();
}
Bambu_StreamInfo info;
if (error == 0)
error = Bambu_GetStreamInfo(tunnel, 0, &info);
AVVideoDecoder decoder;
int minFrameDuration = 0;
if (error == 0) {
decoder.open(info);
m_video_size = { info.format.video.width, info.format.video.height };
adjust_frame_size(m_frame_size, m_video_size, GetSize());
minFrameDuration = 800 / info.format.video.frame_rate; // 80%
NotifyStopped();
}
Bambu_Sample sample;
while (error == 0) {
lk.unlock();
error = Bambu_ReadSample(tunnel, &sample);
lk.lock();
while (error == int(Bambu_would_block)) {
m_cond.wait_for(lk, 100ms);
if (m_url != url) {
error = 1;
break;
}
lk.unlock();
error = Bambu_ReadSample(tunnel, &sample);
lk.lock();
}
if (error == 0) {
auto frame_size = m_frame_size;
lk.unlock();
decoder.decode(sample);
#ifdef _WIN32
wxBitmap bm;
decoder.toWxBitmap(bm, frame_size);
#else
wxImage bm;
decoder.toWxImage(bm, frame_size);
#endif
lk.lock();
if (m_url != url) {
error = 1;
break;
}
if (bm.IsOk()) {
auto now = std::chrono::system_clock::now();
if (m_last_PTS && (sample.decode_time - m_last_PTS) < 30000000ULL) { // 3s
auto next_PTS_expected = m_last_PTS_expected + std::chrono::milliseconds((sample.decode_time - m_last_PTS) / 10000ULL);
// The frame is late, catch up a little
auto next_PTS_practical = m_last_PTS_practical + std::chrono::milliseconds(minFrameDuration);
auto next_PTS = std::max(next_PTS_expected, next_PTS_practical);
if(now < next_PTS)
std::this_thread::sleep_until(next_PTS);
else
next_PTS = now;
//auto text = wxString::Format(L"wxMediaCtrl3 pts diff %ld\n", std::chrono::duration_cast<std::chrono::milliseconds>(next_PTS - next_PTS_expected).count());
//OutputDebugString(text);
m_last_PTS = sample.decode_time;
m_last_PTS_expected = next_PTS_expected;
m_last_PTS_practical = next_PTS;
} else {
// Resync
m_last_PTS = sample.decode_time;
m_last_PTS_expected = now;
m_last_PTS_practical = now;
}
m_frame = bm;
}
CallAfter([this] { Refresh(); });
}
}
if (tunnel) {
lk.unlock();
Bambu_Close(tunnel);
Bambu_Destroy(tunnel);
tunnel = nullptr;
lk.lock();
}
if (m_url == url)
m_error = error;
m_frame_size = wxDefaultSize;
m_video_size = wxDefaultSize;
NotifyStopped();
}
}
void wxMediaCtrl3::NotifyStopped()
{
m_state = wxMEDIASTATE_STOPPED;
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
event.SetId(GetId());
event.SetEventObject(this);
wxPostEvent(this, event);
}
+85
View File
@@ -0,0 +1,85 @@
//
// wxMediaCtrl3.h
// libslic3r_gui
//
// Created by cmguo on 2024/6/22.
//
#ifndef wxMediaCtrl3_h
#define wxMediaCtrl3_h
#include "wx/uri.h"
#include "wx/mediactrl.h"
wxDECLARE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent);
void wxMediaCtrl_OnSize(wxWindow * ctrl, wxSize const & videoSize, int width, int height);
#define BAMBU_DYNAMIC
#include <condition_variable>
#include <thread>
#ifndef _WIN32
#include <wx/image.h>
#endif
#include "Printer/BambuTunnel.h"
class AVVideoDecoder;
class wxMediaCtrl3 : public wxWindow, BambuLib
{
public:
wxMediaCtrl3(wxWindow *parent);
~wxMediaCtrl3();
void Load(wxURI url);
void Play();
void Stop();
void SetIdleImage(wxString const & image);
wxMediaState GetState();
int GetLastError();
wxSize GetVideoSize();
protected:
DECLARE_EVENT_TABLE()
void paintEvent(wxPaintEvent &evt);
wxSize DoGetBestSize() const override;
void DoSetSize(int x, int y, int width, int height, int sizeFlags) override;
static void bambu_log(void *ctx, int level, tchar const *msg);
void PlayThread();
void NotifyStopped();
private:
wxString m_idle_image;
wxMediaState m_state = wxMEDIASTATE_STOPPED;
int m_error = 0;
wxSize m_video_size = wxDefaultSize;
wxSize m_frame_size = wxDefaultSize;
#ifdef _WIN32
wxBitmap m_frame;
#else
wxImage m_frame;
#endif
std::shared_ptr<wxURI> m_url;
std::uint64_t m_last_PTS{0};
std::chrono::system_clock::time_point m_last_PTS_expected;
std::chrono::system_clock::time_point m_last_PTS_practical;
std::mutex m_mutex;
std::condition_variable m_cond;
std::thread m_thread;
};
#endif /* wxMediaCtrl3_h */
+1 -1
View File
@@ -1198,7 +1198,7 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version
version.config_version = cache_ver;
version.comment = description;
// Orca: update vendor.json
updates.updates.emplace_back(std::move(file_path), std::move(path_in_vendor.string()), std::move(version), vendor_name, changelog, "", force_update, false);
updates.updates.emplace_back(std::move(file_path), path_in_vendor.string(), std::move(version), vendor_name, changelog, "", force_update, false);
//Orca: update vendor folder
updates.updates.emplace_back(cache_profile_path / vendor_name, vendor_path / vendor_name, Version(), vendor_name, "", "", force_update, true);
} else {
+1 -1
View File
@@ -631,7 +631,7 @@ void parse_metadata_rfc822(const std::string& content,
bool is_ignored_plugin_directory(const boost::filesystem::path& path)
{
const std::string name = path.filename().string();
return name.empty() || name[0] == '.' || name.rfind("__", 0) == 0 || name == PLUGIN_SUBSCRIBED_DIR;
return name.empty() || name[0] == '.' || name.rfind("__", 0) == 0 || name == PLUGIN_SUBSCRIBED_DIR || name == PLUGIN_DATA_DIR;
}
bool is_safe_relative_path(const boost::filesystem::path& path)
+1
View File
@@ -12,6 +12,7 @@
#include <vector>
#define PLUGIN_SUBSCRIBED_DIR "_subscribed"
#define PLUGIN_DATA_DIR "plugin_data"
namespace Slic3r {

Some files were not shown because too many files have changed in this diff Show More