Merge remote-tracking branch 'PRIVATE/ys_cut' into master

This commit is contained in:
YuSanka 2022-10-27 10:21:23 +02:00
commit cf0f257d05
61 changed files with 19847 additions and 16272 deletions

View File

@ -43,6 +43,14 @@ set(SLIC3R_GTK "2" CACHE STRING "GTK version to use with wxWidgets on Linux")
set(IS_CROSS_COMPILE FALSE) set(IS_CROSS_COMPILE FALSE)
if (SLIC3R_STATIC)
# Prefer config scripts over find modules. This is helpful when building with
# the static dependencies. Many libraries have their own export scripts
# while having a Find<PkgName> module in standard cmake installation.
# (e.g. CURL)
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON)
endif ()
if (APPLE) if (APPLE)
set(CMAKE_FIND_FRAMEWORK LAST) set(CMAKE_FIND_FRAMEWORK LAST)
set(CMAKE_FIND_APPBUNDLE LAST) set(CMAKE_FIND_APPBUNDLE LAST)
@ -437,6 +445,14 @@ include_directories(BEFORE SYSTEM ${EIGEN3_INCLUDE_DIR})
# no matter what. # no matter what.
find_package(EXPAT REQUIRED) find_package(EXPAT REQUIRED)
add_library(libexpat INTERFACE)
if (TARGET EXPAT::EXPAT )
target_link_libraries(libexpat INTERFACE EXPAT::EXPAT)
elseif(TARGET expat::expat)
target_link_libraries(libexpat INTERFACE expat::expat)
endif ()
find_package(PNG REQUIRED) find_package(PNG REQUIRED)
set(OpenGL_GL_PREFERENCE "LEGACY") set(OpenGL_GL_PREFERENCE "LEGACY")

View File

@ -13,8 +13,8 @@ if (UNIX AND NOT APPLE) # wxWidgets will not use char as the underlying type for
endif() endif()
prusaslicer_add_cmake_project(wxWidgets prusaslicer_add_cmake_project(wxWidgets
URL https://github.com/prusa3d/wxWidgets/archive/2a0b365df947138c513a888d707d46248d78a341.zip URL https://github.com/prusa3d/wxWidgets/archive/34b524f8d5134a40a90d93a16360d533af2676ae.zip
URL_HASH SHA256=9ab05cd5179196fad4ae702c78eaae9418e73a402cfd390f7438e469b13eb735 URL_HASH SHA256=e76ca0dd998905c4dbb86f41f264e6e0468504dc2398f7e7e3bba8dc37de2f45
DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} dep_TIFF dep_JPEG dep_NanoSVG DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} dep_TIFF dep_JPEG dep_NanoSVG
CMAKE_ARGS CMAKE_ARGS
-DwxBUILD_PRECOMP=ON -DwxBUILD_PRECOMP=ON

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="expand">
<g><line fill="none" stroke="#FFFFFF" stroke-width="1" stroke-linecap="round" stroke-miterlimit="10" x1="4" y1="8" x2="8" y2="4"/></g>
<g><line fill="none" stroke="#FFFFFF" stroke-width="1" stroke-linecap="round" stroke-miterlimit="10" x1="8" y1="4" x2="12" y2="8"/></g>
<g><line fill="none" stroke="#FFFFFF" stroke-width="1" stroke-linecap="round" stroke-miterlimit="10" x1="4" y1="12" x2="8" y2="8"/></g>
<g><line fill="none" stroke="#FFFFFF" stroke-width="1" stroke-linecap="round" stroke-miterlimit="10" x1="8" y1="8" x2="12" y2="12"/></g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 919 B

28
resources/icons/cut_.svg Normal file
View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve">
<g id="cut">
<g>
<path fill="#ED6B21" d="M118.12,65.5h-10c-0.83,0-1.5-0.67-1.5-1.5s0.67-1.5,1.5-1.5h10c0.83,0,1.5,0.67,1.5,1.5
S118.95,65.5,118.12,65.5z M98.12,65.5h-10c-0.83,0-1.5-0.67-1.5-1.5s0.67-1.5,1.5-1.5h10c0.83,0,1.5,0.67,1.5,1.5
S98.95,65.5,98.12,65.5z M78.12,65.5h-10c-0.83,0-1.5-0.67-1.5-1.5s0.67-1.5,1.5-1.5h10c0.83,0,1.5,0.67,1.5,1.5
S78.95,65.5,78.12,65.5z M58.12,65.5h-10c-0.83,0-1.5-0.67-1.5-1.5s0.67-1.5,1.5-1.5h10c0.83,0,1.5,0.67,1.5,1.5
S58.95,65.5,58.12,65.5z M38.12,65.5h-10c-0.83,0-1.5-0.67-1.5-1.5s0.67-1.5,1.5-1.5h10c0.83,0,1.5,0.67,1.5,1.5
S38.95,65.5,38.12,65.5z M18.12,65.5h-10c-0.83,0-1.5-0.67-1.5-1.5s0.67-1.5,1.5-1.5h10c0.83,0,1.5,0.67,1.5,1.5
S18.95,65.5,18.12,65.5z"/>
</g>
<g>
<g>
<path fill="#808080" d="M108.79,51.6H19.21c-1.93,0-3.5-1.57-3.5-3.5V10.12c0-1.93,1.57-3.5,3.5-3.5h89.57
c1.93,0,3.5,1.57,3.5,3.5V48.1C112.29,50.03,110.71,51.6,108.79,51.6z M19.21,9.62c-0.27,0-0.5,0.23-0.5,0.5V48.1
c0,0.27,0.23,0.5,0.5,0.5h89.57c0.27,0,0.5-0.23,0.5-0.5V10.12c0-0.27-0.23-0.5-0.5-0.5H19.21z"/>
</g>
<g>
<path fill="#808080" d="M108.79,121.38H19.21c-1.93,0-3.5-1.57-3.5-3.5V79.4c0-1.93,1.57-3.5,3.5-3.5h89.57
c1.93,0,3.5,1.57,3.5,3.5v38.49C112.29,119.81,110.71,121.38,108.79,121.38z M19.21,78.9c-0.27,0-0.5,0.23-0.5,0.5v38.49
c0,0.27,0.23,0.5,0.5,0.5h89.57c0.27,0,0.5-0.23,0.5-0.5V79.4c0-0.27-0.23-0.5-0.5-0.5H19.21z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="add_x5F_part">
<g>
<path fill="#ED6B21" d="M14.62,4.37c-0.01-0.14,0.06-0.34,0.15-0.44l0.13-0.15c0.09-0.11,0.12-0.3,0.07-0.43l-0.2-0.49
c-0.05-0.13-0.21-0.24-0.35-0.25l-0.2-0.01c-0.14-0.01-0.33-0.1-0.42-0.21c-0.09-0.1-0.37-0.46-0.38-0.6l-0.01-0.2
c-0.01-0.14-0.12-0.3-0.25-0.35l-0.49-0.2C12.52,0.97,12.33,1,12.22,1.1l-0.15,0.13c-0.11,0.09-0.31,0.16-0.44,0.15
c-0.14-0.01-0.59-0.06-0.69-0.15L10.78,1.1c-0.11-0.09-0.3-0.12-0.43-0.07l-0.49,0.2C9.73,1.28,9.61,1.44,9.6,1.58l-0.01,0.2
C9.58,1.92,9.49,2.11,9.38,2.2c-0.1,0.09-0.46,0.37-0.6,0.38L8.58,2.6c-0.14,0.01-0.3,0.12-0.35,0.25l-0.2,0.49
C7.97,3.48,8,3.67,8.1,3.78l0.13,0.15c0.09,0.11,0.16,0.31,0.15,0.44C8.37,4.52,8.32,4.96,8.23,5.07L8.1,5.22
C8,5.33,7.97,5.52,8.03,5.65l0.2,0.49C8.28,6.27,8.44,6.39,8.58,6.4l0.2,0.01c0.14,0.01,0.33,0.1,0.42,0.21
c0.09,0.1,0.37,0.46,0.38,0.6l0.01,0.2c0.01,0.14,0.12,0.3,0.25,0.35l0.49,0.2C10.48,8.03,10.67,8,10.78,7.9l0.15-0.13
c0.11-0.09,0.31-0.16,0.44-0.15c0.14,0.01,0.59,0.06,0.69,0.15l0.15,0.13c0.11,0.09,0.3,0.12,0.43,0.07l0.49-0.2
c0.13-0.05,0.24-0.21,0.25-0.35l0.01-0.2c0.01-0.14,0.1-0.33,0.21-0.42s0.46-0.37,0.6-0.38l0.2-0.01c0.14-0.01,0.3-0.12,0.35-0.25
l0.2-0.49C15.03,5.52,15,5.33,14.9,5.22l-0.13-0.15C14.68,4.96,14.63,4.51,14.62,4.37z M11.5,6.6c-1.16,0-2.1-0.94-2.1-2.1
s0.94-2.1,2.1-2.1s2.1,0.94,2.1,2.1S12.66,6.6,11.5,6.6z"/>
</g>
<path fill="#808080" d="M10.98,9.78c-0.29,0-0.52,0.23-0.52,0.52v2.09v1.04c0,0.29-0.23,0.52-0.52,0.52H2.62
c-0.29,0-0.53-0.24-0.53-0.53L2.04,6.12c0-0.14,0.05-0.27,0.15-0.37c0.1-0.1,0.23-0.15,0.37-0.15l3.19,0v0
c0.29,0,0.52-0.23,0.52-0.52S6.04,4.55,5.75,4.55H3.66c-0.01,0-0.01,0-0.02,0l-1.08,0c-0.42,0-0.81,0.16-1.11,0.46
C1.16,5.31,1,5.71,1,6.13l0.04,7.31C1.05,14.3,1.75,15,2.62,15h7.31c0.86,0,1.57-0.7,1.57-1.57v-1.04V10.3
C11.5,10.01,11.27,9.78,10.98,9.78z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="expand">
<g><line fill="none" stroke="#FFFFFF" stroke-width="1" stroke-linecap="round" stroke-miterlimit="10" x1="4" y1="4" x2="8" y2="8"/></g>
<g><line fill="none" stroke="#FFFFFF" stroke-width="1" stroke-linecap="round" stroke-miterlimit="10" x1="8" y1="8" x2="12" y2="4"/></g>
<g><line fill="none" stroke="#FFFFFF" stroke-width="1" stroke-linecap="round" stroke-miterlimit="10" x1="4" y1="8" x2="8" y2="12"/></g>
<g><line fill="none" stroke="#FFFFFF" stroke-width="1" stroke-linecap="round" stroke-miterlimit="10" x1="8" y1="12" x2="12" y2="8"/></g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 915 B

View File

@ -32,6 +32,7 @@ src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp
src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp
src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp
src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp
src/slic3r/GUI/Gizmos/GLGizmosManager.cpp src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp
src/slic3r/GUI/GUI.cpp src/slic3r/GUI/GUI.cpp

View File

@ -426,7 +426,9 @@ int CLI::run(int argc, char **argv)
o->cut(Z, m_config.opt_float("cut"), &out); o->cut(Z, m_config.opt_float("cut"), &out);
} }
#else #else
model.objects.front()->cut(0, m_config.opt_float("cut"), ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::FlipLower); // model.objects.front()->cut(0, m_config.opt_float("cut"), ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::FlipLower);
model.objects.front()->cut(0, Geometry::assemble_transform(m_config.opt_float("cut")* Vec3d::UnitZ()),
ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::PlaceOnCutUpper);
#endif #endif
model.delete_object(size_t(0)); model.delete_object(size_t(0));
} }

View File

@ -140,6 +140,7 @@ namespace ImGui
const wchar_t CancelButton = 0x14; const wchar_t CancelButton = 0x14;
const wchar_t CancelHoverButton = 0x15; const wchar_t CancelHoverButton = 0x15;
// const wchar_t VarLayerHeightMarker = 0x16; // const wchar_t VarLayerHeightMarker = 0x16;
const wchar_t RevertButton = 0x16;
const wchar_t RightArrowButton = 0x18; const wchar_t RightArrowButton = 0x18;
const wchar_t RightArrowHoverButton = 0x19; const wchar_t RightArrowHoverButton = 0x19;
@ -168,6 +169,10 @@ namespace ImGui
const wchar_t LegendCOG = 0x2615; const wchar_t LegendCOG = 0x2615;
const wchar_t LegendShells = 0x2616; const wchar_t LegendShells = 0x2616;
const wchar_t LegendToolMarker = 0x2617; const wchar_t LegendToolMarker = 0x2617;
const wchar_t WarningMarkerSmall = 0x2618;
const wchar_t ExpandBtn = 0x2619;
const wchar_t CollapseBtn = 0x2620;
const wchar_t InfoMarkerSmall = 0x2621;
// void MyFunction(const char* name, const MyMatrix44& v); // void MyFunction(const char* name, const MyMatrix44& v);
} }

View File

