diff --git a/cmake/modules/FindwxWidgets.cmake b/cmake/modules/FindwxWidgets.cmake new file mode 100644 index 0000000000..e1f93a6a26 --- /dev/null +++ b/cmake/modules/FindwxWidgets.cmake @@ -0,0 +1,1219 @@ +# PrusaSlicer: this is a direct copy of the FindwxWidgets.cmake module +# within the original CMake 3.27 distribution + +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +FindwxWidgets +------------- + +Find a wxWidgets (a.k.a., wxWindows) installation. + +This module finds if wxWidgets is installed and selects a default +configuration to use. wxWidgets is a modular library. To specify the +modules that you will use, you need to name them as components to the +package: + +find_package(wxWidgets COMPONENTS core base ... OPTIONAL_COMPONENTS net ...) + +.. versionadded:: 3.4 + Support for :command:`find_package` version argument; ``webview`` component. + +.. versionadded:: 3.14 + ``OPTIONAL_COMPONENTS`` support. + +There are two search branches: a windows style and a unix style. For +windows, the following variables are searched for and set to defaults +in case of multiple choices. Change them if the defaults are not +desired (i.e., these are the only variables you should change to +select a configuration): + +:: + + wxWidgets_ROOT_DIR - Base wxWidgets directory + (e.g., C:/wxWidgets-3.2.0). + wxWidgets_LIB_DIR - Path to wxWidgets libraries + (e.g., C:/wxWidgets-3.2.0/lib/vc_x64_lib). + wxWidgets_CONFIGURATION - Configuration to use + (e.g., msw, mswd, mswu, mswunivud, etc.) + wxWidgets_EXCLUDE_COMMON_LIBRARIES + - Set to TRUE to exclude linking of + commonly required libs (e.g., png tiff + jpeg zlib regex expat). + + + +For unix style it uses the wx-config utility. You can select between +debug/release, unicode/ansi, universal/non-universal, and +static/shared in the QtDialog or ccmake interfaces by turning ON/OFF +the following variables: + +:: + + wxWidgets_USE_DEBUG + wxWidgets_USE_UNICODE + wxWidgets_USE_UNIVERSAL + wxWidgets_USE_STATIC + +There is also a wxWidgets_CONFIG_OPTIONS variable for all other +options that need to be passed to the wx-config utility. For example, +to use the base toolkit found in the /usr/local path, set the variable +(before calling the FIND_PACKAGE command) as such: + +:: + + set(wxWidgets_CONFIG_OPTIONS --toolkit=base --prefix=/usr) + + + +The following are set after the configuration is done for both windows +and unix style: + +:: + + wxWidgets_FOUND - Set to TRUE if wxWidgets was found. + wxWidgets_INCLUDE_DIRS - Include directories for WIN32 + i.e., where to find "wx/wx.h" and + "wx/setup.h"; possibly empty for unices. + wxWidgets_LIBRARIES - Path to the wxWidgets libraries. + wxWidgets_LIBRARY_DIRS - compile time link dirs, useful for + rpath on UNIX. Typically an empty string + in WIN32 environment. + wxWidgets_DEFINITIONS - Contains defines required to compile/link + against WX, e.g. WXUSINGDLL + wxWidgets_DEFINITIONS_DEBUG- Contains defines required to compile/link + against WX debug builds, e.g. __WXDEBUG__ + wxWidgets_CXX_FLAGS - Include dirs and compiler flags for + unices, empty on WIN32. Essentially + "`wx-config --cxxflags`". + wxWidgets_USE_FILE - Convenience include file. + +.. versionadded:: 3.11 + The following environment variables can be used as hints: ``WX_CONFIG``, + ``WXRC_CMD``. + + +Sample usage: + +:: + + # Note that for MinGW users the order of libs is important! + find_package(wxWidgets COMPONENTS gl core base OPTIONAL_COMPONENTS net) + if(wxWidgets_FOUND) + include(${wxWidgets_USE_FILE}) + # and for each of your dependent executable/library targets: + target_link_libraries( ${wxWidgets_LIBRARIES}) + endif() + + + +If wxWidgets is required (i.e., not an optional part): + +:: + + find_package(wxWidgets REQUIRED gl core base OPTIONAL_COMPONENTS net) + include(${wxWidgets_USE_FILE}) + # and for each of your dependent executable/library targets: + target_link_libraries( ${wxWidgets_LIBRARIES}) + +Imported targets +^^^^^^^^^^^^^^^^ + +.. versionadded:: 3.27 + +This module defines the following :prop_tgt:`IMPORTED` targets: + +``wxWidgets::wxWidgets`` + An interface library providing usage requirements for the found components. +#]=======================================================================] + +# +# FIXME: check this and provide a correct sample usage... +# Remember to connect back to the upper text. +# Sample usage with monolithic wx build: +# +# find_package(wxWidgets COMPONENTS mono) +# ... + +# NOTES +# +# This module has been tested on the WIN32 platform with wxWidgets +# 2.6.2, 2.6.3, and 2.5.3. However, it has been designed to +# easily extend support to all possible builds, e.g., static/shared, +# debug/release, unicode, universal, multilib/monolithic, etc.. +# +# If you want to use the module and your build type is not supported +# out-of-the-box, please contact me to exchange information on how +# your system is setup and I'll try to add support for it. +# +# AUTHOR +# +# Miguel A. Figueroa-Villanueva (miguelf at ieee dot org). +# Jan Woetzel (jw at mip.informatik.uni-kiel.de). +# +# Based on previous works of: +# Jan Woetzel (FindwxWindows.cmake), +# Jorgen Bodde and Jerry Fath (FindwxWin.cmake). + +# TODO/ideas +# +# (1) Option/Setting to use all available wx libs +# In contrast to expert developer who lists the +# minimal set of required libs in wxWidgets_USE_LIBS +# there is the newbie user: +# - who just wants to link against WX with more 'magic' +# - doesn't know the internal structure of WX or how it was built, +# in particular if it is monolithic or not +# - want to link against all available WX libs +# Basically, the intent here is to mimic what wx-config would do by +# default (i.e., `wx-config --libs`). +# +# Possible solution: +# Add a reserved keyword "std" that initializes to what wx-config +# would default to. If the user has not set the wxWidgets_USE_LIBS, +# default to "std" instead of "base core" as it is now. To implement +# "std" will basically boil down to a FOR_EACH lib-FOUND, but maybe +# checking whether a minimal set was found. + + +# FIXME: This and all the DBG_MSG calls should be removed after the +# module stabilizes. +# +# Helper macro to control the debugging output globally. There are +# two versions for controlling how verbose your output should be. +macro(DBG_MSG _MSG) +# message(STATUS +# "${CMAKE_CURRENT_LIST_FILE}(${CMAKE_CURRENT_LIST_LINE}): ${_MSG}") +endmacro() +macro(DBG_MSG_V _MSG) +# message(STATUS +# "${CMAKE_CURRENT_LIST_FILE}(${CMAKE_CURRENT_LIST_LINE}): ${_MSG}") +endmacro() + +# Clear return values in case the module is loaded more than once. +set(wxWidgets_FOUND FALSE) +set(wxWidgets_INCLUDE_DIRS "") +set(wxWidgets_LIBRARIES "") +set(wxWidgets_LIBRARY_DIRS "") +set(wxWidgets_CXX_FLAGS "") + +# DEPRECATED: This is a patch to support the DEPRECATED use of +# wxWidgets_USE_LIBS. +# +# If wxWidgets_USE_LIBS is set: +# - if using , then override wxWidgets_USE_LIBS +# - else set wxWidgets_FIND_COMPONENTS to wxWidgets_USE_LIBS +if(wxWidgets_USE_LIBS AND NOT wxWidgets_FIND_COMPONENTS) + set(wxWidgets_FIND_COMPONENTS ${wxWidgets_USE_LIBS}) +endif() +DBG_MSG("wxWidgets_FIND_COMPONENTS : ${wxWidgets_FIND_COMPONENTS}") + +# Add the convenience use file if available. +# +# Get dir of this file which may reside in: +# - CMAKE_MAKE_ROOT/Modules on CMake installation +# - CMAKE_MODULE_PATH if user prefers his own specialized version +set(wxWidgets_USE_FILE "") +get_filename_component( + wxWidgets_CURRENT_LIST_DIR ${CMAKE_CURRENT_LIST_FILE} PATH) +# Prefer an existing customized version, but the user might override +# the FindwxWidgets module and not the UsewxWidgets one. +if(EXISTS "${wxWidgets_CURRENT_LIST_DIR}/UsewxWidgets.cmake") + set(wxWidgets_USE_FILE + "${wxWidgets_CURRENT_LIST_DIR}/UsewxWidgets.cmake") +else() + set(wxWidgets_USE_FILE UsewxWidgets) +endif() + +# Known wxWidgets versions. +set(wx_versions 3.3 3.2 3.1 3.0 2.9 2.8 2.7 2.6 2.5) + +macro(wx_extract_version) + unset(_wx_filename) + find_file(_wx_filename wx/version.h PATHS ${wxWidgets_INCLUDE_DIRS} NO_DEFAULT_PATH) + dbg_msg("_wx_filename: ${_wx_filename}") + + if(NOT _wx_filename) + message(FATAL_ERROR "wxWidgets wx/version.h file not found in ${wxWidgets_INCLUDE_DIRS}.") + endif() + + file(READ "${_wx_filename}" _wx_version_h) + unset(_wx_filename CACHE) + + string(REGEX REPLACE "^(.*\n)?#define +wxMAJOR_VERSION +([0-9]+).*" + "\\2" wxWidgets_VERSION_MAJOR "${_wx_version_h}" ) + string(REGEX REPLACE "^(.*\n)?#define +wxMINOR_VERSION +([0-9]+).*" + "\\2" wxWidgets_VERSION_MINOR "${_wx_version_h}" ) + string(REGEX REPLACE "^(.*\n)?#define +wxRELEASE_NUMBER +([0-9]+).*" + "\\2" wxWidgets_VERSION_PATCH "${_wx_version_h}" ) + set(wxWidgets_VERSION_STRING + "${wxWidgets_VERSION_MAJOR}.${wxWidgets_VERSION_MINOR}.${wxWidgets_VERSION_PATCH}" ) + dbg_msg("wxWidgets_VERSION_STRING: ${wxWidgets_VERSION_STRING}") +endmacro() + +#===================================================================== +# Determine whether unix or win32 paths should be used +#===================================================================== +if(WIN32 AND NOT CYGWIN AND NOT MSYS AND NOT CMAKE_CROSSCOMPILING) + set(wxWidgets_FIND_STYLE "win32") +else() + set(wxWidgets_FIND_STYLE "unix") +endif() + +#===================================================================== +# WIN32_FIND_STYLE +#===================================================================== +if(wxWidgets_FIND_STYLE STREQUAL "win32") + # Useful common wx libs needed by almost all components. + set(wxWidgets_COMMON_LIBRARIES png tiff jpeg zlib regex expat) + + # DEPRECATED: Use find_package(wxWidgets COMPONENTS mono) instead. + if(NOT wxWidgets_FIND_COMPONENTS) + if(wxWidgets_USE_MONOLITHIC) + set(wxWidgets_FIND_COMPONENTS mono) + else() + set(wxWidgets_FIND_COMPONENTS core base) # this is default + endif() + endif() + + # Add the common (usually required libs) unless + # wxWidgets_EXCLUDE_COMMON_LIBRARIES has been set. + if(NOT wxWidgets_EXCLUDE_COMMON_LIBRARIES) + list(APPEND wxWidgets_FIND_COMPONENTS + ${wxWidgets_COMMON_LIBRARIES}) + endif() + + #------------------------------------------------------------------- + # WIN32: Helper MACROS + #------------------------------------------------------------------- + # + # Get filename components for a configuration. For example, + # if _CONFIGURATION = mswunivud, then _PF="msw", _UNV=univ, _UCD=u _DBG=d + # if _CONFIGURATION = mswu, then _PF="msw", _UNV="", _UCD=u _DBG="" + # + macro(WX_GET_NAME_COMPONENTS _CONFIGURATION _PF _UNV _UCD _DBG) + DBG_MSG_V(${_CONFIGURATION}) + string(REGEX MATCH "univ" ${_UNV} "${_CONFIGURATION}") + string(REGEX REPLACE "[msw|qt].*(u)[d]*$" "u" ${_UCD} "${_CONFIGURATION}") + if(${_UCD} STREQUAL ${_CONFIGURATION}) + set(${_UCD} "") + endif() + string(REGEX MATCH "d$" ${_DBG} "${_CONFIGURATION}") + string(REGEX MATCH "^[msw|qt]*" ${_PF} "${_CONFIGURATION}") + endmacro() + + # + # Find libraries associated to a configuration. + # + macro(WX_FIND_LIBS _PF _UNV _UCD _DBG _VER) + DBG_MSG_V("m_unv = ${_UNV}") + DBG_MSG_V("m_ucd = ${_UCD}") + DBG_MSG_V("m_dbg = ${_DBG}") + DBG_MSG_V("m_ver = ${_VER}") + + # FIXME: What if both regex libs are available. regex should be + # found outside the loop and only wx${LIB}${_UCD}${_DBG}. + # Find wxWidgets common libraries. + foreach(LIB ${wxWidgets_COMMON_LIBRARIES} scintilla) + find_library(WX_${LIB}${_DBG} + NAMES + wx${LIB}${_UCD}${_DBG} # for regex + wx${LIB}${_DBG} + PATHS ${WX_LIB_DIR} + NO_DEFAULT_PATH + ) + mark_as_advanced(WX_${LIB}${_DBG}) + endforeach() + + # Find wxWidgets multilib base libraries. + find_library(WX_base${_DBG} + NAMES wxbase${_VER}${_UCD}${_DBG} + PATHS ${WX_LIB_DIR} + NO_DEFAULT_PATH + ) + mark_as_advanced(WX_base${_DBG}) + foreach(LIB net odbc xml) + find_library(WX_${LIB}${_DBG} + NAMES wxbase${_VER}${_UCD}${_DBG}_${LIB} + PATHS ${WX_LIB_DIR} + NO_DEFAULT_PATH + ) + mark_as_advanced(WX_${LIB}${_DBG}) + endforeach() + + # Find wxWidgets monolithic library. + find_library(WX_mono${_DBG} + NAMES wx${_PF}${_UNV}${_VER}${_UCD}${_DBG} + PATHS ${WX_LIB_DIR} + NO_DEFAULT_PATH + ) + mark_as_advanced(WX_mono${_DBG}) + + # Find wxWidgets multilib libraries. + foreach(LIB core adv aui html media xrc dbgrid gl qa richtext + stc ribbon propgrid webview) + find_library(WX_${LIB}${_DBG} + NAMES wx${_PF}${_UNV}${_VER}${_UCD}${_DBG}_${LIB} + PATHS ${WX_LIB_DIR} + NO_DEFAULT_PATH + ) + mark_as_advanced(WX_${LIB}${_DBG}) + endforeach() + endmacro() + + # + # Clear all library paths, so that FIND_LIBRARY refinds them. + # + # Clear a lib, reset its found flag, and mark as advanced. + macro(WX_CLEAR_LIB _LIB) + set(${_LIB} "${_LIB}-NOTFOUND" CACHE FILEPATH "Cleared." FORCE) + set(${_LIB}_FOUND FALSE) + mark_as_advanced(${_LIB}) + endmacro() + # Clear all debug or release library paths (arguments are "d" or ""). + macro(WX_CLEAR_ALL_LIBS _DBG) + # Clear wxWidgets common libraries. + foreach(LIB ${wxWidgets_COMMON_LIBRARIES} scintilla) + WX_CLEAR_LIB(WX_${LIB}${_DBG}) + endforeach() + + # Clear wxWidgets multilib base libraries. + WX_CLEAR_LIB(WX_base${_DBG}) + foreach(LIB net odbc xml) + WX_CLEAR_LIB(WX_${LIB}${_DBG}) + endforeach() + + # Clear wxWidgets monolithic library. + WX_CLEAR_LIB(WX_mono${_DBG}) + + # Clear wxWidgets multilib libraries. + foreach(LIB core adv aui html media xrc dbgrid gl qa richtext + webview stc ribbon propgrid) + WX_CLEAR_LIB(WX_${LIB}${_DBG}) + endforeach() + endmacro() + # Clear all wxWidgets debug libraries. + macro(WX_CLEAR_ALL_DBG_LIBS) + WX_CLEAR_ALL_LIBS("d") + endmacro() + # Clear all wxWidgets release libraries. + macro(WX_CLEAR_ALL_REL_LIBS) + WX_CLEAR_ALL_LIBS("") + endmacro() + + # + # Set the wxWidgets_LIBRARIES variable. + # Also, Sets output variable wxWidgets_FOUND to FALSE if it fails. + # + macro(WX_SET_LIBRARIES _LIBS _DBG) + DBG_MSG_V("Looking for ${${_LIBS}}") + if(WX_USE_REL_AND_DBG) + foreach(LIB ${${_LIBS}}) + DBG_MSG_V("Searching for ${LIB} and ${LIB}d") + DBG_MSG_V("WX_${LIB} : ${WX_${LIB}}") + DBG_MSG_V("WX_${LIB}d : ${WX_${LIB}d}") + if(WX_${LIB} AND WX_${LIB}d) + DBG_MSG_V("Found ${LIB} and ${LIB}d") + list(APPEND wxWidgets_LIBRARIES + debug ${WX_${LIB}d} optimized ${WX_${LIB}} + ) + set(wxWidgets_${LIB}_FOUND TRUE) + elseif(NOT wxWidgets_FIND_REQUIRED_${LIB}) + DBG_MSG_V("- ignored optional missing WX_${LIB}=${WX_${LIB}} or WX_${LIB}d=${WX_${LIB}d}") + else() + DBG_MSG_V("- not found due to missing WX_${LIB}=${WX_${LIB}} or WX_${LIB}d=${WX_${LIB}d}") + set(wxWidgets_FOUND FALSE) + endif() + endforeach() + else() + foreach(LIB ${${_LIBS}}) + DBG_MSG_V("Searching for ${LIB}${_DBG}") + DBG_MSG_V("WX_${LIB}${_DBG} : ${WX_${LIB}${_DBG}}") + if(WX_${LIB}${_DBG}) + DBG_MSG_V("Found ${LIB}${_DBG}") + list(APPEND wxWidgets_LIBRARIES ${WX_${LIB}${_DBG}}) + set(wxWidgets_${LIB}_FOUND TRUE) + elseif(NOT wxWidgets_FIND_REQUIRED_${LIB}) + DBG_MSG_V("- ignored optional missing WX_${LIB}${_DBG}=${WX_${LIB}${_DBG}}") + else() + DBG_MSG_V("- not found due to missing WX_${LIB}${_DBG}=${WX_${LIB}${_DBG}}") + set(wxWidgets_FOUND FALSE) + endif() + endforeach() + endif() + + DBG_MSG_V("OpenGL") + list(FIND ${_LIBS} gl WX_USE_GL) + if(NOT WX_USE_GL EQUAL -1) + DBG_MSG_V("- is required.") + list(APPEND wxWidgets_LIBRARIES opengl32 glu32) + endif() + + list(APPEND wxWidgets_LIBRARIES winmm comctl32 uuid oleacc uxtheme rpcrt4 shlwapi version wsock32) + endmacro() + + #------------------------------------------------------------------- + # WIN32: Start actual work. + #------------------------------------------------------------------- + + set(wx_paths "wxWidgets") + foreach(version ${wx_versions}) + foreach(patch RANGE 15 0 -1) + list(APPEND wx_paths "wxWidgets-${version}.${patch}") + endforeach() + endforeach() + + # Look for an installation tree. + find_path(wxWidgets_ROOT_DIR + NAMES include/wx/wx.h + PATHS + ENV wxWidgets_ROOT_DIR + ENV WXWIN + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\wxWidgets_is1;Inno Setup: App Path]" # WX 2.6.x + C:/ + D:/ + ENV ProgramFiles + PATH_SUFFIXES + ${wx_paths} + DOC "wxWidgets base/installation directory" + ) + + # If wxWidgets_ROOT_DIR changed, clear lib dir. + if(NOT WX_ROOT_DIR STREQUAL wxWidgets_ROOT_DIR) + if(NOT wxWidgets_LIB_DIR OR WX_ROOT_DIR) + set(wxWidgets_LIB_DIR "wxWidgets_LIB_DIR-NOTFOUND" + CACHE PATH "Cleared." FORCE) + endif() + set(WX_ROOT_DIR ${wxWidgets_ROOT_DIR} + CACHE INTERNAL "wxWidgets_ROOT_DIR") + endif() + + if(WX_ROOT_DIR) + # Select one default tree inside the already determined wx tree. + # Prefer static/shared order usually consistent with build + # settings. + set(_WX_TOOL "") + set(_WX_TOOLVER "") + set(_WX_ARCH "") + if(MINGW) + set(_WX_TOOL gcc) + elseif(MSVC) + set(_WX_TOOL vc) + set(_WX_TOOLVER ${MSVC_TOOLSET_VERSION}) + # support for a lib/vc14x_x64_dll/ path from wxW 3.1.3 distribution + string(REGEX REPLACE ".$" "x" _WX_TOOLVERx ${_WX_TOOLVER}) + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(_WX_ARCH _x64) + endif() + endif() + if(BUILD_SHARED_LIBS) + find_path(wxWidgets_LIB_DIR + NAMES + qtu/wx/setup.h + qtud/wx/setup.h + msw/wx/setup.h + mswd/wx/setup.h + mswu/wx/setup.h + mswud/wx/setup.h + mswuniv/wx/setup.h + mswunivd/wx/setup.h + mswunivu/wx/setup.h + mswunivud/wx/setup.h + PATHS + ${WX_ROOT_DIR}/lib/${_WX_TOOL}${_WX_TOOLVER}_xp${_WX_ARCH}_dll # prefer shared + ${WX_ROOT_DIR}/lib/${_WX_TOOL}${_WX_TOOLVER}${_WX_ARCH}_dll # prefer shared + ${WX_ROOT_DIR}/lib/${_WX_TOOL}${_WX_TOOLVERx}_xp${_WX_ARCH}_dll # prefer shared + ${WX_ROOT_DIR}/lib/${_WX_TOOL}${_WX_TOOLVERx}${_WX_ARCH}_dll # prefer shared + ${WX_ROOT_DIR}/lib/${_WX_TOOL}${_WX_ARCH}_dll # prefer shared + ${WX_ROOT_DIR}/lib/${_WX_TOOL}${_WX_TOOLVER}_xp${_WX_ARCH}_lib + ${WX_ROOT_DIR}/lib/${_WX_TOOL}${_WX_TOOLVER}${_WX_ARCH}_lib + ${WX_ROOT_DIR}/lib/${_WX_TOOL}${_WX_TOOLVERx}_xp${_WX_ARCH}_lib + ${WX_ROOT_DIR}/lib/${_WX_TOOL}${_WX_TOOLVERx}${_WX_ARCH}_lib + ${WX_ROOT_DIR}/lib/${_WX_TOOL}${_WX_ARCH}_lib + DOC "Path to wxWidgets libraries" + NO_DEFAULT_PATH + ) + else() + find_path(wxWidgets_LIB_DIR + NAMES + qtu/wx/setup.h + qtud/wx/setup.h + msw/wx/setup.h + mswd/wx/setup.h + mswu/wx/setup.h + mswud/wx/setup.h + mswuniv/wx/setup.h + mswunivd/wx/setup.h + mswunivu/wx/setup.h + mswunivud/wx/setup.h + PATHS + ${WX_ROOT_DIR}/lib/${_WX_TOOL}${_WX_TOOLVER}_xp${_WX_ARCH}_lib # prefer static + ${WX_ROOT_DIR}/lib/${_WX_TOOL}${_WX_TOOLVER}${_WX_ARCH}_lib # prefer static + ${WX_ROOT_DIR}/lib/${_WX_TOOL}${_WX_TOOLVERx}_xp${_WX_ARCH}_lib # prefer static + ${WX_ROOT_DIR}/lib/${_WX_TOOL}${_WX_TOOLVERx}${_WX_ARCH}_lib # prefer static + ${WX_ROOT_DIR}/lib/${_WX_TOOL}${_WX_ARCH}_lib # prefer static + ${WX_ROOT_DIR}/lib/${_WX_TOOL}${_WX_TOOLVER}_xp${_WX_ARCH}_dll + ${WX_ROOT_DIR}/lib/${_WX_TOOL}${_WX_TOOLVER}${_WX_ARCH}_dll + ${WX_ROOT_DIR}/lib/${_WX_TOOL}${_WX_TOOLVERx}_xp${_WX_ARCH}_dll + ${WX_ROOT_DIR}/lib/${_WX_TOOL}${_WX_TOOLVERx}${_WX_ARCH}_dll + ${WX_ROOT_DIR}/lib/${_WX_TOOL}${_WX_ARCH}_dll + DOC "Path to wxWidgets libraries" + NO_DEFAULT_PATH + ) + endif() + unset(_WX_TOOL) + unset(_WX_TOOLVER) + unset(_WX_ARCH) + + # If wxWidgets_LIB_DIR changed, clear all libraries. + if(NOT WX_LIB_DIR STREQUAL wxWidgets_LIB_DIR) + set(WX_LIB_DIR ${wxWidgets_LIB_DIR} CACHE INTERNAL "wxWidgets_LIB_DIR") + WX_CLEAR_ALL_DBG_LIBS() + WX_CLEAR_ALL_REL_LIBS() + endif() + + if(WX_LIB_DIR) + # If building shared libs, define WXUSINGDLL to use dllimport. + if(WX_LIB_DIR MATCHES "[dD][lL][lL]") + set(wxWidgets_DEFINITIONS WXUSINGDLL) + DBG_MSG_V("detected SHARED/DLL tree WX_LIB_DIR=${WX_LIB_DIR}") + endif() + + # Search for available configuration types. + foreach(CFG mswunivud mswunivd mswud mswd mswunivu mswuniv mswu msw qt qtd qtu qtud) + set(WX_${CFG}_FOUND FALSE) + if(EXISTS ${WX_LIB_DIR}/${CFG}) + list(APPEND WX_CONFIGURATION_LIST ${CFG}) + set(WX_${CFG}_FOUND TRUE) + set(WX_CONFIGURATION ${CFG}) + endif() + endforeach() + DBG_MSG_V("WX_CONFIGURATION_LIST=${WX_CONFIGURATION_LIST}") + + if(WX_CONFIGURATION) + set(wxWidgets_FOUND TRUE) + + # If the selected configuration wasn't found force the default + # one. Otherwise, use it but still force a refresh for + # updating the doc string with the current list of available + # configurations. + if(NOT WX_${wxWidgets_CONFIGURATION}_FOUND) + set(wxWidgets_CONFIGURATION ${WX_CONFIGURATION} CACHE STRING + "Set wxWidgets configuration (${WX_CONFIGURATION_LIST})" FORCE) + else() + set(wxWidgets_CONFIGURATION ${wxWidgets_CONFIGURATION} CACHE STRING + "Set wxWidgets configuration (${WX_CONFIGURATION_LIST})" FORCE) + endif() + + # If release config selected, and both release/debug exist. + if(WX_${wxWidgets_CONFIGURATION}d_FOUND) + option(wxWidgets_USE_REL_AND_DBG + "Use release and debug configurations?" TRUE) + set(WX_USE_REL_AND_DBG ${wxWidgets_USE_REL_AND_DBG}) + else() + # If the option exists (already in cache), force it false. + if(wxWidgets_USE_REL_AND_DBG) + set(wxWidgets_USE_REL_AND_DBG FALSE CACHE BOOL + "No ${wxWidgets_CONFIGURATION}d found." FORCE) + endif() + set(WX_USE_REL_AND_DBG FALSE) + endif() + + # Get configuration parameters from the name. + WX_GET_NAME_COMPONENTS(${wxWidgets_CONFIGURATION} PF UNV UCD DBG) + + # Set wxWidgets lib setup include directory. + if(EXISTS ${WX_LIB_DIR}/${wxWidgets_CONFIGURATION}/wx/setup.h) + set(wxWidgets_INCLUDE_DIRS + ${WX_LIB_DIR}/${wxWidgets_CONFIGURATION}) + else() + DBG_MSG("wxWidgets_FOUND FALSE because ${WX_LIB_DIR}/${wxWidgets_CONFIGURATION}/wx/setup.h does not exist.") + set(wxWidgets_FOUND FALSE) + endif() + + # Set wxWidgets main include directory. + if(EXISTS ${WX_ROOT_DIR}/include/wx/wx.h) + list(APPEND wxWidgets_INCLUDE_DIRS ${WX_ROOT_DIR}/include) + else() + DBG_MSG("wxWidgets_FOUND FALSE because WX_ROOT_DIR=${WX_ROOT_DIR} has no ${WX_ROOT_DIR}/include/wx/wx.h") + set(wxWidgets_FOUND FALSE) + endif() + + # Get version number. + wx_extract_version() + set(VER "${wxWidgets_VERSION_MAJOR}${wxWidgets_VERSION_MINOR}") + + # Find wxWidgets libraries. + WX_FIND_LIBS("${PF}" "${UNV}" "${UCD}" "${DBG}" "${VER}") + if(WX_USE_REL_AND_DBG) + WX_FIND_LIBS("${PF}" "${UNV}" "${UCD}" "d" "${VER}") + endif() + + # Settings for requested libs (i.e., include dir, libraries, etc.). + WX_SET_LIBRARIES(wxWidgets_FIND_COMPONENTS "${DBG}") + + # Add necessary definitions for unicode builds + if("${UCD}" STREQUAL "u") + list(APPEND wxWidgets_DEFINITIONS UNICODE _UNICODE) + endif() + + # Add necessary definitions for debug builds + set(wxWidgets_DEFINITIONS_DEBUG _DEBUG __WXDEBUG__) + + endif() + endif() + endif() + + if(MINGW AND NOT wxWidgets_FOUND) + # Try unix search mode as well. + set(wxWidgets_FIND_STYLE "unix") + dbg_msg_v("wxWidgets_FIND_STYLE changed to unix") + endif() +endif() + +#===================================================================== +# UNIX_FIND_STYLE +#===================================================================== +if(wxWidgets_FIND_STYLE STREQUAL "unix") + #----------------------------------------------------------------- + # UNIX: Helper MACROS + #----------------------------------------------------------------- + # + # Set the default values based on "wx-config --selected-config". + # + macro(WX_CONFIG_SELECT_GET_DEFAULT) + execute_process( + COMMAND sh "${wxWidgets_CONFIG_EXECUTABLE}" + ${wxWidgets_CONFIG_OPTIONS} --selected-config + OUTPUT_VARIABLE _wx_selected_config + RESULT_VARIABLE _wx_result + ERROR_QUIET + ) + if(_wx_result EQUAL 0) + foreach(_opt_name debug static unicode universal) + string(TOUPPER ${_opt_name} _upper_opt_name) + if(_wx_selected_config MATCHES "${_opt_name}") + set(wxWidgets_DEFAULT_${_upper_opt_name} ON) + else() + set(wxWidgets_DEFAULT_${_upper_opt_name} OFF) + endif() + endforeach() + else() + foreach(_upper_opt_name DEBUG STATIC UNICODE UNIVERSAL) + set(wxWidgets_DEFAULT_${_upper_opt_name} OFF) + endforeach() + endif() + endmacro() + + # + # Query a boolean configuration option to determine if the system + # has both builds available. If so, provide the selection option + # to the user. + # + macro(WX_CONFIG_SELECT_QUERY_BOOL _OPT_NAME _OPT_HELP) + execute_process( + COMMAND sh "${wxWidgets_CONFIG_EXECUTABLE}" + ${wxWidgets_CONFIG_OPTIONS} --${_OPT_NAME}=yes + RESULT_VARIABLE _wx_result_yes + OUTPUT_QUIET + ERROR_QUIET + ) + execute_process( + COMMAND sh "${wxWidgets_CONFIG_EXECUTABLE}" + ${wxWidgets_CONFIG_OPTIONS} --${_OPT_NAME}=no + RESULT_VARIABLE _wx_result_no + OUTPUT_QUIET + ERROR_QUIET + ) + string(TOUPPER ${_OPT_NAME} _UPPER_OPT_NAME) + if(_wx_result_yes EQUAL 0 AND _wx_result_no EQUAL 0) + option(wxWidgets_USE_${_UPPER_OPT_NAME} + ${_OPT_HELP} ${wxWidgets_DEFAULT_${_UPPER_OPT_NAME}}) + else() + # If option exists (already in cache), force to available one. + if(DEFINED wxWidgets_USE_${_UPPER_OPT_NAME}) + if(_wx_result_yes EQUAL 0) + set(wxWidgets_USE_${_UPPER_OPT_NAME} ON CACHE BOOL ${_OPT_HELP} FORCE) + else() + set(wxWidgets_USE_${_UPPER_OPT_NAME} OFF CACHE BOOL ${_OPT_HELP} FORCE) + endif() + endif() + endif() + endmacro() + + # + # Set wxWidgets_SELECT_OPTIONS to wx-config options for selecting + # among multiple builds. + # + macro(WX_CONFIG_SELECT_SET_OPTIONS) + set(wxWidgets_SELECT_OPTIONS ${wxWidgets_CONFIG_OPTIONS}) + foreach(_opt_name debug static unicode universal) + string(TOUPPER ${_opt_name} _upper_opt_name) + if(DEFINED wxWidgets_USE_${_upper_opt_name}) + if(wxWidgets_USE_${_upper_opt_name}) + list(APPEND wxWidgets_SELECT_OPTIONS --${_opt_name}=yes) + else() + list(APPEND wxWidgets_SELECT_OPTIONS --${_opt_name}=no) + endif() + endif() + endforeach() + endmacro() + + #----------------------------------------------------------------- + # UNIX: Start actual work. + #----------------------------------------------------------------- + # Support cross-compiling, only search in the target platform. + # + # Look for wx-config -- this can be set in the environment, + # or try versioned and toolchain-versioned variants of the -config + # executable as well. + set(wx_config_names "wx-config") + foreach(version ${wx_versions}) + list(APPEND wx_config_names "wx-config-${version}" "wxgtk3u-${version}-config" "wxgtk2u-${version}-config") + endforeach() + find_program(wxWidgets_CONFIG_EXECUTABLE + NAMES + $ENV{WX_CONFIG} + ${wx_config_names} + DOC "Location of wxWidgets library configuration provider binary (wx-config)." + ONLY_CMAKE_FIND_ROOT_PATH + ) + + if(wxWidgets_CONFIG_EXECUTABLE) + set(wxWidgets_FOUND TRUE) + + # get defaults based on "wx-config --selected-config" + WX_CONFIG_SELECT_GET_DEFAULT() + + # for each option: if both builds are available, provide option + WX_CONFIG_SELECT_QUERY_BOOL(debug "Use debug build?") + WX_CONFIG_SELECT_QUERY_BOOL(unicode "Use unicode build?") + WX_CONFIG_SELECT_QUERY_BOOL(universal "Use universal build?") + WX_CONFIG_SELECT_QUERY_BOOL(static "Link libraries statically?") + + # process selection to set wxWidgets_SELECT_OPTIONS + WX_CONFIG_SELECT_SET_OPTIONS() + DBG_MSG("wxWidgets_SELECT_OPTIONS=${wxWidgets_SELECT_OPTIONS}") + + # run the wx-config program to get cxxflags + execute_process( + COMMAND sh "${wxWidgets_CONFIG_EXECUTABLE}" + ${wxWidgets_SELECT_OPTIONS} --cxxflags + OUTPUT_VARIABLE wxWidgets_CXX_FLAGS + RESULT_VARIABLE RET + ERROR_QUIET + ) + if(RET EQUAL 0) + string(STRIP "${wxWidgets_CXX_FLAGS}" wxWidgets_CXX_FLAGS) + separate_arguments(wxWidgets_CXX_FLAGS_LIST NATIVE_COMMAND "${wxWidgets_CXX_FLAGS}") + + DBG_MSG_V("wxWidgets_CXX_FLAGS=${wxWidgets_CXX_FLAGS}") + + # parse definitions and include dirs from cxxflags + # drop the -D and -I prefixes + set(wxWidgets_CXX_FLAGS) + foreach(arg IN LISTS wxWidgets_CXX_FLAGS_LIST) + if("${arg}" MATCHES "^-I(.*)$") + # include directory + list(APPEND wxWidgets_INCLUDE_DIRS "${CMAKE_MATCH_1}") + elseif("${arg}" MATCHES "^-D(.*)$") + # compile definition + list(APPEND wxWidgets_DEFINITIONS "${CMAKE_MATCH_1}") + else() + list(APPEND wxWidgets_CXX_FLAGS "${arg}") + endif() + endforeach() + + DBG_MSG_V("wxWidgets_DEFINITIONS=${wxWidgets_DEFINITIONS}") + DBG_MSG_V("wxWidgets_INCLUDE_DIRS=${wxWidgets_INCLUDE_DIRS}") + DBG_MSG_V("wxWidgets_CXX_FLAGS=${wxWidgets_CXX_FLAGS}") + + else() + set(wxWidgets_FOUND FALSE) + DBG_MSG_V( + "${wxWidgets_CONFIG_EXECUTABLE} --cxxflags FAILED with RET=${RET}") + endif() + + # run the wx-config program to get the libs + # - NOTE: wx-config doesn't verify that the libs requested exist + # it just produces the names. Maybe a TRY_COMPILE would + # be useful here... + unset(_cmp_req) + unset(_cmp_opt) + foreach(_cmp IN LISTS wxWidgets_FIND_COMPONENTS) + if(wxWidgets_FIND_REQUIRED_${_cmp}) + list(APPEND _cmp_req "${_cmp}") + else() + list(APPEND _cmp_opt "${_cmp}") + endif() + endforeach() + DBG_MSG_V("wxWidgets required components : ${_cmp_req}") + DBG_MSG_V("wxWidgets optional components : ${_cmp_opt}") + if(DEFINED _cmp_opt) + string(REPLACE ";" "," _cmp_opt "--optional-libs ${_cmp_opt}") + endif() + string(REPLACE ";" "," _cmp_req "${_cmp_req}") + execute_process( + COMMAND sh "${wxWidgets_CONFIG_EXECUTABLE}" + ${wxWidgets_SELECT_OPTIONS} --libs ${_cmp_req} ${_cmp_opt} + OUTPUT_VARIABLE wxWidgets_LIBRARIES + RESULT_VARIABLE RET + ERROR_QUIET + ) + if(RET EQUAL 0) + string(STRIP "${wxWidgets_LIBRARIES}" wxWidgets_LIBRARIES) + separate_arguments(wxWidgets_LIBRARIES) + string(REPLACE "-framework;" "-framework " + wxWidgets_LIBRARIES "${wxWidgets_LIBRARIES}") + string(REPLACE "-weak_framework;" "-weak_framework " + wxWidgets_LIBRARIES "${wxWidgets_LIBRARIES}") + string(REPLACE "-arch;" "-arch " + wxWidgets_LIBRARIES "${wxWidgets_LIBRARIES}") + string(REPLACE "-isysroot;" "-isysroot " + wxWidgets_LIBRARIES "${wxWidgets_LIBRARIES}") + + # extract linkdirs (-L) for rpath (i.e., LINK_DIRECTORIES) + string(REGEX MATCHALL "-L[^;]+" + wxWidgets_LIBRARY_DIRS "${wxWidgets_LIBRARIES}") + string(REGEX REPLACE "-L([^;]+)" "\\1" + wxWidgets_LIBRARY_DIRS "${wxWidgets_LIBRARY_DIRS}") + + DBG_MSG_V("wxWidgets_LIBRARIES=${wxWidgets_LIBRARIES}") + DBG_MSG_V("wxWidgets_LIBRARY_DIRS=${wxWidgets_LIBRARY_DIRS}") + + else() + set(wxWidgets_FOUND FALSE) + DBG_MSG("${wxWidgets_CONFIG_EXECUTABLE} --libs ${_cmp_req} ${_cmp_opt} FAILED with RET=${RET}") + endif() + unset(_cmp_req) + unset(_cmp_opt) + endif() + + # When using wx-config in MSYS, the include paths are UNIX style paths which may or may + # not work correctly depending on you MSYS/MinGW configuration. CMake expects native + # paths internally. + if(wxWidgets_FOUND AND MSYS) + find_program(_cygpath_exe cygpath ONLY_CMAKE_FIND_ROOT_PATH) + DBG_MSG_V("_cygpath_exe: ${_cygpath_exe}") + if(_cygpath_exe) + set(_tmp_path "") + foreach(_path ${wxWidgets_INCLUDE_DIRS}) + execute_process( + COMMAND cygpath -w ${_path} + OUTPUT_VARIABLE _native_path + RESULT_VARIABLE _retv + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + ) + if(_retv EQUAL 0) + file(TO_CMAKE_PATH ${_native_path} _native_path) + DBG_MSG_V("Path ${_path} converted to ${_native_path}") + string(APPEND _tmp_path " ${_native_path}") + endif() + endforeach() + DBG_MSG("Setting wxWidgets_INCLUDE_DIRS = ${_tmp_path}") + set(wxWidgets_INCLUDE_DIRS ${_tmp_path}) + separate_arguments(wxWidgets_INCLUDE_DIRS) + list(REMOVE_ITEM wxWidgets_INCLUDE_DIRS "") + + set(_tmp_path "") + foreach(_path ${wxWidgets_LIBRARY_DIRS}) + execute_process( + COMMAND cygpath -w ${_path} + OUTPUT_VARIABLE _native_path + RESULT_VARIABLE _retv + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + ) + if(_retv EQUAL 0) + file(TO_CMAKE_PATH ${_native_path} _native_path) + DBG_MSG_V("Path ${_path} converted to ${_native_path}") + string(APPEND _tmp_path " ${_native_path}") + endif() + endforeach() + DBG_MSG("Setting wxWidgets_LIBRARY_DIRS = ${_tmp_path}") + set(wxWidgets_LIBRARY_DIRS ${_tmp_path}) + separate_arguments(wxWidgets_LIBRARY_DIRS) + list(REMOVE_ITEM wxWidgets_LIBRARY_DIRS "") + endif() + unset(_cygpath_exe CACHE) + endif() +endif() + +# Check that all libraries are present, as wx-config does not check it +set(_wx_lib_missing "") +foreach(_wx_lib_ ${wxWidgets_LIBRARIES}) + if("${_wx_lib_}" MATCHES "^-l(.*)") + set(_wx_lib_name "${CMAKE_MATCH_1}") + unset(_wx_lib_found CACHE) + find_library(_wx_lib_found NAMES ${_wx_lib_name} HINTS ${wxWidgets_LIBRARY_DIRS}) + if(_wx_lib_found STREQUAL _wx_lib_found-NOTFOUND) + list(APPEND _wx_lib_missing ${_wx_lib_name}) + endif() + unset(_wx_lib_found CACHE) + endif() +endforeach() + +if (_wx_lib_missing) + string(REPLACE ";" " " _wx_lib_missing "${_wx_lib_missing}") + DBG_MSG_V("wxWidgets not found due to following missing libraries: ${_wx_lib_missing}") + set(wxWidgets_FOUND FALSE) + unset(wxWidgets_LIBRARIES) +endif() +unset(_wx_lib_missing) + +# Check if a specific version was requested by find_package(). +if(wxWidgets_FOUND) + wx_extract_version() +endif() + +# Debug output: +DBG_MSG("wxWidgets_FOUND : ${wxWidgets_FOUND}") +DBG_MSG("wxWidgets_INCLUDE_DIRS : ${wxWidgets_INCLUDE_DIRS}") +DBG_MSG("wxWidgets_LIBRARY_DIRS : ${wxWidgets_LIBRARY_DIRS}") +DBG_MSG("wxWidgets_LIBRARIES : ${wxWidgets_LIBRARIES}") +DBG_MSG("wxWidgets_CXX_FLAGS : ${wxWidgets_CXX_FLAGS}") +DBG_MSG("wxWidgets_USE_FILE : ${wxWidgets_USE_FILE}") + +#===================================================================== +#===================================================================== + +include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs_SLIC3R.cmake) + +# FIXME: set wxWidgets__FOUND for wx-config branch +# and use HANDLE_COMPONENTS on Unix too +if(wxWidgets_FIND_STYLE STREQUAL "win32") + set(wxWidgets_HANDLE_COMPONENTS "HANDLE_COMPONENTS") +endif() + +find_package_handle_standard_args(wxWidgets + REQUIRED_VARS wxWidgets_LIBRARIES wxWidgets_INCLUDE_DIRS + VERSION_VAR wxWidgets_VERSION_STRING + ${wxWidgets_HANDLE_COMPONENTS} + ) +unset(wxWidgets_HANDLE_COMPONENTS) + +if(wxWidgets_FOUND AND NOT TARGET wxWidgets::wxWidgets) + add_library(wxWidgets::wxWidgets INTERFACE IMPORTED) + target_link_libraries(wxWidgets::wxWidgets INTERFACE ${wxWidgets_LIBRARIES}) + target_link_directories(wxWidgets::wxWidgets INTERFACE ${wxWidgets_LIBRARY_DIRS}) + target_include_directories(wxWidgets::wxWidgets INTERFACE ${wxWidgets_INCLUDE_DIRS}) + target_compile_options(wxWidgets::wxWidgets INTERFACE ${wxWidgets_CXX_FLAGS}) + target_compile_definitions(wxWidgets::wxWidgets INTERFACE ${wxWidgets_DEFINITIONS}) + # FIXME: Add "$<$:${wxWidgets_DEFINITIONS_DEBUG}>" + # if the debug library variant is available. +endif() + +#===================================================================== +# Macros for use in wxWidgets apps. +# - This module will not fail to find wxWidgets based on the code +# below. Hence, it's required to check for validity of: +# +# wxWidgets_wxrc_EXECUTABLE +#===================================================================== + +# Resource file compiler. +find_program(wxWidgets_wxrc_EXECUTABLE + NAMES $ENV{WXRC_CMD} wxrc + PATHS ${wxWidgets_ROOT_DIR}/utils/wxrc/vc_msw + DOC "Location of wxWidgets resource file compiler binary (wxrc)" + ) + +# +# WX_SPLIT_ARGUMENTS_ON( ...) +# +# Sets and to contain arguments to the left and right, +# respectively, of . +# +# Example usage: +# function(WXWIDGETS_ADD_RESOURCES outfiles) +# WX_SPLIT_ARGUMENTS_ON(OPTIONS wxrc_files wxrc_options ${ARGN}) +# ... +# endfunction() +# +# WXWIDGETS_ADD_RESOURCES(sources ${xrc_files} OPTIONS -e -o file.C) +# +# NOTE: This is a generic piece of code that should be renamed to +# SPLIT_ARGUMENTS_ON and put in a file serving the same purpose as +# FindPackageStandardArgs.cmake. At the time of this writing +# FindQt4.cmake has a QT4_EXTRACT_OPTIONS, which I basically copied +# here a bit more generalized. So, there are already two find modules +# using this approach. +# +function(WX_SPLIT_ARGUMENTS_ON _keyword _leftvar _rightvar) + # FIXME: Document that the input variables will be cleared. + #list(APPEND ${_leftvar} "") + #list(APPEND ${_rightvar} "") + set(${_leftvar} "") + set(${_rightvar} "") + + set(_doing_right FALSE) + foreach(element ${ARGN}) + if("${element}" STREQUAL "${_keyword}") + set(_doing_right TRUE) + else() + if(_doing_right) + list(APPEND ${_rightvar} "${element}") + else() + list(APPEND ${_leftvar} "${element}") + endif() + endif() + endforeach() + + set(${_leftvar} ${${_leftvar}} PARENT_SCOPE) + set(${_rightvar} ${${_rightvar}} PARENT_SCOPE) +endfunction() + +# +# WX_GET_DEPENDENCIES_FROM_XML( +# +# +# +# +# +# ) +# +# FIXME: Add documentation here... +# +function(WX_GET_DEPENDENCIES_FROM_XML + _depends + _match_patt + _clean_patt + _xml_contents + _depends_path + ) + + string(REGEX MATCHALL + ${_match_patt} + dep_file_list + "${${_xml_contents}}" + ) + foreach(dep_file ${dep_file_list}) + string(REGEX REPLACE ${_clean_patt} "" dep_file "${dep_file}") + + # make the file have an absolute path + if(NOT IS_ABSOLUTE "${dep_file}") + set(dep_file "${${_depends_path}}/${dep_file}") + endif() + + # append file to dependency list + list(APPEND ${_depends} "${dep_file}") + endforeach() + + set(${_depends} ${${_depends}} PARENT_SCOPE) +endfunction() + +# +# WXWIDGETS_ADD_RESOURCES( +# OPTIONS [NO_CPP_CODE]) +# +# Adds a custom command for resource file compilation of the +# and appends the output files to . +# +# Example usages: +# WXWIDGETS_ADD_RESOURCES(sources xrc/main_frame.xrc) +# WXWIDGETS_ADD_RESOURCES(sources ${xrc_files} OPTIONS -e -o altname.cxx) +# +function(WXWIDGETS_ADD_RESOURCES _outfiles) + WX_SPLIT_ARGUMENTS_ON(OPTIONS rc_file_list rc_options ${ARGN}) + + # Parse files for dependencies. + set(rc_file_list_abs "") + set(rc_depends "") + foreach(rc_file ${rc_file_list}) + get_filename_component(depends_path ${rc_file} PATH) + + get_filename_component(rc_file_abs ${rc_file} ABSOLUTE) + list(APPEND rc_file_list_abs "${rc_file_abs}") + + # All files have absolute paths or paths relative to the location + # of the rc file. + file(READ "${rc_file_abs}" rc_file_contents) + + # get bitmap/bitmap2 files + WX_GET_DEPENDENCIES_FROM_XML( + rc_depends + "]*>" + rc_file_contents + depends_path + ) + + # get url files + WX_GET_DEPENDENCIES_FROM_XML( + rc_depends + "]*>" + rc_file_contents + depends_path + ) + + # get wxIcon files + WX_GET_DEPENDENCIES_FROM_XML( + rc_depends + "]*class=\"wxIcon\"[^<]+" + "^]*>" + rc_file_contents + depends_path + ) + endforeach() + + # + # Parse options. + # + # If NO_CPP_CODE option specified, then produce .xrs file rather + # than a .cpp file (i.e., don't add the default --cpp-code option). + list(FIND rc_options NO_CPP_CODE index) + if(index EQUAL -1) + list(APPEND rc_options --cpp-code) + # wxrc's default output filename for cpp code. + set(outfile resource.cpp) + else() + list(REMOVE_AT rc_options ${index}) + # wxrc's default output filename for xrs file. + set(outfile resource.xrs) + endif() + + # Get output name for use in ADD_CUSTOM_COMMAND. + # - short option scanning + list(FIND rc_options -o index) + if(NOT index EQUAL -1) + math(EXPR filename_index "${index} + 1") + list(GET rc_options ${filename_index} outfile) + #list(REMOVE_AT rc_options ${index} ${filename_index}) + endif() + # - long option scanning + string(REGEX MATCH "--output=[^;]*" outfile_opt "${rc_options}") + if(outfile_opt) + string(REPLACE "--output=" "" outfile "${outfile_opt}") + endif() + #string(REGEX REPLACE "--output=[^;]*;?" "" rc_options "${rc_options}") + #string(REGEX REPLACE ";$" "" rc_options "${rc_options}") + + if(NOT IS_ABSOLUTE "${outfile}") + set(outfile "${CMAKE_CURRENT_BINARY_DIR}/${outfile}") + endif() + add_custom_command( + OUTPUT "${outfile}" + COMMAND ${wxWidgets_wxrc_EXECUTABLE} ${rc_options} ${rc_file_list_abs} + DEPENDS ${rc_file_list_abs} ${rc_depends} + ) + + # Add generated header to output file list. + list(FIND rc_options -e short_index) + list(FIND rc_options --extra-cpp-code long_index) + if(NOT short_index EQUAL -1 OR NOT long_index EQUAL -1) + get_filename_component(outfile_ext ${outfile} EXT) + string(REPLACE "${outfile_ext}" ".h" outfile_header "${outfile}") + list(APPEND ${_outfiles} "${outfile_header}") + set_source_files_properties( + "${outfile_header}" PROPERTIES GENERATED TRUE + ) + endif() + + # Add generated file to output file list. + list(APPEND ${_outfiles} "${outfile}") + + set(${_outfiles} ${${_outfiles}} PARENT_SCOPE) +endfunction() diff --git a/resources/icons/snap.svg b/resources/icons/snap.svg new file mode 100644 index 0000000000..0242c8e3bc --- /dev/null +++ b/resources/icons/snap.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fec0d4cf70..dc23da212a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -49,26 +49,12 @@ if (SLIC3R_GUI) if (CMAKE_SYSTEM_NAME STREQUAL "Linux") set (wxWidgets_CONFIG_OPTIONS "--toolkit=gtk${SLIC3R_GTK}") - find_package(wxWidgets 3.2 QUIET COMPONENTS base core adv html gl) - if (NOT wxWidgets_FOUND) - message(FATAL_ERROR "Could not find wxWidgets >= 3.2") - endif () - - if (NOT "${wxWidgets_USE_FILE}" STREQUAL "") - include(${wxWidgets_USE_FILE}) - endif () - else () - find_package(wxWidgets 3.2 COMPONENTS html adv gl core base) - if (NOT wxWidgets_FOUND) - message(STATUS "Trying to find wxWidgets in CONFIG mode...") - find_package(wxWidgets 3.2 CONFIG REQUIRED COMPONENTS html adv gl core base) - slic3r_remap_configs(wx::wxhtml wx::wxadv wx::wxgl wx::wxcore wx::wxbase RelWithDebInfo Release) - else () - if (NOT "${wxWidgets_USE_FILE}" STREQUAL "") - include(${wxWidgets_USE_FILE}) - endif () - endif () endif () + find_package(wxWidgets 3.2 MODULE REQUIRED COMPONENTS base core adv html gl) + + include(${wxWidgets_USE_FILE}) + + slic3r_remap_configs(wx::wxhtml wx::wxadv wx::wxgl wx::wxcore wx::wxbase RelWithDebInfo Release) if(UNIX) message(STATUS "wx-config path: ${wxWidgets_CONFIG_EXECUTABLE}") diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index e600f343ca..480f42811e 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -40,6 +40,7 @@ #include "libslic3r/Geometry.hpp" #include "libslic3r/GCode/PostProcessor.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/CutUtils.hpp" #include "libslic3r/ModelArrange.hpp" #include "libslic3r/Platform.hpp" #include "libslic3r/Print.hpp" @@ -437,8 +438,11 @@ int CLI::run(int argc, char **argv) } #else // model.objects.front()->cut(0, m_config.opt_float("cut"), ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::FlipLower); - model.objects.front()->cut(0, Geometry::translation_transform(m_config.opt_float("cut") * Vec3d::UnitZ()), + Cut cut(model.objects.front(), 0, Geometry::translation_transform(m_config.opt_float("cut") * Vec3d::UnitZ()), ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::PlaceOnCutUpper); + auto cut_objects = cut.perform_with_plane(); + for (ModelObject* obj : cut_objects) + model.add_object(*obj); #endif model.delete_object(size_t(0)); } diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h index 5aed978426..99d933ea87 100644 --- a/src/imgui/imconfig.h +++ b/src/imgui/imconfig.h @@ -152,6 +152,7 @@ namespace ImGui // const wchar_t MmuSegmentationMarker = 0x1F; const wchar_t PlugMarker = 0x1C; const wchar_t DowelMarker = 0x1D; + const wchar_t SnapMarker = 0x1E; // Do not forget use following letters only in wstring const wchar_t DocumentationButton = 0x2600; const wchar_t DocumentationHoverButton = 0x2601; diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index de4ec3a5d7..148fac7587 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -206,6 +206,8 @@ set(SLIC3R_SOURCES BlacklistedLibraryCheck.hpp LocalesUtils.cpp LocalesUtils.hpp + CutUtils.cpp + CutUtils.hpp Model.cpp Model.hpp ModelArrange.hpp diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp new file mode 100644 index 0000000000..1622af87e1 --- /dev/null +++ b/src/libslic3r/CutUtils.cpp @@ -0,0 +1,645 @@ + +#include "CutUtils.hpp" +#include "Geometry.hpp" +#include "libslic3r.h" +#include "Model.hpp" +#include "TriangleMeshSlicer.hpp" +#include "TriangleSelector.hpp" +#include "ObjectID.hpp" + + +namespace Slic3r { + +using namespace Geometry; + +static void apply_tolerance(ModelVolume* vol) +{ + ModelVolume::CutInfo& cut_info = vol->cut_info; + + assert(cut_info.is_connector); + if (!cut_info.is_processed) + return; + + Vec3d sf = vol->get_scaling_factor(); + + // make a "hole" wider + sf[X] += double(cut_info.radius_tolerance); + sf[Y] += double(cut_info.radius_tolerance); + + // make a "hole" dipper + sf[Z] += double(cut_info.height_tolerance); + + vol->set_scaling_factor(sf); + + // correct offset in respect to the new depth + Vec3d rot_norm = rotation_transform(vol->get_rotation()) * Vec3d::UnitZ(); + if (rot_norm.norm() != 0.0) + rot_norm.normalize(); + + double z_offset = 0.5 * static_cast(cut_info.height_tolerance); + if (cut_info.connector_type == CutConnectorType::Plug || + cut_info.connector_type == CutConnectorType::Snap) + z_offset -= 0.05; // add small Z offset to better preview + + vol->set_offset(vol->get_offset() + rot_norm * z_offset); +} + +static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelVolume* src_volume, const Transform3d& cut_matrix, const std::string& suffix = {}, ModelVolumeType type = ModelVolumeType::MODEL_PART) +{ + if (mesh.empty()) + return; + + mesh.transform(cut_matrix); + ModelVolume* vol = object->add_volume(mesh); + vol->set_type(type); + + vol->name = src_volume->name + suffix; + // Don't copy the config's ID. + vol->config.assign_config(src_volume->config); + assert(vol->config.id().valid()); + assert(vol->config.id() != src_volume->config.id()); + vol->set_material(src_volume->material_id(), *src_volume->material()); + vol->cut_info = src_volume->cut_info; +} + +static void process_volume_cut( ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, + ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh) +{ + const auto volume_matrix = volume->get_matrix(); + + const Transformation cut_transformation = Transformation(cut_matrix); + const Transform3d invert_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1 * cut_transformation.get_offset()); + + // Transform the mesh by the combined transformation matrix. + // Flip the triangles in case the composite transformation is left handed. + TriangleMesh mesh(volume->mesh()); + mesh.transform(invert_cut_matrix * instance_matrix * volume_matrix, true); + + indexed_triangle_set upper_its, lower_its; + cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its); + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) + upper_mesh = TriangleMesh(upper_its); + if (attributes.has(ModelObjectCutAttribute::KeepLower)) + lower_mesh = TriangleMesh(lower_its); +} + +static void process_connector_cut( ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, + ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, + std::vector& dowels) +{ + assert(volume->cut_info.is_connector); + volume->cut_info.set_processed(); + + const auto volume_matrix = volume->get_matrix(); + + // ! Don't apply instance transformation for the conntectors. + // This transformation is already there + if (volume->cut_info.connector_type != CutConnectorType::Dowel) { + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { + ModelVolume* vol = nullptr; + if (volume->cut_info.connector_type == CutConnectorType::Snap) { + TriangleMesh mesh = TriangleMesh(its_make_cylinder(1.0, 1.0, PI / 180.)); + + vol = upper->add_volume(std::move(mesh)); + vol->set_transformation(volume->get_transformation()); + vol->set_type(ModelVolumeType::NEGATIVE_VOLUME); + + vol->cut_info = volume->cut_info; + vol->name = volume->name; + } + else + vol = upper->add_volume(*volume); + + vol->set_transformation(volume_matrix); + apply_tolerance(vol); + } + if (attributes.has(ModelObjectCutAttribute::KeepLower)) { + ModelVolume* vol = lower->add_volume(*volume); + vol->set_transformation(volume_matrix); + // for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug + vol->set_type(ModelVolumeType::MODEL_PART); + } + } + else { + if (attributes.has(ModelObjectCutAttribute::CreateDowels)) { + ModelObject* dowel{ nullptr }; + // Clone the object to duplicate instances, materials etc. + volume->get_object()->clone_for_cut(&dowel); + + // add one more solid part same as connector if this connector is a dowel + ModelVolume* vol = dowel->add_volume(*volume); + vol->set_type(ModelVolumeType::MODEL_PART); + + // But discard rotation and Z-offset for this volume + vol->set_rotation(Vec3d::Zero()); + vol->set_offset(Z, 0.0); + + dowels.push_back(dowel); + } + + // Cut the dowel + apply_tolerance(volume); + + // Perform cut + TriangleMesh upper_mesh, lower_mesh; + process_volume_cut(volume, Transform3d::Identity(), cut_matrix, attributes, upper_mesh, lower_mesh); + + // add small Z offset to better preview + upper_mesh.translate((-0.05 * Vec3d::UnitZ()).cast()); + lower_mesh.translate((0.05 * Vec3d::UnitZ()).cast()); + + // Add cut parts to the related objects + add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A", volume->type()); + add_cut_volume(lower_mesh, lower, volume, cut_matrix, "_B", volume->type()); + } +} + +static void process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix, + ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower) +{ + const auto volume_matrix = instance_matrix * volume->get_matrix(); + + // Modifiers are not cut, but we still need to add the instance transformation + // to the modifier volume transformation to preserve their shape properly. + volume->set_transformation(Transformation(volume_matrix)); + + if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) { + upper->add_volume(*volume); + return; + } + + // Some logic for the negative volumes/connectors. Add only needed modifiers + auto bb = volume->mesh().transformed_bounding_box(inverse_cut_matrix * volume_matrix); + bool is_crossed_by_cut = bb.min[Z] <= 0 && bb.max[Z] >= 0; + if (attributes.has(ModelObjectCutAttribute::KeepUpper) && (bb.min[Z] >= 0 || is_crossed_by_cut)) + upper->add_volume(*volume); + if (attributes.has(ModelObjectCutAttribute::KeepLower) && (bb.max[Z] <= 0 || is_crossed_by_cut)) + lower->add_volume(*volume); +} + +static void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, + ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower) +{ + // Perform cut + TriangleMesh upper_mesh, lower_mesh; + process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh); + + // Add required cut parts to the objects + + if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) { + add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A"); + if (!lower_mesh.empty()) { + add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B"); + upper->volumes.back()->cut_info.is_from_upper = false; + } + return; + } + + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) + add_cut_volume(upper_mesh, upper, volume, cut_matrix); + + if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) + add_cut_volume(lower_mesh, lower, volume, cut_matrix); +} + +static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx, + const Transform3d& cut_matrix = Transform3d::Identity(), + bool place_on_cut = false, bool flip = false) +{ + // Reset instance transformation except offset and Z-rotation + + for (size_t i = 0; i < object->instances.size(); ++i) { + auto& obj_instance = object->instances[i]; + const double rot_z = obj_instance->get_rotation().z(); + + Transformation inst_trafo = Transformation(obj_instance->get_transformation().get_matrix_no_scaling_factor()); + // add respect to mirroring + if (obj_instance->is_left_handed()) + inst_trafo = inst_trafo * Transformation(scale_transform(Vec3d(-1, 1, 1))); + + obj_instance->set_transformation(inst_trafo); + + Vec3d rotation = Vec3d::Zero(); + if (!flip && !place_on_cut) { + if ( i != src_instance_idx) + rotation[Z] = rot_z; + } + else { + Transform3d rotation_matrix = Transform3d::Identity(); + if (flip) + rotation_matrix = rotation_transform(PI * Vec3d::UnitX()); + + if (place_on_cut) + rotation_matrix = rotation_matrix * Transformation(cut_matrix).get_rotation_matrix().inverse(); + + if (i != src_instance_idx) + rotation_matrix = rotation_transform(rot_z * Vec3d::UnitZ()) * rotation_matrix; + + rotation = Transformation(rotation_matrix).get_rotation(); + } + + obj_instance->set_rotation(rotation); + } +} + + +Cut::Cut(const ModelObject* object, int instance, const Transform3d& cut_matrix, + ModelObjectCutAttributes attributes/*= ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepAsParts*/) + : m_instance(instance), m_cut_matrix(cut_matrix), m_attributes(attributes) +{ + m_model = Model(); + if (object) + m_model.add_object(*object); +} + +void Cut::post_process(ModelObject* object, ModelObjectPtrs& cut_object_ptrs, bool keep, bool place_on_cut, bool flip) +{ + if (!object) return; + + if (keep && !object->volumes.empty()) { + reset_instance_transformation(object, m_instance, m_cut_matrix, place_on_cut, flip); + cut_object_ptrs.push_back(object); + } + else + m_model.objects.push_back(object); // will be deleted in m_model.clear_objects(); +} + +void Cut::post_process(ModelObject* upper, ModelObject* lower, ModelObjectPtrs& cut_object_ptrs) +{ + post_process(upper, cut_object_ptrs, + m_attributes.has(ModelObjectCutAttribute::KeepUpper), + m_attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper), + m_attributes.has(ModelObjectCutAttribute::FlipUpper)); + + post_process(lower, cut_object_ptrs, + m_attributes.has(ModelObjectCutAttribute::KeepLower), + m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower), + m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || m_attributes.has(ModelObjectCutAttribute::FlipLower)); +} + + +void Cut::finalize(const ModelObjectPtrs& objects) +{ + //clear model from temporarry objects + m_model.clear_objects(); + + // add to model result objects + m_model.objects = objects; +} + + +const ModelObjectPtrs& Cut::perform_with_plane() +{ + if (!m_attributes.has(ModelObjectCutAttribute::KeepUpper) && !m_attributes.has(ModelObjectCutAttribute::KeepLower)) { + m_model.clear_objects(); + return m_model.objects; + } + + ModelObject* mo = m_model.objects.front(); + + BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start"; + + // Clone the object to duplicate instances, materials etc. + ModelObject* upper{ nullptr }; + if (m_attributes.has(ModelObjectCutAttribute::KeepUpper)) + mo->clone_for_cut(&upper); + + ModelObject* lower{ nullptr }; + if (m_attributes.has(ModelObjectCutAttribute::KeepLower) && !m_attributes.has(ModelObjectCutAttribute::KeepAsParts)) + mo->clone_for_cut(&lower); + + std::vector dowels; + + // Because transformations are going to be applied to meshes directly, + // we reset transformation of all instances and volumes, + // except for translation and Z-rotation on instances, which are preserved + // in the transformation matrix and not applied to the mesh transform. + + const auto instance_matrix = mo->instances[m_instance]->get_transformation().get_matrix_no_offset(); + const Transformation cut_transformation = Transformation(m_cut_matrix); + const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1. * cut_transformation.get_offset()); + + for (ModelVolume* volume : mo->volumes) { + volume->reset_extra_facets(); + + if (!volume->is_model_part()) { + if (volume->cut_info.is_processed) + process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, m_attributes, upper, lower); + else + process_connector_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower, dowels); + } + else if (!volume->mesh().empty()) + process_solid_part_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower); + } + + // Post-process cut parts + + if (m_attributes.has(ModelObjectCutAttribute::KeepAsParts) && upper->volumes.empty()) { + m_model = Model(); + m_model.objects.push_back(upper); + return m_model.objects; + } + + ModelObjectPtrs cut_object_ptrs; + + if (m_attributes.has(ModelObjectCutAttribute::KeepAsParts) && !upper->volumes.empty()) { + reset_instance_transformation(upper, m_instance, m_cut_matrix); + cut_object_ptrs.push_back(upper); + } + else { + // Delete all modifiers which are not intersecting with solid parts bounding box + auto delete_extra_modifiers = [this](ModelObject* mo) { + if (!mo) return; + const BoundingBoxf3 obj_bb = mo->instance_bounding_box(m_instance); + const Transform3d inst_matrix = mo->instances[m_instance]->get_transformation().get_matrix(); + + for (int i = int(mo->volumes.size()) - 1; i >= 0; --i) + if (const ModelVolume* vol = mo->volumes[i]; + !vol->is_model_part() && !vol->is_cut_connector()) { + auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix()); + if (!obj_bb.intersects(bb)) + mo->delete_volume(i); + } + }; + + post_process(upper, lower, cut_object_ptrs); + delete_extra_modifiers(upper); + delete_extra_modifiers(lower); + + if (m_attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) { + for (auto dowel : dowels) { + reset_instance_transformation(dowel, m_instance); + dowel->name += "-Dowel-" + dowel->volumes[0]->name; + cut_object_ptrs.push_back(dowel); + } + } + } + + BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end"; + + finalize(cut_object_ptrs); + + return m_model.objects; +} + +static void distribute_modifiers_from_object(ModelObject* from_obj, const int instance_idx, ModelObject* to_obj1, ModelObject* to_obj2) +{ + auto obj1_bb = to_obj1 ? to_obj1->instance_bounding_box(instance_idx) : BoundingBoxf3(); + auto obj2_bb = to_obj2 ? to_obj2->instance_bounding_box(instance_idx) : BoundingBoxf3(); + const Transform3d inst_matrix = from_obj->instances[instance_idx]->get_transformation().get_matrix(); + + for (ModelVolume* vol : from_obj->volumes) + if (!vol->is_model_part()) { + auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix()); + // Don't add modifiers which are not intersecting with solid parts + if (obj1_bb.intersects(bb)) + to_obj1->add_volume(*vol); + if (obj2_bb.intersects(bb)) + to_obj2->add_volume(*vol); + } +} + +static void merge_solid_parts_inside_object(ModelObjectPtrs& objects) +{ + for (ModelObject* mo : objects) { + TriangleMesh mesh; + // Merge all SolidPart but not Connectors + for (const ModelVolume* mv : mo->volumes) { + if (mv->is_model_part() && !mv->is_cut_connector()) { + TriangleMesh m = mv->mesh(); + m.transform(mv->get_matrix()); + mesh.merge(m); + } + } + if (!mesh.empty()) { + ModelVolume* new_volume = mo->add_volume(mesh); + new_volume->name = mo->name; + // Delete all merged SolidPart but not Connectors + for (int i = int(mo->volumes.size()) - 2; i >= 0; --i) { + const ModelVolume* mv = mo->volumes[i]; + if (mv->is_model_part() && !mv->is_cut_connector()) + mo->delete_volume(i); + } + } + } +} + + +const ModelObjectPtrs& Cut::perform_by_contour(std::vector parts, int dowels_count) +{ + ModelObject* cut_mo = m_model.objects.front(); + + // Clone the object to duplicate instances, materials etc. + ModelObject* upper{ nullptr }; + if (m_attributes.has(ModelObjectCutAttribute::KeepUpper)) cut_mo->clone_for_cut(&upper); + ModelObject* lower{ nullptr }; + if (m_attributes.has(ModelObjectCutAttribute::KeepLower)) cut_mo->clone_for_cut(&lower); + + const size_t cut_parts_cnt = parts.size(); + bool has_modifiers = false; + + // Distribute SolidParts to the Upper/Lower object + for (size_t id = 0; id < cut_parts_cnt; ++id) { + if (parts[id].is_modifier) + has_modifiers = true; // modifiers will be added later to the related parts + else if (ModelObject* obj = (parts[id].selected ? upper : lower)) + obj->add_volume(*(cut_mo->volumes[id])); + } + + if (has_modifiers) { + // Distribute Modifiers to the Upper/Lower object + distribute_modifiers_from_object(cut_mo, m_instance, upper, lower); + } + + ModelObjectPtrs cut_object_ptrs; + + ModelVolumePtrs& volumes = cut_mo->volumes; + if (volumes.size() == cut_parts_cnt) { + // Means that object is cut without connectors + + // Just add Upper and Lower objects to cut_object_ptrs + post_process(upper, lower, cut_object_ptrs); + } + else if (volumes.size() > cut_parts_cnt) { + // Means that object is cut with connectors + + // All volumes are distributed to Upper / Lower object, + // So we don’t need them anymore + for (size_t id = 0; id < cut_parts_cnt; id++) + delete* (volumes.begin() + id); + volumes.erase(volumes.begin(), volumes.begin() + cut_parts_cnt); + + // Perform cut just to get connectors + Cut cut(cut_mo, m_instance, m_cut_matrix, m_attributes); + const ModelObjectPtrs& cut_connectors_obj = cut.perform_with_plane(); + assert(dowels_count > 0 ? cut_connectors_obj.size() >= 3 : cut_connectors_obj.size() == 2); + + // Connectors from upper object + for (const ModelVolume* volume : cut_connectors_obj[0]->volumes) + upper->add_volume(*volume, volume->type()); + + // Connectors from lower object + for (const ModelVolume* volume : cut_connectors_obj[1]->volumes) + lower->add_volume(*volume, volume->type()); + + // Add Upper and Lower objects to cut_object_ptrs + post_process(upper, lower, cut_object_ptrs); + + // Add Dowel-connectors as separate objects to cut_object_ptrs + if (cut_connectors_obj.size() >= 3) + for (size_t id = 2; id < cut_connectors_obj.size(); id++) + cut_object_ptrs.push_back(cut_connectors_obj[id]); + } + + // Now merge all model parts together: + merge_solid_parts_inside_object(cut_object_ptrs); + + finalize(cut_object_ptrs); + + return m_model.objects; +} + + +const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Transform3d& rotation_m, bool keep_as_parts/* = false*/) +{ + ModelObject* cut_mo = m_model.objects.front(); + + // Clone the object to duplicate instances, materials etc. + ModelObject* upper{ nullptr }; + cut_mo->clone_for_cut(&upper); + ModelObject* lower{ nullptr }; + cut_mo->clone_for_cut(&lower); + + const double groove_half_depth = 0.5 * double(groove.depth); + + Model tmp_model_for_cut = Model(); + + Model tmp_model = Model(); + tmp_model.add_object(*cut_mo); + ModelObject* tmp_object = tmp_model.objects.front(); + + auto add_volumes_from_cut = [](ModelObject* object, const ModelObjectCutAttribute attribute, const Model& tmp_model_for_cut) { + const auto& volumes = tmp_model_for_cut.objects.front()->volumes; + for (const ModelVolume* volume : volumes) + if (volume->is_model_part()) { + if ((attribute == ModelObjectCutAttribute::KeepUpper && volume->is_from_upper()) || + (attribute != ModelObjectCutAttribute::KeepUpper && !volume->is_from_upper())) { + ModelVolume* new_vol = object->add_volume(*volume); + new_vol->reset_from_upper(); + } + } + }; + + auto cut = [this, add_volumes_from_cut] + (ModelObject* object, const Transform3d& cut_matrix, const ModelObjectCutAttribute add_volumes_attribute, Model& tmp_model_for_cut) { + Cut cut(object, m_instance, cut_matrix); + + tmp_model_for_cut = Model(); + tmp_model_for_cut.add_object(*cut.perform_with_plane().front()); + assert(!tmp_model_for_cut.objects.empty()); + + object->clear_volumes(); + add_volumes_from_cut(object, add_volumes_attribute, tmp_model_for_cut); + reset_instance_transformation(object, m_instance); + }; + + // cut by upper plane + + const Transform3d cut_matrix_upper = translation_transform(rotation_m * (groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix; + { + cut(tmp_object, cut_matrix_upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + } + + // cut by lower plane + + const Transform3d cut_matrix_lower = translation_transform(rotation_m * (-groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix; + { + cut(tmp_object, cut_matrix_lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + } + + // cut middle part with 2 angles and add parts to related upper/lower objects + + const double h_side_shift = 0.5 * double(groove.width + groove.depth / tan(groove.flaps_angle)); + + // cut by angle1 plane + { + const Transform3d cut_matrix_angle1 = translation_transform(rotation_m * (-h_side_shift * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle)); + + cut(tmp_object, cut_matrix_angle1, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + } + + // cut by angle2 plane + { + const Transform3d cut_matrix_angle2 = translation_transform(rotation_m * (h_side_shift * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle)); + + cut(tmp_object, cut_matrix_angle2, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + } + + // apply tolerance to the middle part + { + const double h_groove_shift_tolerance = groove_half_depth - (double)groove.depth_tolerance; + + const Transform3d cut_matrix_lower_tolerance = translation_transform(rotation_m * (-h_groove_shift_tolerance * Vec3d::UnitZ())) * m_cut_matrix; + cut(tmp_object, cut_matrix_lower_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + + const double h_side_shift_tolerance = h_side_shift - 0.5 * double(groove.width_tolerance); + + const Transform3d cut_matrix_angle1_tolerance = translation_transform(rotation_m * (-h_side_shift_tolerance * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle)); + cut(tmp_object, cut_matrix_angle1_tolerance, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + + const Transform3d cut_matrix_angle2_tolerance = translation_transform(rotation_m * (h_side_shift_tolerance * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle)); + cut(tmp_object, cut_matrix_angle2_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + } + + // this part can be added to the upper object now + add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + + ModelObjectPtrs cut_object_ptrs; + + if (keep_as_parts) { + // add volumes from lower object to the upper, but mark them as a lower + const auto& volumes = lower->volumes; + for (const ModelVolume* volume : volumes) { + ModelVolume* new_vol = upper->add_volume(*volume); + new_vol->cut_info.is_from_upper = false; + } + + // add modifiers + for (const ModelVolume* volume : cut_mo->volumes) + if (!volume->is_model_part()) + upper->add_volume(*volume); + + cut_object_ptrs.push_back(upper); + + // add lower object to the cut_object_ptrs just to correct delete it from the Model destructor and avoid memory leaks + cut_object_ptrs.push_back(lower); + } + else { + // add modifiers if object has any + for (const ModelVolume* volume : cut_mo->volumes) + if (!volume->is_model_part()) { + distribute_modifiers_from_object(cut_mo, m_instance, upper, lower); + break; + } + + assert(!upper->volumes.empty() && !lower->volumes.empty()); + + // Add Upper and Lower parts to cut_object_ptrs + + post_process(upper, lower, cut_object_ptrs); + + // Now merge all model parts together: + merge_solid_parts_inside_object(cut_object_ptrs); + } + + finalize(cut_object_ptrs); + + return m_model.objects; +} + +} // namespace Slic3r + diff --git a/src/libslic3r/CutUtils.hpp b/src/libslic3r/CutUtils.hpp new file mode 100644 index 0000000000..2c477a3e2b --- /dev/null +++ b/src/libslic3r/CutUtils.hpp @@ -0,0 +1,66 @@ +#ifndef slic3r_CutUtils_hpp_ +#define slic3r_CutUtils_hpp_ + +#include "enum_bitmask.hpp" +#include "Point.hpp" +#include "Model.hpp" + +#include + +namespace Slic3r { + +using ModelObjectPtrs = std::vector; + +enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, KeepAsParts, FlipUpper, FlipLower, PlaceOnCutUpper, PlaceOnCutLower, CreateDowels, InvalidateCutInfo }; +using ModelObjectCutAttributes = enum_bitmask; +ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute); + + +class Cut { + + Model m_model; + int m_instance; + const Transform3d m_cut_matrix; + ModelObjectCutAttributes m_attributes; + + void post_process(ModelObject* object, ModelObjectPtrs& objects, bool keep, bool place_on_cut, bool flip); + void post_process(ModelObject* upper_object, ModelObject* lower_object, ModelObjectPtrs& objects); + void finalize(const ModelObjectPtrs& objects); + +public: + + Cut(const ModelObject* object, int instance, const Transform3d& cut_matrix, + ModelObjectCutAttributes attributes = ModelObjectCutAttribute::KeepUpper | + ModelObjectCutAttribute::KeepLower | + ModelObjectCutAttribute::KeepAsParts ); + ~Cut() { m_model.clear_objects(); } + + struct Groove + { + float depth{ 0.f }; + float width{ 0.f }; + float flaps_angle{ 0.f }; + float angle{ 0.f }; + float depth_init{ 0.f }; + float width_init{ 0.f }; + float flaps_angle_init{ 0.f }; + float angle_init{ 0.f }; + float depth_tolerance{ 0.1f }; + float width_tolerance{ 0.1f }; + }; + + struct Part + { + bool selected; + bool is_modifier; + }; + + const ModelObjectPtrs& perform_with_plane(); + const ModelObjectPtrs& perform_by_contour(std::vector parts, int dowels_count); + const ModelObjectPtrs& perform_with_groove(const Groove& groove, const Transform3d& rotation_m, bool keep_as_parts = false); + +}; // namespace Cut + +} // namespace Slic3r + +#endif /* slic3r_CutUtils_hpp_ */ diff --git a/src/libslic3r/GCode/ExtrusionProcessor.hpp b/src/libslic3r/GCode/ExtrusionProcessor.hpp index 968ba4024f..5314e9afe4 100644 --- a/src/libslic3r/GCode/ExtrusionProcessor.hpp +++ b/src/libslic3r/GCode/ExtrusionProcessor.hpp @@ -146,67 +146,68 @@ std::vector estimate_points_properties(const POINTS std::vector angles_for_curvature(points.size()); std::vector distances_for_curvature(points.size()); - for (int point_idx = 0; point_idx < int(points.size()); ++point_idx) { + for (size_t point_idx = 0; point_idx < points.size(); ++point_idx) { ExtendedPoint &a = points[point_idx]; - ExtendedPoint &prev = points[point_idx > 0 ? point_idx - 1 : point_idx]; + size_t prev = prev_idx_modulo(point_idx, points.size()); + size_t next = next_idx_modulo(point_idx, points.size()); - int prev_point_idx = point_idx; - while (prev_point_idx > 0) { - prev_point_idx--; - if ((a.position - points[prev_point_idx].position).squaredNorm() > EPSILON) { - break; - } + int iter_limit = points.size(); + while ((a.position - points[prev].position).squaredNorm() < 1 && iter_limit > 0) { + prev = prev_idx_modulo(prev, points.size()); + iter_limit--; } - int next_point_index = point_idx; - while (next_point_index < int(points.size()) - 1) { - next_point_index++; - if ((a.position - points[next_point_index].position).squaredNorm() > EPSILON) { - break; - } + while ((a.position - points[next].position).squaredNorm() < 1 && iter_limit > 0) { + next = next_idx_modulo(next, points.size()); + iter_limit--; } - distances_for_curvature[point_idx] = (prev.position - a.position).norm(); - if (prev_point_idx != point_idx && next_point_index != point_idx) { - float alfa = angle(a.position - points[prev_point_idx].position, points[next_point_index].position - a.position); - angles_for_curvature[point_idx] = alfa; - } // else keep zero + distances_for_curvature[point_idx] = (points[prev].position - a.position).norm(); + float alfa = angle(a.position - points[prev].position, points[next].position - a.position); + angles_for_curvature[point_idx] = alfa; } - for (float window_size : {3.0f, 9.0f, 16.0f}) { - size_t tail_point = 0; - float tail_window_acc = 0; - float tail_angle_acc = 0; + if (std::accumulate(distances_for_curvature.begin(), distances_for_curvature.end(), 0) > EPSILON) + for (float window_size : {3.0f, 9.0f, 16.0f}) { + size_t tail_point = 0; + float tail_window_acc = 0; + float tail_angle_acc = 0; - size_t head_point = 0; - float head_window_acc = 0; - float head_angle_acc = 0; + size_t head_point = 0; + float head_window_acc = 0; + float head_angle_acc = 0; - for (int point_idx = 0; point_idx < int(points.size()); ++point_idx) { - if (point_idx > 0) { - tail_window_acc += distances_for_curvature[point_idx - 1]; - tail_angle_acc += angles_for_curvature[point_idx - 1]; - head_window_acc -= distances_for_curvature[point_idx - 1]; - head_angle_acc -= angles_for_curvature[point_idx - 1]; - } - while (tail_window_acc > window_size * 0.5 && int(tail_point) < point_idx) { - tail_window_acc -= distances_for_curvature[tail_point]; - tail_angle_acc -= angles_for_curvature[tail_point]; - tail_point++; - } + for (size_t point_idx = 0; point_idx < points.size(); ++point_idx) { + if (point_idx == 0) { + while (tail_window_acc < window_size * 0.5) { + tail_window_acc += distances_for_curvature[tail_point]; + tail_angle_acc += angles_for_curvature[tail_point]; + tail_point = prev_idx_modulo(tail_point, points.size()); + } + } + while (tail_window_acc - distances_for_curvature[next_idx_modulo(tail_point, points.size())] > window_size * 0.5) { + tail_point = next_idx_modulo(tail_point, points.size()); + tail_window_acc -= distances_for_curvature[tail_point]; + tail_angle_acc -= angles_for_curvature[tail_point]; + } - while (head_window_acc < window_size * 0.5 && int(head_point) < int(points.size()) - 1) { - head_window_acc += distances_for_curvature[head_point]; - head_angle_acc += angles_for_curvature[head_point]; - head_point++; - } + while (head_window_acc < window_size * 0.5) { + head_point = next_idx_modulo(head_point, points.size()); + head_window_acc += distances_for_curvature[head_point]; + head_angle_acc += angles_for_curvature[head_point]; + } - float curvature = (tail_angle_acc + head_angle_acc) / (tail_window_acc + head_window_acc); - if (std::abs(curvature) > std::abs(points[point_idx].curvature)) { - points[point_idx].curvature = curvature; + float curvature = (tail_angle_acc + head_angle_acc) / window_size; + if (std::abs(curvature) > std::abs(points[point_idx].curvature)) { + points[point_idx].curvature = curvature; + } + + tail_window_acc += distances_for_curvature[point_idx]; + tail_angle_acc += angles_for_curvature[point_idx]; + head_window_acc -= distances_for_curvature[point_idx]; + head_angle_acc -= angles_for_curvature[point_idx]; } } - } return points; } diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 39f6ef98eb..2a8c78729b 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1280,64 +1280,6 @@ bool ModelObject::has_connectors() const return false; } -indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes connector_attributes) -{ - indexed_triangle_set connector_mesh; - - int sectorCount {1}; - switch (CutConnectorShape(connector_attributes.shape)) { - case CutConnectorShape::Triangle: - sectorCount = 3; - break; - case CutConnectorShape::Square: - sectorCount = 4; - break; - case CutConnectorShape::Circle: - sectorCount = 360; - break; - case CutConnectorShape::Hexagon: - sectorCount = 6; - break; - default: - break; - } - - if (connector_attributes.style == CutConnectorStyle::Prism) - connector_mesh = its_make_cylinder(1.0, 1.0, (2 * PI / sectorCount)); - else if (connector_attributes.type == CutConnectorType::Plug) - connector_mesh = its_make_frustum(1.0, 1.0, (2 * PI / sectorCount)); - else - connector_mesh = its_make_frustum_dowel(1.0, 1.0, sectorCount); - - return connector_mesh; -} - -void ModelObject::apply_cut_connectors(const std::string& new_name) -{ - if (cut_connectors.empty()) - return; - - using namespace Geometry; - - size_t connector_id = cut_id.connectors_cnt(); - for (const CutConnector& connector : cut_connectors) { - TriangleMesh mesh = TriangleMesh(get_connector_mesh(connector.attribs)); - // Mesh will be centered when loading. - ModelVolume* new_volume = add_volume(std::move(mesh), ModelVolumeType::NEGATIVE_VOLUME); - - // Transform the new modifier to be aligned inside the instance - new_volume->set_transformation(translation_transform(connector.pos) * connector.rotation_m * - scale_transform(Vec3f(connector.radius, connector.radius, connector.height).cast())); - - new_volume->cut_info = { connector.attribs.type, connector.radius_tolerance, connector.height_tolerance }; - new_volume->name = new_name + "-" + std::to_string(++connector_id); - } - cut_id.increase_connectors_cnt(cut_connectors.size()); - - // delete all connectors - cut_connectors.clear(); -} - void ModelObject::invalidate_cut() { this->cut_id.invalidate(); @@ -1390,297 +1332,6 @@ void ModelVolume::reset_extra_facets() this->mmu_segmentation_facets.reset(); } -void ModelVolume::apply_tolerance() -{ - assert(cut_info.is_connector); - if (!cut_info.is_processed) - return; - - Vec3d sf = get_scaling_factor(); - - // make a "hole" wider - sf[X] += double(cut_info.radius_tolerance); - sf[Y] += double(cut_info.radius_tolerance); - - // make a "hole" dipper - sf[Z] += double(cut_info.height_tolerance); - - set_scaling_factor(sf); - - // correct offset in respect to the new depth - Vec3d rot_norm = Geometry::rotation_transform(get_rotation()) * Vec3d::UnitZ(); - if (rot_norm.norm() != 0.0) - rot_norm.normalize(); - - double z_offset = 0.5 * static_cast(cut_info.height_tolerance); - if (cut_info.connector_type == CutConnectorType::Plug) - z_offset -= 0.05; // add small Z offset to better preview - - set_offset(get_offset() + rot_norm * z_offset); -} - -static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelVolume* src_volume, const Transform3d& cut_matrix, const std::string& suffix = {}, ModelVolumeType type = ModelVolumeType::MODEL_PART) -{ - if (mesh.empty()) - return; - - mesh.transform(cut_matrix); - ModelVolume* vol = object->add_volume(mesh); - vol->set_type(type); - - vol->name = src_volume->name + suffix; - // Don't copy the config's ID. - vol->config.assign_config(src_volume->config); - assert(vol->config.id().valid()); - assert(vol->config.id() != src_volume->config.id()); - vol->set_material(src_volume->material_id(), *src_volume->material()); - vol->cut_info = src_volume->cut_info; -} - -void ModelObject::process_connector_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, - ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, - std::vector& dowels) -{ - assert(volume->cut_info.is_connector); - volume->cut_info.set_processed(); - - const auto volume_matrix = volume->get_matrix(); - - // ! Don't apply instance transformation for the conntectors. - // This transformation is already there - if (volume->cut_info.connector_type != CutConnectorType::Dowel) { - if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { - ModelVolume* vol = upper->add_volume(*volume); - vol->set_transformation(volume_matrix); - vol->apply_tolerance(); - } - if (attributes.has(ModelObjectCutAttribute::KeepLower)) { - ModelVolume* vol = lower->add_volume(*volume); - vol->set_transformation(volume_matrix); - // for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug - vol->set_type(ModelVolumeType::MODEL_PART); - } - } - else { - if (attributes.has(ModelObjectCutAttribute::CreateDowels)) { - ModelObject* dowel{ nullptr }; - // Clone the object to duplicate instances, materials etc. - clone_for_cut(&dowel); - - // add one more solid part same as connector if this connector is a dowel - ModelVolume* vol = dowel->add_volume(*volume); - vol->set_type(ModelVolumeType::MODEL_PART); - - // But discard rotation and Z-offset for this volume - vol->set_rotation(Vec3d::Zero()); - vol->set_offset(Z, 0.0); - - dowels.push_back(dowel); - } - - // Cut the dowel - volume->apply_tolerance(); - - // Perform cut - TriangleMesh upper_mesh, lower_mesh; - process_volume_cut(volume, Transform3d::Identity(), cut_matrix, attributes, upper_mesh, lower_mesh); - - // add small Z offset to better preview - upper_mesh.translate((-0.05 * Vec3d::UnitZ()).cast()); - lower_mesh.translate((0.05 * Vec3d::UnitZ()).cast()); - - // Add cut parts to the related objects - add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A", volume->type()); - add_cut_volume(lower_mesh, lower, volume, cut_matrix, "_B", volume->type()); - } -} - -void ModelObject::process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix, - ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower) -{ - const auto volume_matrix = instance_matrix * volume->get_matrix(); - - // Modifiers are not cut, but we still need to add the instance transformation - // to the modifier volume transformation to preserve their shape properly. - volume->set_transformation(Geometry::Transformation(volume_matrix)); - - if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) { - upper->add_volume(*volume); - return; - } - - // Some logic for the negative volumes/connectors. Add only needed modifiers - auto bb = volume->mesh().transformed_bounding_box(inverse_cut_matrix * volume_matrix); - bool is_crossed_by_cut = bb.min[Z] <= 0 && bb.max[Z] >= 0; - if (attributes.has(ModelObjectCutAttribute::KeepUpper) && (bb.min[Z] >= 0 || is_crossed_by_cut)) - upper->add_volume(*volume); - if (attributes.has(ModelObjectCutAttribute::KeepLower) && (bb.max[Z] <= 0 || is_crossed_by_cut)) - lower->add_volume(*volume); -} - -void ModelObject::process_volume_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, - ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh) -{ - const auto volume_matrix = volume->get_matrix(); - - using namespace Geometry; - - const Transformation cut_transformation = Transformation(cut_matrix); - const Transform3d invert_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1 * cut_transformation.get_offset()); - - // Transform the mesh by the combined transformation matrix. - // Flip the triangles in case the composite transformation is left handed. - TriangleMesh mesh(volume->mesh()); - mesh.transform(invert_cut_matrix * instance_matrix * volume_matrix, true); - - indexed_triangle_set upper_its, lower_its; - cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its); - if (attributes.has(ModelObjectCutAttribute::KeepUpper)) - upper_mesh = TriangleMesh(upper_its); - if (attributes.has(ModelObjectCutAttribute::KeepLower)) - lower_mesh = TriangleMesh(lower_its); -} -void ModelObject::process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, - ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower) -{ - // Perform cut - TriangleMesh upper_mesh, lower_mesh; - process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh); - - // Add required cut parts to the objects - - if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) { - add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A"); - add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B"); - return; - } - - if (attributes.has(ModelObjectCutAttribute::KeepUpper)) - add_cut_volume(upper_mesh, upper, volume, cut_matrix); - - if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) - add_cut_volume(lower_mesh, lower, volume, cut_matrix); -} - -void ModelObject::reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix, - bool place_on_cut/* = false*/, bool flip/* = false*/) -{ - using namespace Geometry; - - // Reset instance transformation except offset and Z-rotation - - for (size_t i = 0; i < object->instances.size(); ++i) { - auto& obj_instance = object->instances[i]; - const double rot_z = obj_instance->get_rotation().z(); - - Transformation inst_trafo = Transformation(obj_instance->get_transformation().get_matrix_no_scaling_factor()); - if (obj_instance->is_left_handed()) - inst_trafo = inst_trafo * Transformation(scale_transform(Vec3d(-1, 1, 1))); - - obj_instance->set_transformation(inst_trafo); - - Vec3d rotation = Vec3d::Zero(); - if (!flip && !place_on_cut) { - if ( i != src_instance_idx) - rotation[Z] = rot_z; - } - else { - Transform3d rotation_matrix = Transform3d::Identity(); - if (flip) - rotation_matrix = rotation_transform(PI * Vec3d::UnitX()); - - if (place_on_cut) - rotation_matrix = rotation_matrix * Transformation(cut_matrix).get_rotation_matrix().inverse(); - - if (i != src_instance_idx) - rotation_matrix = rotation_transform(rot_z * Vec3d::UnitZ()) * rotation_matrix; - - rotation = Transformation(rotation_matrix).get_rotation(); - } - - obj_instance->set_rotation(rotation); - } -} - -ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes) -{ - if (!attributes.has(ModelObjectCutAttribute::KeepUpper) && !attributes.has(ModelObjectCutAttribute::KeepLower)) - return {}; - - BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start"; - - // Clone the object to duplicate instances, materials etc. - ModelObject* upper{ nullptr }; - if (attributes.has(ModelObjectCutAttribute::KeepUpper)) - clone_for_cut(&upper); - - ModelObject* lower{ nullptr }; - if (attributes.has(ModelObjectCutAttribute::KeepLower) && !attributes.has(ModelObjectCutAttribute::KeepAsParts)) - clone_for_cut(&lower); - - std::vector dowels; - - using namespace Geometry; - - // Because transformations are going to be applied to meshes directly, - // we reset transformation of all instances and volumes, - // except for translation and Z-rotation on instances, which are preserved - // in the transformation matrix and not applied to the mesh transform. - - // const auto instance_matrix = instances[instance]->get_matrix(true); - const auto instance_matrix = instances[instance]->get_transformation().get_matrix_no_offset(); - const Transformation cut_transformation = Transformation(cut_matrix); - const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1. * cut_transformation.get_offset()); - - for (ModelVolume* volume : volumes) { - volume->reset_extra_facets(); - - if (!volume->is_model_part()) { - if (volume->cut_info.is_processed) - process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, attributes, upper, lower); - else - process_connector_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, dowels); - } - else if (!volume->mesh().empty()) - process_solid_part_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower); - } - - // Post-process cut parts - - ModelObjectPtrs res; - - if (attributes.has(ModelObjectCutAttribute::KeepAsParts) && !upper->volumes.empty()) { - reset_instance_transformation(upper, instance, cut_matrix); - res.push_back(upper); - } - else { - if (attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper->volumes.empty()) { - reset_instance_transformation(upper, instance, cut_matrix, - attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper), - attributes.has(ModelObjectCutAttribute::FlipUpper)); - res.push_back(upper); - } - - if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower->volumes.empty()) { - reset_instance_transformation(lower, instance, cut_matrix, - attributes.has(ModelObjectCutAttribute::PlaceOnCutLower), - attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || attributes.has(ModelObjectCutAttribute::FlipLower)); - res.push_back(lower); - } - - if (attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) { - for (auto dowel : dowels) { - reset_instance_transformation(dowel, instance, Transform3d::Identity()); - dowel->name += "-Dowel-" + dowel->volumes[0]->name; - res.push_back(dowel); - } - } - } - - BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end"; - - return res; -} /// /// Compare TriangleMeshes by Bounding boxes (mainly for sort) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index bedd26436f..221033c521 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -224,6 +224,7 @@ private: enum class CutConnectorType : int { Plug , Dowel + , Snap , Undef }; @@ -316,10 +317,6 @@ enum class ModelVolumeType : int { SUPPORT_ENFORCER, }; -enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, KeepAsParts, FlipUpper, FlipLower, PlaceOnCutUpper, PlaceOnCutLower, CreateDowels, InvalidateCutInfo }; -using ModelObjectCutAttributes = enum_bitmask; -ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute); - // A printable object, possibly having multiple print volumes (each with its own set of parameters and materials), // and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials. // Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed, @@ -461,29 +458,12 @@ public: size_t materials_count() const; size_t facets_count() const; size_t parts_count() const; - static indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes); - void apply_cut_connectors(const std::string& name); // invalidate cut state for this object and its connectors/volumes void invalidate_cut(); // delete volumes which are marked as connector for this object void delete_connectors(); void clone_for_cut(ModelObject **obj); -private: - void process_connector_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, - ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, - std::vector& dowels); - void process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix, - ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower); - void process_volume_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, - ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh); - void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, - ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower); -public: - static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix, - bool place_on_cut = false, bool flip = false); - - ModelObjectPtrs cut(size_t instance, const Transform3d&cut_matrix, ModelObjectCutAttributes attributes); void split(ModelObjectPtrs*new_objects); void merge(); // Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees, @@ -777,6 +757,7 @@ public: // It contains information about connetors struct CutInfo { + bool is_from_upper{ true }; bool is_connector{ false }; bool is_processed{ true }; CutConnectorType connector_type{ CutConnectorType::Plug }; @@ -794,6 +775,7 @@ public: void set_processed() { is_processed = true; } void invalidate() { is_connector = false; } + void reset_from_upper() { is_from_upper = true; } template inline void serialize(Archive& ar) { ar(is_connector, is_processed, connector_type, radius_tolerance, height_tolerance); @@ -801,6 +783,9 @@ public: }; CutInfo cut_info; + bool is_from_upper() const { return cut_info.is_from_upper; } + void reset_from_upper() { cut_info.reset_from_upper(); } + bool is_cut_connector() const { return cut_info.is_processed && cut_info.is_connector; } void invalidate_cut_info() { cut_info.invalidate(); } @@ -846,7 +831,6 @@ public: bool is_the_only_one_part() const; // behave like an object t_model_material_id material_id() const { return m_material_id; } void reset_extra_facets(); - void apply_tolerance(); void set_material_id(t_model_material_id material_id); ModelMaterial* material() const; void set_material(t_model_material_id material_id, const ModelMaterial &material); diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 34cae91a7e..1d41bafc3b 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -678,7 +678,9 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p void PresetBundle::export_selections(AppConfig &config) { assert(this->printers.get_edited_preset().printer_technology() != ptFFF || extruders_filaments.size() >= 1); - assert(this->printers.get_edited_preset().printer_technology() != ptFFF || extruders_filaments.size() > 1 || filaments.get_selected_preset().alias == extruders_filaments.front().get_selected_preset()->alias); + // #ysFIXME_delete_after_test !All filament selections are always saved in extruder_filaments (for MM and SM printers), + // so there is no need to control a correspondence between filaments and extruders_filaments + //assert(this->printers.get_edited_preset().printer_technology() != ptFFF || extruders_filaments.size() > 1 || filaments.get_selected_preset().alias == extruders_filaments.front().get_selected_preset()->alias); config.clear_section("presets"); config.set("presets", "print", prints.get_selected_preset_name()); config.set("presets", "filament", extruders_filaments.front().get_selected_preset_name()); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 0b135f0b94..49573a041a 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1539,21 +1539,33 @@ void PrintObject::discover_vertical_shells() // Finally expand the infill a bit to remove tiny gaps between solid infill and the other regions. narrow_sparse_infill_region_radius - tiny_overlap_radius, ClipperLib::jtSquare); + Polygons object_volume; Polygons internal_volume; { Polygons shrinked_bottom_slice = idx_layer > 0 ? to_polygons(m_layers[idx_layer - 1]->lslices) : Polygons{}; Polygons shrinked_upper_slice = (idx_layer + 1) < m_layers.size() ? to_polygons(m_layers[idx_layer + 1]->lslices) : Polygons{}; - internal_volume = intersection(shrinked_bottom_slice, shrinked_upper_slice); + object_volume = intersection(shrinked_bottom_slice, shrinked_upper_slice); + internal_volume = closing(polygonsInternal, SCALED_EPSILON); } - // The opening operation may cause scattered tiny drops on the smooth parts of the model, filter them out + // The regularization operation may cause scattered tiny drops on the smooth parts of the model, filter them out + // If the region checks both following conditions, it is removed: + // 1. the area is very small, + // OR the area is quite small and it is fully wrapped in model (not visible) + // the in-model condition is there due to small sloping surfaces, e.g. top of the hull of the benchy + // 2. the area does not fully cover an internal polygon + // This is there mainly for a very thin parts, where the solid layers would be missing if the part area is quite small regularized_shell.erase(std::remove_if(regularized_shell.begin(), regularized_shell.end(), - [&min_perimeter_infill_spacing, &internal_volume](const ExPolygon &p) { - return p.area() < min_perimeter_infill_spacing * scaled(1.5) || - (p.area() < min_perimeter_infill_spacing * scaled(8.0) && - diff(to_polygons(p), internal_volume).empty()); + [&internal_volume, &min_perimeter_infill_spacing, + &object_volume](const ExPolygon &p) { + return (p.area() < min_perimeter_infill_spacing * scaled(1.5) || + (p.area() < min_perimeter_infill_spacing * scaled(8.0) && + diff(to_polygons(p), object_volume).empty())) && + diff(internal_volume, + expand(to_polygons(p), min_perimeter_infill_spacing)) + .size() >= internal_volume.size(); }), regularized_shell.end()); } diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 4bf9fd486a..236fa22c51 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -1262,6 +1262,127 @@ indexed_triangle_set its_make_frustum_dowel(double radius, double h, int sectorC return mesh; } +indexed_triangle_set its_make_snap(double r, double h, float space_proportion, float bulge_proportion) +{ + const float radius = (float)r; + const float height = (float)h; + const size_t sectors_cnt = 10; //(float)fa; + const float halfPI = 0.5f * (float)PI; + + const float space_len = space_proportion * radius; + + const float b_len = radius; + const float m_len = (1 + bulge_proportion) * radius; + const float t_len = 0.5f * radius; + + const float b_height = 0.f; + const float m_height = 0.5f * height; + const float t_height = height; + + const float b_angle = acos(space_len/b_len); + const float t_angle = acos(space_len/t_len); + + const float b_angle_step = b_angle / (float)sectors_cnt; + const float t_angle_step = t_angle / (float)sectors_cnt; + + const Vec2f b_vec = Eigen::Vector2f(0, b_len); + const Vec2f t_vec = Eigen::Vector2f(0, t_len); + + + auto add_side_vertices = [b_vec, t_vec, b_height, m_height, t_height](std::vector& vertices, float b_angle, float t_angle, const Vec2f& m_vec) { + Vec2f b_pt = Eigen::Rotation2Df(b_angle) * b_vec; + Vec2f m_pt = Eigen::Rotation2Df(b_angle) * m_vec; + Vec2f t_pt = Eigen::Rotation2Df(t_angle) * t_vec; + + vertices.emplace_back(Vec3f(b_pt(0), b_pt(1), b_height)); + vertices.emplace_back(Vec3f(m_pt(0), m_pt(1), m_height)); + vertices.emplace_back(Vec3f(t_pt(0), t_pt(1), t_height)); + }; + + auto add_side_facets = [](std::vector& facets, int vertices_cnt, int frst_id, int scnd_id) { + int id = vertices_cnt - 1; + + facets.emplace_back(frst_id, id - 2, id - 5); + + facets.emplace_back(id - 2, id - 1, id - 5); + facets.emplace_back(id - 1, id - 4, id - 5); + facets.emplace_back(id - 4, id - 1, id); + facets.emplace_back(id, id - 3, id - 4); + + facets.emplace_back(id, scnd_id, id - 3); + }; + + const float f = (b_len - m_len) / m_len; // Flattening + + auto get_m_len = [b_len, f](float angle) { + const float rad_sqr = b_len * b_len; + const float sin_sqr = sin(angle) * sin(angle); + const float f_sqr = (1-f)*(1-f); + return sqrtf(rad_sqr / (1 + (1 / f_sqr - 1) * sin_sqr)); + }; + + auto add_sub_mesh = [add_side_vertices, add_side_facets, get_m_len, + b_height, t_height, b_angle, t_angle, b_angle_step, t_angle_step] + (indexed_triangle_set& mesh, float center_x, float angle_rotation, int frst_vertex_id) { + auto& vertices = mesh.vertices; + auto& facets = mesh.indices; + + // 2 special vertices, top and bottom center, rest are relative to this + vertices.emplace_back(Vec3f(center_x, 0.f, b_height)); + vertices.emplace_back(Vec3f(center_x, 0.f, t_height)); + + float b_angle_start = angle_rotation - b_angle; + float t_angle_start = angle_rotation - t_angle; + const float b_angle_stop = angle_rotation + b_angle; + + const int frst_id = frst_vertex_id; + const int scnd_id = frst_id + 1; + + // add first side vertices and internal facets + { + const Vec2f m_vec = Eigen::Vector2f(0, get_m_len(b_angle_start)); + add_side_vertices(vertices, b_angle_start, t_angle_start, m_vec); + + int id = (int)vertices.size() - 1; + + facets.emplace_back(frst_id, id - 2, id - 1); + facets.emplace_back(frst_id, id - 1, id); + facets.emplace_back(frst_id, id, scnd_id); + } + + // add d side vertices and facets + while (!is_approx(b_angle_start, b_angle_stop)) { + b_angle_start += b_angle_step; + t_angle_start += t_angle_step; + + const Vec2f m_vec = Eigen::Vector2f(0, get_m_len(b_angle_start)); + add_side_vertices(vertices, b_angle_start, t_angle_start, m_vec); + + add_side_facets(facets, (int)vertices.size(), frst_id, scnd_id); + } + + // add last internal facets to close the mesh + { + int id = (int)vertices.size() - 1; + + facets.emplace_back(frst_id, scnd_id, id); + facets.emplace_back(frst_id, id, id - 1); + facets.emplace_back(frst_id, id - 1, id - 2); + } + }; + + + indexed_triangle_set mesh; + + mesh.vertices.reserve(2 * (3 * (2 * sectors_cnt + 1) + 2)); + mesh.indices.reserve(2 * (6 * 2 * sectors_cnt + 6)); + + add_sub_mesh(mesh, -space_len, halfPI , 0); + add_sub_mesh(mesh, space_len, 3 * halfPI, (int)mesh.vertices.size()); + + return mesh; +} + indexed_triangle_set its_convex_hull(const std::vector &pts) { std::vector dst_vertices; diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 0f43f9d58d..4b524402c0 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -321,6 +321,7 @@ indexed_triangle_set its_make_frustum(double r, double h, double fa=(2*PI/360 indexed_triangle_set its_make_frustum_dowel(double r, double h, int sectorCount); indexed_triangle_set its_make_pyramid(float base, float height); indexed_triangle_set its_make_sphere(double radius, double fa); +indexed_triangle_set its_make_snap(double r, double h, float space_proportion = 0.25f, float bulge_proportion = 0.125f); indexed_triangle_set its_convex_hull(const std::vector &pts); inline indexed_triangle_set its_convex_hull(const indexed_triangle_set &its) { return its_convex_hull(its.vertices); } diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 380e2f5b7e..d96e794621 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -2757,6 +2757,11 @@ bool TickCodeInfo::add_tick(const int tick, Type type, const int extruder, doubl bool TickCodeInfo::edit_tick(std::set::iterator it, double print_z) { + // Save previously value of the tick before the call a Dialog from get_... functions, + // otherwise a background process can change ticks values and current iterator wouldn't be valid for the moment of a Dialog close + // and PS will crash (see https://github.com/prusa3d/PrusaSlicer/issues/10941) + TickCode changed_tick = *it; + std::string edited_value; if (it->type == ColorChange) edited_value = get_new_color(it->color); @@ -2768,7 +2773,10 @@ bool TickCodeInfo::edit_tick(std::set::iterator it, double print_z) if (edited_value.empty()) return false; - TickCode changed_tick = *it; + // Update iterator. For this moment its value can be invalid + if (it = ticks.find(changed_tick); it == ticks.end()) + return false; + if (it->type == ColorChange) { if (it->color == edited_value) return false; diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 74ad028d36..121493b68b 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -588,6 +588,8 @@ bool TextCtrl::value_was_changed() case coFloatOrPercent: case coFloatsOrPercents: return boost::any_cast(m_value) != boost::any_cast(val); + case coPoints: + return boost::any_cast>(m_value) != boost::any_cast>(val); default: return true; } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 2687ddcf4c..f98cc17137 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2689,6 +2689,9 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re if (curr_gizmo != nullptr) curr_gizmo->unregister_raycasters_for_picking(); m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::Gizmo); + if (curr_gizmo != nullptr && !m_selection.is_empty()) + curr_gizmo->register_raycasters_for_picking(); + m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::FallbackGizmo); if (curr_gizmo != nullptr && !m_selection.is_empty()) curr_gizmo->register_raycasters_for_picking(); @@ -3597,11 +3600,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) update_sequential_clearance(true); } } - else if (evt.LeftUp() && - m_gizmos.get_current_type() == GLGizmosManager::EType::Scale && - m_gizmos.get_current()->get_state() == GLGizmoBase::EState::On) { - wxGetApp().obj_list()->selection_changed(); - } return; } @@ -4071,7 +4069,7 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) model_object->invalidate_bounding_box(); } } - else if (v->is_wipe_tower) + else if (m_selection.is_wipe_tower() && v->is_wipe_tower) // Move a wipe tower proxy. wipe_tower_origin = v->get_volume_offset(); } @@ -5749,6 +5747,7 @@ void GLCanvas3D::_picking_pass() break; } case SceneRaycaster::EType::Gizmo: + case SceneRaycaster::EType::FallbackGizmo: { const Size& cnv_size = get_canvas_size(); const bool inside = 0 <= m_mouse.position.x() && m_mouse.position.x() < cnv_size.get_width() && @@ -5781,6 +5780,7 @@ void GLCanvas3D::_picking_pass() { case SceneRaycaster::EType::Bed: { object_type = "Bed"; break; } case SceneRaycaster::EType::Gizmo: { object_type = "Gizmo element"; break; } + case SceneRaycaster::EType::FallbackGizmo: { object_type = "Gizmo2 element"; break; } case SceneRaycaster::EType::Volume: { if (m_volumes.volumes[hit.raycaster_id]->is_wipe_tower) @@ -5835,6 +5835,8 @@ void GLCanvas3D::_picking_pass() add_strings_row_to_table("Volumes", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); sprintf(buf, "%d (%d)", (int)m_scene_raycaster.gizmos_count(), (int)m_scene_raycaster.active_gizmos_count()); add_strings_row_to_table("Gizmo elements", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + sprintf(buf, "%d (%d)", (int)m_scene_raycaster.fallback_gizmos_count(), (int)m_scene_raycaster.active_fallback_gizmos_count()); + add_strings_row_to_table("Gizmo2 elements", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text)); ImGui::EndTable(); } @@ -5852,6 +5854,20 @@ void GLCanvas3D::_picking_pass() } } + std::vector>* gizmo2_raycasters = m_scene_raycaster.get_raycasters(SceneRaycaster::EType::FallbackGizmo); + if (gizmo2_raycasters != nullptr && !gizmo2_raycasters->empty()) { + ImGui::Separator(); + imgui.text("Gizmo2 raycasters IDs:"); + if (ImGui::BeginTable("Gizmo2Raycasters", 3)) { + for (size_t i = 0; i < gizmo2_raycasters->size(); ++i) { + add_strings_row_to_table(std::to_string(i), ImGuiWrapper::COL_ORANGE_LIGHT, + std::to_string(SceneRaycaster::decode_id(SceneRaycaster::EType::FallbackGizmo, (*gizmo2_raycasters)[i]->get_id())), ImGui::GetStyleColorVec4(ImGuiCol_Text), + to_string(Geometry::Transformation((*gizmo2_raycasters)[i]->get_transform()).get_offset()), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + } + ImGui::EndTable(); + } + } + imgui.end(); #endif // ENABLE_RAYCAST_PICKING_DEBUG } diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index ae40e0f766..9ea6198f7a 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -503,6 +503,11 @@ void ObjectManipulation::Show(const bool show) } m_word_local_combo->Show(show_world_local_combo); m_empty_str->Show(!show_world_local_combo); + + m_skew_label->Show(m_show_skew); + m_reset_skew_button->Show(m_show_skew); + + m_parent->Layout(); } } @@ -795,15 +800,22 @@ void ObjectManipulation::update_reset_buttons_visibility() m_mirror_warning_bitmap->SetBitmap(show_mirror ? m_manifold_warning_bmp.bmp() : wxNullBitmap); m_mirror_warning_bitmap->SetMinSize(show_mirror ? m_manifold_warning_bmp.GetSize() : wxSize(0, 0)); m_mirror_warning_bitmap->SetToolTip(show_mirror ? _L("Left handed") : ""); - m_reset_skew_button->Show(show_skew); - m_skew_label->Show(show_skew); - // Because of CallAfter we need to layout sidebar after Show/hide of reset buttons one more time - Sidebar& panel = wxGetApp().sidebar(); - if (!panel.IsFrozen()) { - panel.Freeze(); - panel.Layout(); - panel.Thaw(); + if (m_show_skew == show_skew) + get_sizer()->Layout(); + else { + // Call sidebar layout only if it's really needed, + // it means, when we show/hide additional line for skew information + m_show_skew = show_skew; + m_reset_skew_button->Show(m_show_skew); + m_skew_label->Show(m_show_skew); + // Because of CallAfter we need to layout sidebar after Show/hide of reset buttons one more time + Sidebar& panel = wxGetApp().sidebar(); + if (!panel.IsFrozen()) { + panel.Freeze(); + panel.Layout(); + panel.Thaw(); + } } }); } diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index 139171d999..6b5dfe5447 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -169,6 +169,7 @@ private: bool m_is_enabled { true }; bool m_is_enabled_size_and_scale { true }; + bool m_show_skew { false }; public: ObjectManipulation(wxWindow* parent); diff --git a/src/slic3r/GUI/GalleryDialog.cpp b/src/slic3r/GUI/GalleryDialog.cpp index a08673e235..5ac51d54f7 100644 --- a/src/slic3r/GUI/GalleryDialog.cpp +++ b/src/slic3r/GUI/GalleryDialog.cpp @@ -134,7 +134,12 @@ GalleryDialog::GalleryDialog(wxWindow* parent) : } GalleryDialog::~GalleryDialog() -{ +{ + // From wxWidgets docs: + // The method void wxListCtrl::SetImageList(wxImageList* imageList, int which) + // does not take ownership of the image list, you have to delete it yourself. + if (m_image_list) + delete m_image_list; } int GalleryDialog::show(bool show_from_menu) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 885f9d97a2..9d23a51808 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -35,6 +35,9 @@ static const ColorRGBA CONNECTOR_DEF_COLOR = ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f); static const ColorRGBA CONNECTOR_ERR_COLOR = ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f); static const ColorRGBA HOVERED_ERR_COLOR = ColorRGBA(1.0f, 0.3f, 0.3f, 1.0f); +static const ColorRGBA CUT_PLANE_DEF_COLOR = ColorRGBA(0.9f, 0.9f, 0.9f, 0.5f); +static const ColorRGBA CUT_PLANE_ERR_COLOR = ColorRGBA(1.0f, 0.8f, 0.8f, 0.5f); + const unsigned int AngleResolution = 64; const unsigned int ScaleStepsCount = 72; const float ScaleStepRad = 2.0f * float(PI) / ScaleStepsCount; @@ -183,15 +186,16 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, , m_connector_style (int(CutConnectorStyle::Prism)) , m_connector_shape_id (int(CutConnectorShape::Circle)) { -// m_modes = { _u8L("Planar"), _u8L("Grid") + m_modes = { _u8L("Planar"), _u8L("Tongue and Groove")//, _u8L("Grid") // , _u8L("Radial"), _u8L("Modular") -// }; + }; m_connector_modes = { _u8L("Auto"), _u8L("Manual") }; std::map connetor_types = { {ImGui::PlugMarker , _u8L("Plug") }, - {ImGui::DowelMarker, _u8L("Dowel") }, + {ImGui::DowelMarker, _u8L("Dowel") }, + {ImGui::SnapMarker, _u8L("Snap") }, }; for (auto connector : connetor_types) { std::string type_label = " " + connector.second + " "; @@ -222,9 +226,13 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, {"Shape" , _u8L("Shape")}, {"Depth" , _u8L("Depth")}, {"Size" , _u8L("Size")}, + {"Groove" , _u8L("Groove")}, + {"Width" , _u8L("Width")}, + {"Flaps Angle" , _u8L("Flaps Angle")}, + {"Groove Angle" , _u8L("Groove Angle")}, }; - update_connector_shape(); +// update_connector_shape(); } std::string GLGizmoCut3D::get_tooltip() const @@ -249,13 +257,17 @@ std::string GLGizmoCut3D::get_tooltip() const return tooltip; } - if (!m_dragging && m_hover_id == CutPlane) + if (!m_dragging && m_hover_id == CutPlane) { + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) + return _u8L("Click to flip the cut plane\n" + "Drag to move the cut plane"); return _u8L("Click to flip the cut plane\n" "Drag to move the cut plane\n" "Right-click a part to assign it to the other side"); + } - if (tooltip.empty() && (m_hover_id == X || m_hover_id == Y)) { - std::string axis = m_hover_id == X ? "X" : "Y"; + if (tooltip.empty() && (m_hover_id == X || m_hover_id == Y || m_hover_id == CutPlaneZRotation)) { + std::string axis = m_hover_id == X ? "X" : m_hover_id == Y ? "Y" : "Z"; return axis + ": " + format(float(rad2deg(m_angle)), 1) + _u8L("°"); } @@ -283,6 +295,14 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) else if (mouse_event.Moving()) return false; + if (m_hover_id >= CutPlane && mouse_event.LeftDown() && !m_connectors_editing) { + // before processing of a use_grabbers(), detect start move position as a projection of mouse position to the cut plane + Vec3d pos; + Vec3d pos_world; + if (unproject_on_cut_plane(mouse_pos, pos, pos_world, false)) + m_cut_plane_start_move_pos = pos_world; + } + if (use_grabbers(mouse_event)) { if (m_hover_id >= m_connectors_group_id) { if (mouse_event.LeftDown() && !mouse_event.CmdDown() && !mouse_event.AltDown()) @@ -297,7 +317,7 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) // disable / enable current contour Vec3d pos; Vec3d pos_world; - m_was_contour_selected = unproject_on_cut_plane(mouse_pos.cast(), pos, pos_world, false); + m_was_contour_selected = unproject_on_cut_plane(mouse_pos.cast(), pos, pos_world); if (m_was_contour_selected) { // Following would inform the clipper about the mouse click, so it can // toggle the respective contour as disabled. @@ -311,8 +331,15 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) flip_cut_plane(); } - if (m_part_selection.valid()) - m_parent.toggle_model_objects_visibility(false); + if (m_hover_id >= CutPlane && mouse_event.Dragging() && !m_connectors_editing) { + // if we continue to dragging a cut plane, than update a start move position as a projection of mouse position to the cut plane after processing of a use_grabbers() + Vec3d pos; + Vec3d pos_world; + if (unproject_on_cut_plane(mouse_pos, pos, pos_world, false)) + m_cut_plane_start_move_pos = pos_world; + } + + toggle_model_objects_visibility(); return true; } @@ -353,7 +380,8 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event) return true; } else if (mouse_event.RightDown()) { - if (! m_connectors_editing && mouse_event.GetModifiers() == wxMOD_NONE) { + if (! m_connectors_editing && mouse_event.GetModifiers() == wxMOD_NONE && + CutMode(m_mode) == CutMode::cutPlanar) { // Check the internal part raycasters. if (! m_part_selection.valid()) process_contours(); @@ -466,13 +494,43 @@ void GLGizmoCut3D::set_center(const Vec3d& center, bool update_tbb /*=false*/) update_clipper(); } +void GLGizmoCut3D::switch_to_mode(size_t new_mode) +{ + m_mode = new_mode; + update_raycasters_for_picking(); + + apply_color_clip_plane_colors(); + if (auto oc = m_c->object_clipper()) { + m_contour_width = CutMode(m_mode) == CutMode::cutTongueAndGroove ? 0.f : 0.4f; + oc->set_behavior(m_connectors_editing, m_connectors_editing, double(m_contour_width)); + } + + update_plane_model(); + reset_cut_by_contours(); +} + +bool GLGizmoCut3D::render_cut_mode_combo() +{ + ImGui::AlignTextToFramePadding(); + int selection_idx = int(m_mode); + const bool is_changed = m_imgui->combo(_u8L("Mode"), m_modes, selection_idx, 0, m_label_width, m_control_width); + + if (is_changed) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Change cut mode"), UndoRedo::SnapshotType::GizmoAction); + switch_to_mode(size_t(selection_idx)); + check_and_update_connectors_state(); + } + + return is_changed; +} + bool GLGizmoCut3D::render_combo(const std::string& label, const std::vector& lines, int& selection_idx) { ImGui::AlignTextToFramePadding(); const bool is_changed = m_imgui->combo(label, lines, selection_idx, 0, m_label_width, m_control_width); - if (is_changed) - update_connector_shape(); + //if (is_changed) + // update_connector_shape(); return is_changed; } @@ -497,7 +555,7 @@ bool GLGizmoCut3D::render_double_input(const std::string& label, double& value_i return !is_approx(old_val, value); } -bool GLGizmoCut3D::render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in) +bool GLGizmoCut3D::render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in, float min_val/* = -0.1f*/, float max_tolerance/* = -0.1f*/) { constexpr float UndefMinVal = -0.1f; const float f_mm_to_in = static_cast(ObjectManipulation::mm_to_in); @@ -516,23 +574,29 @@ bool GLGizmoCut3D::render_slider_double_input(const std::string& label, float& v m_imgui->slider_float(label.c_str(), &value, min_val, max_val, format.c_str(), 1.f, true, tooltip); val = value * (m_imperial_units ? static_cast(ObjectManipulation::in_to_mm) : 1.f); + m_is_slider_editing_done |= m_imgui->get_last_slider_status().deactivated_after_edit; + return !is_approx(old_val, value); }; const BoundingBoxf3 bbox = m_bounding_box; const float mean_size = float((bbox.size().x() + bbox.size().y() + bbox.size().z()) / 9.0) * (m_imperial_units ? f_mm_to_in : 1.f); + const float min_v = min_val > 0.f ? /*std::min(max_val, mean_size)*/min_val : 1.f; ImGuiWrapper::text(label); ImGui::SameLine(m_label_width); ImGui::PushItemWidth(m_control_width * 0.7f); - const bool is_value_changed = render_slider("##" + label, value_in, 1.f, mean_size, _L("Value")); +// const bool is_value_changed = render_slider("##" + label, value_in, 1.f, mean_size, _L("Value")); + const bool is_value_changed = render_slider("##" + label, value_in, min_v, mean_size, _L("Value")); ImGui::SameLine(); ImGui::PushItemWidth(m_control_width * 0.45f); - const bool is_tolerance_changed = render_slider("##tolerance_" + label, tolerance_in, 0.f, 0.5f * mean_size, _L("Tolerance")); +// const bool is_tolerance_changed = render_slider("##tolerance_" + label, tolerance_in, 0.f, 0.5f * mean_size, _L("Tolerance")); + const float max_tolerance_v = max_tolerance > 0.f ? std::min(max_tolerance, 0.5f * mean_size) : 0.5f * mean_size; + const bool is_tolerance_changed = render_slider("##tolerance_" + label, tolerance_in, 0.f, max_tolerance_v, _L("Tolerance")); return is_value_changed || is_tolerance_changed; } @@ -568,7 +632,7 @@ bool GLGizmoCut3D::render_connect_type_radio_button(CutConnectorType type) ImGui::PushItemWidth(m_control_width); if (ImGui::RadioButton(m_connector_types[size_t(type)].c_str(), m_connector_type == type)) { m_connector_type = type; - update_connector_shape(); +// update_connector_shape(); return true; } return false; @@ -606,6 +670,232 @@ bool GLGizmoCut3D::render_reset_button(const std::string& label_id, const std::s return revert; } +static double get_grabber_mean_size(const BoundingBoxf3& bb) +{ + return (bb.size().x() + bb.size().y() + bb.size().z()) / 30.; +} + +indexed_triangle_set GLGizmoCut3D::its_make_groove_plane() +{ + // values for calculation + + const float side_width = is_approx(m_groove.flaps_angle, 0.f) ? m_groove.depth : (m_groove.depth / sin(m_groove.flaps_angle)); + const float flaps_width = 2.f * side_width * cos(m_groove.flaps_angle); + + const float groove_half_width_upper = 0.5f * (m_groove.width); + const float groove_half_width_lower = 0.5f * (m_groove.width + flaps_width); + + const float cut_plane_radius = 1.5f * float(m_radius); + const float cut_plane_length = 1.5f * cut_plane_radius; + + const float groove_half_depth = 0.5f * m_groove.depth; + + const float x = 0.5f * cut_plane_radius; + const float y = 0.5f * cut_plane_length; + float z_upper = groove_half_depth; + float z_lower = -groove_half_depth; + + const float proj = y * tan(m_groove.angle); + + float ext_upper_x = groove_half_width_upper + proj; // upper_x extension + float ext_lower_x = groove_half_width_lower + proj; // lower_x extension + + float nar_upper_x = groove_half_width_upper - proj; // upper_x narrowing + float nar_lower_x = groove_half_width_lower - proj; // lower_x narrowing + + const float cut_plane_thiknes = 0.02f;// 0.02f * (float)get_grabber_mean_size(m_bounding_box); // cut_plane_thiknes + + // Vertices of the groove used to detection if groove is valid + // They are written as: + // {left_ext_lower, left_nar_lower, left_ext_upper, left_nar_upper, + // right_ext_lower, right_nar_lower, right_ext_upper, right_nar_upper } + { + m_groove_vertices.clear(); + m_groove_vertices.reserve(8); + + m_groove_vertices.emplace_back(Vec3f(-ext_lower_x, -y, z_lower).cast()); + m_groove_vertices.emplace_back(Vec3f(-nar_lower_x, y, z_lower).cast()); + m_groove_vertices.emplace_back(Vec3f(-ext_upper_x, -y, z_upper).cast()); + m_groove_vertices.emplace_back(Vec3f(-nar_upper_x, y, z_upper).cast()); + m_groove_vertices.emplace_back(Vec3f( ext_lower_x, -y, z_lower).cast()); + m_groove_vertices.emplace_back(Vec3f( nar_lower_x, y, z_lower).cast()); + m_groove_vertices.emplace_back(Vec3f( ext_upper_x, -y, z_upper).cast()); + m_groove_vertices.emplace_back(Vec3f( nar_upper_x, y, z_upper).cast()); + } + + // Different cases of groove plane: + + // groove is open + + if (groove_half_width_upper > proj && groove_half_width_lower > proj) { + indexed_triangle_set mesh; + + auto get_vertices = [x, y](float z_upper, float z_lower, float nar_upper_x, float nar_lower_x, float ext_upper_x, float ext_lower_x) { + return std::vector({ + // upper left part vertices + {-x, -y, z_upper}, {-x, y, z_upper}, {-nar_upper_x, y, z_upper}, {-ext_upper_x, -y, z_upper}, + // lower part vertices + {-ext_lower_x, -y, z_lower}, {-nar_lower_x, y, z_lower}, {nar_lower_x, y, z_lower}, {ext_lower_x, -y, z_lower}, + // upper right part vertices + {ext_upper_x, -y, z_upper}, {nar_upper_x, y, z_upper}, {x, y, z_upper}, {x, -y, z_upper} + }); + }; + + mesh.vertices = get_vertices(z_upper, z_lower, nar_upper_x, nar_lower_x, ext_upper_x, ext_lower_x); + mesh.vertices.reserve(2 * mesh.vertices.size()); + + z_upper -= cut_plane_thiknes; + z_lower -= cut_plane_thiknes; + + const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove.flaps_angle); + + nar_upper_x += under_x_shift; + nar_lower_x += under_x_shift; + ext_upper_x += under_x_shift; + ext_lower_x += under_x_shift; + + std::vector vertices = get_vertices(z_upper, z_lower, nar_upper_x, nar_lower_x, ext_upper_x, ext_lower_x); + mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + + mesh.indices = { + // above view + {5,4,7}, {5,7,6}, // lower part + {3,4,5}, {3,5,2}, // left side + {9,6,8}, {8,6,7}, // right side + {1,0,2}, {2,0,3}, // upper left part + {9,8,10}, {10,8,11}, // upper right part + // under view + {20,21,22}, {20,22,23}, // upper right part + {12,13,14}, {12,14,15}, // upper left part + {18,21,20}, {18,20,19}, // right side + {16,15,14}, {16,14,17}, // left side + {16,17,18}, {16,18,19}, // lower part + // left edge + {1,13,0}, {0,13,12}, + // front edge + {0,12,3}, {3,12,15}, {3,15,4}, {4,15,16}, {4,16,7}, {7,16,19}, {7,19,20}, {7,20,8}, {8,20,11}, {11,20,23}, + // right edge + {11,23,10}, {10,23,22}, + // back edge + {1,13,2}, {2,13,14}, {2,14,17}, {2,17,5}, {5,17,6}, {6,17,18}, {6,18,9}, {9,18,21}, {9,21,10}, {10,21,22} + }; + return mesh; + } + + float cross_pt_upper_y = groove_half_width_upper / tan(m_groove.angle); + + // groove is closed + + if (groove_half_width_upper < proj && groove_half_width_lower < proj) { + float cross_pt_lower_y = groove_half_width_lower / tan(m_groove.angle); + + indexed_triangle_set mesh; + + auto get_vertices = [x, y](float z_upper, float z_lower, float cross_pt_upper_y, float cross_pt_lower_y, float ext_upper_x, float ext_lower_x) { + return std::vector({ + // upper part vertices + {-x, -y, z_upper}, {-x, y, z_upper}, {x, y, z_upper}, {x, -y, z_upper}, + {ext_upper_x, -y, z_upper}, {0.f, cross_pt_upper_y, z_upper}, {-ext_upper_x, -y, z_upper}, + // lower part vertices + {-ext_lower_x, -y, z_lower}, {0.f, cross_pt_lower_y, z_lower}, {ext_lower_x, -y, z_lower} + }); + }; + + mesh.vertices = get_vertices(z_upper, z_lower, cross_pt_upper_y, cross_pt_lower_y, ext_upper_x, ext_lower_x); + mesh.vertices.reserve(2 * mesh.vertices.size()); + + z_upper -= cut_plane_thiknes; + z_lower -= cut_plane_thiknes; + + const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove.flaps_angle); + + cross_pt_upper_y += cut_plane_thiknes; + cross_pt_lower_y += cut_plane_thiknes; + ext_upper_x += under_x_shift; + ext_lower_x += under_x_shift; + + std::vector vertices = get_vertices(z_upper, z_lower, cross_pt_upper_y, cross_pt_lower_y, ext_upper_x, ext_lower_x); + mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + + mesh.indices = { + // above view + {8,7,9}, // lower part + {5,8,6}, {6,8,7}, // left side + {4,9,8}, {4,8,5}, // right side + {1,0,6}, {1,6,5},{1,5,2}, {2,5,4}, {2,4,3}, // upper part + // under view + {10,11,16}, {16,11,15}, {15,11,12}, {15,12,14}, {14,12,13}, // upper part + {18,15,14}, {14,18,19}, // right side + {17,16,15}, {17,15,18}, // left side + {17,18,19}, // lower part + // left edge + {1,11,0}, {0,11,10}, + // front edge + {0,10,6}, {6,10,16}, {6,17,16}, {6,7,17}, {7,17,19}, {7,19,9}, {4,14,19}, {4,19,9}, {4,14,13}, {4,13,3}, + // right edge + {3,13,12}, {3,12,2}, + // back edge + {2,12,11}, {2,11,1} + }; + + return mesh; + } + + // groove is closed from the roof + + indexed_triangle_set mesh; + mesh.vertices = { + // upper part vertices + {-x, -y, z_upper}, {-x, y, z_upper}, {x, y, z_upper}, {x, -y, z_upper}, + {ext_upper_x, -y, z_upper}, {0.f, cross_pt_upper_y, z_upper}, {-ext_upper_x, -y, z_upper}, + // lower part vertices + {-ext_lower_x, -y, z_lower}, {-nar_lower_x, y, z_lower}, {nar_lower_x, y, z_lower}, {ext_lower_x, -y, z_lower} + }; + + mesh.vertices.reserve(2 * mesh.vertices.size() + 1); + + z_upper -= cut_plane_thiknes; + z_lower -= cut_plane_thiknes; + + const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove.flaps_angle); + + nar_lower_x += under_x_shift; + ext_upper_x += under_x_shift; + ext_lower_x += under_x_shift; + + std::vector vertices = { + // upper part vertices + {-x, -y, z_upper}, {-x, y, z_upper}, {x, y, z_upper}, {x, -y, z_upper}, + {ext_upper_x, -y, z_upper}, {under_x_shift, cross_pt_upper_y, z_upper}, {-under_x_shift, cross_pt_upper_y, z_upper}, {-ext_upper_x, -y, z_upper}, + // lower part vertices + {-ext_lower_x, -y, z_lower}, {-nar_lower_x, y, z_lower}, {nar_lower_x, y, z_lower}, {ext_lower_x, -y, z_lower} + }; + mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + + mesh.indices = { + // above view + {8,7,10}, {8,10,9}, // lower part + {5,8,7}, {5,7,6}, // left side + {4,10,9}, {4,9,5}, // right side + {1,0,6}, {1,6,5},{1,5,2}, {2,5,4}, {2,4,3}, // upper part + // under view + {11,12,18}, {18,12,17}, {17,12,16}, {16,12,13}, {16,13,15}, {15,13,14}, // upper part + {21,16,15}, {21,15,22}, // right side + {19,18,17}, {19,17,20}, // left side + {19,20,21}, {19,21,22}, // lower part + // left edge + {1,12,11}, {1,11,0}, + // front edge + {0,11,18}, {0,18,6}, {7,19,18}, {7,18,6}, {7,19,22}, {7,22,10}, {10,22,15}, {10,15,4}, {4,15,14}, {4,14,3}, + // right edge + {3,14,13}, {3,14,2}, + // back edge + {2,13,12}, {2,12,1}, {5,16,21}, {5,21,9}, {9,21,20}, {9,20,8}, {5,17,20}, {5,20,8} + }; + + return mesh; +} + void GLGizmoCut3D::render_cut_plane() { if (cut_line_processing()) @@ -623,19 +913,16 @@ void GLGizmoCut3D::render_cut_plane() shader->start_using(); const Camera& camera = wxGetApp().plater()->get_camera(); - const Transform3d view_model_matrix = camera.get_view_matrix() * translation_transform(m_plane_center) * m_rotation_m; - shader->set_uniform("view_model_matrix", view_model_matrix); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - if (can_perform_cut() && has_valid_contour()) { - if (m_hover_id == CutPlane) - m_plane.model.set_color({ 0.9f, 0.9f, 0.9f, 0.5f }); - else - m_plane.model.set_color({ 0.8f, 0.8f, 0.8f, 0.5f }); - } - else - m_plane.model.set_color({ 1.0f, 0.8f, 0.8f, 0.5f }); + ColorRGBA cp_clr = can_perform_cut() && has_valid_groove() ? CUT_PLANE_DEF_COLOR : CUT_PLANE_ERR_COLOR; + if (m_mode == size_t(CutMode::cutTongueAndGroove)) + cp_clr.a(cp_clr.a() - 0.1f); + m_plane.model.set_color(cp_clr); + + const Transform3d view_model_matrix = camera.get_view_matrix() * translation_transform(m_plane_center) * m_rotation_m; + shader->set_uniform("view_model_matrix", view_model_matrix); m_plane.model.render(); glsafe(::glEnable(GL_CULL_FACE)); @@ -644,11 +931,6 @@ void GLGizmoCut3D::render_cut_plane() shader->stop_using(); } -static double get_grabber_mean_size(const BoundingBoxf3& bb) -{ - return (bb.size().x() + bb.size().y() + bb.size().z()) / 30.; -} - static double get_half_size(double size) { return std::max(size * 0.35, 0.05); @@ -703,8 +985,10 @@ void GLGizmoCut3D::render_rotation_snapping(GrabberID axis, const ColorRGBA& col if (axis == X) view_model_matrix = view_model_matrix * rotation_transform(0.5 * PI * Vec3d::UnitY()) * rotation_transform(-PI * Vec3d::UnitZ()); - else + else if (axis == Y) view_model_matrix = view_model_matrix * rotation_transform(-0.5 * PI * Vec3d::UnitZ()) * rotation_transform(-0.5 * PI * Vec3d::UnitY()); + else + view_model_matrix = view_model_matrix * rotation_transform(-0.5 * PI * Vec3d::UnitZ()); line_shader->start_using(); line_shader->set_uniform("projection_matrix", camera.get_projection_matrix()); @@ -724,9 +1008,9 @@ void GLGizmoCut3D::render_rotation_snapping(GrabberID axis, const ColorRGBA& col line_shader->stop_using(); } -void GLGizmoCut3D::render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix) +void GLGizmoCut3D::render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix, double line_len_koef/* = 1.0*/) { - const Transform3d line_view_matrix = view_matrix * scale_transform(Vec3d(1.0, 1.0, m_grabber_connection_len)); + const Transform3d line_view_matrix = view_matrix * scale_transform(Vec3d(1.0, 1.0, line_len_koef * m_grabber_connection_len)); render_line(m_grabber_connection, color, line_view_matrix, 0.2f); }; @@ -742,9 +1026,9 @@ void GLGizmoCut3D::render_cut_plane_grabbers() const double mean_size = get_grabber_mean_size(m_bounding_box); double size; - const bool dragging_by_cut_plane = m_dragging && m_hover_id == CutPlane; + const bool no_xy_dragging = m_dragging && m_hover_id == CutPlane; - if (!dragging_by_cut_plane) { + if (!no_xy_dragging && m_hover_id != CutPlaneZRotation && m_hover_id != CutPlaneXMove && m_hover_id != CutPlaneYMove) { render_grabber_connection(GRABBER_COLOR, view_matrix); // render sphere grabber @@ -755,11 +1039,11 @@ void GLGizmoCut3D::render_cut_plane_grabbers() render_model(m_sphere.model, color, view_matrix * translation_transform(m_grabber_connection_len * Vec3d::UnitZ()) * scale_transform(size)); } - const bool no_one_grabber_hovered = !m_dragging && (m_hover_id < 0 || m_hover_id == CutPlane); + const bool no_xy_grabber_hovered = !m_dragging && (m_hover_id < 0 || m_hover_id == CutPlane); // render X grabber - if (no_one_grabber_hovered || m_hover_id == X) + if (no_xy_grabber_hovered || m_hover_id == X) { size = m_dragging && m_hover_id == X ? get_dragging_half_size(mean_size) : get_half_size(mean_size); const Vec3d cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); @@ -778,7 +1062,7 @@ void GLGizmoCut3D::render_cut_plane_grabbers() // render Y grabber - if (no_one_grabber_hovered || m_hover_id == Y) + if (no_xy_grabber_hovered || m_hover_id == Y) { size = m_dragging && m_hover_id == Y ? get_dragging_half_size(mean_size) : get_half_size(mean_size); const Vec3d cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); @@ -792,7 +1076,73 @@ void GLGizmoCut3D::render_cut_plane_grabbers() Vec3d offset = Vec3d(1.25 * size, 0.0, m_grabber_connection_len); render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); offset = Vec3d(-1.25 * size, 0.0, m_grabber_connection_len); - render_model(m_cone.model, color, view_matrix * translation_transform(offset)* rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); + render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); + } + + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { + + // render CutPlaneZRotation grabber + + if (no_xy_grabber_hovered || m_hover_id == CutPlaneZRotation) + { + size = 0.75 * (m_dragging ? get_dragging_half_size(mean_size) : get_half_size(mean_size)); + color = ColorRGBA::BLUE(); + const ColorRGBA cp_color = m_hover_id == CutPlaneZRotation ? color : m_plane.model.get_color(); + + const double grabber_shift = -1.75 * m_grabber_connection_len; + + render_model(m_sphere.model, cp_color, view_matrix * translation_transform(grabber_shift * Vec3d::UnitY()) * scale_transform(size)); + + if (m_hover_id == CutPlaneZRotation) { + const Vec3d cone_scale = Vec3d(0.75 * size, 0.75 * size, 1.8 * size); + + render_rotation_snapping(CutPlaneZRotation, color); + render_grabber_connection(GRABBER_COLOR, view_matrix * rotation_transform(0.5 * PI * Vec3d::UnitX()), 1.75); + + Vec3d offset = Vec3d(1.25 * size, grabber_shift, 0.0); + render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); + offset = Vec3d(-1.25 * size, grabber_shift, 0.0); + render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); + } + } + + const double xy_connection_len = 0.75 * m_grabber_connection_len; + + // render CutPlaneXMove grabber + + if (no_xy_grabber_hovered || m_hover_id == CutPlaneXMove) + { + size = (m_dragging ? get_dragging_half_size(mean_size) : get_half_size(mean_size)); + color = m_hover_id == CutPlaneXMove ? ColorRGBA::RED() : m_plane.model.get_color(); + + render_grabber_connection(GRABBER_COLOR, view_matrix * rotation_transform(0.5 * PI * Vec3d::UnitY()), 0.75); + + Vec3d offset = xy_connection_len * Vec3d::UnitX() - 0.5 * size * Vec3d::Ones(); + render_model(m_cube.model, color, view_matrix * translation_transform(offset) * scale_transform(size)); + + const Vec3d cone_scale = Vec3d(0.5 * size, 0.5 * size, 1.8 * size); + + offset = (size + xy_connection_len) * Vec3d::UnitX(); + render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); + } + + // render CutPlaneYMove grabber + + if (m_groove.angle > 0.0f && (no_xy_grabber_hovered || m_hover_id == CutPlaneYMove)) + { + size = (m_dragging ? get_dragging_half_size(mean_size) : get_half_size(mean_size)); + color = m_hover_id == CutPlaneYMove ? ColorRGBA::GREEN() : m_plane.model.get_color(); + + render_grabber_connection(GRABBER_COLOR, view_matrix * rotation_transform(-0.5 * PI * Vec3d::UnitX()), 0.75); + + Vec3d offset = xy_connection_len * Vec3d::UnitY() - 0.5 * size * Vec3d::Ones(); + render_model(m_cube.model, color, view_matrix * translation_transform(offset) * scale_transform(size)); + + const Vec3d cone_scale = Vec3d(0.5 * size, 0.5 * size, 1.8 * size); + + offset = (size + xy_connection_len) * Vec3d::UnitY(); + render_model(m_cone.model, color, view_matrix * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitX()) * scale_transform(cone_scale)); + } } } @@ -832,19 +1182,52 @@ bool GLGizmoCut3D::on_init() void GLGizmoCut3D::on_load(cereal::BinaryInputArchive& ar) { - ar( m_keep_upper, m_keep_lower, m_rotate_lower, m_rotate_upper, m_hide_cut_plane, m_mode, m_connectors_editing, - m_ar_plane_center, m_rotation_m); + size_t mode; + float groove_depth; + float groove_width; + float groove_flaps_angle; + float groove_angle; + float groove_depth_tolerance; + float groove_width_tolerance; + + ar( m_keep_upper, m_keep_lower, m_rotate_lower, m_rotate_upper, m_hide_cut_plane, mode, m_connectors_editing, + m_ar_plane_center, m_rotation_m, + groove_depth, groove_width, groove_flaps_angle, groove_angle, groove_depth_tolerance, groove_width_tolerance); + + m_start_dragging_m = m_rotation_m; m_transformed_bounding_box = transformed_bounding_box(m_ar_plane_center, m_rotation_m); set_center_pos(m_ar_plane_center); + if (m_mode != mode) + switch_to_mode(mode); + else if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { + if (!is_approx(m_groove.depth , groove_depth) || + !is_approx(m_groove.width , groove_width) || + !is_approx(m_groove.flaps_angle , groove_flaps_angle) || + !is_approx(m_groove.angle , groove_angle) || + !is_approx(m_groove.depth_tolerance, groove_depth_tolerance) || + !is_approx(m_groove.width_tolerance, groove_width_tolerance) ) + { + m_groove.depth = groove_depth; + m_groove.width = groove_width; + m_groove.flaps_angle = groove_flaps_angle; + m_groove.angle = groove_angle; + m_groove.depth_tolerance= groove_depth_tolerance; + m_groove.width_tolerance= groove_width_tolerance; + update_plane_model(); + } + reset_cut_by_contours(); + } + m_parent.request_extra_frame(); } void GLGizmoCut3D::on_save(cereal::BinaryOutputArchive& ar) const { ar( m_keep_upper, m_keep_lower, m_rotate_lower, m_rotate_upper, m_hide_cut_plane, m_mode, m_connectors_editing, - m_ar_plane_center, m_start_dragging_m); + m_ar_plane_center, m_start_dragging_m, + m_groove.depth, m_groove.width, m_groove.flaps_angle, m_groove.angle, m_groove.depth_tolerance, m_groove.width_tolerance); } std::string GLGizmoCut3D::on_get_name() const @@ -852,11 +1235,18 @@ std::string GLGizmoCut3D::on_get_name() const return _u8L("Cut"); } +void GLGizmoCut3D::apply_color_clip_plane_colors() +{ + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) + m_parent.set_color_clip_plane_colors({ CUT_PLANE_DEF_COLOR , CUT_PLANE_DEF_COLOR }); + else + m_parent.set_color_clip_plane_colors({ UPPER_PART_COLOR , LOWER_PART_COLOR }); +} + void GLGizmoCut3D::on_set_state() { if (m_state == On) { m_parent.set_use_color_clip_plane(true); - m_parent.set_color_clip_plane_colors({ UPPER_PART_COLOR , LOWER_PART_COLOR }); update_bb(); m_connectors_editing = !m_selected.empty(); @@ -889,7 +1279,9 @@ void GLGizmoCut3D::on_set_state() void GLGizmoCut3D::on_register_raycasters_for_picking() { - assert(m_raycasters.empty()); + // assert(m_raycasters.empty()); + if (!m_raycasters.empty()) + on_unregister_raycasters_for_picking(); // the gizmo grabbers are rendered on top of the scene, so the raytraced picker should take it into account m_parent.set_raycaster_gizmos_on_top(true); @@ -911,7 +1303,19 @@ void GLGizmoCut3D::on_register_raycasters_for_picking() m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, Z, *m_sphere.mesh_raycaster, Transform3d::Identity())); - m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlane, *m_plane.mesh_raycaster, Transform3d::Identity())); + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::FallbackGizmo, CutPlane, *m_plane.mesh_raycaster, Transform3d::Identity())); + + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneZRotation, *m_sphere.mesh_raycaster, Transform3d::Identity())); + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneZRotation, *m_cone.mesh_raycaster, Transform3d::Identity())); + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneZRotation, *m_cone.mesh_raycaster, Transform3d::Identity())); + + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneXMove, *m_cube.mesh_raycaster, Transform3d::Identity())); + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneXMove, *m_cone.mesh_raycaster, Transform3d::Identity())); + + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneYMove, *m_cube.mesh_raycaster, Transform3d::Identity())); + m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CutPlaneYMove, *m_cone.mesh_raycaster, Transform3d::Identity())); + } } update_raycasters_for_picking_transform(); @@ -920,6 +1324,7 @@ void GLGizmoCut3D::on_register_raycasters_for_picking() void GLGizmoCut3D::on_unregister_raycasters_for_picking() { m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo); + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::FallbackGizmo); m_raycasters.clear(); // the gizmo grabbers are rendered on top of the scene, so the raytraced picker should take it into account m_parent.set_raycaster_gizmos_on_top(false); @@ -1003,13 +1408,52 @@ void GLGizmoCut3D::update_raycasters_for_picking_transform() offset = Vec3d(-1.25 * size, 0.0, m_grabber_connection_len); m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(scale)); - offset = 1.25 * size * Vec3d::UnitZ(); m_raycasters[id++]->set_transform(trafo * translation_transform(m_grabber_connection_len * Vec3d::UnitZ()) * scale_transform(size)); m_raycasters[id++]->set_transform(trafo); + + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { + + double grabber_y_shift = -1.75 * m_grabber_connection_len; + + m_raycasters[id++]->set_transform(trafo * translation_transform(grabber_y_shift * Vec3d::UnitY()) * scale_transform(size)); + + offset = Vec3d(1.25 * size, grabber_y_shift, 0.0); + m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(scale)); + offset = Vec3d(-1.25 * size, grabber_y_shift, 0.0); + m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitY()) * scale_transform(scale)); + + const double xy_connection_len = 0.75 * m_grabber_connection_len; + const Vec3d cone_scale = Vec3d(0.5 * size, 0.5 * size, 1.8 * size); + + offset = xy_connection_len * Vec3d::UnitX() - 0.5 * size * Vec3d::Ones(); + m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * scale_transform(size)); + offset = (size + xy_connection_len) * Vec3d::UnitX(); + m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale)); + + if (m_groove.angle > 0.0f) { + offset = xy_connection_len * Vec3d::UnitY() - 0.5 * size * Vec3d::Ones(); + m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * scale_transform(size)); + offset = (size + xy_connection_len) * Vec3d::UnitY(); + m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(-0.5 * PI * Vec3d::UnitX()) * scale_transform(cone_scale)); + } + else { + // discard transformation for CutPlaneYMove grabbers + m_raycasters[id++]->set_transform(Transform3d::Identity()); + m_raycasters[id++]->set_transform(Transform3d::Identity()); + } + } } } +void GLGizmoCut3D::update_plane_model() +{ + m_plane.reset(); + on_unregister_raycasters_for_picking(); + + init_picking_models(); +} + void GLGizmoCut3D::on_set_hover_id() { } @@ -1058,8 +1502,8 @@ Vec3d GLGizmoCut3D::mouse_position_in_local_plane(GrabberID axis, const Linef3& m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitZ())); break; } - default: case Z: + default: { // no rotation applied break; @@ -1072,17 +1516,21 @@ Vec3d GLGizmoCut3D::mouse_position_in_local_plane(GrabberID axis, const Linef3& return transform(mouse_ray, m).intersect_plane(0.0); } -void GLGizmoCut3D::dragging_grabber_z(const GLGizmoBase::UpdateData &data) +void GLGizmoCut3D::dragging_grabber_move(const GLGizmoBase::UpdateData &data) { - const Vec3d grabber_init_pos = (m_hover_id == CutPlane ? 0. : m_grabber_connection_len) * Vec3d::UnitZ(); - const Vec3d starting_drag_position = translation_transform(m_plane_center) * m_rotation_m * grabber_init_pos; - double projection = 0.0; + Vec3d starting_drag_position; + if (m_hover_id == Z) + starting_drag_position = translation_transform(m_plane_center) * m_rotation_m * (m_grabber_connection_len * Vec3d::UnitZ()); + else + starting_drag_position = m_cut_plane_start_move_pos; - Vec3d starting_vec = m_rotation_m * Vec3d::UnitZ(); + double projection = 0.0; + + Vec3d starting_vec = m_rotation_m * (m_hover_id == CutPlaneXMove ? Vec3d::UnitX() : m_hover_id == CutPlaneYMove ? Vec3d::UnitY() : Vec3d::UnitZ()); if (starting_vec.norm() != 0.0) { const Vec3d mouse_dir = data.mouse_ray.unit_vector(); - // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position - // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form + // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing through the starting position + // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebraic form // in our case plane normal and ray direction are the same (orthogonal view) // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal const Vec3d inters = data.mouse_ray.a + (starting_drag_position - data.mouse_ray.a).dot(mouse_dir) * mouse_dir; @@ -1106,7 +1554,7 @@ void GLGizmoCut3D::dragging_grabber_z(const GLGizmoBase::UpdateData &data) m_was_cut_plane_dragged = true; } -void GLGizmoCut3D::dragging_grabber_xy(const GLGizmoBase::UpdateData &data) +void GLGizmoCut3D::dragging_grabber_rotation(const GLGizmoBase::UpdateData &data) { const Vec2d mouse_pos = to_2d(mouse_position_in_local_plane((GrabberID)m_hover_id, data.mouse_ray)); @@ -1133,14 +1581,14 @@ void GLGizmoCut3D::dragging_grabber_xy(const GLGizmoBase::UpdateData &data) if (is_approx(theta, two_pi)) theta = 0.0; - if (m_hover_id == X) + if (m_hover_id != Y) theta += 0.5 * PI; if (!is_approx(theta, 0.0)) reset_cut_by_contours(); Vec3d rotation = Vec3d::Zero(); - rotation[m_hover_id] = theta; + rotation[m_hover_id == CutPlaneZRotation ? Z : m_hover_id] = theta; const Transform3d rotation_tmp = m_start_dragging_m * rotation_transform(rotation); const bool update_tbb = !m_rotation_m.rotation().isApprox(rotation_tmp.rotation()); @@ -1173,13 +1621,16 @@ void GLGizmoCut3D::on_dragging(const UpdateData& data) { if (m_hover_id < 0) return; - if (m_hover_id == Z || m_hover_id == CutPlane) - dragging_grabber_z(data); - else if (m_hover_id == X || m_hover_id == Y) - dragging_grabber_xy(data); + if (m_hover_id == Z || m_hover_id == CutPlane || m_hover_id == CutPlaneXMove || m_hover_id == CutPlaneYMove) + dragging_grabber_move(data); + else if (m_hover_id == X || m_hover_id == Y || m_hover_id == CutPlaneZRotation) + dragging_grabber_rotation(data); else if (m_hover_id >= m_connectors_group_id && m_connector_mode == CutConnectorMode::Manual) dragging_connector(data); check_and_update_connectors_state(); + + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) + reset_cut_by_contours(); } void GLGizmoCut3D::on_start_dragging() @@ -1188,23 +1639,26 @@ void GLGizmoCut3D::on_start_dragging() if (m_hover_id >= m_connectors_group_id && m_connector_mode == CutConnectorMode::Manual) Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move connector"), UndoRedo::SnapshotType::GizmoAction); - if (m_hover_id == X || m_hover_id == Y) + if (m_hover_id == X || m_hover_id == Y || m_hover_id == CutPlaneZRotation) m_start_dragging_m = m_rotation_m; } void GLGizmoCut3D::on_stop_dragging() { - if (m_hover_id == X || m_hover_id == Y) { + if (m_hover_id == X || m_hover_id == Y || m_hover_id == CutPlaneZRotation) { m_angle_arc.reset(); m_angle = 0.0; Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Rotate cut plane"), UndoRedo::SnapshotType::GizmoAction); m_start_dragging_m = m_rotation_m; } - else if (m_hover_id == Z || m_hover_id == CutPlane) { + else if (m_hover_id == Z || m_hover_id == CutPlane || m_hover_id == CutPlaneXMove|| m_hover_id == CutPlaneYMove) { if (m_was_cut_plane_dragged) Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move cut plane"), UndoRedo::SnapshotType::GizmoAction); m_ar_plane_center = m_plane_center; } + + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) + reset_cut_by_contours(); //check_and_update_connectors_state(); } @@ -1218,7 +1672,8 @@ void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos, bool update_tbb /*=fa bool can_set_center_pos = false; { - if (tbb.max.z() > -.5 && tbb.min.z() < .5) + double limit_val = /*CutMode(m_mode) == CutMode::cutTongueAndGroove ? 0.5 * double(m_groove.depth) : */0.5; + if (tbb.max.z() > -limit_val && tbb.min.z() < limit_val) can_set_center_pos = true; else { const double old_dist = (m_bb_center - m_plane_center).norm(); @@ -1284,8 +1739,14 @@ void GLGizmoCut3D::update_bb() m_bounding_box = box; + // check, if mode is set to Planar, when object has a connectors + if (const int object_idx = m_parent.get_selection().get_object_idx(); + object_idx >= 0 && !wxGetApp().plater()->model().objects[object_idx]->cut_connectors.empty()) + m_mode = size_t(CutMode::cutPlanar); + invalidate_cut_plane(); reset_cut_by_contours(); + apply_color_clip_plane_colors(); m_max_pos = box.max; m_min_pos = box.min; @@ -1296,6 +1757,8 @@ void GLGizmoCut3D::update_bb() else set_center_pos(m_bb_center); + m_contour_width = CutMode(m_mode) == CutMode::cutTongueAndGroove ? 0.f : 0.4f; + m_radius = box.radius(); m_grabber_connection_len = 0.5 * m_radius;// std::min(0.75 * m_radius, 35.0); m_grabber_radius = m_grabber_connection_len * 0.85; @@ -1305,9 +1768,15 @@ void GLGizmoCut3D::update_bb() m_snap_fine_in_radius = m_grabber_connection_len * 0.85; m_snap_fine_out_radius = m_grabber_connection_len * 1.15; + // input params for cut with tongue and groove + m_groove.depth = m_groove.depth_init = std::max(1.f , 0.5f * float(get_grabber_mean_size(m_bounding_box))); + m_groove.width = m_groove.width_init = 4.0f * m_groove.depth; + m_groove.flaps_angle = m_groove.flaps_angle_init = float(PI) / 3.f; + m_groove.angle = m_groove.angle_init = 0.f; m_plane.reset(); m_cone.reset(); m_sphere.reset(); + m_cube.reset(); m_grabber_connection.reset(); m_circle.reset(); m_scale.reset(); @@ -1335,10 +1804,17 @@ void GLGizmoCut3D::init_picking_models() m_sphere.model.init_from(its); m_sphere.mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); } + if (!m_cube.model.is_initialized()) { + indexed_triangle_set its = its_make_cube(1., 1., 1.); + m_cube.model.init_from(its); + m_cube.mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); + } if (!m_plane.model.is_initialized() && !m_hide_cut_plane && !m_connectors_editing) { const double cp_width = 0.02 * get_grabber_mean_size(m_bounding_box); - indexed_triangle_set its = its_make_frustum_dowel((double)m_cut_plane_radius_koef * m_radius, cp_width, m_cut_plane_as_circle ? 180 : 4); + indexed_triangle_set its = m_mode == size_t(CutMode::cutTongueAndGroove) ? its_make_groove_plane() : + its_make_frustum_dowel((double)m_cut_plane_radius_koef * m_radius, cp_width, m_cut_plane_as_circle ? 180 : 4); + m_plane.model.init_from(its); m_plane.mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); } @@ -1381,20 +1857,25 @@ void GLGizmoCut3D::render_clipper_cut() ::glEnable(GL_DEPTH_TEST); } - -GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx_in, const Vec3d& center, const Vec3d& normal, const CommonGizmosDataObjects::ObjectClipper& oc) +void GLGizmoCut3D::PartSelection::add_object(const ModelObject* object) { m_model = Model(); - m_model.add_object(*mo); - ModelObjectPtrs cut_part_ptrs = m_model.objects.front()->cut(instance_idx_in, cut_matrix, - ModelObjectCutAttribute::KeepUpper | - ModelObjectCutAttribute::KeepLower | - ModelObjectCutAttribute::KeepAsParts); - assert(cut_part_ptrs.size() == 1); - m_model = Model(); - m_model.add_object(*cut_part_ptrs.front()); + m_model.add_object(*object); - m_instance_idx = instance_idx_in; + const double sla_shift_z = wxGetApp().plater()->canvas3D()->get_selection().get_first_volume()->get_sla_shift_z(); + if (!is_approx(sla_shift_z, 0.)) { + Vec3d inst_offset = model_object()->instances[m_instance_idx]->get_offset(); + inst_offset[Z] += sla_shift_z; + model_object()->instances[m_instance_idx]->set_offset(inst_offset); + } +} + + +GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx_in, const Vec3d& center, const Vec3d& normal, const CommonGizmosDataObjects::ObjectClipper& oc) + : m_instance_idx(instance_idx_in) +{ + Cut cut(mo, instance_idx_in, cut_matrix); + add_object(cut.perform_with_plane().front()); const ModelVolumePtrs& volumes = model_object()->volumes; @@ -1465,6 +1946,26 @@ GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transfor m_valid = true; } +// In CutMode::cutTongueAndGroove we use PartSelection just for rendering +GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* object, int instance_idx_in) + : m_instance_idx (instance_idx_in) +{ + add_object(object); + + m_parts.clear(); + + for (const ModelVolume* volume : object->volumes) { + assert(volume != nullptr); + m_parts.emplace_back(Part{ GLModel(), MeshRaycaster(volume->mesh()), true, !volume->is_model_part() }); + m_parts.back().glmodel.init_from(volume->mesh()); + + // Now check whether this part is below or above the plane. + m_parts.back().selected = volume->is_from_upper(); + } + + m_valid = true; +} + void GLGizmoCut3D::PartSelection::render(const Vec3d* normal, GLModel& sphere_model) { if (! valid()) @@ -1561,6 +2062,16 @@ bool GLGizmoCut3D::PartSelection::is_one_object() const }); } +std::vector GLGizmoCut3D::PartSelection::get_cut_parts() +{ + std::vector parts; + + for (const auto& part : m_parts) + parts.push_back({part.selected, part.is_modifier}); + + return parts; +} + void GLGizmoCut3D::PartSelection::toggle_selection(const Vec2d& mouse_pos) { @@ -1615,6 +2126,8 @@ void GLGizmoCut3D::on_render() m_c->selection_info()->set_use_shift(true); } + // check objects visibility + toggle_model_objects_visibility(); update_clipper(); @@ -1646,9 +2159,7 @@ void GLGizmoCut3D::render_debug_input_window(float x) return; m_imgui->begin(wxString("DEBUG")); - ImVec2 pos = ImGui::GetWindowPos(); - pos.x = x; - ImGui::SetWindowPos(pos, ImGuiCond_Always); + m_imgui->end(); /* static bool hide_clipped = false; static bool fill_cut = false; @@ -1773,22 +2284,30 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors) m_imgui->text(m_labels_map["Type"]); bool type_changed = render_connect_type_radio_button(CutConnectorType::Plug); type_changed |= render_connect_type_radio_button(CutConnectorType::Dowel); + type_changed |= render_connect_type_radio_button(CutConnectorType::Snap); if (type_changed) apply_selected_connectors([this, &connectors] (size_t idx) { connectors[idx].attribs.type = CutConnectorType(m_connector_type); }); - m_imgui->disabled_begin(m_connector_type == CutConnectorType::Dowel); - if (type_changed && m_connector_type == CutConnectorType::Dowel) { - m_connector_style = int(CutConnectorStyle::Prism); - apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.style = CutConnectorStyle(m_connector_style); }); - } - if (render_combo(m_labels_map["Style"], m_connector_styles, m_connector_style)) - apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.style = CutConnectorStyle(m_connector_style); }); + m_imgui->disabled_begin(m_connector_type != CutConnectorType::Plug); + if (type_changed && m_connector_type == CutConnectorType::Dowel) { + m_connector_style = int(CutConnectorStyle::Prism); + apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.style = CutConnectorStyle(m_connector_style); }); + } + if (render_combo(m_labels_map["Style"], m_connector_styles, m_connector_style)) + apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.style = CutConnectorStyle(m_connector_style); }); m_imgui->disabled_end(); - if (render_combo(m_labels_map["Shape"], m_connector_shapes, m_connector_shape_id)) - apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.shape = CutConnectorShape(m_connector_shape_id); }); + m_imgui->disabled_begin(m_connector_type == CutConnectorType::Snap); + if (type_changed && m_connector_type == CutConnectorType::Snap) { + m_connector_shape_id = int(CutConnectorShape::Circle); + apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.shape = CutConnectorShape(m_connector_shape_id); }); + } + if (render_combo(m_labels_map["Shape"], m_connector_shapes, m_connector_shape_id)) + apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.shape = CutConnectorShape(m_connector_shape_id); }); + m_imgui->disabled_end(); - if (render_slider_double_input(m_labels_map["Depth"], m_connector_depth_ratio, m_connector_depth_ratio_tolerance)) + const float depth_min_value = m_connector_type == CutConnectorType::Snap ? m_connector_size : -0.1f; + if (render_slider_double_input(m_labels_map["Depth"], m_connector_depth_ratio, m_connector_depth_ratio_tolerance, depth_min_value)) apply_selected_connectors([this, &connectors](size_t idx) { if (m_connector_depth_ratio > 0) connectors[idx].height = m_connector_depth_ratio; @@ -1804,6 +2323,45 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors) connectors[idx].radius_tolerance = 0.5f * m_connector_size_tolerance; }); + if (m_connector_type == CutConnectorType::Snap) { + + const std::string format = "%.0f %%"; + + bool is_changed = false; + { + const std::string label = _u8L("Bulge"); + ImGuiWrapper::text(label); + + ImGui::SameLine(m_label_width); + ImGui::PushItemWidth(m_control_width * 0.7f); + + float val = m_snap_bulge_proportion *100.f; + if (m_imgui->slider_float(("##snap_" + label).c_str(), &val, 5.f, 100.f * m_snap_space_proportion, format.c_str(), 1.f, true, _u8L("Bulge proportion related to radius"))) { + m_snap_bulge_proportion = val * 0.01f; + is_changed = true; + } + } + + { + const std::string label = _u8L("Space"); + ImGuiWrapper::text(label); + + ImGui::SameLine(m_label_width); + ImGui::PushItemWidth(m_control_width * 0.7f); + + float val = m_snap_space_proportion *100.f; + if (m_imgui->slider_float(("##snap_" + label).c_str(), &val, 10.f, 50.f, format.c_str(), 1.f, true, _u8L("Space proportion related to radius"))) { + m_snap_space_proportion = val * 0.01f; + is_changed = true; + } + } + + if (is_changed) { + update_connector_shape(); + update_raycasters_for_picking(); + } + } + ImGui::Separator(); if (m_imgui->button(_L("Confirm connectors"))) { @@ -1880,30 +2438,50 @@ void GLGizmoCut3D::flip_cut_plane() update_clipper(); m_part_selection.turn_over_selection(); + + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) + reset_cut_by_contours(); } void GLGizmoCut3D::reset_cut_by_contours() { m_part_selection = PartSelection(); - const Selection& selection = m_parent.get_selection(); - const ModelObjectPtrs& model_objects = selection.get_model()->objects; - m_parent.toggle_model_objects_visibility(true, model_objects[selection.get_object_idx()], selection.get_instance_idx()); + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { + if (m_dragging || m_groove_editing || !has_valid_groove()) + return; + process_contours(); + } + else + toggle_model_objects_visibility(); } void GLGizmoCut3D::process_contours() { - reset_cut_by_contours(); - const Selection& selection = m_parent.get_selection(); const ModelObjectPtrs& model_objects = selection.get_model()->objects; - wxBusyCursor wait; const int instance_idx = selection.get_instance_idx(); + if (instance_idx < 0) + return; const int object_idx = selection.get_object_idx(); - m_part_selection = PartSelection(model_objects[object_idx], get_cut_matrix(selection), instance_idx, m_plane_center, m_cut_normal, *m_c->object_clipper()); - m_parent.toggle_model_objects_visibility(false); + wxBusyCursor wait; + + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { + if (has_valid_groove()) { + Cut cut(model_objects[object_idx], instance_idx, get_cut_matrix(selection)); + const ModelObjectPtrs& new_objects = cut.perform_with_groove(m_groove, m_rotation_m, true); + if (!new_objects.empty()) + m_part_selection = PartSelection(new_objects.front(), instance_idx); + } + } + else { + reset_cut_by_contours(); + m_part_selection = PartSelection(model_objects[object_idx], get_cut_matrix(selection), instance_idx, m_plane_center, m_cut_normal, *m_c->object_clipper()); + } + + toggle_model_objects_visibility(); } void GLGizmoCut3D::render_flip_plane_button(bool disable_pred /*=false*/) @@ -1948,12 +2526,100 @@ void GLGizmoCut3D::render_color_marker(float size, const ImU32& color) ImGuiWrapper::text(" "); } +void GLGizmoCut3D::render_groove_float_input(const std::string& label, float& in_val, const float& init_val, float& in_tolerance) +{ + bool is_changed{false}; + + float val = in_val; + float tolerance = in_tolerance; + if (render_slider_double_input(label, val, tolerance, -0.1f, std::min(0.3f*in_val, 1.5f))) { + if (m_imgui->get_last_slider_status().can_take_snapshot) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), format_wxstr("%1%: %2%", _L("Groove change"), label), UndoRedo::SnapshotType::GizmoAction); + m_imgui->get_last_slider_status().invalidate_snapshot(); + m_groove_editing = true; + } + in_val = val; + in_tolerance = tolerance; + is_changed = true; + } + + ImGui::SameLine(); + + m_imgui->disabled_begin(is_approx(in_val, init_val) && is_approx(in_tolerance, 0.1f)); + const std::string act_name = _u8L("Reset"); + if (render_reset_button(("##groove_" + label + act_name).c_str(), act_name)) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), format_wxstr("%1%: %2%", act_name, label), UndoRedo::SnapshotType::GizmoAction); + in_val = init_val; + in_tolerance = 0.1f; + is_changed = true; + } + m_imgui->disabled_end(); + + if (is_changed) { + update_plane_model(); + reset_cut_by_contours(); + } + + if (m_is_slider_editing_done) { + m_groove_editing = false; + reset_cut_by_contours(); + } +} + +void GLGizmoCut3D::render_groove_angle_input(const std::string& label, float& in_val, const float& init_val, float min_val, float max_val) +{ + bool is_changed{ false }; + + ImGuiWrapper::text(label); + + ImGui::SameLine(m_label_width); + ImGui::PushItemWidth(m_control_width * 0.7f); + + float val = rad2deg(in_val); + const float old_val = val; + + const std::string format = "%.0f " + _u8L("°"); + m_imgui->slider_float(("##groove_" + label).c_str(), &val, min_val, max_val, format.c_str(), 1.f, true, from_u8(label)); + + m_is_slider_editing_done |= m_imgui->get_last_slider_status().deactivated_after_edit; + if (!is_approx(old_val, val)) { + if (m_imgui->get_last_slider_status().can_take_snapshot) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), format_wxstr("%1%: %2%", _L("Groove change"), label), UndoRedo::SnapshotType::GizmoAction); + m_imgui->get_last_slider_status().invalidate_snapshot(); + m_groove_editing = true; + } + in_val = deg2rad(val); + is_changed = true; + } + + ImGui::SameLine(); + + m_imgui->disabled_begin(is_approx(in_val, init_val)); + const std::string act_name = _u8L("Reset"); + if (render_reset_button(("##groove_" + label + act_name).c_str(), act_name)) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), format_wxstr("%1%: %2%", act_name, label), UndoRedo::SnapshotType::GizmoAction); + in_val = init_val; + is_changed = true; + } + m_imgui->disabled_end(); + + if (is_changed) { + update_plane_model(); + reset_cut_by_contours(); + } + + if (m_is_slider_editing_done) { + m_groove_editing = false; + reset_cut_by_contours(); + } +} + + void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) { - // WIP : cut plane mode - // render_combo(_u8L("Mode"), m_modes, m_mode); - - if (m_mode == size_t(CutMode::cutPlanar)) { +// if (m_mode == size_t(CutMode::cutPlanar)) { + CutMode mode = CutMode(m_mode); + if (mode == CutMode::cutPlanar || mode == CutMode::cutTongueAndGroove) { ImGui::AlignTextToFramePadding(); ImGuiWrapper::text(wxString(ImGui::InfoMarkerSmall)); ImGui::SameLine(); @@ -1961,6 +2627,13 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) get_wraped_wxString(_L("Hold SHIFT key to draw a cut line"), 40)); ImGui::Separator(); + const bool has_connectors = !connectors.empty(); + + m_imgui->disabled_begin(has_connectors); + if (render_cut_mode_combo()) + mode = CutMode(m_mode); + m_imgui->disabled_end(); + render_build_size(); ImGui::AlignTextToFramePadding(); @@ -1969,8 +2642,6 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) render_move_center_input(Z); ImGui::SameLine(); - const bool has_connectors = !connectors.empty(); - const bool is_cut_plane_init = m_rotation_m.isApprox(Transform3d::Identity()) && m_bb_center.isApprox(m_plane_center); m_imgui->disabled_begin(is_cut_plane_init); wxString act_name = _L("Reset cutting plane"); @@ -1982,23 +2653,34 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) // render_flip_plane_button(); - add_vertical_scaled_interval(0.75f); + if (mode == CutMode::cutPlanar) { + add_vertical_scaled_interval(0.75f); - m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower || m_keep_as_parts || (m_part_selection.valid() && m_part_selection.is_one_object())); - if (m_imgui->button(has_connectors ? _L("Edit connectors") : _L("Add connectors"))) - set_connectors_editing(true); - m_imgui->disabled_end(); + m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower || m_keep_as_parts || (m_part_selection.valid() && m_part_selection.is_one_object())); + if (m_imgui->button(has_connectors ? _L("Edit connectors") : _L("Add connectors"))) + set_connectors_editing(true); + m_imgui->disabled_end(); - ImGui::SameLine(1.5f * m_control_width); + ImGui::SameLine(1.5f * m_control_width); - m_imgui->disabled_begin(is_cut_plane_init && !has_connectors); - act_name = _L("Reset cut"); - if (m_imgui->button(act_name, _L("Reset cutting plane and remove connectors"))) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), act_name, UndoRedo::SnapshotType::GizmoAction); - reset_cut_plane(); - reset_connectors(); - } - m_imgui->disabled_end(); + m_imgui->disabled_begin(is_cut_plane_init && !has_connectors); + act_name = _L("Reset cut"); + if (m_imgui->button(act_name, _L("Reset cutting plane and remove connectors"))) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), act_name, UndoRedo::SnapshotType::GizmoAction); + reset_cut_plane(); + reset_connectors(); + } + m_imgui->disabled_end(); + } + else if (mode == CutMode::cutTongueAndGroove) { + m_is_slider_editing_done = false; + ImGui::Separator(); + ImGuiWrapper::text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, m_labels_map["Groove"] + ": "); + render_groove_float_input(m_labels_map["Depth"], m_groove.depth, m_groove.depth_init, m_groove.depth_tolerance); + render_groove_float_input(m_labels_map["Width"], m_groove.width, m_groove.width_init, m_groove.width_tolerance); + render_groove_angle_input(m_labels_map["Flaps Angle"], m_groove.flaps_angle, m_groove.flaps_angle_init, 30.f, 120.f); + render_groove_angle_input(m_labels_map["Groove Angle"], m_groove.angle, m_groove.angle_init, 0.f, 15.f); + } ImGui::Separator(); @@ -2067,7 +2749,7 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) add_vertical_scaled_interval(0.75f); - m_imgui->disabled_begin(has_connectors || m_part_selection.valid()); + m_imgui->disabled_begin(has_connectors || m_part_selection.valid() || mode == CutMode::cutTongueAndGroove); ImGuiWrapper::text(_L("Cut into") + ":"); if (m_part_selection.valid()) @@ -2092,7 +2774,7 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) ImGui::Separator(); - m_imgui->disabled_begin(!m_is_contour_changed && !can_perform_cut()); + m_imgui->disabled_begin(!can_perform_cut()); if(m_imgui->button(_L("Perform cut"))) perform_cut(m_parent.get_selection()); m_imgui->disabled_end(); @@ -2188,8 +2870,6 @@ void GLGizmoCut3D::init_input_window_data(CutConnectors &connectors) void GLGizmoCut3D::render_input_window_warning() const { - if (m_is_contour_changed) - return; if (! m_invalid_connectors_idxs.empty()) { wxString out = wxString(ImGui::WarningMarkerSmall) + _L("Invalid connectors detected") + ":"; if (m_info_stats.outside_cut_contour > size_t(0)) @@ -2206,6 +2886,8 @@ void GLGizmoCut3D::render_input_window_warning() const m_imgui->text(wxString(ImGui::WarningMarkerSmall) + _L("Select at least one object to keep after cutting.")); if (!has_valid_contour()) m_imgui->text(wxString(ImGui::WarningMarkerSmall) + _L("Cut plane is placed out of object")); + else if (!has_valid_groove()) + m_imgui->text(wxString(ImGui::WarningMarkerSmall) + _L("Cut plane with groove is invalid")); } void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) @@ -2315,6 +2997,8 @@ void GLGizmoCut3D::check_and_update_connectors_state() { m_info_stats.invalidate(); m_invalid_connectors_idxs.clear(); + if (CutMode(m_mode) != CutMode::cutPlanar) + return; const ModelObject* mo = m_c->selection_info()->model_object(); auto inst_id = m_c->selection_info()->get_active_instance(); if (inst_id < 0) @@ -2332,11 +3016,31 @@ void GLGizmoCut3D::check_and_update_connectors_state() } } +void GLGizmoCut3D::toggle_model_objects_visibility() +{ + bool has_active_volume = false; + std::vector>* raycasters = m_parent.get_raycasters_for_picking(SceneRaycaster::EType::Volume); + for (const auto raycaster : *raycasters) + if (raycaster->is_active()) { + has_active_volume = true; + break; + } + + if (m_part_selection.valid() && has_active_volume) + m_parent.toggle_model_objects_visibility(false); + else if (!m_part_selection.valid() && !has_active_volume) { + const Selection& selection = m_parent.get_selection(); + const ModelObjectPtrs& model_objects = selection.get_model()->objects; + m_parent.toggle_model_objects_visibility(true, model_objects[selection.get_object_idx()], selection.get_instance_idx()); + } +} + void GLGizmoCut3D::render_connectors() { ::glEnable(GL_DEPTH_TEST); - if (m_is_contour_changed || cut_line_processing() || + if (cut_line_processing() || + CutMode(m_mode) != CutMode::cutPlanar || m_connector_mode == CutConnectorMode::Auto || !m_c->selection_info()) return; @@ -2412,10 +3116,51 @@ bool GLGizmoCut3D::can_perform_cut() const { if (! m_invalid_connectors_idxs.empty() || (!m_keep_upper && !m_keep_lower) || m_connectors_editing) return false; + + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) + return has_valid_groove(); + if (m_part_selection.valid()) return ! m_part_selection.is_one_object(); - return true;// has_valid_contour(); + return true; +} + +bool GLGizmoCut3D::has_valid_groove() const +{ + if (CutMode(m_mode) != CutMode::cutTongueAndGroove) + return true; + + const float flaps_width = -2.f * m_groove.depth / tan(m_groove.flaps_angle); + if (flaps_width > m_groove.width) + return false; + + const Selection& selection = m_parent.get_selection(); + const auto&list = selection.get_volume_idxs(); + // is more volumes selected? + if (list.empty()) + return false; + + const Transform3d cp_matrix = translation_transform(m_plane_center) * m_rotation_m; + + for (size_t id = 0; id < m_groove_vertices.size(); id += 2) { + const Vec3d beg = cp_matrix * m_groove_vertices[id]; + const Vec3d end = cp_matrix * m_groove_vertices[id + 1]; + + bool intersection = false; + for (const unsigned int volume_idx : list) { + const GLVolume* glvol = selection.get_volume(volume_idx); + if (!glvol->is_modifier && + glvol->mesh_raycaster->intersects_line(beg, end - beg, glvol->world_matrix())) { + intersection = true; + break; + } + } + if (!intersection) + return false; + } + + return true; } bool GLGizmoCut3D::has_valid_contour() const @@ -2426,6 +3171,8 @@ bool GLGizmoCut3D::has_valid_contour() const void GLGizmoCut3D::apply_connectors_in_model(ModelObject* mo, int &dowels_count) { + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) + return; if (m_connector_mode == CutConnectorMode::Manual) { clear_selection(); @@ -2442,7 +3189,7 @@ void GLGizmoCut3D::apply_connectors_in_model(ModelObject* mo, int &dowels_count) connector.pos += m_cut_normal * 0.5 * double(connector.height); } } - mo->apply_cut_connectors(_u8L("Connector")); + apply_cut_connectors(mo, _u8L("Connector")); } } @@ -2516,7 +3263,9 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) // This shall delete the part selection class and deallocate the memory. ScopeGuard part_selection_killer([this]() { m_part_selection = PartSelection(); }); - const bool cut_by_contour = m_part_selection.valid(); + const bool cut_with_groove = CutMode(m_mode) == CutMode::cutTongueAndGroove; + const bool cut_by_contour = !cut_with_groove && m_part_selection.valid(); + ModelObject* cut_mo = cut_by_contour ? m_part_selection.model_object() : nullptr; if (cut_mo) cut_mo->cut_connectors = mo->cut_connectors; @@ -2530,8 +3279,6 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) wxBusyCursor wait; - const Transform3d cut_matrix = get_cut_matrix(selection); - ModelObjectCutAttributes attributes = only_if(has_connectors ? true : m_keep_upper, ModelObjectCutAttribute::KeepUpper) | only_if(has_connectors ? true : m_keep_lower, ModelObjectCutAttribute::KeepLower) | only_if(has_connectors ? false : m_keep_as_parts, ModelObjectCutAttribute::KeepAsParts) | @@ -2540,129 +3287,20 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) only_if(m_rotate_upper, ModelObjectCutAttribute::FlipUpper) | only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower) | only_if(dowels_count > 0, ModelObjectCutAttribute::CreateDowels) | - only_if(!has_connectors && cut_mo->cut_id.id().invalid(), ModelObjectCutAttribute::InvalidateCutInfo); + only_if(!has_connectors && !cut_with_groove && cut_mo->cut_id.id().invalid(), ModelObjectCutAttribute::InvalidateCutInfo); // update cut_id for the cut object in respect to the attributes update_object_cut_id(cut_mo->cut_id, attributes, dowels_count); - ModelObjectPtrs cut_object_ptrs; - if (cut_by_contour) { - // Clone the object to duplicate instances, materials etc. - ModelObject* upper{ nullptr }; - if (m_keep_upper) cut_mo->clone_for_cut(&upper); - ModelObject* lower{ nullptr }; - if (m_keep_lower) cut_mo->clone_for_cut(&lower); - - auto add_cut_objects = [this, &instance_idx, &cut_matrix](ModelObjectPtrs& cut_objects, ModelObject* upper, ModelObject* lower) { - if (upper && !upper->volumes.empty()) { - ModelObject::reset_instance_transformation(upper, instance_idx, cut_matrix, m_place_on_cut_upper, m_rotate_upper); - cut_objects.push_back(upper); - } - if (lower && !lower->volumes.empty()) { - ModelObject::reset_instance_transformation(lower, instance_idx, cut_matrix, m_place_on_cut_lower, m_place_on_cut_lower || m_rotate_lower); - cut_objects.push_back(lower); - } - }; - - const size_t cut_parts_cnt = m_part_selection.parts().size(); - bool has_modifiers = false; - - // Distribute SolidParts to the Upper/Lower object - for (size_t id = 0; id < cut_parts_cnt; ++id) { - if (m_part_selection.parts()[id].is_modifier) - has_modifiers = true; // modifiers will be added later to the related parts - else if (ModelObject* obj = (m_part_selection.parts()[id].selected ? upper : lower)) - obj->add_volume(*(cut_mo->volumes[id])); - } - - if (has_modifiers) { - // Distribute Modifiers to the Upper/Lower object - auto upper_bb = upper ? upper->instance_bounding_box(instance_idx) : BoundingBoxf3(); - auto lower_bb = lower ? lower->instance_bounding_box(instance_idx) : BoundingBoxf3(); - const Transform3d inst_matrix = cut_mo->instances[instance_idx]->get_transformation().get_matrix(); - - for (size_t id = 0; id < cut_parts_cnt; ++id) - if (m_part_selection.parts()[id].is_modifier) { - ModelVolume* vol = cut_mo->volumes[id]; - auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix()); - // Don't add modifiers which are not intersecting with solid parts - if (upper_bb.intersects(bb)) - upper->add_volume(*vol); - if (lower_bb.intersects(bb)) - lower->add_volume(*vol); - } - } - - ModelVolumePtrs& volumes = cut_mo->volumes; - if (volumes.size() == cut_parts_cnt) { - // Means that object is cut without connectors - - // Just add Upper and Lower objects to cut_object_ptrs - add_cut_objects(cut_object_ptrs, upper, lower); - } - else if (volumes.size() > cut_parts_cnt) { - // Means that object is cut with connectors - - // All volumes are distributed to Upper / Lower object, - // So we don’t need them anymore - for (size_t id = 0; id < cut_parts_cnt; id++) - delete *(volumes.begin() + id); - volumes.erase(volumes.begin(), volumes.begin() + cut_parts_cnt); - - // Perform cut just to get connectors - const ModelObjectPtrs cut_connectors_obj = cut_mo->cut(instance_idx, get_cut_matrix(selection), attributes); - assert(dowels_count > 0 ? cut_connectors_obj.size() >= 3 : cut_connectors_obj.size() == 2); - - // Connectors from upper object - for (const ModelVolume* volume : cut_connectors_obj[0]->volumes) - upper->add_volume(*volume, volume->type()); - - // Connectors from lower object - for (const ModelVolume* volume : cut_connectors_obj[1]->volumes) - lower->add_volume(*volume, volume->type()); - - // Add Upper and Lower objects to cut_object_ptrs - add_cut_objects(cut_object_ptrs, upper, lower); - - // Add Dowel-connectors as separate objects to cut_object_ptrs - if (cut_connectors_obj.size() >= 3) - for (size_t id = 2; id < cut_connectors_obj.size(); id++) - cut_object_ptrs.push_back(cut_connectors_obj[id]); - } - - // Now merge all model parts together: - { - for (ModelObject* mo : cut_object_ptrs) { - TriangleMesh mesh; - // Merge all SolidPart but not Connectors - for (const ModelVolume* mv : mo->volumes) { - if (mv->is_model_part() && !mv->is_cut_connector()) { - TriangleMesh m = mv->mesh(); - m.transform(mv->get_matrix()); - mesh.merge(m); - } - } - if (! mesh.empty()) { - ModelVolume* new_volume = mo->add_volume(mesh); - new_volume->name = mo->name; - // Delete all merged SolidPart but not Connectors - for (int i=int(mo->volumes.size())-2; i>=0; --i) { - const ModelVolume* mv = mo->volumes[i]; - if (mv->is_model_part() && !mv->is_cut_connector()) - mo->delete_volume(i); - } - } - } - } - } - else - cut_object_ptrs = cut_mo->cut(instance_idx, cut_matrix, attributes); - + Cut cut(cut_mo, instance_idx, get_cut_matrix(selection), attributes); + const ModelObjectPtrs& new_objects = cut_by_contour ? cut.perform_by_contour(m_part_selection.get_cut_parts(), dowels_count): + cut_with_groove ? cut.perform_with_groove(m_groove, m_rotation_m) : + cut.perform_with_plane(); // save cut_id to post update synchronization const CutObjectBase cut_id = cut_mo->cut_id; // update cut results on plater and in the model - plater->cut(object_idx, cut_object_ptrs); + plater->apply_cut_object_to_model(object_idx, new_objects); synchronize_model_after_cut(plater->model(), cut_id); } @@ -2670,7 +3308,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) // Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal // Return false if no intersection was found, true otherwise. -bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, Vec3d& pos, Vec3d& pos_world, bool respect_disabled_contour/* = true*/) +bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, Vec3d& pos, Vec3d& pos_world, bool respect_contours/* = true*/) { const float sla_shift = m_c->selection_info()->get_sla_shift(); @@ -2707,6 +3345,7 @@ bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, Vec3d& po } }*/ + if (respect_contours) { // Do not react to clicks outside a contour (or inside a contour that is ignored) int cont_id = m_c->object_clipper()->is_projection_inside_cut(hit); @@ -2748,13 +3387,15 @@ void GLGizmoCut3D::reset_connectors() void GLGizmoCut3D::init_connector_shapes() { - for (const CutConnectorType& type : {CutConnectorType::Dowel, CutConnectorType::Plug}) + for (const CutConnectorType& type : {CutConnectorType::Dowel, CutConnectorType::Plug, CutConnectorType::Snap}) for (const CutConnectorStyle& style : {CutConnectorStyle::Frustum, CutConnectorStyle::Prism}) { if (type == CutConnectorType::Dowel && style == CutConnectorStyle::Frustum) continue; for (const CutConnectorShape& shape : {CutConnectorShape::Circle, CutConnectorShape::Hexagon, CutConnectorShape::Square, CutConnectorShape::Triangle}) { + if (type == CutConnectorType::Snap && shape != CutConnectorShape::Circle) + continue; const CutConnectorAttributes attribs = { type, style, shape }; - const indexed_triangle_set its = ModelObject::get_connector_mesh(attribs); + indexed_triangle_set its = get_connector_mesh(attribs); m_shapes[attribs].model.init_from(its); m_shapes[attribs].mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); } @@ -2765,9 +3406,18 @@ void GLGizmoCut3D::update_connector_shape() { CutConnectorAttributes attribs = { m_connector_type, CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id) }; - const indexed_triangle_set its = ModelObject::get_connector_mesh(attribs); - m_connector_mesh.clear(); - m_connector_mesh = TriangleMesh(its); + if (m_connector_type == CutConnectorType::Snap) { + indexed_triangle_set its = get_connector_mesh(attribs); + m_shapes[attribs].reset(); + m_shapes[attribs].model.init_from(its); + m_shapes[attribs].mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); + + //const indexed_triangle_set its = get_connector_mesh(attribs); + //m_connector_mesh.clear(); + //m_connector_mesh = TriangleMesh(its); + } + + } bool GLGizmoCut3D::cut_line_processing() const @@ -2798,6 +3448,8 @@ bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse } if (cut_line_processing()) { + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) + m_groove_editing = true; reset_cut_by_contours(); m_line_end = pt; @@ -2829,6 +3481,11 @@ bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse m_angle_arc.reset(); discard_cut_line_processing(); + + if (CutMode(m_mode) == CutMode::cutTongueAndGroove) { + m_groove_editing = false; + reset_cut_by_contours(); + } } else if (action == SLAGizmoEventType::Moving) this->set_dirty(); @@ -3024,5 +3681,69 @@ void GLGizmoCut3D::data_changed(bool is_serializing) } + + +indexed_triangle_set GLGizmoCut3D::get_connector_mesh(CutConnectorAttributes connector_attributes) +{ + indexed_triangle_set connector_mesh; + + int sectorCount{ 1 }; + switch (CutConnectorShape(connector_attributes.shape)) { + case CutConnectorShape::Triangle: + sectorCount = 3; + break; + case CutConnectorShape::Square: + sectorCount = 4; + break; + case CutConnectorShape::Circle: + sectorCount = 360; + break; + case CutConnectorShape::Hexagon: + sectorCount = 6; + break; + default: + break; + } + + if (connector_attributes.type == CutConnectorType::Snap) + connector_mesh = its_make_snap(1.0, 1.0, m_snap_space_proportion, m_snap_bulge_proportion); + else if (connector_attributes.style == CutConnectorStyle::Prism) + connector_mesh = its_make_cylinder(1.0, 1.0, (2 * PI / sectorCount)); + else if (connector_attributes.type == CutConnectorType::Plug) + connector_mesh = its_make_frustum(1.0, 1.0, (2 * PI / sectorCount)); + else + connector_mesh = its_make_frustum_dowel(1.0, 1.0, sectorCount); + + return connector_mesh; +} + +void GLGizmoCut3D::apply_cut_connectors(ModelObject* mo, const std::string& connector_name) +{ + if (mo->cut_connectors.empty()) + return; + + using namespace Geometry; + + size_t connector_id = mo->cut_id.connectors_cnt(); + for (const CutConnector& connector : mo->cut_connectors) { + TriangleMesh mesh = TriangleMesh(get_connector_mesh(connector.attribs)); + // Mesh will be centered when loading. + ModelVolume* new_volume = mo->add_volume(std::move(mesh), ModelVolumeType::NEGATIVE_VOLUME); + + // Transform the new modifier to be aligned inside the instance + new_volume->set_transformation(translation_transform(connector.pos) * connector.rotation_m * + scale_transform(Vec3f(connector.radius, connector.radius, connector.height).cast())); + + new_volume->cut_info = { connector.attribs.type, connector.radius_tolerance, connector.height_tolerance }; + new_volume->name = connector_name + "-" + std::to_string(++connector_id); + } + mo->cut_id.increase_connectors_cnt(mo->cut_connectors.size()); + + // delete all connectors + mo->cut_connectors.clear(); +} + + + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 51174a51af..aa8ed04dd9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -7,12 +7,14 @@ #include "slic3r/GUI/I18N.hpp" #include "libslic3r/TriangleMesh.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/CutUtils.hpp" #include "imgui/imgui.h" namespace Slic3r { enum class CutConnectorType : int; class ModelVolume; +class GLShaderProgram; struct CutConnectorAttributes; namespace GUI { @@ -29,6 +31,9 @@ class GLGizmoCut3D : public GLGizmoBase Y, Z, CutPlane, + CutPlaneZRotation, + CutPlaneXMove, + CutPlaneYMove, Count, }; @@ -54,6 +59,7 @@ class GLGizmoCut3D : public GLGizmoBase double m_radius{ 0.0 }; double m_grabber_radius{ 0.0 }; double m_grabber_connection_len{ 0.0 }; + Vec3d m_cut_plane_start_move_pos {Vec3d::Zero()}; double m_snap_coarse_in_radius{ 0.0 }; double m_snap_coarse_out_radius{ 0.0 }; @@ -78,6 +84,7 @@ class GLGizmoCut3D : public GLGizmoBase PickingModel m_plane; PickingModel m_sphere; PickingModel m_cone; + PickingModel m_cube; std::map m_shapes; std::vector> m_raycasters; @@ -111,6 +118,16 @@ class GLGizmoCut3D : public GLGizmoBase bool m_rotate_upper{ false }; bool m_rotate_lower{ false }; + // Input params for cut with tongue and groove + Cut::Groove m_groove; + bool m_groove_editing { false }; + + bool m_is_slider_editing_done { false }; + + // Input params for cut with snaps + float m_snap_bulge_proportion{ 0.15f }; + float m_snap_space_proportion{ 0.3f }; + bool m_hide_cut_plane{ false }; bool m_connectors_editing{ false }; bool m_cut_plane_as_circle{ false }; @@ -127,7 +144,6 @@ class GLGizmoCut3D : public GLGizmoBase float m_contour_width{ 0.4f }; float m_cut_plane_radius_koef{ 1.5f }; - bool m_is_contour_changed{ false }; float m_shortcut_label_width{ -1.f }; mutable std::vector m_selected; // which pins are currently selected @@ -139,10 +155,14 @@ class GLGizmoCut3D : public GLGizmoBase bool m_was_cut_plane_dragged { false }; bool m_was_contour_selected { false }; + // Vertices of the groove used to detection if groove is valid + std::vector m_groove_vertices; + class PartSelection { public: PartSelection() = default; PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx, const Vec3d& center, const Vec3d& normal, const CommonGizmosDataObjects::ObjectClipper& oc); + PartSelection(const ModelObject* mo, int instance_idx_in); ~PartSelection() { m_model.clear_objects(); } struct Part { @@ -161,6 +181,8 @@ class GLGizmoCut3D : public GLGizmoBase const std::vector& parts() const { return m_parts; } const std::vector* get_ignored_contours_ptr() const { return (valid() ? &m_ignored_contours : nullptr); } + std::vector get_cut_parts(); + private: Model m_model; int m_instance_idx; @@ -171,6 +193,8 @@ class GLGizmoCut3D : public GLGizmoBase std::vector m_contour_points; // Debugging std::vector> m_debug_pts; // Debugging + + void add_object(const ModelObject* object); }; PartSelection m_part_selection; @@ -180,7 +204,8 @@ class GLGizmoCut3D : public GLGizmoBase enum class CutMode { cutPlanar - , cutGrig + , cutTongueAndGroove + //, cutGrig //,cutRadial //,cutModular }; @@ -190,7 +215,7 @@ class GLGizmoCut3D : public GLGizmoBase , Manual }; -// std::vector m_modes; + std::vector m_modes; size_t m_mode{ size_t(CutMode::cutPlanar) }; std::vector m_connector_modes; @@ -215,7 +240,7 @@ public: GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); std::string get_tooltip() const override; - bool unproject_on_cut_plane(const Vec2d& mouse_pos, Vec3d& pos, Vec3d& pos_world, bool respect_disabled_contour = true); + bool unproject_on_cut_plane(const Vec2d& mouse_pos, Vec3d& pos, Vec3d& pos_world, bool respect_contours = true); bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); bool is_in_editing_mode() const override { return m_connectors_editing; } @@ -249,8 +274,8 @@ protected: bool on_is_activable() const override; bool on_is_selectable() const override; Vec3d mouse_position_in_local_plane(GrabberID axis, const Linef3&mouse_ray) const; - void dragging_grabber_z(const GLGizmoBase::UpdateData &data); - void dragging_grabber_xy(const GLGizmoBase::UpdateData &data); + void dragging_grabber_move(const GLGizmoBase::UpdateData &data); + void dragging_grabber_rotation(const GLGizmoBase::UpdateData &data); void dragging_connector(const GLGizmoBase::UpdateData &data); void on_dragging(const UpdateData&data) override; void on_start_dragging() override; @@ -275,6 +300,8 @@ protected: void add_horizontal_scaled_interval(float interval); void add_horizontal_shift(float shift); void render_color_marker(float size, const ImU32& color); + void render_groove_float_input(const std::string &label, float &in_val, const float &init_val, float &in_tolerance); + void render_groove_angle_input(const std::string &label, float &in_val, const float &init_val, float min_val, float max_val); void render_cut_plane_input_window(CutConnectors &connectors); void init_input_window_data(CutConnectors &connectors); void render_input_window_warning() const; @@ -290,6 +317,8 @@ protected: void set_volumes_picking_state(bool state); void update_raycasters_for_picking_transform(); + void update_plane_model(); + void on_render_input_window(float x, float y, float bottom_limit) override; bool wants_enter_leave_snapshots() const override { return true; } @@ -301,10 +330,12 @@ protected: Transform3d get_cut_matrix(const Selection& selection); private: - void set_center(const Vec3d& center, bool update_tbb = false); - bool render_combo(const std::string& label, const std::vector& lines, int& selection_idx); + void set_center(const Vec3d¢er, bool update_tbb = false); + void switch_to_mode(size_t new_mode); + bool render_cut_mode_combo(); + bool render_combo(const std::string&label, const std::vector&lines, int&selection_idx); bool render_double_input(const std::string& label, double& value_in); - bool render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in); + bool render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in, float min_val = -0.1f, float max_tolerance = -0.1f); void render_move_center_input(int axis); void render_connect_mode_radio_button(CutConnectorMode mode); bool render_reset_button(const std::string& label_id, const std::string& tooltip) const; @@ -314,16 +345,18 @@ private: void render_connectors(); bool can_perform_cut() const; + bool has_valid_groove() const; bool has_valid_contour() const; void apply_connectors_in_model(ModelObject* mo, int &dowels_count); bool cut_line_processing() const; void discard_cut_line_processing(); + void apply_color_clip_plane_colors(); void render_cut_plane(); static void render_model(GLModel& model, const ColorRGBA& color, Transform3d view_model_matrix); void render_line(GLModel& line_model, const ColorRGBA& color, Transform3d view_model_matrix, float width); void render_rotation_snapping(GrabberID axis, const ColorRGBA& color); - void render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix); + void render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix, double line_len_koef = 1.0); void render_cut_plane_grabbers(); void render_cut_line(); void perform_cut(const Selection&selection); @@ -339,6 +372,13 @@ private: void validate_connector_settings(); bool process_cut_line(SLAGizmoEventType action, const Vec2d& mouse_position); void check_and_update_connectors_state(); + + void toggle_model_objects_visibility(); + + indexed_triangle_set its_make_groove_plane(); + + indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes); + void apply_cut_connectors(ModelObject* mo, const std::string& connector_name); }; } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 1a7251ddf7..6cc852c6a6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -99,6 +99,12 @@ void GLGizmoScale3D::enable_ununiversal_scale(bool enable) m_grabbers[i].enabled = enable; } +void GLGizmoScale3D::on_set_state() +{ + if (m_state == On) + wxGetApp().obj_list()->selection_changed(); +} + void GLGizmoScale3D::data_changed(bool is_serializing) { set_scale(Vec3d::Ones()); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index 6a7b4a331e..6e304061ab 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -74,6 +74,8 @@ protected: virtual void on_register_raycasters_for_picking() override; virtual void on_unregister_raycasters_for_picking() override; + void on_set_state() override; + private: void render_grabbers_connection(unsigned int id_1, unsigned int id_2, const ColorRGBA& color); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index d774aeb044..f2c73cf64c 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -67,6 +67,7 @@ static const std::map font_icons = { {ImGui::InfoMarkerSmall , "notification_info" }, {ImGui::PlugMarker , "plug" }, {ImGui::DowelMarker , "dowel" }, + {ImGui::SnapMarker , "snap" }, }; static const std::map font_icons_large = { @@ -639,6 +640,8 @@ bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float m_last_slider_status.edited = ImGui::IsItemEdited(); m_last_slider_status.clicked = ImGui::IsItemClicked(); m_last_slider_status.deactivated_after_edit = ImGui::IsItemDeactivatedAfterEdit(); + if (!m_last_slider_status.can_take_snapshot) + m_last_slider_status.can_take_snapshot = ImGui::IsItemClicked(); if (!tooltip.empty() && ImGui::IsItemHovered()) this->tooltip(into_u8(tooltip).c_str(), max_tooltip_width); diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 26e58d4ad1..92e761a434 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -49,6 +49,11 @@ public: bool edited { false }; bool clicked { false }; bool deactivated_after_edit { false }; + // flag to indicate possibility to take snapshot from the slider value + // It's used from Gizmos to take snapshots just from the very beginning of the editing + bool can_take_snapshot { false }; + // When Undo/Redo snapshot is taken, then call this function + void invalidate_snapshot() { can_take_snapshot = false; } }; ImGuiWrapper(); @@ -80,6 +85,7 @@ public: ImVec2 get_item_spacing() const; float get_slider_float_height() const; const LastSliderStatus& get_last_slider_status() const { return m_last_slider_status; } + LastSliderStatus& get_last_slider_status() { return m_last_slider_status; } void set_next_window_pos(float x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f); void set_next_window_bg_alpha(float alpha); diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index 60ea6c856d..88a4f29728 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -465,14 +465,17 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& -bool MeshRaycaster::is_valid_intersection(Vec3d point, Vec3d direction, const Transform3d& trafo) const +bool MeshRaycaster::intersects_line(Vec3d point, Vec3d direction, const Transform3d& trafo) const { - point = trafo.inverse() * point; + Transform3d trafo_inv = trafo.inverse(); + Vec3d to = trafo_inv * (point + direction); + point = trafo_inv * point; + direction = (to-point).normalized(); std::vector hits = m_emesh.query_ray_hits(point, direction); std::vector neg_hits = m_emesh.query_ray_hits(point, -direction); - return !hits.empty() && !neg_hits.empty(); + return !hits.empty() || !neg_hits.empty(); } diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index 3645ecc026..a7dd3d7f75 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -187,7 +187,9 @@ public: const AABBMesh &get_aabb_mesh() const { return m_emesh; } - bool is_valid_intersection(Vec3d point, Vec3d direction, const Transform3d& trafo) const; + // Given a point and direction in world coords, returns whether the respective line + // intersects the mesh if it is transformed into world by trafo. + bool intersects_line(Vec3d point, Vec3d direction, const Transform3d& trafo) const; // Given a vector of points in woorld coordinates, this returns vector // of indices of points that are visible (i.e. not cut by clipping plane diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 018e338335..50c112c76e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1280,9 +1280,11 @@ void Sidebar::show_info_sizer() { Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); ModelObjectPtrs objects = p->plater->model().objects; - int obj_idx = selection.get_object_idx(); + const int obj_idx = selection.get_object_idx(); + const int inst_idx = selection.get_instance_idx(); if (m_mode < comExpert || objects.empty() || obj_idx < 0 || int(objects.size()) <= obj_idx || + inst_idx < 0 || int(objects[obj_idx]->instances.size()) <= inst_idx || objects[obj_idx]->volumes.empty() || // hack to avoid crash when deleting the last object on the bed (selection.is_single_full_object() && objects[obj_idx]->instances.size()> 1) || !(selection.is_single_full_instance() || selection.is_single_volume())) { @@ -1292,9 +1294,6 @@ void Sidebar::show_info_sizer() const ModelObject* model_object = objects[obj_idx]; - int inst_idx = selection.get_instance_idx(); - assert(inst_idx >= 0); - bool imperial_units = wxGetApp().app_config->get_bool("use_inches"); double koef = imperial_units ? ObjectManipulation::mm_to_in : 1.0f; @@ -6417,20 +6416,7 @@ void Plater::toggle_layers_editing(bool enable) canvas3D()->force_main_toolbar_left_action(canvas3D()->get_main_toolbar_item_id("layersediting")); } -void Plater::cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes) -{ - wxCHECK_RET(obj_idx < p->model.objects.size(), "obj_idx out of bounds"); - auto* object = p->model.objects[obj_idx]; - - wxCHECK_RET(instance_idx < object->instances.size(), "instance_idx out of bounds"); - - wxBusyCursor wait; - - const auto new_objects = object->cut(instance_idx, cut_matrix, attributes); - cut(obj_idx, new_objects); -} - -void Plater::cut(size_t obj_idx, const ModelObjectPtrs& new_objects) +void Plater::apply_cut_object_to_model(size_t obj_idx, const ModelObjectPtrs& new_objects) { model().delete_object(obj_idx); sidebar().obj_list()->delete_object_from_list(obj_idx); @@ -7232,7 +7218,9 @@ void Plater::force_filament_cb_update() // Update preset comboboxes on sidebar and filaments tab p->sidebar->update_presets(Preset::TYPE_FILAMENT); - wxGetApp().get_tab(Preset::TYPE_FILAMENT)->select_preset(wxGetApp().preset_bundle->filaments.get_selected_preset_name()); + + TabFilament* tab = dynamic_cast(wxGetApp().get_tab(Preset::TYPE_FILAMENT)); + tab->select_preset(wxGetApp().preset_bundle->extruders_filaments[tab->get_active_extruder()].get_selected_preset_name()); } void Plater::force_print_bed_update() diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 342555040b..64733f98f1 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -268,8 +268,7 @@ public: void convert_unit(ConversionType conv_type); void toggle_layers_editing(bool enable); - void cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes); - void cut(size_t init_obj_idx, const ModelObjectPtrs& cut_objects); + void apply_cut_object_to_model(size_t init_obj_idx, const ModelObjectPtrs& cut_objects); void export_gcode(bool prefer_removable); void export_stl_obj(bool extended = false, bool selection_only = false); diff --git a/src/slic3r/GUI/SceneRaycaster.cpp b/src/slic3r/GUI/SceneRaycaster.cpp index 64493d86b4..08c3407632 100644 --- a/src/slic3r/GUI/SceneRaycaster.cpp +++ b/src/slic3r/GUI/SceneRaycaster.cpp @@ -40,6 +40,7 @@ std::shared_ptr SceneRaycaster::add_raycaster(EType type, in case EType::Bed: { return m_bed.emplace_back(std::make_shared(encode_id(type, id), raycaster, trafo, use_back_faces)); } case EType::Volume: { return m_volumes.emplace_back(std::make_shared(encode_id(type, id), raycaster, trafo, use_back_faces)); } case EType::Gizmo: { return m_gizmos.emplace_back(std::make_shared(encode_id(type, id), raycaster, trafo, use_back_faces)); } + case EType::FallbackGizmo: { return m_fallback_gizmos.emplace_back(std::make_shared(encode_id(type, id), raycaster, trafo, use_back_faces)); } default: { assert(false); return nullptr; } }; } @@ -62,6 +63,7 @@ void SceneRaycaster::remove_raycasters(EType type) case EType::Bed: { m_bed.clear(); break; } case EType::Volume: { m_volumes.clear(); break; } case EType::Gizmo: { m_gizmos.clear(); break; } + case EType::FallbackGizmo: { m_fallback_gizmos.clear(); break; } default: { break; } }; } @@ -86,6 +88,12 @@ void SceneRaycaster::remove_raycaster(std::shared_ptr item) return; } } + for (auto it = m_fallback_gizmos.begin(); it != m_fallback_gizmos.end(); ++it) { + if (*it == item) { + m_fallback_gizmos.erase(it); + return; + } + } } SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Camera& camera, const ClippingPlane* clipping_plane) const @@ -174,6 +182,9 @@ SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Came if (!m_gizmos.empty()) test_raycasters(EType::Gizmo, mouse_pos, camera, ret); + if (!m_fallback_gizmos.empty() && !ret.is_valid()) + test_raycasters(EType::FallbackGizmo, mouse_pos, camera, ret); + if (!m_gizmos_on_top || !ret.is_valid()) { if (camera.is_looking_downward() && !m_bed.empty()) test_raycasters(EType::Bed, mouse_pos, camera, ret); @@ -241,6 +252,14 @@ size_t SceneRaycaster::active_gizmos_count() const { } return count; } +size_t SceneRaycaster::active_fallback_gizmos_count() const { + size_t count = 0; + for (const auto& g : m_fallback_gizmos) { + if (g->is_active()) + ++count; + } + return count; +} #endif // ENABLE_RAYCAST_PICKING_DEBUG std::vector>* SceneRaycaster::get_raycasters(EType type) @@ -251,6 +270,7 @@ std::vector>* SceneRaycaster::get_raycasters case EType::Bed: { ret = &m_bed; break; } case EType::Volume: { ret = &m_volumes; break; } case EType::Gizmo: { ret = &m_gizmos; break; } + case EType::FallbackGizmo: { ret = &m_fallback_gizmos; break; } default: { break; } } assert(ret != nullptr); @@ -265,6 +285,7 @@ const std::vector>* SceneRaycaster::get_rayc case EType::Bed: { ret = &m_bed; break; } case EType::Volume: { ret = &m_volumes; break; } case EType::Gizmo: { ret = &m_gizmos; break; } + case EType::FallbackGizmo: { ret = &m_fallback_gizmos; break; } default: { break; } } assert(ret != nullptr); @@ -278,6 +299,7 @@ int SceneRaycaster::base_id(EType type) case EType::Bed: { return int(EIdBase::Bed); } case EType::Volume: { return int(EIdBase::Volume); } case EType::Gizmo: { return int(EIdBase::Gizmo); } + case EType::FallbackGizmo: { return int(EIdBase::FallbackGizmo); } default: { break; } }; diff --git a/src/slic3r/GUI/SceneRaycaster.hpp b/src/slic3r/GUI/SceneRaycaster.hpp index df44b1701c..8102e20de4 100644 --- a/src/slic3r/GUI/SceneRaycaster.hpp +++ b/src/slic3r/GUI/SceneRaycaster.hpp @@ -42,14 +42,16 @@ public: None, Bed, Volume, - Gizmo + Gizmo, + FallbackGizmo // Is used for gizmo grabbers which will be hit after all grabbers of Gizmo type }; enum class EIdBase { Bed = 0, Volume = 1000, - Gizmo = 1000000 + Gizmo = 1000000, + FallbackGizmo = 2000000 }; struct HitResult @@ -66,6 +68,7 @@ private: std::vector> m_bed; std::vector> m_volumes; std::vector> m_gizmos; + std::vector> m_fallback_gizmos; // When set to true, if checking gizmos returns a valid hit, // the search is not performed on other types @@ -99,9 +102,11 @@ public: size_t beds_count() const { return m_bed.size(); } size_t volumes_count() const { return m_volumes.size(); } size_t gizmos_count() const { return m_gizmos.size(); } + size_t fallback_gizmos_count() const { return m_fallback_gizmos.size(); } size_t active_beds_count() const; size_t active_volumes_count() const; size_t active_gizmos_count() const; + size_t active_fallback_gizmos_count() const; #endif // ENABLE_RAYCAST_PICKING_DEBUG static int decode_id(EType type, int id);