@ -19,6 +19,13 @@ void ExPolygon::scale(double factor)
hole.scale(factor); hole.scale(factor);
} }
void ExPolygon::scale(double factor_x, double factor_y)
{
contour.scale(factor_x, factor_y);
for (Polygon &hole : holes)
hole.scale(factor_x, factor_y);
}
void ExPolygon::translate(const Point &p) void ExPolygon::translate(const Point &p)
{ {
contour.translate(p); contour.translate(p);

View File

@ -37,6 +37,7 @@ public:
void clear() { contour.points.clear(); holes.clear(); } void clear() { contour.points.clear(); holes.clear(); }
void scale(double factor); void scale(double factor);
void scale(double factor_x, double factor_y);
void translate(double x, double y) { this->translate(Point(coord_t(x), coord_t(y))); } void translate(double x, double y) { this->translate(Point(coord_t(x), coord_t(y))); }
void translate(const Point &vector); void translate(const Point &vector);
void rotate(double angle); void rotate(double angle);

View File

@ -77,6 +77,7 @@ const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/Prusa_Slicer_layer_config
const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt"; const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt";
const std::string SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt"; const std::string SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt";
const std::string CUSTOM_GCODE_PER_PRINT_Z_FILE = "Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml"; const std::string CUSTOM_GCODE_PER_PRINT_Z_FILE = "Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml";
const std::string CUT_INFORMATION_FILE = "Metadata/Prusa_Slicer_cut_information.xml";
static constexpr const char* MODEL_TAG = "model"; static constexpr const char* MODEL_TAG = "model";
static constexpr const char* RESOURCES_TAG = "resources"; static constexpr const char* RESOURCES_TAG = "resources";
@ -408,6 +409,19 @@ namespace Slic3r {
VolumeMetadataList volumes; VolumeMetadataList volumes;
}; };
struct CutObjectInfo
{
struct Connector
{
int volume_id;
int type;
float r_tolerance;
float h_tolerance;
};
CutObjectBase id;
std::vector<Connector> connectors;
};
// Map from a 1 based 3MF object ID to a 0 based ModelObject index inside m_model->objects. // Map from a 1 based 3MF object ID to a 0 based ModelObject index inside m_model->objects.
typedef std::map<int, int> IdToModelObjectMap; typedef std::map<int, int> IdToModelObjectMap;
typedef std::map<int, ComponentsList> IdToAliasesMap; typedef std::map<int, ComponentsList> IdToAliasesMap;
@ -416,6 +430,7 @@ namespace Slic3r {
typedef std::map<int, Geometry> IdToGeometryMap; typedef std::map<int, Geometry> IdToGeometryMap;
typedef std::map<int, std::vector<coordf_t>> IdToLayerHeightsProfileMap; typedef std::map<int, std::vector<coordf_t>> IdToLayerHeightsProfileMap;
typedef std::map<int, t_layer_config_ranges> IdToLayerConfigRangesMap; typedef std::map<int, t_layer_config_ranges> IdToLayerConfigRangesMap;
typedef std::map<int, CutObjectInfo> IdToCutObjectInfoMap;
typedef std::map<int, std::vector<sla::SupportPoint>> IdToSlaSupportPointsMap; typedef std::map<int, std::vector<sla::SupportPoint>> IdToSlaSupportPointsMap;
typedef std::map<int, std::vector<sla::DrainHole>> IdToSlaDrainHolesMap; typedef std::map<int, std::vector<sla::DrainHole>> IdToSlaDrainHolesMap;
@ -443,6 +458,7 @@ namespace Slic3r {
IdToGeometryMap m_geometries; IdToGeometryMap m_geometries;
CurrentConfig m_curr_config; CurrentConfig m_curr_config;
IdToMetadataMap m_objects_metadata; IdToMetadataMap m_objects_metadata;
IdToCutObjectInfoMap m_cut_object_infos;
IdToLayerHeightsProfileMap m_layer_heights_profiles; IdToLayerHeightsProfileMap m_layer_heights_profiles;
IdToLayerConfigRangesMap m_layer_config_ranges; IdToLayerConfigRangesMap m_layer_config_ranges;
IdToSlaSupportPointsMap m_sla_support_points; IdToSlaSupportPointsMap m_sla_support_points;
@ -474,6 +490,7 @@ namespace Slic3r {
bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions); bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions);
bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_cut_information_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions);
void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions); void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions);
void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
@ -676,6 +693,10 @@ namespace Slic3r {
// extract slic3r layer heights profile file // extract slic3r layer heights profile file
_extract_layer_heights_profile_config_from_archive(archive, stat); _extract_layer_heights_profile_config_from_archive(archive, stat);
} }
else if (boost::algorithm::iequals(name, CUT_INFORMATION_FILE)) {
// extract slic3r layer config ranges file
_extract_cut_information_from_archive(archive, stat, config_substitutions);
}
else if (boost::algorithm::iequals(name, LAYER_CONFIG_RANGES_FILE)) { else if (boost::algorithm::iequals(name, LAYER_CONFIG_RANGES_FILE)) {
// extract slic3r layer config ranges file // extract slic3r layer config ranges file
_extract_layer_config_ranges_from_archive(archive, stat, config_substitutions); _extract_layer_config_ranges_from_archive(archive, stat, config_substitutions);
@ -818,6 +839,19 @@ namespace Slic3r {
if (!_generate_volumes(*model_object, obj_geometry->second, *volumes_ptr, config_substitutions)) if (!_generate_volumes(*model_object, obj_geometry->second, *volumes_ptr, config_substitutions))
return false; return false;
// Apply cut information for object if any was loaded
// m_cut_object_ids are indexed by a 1 based model object index.
IdToCutObjectInfoMap::iterator cut_object_info = m_cut_object_infos.find(object.second + 1);
if (cut_object_info != m_cut_object_infos.end()) {
model_object->cut_id = cut_object_info->second.id;
for (auto connector : cut_object_info->second.connectors) {
assert(0 <= connector.volume_id && connector.volume_id <= int(model_object->volumes.size()));
model_object->volumes[connector.volume_id]->cut_info =
ModelVolume::CutInfo(CutConnectorType(connector.type), connector.r_tolerance, connector.h_tolerance, true);
}
}
} }
// If instances contain a single volume, the volume offset should be 0,0,0 // If instances contain a single volume, the volume offset should be 0,0,0
@ -944,6 +978,65 @@ namespace Slic3r {
return true; return true;
} }
void _3MF_Importer::_extract_cut_information_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions)
{
if (stat.m_uncomp_size > 0) {
std::string buffer((size_t)stat.m_uncomp_size, 0);
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
if (res == 0) {
add_error("Error while reading cut information data to buffer");
return;
}
std::istringstream iss(buffer); // wrap returned xml to istringstream
pt::ptree objects_tree;
pt::read_xml(iss, objects_tree);
for (const auto& object : objects_tree.get_child("objects")) {
pt::ptree object_tree = object.second;
int obj_idx = object_tree.get<int>("<xmlattr>.id", -1);
if (obj_idx <= 0) {
add_error("Found invalid object id");
continue;
}
IdToCutObjectInfoMap::iterator object_item = m_cut_object_infos.find(obj_idx);
if (object_item != m_cut_object_infos.end()) {
add_error("Found duplicated cut_object_id");
continue;
}
CutObjectBase cut_id;
std::vector<CutObjectInfo::Connector> connectors;
for (const auto& obj_cut_info : object_tree) {
if (obj_cut_info.first == "cut_id") {
pt::ptree cut_id_tree = obj_cut_info.second;
cut_id = CutObjectBase(ObjectID( cut_id_tree.get<size_t>("<xmlattr>.id")),
cut_id_tree.get<size_t>("<xmlattr>.check_sum"),
cut_id_tree.get<size_t>("<xmlattr>.connectors_cnt"));
}
if (obj_cut_info.first == "connectors") {
pt::ptree cut_connectors_tree = obj_cut_info.second;
for (const auto& cut_connector : cut_connectors_tree) {
if (cut_connector.first != "connector")
continue;
pt::ptree connector_tree = cut_connector.second;
CutObjectInfo::Connector connector = {connector_tree.get<int>("<xmlattr>.volume_id"),
connector_tree.get<int>("<xmlattr>.type"),
connector_tree.get<float>("<xmlattr>.r_tolerance"),
connector_tree.get<float>("<xmlattr>.h_tolerance")};
connectors.emplace_back(connector);
}
}
}
CutObjectInfo cut_info {cut_id, connectors};
m_cut_object_infos.insert({ obj_idx, cut_info });
}
}
}
void _3MF_Importer::_extract_print_config_from_archive( void _3MF_Importer::_extract_print_config_from_archive(
mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, mz_zip_archive& archive, const mz_zip_archive_file_stat& stat,
DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions,
@ -2219,6 +2312,7 @@ namespace Slic3r {
bool _add_object_to_model_stream(mz_zip_writer_staged_context &context, unsigned int& object_id, ModelObject& object, BuildItemsList& build_items, VolumeToOffsetsMap& volumes_offsets); bool _add_object_to_model_stream(mz_zip_writer_staged_context &context, unsigned int& object_id, ModelObject& object, BuildItemsList& build_items, VolumeToOffsetsMap& volumes_offsets);
bool _add_mesh_to_object_stream(mz_zip_writer_staged_context &context, ModelObject& object, VolumeToOffsetsMap& volumes_offsets); bool _add_mesh_to_object_stream(mz_zip_writer_staged_context &context, ModelObject& object, VolumeToOffsetsMap& volumes_offsets);
bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items); bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items);
bool _add_cut_information_file_to_archive(mz_zip_archive& archive, Model& model);
bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model);
bool _add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model);
bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model);
@ -2281,6 +2375,15 @@ namespace Slic3r {
return false; return false;
} }
// Adds file with information for object cut ("Metadata/Slic3r_PE_cut_information.txt").
// All information for object cut of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model.
// The index differes from the index of an object ID of an object instance of a 3MF file!
if (!_add_cut_information_file_to_archive(archive, model)) {
close_zip_writer(&archive);
boost::filesystem::remove(filename);
return false;
}
// Adds layer height profile file ("Metadata/Slic3r_PE_layer_heights_profile.txt"). // Adds layer height profile file ("Metadata/Slic3r_PE_layer_heights_profile.txt").
// All layer height profiles of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model. // All layer height profiles of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model.
// The index differes from the index of an object ID of an object instance of a 3MF file! // The index differes from the index of an object ID of an object instance of a 3MF file!
@ -2781,6 +2884,67 @@ namespace Slic3r {
return true; return true;
} }
bool _3MF_Exporter::_add_cut_information_file_to_archive(mz_zip_archive& archive, Model& model)
{
std::string out = "";
pt::ptree tree;
unsigned int object_cnt = 0;
for (const ModelObject* object : model.objects) {
object_cnt++;
pt::ptree& obj_tree = tree.add("objects.object", "");
obj_tree.put("<xmlattr>.id", object_cnt);
// Store info for cut_id
pt::ptree& cut_id_tree = obj_tree.add("cut_id", "");
// store cut_id atributes
cut_id_tree.put("<xmlattr>.id", object->cut_id.id().id);
cut_id_tree.put("<xmlattr>.check_sum", object->cut_id.check_sum());
cut_id_tree.put("<xmlattr>.connectors_cnt", object->cut_id.connectors_cnt());
int volume_idx = -1;
for (const ModelVolume* volume : object->volumes) {
++volume_idx;
if (volume->is_cut_connector()) {
pt::ptree& connectors_tree = obj_tree.add("connectors.connector", "");
connectors_tree.put("<xmlattr>.volume_id", volume_idx);
connectors_tree.put("<xmlattr>.type", int(volume->cut_info.connector_type));
connectors_tree.put("<xmlattr>.r_tolerance", volume->cut_info.radius_tolerance);
connectors_tree.put("<xmlattr>.h_tolerance", volume->cut_info.height_tolerance);
}
}
}
if (!tree.empty()) {
std::ostringstream oss;
pt::write_xml(oss, tree);
out = oss.str();
// Post processing("beautification") of the output string for a better preview
boost::replace_all(out, "><object", ">\n <object");
boost::replace_all(out, "><cut_id", ">\n <cut_id");
boost::replace_all(out, "></cut_id>", ">\n </cut_id>");
boost::replace_all(out, "><connectors", ">\n <connectors");
boost::replace_all(out, "></connectors>", ">\n </connectors>");
boost::replace_all(out, "><connector", ">\n <connector");
boost::replace_all(out, "></connector>", ">\n </connector>");
boost::replace_all(out, "></object>", ">\n </object>");
// OR just
boost::replace_all(out, "><", ">\n<");
}
if (!out.empty()) {
if (!mz_zip_writer_add_mem(&archive, CUT_INFORMATION_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) {
add_error("Unable to add cut information file to archive");
return false;
}
}
return true;
}
bool _3MF_Exporter::_add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model) bool _3MF_Exporter::_add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model)
{ {
assert(is_decimal_separator_point()); assert(is_decimal_separator_point());

View File

@ -464,12 +464,25 @@ static constexpr const double volume_threshold_inches = 9.0; // 9 = 3*3*3;
bool Model::looks_like_imperial_units() const bool Model::looks_like_imperial_units() const
{ {
if (this->objects.size() == 0) if (this->objects.empty())
return false; return false;
for (ModelObject* obj : this->objects) for (ModelObject* obj : this->objects)
if (obj->get_object_stl_stats().volume < volume_threshold_inches) if (obj->get_object_stl_stats().volume < volume_threshold_inches) {
return true; if (!obj->is_cut())
return true;
bool all_cut_parts_look_like_imperial_units = true;
for (ModelObject* obj_other : this->objects) {
if (obj_other == obj)
continue;
if (obj_other->cut_id.is_equal(obj->cut_id) && obj_other->get_object_stl_stats().volume >= volume_threshold_inches) {
all_cut_parts_look_like_imperial_units = false;
break;
}
}
if (all_cut_parts_look_like_imperial_units)
return true;
}
return false; return false;
} }
@ -613,6 +626,7 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs)
this->layer_height_profile = rhs.layer_height_profile; this->layer_height_profile = rhs.layer_height_profile;
this->printable = rhs.printable; this->printable = rhs.printable;
this->origin_translation = rhs.origin_translation; this->origin_translation = rhs.origin_translation;
this->cut_id.copy(rhs.cut_id);
m_bounding_box = rhs.m_bounding_box; m_bounding_box = rhs.m_bounding_box;
m_bounding_box_valid = rhs.m_bounding_box_valid; m_bounding_box_valid = rhs.m_bounding_box_valid;
m_raw_bounding_box = rhs.m_raw_bounding_box; m_raw_bounding_box = rhs.m_raw_bounding_box;
@ -715,6 +729,7 @@ ModelVolume* ModelObject::add_volume(const ModelVolume &other, ModelVolumeType t
ModelVolume* v = new ModelVolume(this, other); ModelVolume* v = new ModelVolume(this, other);
if (type != ModelVolumeType::INVALID && v->type() != type) if (type != ModelVolumeType::INVALID && v->type() != type)
v->set_type(type); v->set_type(type);
v->cut_info = other.cut_info;
this->volumes.push_back(v); this->volumes.push_back(v);
// The volume should already be centered at this point of time when copying shared pointers of the triangle mesh and convex hull. // The volume should already be centered at this point of time when copying shared pointers of the triangle mesh and convex hull.
// v->center_geometry_after_creation(); // v->center_geometry_after_creation();
@ -1189,34 +1204,355 @@ size_t ModelObject::parts_count() const
return num; return num;
} }
ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes) bool ModelObject::has_connectors() const
{ {
if (! attributes.has(ModelObjectCutAttribute::KeepUpper) && ! attributes.has(ModelObjectCutAttribute::KeepLower)) assert(is_cut());
for (const ModelVolume* v : this->volumes)
if (v->cut_info.is_connector)
return true;
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::Prizm)
connector_mesh = its_make_cylinder(1.0, 1.0, (2 * PI / sectorCount));
else if (connector_attributes.type == CutConnectorType::Plug)
connector_mesh = its_make_cone(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(assemble_transform(connector.pos) * connector.rotation_m *
scale_transform(Vec3f(connector.radius, connector.radius, connector.height).cast<double>()));
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();
for (ModelVolume* volume : this->volumes)
volume->invalidate_cut_info();
}
void ModelObject::synchronize_model_after_cut()
{
for (ModelObject* obj : m_model->objects) {
if (obj == this || obj->cut_id.is_equal(this->cut_id))
continue;
if (obj->is_cut() && obj->cut_id.has_same_id(this->cut_id))
obj->cut_id.copy(this->cut_id);
}
}
void ModelObject::apply_cut_attributes(ModelObjectCutAttributes attributes)
{
// we don't save cut information, if result will not contains all parts of initial object
if (!attributes.has(ModelObjectCutAttribute::KeepUpper) || !attributes.has(ModelObjectCutAttribute::KeepLower))
return;
if (cut_id.id().invalid())
cut_id.init();
{
int cut_obj_cnt = -1;
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) cut_obj_cnt++;
if (attributes.has(ModelObjectCutAttribute::KeepLower)) cut_obj_cnt++;
if (attributes.has(ModelObjectCutAttribute::CreateDowels)) cut_obj_cnt++;
if (cut_obj_cnt > 0)
cut_id.increase_check_sum(size_t(cut_obj_cnt));
}
}
void ModelObject::clone_for_cut(ModelObject** obj)
{
(*obj) = ModelObject::new_clone(*this);
(*obj)->set_model(nullptr);
(*obj)->sla_support_points.clear();
(*obj)->sla_drain_holes.clear();
(*obj)->sla_points_status = sla::PointsStatus::NoPoints;
(*obj)->clear_volumes();
(*obj)->input_file.clear();
}
void ModelVolume::reset_extra_facets()
{
this->supported_facets.reset();
this->seam_facets.reset();
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();
/*
// correct Z offset in respect to the new size
Vec3d pos = vol->get_offset();
pos[Z] += sf[Z] * 0.5 * vol->cut_info.height_tolerance;
vol->set_offset(pos);
*/
// make a "hole" wider
sf[X] *= 1. + double(cut_info.radius_tolerance);
sf[Y] *= 1. + double(cut_info.radius_tolerance);
// make a "hole" dipper
sf[Z] *= 1. + double(cut_info.height_tolerance);
set_scaling_factor(sf);
}
void ModelObject::process_connector_cut(ModelVolume* volume, ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
std::vector<ModelObject*>& dowels, Vec3d& local_dowels_displace)
{
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 (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);
if (volume->cut_info.connector_type == CutConnectorType::Dowel)
vol->apply_tolerance();
else
// 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);
}
if (volume->cut_info.connector_type == CutConnectorType::Dowel &&
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);
// Compute the displacement (in instance coordinates) to be applied to place the dowels
local_dowels_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(1.0, 1.0, 0.0));
dowels.push_back(dowel);
}
}
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));
// 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 add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelVolume* src_volume, const Transform3d& cut_matrix)
{
if (mesh.empty())
return;
mesh.transform(cut_matrix);
ModelVolume* vol = object->add_volume(mesh);
vol->name = src_volume->name;
// 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_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, Vec3d& local_displace)
{
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() * assemble_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);
volume->reset_mesh();
// Reset volume transformation except for offset
const Vec3d offset = volume->get_offset();
volume->set_transformation(Geometry::Transformation());
volume->set_offset(offset);
// Perform cut
TriangleMesh upper_mesh, lower_mesh;
{
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);
}
// Add required cut parts to the objects
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);
// Compute the displacement (in instance coordinates) to be applied to place the upper parts
// The upper part displacement is set to half of the lower part bounding box
// this is done in hope at least a part of the upper part will always be visible and draggable
local_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-0.5, -0.5, 0.0));
}
}
static void invalidate_translations(ModelObject* object, const ModelInstance* src_instance)
{
if (!object->origin_translation.isApprox(Vec3d::Zero()) && src_instance->get_offset().isApprox(Vec3d::Zero())) {
object->center_around_origin();
object->translate_instances(-object->origin_translation);
object->origin_translation = Vec3d::Zero();
}
else {
object->invalidate_bounding_box();
object->center_around_origin();
}
}
static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix,
bool place_on_cut = false, bool flip = false, Vec3d local_displace = Vec3d::Zero())
{
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 Vec3d offset = obj_instance->get_offset();
const double rot_z = obj_instance->get_rotation().z();
obj_instance->set_transformation(Transformation());
const Vec3d displace = local_displace.isApprox(Vec3d::Zero()) ? Vec3d::Zero() :
assemble_transform(Vec3d::Zero(), obj_instance->get_rotation()) * local_displace;
obj_instance->set_offset(offset + displace);
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 {}; return {};
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start"; BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start";
// apply cut attributes for object
apply_cut_attributes(attributes);
// Clone the object to duplicate instances, materials etc. // Clone the object to duplicate instances, materials etc.
ModelObject* upper = attributes.has(ModelObjectCutAttribute::KeepUpper) ? ModelObject::new_clone(*this) : nullptr; ModelObject* upper{ nullptr };
ModelObject* lower = attributes.has(ModelObjectCutAttribute::KeepLower) ? ModelObject::new_clone(*this) : nullptr; if (attributes.has(ModelObjectCutAttribute::KeepUpper))
clone_for_cut(&upper);
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { ModelObject* lower{ nullptr };
upper->set_model(nullptr); if (attributes.has(ModelObjectCutAttribute::KeepLower))
upper->sla_support_points.clear(); clone_for_cut(&lower);
upper->sla_drain_holes.clear();
upper->sla_points_status = sla::PointsStatus::NoPoints;
upper->clear_volumes();
upper->input_file.clear();
}
if (attributes.has(ModelObjectCutAttribute::KeepLower)) { std::vector<ModelObject*> dowels;
lower->set_model(nullptr);
lower->sla_support_points.clear(); using namespace Geometry;
lower->sla_drain_holes.clear();
lower->sla_points_status = sla::PointsStatus::NoPoints;
lower->clear_volumes();
lower->input_file.clear();
}
// Because transformations are going to be applied to meshes directly, // Because transformations are going to be applied to meshes directly,
// we reset transformation of all instances and volumes, // we reset transformation of all instances and volumes,
@ -1224,128 +1560,72 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttr
// in the transformation matrix and not applied to the mesh transform. // 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_matrix(true);
const auto instance_matrix = Geometry::assemble_transform( const auto instance_matrix = assemble_transform(
Vec3d::Zero(), // don't apply offset Vec3d::Zero(), // don't apply offset
instances[instance]->get_rotation().cwiseProduct(Vec3d(1.0, 1.0, 0.0)), // don't apply Z-rotation instances[instance]->get_rotation(),
instances[instance]->get_scaling_factor(), instances[instance]->get_scaling_factor(),
instances[instance]->get_mirror() instances[instance]->get_mirror()
); );
z -= instances[instance]->get_offset().z(); const Transformation cut_transformation = Transformation(cut_matrix);
const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * assemble_transform(-1. * cut_transformation.get_offset());
// Displacement (in instance coordinates) to be applied to place the upper parts // Displacement (in instance coordinates) to be applied to place the upper parts
Vec3d local_displace = Vec3d::Zero(); Vec3d local_displace = Vec3d::Zero();
Vec3d local_dowels_displace = Vec3d::Zero();
for (ModelVolume *volume : volumes) { for (ModelVolume* volume : volumes) {
const auto volume_matrix = volume->get_matrix(); volume->reset_extra_facets();
volume->supported_facets.reset(); if (!volume->is_model_part()) {
volume->seam_facets.reset(); if (volume->cut_info.is_processed)
volume->mmu_segmentation_facets.reset(); process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, attributes, upper, lower);
else
if (! volume->is_model_part()) { process_connector_cut(volume, attributes, upper, lower, dowels, local_dowels_displace);
// 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(instance_matrix * volume_matrix));
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
upper->add_volume(*volume);
if (attributes.has(ModelObjectCutAttribute::KeepLower))
lower->add_volume(*volume);
}
else if (! volume->mesh().empty()) {
// 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(instance_matrix * volume_matrix, true);
volume->reset_mesh();
// Reset volume transformation except for offset
const Vec3d offset = volume->get_offset();
volume->set_transformation(Geometry::Transformation());
volume->set_offset(offset);
// Perform cut
TriangleMesh upper_mesh, lower_mesh;
{
indexed_triangle_set upper_its, lower_its;
cut_mesh(mesh.its, float(z), &upper_its, &lower_its);
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
upper_mesh = TriangleMesh(upper_its);
if (attributes.has(ModelObjectCutAttribute::KeepLower))
lower_mesh = TriangleMesh(lower_its);
}
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && ! upper_mesh.empty()) {
ModelVolume* vol = upper->add_volume(upper_mesh);
vol->name = volume->name;
// Don't copy the config's ID.
vol->config.assign_config(volume->config);
assert(vol->config.id().valid());
assert(vol->config.id() != volume->config.id());
vol->set_material(volume->material_id(), *volume->material());
}
if (attributes.has(ModelObjectCutAttribute::KeepLower) && ! lower_mesh.empty()) {
ModelVolume* vol = lower->add_volume(lower_mesh);
vol->name = volume->name;
// Don't copy the config's ID.
vol->config.assign_config(volume->config);
assert(vol->config.id().valid());
assert(vol->config.id() != volume->config.id());
vol->set_material(volume->material_id(), *volume->material());
// Compute the displacement (in instance coordinates) to be applied to place the upper parts
// The upper part displacement is set to half of the lower part bounding box
// this is done in hope at least a part of the upper part will always be visible and draggable
local_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-0.5, -0.5, 0.0));
}
} }
else if (!volume->mesh().empty())
process_solid_part_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, local_displace);
} }
// Post-process cut parts
ModelObjectPtrs res; ModelObjectPtrs res;
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && upper->volumes.size() > 0) { if (attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper->volumes.empty()) {
if (!upper->origin_translation.isApprox(Vec3d::Zero()) && instances[instance]->get_offset().isApprox(Vec3d::Zero())) { invalidate_translations(upper, instances[instance]);
upper->center_around_origin();
upper->translate_instances(-upper->origin_translation);
upper->origin_translation = Vec3d::Zero();
}
// Reset instance transformation except offset and Z-rotation
for (size_t i = 0; i < instances.size(); ++i) {
auto &instance = upper->instances[i];
const Vec3d offset = instance->get_offset();
const double rot_z = instance->get_rotation().z();
const Vec3d displace = Geometry::assemble_transform(Vec3d::Zero(), instance->get_rotation()) * local_displace;
instance->set_transformation(Geometry::Transformation());
instance->set_offset(offset + displace);
instance->set_rotation(Vec3d(0.0, 0.0, rot_z));
}
reset_instance_transformation(upper, instance, cut_matrix,
attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper),
attributes.has(ModelObjectCutAttribute::FlipUpper),
local_displace);
res.push_back(upper); res.push_back(upper);
} }
if (attributes.has(ModelObjectCutAttribute::KeepLower) && lower->volumes.size() > 0) {
if (!lower->origin_translation.isApprox(Vec3d::Zero()) && instances[instance]->get_offset().isApprox(Vec3d::Zero())) {
lower->center_around_origin();
lower->translate_instances(-lower->origin_translation);
lower->origin_translation = Vec3d::Zero();
}
// Reset instance transformation except offset and Z-rotation if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower->volumes.empty()) {
for (auto *instance : lower->instances) { invalidate_translations(lower, instances[instance]);
const Vec3d offset = instance->get_offset();
const double rot_z = instance->get_rotation().z();
instance->set_transformation(Geometry::Transformation());
instance->set_offset(offset);
instance->set_rotation(Vec3d(attributes.has(ModelObjectCutAttribute::FlipLower) ? Geometry::deg2rad(180.0) : 0.0, 0.0, rot_z));
}
reset_instance_transformation(lower, instance, cut_matrix,
attributes.has(ModelObjectCutAttribute::PlaceOnCutLower),
attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) ? true : attributes.has(ModelObjectCutAttribute::FlipLower));
res.push_back(lower); res.push_back(lower);
} }
if (attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) {
for (auto dowel : dowels) {
invalidate_translations(dowel, instances[instance]);
reset_instance_transformation(dowel, instance, Transform3d::Identity(), false, false, local_dowels_displace);
local_dowels_displace += dowel->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-1.5, -1.5, 0.0));
dowel->name += "-Dowel-" + dowel->volumes[0]->name;
res.push_back(dowel);
}
}
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end"; BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end";
synchronize_model_after_cut();
return res; return res;
} }
@ -2289,6 +2569,14 @@ bool model_has_multi_part_objects(const Model &model)
return false; return false;
} }
bool model_has_connectors(const Model &model)
{
for (const ModelObject *model_object : model.objects)
if (!model_object->cut_connectors.empty())
return true;
return false;
}
bool model_has_advanced_features(const Model &model) bool model_has_advanced_features(const Model &model)
{ {
auto config_is_advanced = [](const ModelConfig &config) { auto config_is_advanced = [](const ModelConfig &config) {

View File

@ -219,6 +219,91 @@ private:
friend class ModelObject; friend class ModelObject;
}; };
enum class CutConnectorType : int {
Plug
, Dowel
, Undef
};
enum class CutConnectorStyle : int {
Prizm
, Frustum
, Undef
//,Claw
};
enum class CutConnectorShape : int {
Triangle
, Square
, Hexagon
, Circle
, Undef
//,D-shape
};
struct CutConnectorAttributes
{
CutConnectorType type{ CutConnectorType::Plug };
CutConnectorStyle style{ CutConnectorStyle::Prizm };
CutConnectorShape shape{ CutConnectorShape::Circle };
CutConnectorAttributes() {}
CutConnectorAttributes(CutConnectorType t, CutConnectorStyle st, CutConnectorShape sh)
: type(t), style(st), shape(sh)
{}
CutConnectorAttributes(const CutConnectorAttributes& rhs) :
CutConnectorAttributes(rhs.type, rhs.style, rhs.shape) {}
bool operator==(const CutConnectorAttributes& other) const;
bool operator!=(const CutConnectorAttributes& other) const { return !(other == (*this)); }
bool operator<(const CutConnectorAttributes& other) const {
return this->type < other.type ||
(this->type == other.type && this->style < other.style) ||
(this->type == other.type && this->style == other.style && this->shape < other.shape);
}
template<class Archive> inline void serialize(Archive& ar) {
ar(type, style, shape);
}
};
struct CutConnector
{
Vec3d pos;
Transform3d rotation_m;
float radius;
float height;
float radius_tolerance;// [0.f : 1.f]
float height_tolerance;// [0.f : 1.f]
CutConnectorAttributes attribs;
CutConnector()
: pos(Vec3d::Zero()), rotation_m(Transform3d::Identity()), radius(5.f), height(10.f), radius_tolerance(0.f), height_tolerance(0.1f)
{}
CutConnector(Vec3d p, Transform3d rot, float r, float h, float rt, float ht, CutConnectorAttributes attributes)
: pos(p), rotation_m(rot), radius(r), height(h), radius_tolerance(rt), height_tolerance(ht), attribs(attributes)
{}
CutConnector(const CutConnector& rhs) :
CutConnector(rhs.pos, rhs.rotation_m, rhs.radius, rhs.height, rhs.radius_tolerance, rhs.height_tolerance, rhs.attribs) {}
bool operator==(const CutConnector& other) const;
bool operator!=(const CutConnector& other) const { return !(other == (*this)); }
template<class Archive> inline void serialize(Archive& ar) {
ar(pos, rotation_m, radius, height, radius_tolerance, height_tolerance, attribs);
}
};
using CutConnectors = std::vector<CutConnector>;
// Declared outside of ModelVolume, so it could be forward declared. // Declared outside of ModelVolume, so it could be forward declared.
enum class ModelVolumeType : int { enum class ModelVolumeType : int {
INVALID = -1, INVALID = -1,
@ -229,7 +314,7 @@ enum class ModelVolumeType : int {
SUPPORT_ENFORCER, SUPPORT_ENFORCER,
}; };
enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, FlipLower }; enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, FlipUpper, FlipLower, PlaceOnCutUpper, PlaceOnCutLower, CreateDowels };
using ModelObjectCutAttributes = enum_bitmask<ModelObjectCutAttribute>; using ModelObjectCutAttributes = enum_bitmask<ModelObjectCutAttribute>;
ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute); ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute);
@ -269,6 +354,10 @@ public:
// Holes to be drilled into the object so resin can flow out // Holes to be drilled into the object so resin can flow out
sla::DrainHoles sla_drain_holes; sla::DrainHoles sla_drain_holes;
// Connectors to be added into the object before cut and are used to create a solid/negative volumes during a cut perform
CutConnectors cut_connectors;
CutObjectBase cut_id;
/* This vector accumulates the total translation applied to the object by the /* This vector accumulates the total translation applied to the object by the
center_around_origin() method. Callers might want to apply the same translation center_around_origin() method. Callers might want to apply the same translation
to new volumes before adding them to this object in order to preserve alignment to new volumes before adding them to this object in order to preserve alignment
@ -353,8 +442,21 @@ public:
size_t materials_count() const; size_t materials_count() const;
size_t facets_count() const; size_t facets_count() const;
size_t parts_count() const; size_t parts_count() const;
ModelObjectPtrs cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes); static indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes);
void split(ModelObjectPtrs* new_objects); void apply_cut_connectors(const std::string& name);
// invalidate cut state for this object and its connectors/volumes
void invalidate_cut();
void synchronize_model_after_cut();
void apply_cut_attributes(ModelObjectCutAttributes attributes);
void clone_for_cut(ModelObject **obj);
void process_connector_cut(ModelVolume* volume, ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
std::vector<ModelObject*>& dowels, Vec3d& local_dowels_displace);
void process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower);
void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, Vec3d& local_displace);
ModelObjectPtrs cut(size_t instance, const Transform3d&cut_matrix, ModelObjectCutAttributes attributes);
void split(ModelObjectPtrs*new_objects);
void merge(); void merge();
// Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees, // Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees,
// then the scaling in world coordinate system is not representable by the Geometry::Transformation structure. // then the scaling in world coordinate system is not representable by the Geometry::Transformation structure.
@ -377,6 +479,9 @@ public:
// Get count of errors in the mesh( or all object's meshes, if volume index isn't defined) // Get count of errors in the mesh( or all object's meshes, if volume index isn't defined)
int get_repaired_errors_count(const int vol_idx = -1) const; int get_repaired_errors_count(const int vol_idx = -1) const;
bool is_cut() const { return cut_id.id().valid(); }
bool has_connectors() const;
private: private:
friend class Model; friend class Model;
// This constructor assigns new ID to this ModelObject and its config. // This constructor assigns new ID to this ModelObject and its config.
@ -497,7 +602,8 @@ private:
Internal::StaticSerializationWrapper<LayerHeightProfile> layer_heigth_profile_wrapper(layer_height_profile); Internal::StaticSerializationWrapper<LayerHeightProfile> layer_heigth_profile_wrapper(layer_height_profile);
ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_heigth_profile_wrapper, ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_heigth_profile_wrapper,
sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation, sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation,
m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid); m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid,
cut_connectors, cut_id);
} }
// Called by Print::validate() from the UI thread. // Called by Print::validate() from the UI thread.
@ -616,6 +722,37 @@ public:
}; };
Source source; Source source;
// struct used by cut command
// It contains information about connetors
struct CutInfo
{
bool is_connector{ false };
bool is_processed{ true };
CutConnectorType connector_type{ CutConnectorType::Plug };
float radius_tolerance{ 0.f };// [0.f : 1.f]
float height_tolerance{ 0.f };// [0.f : 1.f]
CutInfo() = default;
CutInfo(CutConnectorType type, float rad_tolerance, float h_tolerance, bool processed = false) :
is_connector(true),
is_processed(processed),
connector_type(type),
radius_tolerance(rad_tolerance),
height_tolerance(h_tolerance)
{}
void set_processed() { is_processed = true; }
void invalidate() { is_connector = false; }
template<class Archive> inline void serialize(Archive& ar) {
ar(is_connector, is_processed, connector_type, radius_tolerance, height_tolerance);
}
};
CutInfo cut_info;
bool is_cut_connector() const { return cut_info.is_processed && cut_info.is_connector; }
void invalidate_cut_info() { cut_info.invalidate(); }
// The triangular model. // The triangular model.
const TriangleMesh& mesh() const { return *m_mesh.get(); } const TriangleMesh& mesh() const { return *m_mesh.get(); }
#if ENABLE_RAYCAST_PICKING #if ENABLE_RAYCAST_PICKING
@ -652,6 +789,8 @@ public:
bool is_support_blocker() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER; } bool is_support_blocker() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER; }
bool is_support_modifier() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER || m_type == ModelVolumeType::SUPPORT_ENFORCER; } bool is_support_modifier() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER || m_type == ModelVolumeType::SUPPORT_ENFORCER; }
t_model_material_id material_id() const { return m_material_id; } 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); void set_material_id(t_model_material_id material_id);
ModelMaterial* material() const; ModelMaterial* material() const;
void set_material(t_model_material_id material_id, const ModelMaterial &material); void set_material(t_model_material_id material_id, const ModelMaterial &material);
@ -821,7 +960,8 @@ private:
ObjectBase(other), ObjectBase(other),
name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull),
config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation),
supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets) supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets),
cut_info(other.cut_info)
{ {
assert(this->id().valid()); assert(this->id().valid());
assert(this->config.id().valid()); assert(this->config.id().valid());
@ -841,7 +981,8 @@ private:
} }
// Providing a new mesh, therefore this volume will get a new unique ID assigned. // Providing a new mesh, therefore this volume will get a new unique ID assigned.
ModelVolume(ModelObject *object, const ModelVolume &other, TriangleMesh &&mesh) : ModelVolume(ModelObject *object, const ModelVolume &other, TriangleMesh &&mesh) :
name(other.name), source(other.source), config(other.config), object(object), m_mesh(new TriangleMesh(std::move(mesh))), m_type(other.m_type), m_transformation(other.m_transformation) name(other.name), source(other.source), config(other.config), object(object), m_mesh(new TriangleMesh(std::move(mesh))), m_type(other.m_type), m_transformation(other.m_transformation),
cut_info(other.cut_info)
{ {
assert(this->id().valid()); assert(this->id().valid());
assert(this->config.id().valid()); assert(this->config.id().valid());
@ -883,7 +1024,7 @@ private:
} }
template<class Archive> void load(Archive &ar) { template<class Archive> void load(Archive &ar) {
bool has_convex_hull; bool has_convex_hull;
ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, cut_info);
cereal::load_by_value(ar, supported_facets); cereal::load_by_value(ar, supported_facets);
cereal::load_by_value(ar, seam_facets); cereal::load_by_value(ar, seam_facets);
cereal::load_by_value(ar, mmu_segmentation_facets); cereal::load_by_value(ar, mmu_segmentation_facets);
@ -899,7 +1040,7 @@ private:
} }
template<class Archive> void save(Archive &ar) const { template<class Archive> void save(Archive &ar) const {
bool has_convex_hull = m_convex_hull.get() != nullptr; bool has_convex_hull = m_convex_hull.get() != nullptr;
ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, cut_info);
cereal::save_by_value(ar, supported_facets); cereal::save_by_value(ar, supported_facets);
cereal::save_by_value(ar, seam_facets); cereal::save_by_value(ar, seam_facets);
cereal::save_by_value(ar, mmu_segmentation_facets); cereal::save_by_value(ar, mmu_segmentation_facets);
@ -1233,6 +1374,8 @@ extern bool model_mmu_segmentation_data_changed(const ModelObject& mo, const Mod
// If the model has multi-part objects, then it is currently not supported by the SLA mode. // If the model has multi-part objects, then it is currently not supported by the SLA mode.
// Either the model cannot be loaded, or a SLA printer has to be activated. // Either the model cannot be loaded, or a SLA printer has to be activated.
bool model_has_multi_part_objects(const Model &model); bool model_has_multi_part_objects(const Model &model);
// If the model has objects with cut connectrs, then it is currently not supported by the SLA mode.
bool model_has_connectors(const Model& model);
// If the model has advanced features, then it cannot be processed in simple mode. // If the model has advanced features, then it cannot be processed in simple mode.
bool model_has_advanced_features(const Model &model); bool model_has_advanced_features(const Model &model);

View File

@ -2,6 +2,7 @@
#define slic3r_ObjectID_hpp_ #define slic3r_ObjectID_hpp_
#include <cereal/access.hpp> #include <cereal/access.hpp>
#include <cereal/types/base_class.hpp>
namespace Slic3r { namespace Slic3r {
@ -89,7 +90,9 @@ private:
friend class cereal::access; friend class cereal::access;
friend class Slic3r::UndoRedo::StackImpl; friend class Slic3r::UndoRedo::StackImpl;
template<class Archive> void serialize(Archive &ar) { ar(m_id); } template<class Archive> void serialize(Archive &ar) { ar(m_id); }
protected: // #vbCHECKME && #ysFIXME
ObjectBase(const ObjectID id) : m_id(id) {} ObjectBase(const ObjectID id) : m_id(id) {}
private:
template<class Archive> static void load_and_construct(Archive & ar, cereal::construct<ObjectBase> &construct) { ObjectID id; ar(id); construct(id); } template<class Archive> static void load_and_construct(Archive & ar, cereal::construct<ObjectBase> &construct) { ObjectID id; ar(id); construct(id); }
}; };
@ -128,6 +131,64 @@ private:
template<class Archive> void serialize(Archive &ar) { ar(m_timestamp); } template<class Archive> void serialize(Archive &ar) { ar(m_timestamp); }
}; };
class CutObjectBase : public ObjectBase
{
// check sum of CutParts in initial Object
size_t m_check_sum{ 1 };
// connectors count
size_t m_connectors_cnt{ 0 };
public:
// Default Constructor to assign an invalid ID
CutObjectBase() : ObjectBase(-1) {}
// Constructor with ignored int parameter to assign an invalid ID, to be replaced
// by an existing ID copied from elsewhere.
CutObjectBase(int) : ObjectBase(-1) {}
// Constructor to initialize full information from 3mf
CutObjectBase(ObjectID id, size_t check_sum, size_t connectors_cnt) : ObjectBase(id), m_check_sum(check_sum), m_connectors_cnt(connectors_cnt) {}
// The class tree will have virtual tables and type information.
virtual ~CutObjectBase() = default;
bool operator<(const CutObjectBase& other) const { return other.id() > this->id(); }
bool operator==(const CutObjectBase& other) const { return other.id() == this->id(); }
void copy(const CutObjectBase& rhs) {
this->copy_id(rhs);
this->m_check_sum = rhs.check_sum();
this->m_connectors_cnt = rhs.connectors_cnt() ;
}
CutObjectBase& operator=(const CutObjectBase& other) {
this->copy(other);
return *this;
}
void invalidate() {
set_invalid_id();
m_check_sum = 1;
m_connectors_cnt = 0;
}
void init() { this->set_new_unique_id(); }
bool has_same_id(const CutObjectBase& rhs) { return this->id() == rhs.id(); }
bool is_equal(const CutObjectBase& rhs) { return this->id() == rhs.id() &&
this->check_sum() == rhs.check_sum() &&
this->connectors_cnt() == rhs.connectors_cnt() ; }
size_t check_sum() const { return m_check_sum; }
void set_check_sum(size_t cs) { m_check_sum = cs; }
void increase_check_sum(size_t cnt) { m_check_sum += cnt; }
size_t connectors_cnt() const { return m_connectors_cnt; }
void increase_connectors_cnt(size_t connectors_cnt) { m_connectors_cnt += connectors_cnt; }
private:
friend class cereal::access;
template<class Archive> void serialize(Archive& ar) {
ar(cereal::base_class<ObjectBase>(this));
ar(m_check_sum, m_connectors_cnt);
}
};
// Unique object / instance ID for the wipe tower. // Unique object / instance ID for the wipe tower.
extern ObjectID wipe_tower_object_id(); extern ObjectID wipe_tower_object_id();
extern ObjectID wipe_tower_instance_id(); extern ObjectID wipe_tower_instance_id();

View File

@ -1061,6 +1061,61 @@ indexed_triangle_set its_make_sphere(double radius, double fa)
return mesh; return mesh;
} }
// Generates mesh for a frustum dowel centered about the origin, using the count of sectors
// Note: This function uses code for sphere generation, but for stackCount = 2;
indexed_triangle_set its_make_frustum_dowel(double radius, double h, int sectorCount)
{
int stackCount = 2;
float sectorStep = float(2. * M_PI / sectorCount);
float stackStep = float(M_PI / stackCount);
indexed_triangle_set mesh;
auto& vertices = mesh.vertices;
vertices.reserve((stackCount - 1) * sectorCount + 2);
for (int i = 0; i <= stackCount; ++i) {
// from pi/2 to -pi/2
double stackAngle = 0.5 * M_PI - stackStep * i;
double xy = radius * cos(stackAngle);
double z = radius * sin(stackAngle);
if (i == 0 || i == stackCount)
vertices.emplace_back(Vec3f(float(xy), 0.f, float(h * sin(stackAngle))));
else
for (int j = 0; j < sectorCount; ++j) {
// from 0 to 2pi
double sectorAngle = sectorStep * j;
vertices.emplace_back(Vec3d(xy * std::cos(sectorAngle), xy * std::sin(sectorAngle), z).cast<float>());
}
}
auto& facets = mesh.indices;
facets.reserve(2 * (stackCount - 1) * sectorCount);
for (int i = 0; i < stackCount; ++i) {
// Beginning of current stack.
int k1 = (i == 0) ? 0 : (1 + (i - 1) * sectorCount);
int k1_first = k1;
// Beginning of next stack.
int k2 = (i == 0) ? 1 : (k1 + sectorCount);
int k2_first = k2;
for (int j = 0; j < sectorCount; ++j) {
// 2 triangles per sector excluding first and last stacks
int k1_next = k1;
int k2_next = k2;
if (i != 0) {
k1_next = (j + 1 == sectorCount) ? k1_first : (k1 + 1);
facets.emplace_back(k1, k2, k1_next);
}
if (i + 1 != stackCount) {
k2_next = (j + 1 == sectorCount) ? k2_first : (k2 + 1);
facets.emplace_back(k1_next, k2, k2_next);
}
k1 = k1_next;
k2 = k2_next;
}
}
return mesh;
}
indexed_triangle_set its_convex_hull(const std::vector<Vec3f> &pts) indexed_triangle_set its_convex_hull(const std::vector<Vec3f> &pts)
{ {
std::vector<Vec3f> dst_vertices; std::vector<Vec3f> dst_vertices;

View File

@ -301,6 +301,7 @@ indexed_triangle_set its_make_cube(double x, double y, double z);
indexed_triangle_set its_make_prism(float width, float length, float height); indexed_triangle_set its_make_prism(float width, float length, float height);
indexed_triangle_set its_make_cylinder(double r, double h, double fa=(2*PI/360)); indexed_triangle_set its_make_cylinder(double r, double h, double fa=(2*PI/360));
indexed_triangle_set its_make_cone(double r, double h, double fa=(2*PI/360)); indexed_triangle_set its_make_cone(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_pyramid(float base, float height);
indexed_triangle_set its_make_sphere(double radius, double fa); indexed_triangle_set its_make_sphere(double radius, double fa);

View File

@ -262,9 +262,9 @@ constexpr inline T lerp(const T& a, const T& b, Number t)
} }
template <typename Number> template <typename Number>
constexpr inline bool is_approx(Number value, Number test_value) constexpr inline bool is_approx(Number value, Number test_value, Number precision = EPSILON)
{ {
return std::fabs(double(value) - double(test_value)) < double(EPSILON); return std::fabs(double(value) - double(test_value)) < double(precision);
} }
// A meta-predicate which is true for integers wider than or equal to coord_t // A meta-predicate which is true for integers wider than or equal to coord_t

View File

@ -256,6 +256,8 @@ set(SLIC3R_GUI_SOURCES
Utils/WinRegistry.hpp Utils/WinRegistry.hpp
) )
find_package(NanoSVG REQUIRED)
if (APPLE) if (APPLE)
list(APPEND SLIC3R_GUI_SOURCES list(APPEND SLIC3R_GUI_SOURCES
Utils/RetinaHelperImpl.mm Utils/RetinaHelperImpl.mm

View File

@ -33,7 +33,7 @@ wxString double_to_string(double const value, const int max_precision /*= 4*/)
// Style_NoTrailingZeroes does not work on OSX. It also does not work correctly with some locales on Windows. // Style_NoTrailingZeroes does not work on OSX. It also does not work correctly with some locales on Windows.
// return wxNumberFormatter::ToString(value, max_precision, wxNumberFormatter::Style_NoTrailingZeroes); // return wxNumberFormatter::ToString(value, max_precision, wxNumberFormatter::Style_NoTrailingZeroes);
wxString s = wxNumberFormatter::ToString(value, value < 0.0001 ? 10 : max_precision, wxNumberFormatter::Style_None); wxString s = wxNumberFormatter::ToString(value, std::abs(value) < 0.0001 ? 10 : max_precision, wxNumberFormatter::Style_None);
// The following code comes from wxNumberFormatter::RemoveTrailingZeroes(wxString& s) // The following code comes from wxNumberFormatter::RemoveTrailingZeroes(wxString& s)
// with the exception that here one sets the decimal separator explicitely to dot. // with the exception that here one sets the decimal separator explicitely to dot.

View File

@ -1367,6 +1367,8 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject
&& (instance_idx == -1 || vol->composite_id.instance_id == instance_idx) && (instance_idx == -1 || vol->composite_id.instance_id == instance_idx)
&& (mv == nullptr || m_model->objects[vol->composite_id.object_id]->volumes[vol->composite_id.volume_id] == mv)) { && (mv == nullptr || m_model->objects[vol->composite_id.object_id]->volumes[vol->composite_id.volume_id] == mv)) {
vol->is_active = visible; vol->is_active = visible;
if (!vol->is_modifier)
vol->color.a(1.f);
if (instance_idx == -1) { if (instance_idx == -1) {
vol->force_native_color = false; vol->force_native_color = false;
@ -1375,9 +1377,13 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject
const GLGizmosManager& gm = get_gizmos_manager(); const GLGizmosManager& gm = get_gizmos_manager();
auto gizmo_type = gm.get_current_type(); auto gizmo_type = gm.get_current_type();
if ( (gizmo_type == GLGizmosManager::FdmSupports if ( (gizmo_type == GLGizmosManager::FdmSupports
|| gizmo_type == GLGizmosManager::Seam) || gizmo_type == GLGizmosManager::Seam
&& ! vol->is_modifier) || gizmo_type == GLGizmosManager::Cut)
&& ! vol->is_modifier) {
vol->force_neutral_color = true; vol->force_neutral_color = true;
if (gizmo_type == GLGizmosManager::Cut)
vol->color.a(0.95f);
}
else if (gizmo_type == GLGizmosManager::MmuSegmentation) else if (gizmo_type == GLGizmosManager::MmuSegmentation)
vol->is_active = false; vol->is_active = false;
else else
@ -3371,6 +3377,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
show_sinking_contours(); show_sinking_contours();
} }
} }
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; return;
} }
@ -3446,6 +3457,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
if (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports && if (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports &&
m_gizmos.get_current_type() != GLGizmosManager::FdmSupports && m_gizmos.get_current_type() != GLGizmosManager::FdmSupports &&
m_gizmos.get_current_type() != GLGizmosManager::Seam && m_gizmos.get_current_type() != GLGizmosManager::Seam &&
m_gizmos.get_current_type() != GLGizmosManager::Cut &&
m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation) { m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation) {
m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect); m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect);
m_dirty = true; m_dirty = true;
@ -3495,7 +3507,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
const int volume_idx = get_first_hover_volume_idx(); const int volume_idx = get_first_hover_volume_idx();
BoundingBoxf3 volume_bbox = m_volumes.volumes[volume_idx]->transformed_bounding_box(); BoundingBoxf3 volume_bbox = m_volumes.volumes[volume_idx]->transformed_bounding_box();
volume_bbox.offset(1.0); volume_bbox.offset(1.0);
if ((!any_gizmo_active || !evt.CmdDown()) && volume_bbox.contains(m_mouse.scene_position)) { const bool is_cut_connector_selected = m_selection.is_any_connector();
if ((!any_gizmo_active || !evt.CmdDown()) && volume_bbox.contains(m_mouse.scene_position) && !is_cut_connector_selected) {
m_volumes.volumes[volume_idx]->hover = GLVolume::HS_None; m_volumes.volumes[volume_idx]->hover = GLVolume::HS_None;
// The dragging operation is initiated. // The dragging operation is initiated.
m_mouse.drag.move_volume_idx = volume_idx; m_mouse.drag.move_volume_idx = volume_idx;

View File

@ -2890,6 +2890,13 @@ bool GUI_App::may_switch_to_SLA_preset(const wxString& caption)
caption); caption);
return false; return false;
} }
if (model_has_connectors(model())) {
show_info(nullptr,
_L("SLA technology doesn't support cut with connectors") + "\n\n" +
_L("Please check your object list before preset changing."),
caption);
return false;
}
return true; return true;
} }

View File

@ -493,7 +493,9 @@ void MenuFactory::append_menu_items_add_volume(wxMenu* menu)
append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].first), "", append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].first), "",
[](wxCommandEvent&) { obj_list()->load_subobject(ModelVolumeType::MODEL_PART); }, [](wxCommandEvent&) { obj_list()->load_subobject(ModelVolumeType::MODEL_PART); },
ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].second, nullptr, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].second, nullptr,
[]() { return obj_list()->is_instance_or_object_selected(); }, m_parent); []() { return obj_list()->is_instance_or_object_selected()
&& !obj_list()->is_selected_object_cut();
}, m_parent);
} }
if (mode == comSimple) { if (mode == comSimple) {
append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)].first), "", append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)].first), "",
@ -514,7 +516,10 @@ void MenuFactory::append_menu_items_add_volume(wxMenu* menu)
wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType(type)); wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType(type));
append_submenu(menu, sub_menu, wxID_ANY, _(item.first), "", item.second, append_submenu(menu, sub_menu, wxID_ANY, _(item.first), "", item.second,
[]() { return obj_list()->is_instance_or_object_selected(); }, m_parent); [type]() {
bool can_add = type < size_t(ModelVolumeType::PARAMETER_MODIFIER) ? !obj_list()->is_selected_object_cut() : true;
return can_add && obj_list()->is_instance_or_object_selected();
}, m_parent);
} }
append_menu_item_layers_editing(menu); append_menu_item_layers_editing(menu);
@ -680,6 +685,21 @@ wxMenuItem* MenuFactory::append_menu_item_printable(wxMenu* menu)
return menu_item_printable; return menu_item_printable;
} }
void MenuFactory::append_menu_item_invalidate_cut_info(wxMenu* menu)
{
const wxString menu_name = _L("Invalidate cut info");
auto menu_item_id = menu->FindItem(menu_name);
if (menu_item_id != wxNOT_FOUND)
// Delete old menu item if selected object isn't cut
menu->Destroy(menu_item_id);
if (obj_list()->has_selected_cut_object())
append_menu_item(menu, wxID_ANY, menu_name, "",
[](wxCommandEvent&) { obj_list()->invalidate_cut_info_for_selection(); }, "", menu,
[]() { return true; }, m_parent);
}
void MenuFactory::append_menu_items_osx(wxMenu* menu) void MenuFactory::append_menu_items_osx(wxMenu* menu)
{ {
append_menu_item(menu, wxID_ANY, _L("Rename"), "", append_menu_item(menu, wxID_ANY, _L("Rename"), "",
@ -816,6 +836,8 @@ void MenuFactory::append_menu_items_convert_unit(wxMenu* menu, int insert_pos/*
ModelObjectPtrs objects; ModelObjectPtrs objects;
for (int obj_idx : obj_idxs) { for (int obj_idx : obj_idxs) {
ModelObject* object = obj_list()->object(obj_idx); ModelObject* object = obj_list()->object(obj_idx);
if (object->is_cut())
return false;
if (vol_idxs.empty()) { if (vol_idxs.empty()) {
for (ModelVolume* volume : object->volumes) for (ModelVolume* volume : object->volumes)
if (volume_respects_conversion(volume, conver_type)) if (volume_respects_conversion(volume, conver_type))
@ -1016,6 +1038,7 @@ wxMenu* MenuFactory::object_menu()
append_menu_item_settings(&m_object_menu); append_menu_item_settings(&m_object_menu);
append_menu_item_change_extruder(&m_object_menu); append_menu_item_change_extruder(&m_object_menu);
update_menu_items_instance_manipulation(mtObjectFFF); update_menu_items_instance_manipulation(mtObjectFFF);
append_menu_item_invalidate_cut_info(&m_object_menu);
return &m_object_menu; return &m_object_menu;
} }
@ -1025,6 +1048,7 @@ wxMenu* MenuFactory::sla_object_menu()
append_menu_items_convert_unit(&m_sla_object_menu, 11); append_menu_items_convert_unit(&m_sla_object_menu, 11);
append_menu_item_settings(&m_sla_object_menu); append_menu_item_settings(&m_sla_object_menu);
update_menu_items_instance_manipulation(mtObjectSLA); update_menu_items_instance_manipulation(mtObjectSLA);
append_menu_item_invalidate_cut_info(&m_sla_object_menu);
return &m_sla_object_menu; return &m_sla_object_menu;
} }
@ -1056,6 +1080,9 @@ wxMenu* MenuFactory::multi_selection_menu()
wxDataViewItemArray sels; wxDataViewItemArray sels;
obj_list()->GetSelections(sels); obj_list()->GetSelections(sels);
if (sels.IsEmpty())
return nullptr;
for (const wxDataViewItem& item : sels) for (const wxDataViewItem& item : sels)
if (!(list_model()->GetItemType(item) & (itVolume | itObject | itInstance))) if (!(list_model()->GetItemType(item) & (itVolume | itObject | itInstance)))
// show this menu only for Objects(Instances mixed with Objects)/Volumes selection // show this menu only for Objects(Instances mixed with Objects)/Volumes selection

View File

@ -89,6 +89,7 @@ private:
wxMenuItem* append_menu_item_change_type(wxMenu* menu); wxMenuItem* append_menu_item_change_type(wxMenu* menu);
wxMenuItem* append_menu_item_instance_to_object(wxMenu* menu); wxMenuItem* append_menu_item_instance_to_object(wxMenu* menu);
wxMenuItem* append_menu_item_printable(wxMenu* menu); wxMenuItem* append_menu_item_printable(wxMenu* menu);
void append_menu_item_invalidate_cut_info(wxMenu *menu);
void append_menu_items_osx(wxMenu* menu); void append_menu_items_osx(wxMenu* menu);
wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu); wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu);
wxMenuItem* append_menu_item_simplify(wxMenu* menu); wxMenuItem* append_menu_item_simplify(wxMenu* menu);

View File

@ -11,6 +11,8 @@
#include "GalleryDialog.hpp" #include "GalleryDialog.hpp"
#include "MainFrame.hpp" #include "MainFrame.hpp"
#include "slic3r/Utils/UndoRedo.hpp" #include "slic3r/Utils/UndoRedo.hpp"
#include "Gizmos/GLGizmoCut.hpp"
#include "Gizmos/GLGizmoScale.hpp"
#include "OptionsGroup.hpp" #include "OptionsGroup.hpp"
#include "Tab.hpp" #include "Tab.hpp"
@ -401,6 +403,13 @@ MeshErrorsInfo ObjectList::get_mesh_errors_info(const int obj_idx, const int vol
if (obj_idx < 0) if (obj_idx < 0)
return { {}, {} }; // hide tooltip return { {}, {} }; // hide tooltip
const ModelObject* object = (*m_objects)[obj_idx];
if (vol_idx != -1 && vol_idx >= int(object->volumes.size())) {
if (sidebar_info)
*sidebar_info = _L("Wrong volume index ");
return { {}, {} }; // hide tooltip
}
const TriangleMeshStats& stats = vol_idx == -1 ? const TriangleMeshStats& stats = vol_idx == -1 ?
(*m_objects)[obj_idx]->get_object_stl_stats() : (*m_objects)[obj_idx]->get_object_stl_stats() :
(*m_objects)[obj_idx]->volumes[vol_idx]->mesh().stats(); (*m_objects)[obj_idx]->volumes[vol_idx]->mesh().stats();
@ -673,6 +682,8 @@ void ObjectList::selection_changed()
fix_multiselection_conflicts(); fix_multiselection_conflicts();
fix_cut_selection();
// update object selection on Plater // update object selection on Plater
if (!m_prevent_canvas_selection_update) if (!m_prevent_canvas_selection_update)
update_selections_on_canvas(); update_selections_on_canvas();
@ -1389,6 +1400,15 @@ bool ObjectList::is_instance_or_object_selected()
return selection.is_single_full_instance() || selection.is_single_full_object(); return selection.is_single_full_instance() || selection.is_single_full_object();
} }
bool ObjectList::is_selected_object_cut()
{
const Selection& selection = scene_selection();
int obj_idx = selection.get_object_idx();
if (obj_idx < 0)
return false;
return object(obj_idx)->is_cut();
}
void ObjectList::load_subobject(ModelVolumeType type, bool from_galery/* = false*/) void ObjectList::load_subobject(ModelVolumeType type, bool from_galery/* = false*/)
{ {
if (type == ModelVolumeType::INVALID && from_galery) { if (type == ModelVolumeType::INVALID && from_galery) {
@ -1708,6 +1728,9 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode
// update printable state on canvas // update printable state on canvas
wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_object((size_t)obj_idx); wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_object((size_t)obj_idx);
if (model_object.is_cut())
update_info_items(obj_idx);
selection_changed(); selection_changed();
} }
@ -1804,22 +1827,22 @@ void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name
#endif /* _DEBUG */ #endif /* _DEBUG */
} }
void ObjectList::del_object(const int obj_idx) bool ObjectList::del_object(const int obj_idx)
{ {
wxGetApp().plater()->delete_object_from_model(obj_idx); return wxGetApp().plater()->delete_object_from_model(obj_idx);
} }
// Delete subobject // Delete subobject
void ObjectList::del_subobject_item(wxDataViewItem& item) bool ObjectList::del_subobject_item(wxDataViewItem& item)
{ {
if (!item) return; if (!item) return false;
int obj_idx, idx; int obj_idx, idx;
ItemType type; ItemType type;
m_objects_model->GetItemInfo(item, type, obj_idx, idx); m_objects_model->GetItemInfo(item, type, obj_idx, idx);
if (type == itUndef) if (type == itUndef)
return; return false;
wxDataViewItem parent = m_objects_model->GetParent(item); wxDataViewItem parent = m_objects_model->GetParent(item);
@ -1833,19 +1856,21 @@ void ObjectList::del_subobject_item(wxDataViewItem& item)
del_layer_from_object(obj_idx, m_objects_model->GetLayerRangeByItem(item)); del_layer_from_object(obj_idx, m_objects_model->GetLayerRangeByItem(item));
else if (type & itInfo && obj_idx != -1) else if (type & itInfo && obj_idx != -1)
del_info_item(obj_idx, m_objects_model->GetInfoItemType(item)); del_info_item(obj_idx, m_objects_model->GetInfoItemType(item));
else if (idx == -1) else if (idx == -1 || !del_subobject_from_object(obj_idx, idx, type))
return; return false;
else if (!del_subobject_from_object(obj_idx, idx, type))
return;
// If last volume item with warning was deleted, unmark object item // If last volume item with warning was deleted, unmark object item
if (type & itVolume) { if (type & itVolume) {
add_volumes_to_object_in_list(obj_idx);
const std::string& icon_name = get_warning_icon_name(object(obj_idx)->get_object_stl_stats()); const std::string& icon_name = get_warning_icon_name(object(obj_idx)->get_object_stl_stats());
m_objects_model->UpdateWarningIcon(parent, icon_name); m_objects_model->UpdateWarningIcon(parent, icon_name);
} }
else
m_objects_model->Delete(item);
m_objects_model->Delete(item);
update_info_items(obj_idx); update_info_items(obj_idx);
return true;
} }
void ObjectList::del_info_item(const int obj_idx, InfoItemType type) void ObjectList::del_info_item(const int obj_idx, InfoItemType type)
@ -1868,6 +1893,10 @@ void ObjectList::del_info_item(const int obj_idx, InfoItemType type)
mv->seam_facets.reset(); mv->seam_facets.reset();
break; break;
case InfoItemType::CutConnectors:
show_error(nullptr, _L("Connectors cannot be deleted from cut object."));
break;
case InfoItemType::MmuSegmentation: case InfoItemType::MmuSegmentation:
cnv->get_gizmos_manager().reset_all_states(); cnv->get_gizmos_manager().reset_all_states();
Plater::TakeSnapshot(plater, _L("Remove Multi Material painting")); Plater::TakeSnapshot(plater, _L("Remove Multi Material painting"));
@ -1978,6 +2007,16 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con
Slic3r::GUI::show_error(nullptr, _L("From Object List You can't delete the last solid part from object.")); Slic3r::GUI::show_error(nullptr, _L("From Object List You can't delete the last solid part from object."));
return false; return false;
} }
if (object->is_cut()) {
if (volume->is_model_part()) {
Slic3r::GUI::show_error(nullptr, _L("Solid part cannot be deleted from cut object."));
return false;
}
if (volume->is_negative_volume()) {
Slic3r::GUI::show_error(nullptr, _L("Negative volume cannot be deleted from cut object."));
return false;
}
}
take_snapshot(_L("Delete Subobject")); take_snapshot(_L("Delete Subobject"));
@ -2005,6 +2044,10 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con
Slic3r::GUI::show_error(nullptr, _L("Last instance of an object cannot be deleted.")); Slic3r::GUI::show_error(nullptr, _L("Last instance of an object cannot be deleted."));
return false; return false;
} }
if (object->is_cut()) {
Slic3r::GUI::show_error(nullptr, _L("Instance cannot be deleted from cut object."));
return false;
}
take_snapshot(_L("Delete Instance")); take_snapshot(_L("Delete Instance"));
object->delete_instance(idx); object->delete_instance(idx);
@ -2038,30 +2081,11 @@ void ObjectList::split()
volume->split(nozzle_dmrs_cnt); volume->split(nozzle_dmrs_cnt);
(*m_objects)[obj_idx]->input_file.clear();
wxBusyCursor wait; wxBusyCursor wait;
auto model_object = (*m_objects)[obj_idx]; add_volumes_to_object_in_list(obj_idx);
auto parent = m_objects_model->GetTopParent(item);
if (parent)
m_objects_model->DeleteVolumeChildren(parent);
else
parent = item;
for (const ModelVolume* volume : model_object->volumes) {
const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(parent, from_u8(volume->name),
volume->type(),// is_modifier() ? ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART,
get_warning_icon_name(volume->mesh().stats()),
volume->config.has("extruder") ? volume->config.extruder() : 0,
false);
// add settings to the part, if it has those
add_settings_item(vol_item, &volume->config.get());
}
model_object->input_file.clear();
if (parent == item)
Expand(parent);
changed_object(obj_idx); changed_object(obj_idx);
// update printable state for new volumes on canvas3D // update printable state for new volumes on canvas3D
@ -2380,9 +2404,12 @@ bool ObjectList::is_splittable(bool to_objects)
auto obj_idx = get_selected_obj_idx(); auto obj_idx = get_selected_obj_idx();
if (obj_idx < 0) if (obj_idx < 0)
return false; return false;
if ((*m_objects)[obj_idx]->volumes.size() > 1) const ModelObject* object = (*m_objects)[obj_idx];
if (object->is_cut())
return false;
if (object->volumes.size() > 1)
return true; return true;
return (*m_objects)[obj_idx]->volumes[0]->is_splittable(); return object->volumes[0]->is_splittable();
} }
return false; return false;
} }
@ -2415,9 +2442,59 @@ bool ObjectList::can_split_instances()
return selection.is_multiple_full_instance() || selection.is_single_full_instance(); return selection.is_multiple_full_instance() || selection.is_single_full_instance();
} }
bool ObjectList::has_selected_cut_object() const
{
wxDataViewItemArray sels;
GetSelections(sels);
if (sels.IsEmpty())
return false;
for (wxDataViewItem item : sels) {
const int obj_idx = m_objects_model->GetObjectIdByItem(item);
if (obj_idx >= 0 && object(obj_idx)->is_cut())
return true;
}
return false;
}
void ObjectList::invalidate_cut_info_for_selection()
{
const wxDataViewItem item = GetSelection();
if (item) {
const int obj_idx = m_objects_model->GetObjectIdByItem(item);
if (obj_idx >= 0)
invalidate_cut_info_for_object(size_t(obj_idx));
}
}
void ObjectList::invalidate_cut_info_for_object(size_t obj_idx)
{
ModelObject* init_obj = object(int(obj_idx));
if (!init_obj->is_cut())
return;
take_snapshot(_L("Invalidate cut info"));
auto invalidate_cut = [this](size_t obj_idx) {
object(int(obj_idx))->invalidate_cut();
update_info_items(obj_idx);
add_volumes_to_object_in_list(obj_idx);
};
// invalidate cut for related objects (which have the same cut_id)
for (size_t idx = 0; idx < m_objects->size(); idx++)
if (ModelObject* obj = object(idx); obj != init_obj && obj->cut_id.is_equal(init_obj->cut_id))
invalidate_cut(idx);
// invalidate own cut information
invalidate_cut(size_t(obj_idx));
update_lock_icons_for_model();
}
bool ObjectList::can_merge_to_multipart_object() const bool ObjectList::can_merge_to_multipart_object() const
{ {
if (printer_technology() == ptSLA) if (printer_technology() == ptSLA || has_selected_cut_object())
return false; return false;
wxDataViewItemArray sels; wxDataViewItemArray sels;
@ -2445,17 +2522,7 @@ bool ObjectList::can_merge_to_single_object() const
wxPoint ObjectList::get_mouse_position_in_control() const wxPoint ObjectList::get_mouse_position_in_control() const
{ {
wxPoint pt = wxGetMousePosition() - this->GetScreenPosition(); return wxGetMousePosition() - this->GetScreenPosition();
#ifdef __APPLE__
// Workaround for OSX. From wxWidgets 3.1.6 Hittest doesn't respect to the header of wxDataViewCtrl
if (wxDataViewItem top_item = this->GetTopItem(); top_item.IsOk()) {
auto rect = this->GetItemRect(top_item, this->GetColumn(0));
pt.y -= rect.y;
}
#endif // __APPLE__
return pt;
} }
// NO_PARAMETERS function call means that changed object index will be determine from Selection() // NO_PARAMETERS function call means that changed object index will be determine from Selection()
@ -2476,14 +2543,65 @@ void ObjectList::part_selection_changed()
bool update_and_show_settings = false; bool update_and_show_settings = false;
bool update_and_show_layers = false; bool update_and_show_layers = false;
bool enable_manipulation {true};
bool disable_ss_manipulation {false};
bool disable_ununiform_scale {false};
const auto item = GetSelection(); const auto item = GetSelection();
if ( multiple_selection() || (item && m_objects_model->GetItemType(item) == itInstanceRoot )) { GLGizmosManager& gizmos_mgr = wxGetApp().plater()->canvas3D()->get_gizmos_manager();
if (item && m_objects_model->GetItemType(item) == itInfo && m_objects_model->GetInfoItemType(item) == InfoItemType::CutConnectors) {
og_name = _L("Cut Connectors information");
update_and_show_manipulations = true;
enable_manipulation = false;
disable_ununiform_scale = true;
}
else if ( multiple_selection() || (item && m_objects_model->GetItemType(item) == itInstanceRoot )) {
og_name = _L("Group manipulation"); og_name = _L("Group manipulation");
const Selection& selection = scene_selection(); const Selection& selection = scene_selection();
// don't show manipulation panel for case of all Object's parts selection // don't show manipulation panel for case of all Object's parts selection
update_and_show_manipulations = !selection.is_single_full_instance(); update_and_show_manipulations = !selection.is_single_full_instance();
if (int obj_idx = selection.get_object_idx(); obj_idx >= 0) {
if (selection.is_any_volume() || selection.is_any_modifier())
enable_manipulation = !(*m_objects)[obj_idx]->is_cut();
else// if (item && m_objects_model->GetItemType(item) == itInstanceRoot)
disable_ss_manipulation = (*m_objects)[obj_idx]->is_cut();
}
else {
wxDataViewItemArray sels;
GetSelections(sels);
if (selection.is_single_full_object() || selection.is_multiple_full_instance() ) {
int obj_idx = m_objects_model->GetObjectIdByItem(sels.front());
disable_ss_manipulation = (*m_objects)[obj_idx]->is_cut();
}
else if (selection.is_mixed() || selection.is_multiple_full_object()) {
std::map<CutObjectBase, std::set<int>> cut_objects;
// find cut objects
for (auto item : sels) {
int obj_idx = m_objects_model->GetObjectIdByItem(item);
const ModelObject* obj = object(obj_idx);
if (obj->is_cut()) {
if (cut_objects.find(obj->cut_id) == cut_objects.end())
cut_objects[obj->cut_id] = std::set<int>{ obj_idx };
else
cut_objects.at(obj->cut_id).insert(obj_idx);
}
}
// check if selected cut objects are "full selected"
for (auto cut_object : cut_objects)
if (cut_object.first.check_sum() != cut_object.second.size()) {
disable_ss_manipulation = true;
break;
}
disable_ununiform_scale = !cut_objects.empty();
}
}
} }
else { else {
if (item) { if (item) {
@ -2491,11 +2609,12 @@ void ObjectList::part_selection_changed()
const wxDataViewItem parent = m_objects_model->GetParent(item); const wxDataViewItem parent = m_objects_model->GetParent(item);
const ItemType parent_type = m_objects_model->GetItemType(parent); const ItemType parent_type = m_objects_model->GetItemType(parent);
obj_idx = m_objects_model->GetObjectIdByItem(item); obj_idx = m_objects_model->GetObjectIdByItem(item);
ModelObject* object = (*m_objects)[obj_idx];
if (parent == wxDataViewItem(nullptr) if (parent == wxDataViewItem(nullptr)
|| type == itInfo) { || type == itInfo) {
og_name = _L("Object manipulation"); og_name = _L("Object manipulation");
m_config = &(*m_objects)[obj_idx]->config; m_config = &object->config;
update_and_show_manipulations = true; update_and_show_manipulations = true;
if (type == itInfo) { if (type == itInfo) {
@ -2511,29 +2630,30 @@ void ObjectList::part_selection_changed()
case InfoItemType::CustomSeam: case InfoItemType::CustomSeam:
case InfoItemType::MmuSegmentation: case InfoItemType::MmuSegmentation:
{ {
GLGizmosManager::EType gizmo_type = info_type == InfoItemType::CustomSupports ? GLGizmosManager::EType::FdmSupports : GLGizmosManager::EType gizmo_type = info_type == InfoItemType::CustomSupports ? GLGizmosManager::EType::FdmSupports :
info_type == InfoItemType::CustomSeam ? GLGizmosManager::EType::Seam : info_type == InfoItemType::CustomSeam ? GLGizmosManager::EType::Seam :
GLGizmosManager::EType::MmuSegmentation; GLGizmosManager::EType::MmuSegmentation;
GLGizmosManager& gizmos_mgr = wxGetApp().plater()->canvas3D()->get_gizmos_manager();
if (gizmos_mgr.get_current_type() != gizmo_type) if (gizmos_mgr.get_current_type() != gizmo_type)
gizmos_mgr.open_gizmo(gizmo_type); gizmos_mgr.open_gizmo(gizmo_type);
break; break;
} }
case InfoItemType::Sinking: { break; } case InfoItemType::Sinking:
default: { break; } default: { break; }
} }
} }
else
disable_ss_manipulation = object->is_cut();
} }
else { else {
if (type & itSettings) { if (type & itSettings) {
if (parent_type & itObject) { if (parent_type & itObject) {
og_name = _L("Object Settings to modify"); og_name = _L("Object Settings to modify");
m_config = &(*m_objects)[obj_idx]->config; m_config = &object->config;
} }
else if (parent_type & itVolume) { else if (parent_type & itVolume) {
og_name = _L("Part Settings to modify"); og_name = _L("Part Settings to modify");
volume_id = m_objects_model->GetVolumeIdByItem(parent); volume_id = m_objects_model->GetVolumeIdByItem(parent);
m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; m_config = &object->volumes[volume_id]->config;
} }
else if (parent_type & itLayer) { else if (parent_type & itLayer) {
og_name = _L("Layer range Settings to modify"); og_name = _L("Layer range Settings to modify");
@ -2544,15 +2664,18 @@ void ObjectList::part_selection_changed()
else if (type & itVolume) { else if (type & itVolume) {
og_name = _L("Part manipulation"); og_name = _L("Part manipulation");
volume_id = m_objects_model->GetVolumeIdByItem(item); volume_id = m_objects_model->GetVolumeIdByItem(item);
m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; m_config = &object->volumes[volume_id]->config;
update_and_show_manipulations = true; update_and_show_manipulations = true;
const ModelVolume* volume = object->volumes[volume_id];
enable_manipulation = !(object->is_cut() && (volume->is_cut_connector() || volume->is_model_part()));
} }
else if (type & itInstance) { else if (type & itInstance) {
og_name = _L("Instance manipulation"); og_name = _L("Instance manipulation");
update_and_show_manipulations = true; update_and_show_manipulations = true;
// fill m_config by object's values // fill m_config by object's values
m_config = &(*m_objects)[obj_idx]->config; m_config = &object->config;
disable_ss_manipulation = object->is_cut();
} }
else if (type & (itLayerRoot|itLayer)) { else if (type & (itLayerRoot|itLayer)) {
og_name = type & itLayerRoot ? _L("Height ranges") : _L("Settings for height range"); og_name = type & itLayerRoot ? _L("Height ranges") : _L("Settings for height range");
@ -2571,10 +2694,20 @@ void ObjectList::part_selection_changed()
wxGetApp().obj_manipul()->get_og()->set_name(" " + og_name + " "); wxGetApp().obj_manipul()->get_og()->set_name(" " + og_name + " ");
if (item) { if (item) {
// wxGetApp().obj_manipul()->get_og()->set_value("object_name", m_objects_model->GetName(item));
wxGetApp().obj_manipul()->update_item_name(m_objects_model->GetName(item)); wxGetApp().obj_manipul()->update_item_name(m_objects_model->GetName(item));
wxGetApp().obj_manipul()->update_warning_icon_state(get_mesh_errors_info(obj_idx, volume_id)); wxGetApp().obj_manipul()->update_warning_icon_state(get_mesh_errors_info(obj_idx, volume_id));
} }
if (disable_ss_manipulation)
wxGetApp().obj_manipul()->DisableScale();
else {
wxGetApp().obj_manipul()->Enable(enable_manipulation);
if (disable_ununiform_scale)
wxGetApp().obj_manipul()->DisableUnuniformScale();
}
if (GLGizmoScale3D* scale = dynamic_cast<GLGizmoScale3D*>(gizmos_mgr.get_gizmo(GLGizmosManager::Scale)))
scale->enable_ununiversal_scale(!disable_ununiform_scale);
} }
if (update_and_show_settings) if (update_and_show_settings)
@ -2597,6 +2730,7 @@ void ObjectList::part_selection_changed()
#else #else
wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false);
#endif // ENABLE_WORLD_COORDINATE #endif // ENABLE_WORLD_COORDINATE
wxGetApp().plater()->canvas3D()->enable_moving(enable_manipulation); // ysFIXME
wxGetApp().obj_manipul() ->UpdateAndShow(update_and_show_manipulations); wxGetApp().obj_manipul() ->UpdateAndShow(update_and_show_manipulations);
wxGetApp().obj_settings()->UpdateAndShow(update_and_show_settings); wxGetApp().obj_settings()->UpdateAndShow(update_and_show_settings);
wxGetApp().obj_layers() ->UpdateAndShow(update_and_show_layers); wxGetApp().obj_layers() ->UpdateAndShow(update_and_show_layers);
@ -2655,6 +2789,7 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio
for (InfoItemType type : {InfoItemType::CustomSupports, for (InfoItemType type : {InfoItemType::CustomSupports,
InfoItemType::CustomSeam, InfoItemType::CustomSeam,
InfoItemType::CutConnectors,
InfoItemType::MmuSegmentation, InfoItemType::MmuSegmentation,
InfoItemType::Sinking, InfoItemType::Sinking,
InfoItemType::VariableLayerHeight}) { InfoItemType::VariableLayerHeight}) {
@ -2675,6 +2810,9 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio
}); });
break; break;
case InfoItemType::CutConnectors:
should_show = model_object->is_cut() && model_object->has_connectors() && model_object->volumes.size() > 1;
break;
case InfoItemType::VariableLayerHeight : case InfoItemType::VariableLayerHeight :
should_show = printer_technology() == ptFFF should_show = printer_technology() == ptFFF
&& ! model_object->layer_height_profile.empty(); && ! model_object->layer_height_profile.empty();
@ -2714,31 +2852,73 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio
} }
} }
static wxString extruder2str(int extruder)
{
return extruder == 0 ? _L("default") : wxString::Format("%d", extruder);
}
static bool can_add_volumes_to_object(const ModelObject* object)
{
bool can = object->volumes.size() > 1;
if (can && object->is_cut()) {
int no_connectors_cnt = 0;
for (const ModelVolume* v : object->volumes)
if (!v->is_cut_connector()) {
if (!v->is_model_part())
return true;
no_connectors_cnt++;
}
can = no_connectors_cnt > 1;
}
return can;
}
wxDataViewItemArray ObjectList::add_volumes_to_object_in_list(size_t obj_idx, std::function<bool(const ModelVolume*)> add_to_selection/* = nullptr*/)
{
wxDataViewItem object_item = m_objects_model->GetItemById(int(obj_idx));
m_objects_model->DeleteVolumeChildren(object_item);
wxDataViewItemArray items;
const ModelObject* object = (*m_objects)[obj_idx];
// add volumes to the object
if (can_add_volumes_to_object(object)) {
int volume_idx{ -1 };
for (const ModelVolume* volume : object->volumes) {
++volume_idx;
if (object->is_cut() && volume->is_cut_connector())
continue;
const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(object_item,
from_u8(volume->name),
volume_idx,
volume->type(),
get_warning_icon_name(volume->mesh().stats()),
extruder2str(volume->config.has("extruder") ? volume->config.extruder() : 0));
add_settings_item(vol_item, &volume->config.get());
if (add_to_selection && add_to_selection(volume))
items.Add(vol_item);
}
Expand(object_item);
}
return items;
}
void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed)
{ {
auto model_object = (*m_objects)[obj_idx]; auto model_object = (*m_objects)[obj_idx];
const wxString& item_name = from_u8(model_object->name); const wxString& item_name = from_u8(model_object->name);
const auto item = m_objects_model->Add(item_name, const auto item = m_objects_model->AddObject(item_name,
model_object->config.has("extruder") ? model_object->config.extruder() : 0, extruder2str(model_object->config.has("extruder") ? model_object->config.extruder() : 0),
get_warning_icon_name(model_object->mesh().stats())); get_warning_icon_name(model_object->mesh().stats()),
model_object->is_cut());
update_info_items(obj_idx, nullptr, call_selection_changed); update_info_items(obj_idx, nullptr, call_selection_changed);
// add volumes to the object add_volumes_to_object_in_list(obj_idx);
if (model_object->volumes.size() > 1) {
for (const ModelVolume* volume : model_object->volumes) {
const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(item,
from_u8(volume->name),
volume->type(),
get_warning_icon_name(volume->mesh().stats()),
volume->config.has("extruder") ? volume->config.extruder() : 0,
false);
add_settings_item(vol_item, &volume->config.get());
}
Expand(item);
}
// add instances to the object, if it has those // add instances to the object, if it has those
if (model_object->instances.size()>1) if (model_object->instances.size()>1)
@ -2792,29 +2972,39 @@ void ObjectList::delete_instance_from_list(const size_t obj_idx, const size_t in
select_item([this, obj_idx, inst_idx]() { return m_objects_model->Delete(m_objects_model->GetItemByInstanceId(obj_idx, inst_idx)); }); select_item([this, obj_idx, inst_idx]() { return m_objects_model->Delete(m_objects_model->GetItemByInstanceId(obj_idx, inst_idx)); });
} }
void ObjectList::delete_from_model_and_list(const ItemType type, const int obj_idx, const int sub_obj_idx) void ObjectList::update_lock_icons_for_model()
{ {
if ( !(type&(itObject|itVolume|itInstance)) ) for (size_t obj_idx = 0; obj_idx < (*m_objects).size(); ++obj_idx)
return; if (!(*m_objects)[obj_idx]->is_cut())
m_objects_model->UpdateLockIcon(m_objects_model->GetItemById(int(obj_idx)), false);
take_snapshot(_(L("Delete Selected Item")));
if (type&itObject) {
del_object(obj_idx);
delete_object_from_list(obj_idx);
}
else {
del_subobject_from_object(obj_idx, sub_obj_idx, type);
type == itVolume ? delete_volume_from_list(obj_idx, sub_obj_idx) :
delete_instance_from_list(obj_idx, sub_obj_idx);
}
} }
void ObjectList::delete_from_model_and_list(const std::vector<ItemForDelete>& items_for_delete) bool ObjectList::delete_from_model_and_list(const ItemType type, const int obj_idx, const int sub_obj_idx)
{
if (type & (itObject | itVolume | itInstance)) {
if (type & itObject) {
bool was_cut = object(obj_idx)->is_cut();
if (del_object(obj_idx)) {
delete_object_from_list(obj_idx);
if (was_cut)
update_lock_icons_for_model();
return true;
}
return false;
}
if (del_subobject_from_object(obj_idx, sub_obj_idx, type)) {
type == itVolume ? delete_volume_from_list(obj_idx, sub_obj_idx) :
delete_instance_from_list(obj_idx, sub_obj_idx);
return true;
}
}
return false;
}
bool ObjectList::delete_from_model_and_list(const std::vector<ItemForDelete>& items_for_delete)
{ {
if (items_for_delete.empty()) if (items_for_delete.empty())
return; return false;
m_prevent_list_events = true; m_prevent_list_events = true;
@ -2823,14 +3013,18 @@ void ObjectList::delete_from_model_and_list(const std::vector<ItemForDelete>& it
if (!(item->type&(itObject | itVolume | itInstance))) if (!(item->type&(itObject | itVolume | itInstance)))
continue; continue;
if (item->type&itObject) { if (item->type&itObject) {
del_object(item->obj_idx); bool was_cut = object(item->obj_idx)->is_cut();
if (!del_object(item->obj_idx))
return false;// continue;
m_objects_model->Delete(m_objects_model->GetItemById(item->obj_idx)); m_objects_model->Delete(m_objects_model->GetItemById(item->obj_idx));
if (was_cut)
update_lock_icons_for_model();
} }
else { else {
if (!del_subobject_from_object(item->obj_idx, item->sub_obj_idx, item->type)) if (!del_subobject_from_object(item->obj_idx, item->sub_obj_idx, item->type))
continue; continue;
if (item->type&itVolume) { if (item->type&itVolume) {
m_objects_model->Delete(m_objects_model->GetItemByVolumeId(item->obj_idx, item->sub_obj_idx)); add_volumes_to_object_in_list(item->obj_idx);
ModelObject* obj = object(item->obj_idx); ModelObject* obj = object(item->obj_idx);
if (obj->volumes.size() == 1) { if (obj->volumes.size() == 1) {
wxDataViewItem parent = m_objects_model->GetItemById(item->obj_idx); wxDataViewItem parent = m_objects_model->GetItemById(item->obj_idx);
@ -2854,8 +3048,12 @@ void ObjectList::delete_from_model_and_list(const std::vector<ItemForDelete>& it
update_info_items(id); update_info_items(id);
} }
m_prevent_list_events = true; m_prevent_list_events = false;
if (modified_objects_ids.empty())
return false;
part_selection_changed(); part_selection_changed();
return true;
} }
void ObjectList::delete_all_objects_from_list() void ObjectList::delete_all_objects_from_list()
@ -2960,8 +3158,10 @@ void ObjectList::remove()
{ {
wxDataViewItem parent = m_objects_model->GetParent(item); wxDataViewItem parent = m_objects_model->GetParent(item);
ItemType type = m_objects_model->GetItemType(item); ItemType type = m_objects_model->GetItemType(item);
if (type & itObject) if (type & itObject) {
delete_from_model_and_list(itObject, m_objects_model->GetIdByItem(item), -1); if (!delete_from_model_and_list(itObject, m_objects_model->GetIdByItem(item), -1))
return item;
}
else { else {
if (type & (itLayer | itInstance)) { if (type & (itLayer | itInstance)) {
// In case there is just one layer or two instances and we delete it, del_subobject_item will // In case there is just one layer or two instances and we delete it, del_subobject_item will
@ -2971,7 +3171,8 @@ void ObjectList::remove()
parent = m_objects_model->GetTopParent(item); parent = m_objects_model->GetTopParent(item);
} }
del_subobject_item(item); if (!del_subobject_item(item))
return item;
} }
return parent; return parent;
@ -2997,6 +3198,8 @@ void ObjectList::remove()
if (m_objects_model->InvalidItem(item)) // item can be deleted for this moment (like last 2 Instances or Volumes) if (m_objects_model->InvalidItem(item)) // item can be deleted for this moment (like last 2 Instances or Volumes)
continue; continue;
parent = delete_item(item); parent = delete_item(item);
if (parent == item && m_objects_model->GetItemType(item) & itObject) // Object wasn't deleted
break;
} }
} }
@ -3189,7 +3392,7 @@ void ObjectList::add_layer_item(const t_layer_height_range& range,
const auto layer_item = m_objects_model->AddLayersChild(layers_item, const auto layer_item = m_objects_model->AddLayersChild(layers_item,
range, range,
config.opt_int("extruder"), extruder2str(config.opt_int("extruder")),
layer_idx); layer_idx);
add_settings_item(layer_item, &config); add_settings_item(layer_item, &config);
} }
@ -3283,6 +3486,24 @@ bool ObjectList::is_selected(const ItemType type) const
return false; return false;
} }
bool ObjectList::is_connectors_item_selected() const
{
const wxDataViewItem& item = GetSelection();
if (item)
return m_objects_model->GetItemType(item) == itInfo && m_objects_model->GetInfoItemType(item) == InfoItemType::CutConnectors;
return false;
}
bool ObjectList::is_connectors_item_selected(const wxDataViewItemArray& sels) const
{
for (auto item : sels)
if (m_objects_model->GetItemType(item) == itInfo && m_objects_model->GetInfoItemType(item) == InfoItemType::CutConnectors)
return true;
return false;
}
int ObjectList::get_selected_layers_range_idx() const int ObjectList::get_selected_layers_range_idx() const
{ {
const wxDataViewItem& item = GetSelection(); const wxDataViewItem& item = GetSelection();
@ -3409,11 +3630,18 @@ void ObjectList::update_selections()
else { else {
for (auto idx : selection.get_volume_idxs()) { for (auto idx : selection.get_volume_idxs()) {
const auto gl_vol = selection.get_volume(idx); const auto gl_vol = selection.get_volume(idx);
if (gl_vol->volume_idx() >= 0) if (gl_vol->volume_idx() >= 0) {
// Only add GLVolumes with non-negative volume_ids. GLVolumes with negative volume ids // Only add GLVolumes with non-negative volume_ids. GLVolumes with negative volume ids
// are not associated with ModelVolumes, but they are temporarily generated by the backend // are not associated with ModelVolumes, but they are temporarily generated by the backend
// (for example, SLA supports or SLA pad). // (for example, SLA supports or SLA pad).
sels.Add(m_objects_model->GetItemByVolumeId(gl_vol->object_idx(), gl_vol->volume_idx())); int obj_idx = gl_vol->object_idx();
int vol_idx = gl_vol->volume_idx();
assert(obj_idx >= 0 && vol_idx >= 0);
if (object(obj_idx)->volumes[vol_idx]->is_cut_connector())
sels.Add(m_objects_model->GetInfoItemByType(m_objects_model->GetItemById(obj_idx), InfoItemType::CutConnectors));
else
sels.Add(m_objects_model->GetItemByVolumeId(obj_idx, vol_idx));
}
} }
m_selection_mode = smVolume; } m_selection_mode = smVolume; }
} }
@ -3464,10 +3692,33 @@ void ObjectList::update_selections()
if (sels.size() == 0 || m_selection_mode & smSettings) if (sels.size() == 0 || m_selection_mode & smSettings)
m_selection_mode = smUndef; m_selection_mode = smUndef;
select_items(sels); if (fix_cut_selection(sels) || is_connectors_item_selected(sels)) {
m_prevent_list_events = true;
// Scroll selected Item in the middle of an object list // If some part is selected, unselect all items except of selected parts of the current object
ensure_current_item_visible(); UnselectAll();
SetSelections(sels);
m_prevent_list_events = false;
// update object selection on Plater
if (!m_prevent_canvas_selection_update)
update_selections_on_canvas();
// to update the toolbar and info sizer
if (!GetSelection() || m_objects_model->GetItemType(GetSelection()) == itObject || is_connectors_item_selected()) {
auto event = SimpleEvent(EVT_OBJ_LIST_OBJECT_SELECT);
event.SetEventObject(this);
wxPostEvent(this, event);
}
part_selection_changed();
}
else {
select_items(sels);
// Scroll selected Item in the middle of an object list
ensure_current_item_visible();
}
} }
void ObjectList::update_selections_on_canvas() void ObjectList::update_selections_on_canvas()
@ -3501,16 +3752,29 @@ void ObjectList::update_selections_on_canvas()
volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end()); volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end());
} }
else if (type == itInfo) { else if (type == itInfo) {
// When selecting an info item, select one instance of the if (m_objects_model->GetInfoItemType(item) == InfoItemType::CutConnectors) {
// respective object - a gizmo may want to be opened. mode = Selection::Volume;
int inst_idx = selection.get_instance_idx();
int scene_obj_idx = selection.get_object_idx(); // When selecting CutConnectors info item, select all object volumes, which are marked as a connector
mode = Selection::Instance; const ModelObject* obj = object(obj_idx);
// select first instance, unless an instance of the object is already selected for (unsigned int vol_idx = 0; vol_idx < obj->volumes.size(); vol_idx++)
if (scene_obj_idx == -1 || inst_idx == -1 || scene_obj_idx != obj_idx) if (obj->volumes[vol_idx]->is_cut_connector()) {
inst_idx = 0; std::vector<unsigned int> idxs = selection.get_volume_idxs_from_volume(obj_idx, std::max(instance_idx, 0), vol_idx);
std::vector<unsigned int> idxs = selection.get_volume_idxs_from_instance(obj_idx, inst_idx); volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end());
volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end()); }
}
else {
// When selecting an info item, select one instance of the
// respective object - a gizmo may want to be opened.
int inst_idx = selection.get_instance_idx();
int scene_obj_idx = selection.get_object_idx();
mode = Selection::Instance;
// select first instance, unless an instance of the object is already selected
if (scene_obj_idx == -1 || inst_idx == -1 || scene_obj_idx != obj_idx)
inst_idx = 0;
std::vector<unsigned int> idxs = selection.get_volume_idxs_from_instance(obj_idx, inst_idx);
volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end());
}
} }
else else
{ {
@ -3526,6 +3790,8 @@ void ObjectList::update_selections_on_canvas()
if (sel_cnt == 1) { if (sel_cnt == 1) {
wxDataViewItem item = GetSelection(); wxDataViewItem item = GetSelection();
if (m_objects_model->GetInfoItemType(item) == InfoItemType::CutConnectors)
selection.remove_all();
if (m_objects_model->GetItemType(item) & (itSettings | itInstanceRoot | itLayerRoot | itLayer)) if (m_objects_model->GetItemType(item) & (itSettings | itInstanceRoot | itLayerRoot | itLayer))
add_to_selection(m_objects_model->GetParent(item), selection, instance_idx, mode); add_to_selection(m_objects_model->GetParent(item), selection, instance_idx, mode);
else else
@ -3796,6 +4062,53 @@ void ObjectList::fix_multiselection_conflicts()
m_prevent_list_events = false; m_prevent_list_events = false;
} }
bool ObjectList::fix_cut_selection(wxDataViewItemArray& sels)
{
if (wxGetApp().plater()->canvas3D()->get_gizmos_manager().get_current_type() == GLGizmosManager::Scale) {
for (const auto& item : sels) {
if (m_objects_model->GetItemType(item) & (itInstance | itObject) ||
(m_objects_model->GetItemType(item) & itSettings &&
m_objects_model->GetItemType(m_objects_model->GetParent(item)) & itObject)) {
bool is_instance_selection = m_objects_model->GetItemType(item) & itInstance;
int object_idx = m_objects_model->GetObjectIdByItem(item);
int inst_idx = is_instance_selection ? m_objects_model->GetInstanceIdByItem(item) : 0;
if (auto obj = object(object_idx); obj->is_cut()) {
sels.Clear();
auto cut_id = obj->cut_id;
int objects_cnt = int((*m_objects).size());
for (int obj_idx = 0; obj_idx < objects_cnt; ++obj_idx) {
auto object = (*m_objects)[obj_idx];
if (object->is_cut() && object->cut_id.has_same_id(cut_id))
sels.Add(is_instance_selection ? m_objects_model->GetItemByInstanceId(obj_idx, inst_idx) : m_objects_model->GetItemById(obj_idx));
}
return true;
}
}
}
}
return false;
}
void ObjectList::fix_cut_selection()
{
wxDataViewItemArray sels;
GetSelections(sels);
if (fix_cut_selection(sels)) {
m_prevent_list_events = true;
// If some part is selected, unselect all items except of selected parts of the current object
UnselectAll();
SetSelections(sels);
m_prevent_list_events = false;
}
}
ModelVolume* ObjectList::get_selected_model_volume() ModelVolume* ObjectList::get_selected_model_volume()
{ {
wxDataViewItem item = GetSelection(); wxDataViewItem item = GetSelection();
@ -3826,10 +4139,11 @@ void ObjectList::change_part_type()
if (obj_idx < 0) return; if (obj_idx < 0) return;
const ModelVolumeType type = volume->type(); const ModelVolumeType type = volume->type();
const ModelObject* obj = object(obj_idx);
if (type == ModelVolumeType::MODEL_PART) if (type == ModelVolumeType::MODEL_PART)
{ {
int model_part_cnt = 0; int model_part_cnt = 0;
for (auto vol : (*m_objects)[obj_idx]->volumes) { for (auto vol : obj->volumes) {
if (vol->type() == ModelVolumeType::MODEL_PART) if (vol->type() == ModelVolumeType::MODEL_PART)
++model_part_cnt; ++model_part_cnt;
} }
@ -3840,9 +4154,18 @@ void ObjectList::change_part_type()
} }
} }
const wxString names[] = { _L("Part"), _L("Negative Volume"), _L("Modifier"), _L("Support Blocker"), _L("Support Enforcer") }; const bool is_cut_object = obj->is_cut();
auto new_type = ModelVolumeType(wxGetApp().GetSingleChoiceIndex(_L("Type:"), _L("Select type of part"), wxArrayString(5, names), int(type)));
wxArrayString names;
names.Alloc(is_cut_object ? 3 : 5);
if (!is_cut_object)
for (const wxString& type : { _L("Part"), _L("Negative Volume") })
names.Add(type);
for (const wxString& type : { _L("Modifier"), _L("Support Blocker"), _L("Support Enforcer") } )
names.Add(type);
const int type_shift = is_cut_object ? 2 : 0;
auto new_type = ModelVolumeType(type_shift + wxGetApp().GetSingleChoiceIndex(_L("Type:"), _L("Select type of part"), names, int(type) - type_shift));
if (new_type == type || new_type == ModelVolumeType::INVALID) if (new_type == type || new_type == ModelVolumeType::INVALID)
return; return;
@ -4362,33 +4685,14 @@ void ObjectList::set_extruder_for_selected_items(const int extruder) const
wxGetApp().plater()->update(); wxGetApp().plater()->update();
} }
wxDataViewItemArray ObjectList::reorder_volumes_and_get_selection(int obj_idx, std::function<bool(const ModelVolume*)> add_to_selection/* = nullptr*/) wxDataViewItemArray ObjectList::reorder_volumes_and_get_selection(size_t obj_idx, std::function<bool(const ModelVolume*)> add_to_selection/* = nullptr*/)
{ {
wxDataViewItemArray items; (*m_objects)[obj_idx]->sort_volumes(wxGetApp().app_config->get("order_volumes") == "1");
ModelObject* object = (*m_objects)[obj_idx]; wxDataViewItemArray items = add_volumes_to_object_in_list(obj_idx, std::move(add_to_selection));
if (object->volumes.size() <= 1)
return items;
object->sort_volumes(wxGetApp().app_config->get("order_volumes") == "1"); changed_object(int(obj_idx));
wxDataViewItem object_item = m_objects_model->GetItemById(obj_idx);
m_objects_model->DeleteVolumeChildren(object_item);
for (const ModelVolume* volume : object->volumes) {
wxDataViewItem vol_item = m_objects_model->AddVolumeChild(object_item, from_u8(volume->name),
volume->type(),
get_warning_icon_name(volume->mesh().stats()),
volume->config.has("extruder") ? volume->config.extruder() : 0,
false);
// add settings to the part, if it has those
add_settings_item(vol_item, &volume->config.get());
if (add_to_selection && add_to_selection(volume))
items.Add(vol_item);
}
changed_object(obj_idx);
return items; return items;
} }

View File

@ -247,7 +247,7 @@ public:
void add_category_to_settings_from_frequent(const std::vector<std::string>& category_options, wxDataViewItem item); void add_category_to_settings_from_frequent(const std::vector<std::string>& category_options, wxDataViewItem item);
void show_settings(const wxDataViewItem settings_item); void show_settings(const wxDataViewItem settings_item);
bool is_instance_or_object_selected(); bool is_instance_or_object_selected();
bool is_selected_object_cut();
void load_subobject(ModelVolumeType type, bool from_galery = false); void load_subobject(ModelVolumeType type, bool from_galery = false);
// ! ysFIXME - delete commented code after testing and rename "load_modifier" to something common // ! ysFIXME - delete commented code after testing and rename "load_modifier" to something common
//void load_part(ModelObject& model_object, std::vector<ModelVolume*>& added_volumes, ModelVolumeType type, bool from_galery = false); //void load_part(ModelObject& model_object, std::vector<ModelVolume*>& added_volumes, ModelVolumeType type, bool from_galery = false);
@ -257,8 +257,8 @@ public:
void load_shape_object_from_gallery(); void load_shape_object_from_gallery();
void load_shape_object_from_gallery(const wxArrayString& input_files); void load_shape_object_from_gallery(const wxArrayString& input_files);
void load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center = true); void load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center = true);
void del_object(const int obj_idx); bool del_object(const int obj_idx);
void del_subobject_item(wxDataViewItem& item); bool del_subobject_item(wxDataViewItem& item);
void del_settings_from_config(const wxDataViewItem& parent_item); void del_settings_from_config(const wxDataViewItem& parent_item);
void del_instances_from_object(const int obj_idx); void del_instances_from_object(const int obj_idx);
void del_layer_from_object(const int obj_idx, const t_layer_height_range& layer_range); void del_layer_from_object(const int obj_idx, const t_layer_height_range& layer_range);
@ -277,6 +277,9 @@ public:
bool is_splittable(bool to_objects); bool is_splittable(bool to_objects);
bool selected_instances_of_same_object(); bool selected_instances_of_same_object();
bool can_split_instances(); bool can_split_instances();
bool has_selected_cut_object() const;
void invalidate_cut_info_for_selection();
void invalidate_cut_info_for_object(size_t obj_idx);
bool can_merge_to_multipart_object() const; bool can_merge_to_multipart_object() const;
bool can_merge_to_single_object() const; bool can_merge_to_single_object() const;
@ -288,6 +291,9 @@ public:
void changed_object(const int obj_idx = -1) const; void changed_object(const int obj_idx = -1) const;
void part_selection_changed(); void part_selection_changed();
// Add object's volumes to the list
// Return selected items, if add_to_selection is defined
wxDataViewItemArray add_volumes_to_object_in_list(size_t obj_idx, std::function<bool(const ModelVolume*)> add_to_selection = nullptr);
// Add object to the list // Add object to the list
void add_object_to_list(size_t obj_idx, bool call_selection_changed = true); void add_object_to_list(size_t obj_idx, bool call_selection_changed = true);
// Delete object from the list // Delete object from the list
@ -295,8 +301,9 @@ public:
void delete_object_from_list(const size_t obj_idx); void delete_object_from_list(const size_t obj_idx);
void delete_volume_from_list(const size_t obj_idx, const size_t vol_idx); void delete_volume_from_list(const size_t obj_idx, const size_t vol_idx);
void delete_instance_from_list(const size_t obj_idx, const size_t inst_idx); void delete_instance_from_list(const size_t obj_idx, const size_t inst_idx);
void delete_from_model_and_list(const ItemType type, const int obj_idx, const int sub_obj_idx); void update_lock_icons_for_model();
void delete_from_model_and_list(const std::vector<ItemForDelete>& items_for_delete); bool delete_from_model_and_list(const ItemType type, const int obj_idx, const int sub_obj_idx);
bool delete_from_model_and_list(const std::vector<ItemForDelete>& items_for_delete);
// Delete all objects from the list // Delete all objects from the list
void delete_all_objects_from_list(); void delete_all_objects_from_list();
// Increase instances count // Increase instances count
@ -339,6 +346,8 @@ public:
void init_objects(); void init_objects();
bool multiple_selection() const ; bool multiple_selection() const ;
bool is_selected(const ItemType type) const; bool is_selected(const ItemType type) const;
bool is_connectors_item_selected() const;
bool is_connectors_item_selected(const wxDataViewItemArray& sels) const;
int get_selected_layers_range_idx() const; int get_selected_layers_range_idx() const;
void set_selected_layers_range_idx(const int range_idx) { m_selected_layers_range_idx = range_idx; } void set_selected_layers_range_idx(const int range_idx) { m_selected_layers_range_idx = range_idx; }
void set_selection_mode(SELECTION_MODE mode) { m_selection_mode = mode; } void set_selection_mode(SELECTION_MODE mode) { m_selection_mode = mode; }
@ -353,6 +362,9 @@ public:
bool check_last_selection(wxString& msg_str); bool check_last_selection(wxString& msg_str);
// correct current selections to avoid of the possible conflicts // correct current selections to avoid of the possible conflicts
void fix_multiselection_conflicts(); void fix_multiselection_conflicts();
// correct selection in respect to the cut_id if any exists
void fix_cut_selection();
bool fix_cut_selection(wxDataViewItemArray& sels);
ModelVolume* get_selected_model_volume(); ModelVolume* get_selected_model_volume();
void change_part_type(); void change_part_type();
@ -388,7 +400,7 @@ public:
void toggle_printable_state(); void toggle_printable_state();
void set_extruder_for_selected_items(const int extruder) const ; void set_extruder_for_selected_items(const int extruder) const ;
wxDataViewItemArray reorder_volumes_and_get_selection(int obj_idx, std::function<bool(const ModelVolume*)> add_to_selection = nullptr); wxDataViewItemArray reorder_volumes_and_get_selection(size_t obj_idx, std::function<bool(const ModelVolume*)> add_to_selection = nullptr);
void apply_volumes_order(); void apply_volumes_order();
bool has_paint_on_segmentation(); bool has_paint_on_segmentation();

View File

@ -576,6 +576,35 @@ void ObjectManipulation::UpdateAndShow(const bool show)
OG_Settings::UpdateAndShow(show); OG_Settings::UpdateAndShow(show);
} }
void ObjectManipulation::Enable(const bool enadle)
{
for (auto editor : m_editors)
editor->Enable(enadle);
for (wxWindow* win : std::initializer_list<wxWindow*>{ m_reset_scale_button, m_reset_rotation_button, m_drop_to_bed_button, m_check_inch, m_lock_bnt
#if ENABLE_WORLD_COORDINATE
,m_reset_skew_button
#endif // ENABLE_WORLD_COORDINATE
})
win->Enable(enadle);
}
void ObjectManipulation::DisableScale()
{
for (auto editor : m_editors)
editor->Enable(editor->has_opt_key("scale") || editor->has_opt_key("size") ? false : true);
for (wxWindow* win : std::initializer_list<wxWindow*>{ m_reset_scale_button, m_lock_bnt
#if ENABLE_WORLD_COORDINATE
,m_reset_skew_button
#endif // ENABLE_WORLD_COORDINATE
})
win->Enable(false);
}
void ObjectManipulation::DisableUnuniformScale()
{
m_lock_bnt->Enable(false);
}
void ObjectManipulation::update_ui_from_settings() void ObjectManipulation::update_ui_from_settings()
{ {
if (m_imperial_units != (wxGetApp().app_config->get("use_inches") == "1")) { if (m_imperial_units != (wxGetApp().app_config->get("use_inches") == "1")) {
@ -734,7 +763,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection)
#endif // ENABLE_WORLD_COORDINATE #endif // ENABLE_WORLD_COORDINATE
m_new_enabled = true; m_new_enabled = true;
} }
else if (obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot)) { else if (obj_list->is_connectors_item_selected() || obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot)) {
reset_settings_value(); reset_settings_value();
m_new_move_label_string = L("Translate"); m_new_move_label_string = L("Translate");
m_new_rotate_label_string = L("Rotate"); m_new_rotate_label_string = L("Rotate");

View File

@ -64,6 +64,8 @@ public:
const std::string& get_full_opt_name() const { return m_full_opt_name; } const std::string& get_full_opt_name() const { return m_full_opt_name; }
#endif // ENABLE_WORLD_COORDINATE #endif // ENABLE_WORLD_COORDINATE
bool has_opt_key(const std::string& key) { return m_opt_key == key; }
private: private:
double get_value(); double get_value();
}; };
@ -197,6 +199,10 @@ public:
void Show(const bool show) override; void Show(const bool show) override;
bool IsShown() override; bool IsShown() override;
void UpdateAndShow(const bool show) override; void UpdateAndShow(const bool show) override;
void Enable(const bool enadle = true);
void Disable() { Enable(false); }
void DisableScale();
void DisableUnuniformScale();
void update_ui_from_settings(); void update_ui_from_settings();
bool use_colors() { return m_use_colors; } bool use_colors() { return m_use_colors; }

View File

@ -310,8 +310,8 @@ void GLGizmoBase::set_hover_id(int id)
assert(!m_dragging); assert(!m_dragging);
// allow empty grabbers when not using grabbers but use hover_id - flatten, rotate // allow empty grabbers when not using grabbers but use hover_id - flatten, rotate
if (!m_grabbers.empty() && id >= (int) m_grabbers.size()) // if (!m_grabbers.empty() && id >= (int) m_grabbers.size())
return; // return;
m_hover_id = id; m_hover_id = id;
on_set_hover_id(); on_set_hover_id();
@ -404,14 +404,14 @@ bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) {
if (mouse_event.LeftDown()) { if (mouse_event.LeftDown()) {
Selection &selection = m_parent.get_selection(); Selection &selection = m_parent.get_selection();
if (!selection.is_empty() && m_hover_id != -1 && if (!selection.is_empty() && m_hover_id != -1 /* &&
(m_grabbers.empty() || m_hover_id < static_cast<int>(m_grabbers.size()))) { (m_grabbers.empty() || m_hover_id < static_cast<int>(m_grabbers.size()))*/) {
selection.setup_cache(); selection.setup_cache();
m_dragging = true; m_dragging = true;
for (auto &grabber : m_grabbers) grabber.dragging = false; for (auto &grabber : m_grabbers) grabber.dragging = false;
if (!m_grabbers.empty() && m_hover_id < int(m_grabbers.size())) // if (!m_grabbers.empty() && m_hover_id < int(m_grabbers.size()))
m_grabbers[m_hover_id].dragging = true; // m_grabbers[m_hover_id].dragging = true;
on_start_dragging(); on_start_dragging();

View File

@ -212,6 +212,9 @@ public:
void unregister_raycasters_for_picking() { unregister_grabbers_for_picking(); on_unregister_raycasters_for_picking(); } void unregister_raycasters_for_picking() { unregister_grabbers_for_picking(); on_unregister_raycasters_for_picking(); }
#endif // ENABLE_RAYCAST_PICKING #endif // ENABLE_RAYCAST_PICKING
virtual bool is_in_editing_mode() const { return false; }
virtual bool is_selection_rectangle_dragging() const { return false; }
protected: protected:
virtual bool on_init() = 0; virtual bool on_init() = 0;
virtual void on_load(cereal::BinaryInputArchive& ar) {} virtual void on_load(cereal::BinaryInputArchive& ar) {}

File diff suppressed because it is too large Load Diff

View File

@ -2,55 +2,161 @@
#define slic3r_GLGizmoCut_hpp_ #define slic3r_GLGizmoCut_hpp_
#include "GLGizmoBase.hpp" #include "GLGizmoBase.hpp"
#include "slic3r/GUI/GLSelectionRectangle.hpp"
#include "slic3r/GUI/GLModel.hpp" #include "slic3r/GUI/GLModel.hpp"
#include "libslic3r/TriangleMesh.hpp" #include "libslic3r/TriangleMesh.hpp"
#include "libslic3r/ObjectID.hpp" #include "libslic3r/Model.hpp"
namespace Slic3r { namespace Slic3r {
enum class CutConnectorType : int;
class ModelVolume;
struct CutConnectorAttributes;
namespace GUI { namespace GUI {
class Selection; class Selection;
class GLGizmoCut : public GLGizmoBase enum class SLAGizmoEventType : unsigned char;
{
static const double Offset; class GLGizmoCut3D : public GLGizmoBase
static const double Margin; {
Transform3d m_rotation_m{ Transform3d::Identity() };
double m_snap_step{ 1.0 };
int m_connectors_group_id;
// archived values
Vec3d m_ar_plane_center { Vec3d::Zero() };
Vec3d m_plane_center{ Vec3d::Zero() };
// data to check position of the cut palne center on gizmo activation
Vec3d m_min_pos{ Vec3d::Zero() };
Vec3d m_max_pos{ Vec3d::Zero() };
Vec3d m_bb_center{ Vec3d::Zero() };
Vec3d m_center_offset{ Vec3d::Zero() };
// values from RotationGizmo
double m_radius{ 0.0 };
double m_grabber_radius{ 0.0 };
double m_grabber_connection_len{ 0.0 };
double m_snap_coarse_in_radius{ 0.0 };
double m_snap_coarse_out_radius{ 0.0 };
double m_snap_fine_in_radius{ 0.0 };
double m_snap_fine_out_radius{ 0.0 };
// dragging angel in hovered axes
Transform3d m_start_dragging_m{ Transform3d::Identity() };
double m_angle{ 0.0 };
TriangleMesh m_connector_mesh;
// workaround for using of the clipping plane normal
Vec3d m_clp_normal{ Vec3d::Ones() };
Vec3d m_line_beg{ Vec3d::Zero() };
Vec3d m_line_end{ Vec3d::Zero() };
Vec2d m_ldown_mouse_position{ Vec2d::Zero() };
double m_cut_z{ 0.0 };
double m_max_z{ 0.0 };
double m_start_z{ 0.0 };
Vec3d m_drag_pos;
Vec3d m_drag_center;
bool m_keep_upper{ true };
bool m_keep_lower{ true };
bool m_rotate_lower{ false };
#if ENABLE_LEGACY_OPENGL_REMOVAL
GLModel m_plane; GLModel m_plane;
GLModel m_grabber_connection; GLModel m_grabber_connection;
Vec3d m_old_center; GLModel m_cut_line;
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
struct CutContours PickingModel m_sphere;
PickingModel m_cone;
std::map<CutConnectorAttributes, PickingModel> m_shapes;
std::vector<std::shared_ptr<SceneRaycasterItem>> m_raycasters;
GLModel m_circle;
GLModel m_scale;
GLModel m_snap_radii;
GLModel m_reference_radius;
GLModel m_angle_arc;
Vec3d m_old_center;
struct InvalidConnectorsStatistics
{ {
TriangleMesh mesh; unsigned int outside_cut_contour;
GLModel contours; unsigned int outside_bb;
double cut_z{ 0.0 }; bool is_overlap;
Vec3d position{ Vec3d::Zero() };
Vec3d shift{ Vec3d::Zero() }; void invalidate() {
ObjectID object_id; outside_cut_contour = 0;
int instance_idx{ -1 }; outside_bb = 0;
std::vector<ObjectID> volumes_idxs; is_overlap = false;
std::vector<Transform3d> volumes_trafos; }
} m_info_stats;
bool m_keep_upper{ true };
bool m_keep_lower{ true };
bool m_place_on_cut_upper{ true };
bool m_place_on_cut_lower{ false };
bool m_rotate_upper{ false };
bool m_rotate_lower{ false };
bool m_hide_cut_plane{ false };
bool m_connectors_editing{ false };
bool m_cut_plane_as_circle{ false };
float m_connector_depth_ratio{ 3.f };
float m_connector_size{ 2.5f };
float m_connector_depth_ratio_tolerance{ 0.1f };
float m_connector_size_tolerance{ 0.f };
float m_label_width{ 150.0 };
float m_control_width{ 200.0 };
bool m_imperial_units{ false };
bool force_update_clipper_on_render{false};
mutable std::vector<bool> m_selected; // which pins are currently selected
int m_selected_count{ 0 };
GLSelectionRectangle m_selection_rectangle;
bool m_has_invalid_connector{ false };
bool m_show_shortcuts{ false };
std::vector<std::pair<wxString, wxString>> m_shortcuts;
enum class CutMode {
cutPlanar
, cutGrig
//,cutRadial
//,cutModular
}; };
CutContours m_cut_contours; enum class CutConnectorMode {
Auto
, Manual
};
std::vector<std::string> m_modes;
size_t m_mode{ size_t(CutMode::cutPlanar) };
std::vector<std::string> m_connector_modes;
CutConnectorMode m_connector_mode{ CutConnectorMode::Manual };
std::vector<std::string> m_connector_types;
CutConnectorType m_connector_type;
std::vector<std::string> m_connector_styles;
size_t m_connector_style;
std::vector<std::string> m_connector_shapes;
size_t m_connector_shape_id;
std::vector<std::string> m_axis_names;
public: public:
GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
double get_cut_z() const { return m_cut_z; }
void set_cut_z(double cut_z);
std::string get_tooltip() const override; std::string get_tooltip() const override;
bool unproject_on_cut_plane(const Vec2d& mouse_pos, std::pair<Vec3d, Vec3d>& pos_and_normal, Vec3d& pos_world);
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; }
bool is_selection_rectangle_dragging() const override { return m_selection_rectangle.is_dragging(); }
/// <summary> /// <summary>
/// Drag of plane /// Drag of plane
@ -59,29 +165,105 @@ public:
/// <returns>Return True when use the information otherwise False.</returns> /// <returns>Return True when use the information otherwise False.</returns>
bool on_mouse(const wxMouseEvent &mouse_event) override; bool on_mouse(const wxMouseEvent &mouse_event) override;
void shift_cut_z(double delta);
void rotate_vec3d_around_plane_center(Vec3d&vec);
void put_connectors_on_cut_plane(const Vec3d& cp_normal, double cp_offset);
void update_clipper();
void update_clipper_on_render();
void set_connectors_editing() { m_connectors_editing = true; }
void invalidate_cut_plane();
BoundingBoxf3 bounding_box() const;
BoundingBoxf3 transformed_bounding_box(const Vec3d& plane_center, bool revert_move = false) const;
protected: protected:
virtual bool on_init() override; bool on_init() override;
virtual void on_load(cereal::BinaryInputArchive& ar) override { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); } void on_load(cereal::BinaryInputArchive&ar) override;
virtual void on_save(cereal::BinaryOutputArchive& ar) const override { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); } void on_save(cereal::BinaryOutputArchive&ar) const override;
virtual std::string on_get_name() const override; std::string on_get_name() const override;
virtual void on_set_state() override; void on_set_state() override;
virtual bool on_is_activable() const override; CommonGizmosDataID on_get_requirements() const override;
virtual void on_start_dragging() override; void on_set_hover_id() override;
virtual void on_dragging(const UpdateData& data) override; bool on_is_activable() const override;
virtual void on_render() override; bool on_is_selectable() const override;
#if ENABLE_RAYCAST_PICKING Vec3d mouse_position_in_local_plane(Axis axis, const Linef3&mouse_ray) const;
void dragging_grabber_z(const GLGizmoBase::UpdateData &data);
void dragging_grabber_xy(const GLGizmoBase::UpdateData &data);
void dragging_connector(const GLGizmoBase::UpdateData &data);
void on_dragging(const UpdateData&data) override;
void on_start_dragging() override;
void on_stop_dragging() override;
void on_render() override;
void render_debug_input_window();
void adjust_window_position(float x, float y, float bottom_limit);
void unselect_all_connectors();
void select_all_connectors();
void render_shortcuts();
void apply_selected_connectors(std::function<void(size_t idx)> apply_fn);
void render_connectors_input_window(CutConnectors &connectors);
void render_build_size();
void reset_cut_plane();
void set_connectors_editing(bool connectors_editing);
void render_cut_plane_input_window(CutConnectors &connectors);
void init_input_window_data(CutConnectors &connectors);
void render_input_window_warning() const;
bool add_connector(CutConnectors&connectors, const Vec2d&mouse_position);
bool delete_selected_connectors(CutConnectors&connectors);
void select_connector(int idx, bool select);
bool is_selection_changed(bool alt_down, bool shift_down);
void process_selection_rectangle(CutConnectors &connectors);
virtual void on_register_raycasters_for_picking() override; virtual void on_register_raycasters_for_picking() override;
virtual void on_unregister_raycasters_for_picking() override; virtual void on_unregister_raycasters_for_picking() override;
#else void update_raycasters_for_picking();
virtual void on_render_for_picking() override; void set_volumes_picking_state(bool state);
#endif // ENABLE_RAYCAST_PICKING void update_raycasters_for_picking_transform();
virtual void on_render_input_window(float x, float y, float bottom_limit) override;
void on_render_input_window(float x, float y, float bottom_limit) override;
bool wants_enter_leave_snapshots() const override { return true; }
std::string get_gizmo_entering_text() const override { return _u8L("Entering Cut gizmo"); }
std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Cut gizmo"); }
std::string get_action_snapshot_name() override { return _u8L("Cut gizmo editing"); }
private: private:
void perform_cut(const Selection& selection); void set_center(const Vec3d& center);
double calc_projection(const Linef3& mouse_ray) const; bool render_combo(const std::string& label, const std::vector<std::string>& lines, size_t& selection_idx);
BoundingBoxf3 bounding_box() const; bool render_double_input(const std::string& label, double& value_in);
void update_contours(); bool render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in);
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;
bool render_connect_type_radio_button(CutConnectorType type);
Transform3d get_volume_transformation(const ModelVolume* volume) const;
bool is_conflict_for_connector(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos);
void render_connectors();
bool can_perform_cut() const;
void apply_connectors_in_model(ModelObject* mo, bool &create_dowels_as_separate_object);
bool cut_line_processing() const;
void discard_cut_line_processing();
void render_cut_plane();
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(Axis axis, const ColorRGBA& color);
void render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix);
void render_cut_plane_grabbers();
void render_cut_line();
void perform_cut(const Selection&selection);
void set_center_pos(const Vec3d&center_pos, bool force = false);
bool update_bb();
void init_picking_models();
void init_rendering_items();
void render_clipper_cut();
void clear_selection();
void reset_connectors();
void init_connector_shapes();
void update_connector_shape();
void validate_connector_settings();
bool process_cut_line(SLAGizmoEventType action, const Vec2d& mouse_position);
}; };
} // namespace GUI } // namespace GUI

View File

@ -308,7 +308,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
else { else {
if (m_imgui->button(m_desc.at("reset_direction"))) { if (m_imgui->button(m_desc.at("reset_direction"))) {
wxGetApp().CallAfter([this](){ wxGetApp().CallAfter([this](){
m_c->object_clipper()->set_position(-1., false); m_c->object_clipper()->set_position_by_ratio(-1., false);
}); });
} }
} }
@ -317,7 +317,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
ImGui::SameLine(sliders_left_width); ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, _L("Ctrl + Mouse wheel"))) if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, _L("Ctrl + Mouse wheel")))
m_c->object_clipper()->set_position(clp_dist, true); m_c->object_clipper()->set_position_by_ratio(clp_dist, true);
ImGui::Separator(); ImGui::Separator();
if (m_imgui->button(m_desc.at("remove_all"))) { if (m_imgui->button(m_desc.at("remove_all"))) {

View File

@ -478,19 +478,19 @@ bool GLGizmoHollow::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_pos
if (action == SLAGizmoEventType::MouseWheelUp && control_down) { if (action == SLAGizmoEventType::MouseWheelUp && control_down) {
double pos = m_c->object_clipper()->get_position(); double pos = m_c->object_clipper()->get_position();
pos = std::min(1., pos + 0.01); pos = std::min(1., pos + 0.01);
m_c->object_clipper()->set_position(pos, true); m_c->object_clipper()->set_position_by_ratio(pos, true);
return true; return true;
} }
if (action == SLAGizmoEventType::MouseWheelDown && control_down) { if (action == SLAGizmoEventType::MouseWheelDown && control_down) {
double pos = m_c->object_clipper()->get_position(); double pos = m_c->object_clipper()->get_position();
pos = std::max(0., pos - 0.01); pos = std::max(0., pos - 0.01);
m_c->object_clipper()->set_position(pos, true); m_c->object_clipper()->set_position_by_ratio(pos, true);
return true; return true;
} }
if (action == SLAGizmoEventType::ResetClippingPlane) { if (action == SLAGizmoEventType::ResetClippingPlane) {
m_c->object_clipper()->set_position(-1., false); m_c->object_clipper()->set_position_by_ratio(-1., false);
return true; return true;
} }
@ -885,7 +885,7 @@ RENDER_AGAIN:
else { else {
if (m_imgui->button(m_desc.at("reset_direction"))) { if (m_imgui->button(m_desc.at("reset_direction"))) {
wxGetApp().CallAfter([this](){ wxGetApp().CallAfter([this](){
m_c->object_clipper()->set_position(-1., false); m_c->object_clipper()->set_position_by_ratio(-1., false);
}); });
} }
} }
@ -894,7 +894,7 @@ RENDER_AGAIN:
ImGui::PushItemWidth(window_width - settings_sliders_left); ImGui::PushItemWidth(window_width - settings_sliders_left);
float clp_dist = m_c->object_clipper()->get_position(); float clp_dist = m_c->object_clipper()->get_position();
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f"))
m_c->object_clipper()->set_position(clp_dist, true); m_c->object_clipper()->set_position_by_ratio(clp_dist, true);
// make sure supports are shown/hidden as appropriate // make sure supports are shown/hidden as appropriate
bool show_sups = m_c->instances_hider()->are_supports_shown(); bool show_sups = m_c->instances_hider()->are_supports_shown();

View File

@ -31,7 +31,7 @@ public:
void data_changed() override; void data_changed() override;
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
void delete_selected_points(); void delete_selected_points();
bool is_selection_rectangle_dragging() const { bool is_selection_rectangle_dragging() const override {
return m_selection_rectangle.is_dragging(); return m_selection_rectangle.is_dragging();
} }

View File

@ -476,7 +476,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
m_imgui->text(m_desc.at("clipping_of_view")); m_imgui->text(m_desc.at("clipping_of_view"));
} else { } else {
if (m_imgui->button(m_desc.at("reset_direction"))) { if (m_imgui->button(m_desc.at("reset_direction"))) {
wxGetApp().CallAfter([this]() { m_c->object_clipper()->set_position(-1., false); }); wxGetApp().CallAfter([this]() { m_c->object_clipper()->set_position_by_ratio(-1., false); });
} }
} }
@ -484,7 +484,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
ImGui::SameLine(sliders_left_width); ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, _L("Ctrl + Mouse wheel"))) if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, _L("Ctrl + Mouse wheel")))
m_c->object_clipper()->set_position(clp_dist, true); m_c->object_clipper()->set_position_by_ratio(clp_dist, true);
ImGui::Separator(); ImGui::Separator();
if (m_imgui->button(m_desc.at("remove_all"))) { if (m_imgui->button(m_desc.at("remove_all"))) {

View File

@ -8,6 +8,7 @@
#if ENABLE_LEGACY_OPENGL_REMOVAL #if ENABLE_LEGACY_OPENGL_REMOVAL
#include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/Plater.hpp"
#endif // ENABLE_LEGACY_OPENGL_REMOVAL #endif // ENABLE_LEGACY_OPENGL_REMOVAL
#include "libslic3r/Model.hpp"
#include <GL/glew.h> #include <GL/glew.h>
@ -79,7 +80,8 @@ std::string GLGizmoMove3D::on_get_name() const
bool GLGizmoMove3D::on_is_activable() const bool GLGizmoMove3D::on_is_activable() const
{ {
return !m_parent.get_selection().is_empty(); const Selection& selection = m_parent.get_selection();
return !selection.is_any_cut_volume() && !selection.is_any_connector() && !selection.is_empty();
} }
void GLGizmoMove3D::on_start_dragging() void GLGizmoMove3D::on_start_dragging()

View File

@ -537,7 +537,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
pos = action == SLAGizmoEventType::MouseWheelDown pos = action == SLAGizmoEventType::MouseWheelDown
? std::max(0., pos - 0.01) ? std::max(0., pos - 0.01)
: std::min(1., pos + 0.01); : std::min(1., pos + 0.01);
m_c->object_clipper()->set_position(pos, true); m_c->object_clipper()->set_position_by_ratio(pos, true);
return true; return true;
} }
else if (alt_down) { else if (alt_down) {
@ -573,7 +573,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
} }
if (action == SLAGizmoEventType::ResetClippingPlane) { if (action == SLAGizmoEventType::ResetClippingPlane) {
m_c->object_clipper()->set_position(-1., false); m_c->object_clipper()->set_position_by_ratio(-1., false);
return true; return true;
} }

View File

@ -12,6 +12,7 @@
#include "slic3r/GUI/Jobs/RotoptimizeJob.hpp" #include "slic3r/GUI/Jobs/RotoptimizeJob.hpp"
#include "libslic3r/PresetBundle.hpp" #include "libslic3r/PresetBundle.hpp"
#include "libslic3r/Model.hpp"
#include <GL/glew.h> #include <GL/glew.h>
@ -867,7 +868,8 @@ std::string GLGizmoRotate3D::on_get_name() const
bool GLGizmoRotate3D::on_is_activable() const bool GLGizmoRotate3D::on_is_activable() const
{ {
return !m_parent.get_selection().is_empty(); const Selection& selection = m_parent.get_selection();
return !selection.is_any_cut_volume() && !selection.is_any_connector() && !selection.is_empty();
} }
void GLGizmoRotate3D::on_start_dragging() void GLGizmoRotate3D::on_start_dragging()

View File

@ -8,6 +8,7 @@
#if ENABLE_LEGACY_OPENGL_REMOVAL #if ENABLE_LEGACY_OPENGL_REMOVAL
#include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/Plater.hpp"
#endif // ENABLE_LEGACY_OPENGL_REMOVAL #endif // ENABLE_LEGACY_OPENGL_REMOVAL
#include "libslic3r/Model.hpp"
#include <GL/glew.h> #include <GL/glew.h>
@ -103,6 +104,12 @@ bool GLGizmoScale3D::on_mouse(const wxMouseEvent &mouse_event)
return use_grabbers(mouse_event); return use_grabbers(mouse_event);
} }
void GLGizmoScale3D::enable_ununiversal_scale(bool enable)
{
for (unsigned int i = 0; i < 6; ++i)
m_grabbers[i].enabled = enable;
}
void GLGizmoScale3D::data_changed() void GLGizmoScale3D::data_changed()
{ {
#if ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE
@ -161,7 +168,7 @@ std::string GLGizmoScale3D::on_get_name() const
bool GLGizmoScale3D::on_is_activable() const bool GLGizmoScale3D::on_is_activable() const
{ {
const Selection& selection = m_parent.get_selection(); const Selection& selection = m_parent.get_selection();
return !selection.is_empty() && !selection.is_wipe_tower(); return !selection.is_any_cut_volume() && !selection.is_any_connector() && !selection.is_empty() && !selection.is_wipe_tower();
} }
void GLGizmoScale3D::on_start_dragging() void GLGizmoScale3D::on_start_dragging()
@ -448,7 +455,7 @@ void GLGizmoScale3D::on_render()
// draw grabbers // draw grabbers
render_grabbers(grabber_mean_size); render_grabbers(grabber_mean_size);
} }
else if (m_hover_id == 0 || m_hover_id == 1) { else if ((m_hover_id == 0 || m_hover_id == 1) && m_grabbers[0].enabled && m_grabbers[1].enabled) {
#if ENABLE_LEGACY_OPENGL_REMOVAL #if ENABLE_LEGACY_OPENGL_REMOVAL
// draw connections // draw connections
#if ENABLE_GL_CORE_PROFILE #if ENABLE_GL_CORE_PROFILE
@ -493,7 +500,7 @@ void GLGizmoScale3D::on_render()
shader->stop_using(); shader->stop_using();
} }
} }
else if (m_hover_id == 2 || m_hover_id == 3) { else if ((m_hover_id == 2 || m_hover_id == 3) && m_grabbers[2].enabled && m_grabbers[3].enabled) {
#if ENABLE_LEGACY_OPENGL_REMOVAL #if ENABLE_LEGACY_OPENGL_REMOVAL
// draw connections // draw connections
#if ENABLE_GL_CORE_PROFILE #if ENABLE_GL_CORE_PROFILE
@ -538,7 +545,7 @@ void GLGizmoScale3D::on_render()
shader->stop_using(); shader->stop_using();
} }
} }
else if (m_hover_id == 4 || m_hover_id == 5) { else if ((m_hover_id == 4 || m_hover_id == 5) && m_grabbers[4].enabled && m_grabbers[5].enabled) {
#if ENABLE_LEGACY_OPENGL_REMOVAL #if ENABLE_LEGACY_OPENGL_REMOVAL
// draw connections // draw connections
#if ENABLE_GL_CORE_PROFILE #if ENABLE_GL_CORE_PROFILE

View File

@ -85,6 +85,7 @@ public:
bool on_mouse(const wxMouseEvent &mouse_event) override; bool on_mouse(const wxMouseEvent &mouse_event) override;
void data_changed() override; void data_changed() override;
void enable_ununiversal_scale(bool enable);
protected: protected:
virtual bool on_init() override; virtual bool on_init() override;
virtual std::string on_get_name() const override; virtual std::string on_get_name() const override;

View File

@ -156,7 +156,7 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
else { else {
if (m_imgui->button(m_desc.at("reset_direction"))) { if (m_imgui->button(m_desc.at("reset_direction"))) {
wxGetApp().CallAfter([this](){ wxGetApp().CallAfter([this](){
m_c->object_clipper()->set_position(-1., false); m_c->object_clipper()->set_position_by_ratio(-1., false);
}); });
} }
} }
@ -165,7 +165,7 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
ImGui::SameLine(sliders_left_width); ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, _L("Ctrl + Mouse wheel"))) if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, _L("Ctrl + Mouse wheel")))
m_c->object_clipper()->set_position(clp_dist, true); m_c->object_clipper()->set_position_by_ratio(clp_dist, true);
ImGui::Separator(); ImGui::Separator();
if (m_imgui->button(m_desc.at("remove_all"))) { if (m_imgui->button(m_desc.at("remove_all"))) {

View File

@ -673,19 +673,19 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
if (action == SLAGizmoEventType::MouseWheelUp && control_down) { if (action == SLAGizmoEventType::MouseWheelUp && control_down) {
double pos = m_c->object_clipper()->get_position(); double pos = m_c->object_clipper()->get_position();
pos = std::min(1., pos + 0.01); pos = std::min(1., pos + 0.01);
m_c->object_clipper()->set_position(pos, true); m_c->object_clipper()->set_position_by_ratio(pos, true);
return true; return true;
} }
if (action == SLAGizmoEventType::MouseWheelDown && control_down) { if (action == SLAGizmoEventType::MouseWheelDown && control_down) {
double pos = m_c->object_clipper()->get_position(); double pos = m_c->object_clipper()->get_position();
pos = std::max(0., pos - 0.01); pos = std::max(0., pos - 0.01);
m_c->object_clipper()->set_position(pos, true); m_c->object_clipper()->set_position_by_ratio(pos, true);
return true; return true;
} }
if (action == SLAGizmoEventType::ResetClippingPlane) { if (action == SLAGizmoEventType::ResetClippingPlane) {
m_c->object_clipper()->set_position(-1., false); m_c->object_clipper()->set_position_by_ratio(-1., false);
return true; return true;
} }
@ -972,7 +972,7 @@ RENDER_AGAIN:
else { else {
if (m_imgui->button(m_desc.at("reset_direction"))) { if (m_imgui->button(m_desc.at("reset_direction"))) {
wxGetApp().CallAfter([this](){ wxGetApp().CallAfter([this](){
m_c->object_clipper()->set_position(-1., false); m_c->object_clipper()->set_position_by_ratio(-1., false);
}); });
} }
} }
@ -981,7 +981,7 @@ RENDER_AGAIN:
ImGui::PushItemWidth(window_width - clipping_slider_left); ImGui::PushItemWidth(window_width - clipping_slider_left);
float clp_dist = m_c->object_clipper()->get_position(); float clp_dist = m_c->object_clipper()->get_position();
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f"))
m_c->object_clipper()->set_position(clp_dist, true); m_c->object_clipper()->set_position_by_ratio(clp_dist, true);
if (m_imgui->button("?")) { if (m_imgui->button("?")) {

View File

@ -62,8 +62,8 @@ public:
void delete_selected_points(bool force = false); void delete_selected_points(bool force = false);
//ClippingPlane get_sla_clipping_plane() const; //ClippingPlane get_sla_clipping_plane() const;
bool is_in_editing_mode() const { return m_editing_mode; } bool is_in_editing_mode() const override { return m_editing_mode; }
bool is_selection_rectangle_dragging() const { return m_selection_rectangle.is_dragging(); } bool is_selection_rectangle_dragging() const override { return m_selection_rectangle.is_dragging(); }
bool has_backend_supports() const; bool has_backend_supports() const;
void reslice_SLA_supports(bool postpone_error_messages = false) const; void reslice_SLA_supports(bool postpone_error_messages = false) const;

View File

@ -425,8 +425,11 @@ void ObjectClipper::render_cut() const
if (m_clp_ratio == 0.) if (m_clp_ratio == 0.)
return; return;
const SelectionInfo* sel_info = get_pool()->selection_info(); const SelectionInfo* sel_info = get_pool()->selection_info();
int sel_instance_idx = sel_info->get_active_instance();
if (sel_instance_idx < 0)
return;
const ModelObject* mo = sel_info->model_object(); const ModelObject* mo = sel_info->model_object();
const Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation(); const Geometry::Transformation inst_trafo = mo->instances[sel_instance_idx]->get_transformation();
size_t clipper_id = 0; size_t clipper_id = 0;
for (const ModelVolume* mv : mo->volumes) { for (const ModelVolume* mv : mo->volumes) {
@ -440,19 +443,30 @@ void ObjectClipper::render_cut() const
clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD));
#if ENABLE_LEGACY_OPENGL_REMOVAL #if ENABLE_LEGACY_OPENGL_REMOVAL
clipper->render_cut({ 1.0f, 0.37f, 0.0f, 1.0f }); clipper->render_cut({ 1.0f, 0.37f, 0.0f, 1.0f });
clipper->render_contour({ 1.f, 1.f, 1.f, 1.f});
#else #else
glsafe(::glPushMatrix()); glsafe(::glPushMatrix());
glsafe(::glColor3f(1.0f, 0.37f, 0.0f)); glsafe(::glColor3f(1.0f, 0.37f, 0.0f));
clipper->render_cut(); clipper->render_cut();
glsafe(::glPopMatrix()); glsafe(::glColor3f(1.f, 1.f, 1.f));
clipper->render_contour();
#endif // ENABLE_LEGACY_OPENGL_REMOVAL #endif // ENABLE_LEGACY_OPENGL_REMOVAL
++clipper_id; ++clipper_id;
} }
} }
bool ObjectClipper::is_projection_inside_cut(const Vec3d& point) const
{
return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [point](const std::unique_ptr<MeshClipper>& cl) { return cl->is_projection_inside_cut(point); });
}
void ObjectClipper::set_position(double pos, bool keep_normal) bool ObjectClipper::has_valid_contour() const
{
return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [](const std::unique_ptr<MeshClipper>& cl) { return cl->has_valid_contour(); });
}
void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal)
{ {
const ModelObject* mo = get_pool()->selection_info()->model_object(); const ModelObject* mo = get_pool()->selection_info()->model_object();
int active_inst = get_pool()->selection_info()->get_active_instance(); int active_inst = get_pool()->selection_info()->get_active_instance();
@ -470,7 +484,36 @@ void ObjectClipper::set_position(double pos, bool keep_normal)
get_pool()->get_canvas()->set_as_dirty(); get_pool()->get_canvas()->set_as_dirty();
} }
void ObjectClipper::set_range_and_pos(const Vec3d& cpl_normal, double cpl_offset, double pos)
{
m_clp.reset(new ClippingPlane(cpl_normal, cpl_offset));
m_clp_ratio = pos;
get_pool()->get_canvas()->set_as_dirty();
}
const ClippingPlane* ObjectClipper::get_clipping_plane(bool ignore_hide_clipped) const
{
static const ClippingPlane no_clip = ClippingPlane::ClipsNothing();
return (ignore_hide_clipped || m_hide_clipped) ? m_clp.get() : &no_clip;
}
void ObjectClipper::set_behavior(bool hide_clipped, bool fill_cut, double contour_width)
{
m_hide_clipped = hide_clipped;
for (auto& clipper : m_clippers)
clipper->set_behaviour(fill_cut, contour_width);
}
void ObjectClipper::pass_mouse_click(const Vec3d& pt)
{
for (auto& clipper : m_clippers)
clipper->pass_mouse_click(pt);
}
std::vector<Vec3d> ObjectClipper::get_disabled_contours() const
{
return std::vector<Vec3d>();
}
void SupportsClipper::on_update() void SupportsClipper::on_update()
{ {
@ -557,11 +600,13 @@ void SupportsClipper::render_cut() const
#if ENABLE_LEGACY_OPENGL_REMOVAL #if ENABLE_LEGACY_OPENGL_REMOVAL
m_clipper->render_cut({ 1.0f, 0.f, 0.37f, 1.0f }); m_clipper->render_cut({ 1.0f, 0.f, 0.37f, 1.0f });
m_clipper->render_contour({ 1.f, 1.f, 1.f, 1.f });
#else #else
glsafe(::glPushMatrix()); glsafe(::glPushMatrix());
glsafe(::glColor3f(1.0f, 0.f, 0.37f)); glsafe(::glColor3f(1.0f, 0.f, 0.37f));
m_clipper->render_cut(); m_clipper->render_cut();
glsafe(::glPopMatrix()); glsafe(::glColor3f(1.0f, 1.f, 1.f));
m_clipper->render_contour();
#endif // ENABLE_LEGACY_OPENGL_REMOVAL #endif // ENABLE_LEGACY_OPENGL_REMOVAL
} }

View File

@ -255,10 +255,19 @@ public:
CommonGizmosDataID get_dependencies() const override { return CommonGizmosDataID::SelectionInfo; } CommonGizmosDataID get_dependencies() const override { return CommonGizmosDataID::SelectionInfo; }
#endif // NDEBUG #endif // NDEBUG
void set_position(double pos, bool keep_normal); void set_normal(const Vec3d& dir);
double get_position() const { return m_clp_ratio; } double get_position() const { return m_clp_ratio; }
ClippingPlane* get_clipping_plane() const { return m_clp.get(); } const ClippingPlane* get_clipping_plane(bool ignore_hide_clipped = false) const;
void render_cut() const; void render_cut() const;
void set_position_by_ratio(double pos, bool keep_normal);
void set_range_and_pos(const Vec3d& cpl_normal, double cpl_offset, double pos);
void set_behavior(bool hide_clipped, bool fill_cut, double contour_width);
void pass_mouse_click(const Vec3d& pt);
std::vector<Vec3d> get_disabled_contours() const;
bool is_projection_inside_cut(const Vec3d& point_in) const;
bool has_valid_contour() const;
protected: protected:
@ -271,6 +280,7 @@ private:
std::unique_ptr<ClippingPlane> m_clp; std::unique_ptr<ClippingPlane> m_clp;
double m_clp_ratio = 0.; double m_clp_ratio = 0.;
double m_active_inst_bb_radius = 0.; double m_active_inst_bb_radius = 0.;
bool m_hide_clipped = true;
}; };

View File

@ -99,7 +99,7 @@ bool GLGizmosManager::init()
m_gizmos.emplace_back(new GLGizmoScale3D(m_parent, "scale.svg", 1)); m_gizmos.emplace_back(new GLGizmoScale3D(m_parent, "scale.svg", 1));
m_gizmos.emplace_back(new GLGizmoRotate3D(m_parent, "rotate.svg", 2)); m_gizmos.emplace_back(new GLGizmoRotate3D(m_parent, "rotate.svg", 2));
m_gizmos.emplace_back(new GLGizmoFlatten(m_parent, "place.svg", 3)); m_gizmos.emplace_back(new GLGizmoFlatten(m_parent, "place.svg", 3));
m_gizmos.emplace_back(new GLGizmoCut(m_parent, "cut.svg", 4)); m_gizmos.emplace_back(new GLGizmoCut3D(m_parent, "cut.svg", 4));
m_gizmos.emplace_back(new GLGizmoHollow(m_parent, "hollow.svg", 5)); m_gizmos.emplace_back(new GLGizmoHollow(m_parent, "hollow.svg", 5));
m_gizmos.emplace_back(new GLGizmoSlaSupports(m_parent, "sla_supports.svg", 6)); m_gizmos.emplace_back(new GLGizmoSlaSupports(m_parent, "sla_supports.svg", 6));
m_gizmos.emplace_back(new GLGizmoFdmSupports(m_parent, "fdm_supports.svg", 7)); m_gizmos.emplace_back(new GLGizmoFdmSupports(m_parent, "fdm_supports.svg", 7));
@ -288,6 +288,8 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p
return dynamic_cast<GLGizmoSeam*>(m_gizmos[Seam].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); return dynamic_cast<GLGizmoSeam*>(m_gizmos[Seam].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else if (m_current == MmuSegmentation) else if (m_current == MmuSegmentation)
return dynamic_cast<GLGizmoMmuSegmentation*>(m_gizmos[MmuSegmentation].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); return dynamic_cast<GLGizmoMmuSegmentation*>(m_gizmos[MmuSegmentation].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else if (m_current == Cut)
return dynamic_cast<GLGizmoCut3D*>(m_gizmos[Cut].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else else
return false; return false;
} }
@ -505,7 +507,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
#endif /* __APPLE__ */ #endif /* __APPLE__ */
{ {
// Sla gizmo selects all support points // Sla gizmo selects all support points
if ((m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::SelectAll)) if ((m_current == SlaSupports || m_current == Hollow || m_current == Cut) && gizmo_event(SLAGizmoEventType::SelectAll))
processed = true; processed = true;
break; break;
@ -549,7 +551,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
case WXK_BACK: case WXK_BACK:
case WXK_DELETE: case WXK_DELETE:
{ {
if ((m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::Delete)) if ((m_current == SlaSupports || m_current == Hollow || m_current == Cut) && gizmo_event(SLAGizmoEventType::Delete))
processed = true; processed = true;
break; break;
@ -608,20 +610,11 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
if (evt.GetEventType() == wxEVT_KEY_UP) if (evt.GetEventType() == wxEVT_KEY_UP)
{ {
if (m_current == SlaSupports || m_current == Hollow) if (m_current == SlaSupports || m_current == Hollow || m_current == Cut)
{ {
bool is_editing = true; GLGizmoBase* gizmo = get_current();
bool is_rectangle_dragging = false; const bool is_editing = m_current == Hollow ? true : gizmo->is_in_editing_mode();
const bool is_rectangle_dragging = gizmo->is_selection_rectangle_dragging();
if (m_current == SlaSupports) {
GLGizmoSlaSupports* gizmo = dynamic_cast<GLGizmoSlaSupports*>(get_current());
is_editing = gizmo->is_in_editing_mode();
is_rectangle_dragging = gizmo->is_selection_rectangle_dragging();
}
else {
GLGizmoHollow* gizmo = dynamic_cast<GLGizmoHollow*>(get_current());
is_rectangle_dragging = gizmo->is_selection_rectangle_dragging();
}
if (keyCode == WXK_SHIFT) if (keyCode == WXK_SHIFT)
{ {
@ -643,7 +636,7 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
else if (evt.GetEventType() == wxEVT_KEY_DOWN) else if (evt.GetEventType() == wxEVT_KEY_DOWN)
{ {
if ((m_current == SlaSupports) && ((keyCode == WXK_SHIFT) || (keyCode == WXK_ALT)) if ((m_current == SlaSupports) && ((keyCode == WXK_SHIFT) || (keyCode == WXK_ALT))
&& dynamic_cast<GLGizmoSlaSupports*>(get_current())->is_in_editing_mode()) && get_current()->is_in_editing_mode())
{ {
// m_parent.set_cursor(GLCanvas3D::Cross); // m_parent.set_cursor(GLCanvas3D::Cross);
processed = true; processed = true;
@ -651,8 +644,8 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
else if (m_current == Cut) else if (m_current == Cut)
{ {
auto do_move = [this, &processed](double delta_z) { auto do_move = [this, &processed](double delta_z) {
GLGizmoCut* cut = dynamic_cast<GLGizmoCut*>(get_current()); GLGizmoCut3D* cut = dynamic_cast<GLGizmoCut3D*>(get_current());
cut->set_cut_z(delta_z + cut->get_cut_z()); cut->shift_cut_z(delta_z);
processed = true; processed = true;
}; };
@ -660,6 +653,9 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
{ {
case WXK_NUMPAD_UP: case WXK_UP: { do_move(1.0); break; } case WXK_NUMPAD_UP: case WXK_UP: { do_move(1.0); break; }
case WXK_NUMPAD_DOWN: case WXK_DOWN: { do_move(-1.0); break; } case WXK_NUMPAD_DOWN: case WXK_DOWN: { do_move(-1.0); break; }
case WXK_SHIFT : case WXK_ALT: {
processed = get_current()->is_in_editing_mode();
}
default: { break; } default: { break; }
} }
} else if (m_current == Simplify && keyCode == WXK_ESCAPE) { } else if (m_current == Simplify && keyCode == WXK_ESCAPE) {
@ -1042,6 +1038,11 @@ GLGizmoBase* GLGizmosManager::get_current() const
return ((m_current == Undefined) || m_gizmos.empty()) ? nullptr : m_gizmos[m_current].get(); return ((m_current == Undefined) || m_gizmos.empty()) ? nullptr : m_gizmos[m_current].get();
} }
GLGizmoBase* GLGizmosManager::get_gizmo(GLGizmosManager::EType type) const
{
return ((type == Undefined) || m_gizmos.empty()) ? nullptr : m_gizmos[type].get();
}
GLGizmosManager::EType GLGizmosManager::get_gizmo_from_name(const std::string& gizmo_name) const GLGizmosManager::EType GLGizmosManager::get_gizmo_from_name(const std::string& gizmo_name) const
{ {
std::vector<size_t> selectable_idxs = get_selectable_idxs(); std::vector<size_t> selectable_idxs = get_selectable_idxs();

View File

@ -199,6 +199,7 @@ public:
EType get_current_type() const { return m_current; } EType get_current_type() const { return m_current; }
GLGizmoBase* get_current() const; GLGizmoBase* get_current() const;
GLGizmoBase* get_gizmo(GLGizmosManager::EType type) const;
EType get_gizmo_from_name(const std::string& gizmo_name) const; EType get_gizmo_from_name(const std::string& gizmo_name) const;
bool is_running() const; bool is_running() const;

View File

@ -56,6 +56,11 @@ static const std::map<const wchar_t, std::string> font_icons = {
{ImGui::PreferencesHoverButton, "notification_preferences_hover"}, {ImGui::PreferencesHoverButton, "notification_preferences_hover"},
{ImGui::SliderFloatEditBtnIcon, "edit_button" }, {ImGui::SliderFloatEditBtnIcon, "edit_button" },
{ImGui::SliderFloatEditBtnPressedIcon, "edit_button_pressed" }, {ImGui::SliderFloatEditBtnPressedIcon, "edit_button_pressed" },
{ImGui::ExpandBtn , "expand_btn" },
{ImGui::CollapseBtn , "collapse_btn" },
{ImGui::RevertButton , "undo" },
{ImGui::WarningMarkerSmall , "notification_warning" },
{ImGui::InfoMarkerSmall , "notification_info" },
}; };
static const std::map<const wchar_t, std::string> font_icons_large = { static const std::map<const wchar_t, std::string> font_icons_large = {

View File

@ -16,15 +16,27 @@
#include <igl/unproject.h> #include <igl/unproject.h>
#include <cstdint>
namespace Slic3r { namespace Slic3r {
namespace GUI { namespace GUI {
void MeshClipper::set_behaviour(bool fill_cut, double contour_width)
{
if (fill_cut != m_fill_cut || contour_width != m_contour_width)
m_result.reset();
m_fill_cut = fill_cut;
m_contour_width = contour_width;
}
void MeshClipper::set_plane(const ClippingPlane& plane) void MeshClipper::set_plane(const ClippingPlane& plane)
{ {
if (m_plane != plane) { if (m_plane != plane) {
m_plane = plane; m_plane = plane;
m_triangles_valid = false; m_result.reset();
} }
} }
@ -33,7 +45,7 @@ void MeshClipper::set_limiting_plane(const ClippingPlane& plane)
{ {
if (m_limiting_plane != plane) { if (m_limiting_plane != plane) {
m_limiting_plane = plane; m_limiting_plane = plane;
m_triangles_valid = false; m_result.reset();
} }
} }
@ -43,8 +55,7 @@ void MeshClipper::set_mesh(const TriangleMesh& mesh)
{ {
if (m_mesh != &mesh) { if (m_mesh != &mesh) {
m_mesh = &mesh; m_mesh = &mesh;
m_triangles_valid = false; m_result.reset();
m_triangles2d.resize(0);
} }
} }
@ -52,8 +63,7 @@ void MeshClipper::set_negative_mesh(const TriangleMesh& mesh)
{ {
if (m_negative_mesh != &mesh) { if (m_negative_mesh != &mesh) {
m_negative_mesh = &mesh; m_negative_mesh = &mesh;
m_triangles_valid = false; m_result.reset();
m_triangles2d.resize(0);
} }
} }
@ -63,8 +73,7 @@ void MeshClipper::set_transformation(const Geometry::Transformation& trafo)
{ {
if (! m_trafo.get_matrix().isApprox(trafo.get_matrix())) { if (! m_trafo.get_matrix().isApprox(trafo.get_matrix())) {
m_trafo = trafo; m_trafo = trafo;
m_triangles_valid = false; m_result.reset();
m_triangles2d.resize(0);
} }
} }
@ -74,13 +83,9 @@ void MeshClipper::render_cut(const ColorRGBA& color)
void MeshClipper::render_cut() void MeshClipper::render_cut()
#endif // ENABLE_LEGACY_OPENGL_REMOVAL #endif // ENABLE_LEGACY_OPENGL_REMOVAL
{ {
if (! m_triangles_valid) if (! m_result)
recalculate_triangles(); recalculate_triangles();
#if ENABLE_LEGACY_OPENGL_REMOVAL #if ENABLE_LEGACY_OPENGL_REMOVAL
if (m_model.vertices_count() == 0 || m_model.indices_count() == 0)
return;
GLShaderProgram* curr_shader = wxGetApp().get_current_shader(); GLShaderProgram* curr_shader = wxGetApp().get_current_shader();
if (curr_shader != nullptr) if (curr_shader != nullptr)
curr_shader->stop_using(); curr_shader->stop_using();
@ -91,8 +96,10 @@ void MeshClipper::render_cut()
const Camera& camera = wxGetApp().plater()->get_camera(); const Camera& camera = wxGetApp().plater()->get_camera();
shader->set_uniform("view_model_matrix", camera.get_view_matrix()); shader->set_uniform("view_model_matrix", camera.get_view_matrix());
shader->set_uniform("projection_matrix", camera.get_projection_matrix()); shader->set_uniform("projection_matrix", camera.get_projection_matrix());
m_model.set_color(color); for (CutIsland& isl : m_result->cut_islands) {
m_model.render(); isl.model.set_color(isl.disabled ? ColorRGBA(1.f, 0.f, 0.f, 1.f) : color);
isl.model.render();
}
shader->stop_using(); shader->stop_using();
} }
@ -105,19 +112,80 @@ void MeshClipper::render_cut()
} }
#if ENABLE_LEGACY_OPENGL_REMOVAL
void MeshClipper::render_contour(const ColorRGBA& color)
#else
void MeshClipper::render_contour()
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
{
if (! m_result)
recalculate_triangles();
#if ENABLE_LEGACY_OPENGL_REMOVAL
GLShaderProgram* curr_shader = wxGetApp().get_current_shader();
if (curr_shader != nullptr)
curr_shader->stop_using();
GLShaderProgram* shader = wxGetApp().get_shader("flat");
if (shader != nullptr) {
shader->start_using();
const Camera& camera = wxGetApp().plater()->get_camera();
shader->set_uniform("view_model_matrix", camera.get_view_matrix());
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
for (CutIsland& isl : m_result->cut_islands) {
isl.model_expanded.set_color(color);
isl.model_expanded.render();
}
shader->stop_using();
}
if (curr_shader != nullptr)
curr_shader->start_using();
#else
if (m_vertex_array_expanded.has_VBOs())
m_vertex_array_expanded.render();
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
}
bool MeshClipper::is_projection_inside_cut(const Vec3d& point_in) const
{
if (!m_result || m_result->cut_islands.empty())
return false;
Vec3d point = m_result->trafo.inverse() * point_in;
Point pt_2d = Point::new_scale(Vec2d(point.x(), point.y()));
for (const CutIsland& isl : m_result->cut_islands) {
if (isl.expoly_bb.contains(pt_2d) && isl.expoly.contains(pt_2d))
return true;
}
return false;
}
bool MeshClipper::has_valid_contour() const
{
return m_result && std::any_of(m_result->cut_islands.begin(), m_result->cut_islands.end(), [](const CutIsland& isl) { return !isl.expoly.empty(); });
}
void MeshClipper::pass_mouse_click(const Vec3d& point_in)
{
if (! m_result || m_result->cut_islands.empty())
return;
Vec3d point = m_result->trafo.inverse() * point_in;
Point pt_2d = Point::new_scale(Vec2d(point.x(), point.y()));
for (CutIsland& isl : m_result->cut_islands) {
if (isl.expoly_bb.contains(pt_2d) && isl.expoly.contains(pt_2d))
isl.disabled = ! isl.disabled;
}
}
void MeshClipper::recalculate_triangles() void MeshClipper::recalculate_triangles()
{ {
#if ENABLE_WORLD_COORDINATE m_result = ClipResult();
const Transform3f instance_matrix_no_translation_no_scaling = m_trafo.get_rotation_matrix().cast<float>();
#else auto plane_mesh = Eigen::Hyperplane<double, 3>(m_plane.get_normal(), -m_plane.distance(Vec3d::Zero())).transform(m_trafo.get_matrix().inverse());
const Transform3f& instance_matrix_no_translation_no_scaling = m_trafo.get_matrix(true,false,true).cast<float>(); const Vec3d up = plane_mesh.normal();
#endif // ENABLE_WORLD_COORDINATE const float height_mesh = -plane_mesh.offset();
// Calculate clipping plane normal in mesh coordinates.
const Vec3f up_noscale = instance_matrix_no_translation_no_scaling.inverse() * m_plane.get_normal().cast<float>();
const Vec3d up = up_noscale.cast<double>().cwiseProduct(m_trafo.get_scaling_factor());
// Calculate distance from mesh origin to the clipping plane (in mesh coordinates).
const float height_mesh = m_plane.distance(m_trafo.get_offset()) * (up_noscale.norm()/up.norm());
// Now do the cutting // Now do the cutting
MeshSlicingParams slicing_params; MeshSlicingParams slicing_params;
@ -137,6 +205,8 @@ void MeshClipper::recalculate_triangles()
tr.rotate(q); tr.rotate(q);
tr = m_trafo.get_matrix() * tr; tr = m_trafo.get_matrix() * tr;
m_result->trafo = tr;
if (m_limiting_plane != ClippingPlane::ClipsNothing()) if (m_limiting_plane != ClippingPlane::ClipsNothing())
{ {
// Now remove whatever ended up below the limiting plane (e.g. sinking objects). // Now remove whatever ended up below the limiting plane (e.g. sinking objects).
@ -190,42 +260,108 @@ void MeshClipper::recalculate_triangles()
} }
} }
m_triangles2d = triangulate_expolygons_2f(expolys, m_trafo.get_matrix().matrix().determinant() < 0.);
tr.pretranslate(0.001 * m_plane.get_normal().normalized()); // to avoid z-fighting tr.pretranslate(0.001 * m_plane.get_normal().normalized()); // to avoid z-fighting
Transform3d tr2 = tr;
tr2.pretranslate(0.002 * m_plane.get_normal().normalized());
#if ENABLE_LEGACY_OPENGL_REMOVAL #if ENABLE_LEGACY_OPENGL_REMOVAL
m_model.reset(); std::vector<Vec2f> triangles2d;
GLModel::Geometry init_data; for (const ExPolygon& exp : expolys) {
init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; triangles2d.clear();
init_data.reserve_vertices(m_triangles2d.size());
init_data.reserve_indices(m_triangles2d.size());
// vertices + indices m_result->cut_islands.push_back(CutIsland());
for (auto it = m_triangles2d.cbegin(); it != m_triangles2d.cend(); it = it + 3) { CutIsland& isl = m_result->cut_islands.back();
init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast<float>(), (Vec3f)up.cast<float>());
init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast<float>(), (Vec3f)up.cast<float>()); if (m_fill_cut) {
init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast<float>(), (Vec3f)up.cast<float>()); triangles2d = triangulate_expolygon_2f(exp, m_trafo.get_matrix().matrix().determinant() < 0.);
const size_t idx = it - m_triangles2d.cbegin(); GLModel::Geometry init_data;
init_data.add_triangle((unsigned int)idx, (unsigned int)idx + 1, (unsigned int)idx + 2); init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 };
init_data.reserve_vertices(triangles2d.size());
init_data.reserve_indices(triangles2d.size());
// vertices + indices
for (auto it = triangles2d.cbegin(); it != triangles2d.cend(); it = it + 3) {
init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast<float>(), (Vec3f)up.cast<float>());
init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast<float>(), (Vec3f)up.cast<float>());
init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast<float>(), (Vec3f)up.cast<float>());
const size_t idx = it - triangles2d.cbegin();
init_data.add_triangle((unsigned int)idx, (unsigned int)idx + 1, (unsigned int)idx + 2);
}
if (!init_data.is_empty())
isl.model.init_from(std::move(init_data));
}
if (m_contour_width != 0. && ! exp.contour.empty()) {
triangles2d.clear();
// The contours must not scale with the object. Check the scale factor
// in the respective directions, create a scaled copy of the ExPolygon
// offset it and then unscale the result again.
Transform3d t = tr;
t.translation() = Vec3d::Zero();
double scale_x = (t * Vec3d::UnitX()).norm();
double scale_y = (t * Vec3d::UnitY()).norm();
// To prevent overflow after scaling, downscale the input if needed:
double extra_scale = 1.;
int32_t limit = int32_t(std::min(std::numeric_limits<coord_t>::max() / (2. * scale_x), std::numeric_limits<coord_t>::max() / (2. * scale_y)));
int32_t max_coord = 0;
for (const Point& pt : exp.contour)
max_coord = std::max(max_coord, std::max(std::abs(pt.x()), std::abs(pt.y())));
if (max_coord + m_contour_width >= limit)
extra_scale = 0.9 * double(limit) / max_coord;
ExPolygon exp_copy = exp;
if (extra_scale != 1.)
exp_copy.scale(extra_scale);
exp_copy.scale(scale_x, scale_y);
ExPolygons expolys_exp = offset_ex(exp_copy, scale_(m_contour_width));
expolys_exp = diff_ex(expolys_exp, ExPolygons({exp_copy}));
for (ExPolygon& e : expolys_exp) {
e.scale(1./scale_x, 1./scale_y);
if (extra_scale != 1.)
e.scale(1./extra_scale);
}
triangles2d = triangulate_expolygons_2f(expolys_exp, m_trafo.get_matrix().matrix().determinant() < 0.);
GLModel::Geometry init_data = GLModel::Geometry();
init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 };
init_data.reserve_vertices(triangles2d.size());
init_data.reserve_indices(triangles2d.size());
// vertices + indices
for (auto it = triangles2d.cbegin(); it != triangles2d.cend(); it = it + 3) {
init_data.add_vertex((Vec3f)(tr2 * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast<float>(), (Vec3f)up.cast<float>());
init_data.add_vertex((Vec3f)(tr2 * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast<float>(), (Vec3f)up.cast<float>());
init_data.add_vertex((Vec3f)(tr2 * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast<float>(), (Vec3f)up.cast<float>());
const size_t idx = it - triangles2d.cbegin();
init_data.add_triangle((unsigned short)idx, (unsigned short)idx + 1, (unsigned short)idx + 2);
}
if (!init_data.is_empty())
isl.model_expanded.init_from(std::move(init_data));
}
isl.expoly = std::move(exp);
isl.expoly_bb = get_extents(exp);
} }
if (!init_data.is_empty())
m_model.init_from(std::move(init_data));
#else #else
m_vertex_array.release_geometry(); #error NOT IMPLEMENTED
for (auto it=m_triangles2d.cbegin(); it != m_triangles2d.cend(); it=it+3) {
m_vertex_array.push_geometry(tr * Vec3d((*(it+0))(0), (*(it+0))(1), height_mesh), up);
m_vertex_array.push_geometry(tr * Vec3d((*(it+1))(0), (*(it+1))(1), height_mesh), up);
m_vertex_array.push_geometry(tr * Vec3d((*(it+2))(0), (*(it+2))(1), height_mesh), up);
const size_t idx = it - m_triangles2d.cbegin();
m_vertex_array.push_triangle(idx, idx+1, idx+2);
}
m_vertex_array.finalize_geometry(true);
#endif // ENABLE_LEGACY_OPENGL_REMOVAL #endif // ENABLE_LEGACY_OPENGL_REMOVAL
m_triangles_valid = true;
#if ENABLE_LEGACY_OPENGL_REMOVAL
#else
#error NOT IMPLEMENTED
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
} }
@ -239,7 +375,7 @@ void MeshRaycaster::line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3
Vec3d& point, Vec3d& direction) Vec3d& point, Vec3d& direction)
#else #else
void MeshRaycaster::line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, void MeshRaycaster::line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
Vec3d& point, Vec3d& direction) const Vec3d& point, Vec3d& direction)
#endif // ENABLE_RAYCAST_PICKING #endif // ENABLE_RAYCAST_PICKING
{ {
Matrix4d modelview = camera.get_view_matrix().matrix(); Matrix4d modelview = camera.get_view_matrix().matrix();
@ -264,8 +400,11 @@ void MeshRaycaster::line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3
bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane, Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane,
size_t* facet_idx) const size_t* facet_idx, bool* was_clipping_plane_hit) const
{ {
if (was_clipping_plane_hit)
*was_clipping_plane_hit = false;
Vec3d point; Vec3d point;
Vec3d direction; Vec3d direction;
line_from_mouse_pos(mouse_pos, trafo, camera, point, direction); line_from_mouse_pos(mouse_pos, trafo, camera, point, direction);
@ -286,9 +425,26 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d&
break; break;
} }
if (i==hits.size() || (hits.size()-i) % 2 != 0) { if (i==hits.size()) {
// All hits are either clipped, or there is an odd number of unclipped // All hits are clipped.
// hits - meaning the nearest must be from inside the mesh. return false;
}
if ((hits.size()-i) % 2 != 0) {
// There is an odd number of unclipped hits - meaning the nearest must be from inside the mesh.
// In that case, calculate intersection with the clipping place.
if (clipping_plane && was_clipping_plane_hit) {
direction = direction + point;
point = trafo * point; // transform to world coords
direction = trafo * direction - point;
Vec3d normal = -clipping_plane->get_normal().cast<double>();
double den = normal.dot(direction);
if (den != 0.) {
double t = (-clipping_plane->get_offset() - normal.dot(point))/den;
position = (point + t * direction).cast<float>();
*was_clipping_plane_hit = true;
}
}
return false; return false;
} }
@ -302,6 +458,35 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d&
return true; return true;
} }
bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
Vec3d& position, Vec3d& normal) const
{
Vec3d point;
Vec3d direction;
line_from_mouse_pos(mouse_pos, trafo, camera, point, direction);
std::vector<AABBMesh::hit_result> hits = m_emesh.query_ray_hits(point, direction);
if (hits.empty())
return false; // no intersection found
// Now stuff the points in the provided vector and calculate normals if asked about them:
position = hits[0].position();
normal = hits[0].normal();
return true;
}
bool MeshRaycaster::is_valid_intersection(Vec3d point, Vec3d direction, const Transform3d& trafo) const
{
point = trafo.inverse() * point;
std::vector<AABBMesh::hit_result> hits = m_emesh.query_ray_hits(point, direction);
std::vector<AABBMesh::hit_result> neg_hits = m_emesh.query_ray_hits(point, -direction);
return !hits.empty() && !neg_hits.empty();
}
std::vector<unsigned> MeshRaycaster::get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera, const std::vector<Vec3f>& points, std::vector<unsigned> MeshRaycaster::get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera, const std::vector<Vec3f>& points,
const ClippingPlane* clipping_plane) const const ClippingPlane* clipping_plane) const

View File

@ -14,6 +14,7 @@
#endif // ENABLE_LEGACY_OPENGL_REMOVAL #endif // ENABLE_LEGACY_OPENGL_REMOVAL
#include <cfloat> #include <cfloat>
#include <optional>
#if ENABLE_RAYCAST_PICKING #if ENABLE_RAYCAST_PICKING
#include <memory> #include <memory>
#endif // ENABLE_RAYCAST_PICKING #endif // ENABLE_RAYCAST_PICKING
@ -80,6 +81,10 @@ public:
class MeshClipper class MeshClipper
{ {
public: public:
// Set whether the cut should be triangulated and whether a cut
// contour should be calculated and shown.
void set_behaviour(bool fill_cut, double contour_width);
// Inform MeshClipper about which plane we want to use to cut the mesh // Inform MeshClipper about which plane we want to use to cut the mesh
// This is supposed to be in world coordinates. // This is supposed to be in world coordinates.
void set_plane(const ClippingPlane& plane); void set_plane(const ClippingPlane& plane);
@ -103,10 +108,16 @@ public:
// be set in world coords. // be set in world coords.
#if ENABLE_LEGACY_OPENGL_REMOVAL #if ENABLE_LEGACY_OPENGL_REMOVAL
void render_cut(const ColorRGBA& color); void render_cut(const ColorRGBA& color);
void render_contour(const ColorRGBA& color);
#else #else
void render_cut(); void render_cut();
#endif // ENABLE_LEGACY_OPENGL_REMOVAL #endif // ENABLE_LEGACY_OPENGL_REMOVAL
void pass_mouse_click(const Vec3d& pt);
bool is_projection_inside_cut(const Vec3d& point) const;
bool has_valid_contour() const;
private: private:
void recalculate_triangles(); void recalculate_triangles();
@ -115,13 +126,27 @@ private:
const TriangleMesh* m_negative_mesh = nullptr; const TriangleMesh* m_negative_mesh = nullptr;
ClippingPlane m_plane; ClippingPlane m_plane;
ClippingPlane m_limiting_plane = ClippingPlane::ClipsNothing(); ClippingPlane m_limiting_plane = ClippingPlane::ClipsNothing();
std::vector<Vec2f> m_triangles2d;
#if ENABLE_LEGACY_OPENGL_REMOVAL #if ENABLE_LEGACY_OPENGL_REMOVAL
GLModel m_model;
struct CutIsland {
GLModel model;
GLModel model_expanded;
ExPolygon expoly;
BoundingBox expoly_bb;
bool disabled = false;
};
struct ClipResult {
std::vector<CutIsland> cut_islands;
Transform3d trafo; // this rotates the cut into world coords
};
std::optional<ClipResult> m_result;
#else #else
#error NOT IMLEMENTED
GLIndexedVertexArray m_vertex_array; GLIndexedVertexArray m_vertex_array;
#endif // ENABLE_LEGACY_OPENGL_REMOVAL #endif // ENABLE_LEGACY_OPENGL_REMOVAL
bool m_triangles_valid = false; bool m_fill_cut = true;
double m_contour_width = 0.;
}; };
@ -150,8 +175,10 @@ public:
{ {
} }
void line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, static void line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
Vec3d& point, Vec3d& direction) const; Vec3d& point, Vec3d& direction);
// void line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
// Vec3d& point, Vec3d& direction) const;
#endif // ENABLE_RAYCAST_PICKING #endif // ENABLE_RAYCAST_PICKING
// Given a mouse position, this returns true in case it is on the mesh. // Given a mouse position, this returns true in case it is on the mesh.
@ -159,12 +186,18 @@ public:
const Vec2d& mouse_pos, const Vec2d& mouse_pos,
const Transform3d& trafo, // how to get the mesh into world coords const Transform3d& trafo, // how to get the mesh into world coords
const Camera& camera, // current camera position const Camera& camera, // current camera position
Vec3f& position, // where to save the positibon of the hit (mesh coords) Vec3f& position, // where to save the positibon of the hit (mesh coords if mesh, world coords if clipping plane)
Vec3f& normal, // normal of the triangle that was hit Vec3f& normal, // normal of the triangle that was hit
const ClippingPlane* clipping_plane = nullptr, // clipping plane (if active) const ClippingPlane* clipping_plane = nullptr, // clipping plane (if active)
size_t* facet_idx = nullptr // index of the facet hit size_t* facet_idx = nullptr, // index of the facet hit
bool* was_clipping_plane_hit = nullptr // is the hit on the clipping place cross section?
) const; ) const;
// Given a mouse position, this returns true in case it is on the mesh.
bool unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, Vec3d& position, Vec3d& normal) const;
bool is_valid_intersection(Vec3d point, Vec3d direction, const Transform3d& trafo) const;
// Given a vector of points in woorld coordinates, this returns vector // 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 // of indices of points that are visible (i.e. not cut by clipping plane
// or obscured by part of the mesh. // or obscured by part of the mesh.

View File

@ -1228,6 +1228,7 @@ void NotificationManager::UpdatedItemsInfoNotification::add_type(InfoItemType ty
case InfoItemType::MmuSegmentation: text += format(_L_PLURAL("%1$d object was loaded with multimaterial painting.", "%1$d objects were loaded with multimaterial painting.",(*it).second), (*it).second) + "\n"; break; case InfoItemType::MmuSegmentation: text += format(_L_PLURAL("%1$d object was loaded with multimaterial painting.", "%1$d objects were loaded with multimaterial painting.",(*it).second), (*it).second) + "\n"; break;
case InfoItemType::VariableLayerHeight: text += format(_L_PLURAL("%1$d object was loaded with variable layer height.", "%1$d objects were loaded with variable layer height.", (*it).second), (*it).second) + "\n"; break; case InfoItemType::VariableLayerHeight: text += format(_L_PLURAL("%1$d object was loaded with variable layer height.", "%1$d objects were loaded with variable layer height.", (*it).second), (*it).second) + "\n"; break;
case InfoItemType::Sinking: text += format(_L_PLURAL("%1$d object was loaded with partial sinking.", "%1$d objects were loaded with partial sinking.", (*it).second), (*it).second) + "\n"; break; case InfoItemType::Sinking: text += format(_L_PLURAL("%1$d object was loaded with partial sinking.", "%1$d objects were loaded with partial sinking.", (*it).second), (*it).second) + "\n"; break;
case InfoItemType::CutConnectors: text += format(_L_PLURAL("%1$d object was loaded as a part of cut object.", "%1$d objects were loaded as parts of cut object", (*it).second), (*it).second) + "\n"; break;
default: BOOST_LOG_TRIVIAL(error) << "Unknown InfoItemType: " << (*it).second; break; default: BOOST_LOG_TRIVIAL(error) << "Unknown InfoItemType: " << (*it).second; break;
} }
} }

View File

@ -34,10 +34,19 @@ void ObjectDataViewModelNode::init_container()
#endif //__WXGTK__ #endif //__WXGTK__
} }
void ObjectDataViewModelNode::invalidate_container()
{
#ifndef __WXGTK__
if (this->GetChildCount() == 0)
this->m_container = false;
#endif //__WXGTK__
}
static constexpr char LayerRootIcon[] = "edit_layers_all"; static constexpr char LayerRootIcon[] = "edit_layers_all";
static constexpr char LayerIcon[] = "edit_layers_some"; static constexpr char LayerIcon[] = "edit_layers_some";
static constexpr char WarningIcon[] = "exclamation"; static constexpr char WarningIcon[] = "exclamation";
static constexpr char WarningManifoldIcon[] = "exclamation_manifold"; static constexpr char WarningManifoldIcon[] = "exclamation_manifold";
static constexpr char LockIcon[] = "cut_";
struct InfoItemAtributes { struct InfoItemAtributes {
std::string name; std::string name;
@ -48,6 +57,7 @@ const std::map<InfoItemType, InfoItemAtributes> INFO_ITEMS{
// info_item Type info_item Name info_item BitmapName // info_item Type info_item Name info_item BitmapName
{ InfoItemType::CustomSupports, {L("Paint-on supports"), "fdm_supports_" }, }, { InfoItemType::CustomSupports, {L("Paint-on supports"), "fdm_supports_" }, },
{ InfoItemType::CustomSeam, {L("Paint-on seam"), "seam_" }, }, { InfoItemType::CustomSeam, {L("Paint-on seam"), "seam_" }, },
{ InfoItemType::CutConnectors, {L("Cut connectors"), "cut_connectors" }, },
{ InfoItemType::MmuSegmentation, {L("Multimaterial painting"), "mmu_segmentation_"}, }, { InfoItemType::MmuSegmentation, {L("Multimaterial painting"), "mmu_segmentation_"}, },
{ InfoItemType::Sinking, {L("Sinking"), "sinking"}, }, { InfoItemType::Sinking, {L("Sinking"), "sinking"}, },
{ InfoItemType::VariableLayerHeight, {L("Variable layer height"), "layers"}, }, { InfoItemType::VariableLayerHeight, {L("Variable layer height"), "layers"}, },
@ -56,19 +66,15 @@ const std::map<InfoItemType, InfoItemAtributes> INFO_ITEMS{
ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent,
const wxString& sub_obj_name, const wxString& sub_obj_name,
Slic3r::ModelVolumeType type, Slic3r::ModelVolumeType type,
const wxBitmapBundle& bmp,
const wxString& extruder, const wxString& extruder,
const int idx/* = -1*/, const int idx/* = -1*/) :
const std::string& warning_icon_name /*= std::string*/) :
m_parent(parent), m_parent(parent),
m_name(sub_obj_name), m_name(sub_obj_name),
m_type(itVolume), m_type(itVolume),
m_volume_type(type), m_volume_type(type),
m_idx(idx), m_idx(idx),
m_extruder(type == Slic3r::ModelVolumeType::MODEL_PART || type == Slic3r::ModelVolumeType::PARAMETER_MODIFIER ? extruder : ""), m_extruder(type == Slic3r::ModelVolumeType::MODEL_PART || type == Slic3r::ModelVolumeType::PARAMETER_MODIFIER ? extruder : "")
m_warning_icon_name(warning_icon_name)
{ {
m_bmp = bmp;
set_action_and_extruder_icons(); set_action_and_extruder_icons();
init_container(); init_container();
} }
@ -173,13 +179,6 @@ void ObjectDataViewModelNode::set_printable_icon(PrintIndicator printable)
*get_bmp_bundle(m_printable == piPrintable ? "eye_open" : "eye_closed"); *get_bmp_bundle(m_printable == piPrintable ? "eye_open" : "eye_closed");
} }
void ObjectDataViewModelNode::set_warning_icon(const std::string& warning_icon_name)
{
m_warning_icon_name = warning_icon_name;
if (warning_icon_name.empty())
m_bmp = m_empty_bmp;
}
void ObjectDataViewModelNode::update_settings_digest_bitmaps() void ObjectDataViewModelNode::update_settings_digest_bitmaps()
{ {
m_bmp = m_empty_bmp; m_bmp = m_empty_bmp;
@ -327,6 +326,7 @@ ObjectDataViewModel::ObjectDataViewModel()
m_volume_bmps = MenuFactory::get_volume_bitmaps(); m_volume_bmps = MenuFactory::get_volume_bitmaps();
m_warning_bmp = *get_bmp_bundle(WarningIcon); m_warning_bmp = *get_bmp_bundle(WarningIcon);
m_warning_manifold_bmp = *get_bmp_bundle(WarningManifoldIcon); m_warning_manifold_bmp = *get_bmp_bundle(WarningManifoldIcon);
m_lock_bmp = *get_bmp_bundle(LockIcon);
for (auto item : INFO_ITEMS) for (auto item : INFO_ITEMS)
m_info_bmps[item.first] = get_bmp_bundle(item.second.bmp_name); m_info_bmps[item.first] = get_bmp_bundle(item.second.bmp_name);
@ -340,19 +340,55 @@ ObjectDataViewModel::~ObjectDataViewModel()
m_bitmap_cache = nullptr; m_bitmap_cache = nullptr;
} }
wxBitmapBundle& ObjectDataViewModel::GetWarningBitmap(const std::string& warning_icon_name) void ObjectDataViewModel::UpdateBitmapForNode(ObjectDataViewModelNode* node)
{ {
return warning_icon_name.empty() ? m_empty_bmp : warning_icon_name == WarningIcon ? m_warning_bmp : m_warning_manifold_bmp; int vol_type = static_cast<int>(node->GetVolumeType());
bool is_volume_node = vol_type >= 0;
if (!node->has_warning_icon() && !node->has_lock()) {
node->SetBitmap(is_volume_node ? *m_volume_bmps.at(vol_type) : m_empty_bmp);
return;
}
std::string scaled_bitmap_name = std::string();
if (node->has_warning_icon())
scaled_bitmap_name += node->warning_icon_name();
if (node->has_lock())
scaled_bitmap_name += LockIcon;
if (is_volume_node)
scaled_bitmap_name += std::to_string(vol_type);
scaled_bitmap_name += (wxGetApp().dark_mode() ? "-dm" : "-lm");
wxBitmapBundle* bmp = m_bitmap_cache->find_bndl(scaled_bitmap_name);
if (!bmp) {
std::vector<wxBitmapBundle*> bmps;
if (node->has_warning_icon())
bmps.emplace_back(node->warning_icon_name() == WarningIcon ? &m_warning_bmp : &m_warning_manifold_bmp);
if (node->has_lock())
bmps.emplace_back(&m_lock_bmp);
if (is_volume_node)
bmps.emplace_back(m_volume_bmps[vol_type]);
bmp = m_bitmap_cache->insert_bndl(scaled_bitmap_name, bmps);
}
node->SetBitmap(*bmp);
} }
wxDataViewItem ObjectDataViewModel::Add(const wxString &name, void ObjectDataViewModel::UpdateBitmapForNode(ObjectDataViewModelNode* node, const std::string& warning_icon_name, bool has_lock)
const int extruder,
const std::string& warning_icon_name/* = std::string()*/ )
{ {
const wxString extruder_str = extruder == 0 ? _L("default") : wxString::Format("%d", extruder); node->SetWarningIconName(warning_icon_name);
auto root = new ObjectDataViewModelNode(name, extruder_str); node->SetLock(has_lock);
UpdateBitmapForNode(node);
}
wxDataViewItem ObjectDataViewModel::AddObject(const wxString &name,
const wxString& extruder,
const std::string& warning_icon_name,
const bool has_lock)
{
auto root = new ObjectDataViewModelNode(name, extruder);
// Add warning icon if detected auto-repaire // Add warning icon if detected auto-repaire
root->SetWarningBitmap(GetWarningBitmap(warning_icon_name), warning_icon_name); UpdateBitmapForNode(root, warning_icon_name, has_lock);
m_objects.push_back(root); m_objects.push_back(root);
// notify control // notify control
@ -365,42 +401,29 @@ wxDataViewItem ObjectDataViewModel::Add(const wxString &name,
wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent_item, wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent_item,
const wxString &name, const wxString &name,
const int volume_idx,
const Slic3r::ModelVolumeType volume_type, const Slic3r::ModelVolumeType volume_type,
const std::string& warning_icon_name/* = std::string()*/, const std::string& warning_icon_name,
const int extruder/* = 0*/, const wxString& extruder)
const bool create_frst_child/* = true*/)
{ {
ObjectDataViewModelNode *root = static_cast<ObjectDataViewModelNode*>(parent_item.GetID()); ObjectDataViewModelNode *root = static_cast<ObjectDataViewModelNode*>(parent_item.GetID());
if (!root) return wxDataViewItem(0); if (!root) return wxDataViewItem(0);
wxString extruder_str = extruder == 0 ? _(L("default")) : wxString::Format("%d", extruder);
// get insertion position according to the existed Layers and/or Instances Items // get insertion position according to the existed Layers and/or Instances Items
int insert_position = get_root_idx(root, itLayerRoot); int insert_position = get_root_idx(root, itLayerRoot);
if (insert_position < 0) if (insert_position < 0)
insert_position = get_root_idx(root, itInstanceRoot); insert_position = get_root_idx(root, itInstanceRoot);
if (create_frst_child && root->m_volumes_cnt == 0) const auto node = new ObjectDataViewModelNode(root, name, volume_type, extruder, volume_idx);
{ UpdateBitmapForNode(node, warning_icon_name, root->has_lock() && volume_type < ModelVolumeType::PARAMETER_MODIFIER);
const Slic3r::ModelVolumeType type = Slic3r::ModelVolumeType::MODEL_PART;
const auto node = new ObjectDataViewModelNode(root, root->m_name, type, GetVolumeIcon(type, root->m_warning_icon_name), extruder_str, 0, root->m_warning_icon_name);
insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position);
// notify control
const wxDataViewItem child((void*)node);
ItemAdded(parent_item, child);
root->m_volumes_cnt++;
if (insert_position >= 0) insert_position++;
}
const auto node = new ObjectDataViewModelNode(root, name, volume_type, GetVolumeIcon(volume_type, warning_icon_name), extruder_str, root->m_volumes_cnt, warning_icon_name);
insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position); insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position);
// if part with errors is added, but object wasn't marked, then mark it // if part with errors is added, but object wasn't marked, then mark it
if (!warning_icon_name.empty() && warning_icon_name != root->m_warning_icon_name && if (!warning_icon_name.empty() && warning_icon_name != root->warning_icon_name() &&
(root->m_warning_icon_name.empty() || root->m_warning_icon_name == WarningManifoldIcon) ) (!root->has_warning_icon() || root->warning_icon_name() == WarningManifoldIcon)) {
root->SetWarningBitmap(GetWarningBitmap(warning_icon_name), warning_icon_name); root->SetWarningIconName(warning_icon_name);
UpdateBitmapForNode(root);
}
// notify control // notify control
const wxDataViewItem child((void*)node); const wxDataViewItem child((void*)node);
@ -598,14 +621,12 @@ wxDataViewItem ObjectDataViewModel::AddLayersRoot(const wxDataViewItem &parent_i
wxDataViewItem ObjectDataViewModel::AddLayersChild(const wxDataViewItem &parent_item, wxDataViewItem ObjectDataViewModel::AddLayersChild(const wxDataViewItem &parent_item,
const t_layer_height_range& layer_range, const t_layer_height_range& layer_range,
const int extruder/* = 0*/, const wxString& extruder,
const int index /* = -1*/) const int index /* = -1*/)
{ {
ObjectDataViewModelNode *parent_node = static_cast<ObjectDataViewModelNode*>(parent_item.GetID()); ObjectDataViewModelNode *parent_node = static_cast<ObjectDataViewModelNode*>(parent_item.GetID());
if (!parent_node) return wxDataViewItem(0); if (!parent_node) return wxDataViewItem(0);
wxString extruder_str = extruder == 0 ? _(L("default")) : wxString::Format("%d", extruder);
// get LayerRoot node // get LayerRoot node
ObjectDataViewModelNode *layer_root_node; ObjectDataViewModelNode *layer_root_node;
wxDataViewItem layer_root_item; wxDataViewItem layer_root_item;
@ -622,7 +643,7 @@ wxDataViewItem ObjectDataViewModel::AddLayersChild(const wxDataViewItem &parent_
} }
// Add layer node // Add layer node
ObjectDataViewModelNode *layer_node = new ObjectDataViewModelNode(layer_root_node, layer_range, index, extruder_str); ObjectDataViewModelNode *layer_node = new ObjectDataViewModelNode(layer_root_node, layer_range, index, extruder);
if (index < 0) if (index < 0)
layer_root_node->Append(layer_node); layer_root_node->Append(layer_node);
else else
@ -711,10 +732,7 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item)
delete node_parent; delete node_parent;
ret_item = wxDataViewItem(obj_node); ret_item = wxDataViewItem(obj_node);
#ifndef __WXGTK__ obj_node->invalidate_container();
if (obj_node->GetChildCount() == 0)
obj_node->m_container = false;
#endif //__WXGTK__
ItemDeleted(ret_item, wxDataViewItem(node_parent)); ItemDeleted(ret_item, wxDataViewItem(node_parent));
return ret_item; return ret_item;
} }
@ -730,10 +748,7 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item)
delete node_parent; delete node_parent;
ret_item = wxDataViewItem(obj_node); ret_item = wxDataViewItem(obj_node);
#ifndef __WXGTK__ obj_node->invalidate_container();
if (obj_node->GetChildCount() == 0)
obj_node->m_container = false;
#endif //__WXGTK__
ItemDeleted(ret_item, wxDataViewItem(node_parent)); ItemDeleted(ret_item, wxDataViewItem(node_parent));
return ret_item; return ret_item;
} }
@ -755,10 +770,7 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item)
node_parent->m_volumes_cnt = 0; node_parent->m_volumes_cnt = 0;
delete last_child_node; delete last_child_node;
#ifndef __WXGTK__ node_parent->invalidate_container();
if (node_parent->GetChildCount() == 0)
node_parent->m_container = false;
#endif //__WXGTK__
ItemDeleted(parent, wxDataViewItem(last_child_node)); ItemDeleted(parent, wxDataViewItem(last_child_node));
wxCommandEvent event(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED); wxCommandEvent event(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED);
@ -793,10 +805,7 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item)
// set m_containet to FALSE if parent has no child // set m_containet to FALSE if parent has no child
if (node_parent) { if (node_parent) {
#ifndef __WXGTK__ node_parent->invalidate_container();
if (node_parent->GetChildCount() == 0)
node_parent->m_container = false;
#endif //__WXGTK__
ret_item = parent; ret_item = parent;
} }
@ -838,10 +847,7 @@ wxDataViewItem ObjectDataViewModel::DeleteLastInstance(const wxDataViewItem &par
parent_node->set_printable_icon(last_inst_printable); parent_node->set_printable_icon(last_inst_printable);
ItemDeleted(parent_item, inst_root_item); ItemDeleted(parent_item, inst_root_item);
ItemChanged(parent_item); ItemChanged(parent_item);
#ifndef __WXGTK__ parent_node->invalidate_container();
if (parent_node->GetChildCount() == 0)
parent_node->m_container = false;
#endif //__WXGTK__
} }
// update object_node printable property // update object_node printable property
@ -886,10 +892,7 @@ void ObjectDataViewModel::DeleteChildren(wxDataViewItem& parent)
ItemDeleted(parent, item); ItemDeleted(parent, item);
} }
// set m_containet to FALSE if parent has no child root->invalidate_container();
#ifndef __WXGTK__
root->m_container = false;
#endif //__WXGTK__
} }
void ObjectDataViewModel::DeleteVolumeChildren(wxDataViewItem& parent) void ObjectDataViewModel::DeleteVolumeChildren(wxDataViewItem& parent)
@ -919,11 +922,7 @@ void ObjectDataViewModel::DeleteVolumeChildren(wxDataViewItem& parent)
ItemDeleted(parent, item); ItemDeleted(parent, item);
} }
root->m_volumes_cnt = 0; root->m_volumes_cnt = 0;
root->invalidate_container();
// set m_containet to FALSE if parent has no child
#ifndef __WXGTK__
root->m_container = false;
#endif //__WXGTK__
} }
void ObjectDataViewModel::DeleteSettings(const wxDataViewItem& parent) void ObjectDataViewModel::DeleteSettings(const wxDataViewItem& parent)
@ -1681,6 +1680,7 @@ void ObjectDataViewModel::UpdateBitmaps()
m_volume_bmps = MenuFactory::get_volume_bitmaps(); m_volume_bmps = MenuFactory::get_volume_bitmaps();
m_warning_bmp = *get_bmp_bundle(WarningIcon); m_warning_bmp = *get_bmp_bundle(WarningIcon);
m_warning_manifold_bmp = *get_bmp_bundle(WarningManifoldIcon); m_warning_manifold_bmp = *get_bmp_bundle(WarningManifoldIcon);
m_lock_bmp = *get_bmp_bundle(LockIcon);
for (auto item : INFO_ITEMS) for (auto item : INFO_ITEMS)
m_info_bmps[item.first] = get_bmp_bundle(item.second.bmp_name); m_info_bmps[item.first] = get_bmp_bundle(item.second.bmp_name);
@ -1699,10 +1699,8 @@ void ObjectDataViewModel::UpdateBitmaps()
switch (node->m_type) switch (node->m_type)
{ {
case itObject: case itObject:
if (node->m_bmp.IsOk()) node->m_bmp = GetWarningBitmap(node->m_warning_icon_name);
break;
case itVolume: case itVolume:
node->m_bmp = GetVolumeIcon(node->m_volume_type, node->m_warning_icon_name); UpdateBitmapForNode(node);
break; break;
case itLayerRoot: case itLayerRoot:
node->m_bmp = *get_bmp_bundle(LayerRootIcon); node->m_bmp = *get_bmp_bundle(LayerRootIcon);
@ -1720,27 +1718,6 @@ void ObjectDataViewModel::UpdateBitmaps()
} }
} }
wxBitmapBundle ObjectDataViewModel::GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, const std::string& warning_icon_name/* = std::string()*/)
{
if (warning_icon_name.empty())
return *m_volume_bmps[static_cast<int>(vol_type)];
std::string scaled_bitmap_name = warning_icon_name + std::to_string(static_cast<int>(vol_type));
scaled_bitmap_name += "-em" + std::to_string(wxGetApp().em_unit()) + (wxGetApp().dark_mode() ? "-dm" : "-lm");
wxBitmapBundle *bmp = m_bitmap_cache->find_bndl(scaled_bitmap_name);
if (bmp == nullptr) {
std::vector<wxBitmapBundle*> bmps;
bmps.emplace_back(&GetWarningBitmap(warning_icon_name));
bmps.emplace_back(m_volume_bmps[static_cast<int>(vol_type)]);
bmp = m_bitmap_cache->insert_bndl(scaled_bitmap_name, bmps);
}
return *bmp;
}
void ObjectDataViewModel::AddWarningIcon(const wxDataViewItem& item, const std::string& warning_icon_name) void ObjectDataViewModel::AddWarningIcon(const wxDataViewItem& item, const std::string& warning_icon_name)
{ {
if (!item.IsOk()) if (!item.IsOk())
@ -1748,13 +1725,14 @@ void ObjectDataViewModel::AddWarningIcon(const wxDataViewItem& item, const std::
ObjectDataViewModelNode *node = static_cast<ObjectDataViewModelNode*>(item.GetID()); ObjectDataViewModelNode *node = static_cast<ObjectDataViewModelNode*>(item.GetID());
if (node->GetType() & itObject) { if (node->GetType() & itObject) {
node->SetWarningBitmap(GetWarningBitmap(warning_icon_name), warning_icon_name); UpdateBitmapForNode(node, warning_icon_name, node->has_lock());
return; return;
} }
if (node->GetType() & itVolume) { if (node->GetType() & itVolume) {
node->SetWarningBitmap(GetVolumeIcon(node->GetVolumeType(), warning_icon_name), warning_icon_name); UpdateBitmapForNode(node, warning_icon_name, node->has_lock());
node->GetParent()->SetWarningBitmap(GetWarningBitmap(warning_icon_name), warning_icon_name); if (ObjectDataViewModelNode* parent = node->GetParent())
UpdateBitmapForNode(parent, warning_icon_name, parent->has_lock());
return; return;
} }
} }
@ -1769,12 +1747,9 @@ void ObjectDataViewModel::DeleteWarningIcon(const wxDataViewItem& item, const bo
if (!node->GetBitmap().IsOk() || !(node->GetType() & (itVolume | itObject))) if (!node->GetBitmap().IsOk() || !(node->GetType() & (itVolume | itObject)))
return; return;
if (node->GetType() & itVolume) { node->SetWarningIconName(std::string());
node->SetWarningBitmap(*m_volume_bmps[static_cast<int>(node->volume_type())], ""); UpdateBitmapForNode(node);
return;
}
node->SetWarningBitmap(wxNullBitmap, "");
if (unmark_object) if (unmark_object)
{ {
wxDataViewItemArray children; wxDataViewItemArray children;
@ -1801,6 +1776,26 @@ void ObjectDataViewModel::UpdateWarningIcon(const wxDataViewItem& item, const st
AddWarningIcon(item, warning_icon_name); AddWarningIcon(item, warning_icon_name);
} }
void ObjectDataViewModel::UpdateLockIcon(const wxDataViewItem& item, bool has_lock)
{
if (!item.IsOk())
return;
ObjectDataViewModelNode* node = static_cast<ObjectDataViewModelNode*>(item.GetID());
if (node->has_lock() == has_lock)
return;
node->SetLock(has_lock);
UpdateBitmapForNode(node);
if (node->GetType() & itObject) {
wxDataViewItemArray children;
GetChildren(item, children);
for (const wxDataViewItem& child : children)
UpdateLockIcon(child, has_lock);
}
ItemChanged(item);
}
} // namespace GUI } // namespace GUI
} // namespace Slic3r } // namespace Slic3r

View File

@ -51,6 +51,7 @@ enum class InfoItemType
Undef, Undef,
CustomSupports, CustomSupports,
CustomSeam, CustomSeam,
CutConnectors,
MmuSegmentation, MmuSegmentation,
Sinking, Sinking,
VariableLayerHeight VariableLayerHeight
@ -79,9 +80,10 @@ class ObjectDataViewModelNode
PrintIndicator m_printable {piUndef}; PrintIndicator m_printable {piUndef};
wxBitmapBundle m_printable_icon; wxBitmapBundle m_printable_icon;
std::string m_warning_icon_name{ "" }; std::string m_warning_icon_name{ "" };
bool m_has_lock{false};
std::string m_action_icon_name = ""; std::string m_action_icon_name = "";
ModelVolumeType m_volume_type; ModelVolumeType m_volume_type{ -1 };
InfoItemType m_info_item_type {InfoItemType::Undef}; InfoItemType m_info_item_type {InfoItemType::Undef};
public: public:
@ -99,10 +101,8 @@ public:
ObjectDataViewModelNode(ObjectDataViewModelNode* parent, ObjectDataViewModelNode(ObjectDataViewModelNode* parent,
const wxString& sub_obj_name, const wxString& sub_obj_name,
Slic3r::ModelVolumeType type, Slic3r::ModelVolumeType type,
const wxBitmapBundle& bmp,
const wxString& extruder, const wxString& extruder,
const int idx = -1, const int idx = -1 );
const std::string& warning_icon_name = std::string());
ObjectDataViewModelNode(ObjectDataViewModelNode* parent, ObjectDataViewModelNode(ObjectDataViewModelNode* parent,
const t_layer_height_range& layer_range, const t_layer_height_range& layer_range,
@ -128,7 +128,9 @@ public:
} }
void init_container(); void init_container();
bool IsContainer() const void invalidate_container();
bool IsContainer() const
{ {
return m_container; return m_container;
} }
@ -181,7 +183,8 @@ public:
void SetVolumeType(ModelVolumeType type) { m_volume_type = type; } void SetVolumeType(ModelVolumeType type) { m_volume_type = type; }
void SetBitmap(const wxBitmapBundle &icon) { m_bmp = icon; } void SetBitmap(const wxBitmapBundle &icon) { m_bmp = icon; }
void SetExtruder(const wxString &extruder) { m_extruder = extruder; } void SetExtruder(const wxString &extruder) { m_extruder = extruder; }
void SetWarningBitmap(const wxBitmapBundle& icon, const std::string& warning_icon_name) { m_bmp = icon; m_warning_icon_name = warning_icon_name; } void SetWarningIconName(const std::string& warning_icon_name) { m_warning_icon_name = warning_icon_name; }
void SetLock(bool has_lock) { m_has_lock = has_lock; }
const wxBitmapBundle& GetBitmap() const { return m_bmp; } const wxBitmapBundle& GetBitmap() const { return m_bmp; }
const wxString& GetName() const { return m_name; } const wxString& GetName() const { return m_name; }
ItemType GetType() const { return m_type; } ItemType GetType() const { return m_type; }
@ -228,8 +231,6 @@ public:
void set_extruder_icon(); void set_extruder_icon();
// Set printable icon for node // Set printable icon for node
void set_printable_icon(PrintIndicator printable); void set_printable_icon(PrintIndicator printable);
// Set warning icon for node
void set_warning_icon(const std::string& warning_icon);
void update_settings_digest_bitmaps(); void update_settings_digest_bitmaps();
bool update_settings_digest(const std::vector<std::string>& categories); bool update_settings_digest(const std::vector<std::string>& categories);
@ -240,7 +241,9 @@ public:
bool valid(); bool valid();
#endif /* NDEBUG */ #endif /* NDEBUG */
bool invalid() const { return m_idx < -1; } bool invalid() const { return m_idx < -1; }
bool has_warning_icon() const { return !m_warning_icon_name.empty(); } bool has_warning_icon() const { return !m_warning_icon_name.empty(); }
bool has_lock() const { return m_has_lock; }
const std::string& warning_icon_name() const { return m_warning_icon_name; }
private: private:
friend class ObjectDataViewModel; friend class ObjectDataViewModel;
@ -262,6 +265,7 @@ class ObjectDataViewModel :public wxDataViewModel
wxBitmapBundle m_empty_bmp; wxBitmapBundle m_empty_bmp;
wxBitmapBundle m_warning_bmp; wxBitmapBundle m_warning_bmp;
wxBitmapBundle m_warning_manifold_bmp; wxBitmapBundle m_warning_manifold_bmp;
wxBitmapBundle m_lock_bmp;
wxDataViewCtrl* m_ctrl { nullptr }; wxDataViewCtrl* m_ctrl { nullptr };
@ -269,15 +273,16 @@ public:
ObjectDataViewModel(); ObjectDataViewModel();
~ObjectDataViewModel(); ~ObjectDataViewModel();
wxDataViewItem Add( const wxString &name, wxDataViewItem AddObject( const wxString &name,
const int extruder, const wxString& extruder,
const std::string& warning_icon_name = std::string()); const std::string& warning_icon_name,
const bool has_lock);
wxDataViewItem AddVolumeChild( const wxDataViewItem &parent_item, wxDataViewItem AddVolumeChild( const wxDataViewItem &parent_item,
const wxString &name, const wxString &name,
const int volume_idx,
const Slic3r::ModelVolumeType volume_type, const Slic3r::ModelVolumeType volume_type,
const std::string& warning_icon_name = std::string(), const std::string& warning_icon_name,
const int extruder = 0, const wxString& extruder);
const bool create_frst_child = true);
wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item);
wxDataViewItem AddInfoChild(const wxDataViewItem &parent_item, InfoItemType info_type); wxDataViewItem AddInfoChild(const wxDataViewItem &parent_item, InfoItemType info_type);
wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num); wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num);
@ -285,7 +290,7 @@ public:
wxDataViewItem AddLayersRoot(const wxDataViewItem &parent_item); wxDataViewItem AddLayersRoot(const wxDataViewItem &parent_item);
wxDataViewItem AddLayersChild( const wxDataViewItem &parent_item, wxDataViewItem AddLayersChild( const wxDataViewItem &parent_item,
const t_layer_height_range& layer_range, const t_layer_height_range& layer_range,
const int extruder = 0, const wxString& extruder,
const int index = -1); const int index = -1);
size_t GetItemIndexForFirstVolume(ObjectDataViewModelNode* node_parent); size_t GetItemIndexForFirstVolume(ObjectDataViewModelNode* node_parent);
wxDataViewItem Delete(const wxDataViewItem &item); wxDataViewItem Delete(const wxDataViewItem &item);
@ -390,6 +395,7 @@ public:
void AddWarningIcon(const wxDataViewItem& item, const std::string& warning_name); void AddWarningIcon(const wxDataViewItem& item, const std::string& warning_name);
void DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false); void DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false);
void UpdateWarningIcon(const wxDataViewItem& item, const std::string& warning_name); void UpdateWarningIcon(const wxDataViewItem& item, const std::string& warning_name);
void UpdateLockIcon(const wxDataViewItem& item, bool has_lock);
bool HasWarningIcon(const wxDataViewItem& item) const; bool HasWarningIcon(const wxDataViewItem& item) const;
t_layer_height_range GetLayerRangeByItem(const wxDataViewItem& item) const; t_layer_height_range GetLayerRangeByItem(const wxDataViewItem& item) const;
@ -403,7 +409,8 @@ private:
wxDataViewItem AddInstanceRoot(const wxDataViewItem& parent_item); wxDataViewItem AddInstanceRoot(const wxDataViewItem& parent_item);
void AddAllChildren(const wxDataViewItem& parent); void AddAllChildren(const wxDataViewItem& parent);
wxBitmapBundle& GetWarningBitmap(const std::string& warning_icon_name); void UpdateBitmapForNode(ObjectDataViewModelNode* node);
void UpdateBitmapForNode(ObjectDataViewModelNode* node, const std::string& warning_icon_name, bool has_lock);
}; };

View File

@ -97,6 +97,7 @@
#include "MsgDialog.hpp" #include "MsgDialog.hpp"
#include "ProjectDirtyStateManager.hpp" #include "ProjectDirtyStateManager.hpp"
#include "Gizmos/GLGizmoSimplify.hpp" // create suggestion notification #include "Gizmos/GLGizmoSimplify.hpp" // create suggestion notification
#include "Gizmos/GLGizmoCut.hpp"
#ifdef __APPLE__ #ifdef __APPLE__
#include "Gizmos/GLGizmosManager.hpp" #include "Gizmos/GLGizmosManager.hpp"
@ -1792,7 +1793,7 @@ struct Plater::priv
std::string get_config(const std::string &key) const; std::string get_config(const std::string &key) const;
std::vector<size_t> load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config, bool used_inches = false); std::vector<size_t> load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config, bool used_inches = false);
std::vector<size_t> load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z = false); std::vector<size_t> load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z = false, bool call_selection_changed = true);
fs::path get_export_file_path(GUI::FileType file_type); fs::path get_export_file_path(GUI::FileType file_type);
wxString get_export_file(GUI::FileType file_type); wxString get_export_file(GUI::FileType file_type);
@ -1807,7 +1808,7 @@ struct Plater::priv
void select_all(); void select_all();
void deselect_all(); void deselect_all();
void remove(size_t obj_idx); void remove(size_t obj_idx);
void delete_object_from_model(size_t obj_idx); bool delete_object_from_model(size_t obj_idx);
void delete_all_objects_from_model(); void delete_all_objects_from_model();
void reset(); void reset();
void mirror(Axis axis); void mirror(Axis axis);
@ -2718,7 +2719,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
// #define AUTOPLACEMENT_ON_LOAD // #define AUTOPLACEMENT_ON_LOAD
std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z) std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z, bool call_selection_changed /*= true*/)
{ {
const Vec3d bed_size = Slic3r::to_3d(this->bed.build_volume().bounding_volume2d().size(), 1.0) - 2.0 * Vec3d::Ones(); const Vec3d bed_size = Slic3r::to_3d(this->bed.build_volume().bounding_volume2d().size(), 1.0) - 2.0 * Vec3d::Ones();
@ -2801,17 +2802,18 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs& mode
notification_manager->close_notification_of_type(NotificationType::UpdatedItemsInfo); notification_manager->close_notification_of_type(NotificationType::UpdatedItemsInfo);
for (const size_t idx : obj_idxs) { for (const size_t idx : obj_idxs) {
wxGetApp().obj_list()->add_object_to_list(idx); wxGetApp().obj_list()->add_object_to_list(idx, call_selection_changed);
} }
update(); if (call_selection_changed) {
// Update InfoItems in ObjectList after update() to use of a correct value of the GLCanvas3D::is_sinking(), update();
// which is updated after a view3D->reload_scene(false, flags & (unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH) call // Update InfoItems in ObjectList after update() to use of a correct value of the GLCanvas3D::is_sinking(),
for (const size_t idx : obj_idxs) // which is updated after a view3D->reload_scene(false, flags & (unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH) call
wxGetApp().obj_list()->update_info_items(idx); for (const size_t idx : obj_idxs)
wxGetApp().obj_list()->update_info_items(idx);
object_list_changed();
object_list_changed();
}
this->schedule_background_process(); this->schedule_background_process();
return obj_idxs; return obj_idxs;
@ -2990,16 +2992,35 @@ void Plater::priv::remove(size_t obj_idx)
} }
void Plater::priv::delete_object_from_model(size_t obj_idx) bool Plater::priv::delete_object_from_model(size_t obj_idx)
{ {
// check if object isn't cut
// show warning message that "cut consistancy" will not be supported any more
ModelObject* obj = model.objects[obj_idx];
if (obj->is_cut()) {
InfoDialog dialog(q, _L("Delete object which is a part of cut object"),
_L("You try to delete an object which is a part of a cut object.\n"
"This action will break a cut correspondence.\n"
"After that PrusaSlicer can't garantie model consistency"),
false, wxYES | wxCANCEL | wxCANCEL_DEFAULT | wxICON_WARNING);
dialog.SetButtonLabel(wxID_YES, _L("Delete object"));
if (dialog.ShowModal() == wxID_CANCEL)
return false;
}
wxString snapshot_label = _L("Delete Object"); wxString snapshot_label = _L("Delete Object");
if (! model.objects[obj_idx]->name.empty()) if (!obj->name.empty())
snapshot_label += ": " + wxString::FromUTF8(model.objects[obj_idx]->name.c_str()); snapshot_label += ": " + wxString::FromUTF8(obj->name.c_str());
Plater::TakeSnapshot snapshot(q, snapshot_label); Plater::TakeSnapshot snapshot(q, snapshot_label);
m_worker.cancel_all(); m_worker.cancel_all();
if (obj->is_cut())
sidebar->obj_list()->invalidate_cut_info_for_object(obj_idx);
model.delete_object(obj_idx); model.delete_object(obj_idx);
update(); update();
object_list_changed(); object_list_changed();
return true;
} }
void Plater::priv::delete_all_objects_from_model() void Plater::priv::delete_all_objects_from_model()
@ -4438,7 +4459,10 @@ void Plater::priv::on_action_split_volumes(SimpleEvent&)
void Plater::priv::on_action_layersediting(SimpleEvent&) void Plater::priv::on_action_layersediting(SimpleEvent&)
{ {
view3D->enable_layers_editing(!view3D->is_layers_editing_enabled()); const bool enable_layersediting = !view3D->is_layers_editing_enabled();
view3D->enable_layers_editing(enable_layersediting);
if (enable_layersediting)
view3D->get_canvas3d()->reset_all_gizmos();
notification_manager->set_move_from_overlay(view3D->is_layers_editing_enabled()); notification_manager->set_move_from_overlay(view3D->is_layers_editing_enabled());
} }
@ -4481,7 +4505,7 @@ void Plater::priv::on_right_click(RBtnEvent& evt)
selection.is_single_full_object() || selection.is_single_full_object() ||
selection.is_multiple_full_instance(); selection.is_multiple_full_instance();
#if ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE
const bool is_part = selection.is_single_volume_or_modifier(); const bool is_part = selection.is_single_volume_or_modifier() && ! selection.is_any_connector();
#else #else
const bool is_part = selection.is_single_volume() || selection.is_single_modifier(); const bool is_part = selection.is_single_volume() || selection.is_single_modifier();
#endif // ENABLE_WORLD_COORDINATE #endif // ENABLE_WORLD_COORDINATE
@ -4753,7 +4777,8 @@ bool Plater::priv::can_split(bool to_objects) const
bool Plater::priv::can_scale_to_print_volume() const bool Plater::priv::can_scale_to_print_volume() const
{ {
const BuildVolume::Type type = this->bed.build_volume().type(); const BuildVolume::Type type = this->bed.build_volume().type();
return !view3D->get_canvas3d()->get_selection().is_empty() && (type == BuildVolume::Type::Rectangle || type == BuildVolume::Type::Circle); return !sidebar->obj_list()->has_selected_cut_object() &&
!view3D->get_canvas3d()->get_selection().is_empty() && (type == BuildVolume::Type::Rectangle || type == BuildVolume::Type::Circle);
} }
bool Plater::priv::layers_height_allowed() const bool Plater::priv::layers_height_allowed() const
@ -4768,16 +4793,19 @@ bool Plater::priv::layers_height_allowed() const
bool Plater::priv::can_mirror() const bool Plater::priv::can_mirror() const
{ {
return get_selection().is_from_single_instance(); return !sidebar->obj_list()->has_selected_cut_object() && get_selection().is_from_single_instance();
} }
bool Plater::priv::can_replace_with_stl() const bool Plater::priv::can_replace_with_stl() const
{ {
return get_selection().get_volume_idxs().size() == 1; return !sidebar->obj_list()->has_selected_cut_object() && get_selection().get_volume_idxs().size() == 1;
} }
bool Plater::priv::can_reload_from_disk() const bool Plater::priv::can_reload_from_disk() const
{ {
if (sidebar->obj_list()->has_selected_cut_object())
return false;
#if ENABLE_RELOAD_FROM_DISK_REWORK #if ENABLE_RELOAD_FROM_DISK_REWORK
// collect selected reloadable ModelVolumes // collect selected reloadable ModelVolumes
std::vector<std::pair<int, int>> selected_volumes = reloadable_volumes(model, get_selection()); std::vector<std::pair<int, int>> selected_volumes = reloadable_volumes(model, get_selection());
@ -4891,8 +4919,12 @@ bool Plater::priv::can_fix_through_netfabb() const
bool Plater::priv::can_simplify() const bool Plater::priv::can_simplify() const
{ {
const int obj_idx = get_selected_object_idx();
// is object for simplification selected // is object for simplification selected
if (get_selected_object_idx() < 0) return false; // cut object can't be simplify
if (obj_idx < 0 || model.objects[obj_idx]->is_cut())
return false;
// is already opened? // is already opened?
if (q->canvas3D()->get_gizmos_manager().get_current_type() == if (q->canvas3D()->get_gizmos_manager().get_current_type() ==
GLGizmosManager::EType::Simplify) GLGizmosManager::EType::Simplify)
@ -4906,8 +4938,7 @@ bool Plater::priv::can_increase_instances() const
|| q->canvas3D()->get_gizmos_manager().is_in_editing_mode()) || q->canvas3D()->get_gizmos_manager().is_in_editing_mode())
return false; return false;
int obj_idx = get_selected_object_idx(); return !sidebar->obj_list()->has_selected_cut_object();
return (0 <= obj_idx) && (obj_idx < (int)model.objects.size());
} }
bool Plater::priv::can_decrease_instances() const bool Plater::priv::can_decrease_instances() const
@ -4916,8 +4947,7 @@ bool Plater::priv::can_decrease_instances() const
|| q->canvas3D()->get_gizmos_manager().is_in_editing_mode()) || q->canvas3D()->get_gizmos_manager().is_in_editing_mode())
return false; return false;
int obj_idx = get_selected_object_idx(); return !sidebar->obj_list()->has_selected_cut_object();
return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && (model.objects[obj_idx]->instances.size() > 1);
} }
bool Plater::priv::can_split_to_objects() const bool Plater::priv::can_split_to_objects() const
@ -5721,7 +5751,7 @@ void Plater::reset_with_confirm()
reset(); reset();
} }
void Plater::delete_object_from_model(size_t obj_idx) { p->delete_object_from_model(obj_idx); } bool Plater::delete_object_from_model(size_t obj_idx) { return p->delete_object_from_model(obj_idx); }
void Plater::remove_selected() void Plater::remove_selected()
{ {
@ -5898,23 +5928,29 @@ void Plater::toggle_layers_editing(bool enable)
canvas3D()->force_main_toolbar_left_action(canvas3D()->get_main_toolbar_item_id("layersediting")); canvas3D()->force_main_toolbar_left_action(canvas3D()->get_main_toolbar_item_id("layersediting"));
} }
void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, ModelObjectCutAttributes attributes) 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"); wxCHECK_RET(obj_idx < p->model.objects.size(), "obj_idx out of bounds");
auto *object = p->model.objects[obj_idx]; auto* object = p->model.objects[obj_idx];
wxCHECK_RET(instance_idx < object->instances.size(), "instance_idx out of bounds"); wxCHECK_RET(instance_idx < object->instances.size(), "instance_idx out of bounds");
if (! attributes.has(ModelObjectCutAttribute::KeepUpper) && ! attributes.has(ModelObjectCutAttribute::KeepLower))
return;
Plater::TakeSnapshot snapshot(this, _L("Cut by Plane"));
wxBusyCursor wait; wxBusyCursor wait;
const auto new_objects = object->cut(instance_idx, z, attributes);
remove(obj_idx); const auto new_objects = object->cut(instance_idx, cut_matrix, attributes);
p->load_model_objects(new_objects);
model().delete_object(obj_idx);
sidebar().obj_list()->delete_object_from_list(obj_idx);
// suppress to call selection update for Object List to avoid call of early Gizmos on/off update
p->load_model_objects(new_objects, false, false);
// now process all updates of the 3d scene
update();
// Update InfoItems in ObjectList after update() to use of a correct value of the GLCanvas3D::is_sinking(),
// which is updated after a view3D->reload_scene(false, flags & (unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH) call
for (size_t idx = 0; idx < p->model.objects.size(); idx++)
wxGetApp().obj_list()->update_info_items(idx);
Selection& selection = p->get_selection(); Selection& selection = p->get_selection();
size_t last_id = p->model.objects.size() - 1; size_t last_id = p->model.objects.size() - 1;

View File

@ -242,7 +242,7 @@ public:
void remove(size_t obj_idx); void remove(size_t obj_idx);
void reset(); void reset();
void reset_with_confirm(); void reset_with_confirm();
void delete_object_from_model(size_t obj_idx); bool delete_object_from_model(size_t obj_idx);
void remove_selected(); void remove_selected();
void increase_instances(size_t num = 1); void increase_instances(size_t num = 1);
void decrease_instances(size_t num = 1); void decrease_instances(size_t num = 1);
@ -253,7 +253,7 @@ public:
void convert_unit(ConversionType conv_type); void convert_unit(ConversionType conv_type);
void toggle_layers_editing(bool enable); void toggle_layers_editing(bool enable);
void cut(size_t obj_idx, size_t instance_idx, coordf_t z, ModelObjectCutAttributes attributes); void cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes);
void export_gcode(bool prefer_removable); void export_gcode(bool prefer_removable);
void export_stl_obj(bool extended = false, bool selection_only = false); void export_stl_obj(bool extended = false, bool selection_only = false);

View File

@ -163,7 +163,7 @@ void SavePresetDialog::Item::update()
if (m_valid_type == ValidationType::Valid && existing) if (m_valid_type == ValidationType::Valid && existing)
{ {
if (m_preset_name == m_presets->get_selected_preset_name()) { if (m_preset_name == m_presets->get_selected_preset_name()) {
if (!rename && m_presets->get_edited_preset().is_dirty || if ((!rename && m_presets->get_edited_preset().is_dirty) ||
m_parent->get_preset_bundle()) // means that we save modifications from the DiffDialog m_parent->get_preset_bundle()) // means that we save modifications from the DiffDialog
info_line = _L("Save preset modifications to existing user profile"); info_line = _L("Save preset modifications to existing user profile");
else else

View File

@ -510,6 +510,28 @@ void Selection::volumes_changed(const std::vector<size_t> &map_volume_old_to_new
this->set_bounding_boxes_dirty(); this->set_bounding_boxes_dirty();
} }
bool Selection::is_any_connector() const
{
const int obj_idx = get_object_idx();
if ((is_any_volume() || is_any_modifier() || is_mixed()) && // some solid_part AND/OR modifier is selected
obj_idx >= 0 && m_model->objects[obj_idx]->is_cut()) {
const ModelVolumePtrs& obj_volumes = m_model->objects[obj_idx]->volumes;
for (size_t vol_idx = 0; vol_idx < obj_volumes.size(); vol_idx++)
if (obj_volumes[vol_idx]->is_cut_connector())
for (const GLVolume* v : *m_volumes)
if (v->object_idx() == obj_idx && v->volume_idx() == (int)vol_idx && v->selected)
return true;
}
return false;
}
bool Selection::is_any_cut_volume() const
{
const int obj_idx = get_object_idx();
return is_any_volume() && obj_idx >= 0 && m_model->objects[obj_idx]->is_cut();
}
bool Selection::is_single_full_instance() const bool Selection::is_single_full_instance() const
{ {
if (m_type == SingleFullInstance) if (m_type == SingleFullInstance)

View File

@ -320,6 +320,8 @@ public:
bool is_single_volume() const { return m_type == SingleVolume; } bool is_single_volume() const { return m_type == SingleVolume; }
bool is_multiple_volume() const { return m_type == MultipleVolume; } bool is_multiple_volume() const { return m_type == MultipleVolume; }
bool is_any_volume() const { return is_single_volume() || is_multiple_volume(); } bool is_any_volume() const { return is_single_volume() || is_multiple_volume(); }
bool is_any_connector() const;
bool is_any_cut_volume() const;
bool is_mixed() const { return m_type == Mixed; } bool is_mixed() const { return m_type == Mixed; }
bool is_from_single_instance() const { return get_instance_idx() != -1; } bool is_from_single_instance() const { return get_instance_idx() != -1; }
bool is_from_single_object() const; bool is_from_single_object() const;

View File

@ -414,7 +414,7 @@ public:
std::string get_left_preset_name(Preset::Type type); std::string get_left_preset_name(Preset::Type type);
std::string get_right_preset_name(Preset::Type type); std::string get_right_preset_name(Preset::Type type);
std::vector<std::string> get_selected_options(Preset::Type type) const { return std::move(m_tree->options(type, true)); } std::vector<std::string> get_selected_options(Preset::Type type) const { return m_tree->options(type, true); }
protected: protected:
void on_dpi_changed(const wxRect& suggested_rect) override; void on_dpi_changed(const wxRect& suggested_rect) override;