Merge remote-tracking branch 'PRIVATE/ys_cut' into master
This commit is contained in:
commit
cf0f257d05
@ -43,6 +43,14 @@ set(SLIC3R_GTK "2" CACHE STRING "GTK version to use with wxWidgets on Linux")
|
||||
|
||||
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)
|
||||
set(CMAKE_FIND_FRAMEWORK LAST)
|
||||
set(CMAKE_FIND_APPBUNDLE LAST)
|
||||
@ -437,6 +445,14 @@ include_directories(BEFORE SYSTEM ${EIGEN3_INCLUDE_DIR})
|
||||
# no matter what.
|
||||
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)
|
||||
|
||||
set(OpenGL_GL_PREFERENCE "LEGACY")
|
||||
|
4
deps/wxWidgets/wxWidgets.cmake
vendored
4
deps/wxWidgets/wxWidgets.cmake
vendored
@ -13,8 +13,8 @@ if (UNIX AND NOT APPLE) # wxWidgets will not use char as the underlying type for
|
||||
endif()
|
||||
|
||||
prusaslicer_add_cmake_project(wxWidgets
|
||||
URL https://github.com/prusa3d/wxWidgets/archive/2a0b365df947138c513a888d707d46248d78a341.zip
|
||||
URL_HASH SHA256=9ab05cd5179196fad4ae702c78eaae9418e73a402cfd390f7438e469b13eb735
|
||||
URL https://github.com/prusa3d/wxWidgets/archive/34b524f8d5134a40a90d93a16360d533af2676ae.zip
|
||||
URL_HASH SHA256=e76ca0dd998905c4dbb86f41f264e6e0468504dc2398f7e7e3bba8dc37de2f45
|
||||
DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} dep_TIFF dep_JPEG dep_NanoSVG
|
||||
CMAKE_ARGS
|
||||
-DwxBUILD_PRECOMP=ON
|
||||
|
13
resources/icons/collapse_btn.svg
Normal file
13
resources/icons/collapse_btn.svg
Normal 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
28
resources/icons/cut_.svg
Normal 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 |
26
resources/icons/cut_connectors.svg
Normal file
26
resources/icons/cut_connectors.svg
Normal 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 |
12
resources/icons/expand_btn.svg
Normal file
12
resources/icons/expand_btn.svg
Normal 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 |
@ -32,6 +32,7 @@ src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp
|
||||
src/slic3r/GUI/GUI.cpp
|
||||
|
@ -426,7 +426,9 @@ int CLI::run(int argc, char **argv)
|
||||
o->cut(Z, m_config.opt_float("cut"), &out);
|
||||
}
|
||||
#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
|
||||
model.delete_object(size_t(0));
|
||||
}
|
||||
|
@ -140,6 +140,7 @@ namespace ImGui
|
||||
const wchar_t CancelButton = 0x14;
|
||||
const wchar_t CancelHoverButton = 0x15;
|
||||
// const wchar_t VarLayerHeightMarker = 0x16;
|
||||
const wchar_t RevertButton = 0x16;
|
||||
|
||||
const wchar_t RightArrowButton = 0x18;
|
||||
const wchar_t RightArrowHoverButton = 0x19;
|
||||
@ -168,6 +169,10 @@ namespace ImGui
|
||||
const wchar_t LegendCOG = 0x2615;
|
||||
const wchar_t LegendShells = 0x2616;
|
||||
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);
|
||||
}
|
||||
|
@ -19,6 +19,13 @@ void ExPolygon::scale(double 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)
|
||||
{
|
||||
contour.translate(p);
|
||||
|
@ -37,6 +37,7 @@ public:
|
||||
|
||||
void clear() { contour.points.clear(); holes.clear(); }
|
||||
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(const Point &vector);
|
||||
void rotate(double angle);
|
||||
|
@ -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_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 CUT_INFORMATION_FILE = "Metadata/Prusa_Slicer_cut_information.xml";
|
||||
|
||||
static constexpr const char* MODEL_TAG = "model";
|
||||
static constexpr const char* RESOURCES_TAG = "resources";
|
||||
@ -408,6 +409,19 @@ namespace Slic3r {
|
||||
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.
|
||||
typedef std::map<int, int> IdToModelObjectMap;
|
||||
typedef std::map<int, ComponentsList> IdToAliasesMap;
|
||||
@ -416,6 +430,7 @@ namespace Slic3r {
|
||||
typedef std::map<int, Geometry> IdToGeometryMap;
|
||||
typedef std::map<int, std::vector<coordf_t>> IdToLayerHeightsProfileMap;
|
||||
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::DrainHole>> IdToSlaDrainHolesMap;
|
||||
|
||||
@ -443,6 +458,7 @@ namespace Slic3r {
|
||||
IdToGeometryMap m_geometries;
|
||||
CurrentConfig m_curr_config;
|
||||
IdToMetadataMap m_objects_metadata;
|
||||
IdToCutObjectInfoMap m_cut_object_infos;
|
||||
IdToLayerHeightsProfileMap m_layer_heights_profiles;
|
||||
IdToLayerConfigRangesMap m_layer_config_ranges;
|
||||
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 _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_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);
|
||||
@ -676,6 +693,10 @@ namespace Slic3r {
|
||||
// extract slic3r layer heights profile file
|
||||
_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)) {
|
||||
// extract slic3r layer config ranges file
|
||||
_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))
|
||||
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
|
||||
@ -944,6 +978,65 @@ namespace Slic3r {
|
||||
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(
|
||||
mz_zip_archive& archive, const mz_zip_archive_file_stat& stat,
|
||||
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_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_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_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);
|
||||
@ -2281,6 +2375,15 @@ namespace Slic3r {
|
||||
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").
|
||||
// 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!
|
||||
@ -2781,6 +2884,67 @@ namespace Slic3r {
|
||||
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)
|
||||
{
|
||||
assert(is_decimal_separator_point());
|
||||
|
@ -464,12 +464,25 @@ static constexpr const double volume_threshold_inches = 9.0; // 9 = 3*3*3;
|
||||
|
||||
bool Model::looks_like_imperial_units() const
|
||||
{
|
||||
if (this->objects.size() == 0)
|
||||
if (this->objects.empty())
|
||||
return false;
|
||||
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
@ -613,6 +626,7 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs)
|
||||
this->layer_height_profile = rhs.layer_height_profile;
|
||||
this->printable = rhs.printable;
|
||||
this->origin_translation = rhs.origin_translation;
|
||||
this->cut_id.copy(rhs.cut_id);
|
||||
m_bounding_box = rhs.m_bounding_box;
|
||||
m_bounding_box_valid = rhs.m_bounding_box_valid;
|
||||
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);
|
||||
if (type != ModelVolumeType::INVALID && v->type() != type)
|
||||
v->set_type(type);
|
||||
v->cut_info = other.cut_info;
|
||||
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.
|
||||
// v->center_geometry_after_creation();
|
||||
@ -1189,76 +1204,246 @@ size_t ModelObject::parts_count() const
|
||||
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))
|
||||
return {};
|
||||
assert(is_cut());
|
||||
for (const ModelVolume* v : this->volumes)
|
||||
if (v->cut_info.is_connector)
|
||||
return true;
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start";
|
||||
|
||||
// Clone the object to duplicate instances, materials etc.
|
||||
ModelObject* upper = attributes.has(ModelObjectCutAttribute::KeepUpper) ? ModelObject::new_clone(*this) : nullptr;
|
||||
ModelObject* lower = attributes.has(ModelObjectCutAttribute::KeepLower) ? ModelObject::new_clone(*this) : nullptr;
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
|
||||
upper->set_model(nullptr);
|
||||
upper->sla_support_points.clear();
|
||||
upper->sla_drain_holes.clear();
|
||||
upper->sla_points_status = sla::PointsStatus::NoPoints;
|
||||
upper->clear_volumes();
|
||||
upper->input_file.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower)) {
|
||||
lower->set_model(nullptr);
|
||||
lower->sla_support_points.clear();
|
||||
lower->sla_drain_holes.clear();
|
||||
lower->sla_points_status = sla::PointsStatus::NoPoints;
|
||||
lower->clear_volumes();
|
||||
lower->input_file.clear();
|
||||
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;
|
||||
}
|
||||
|
||||
// Because transformations are going to be applied to meshes directly,
|
||||
// we reset transformation of all instances and volumes,
|
||||
// except for translation and Z-rotation on instances, which are preserved
|
||||
// in the transformation matrix and not applied to the mesh transform.
|
||||
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);
|
||||
|
||||
// const auto instance_matrix = instances[instance]->get_matrix(true);
|
||||
const auto instance_matrix = Geometry::assemble_transform(
|
||||
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_scaling_factor(),
|
||||
instances[instance]->get_mirror()
|
||||
);
|
||||
return connector_mesh;
|
||||
}
|
||||
|
||||
z -= instances[instance]->get_offset().z();
|
||||
void ModelObject::apply_cut_connectors(const std::string& new_name)
|
||||
{
|
||||
if (cut_connectors.empty())
|
||||
return;
|
||||
|
||||
// Displacement (in instance coordinates) to be applied to place the upper parts
|
||||
Vec3d local_displace = Vec3d::Zero();
|
||||
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();
|
||||
|
||||
for (ModelVolume *volume : volumes) {
|
||||
const auto volume_matrix = volume->get_matrix();
|
||||
|
||||
volume->supported_facets.reset();
|
||||
volume->seam_facets.reset();
|
||||
volume->mmu_segmentation_facets.reset();
|
||||
// ! 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();
|
||||
|
||||
if (! volume->is_model_part()) {
|
||||
// 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));
|
||||
|
||||
volume->set_transformation(Geometry::Transformation(instance_matrix * volume_matrix));
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
// 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))
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && (bb.max[Z] <= 0 || is_crossed_by_cut))
|
||||
lower->add_volume(*volume);
|
||||
}
|
||||
else if (! volume->mesh().empty()) {
|
||||
|
||||
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(instance_matrix * volume_matrix, true);
|
||||
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();
|
||||
@ -1266,33 +1451,24 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttr
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
// 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()) {
|
||||
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());
|
||||
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
|
||||
@ -1300,52 +1476,156 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttr
|
||||
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 {};
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start";
|
||||
|
||||
// apply cut attributes for object
|
||||
apply_cut_attributes(attributes);
|
||||
|
||||
// Clone the object to duplicate instances, materials etc.
|
||||
ModelObject* upper{ nullptr };
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
clone_for_cut(&upper);
|
||||
|
||||
ModelObject* lower{ nullptr };
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower))
|
||||
clone_for_cut(&lower);
|
||||
|
||||
std::vector<ModelObject*> dowels;
|
||||
|
||||
using namespace Geometry;
|
||||
|
||||
// Because transformations are going to be applied to meshes directly,
|
||||
// we reset transformation of all instances and volumes,
|
||||
// except for translation and Z-rotation on instances, which are preserved
|
||||
// in the transformation matrix and not applied to the mesh transform.
|
||||
|
||||
// const auto instance_matrix = instances[instance]->get_matrix(true);
|
||||
const auto instance_matrix = assemble_transform(
|
||||
Vec3d::Zero(), // don't apply offset
|
||||
instances[instance]->get_rotation(),
|
||||
instances[instance]->get_scaling_factor(),
|
||||
instances[instance]->get_mirror()
|
||||
);
|
||||
|
||||
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
|
||||
Vec3d local_displace = Vec3d::Zero();
|
||||
Vec3d local_dowels_displace = Vec3d::Zero();
|
||||
|
||||
for (ModelVolume* volume : volumes) {
|
||||
volume->reset_extra_facets();
|
||||
|
||||
if (!volume->is_model_part()) {
|
||||
if (volume->cut_info.is_processed)
|
||||
process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, attributes, upper, lower);
|
||||
else
|
||||
process_connector_cut(volume, attributes, upper, lower, dowels, local_dowels_displace);
|
||||
}
|
||||
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;
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && upper->volumes.size() > 0) {
|
||||
if (!upper->origin_translation.isApprox(Vec3d::Zero()) && instances[instance]->get_offset().isApprox(Vec3d::Zero())) {
|
||||
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));
|
||||
}
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper->volumes.empty()) {
|
||||
invalidate_translations(upper, instances[instance]);
|
||||
|
||||
reset_instance_transformation(upper, instance, cut_matrix,
|
||||
attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper),
|
||||
attributes.has(ModelObjectCutAttribute::FlipUpper),
|
||||
local_displace);
|
||||
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
|
||||
for (auto *instance : lower->instances) {
|
||||
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));
|
||||
}
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower->volumes.empty()) {
|
||||
invalidate_translations(lower, instances[instance]);
|
||||
|
||||
reset_instance_transformation(lower, instance, cut_matrix,
|
||||
attributes.has(ModelObjectCutAttribute::PlaceOnCutLower),
|
||||
attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) ? true : attributes.has(ModelObjectCutAttribute::FlipLower));
|
||||
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";
|
||||
|
||||
synchronize_model_after_cut();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@ -2289,6 +2569,14 @@ bool model_has_multi_part_objects(const Model &model)
|
||||
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)
|
||||
{
|
||||
auto config_is_advanced = [](const ModelConfig &config) {
|
||||
|
@ -219,6 +219,91 @@ private:
|
||||
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.
|
||||
enum class ModelVolumeType : int {
|
||||
INVALID = -1,
|
||||
@ -229,7 +314,7 @@ enum class ModelVolumeType : int {
|
||||
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>;
|
||||
ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute);
|
||||
|
||||
@ -269,6 +354,10 @@ public:
|
||||
// Holes to be drilled into the object so resin can flow out
|
||||
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
|
||||
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
|
||||
@ -353,7 +442,20 @@ public:
|
||||
size_t materials_count() const;
|
||||
size_t facets_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 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();
|
||||
// Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees,
|
||||
@ -377,6 +479,9 @@ public:
|
||||
// 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;
|
||||
|
||||
bool is_cut() const { return cut_id.id().valid(); }
|
||||
bool has_connectors() const;
|
||||
|
||||
private:
|
||||
friend class Model;
|
||||
// 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);
|
||||
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,
|
||||
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.
|
||||
@ -616,6 +722,37 @@ public:
|
||||
};
|
||||
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.
|
||||
const TriangleMesh& mesh() const { return *m_mesh.get(); }
|
||||
#if ENABLE_RAYCAST_PICKING
|
||||
@ -652,6 +789,8 @@ public:
|
||||
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; }
|
||||
t_model_material_id material_id() const { return m_material_id; }
|
||||
void reset_extra_facets();
|
||||
void apply_tolerance();
|
||||
void set_material_id(t_model_material_id material_id);
|
||||
ModelMaterial* material() const;
|
||||
void set_material(t_model_material_id material_id, const ModelMaterial &material);
|
||||
@ -821,7 +960,8 @@ private:
|
||||
ObjectBase(other),
|
||||
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),
|
||||
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->config.id().valid());
|
||||
@ -841,7 +981,8 @@ private:
|
||||
}
|
||||
// Providing a new mesh, therefore this volume will get a new unique ID assigned.
|
||||
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->config.id().valid());
|
||||
@ -883,7 +1024,7 @@ private:
|
||||
}
|
||||
template<class Archive> void load(Archive &ar) {
|
||||
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, seam_facets);
|
||||
cereal::load_by_value(ar, mmu_segmentation_facets);
|
||||
@ -899,7 +1040,7 @@ private:
|
||||
}
|
||||
template<class Archive> void save(Archive &ar) const {
|
||||
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, seam_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.
|
||||
// Either the model cannot be loaded, or a SLA printer has to be activated.
|
||||
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.
|
||||
bool model_has_advanced_features(const Model &model);
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
#define slic3r_ObjectID_hpp_
|
||||
|
||||
#include <cereal/access.hpp>
|
||||
#include <cereal/types/base_class.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@ -89,7 +90,9 @@ private:
|
||||
friend class cereal::access;
|
||||
friend class Slic3r::UndoRedo::StackImpl;
|
||||
template<class Archive> void serialize(Archive &ar) { ar(m_id); }
|
||||
protected: // #vbCHECKME && #ysFIXME
|
||||
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); }
|
||||
};
|
||||
|
||||
@ -128,6 +131,64 @@ private:
|
||||
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.
|
||||
extern ObjectID wipe_tower_object_id();
|
||||
extern ObjectID wipe_tower_instance_id();
|
||||
|
@ -1061,6 +1061,61 @@ indexed_triangle_set its_make_sphere(double radius, double fa)
|
||||
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)
|
||||
{
|
||||
std::vector<Vec3f> dst_vertices;
|
||||
|
@ -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_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_frustum_dowel(double r, double h, int sectorCount);
|
||||
indexed_triangle_set its_make_pyramid(float base, float height);
|
||||
indexed_triangle_set its_make_sphere(double radius, double fa);
|
||||
|
||||
|
@ -262,9 +262,9 @@ constexpr inline T lerp(const T& a, const T& b, Number t)
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -256,6 +256,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
Utils/WinRegistry.hpp
|
||||
)
|
||||
|
||||
find_package(NanoSVG REQUIRED)
|
||||
|
||||
if (APPLE)
|
||||
list(APPEND SLIC3R_GUI_SOURCES
|
||||
Utils/RetinaHelperImpl.mm
|
||||
|
@ -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.
|
||||
// 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)
|
||||
// with the exception that here one sets the decimal separator explicitely to dot.
|
||||
|
@ -1367,6 +1367,8 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject
|
||||
&& (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)) {
|
||||
vol->is_active = visible;
|
||||
if (!vol->is_modifier)
|
||||
vol->color.a(1.f);
|
||||
|
||||
if (instance_idx == -1) {
|
||||
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();
|
||||
auto gizmo_type = gm.get_current_type();
|
||||
if ( (gizmo_type == GLGizmosManager::FdmSupports
|
||||
|| gizmo_type == GLGizmosManager::Seam)
|
||||
&& ! vol->is_modifier)
|
||||
|| gizmo_type == GLGizmosManager::Seam
|
||||
|| gizmo_type == GLGizmosManager::Cut)
|
||||
&& ! vol->is_modifier) {
|
||||
vol->force_neutral_color = true;
|
||||
if (gizmo_type == GLGizmosManager::Cut)
|
||||
vol->color.a(0.95f);
|
||||
}
|
||||
else if (gizmo_type == GLGizmosManager::MmuSegmentation)
|
||||
vol->is_active = false;
|
||||
else
|
||||
@ -3371,6 +3377,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
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;
|
||||
}
|
||||
@ -3446,6 +3457,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
if (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports &&
|
||||
m_gizmos.get_current_type() != GLGizmosManager::FdmSupports &&
|
||||
m_gizmos.get_current_type() != GLGizmosManager::Seam &&
|
||||
m_gizmos.get_current_type() != GLGizmosManager::Cut &&
|
||||
m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation) {
|
||||
m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect);
|
||||
m_dirty = true;
|
||||
@ -3495,7 +3507,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
const int volume_idx = get_first_hover_volume_idx();
|
||||
BoundingBoxf3 volume_bbox = m_volumes.volumes[volume_idx]->transformed_bounding_box();
|
||||
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;
|
||||
// The dragging operation is initiated.
|
||||
m_mouse.drag.move_volume_idx = volume_idx;
|
||||
|
@ -2890,6 +2890,13 @@ bool GUI_App::may_switch_to_SLA_preset(const wxString& caption)
|
||||
caption);
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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), "",
|
||||
[](wxCommandEvent&) { obj_list()->load_subobject(ModelVolumeType::MODEL_PART); },
|
||||
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) {
|
||||
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));
|
||||
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);
|
||||
@ -680,6 +685,21 @@ wxMenuItem* MenuFactory::append_menu_item_printable(wxMenu* menu)
|
||||
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)
|
||||
{
|
||||
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;
|
||||
for (int obj_idx : obj_idxs) {
|
||||
ModelObject* object = obj_list()->object(obj_idx);
|
||||
if (object->is_cut())
|
||||
return false;
|
||||
if (vol_idxs.empty()) {
|
||||
for (ModelVolume* volume : object->volumes)
|
||||
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_change_extruder(&m_object_menu);
|
||||
update_menu_items_instance_manipulation(mtObjectFFF);
|
||||
append_menu_item_invalidate_cut_info(&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_item_settings(&m_sla_object_menu);
|
||||
update_menu_items_instance_manipulation(mtObjectSLA);
|
||||
append_menu_item_invalidate_cut_info(&m_sla_object_menu);
|
||||
|
||||
return &m_sla_object_menu;
|
||||
}
|
||||
@ -1056,6 +1080,9 @@ wxMenu* MenuFactory::multi_selection_menu()
|
||||
wxDataViewItemArray sels;
|
||||
obj_list()->GetSelections(sels);
|
||||
|
||||
if (sels.IsEmpty())
|
||||
return nullptr;
|
||||
|
||||
for (const wxDataViewItem& item : sels)
|
||||
if (!(list_model()->GetItemType(item) & (itVolume | itObject | itInstance)))
|
||||
// show this menu only for Objects(Instances mixed with Objects)/Volumes selection
|
||||
|
@ -89,6 +89,7 @@ private:
|
||||
wxMenuItem* append_menu_item_change_type(wxMenu* menu);
|
||||
wxMenuItem* append_menu_item_instance_to_object(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);
|
||||
wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu);
|
||||
wxMenuItem* append_menu_item_simplify(wxMenu* menu);
|
||||
|
@ -11,6 +11,8 @@
|
||||
#include "GalleryDialog.hpp"
|
||||
#include "MainFrame.hpp"
|
||||
#include "slic3r/Utils/UndoRedo.hpp"
|
||||
#include "Gizmos/GLGizmoCut.hpp"
|
||||
#include "Gizmos/GLGizmoScale.hpp"
|
||||
|
||||
#include "OptionsGroup.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)
|
||||
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 ?
|
||||
(*m_objects)[obj_idx]->get_object_stl_stats() :
|
||||
(*m_objects)[obj_idx]->volumes[vol_idx]->mesh().stats();
|
||||
@ -673,6 +682,8 @@ void ObjectList::selection_changed()
|
||||
|
||||
fix_multiselection_conflicts();
|
||||
|
||||
fix_cut_selection();
|
||||
|
||||
// update object selection on Plater
|
||||
if (!m_prevent_canvas_selection_update)
|
||||
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();
|
||||
}
|
||||
|
||||
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*/)
|
||||
{
|
||||
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
|
||||
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();
|
||||
}
|
||||
|
||||
@ -1804,22 +1827,22 @@ void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name
|
||||
#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
|
||||
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;
|
||||
ItemType type;
|
||||
|
||||
m_objects_model->GetItemInfo(item, type, obj_idx, idx);
|
||||
if (type == itUndef)
|
||||
return;
|
||||
return false;
|
||||
|
||||
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));
|
||||
else if (type & itInfo && obj_idx != -1)
|
||||
del_info_item(obj_idx, m_objects_model->GetInfoItemType(item));
|
||||
else if (idx == -1)
|
||||
return;
|
||||
else if (!del_subobject_from_object(obj_idx, idx, type))
|
||||
return;
|
||||
else if (idx == -1 || !del_subobject_from_object(obj_idx, idx, type))
|
||||
return false;
|
||||
|
||||
// If last volume item with warning was deleted, unmark object item
|
||||
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());
|
||||
m_objects_model->UpdateWarningIcon(parent, icon_name);
|
||||
}
|
||||
|
||||
else
|
||||
m_objects_model->Delete(item);
|
||||
|
||||
update_info_items(obj_idx);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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();
|
||||
break;
|
||||
|
||||
case InfoItemType::CutConnectors:
|
||||
show_error(nullptr, _L("Connectors cannot be deleted from cut object."));
|
||||
break;
|
||||
|
||||
case InfoItemType::MmuSegmentation:
|
||||
cnv->get_gizmos_manager().reset_all_states();
|
||||
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."));
|
||||
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"));
|
||||
|
||||
@ -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."));
|
||||
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"));
|
||||
object->delete_instance(idx);
|
||||
@ -2038,30 +2081,11 @@ void ObjectList::split()
|
||||
|
||||
volume->split(nozzle_dmrs_cnt);
|
||||
|
||||
(*m_objects)[obj_idx]->input_file.clear();
|
||||
|
||||
wxBusyCursor wait;
|
||||
|
||||
auto model_object = (*m_objects)[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);
|
||||
add_volumes_to_object_in_list(obj_idx);
|
||||
|
||||
changed_object(obj_idx);
|
||||
// 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();
|
||||
if (obj_idx < 0)
|
||||
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 (*m_objects)[obj_idx]->volumes[0]->is_splittable();
|
||||
return object->volumes[0]->is_splittable();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -2415,9 +2442,59 @@ bool ObjectList::can_split_instances()
|
||||
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
|
||||
{
|
||||
if (printer_technology() == ptSLA)
|
||||
if (printer_technology() == ptSLA || has_selected_cut_object())
|
||||
return false;
|
||||
|
||||
wxDataViewItemArray sels;
|
||||
@ -2445,17 +2522,7 @@ bool ObjectList::can_merge_to_single_object() const
|
||||
|
||||
wxPoint ObjectList::get_mouse_position_in_control() const
|
||||
{
|
||||
wxPoint pt = 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;
|
||||
return wxGetMousePosition() - this->GetScreenPosition();
|
||||
}
|
||||
|
||||
// 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_layers = false;
|
||||
|
||||
bool enable_manipulation {true};
|
||||
bool disable_ss_manipulation {false};
|
||||
bool disable_ununiform_scale {false};
|
||||
|
||||
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");
|
||||
|
||||
const Selection& selection = scene_selection();
|
||||
// don't show manipulation panel for case of all Object's parts selection
|
||||
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 {
|
||||
if (item) {
|
||||
@ -2491,11 +2609,12 @@ void ObjectList::part_selection_changed()
|
||||
const wxDataViewItem parent = m_objects_model->GetParent(item);
|
||||
const ItemType parent_type = m_objects_model->GetItemType(parent);
|
||||
obj_idx = m_objects_model->GetObjectIdByItem(item);
|
||||
ModelObject* object = (*m_objects)[obj_idx];
|
||||
|
||||
if (parent == wxDataViewItem(nullptr)
|
||||
|| type == itInfo) {
|
||||
og_name = _L("Object manipulation");
|
||||
m_config = &(*m_objects)[obj_idx]->config;
|
||||
m_config = &object->config;
|
||||
update_and_show_manipulations = true;
|
||||
|
||||
if (type == itInfo) {
|
||||
@ -2514,26 +2633,27 @@ void ObjectList::part_selection_changed()
|
||||
GLGizmosManager::EType gizmo_type = info_type == InfoItemType::CustomSupports ? GLGizmosManager::EType::FdmSupports :
|
||||
info_type == InfoItemType::CustomSeam ? GLGizmosManager::EType::Seam :
|
||||
GLGizmosManager::EType::MmuSegmentation;
|
||||
GLGizmosManager& gizmos_mgr = wxGetApp().plater()->canvas3D()->get_gizmos_manager();
|
||||
if (gizmos_mgr.get_current_type() != gizmo_type)
|
||||
gizmos_mgr.open_gizmo(gizmo_type);
|
||||
break;
|
||||
}
|
||||
case InfoItemType::Sinking: { break; }
|
||||
case InfoItemType::Sinking:
|
||||
default: { break; }
|
||||
}
|
||||
}
|
||||
else
|
||||
disable_ss_manipulation = object->is_cut();
|
||||
}
|
||||
else {
|
||||
if (type & itSettings) {
|
||||
if (parent_type & itObject) {
|
||||
og_name = _L("Object Settings to modify");
|
||||
m_config = &(*m_objects)[obj_idx]->config;
|
||||
m_config = &object->config;
|
||||
}
|
||||
else if (parent_type & itVolume) {
|
||||
og_name = _L("Part Settings to modify");
|
||||
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) {
|
||||
og_name = _L("Layer range Settings to modify");
|
||||
@ -2544,15 +2664,18 @@ void ObjectList::part_selection_changed()
|
||||
else if (type & itVolume) {
|
||||
og_name = _L("Part manipulation");
|
||||
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;
|
||||
const ModelVolume* volume = object->volumes[volume_id];
|
||||
enable_manipulation = !(object->is_cut() && (volume->is_cut_connector() || volume->is_model_part()));
|
||||
}
|
||||
else if (type & itInstance) {
|
||||
og_name = _L("Instance manipulation");
|
||||
update_and_show_manipulations = true;
|
||||
|
||||
// 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)) {
|
||||
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 + " ");
|
||||
|
||||
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_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)
|
||||
@ -2597,6 +2730,7 @@ void ObjectList::part_selection_changed()
|
||||
#else
|
||||
wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false);
|
||||
#endif // ENABLE_WORLD_COORDINATE
|
||||
wxGetApp().plater()->canvas3D()->enable_moving(enable_manipulation); // ysFIXME
|
||||
wxGetApp().obj_manipul() ->UpdateAndShow(update_and_show_manipulations);
|
||||
wxGetApp().obj_settings()->UpdateAndShow(update_and_show_settings);
|
||||
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,
|
||||
InfoItemType::CustomSeam,
|
||||
InfoItemType::CutConnectors,
|
||||
InfoItemType::MmuSegmentation,
|
||||
InfoItemType::Sinking,
|
||||
InfoItemType::VariableLayerHeight}) {
|
||||
@ -2675,6 +2810,9 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio
|
||||
});
|
||||
break;
|
||||
|
||||
case InfoItemType::CutConnectors:
|
||||
should_show = model_object->is_cut() && model_object->has_connectors() && model_object->volumes.size() > 1;
|
||||
break;
|
||||
case InfoItemType::VariableLayerHeight :
|
||||
should_show = printer_technology() == ptFFF
|
||||
&& ! 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)
|
||||
{
|
||||
auto model_object = (*m_objects)[obj_idx];
|
||||
const wxString& item_name = from_u8(model_object->name);
|
||||
const auto item = m_objects_model->Add(item_name,
|
||||
model_object->config.has("extruder") ? model_object->config.extruder() : 0,
|
||||
get_warning_icon_name(model_object->mesh().stats()));
|
||||
const auto item = m_objects_model->AddObject(item_name,
|
||||
extruder2str(model_object->config.has("extruder") ? model_object->config.extruder() : 0),
|
||||
get_warning_icon_name(model_object->mesh().stats()),
|
||||
model_object->is_cut());
|
||||
|
||||
update_info_items(obj_idx, nullptr, call_selection_changed);
|
||||
|
||||
// add volumes to the object
|
||||
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_volumes_to_object_in_list(obj_idx);
|
||||
|
||||
// add instances to the object, if it has those
|
||||
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)); });
|
||||
}
|
||||
|
||||
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)) )
|
||||
return;
|
||||
|
||||
take_snapshot(_(L("Delete Selected Item")));
|
||||
|
||||
if (type&itObject) {
|
||||
del_object(obj_idx);
|
||||
delete_object_from_list(obj_idx);
|
||||
for (size_t obj_idx = 0; obj_idx < (*m_objects).size(); ++obj_idx)
|
||||
if (!(*m_objects)[obj_idx]->is_cut())
|
||||
m_objects_model->UpdateLockIcon(m_objects_model->GetItemById(int(obj_idx)), false);
|
||||
}
|
||||
else {
|
||||
del_subobject_from_object(obj_idx, sub_obj_idx, type);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void ObjectList::delete_from_model_and_list(const std::vector<ItemForDelete>& items_for_delete)
|
||||
bool ObjectList::delete_from_model_and_list(const std::vector<ItemForDelete>& items_for_delete)
|
||||
{
|
||||
if (items_for_delete.empty())
|
||||
return;
|
||||
return false;
|
||||
|
||||
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)))
|
||||
continue;
|
||||
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));
|
||||
if (was_cut)
|
||||
update_lock_icons_for_model();
|
||||
}
|
||||
else {
|
||||
if (!del_subobject_from_object(item->obj_idx, item->sub_obj_idx, item->type))
|
||||
continue;
|
||||
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);
|
||||
if (obj->volumes.size() == 1) {
|
||||
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);
|
||||
}
|
||||
|
||||
m_prevent_list_events = true;
|
||||
m_prevent_list_events = false;
|
||||
if (modified_objects_ids.empty())
|
||||
return false;
|
||||
part_selection_changed();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ObjectList::delete_all_objects_from_list()
|
||||
@ -2960,8 +3158,10 @@ void ObjectList::remove()
|
||||
{
|
||||
wxDataViewItem parent = m_objects_model->GetParent(item);
|
||||
ItemType type = m_objects_model->GetItemType(item);
|
||||
if (type & itObject)
|
||||
delete_from_model_and_list(itObject, m_objects_model->GetIdByItem(item), -1);
|
||||
if (type & itObject) {
|
||||
if (!delete_from_model_and_list(itObject, m_objects_model->GetIdByItem(item), -1))
|
||||
return item;
|
||||
}
|
||||
else {
|
||||
if (type & (itLayer | itInstance)) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
del_subobject_item(item);
|
||||
if (!del_subobject_item(item))
|
||||
return item;
|
||||
}
|
||||
|
||||
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)
|
||||
continue;
|
||||
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,
|
||||
range,
|
||||
config.opt_int("extruder"),
|
||||
extruder2str(config.opt_int("extruder")),
|
||||
layer_idx);
|
||||
add_settings_item(layer_item, &config);
|
||||
}
|
||||
@ -3283,6 +3486,24 @@ bool ObjectList::is_selected(const ItemType type) const
|
||||
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
|
||||
{
|
||||
const wxDataViewItem& item = GetSelection();
|
||||
@ -3409,11 +3630,18 @@ void ObjectList::update_selections()
|
||||
else {
|
||||
for (auto idx : selection.get_volume_idxs()) {
|
||||
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
|
||||
// are not associated with ModelVolumes, but they are temporarily generated by the backend
|
||||
// (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; }
|
||||
}
|
||||
@ -3464,11 +3692,34 @@ void ObjectList::update_selections()
|
||||
if (sels.size() == 0 || m_selection_mode & smSettings)
|
||||
m_selection_mode = smUndef;
|
||||
|
||||
if (fix_cut_selection(sels) || is_connectors_item_selected(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;
|
||||
|
||||
// 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()
|
||||
{
|
||||
@ -3501,6 +3752,18 @@ void ObjectList::update_selections_on_canvas()
|
||||
volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end());
|
||||
}
|
||||
else if (type == itInfo) {
|
||||
if (m_objects_model->GetInfoItemType(item) == InfoItemType::CutConnectors) {
|
||||
mode = Selection::Volume;
|
||||
|
||||
// When selecting CutConnectors info item, select all object volumes, which are marked as a connector
|
||||
const ModelObject* obj = object(obj_idx);
|
||||
for (unsigned int vol_idx = 0; vol_idx < obj->volumes.size(); vol_idx++)
|
||||
if (obj->volumes[vol_idx]->is_cut_connector()) {
|
||||
std::vector<unsigned int> idxs = selection.get_volume_idxs_from_volume(obj_idx, std::max(instance_idx, 0), vol_idx);
|
||||
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();
|
||||
@ -3512,6 +3775,7 @@ void ObjectList::update_selections_on_canvas()
|
||||
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
|
||||
{
|
||||
mode = Selection::Instance;
|
||||
@ -3526,6 +3790,8 @@ void ObjectList::update_selections_on_canvas()
|
||||
|
||||
if (sel_cnt == 1) {
|
||||
wxDataViewItem item = GetSelection();
|
||||
if (m_objects_model->GetInfoItemType(item) == InfoItemType::CutConnectors)
|
||||
selection.remove_all();
|
||||
if (m_objects_model->GetItemType(item) & (itSettings | itInstanceRoot | itLayerRoot | itLayer))
|
||||
add_to_selection(m_objects_model->GetParent(item), selection, instance_idx, mode);
|
||||
else
|
||||
@ -3796,6 +4062,53 @@ void ObjectList::fix_multiselection_conflicts()
|
||||
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()
|
||||
{
|
||||
wxDataViewItem item = GetSelection();
|
||||
@ -3826,10 +4139,11 @@ void ObjectList::change_part_type()
|
||||
if (obj_idx < 0) return;
|
||||
|
||||
const ModelVolumeType type = volume->type();
|
||||
const ModelObject* obj = object(obj_idx);
|
||||
if (type == ModelVolumeType::MODEL_PART)
|
||||
{
|
||||
int model_part_cnt = 0;
|
||||
for (auto vol : (*m_objects)[obj_idx]->volumes) {
|
||||
for (auto vol : obj->volumes) {
|
||||
if (vol->type() == ModelVolumeType::MODEL_PART)
|
||||
++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") };
|
||||
auto new_type = ModelVolumeType(wxGetApp().GetSingleChoiceIndex(_L("Type:"), _L("Select type of part"), wxArrayString(5, names), int(type)));
|
||||
const bool is_cut_object = obj->is_cut();
|
||||
|
||||
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)
|
||||
return;
|
||||
|
||||
@ -4362,33 +4685,14 @@ void ObjectList::set_extruder_for_selected_items(const int extruder) const
|
||||
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];
|
||||
if (object->volumes.size() <= 1)
|
||||
return items;
|
||||
wxDataViewItemArray items = add_volumes_to_object_in_list(obj_idx, std::move(add_to_selection));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -247,7 +247,7 @@ public:
|
||||
void add_category_to_settings_from_frequent(const std::vector<std::string>& category_options, wxDataViewItem item);
|
||||
void show_settings(const wxDataViewItem settings_item);
|
||||
bool is_instance_or_object_selected();
|
||||
|
||||
bool is_selected_object_cut();
|
||||
void load_subobject(ModelVolumeType type, bool from_galery = false);
|
||||
// ! 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);
|
||||
@ -257,8 +257,8 @@ public:
|
||||
void load_shape_object_from_gallery();
|
||||
void load_shape_object_from_gallery(const wxArrayString& input_files);
|
||||
void load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center = true);
|
||||
void del_object(const int obj_idx);
|
||||
void del_subobject_item(wxDataViewItem& item);
|
||||
bool del_object(const int obj_idx);
|
||||
bool del_subobject_item(wxDataViewItem& item);
|
||||
void del_settings_from_config(const wxDataViewItem& parent_item);
|
||||
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);
|
||||
@ -277,6 +277,9 @@ public:
|
||||
bool is_splittable(bool to_objects);
|
||||
bool selected_instances_of_same_object();
|
||||
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_single_object() const;
|
||||
|
||||
@ -288,6 +291,9 @@ public:
|
||||
void changed_object(const int obj_idx = -1) const;
|
||||
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
|
||||
void add_object_to_list(size_t obj_idx, bool call_selection_changed = true);
|
||||
// Delete object from the list
|
||||
@ -295,8 +301,9 @@ public:
|
||||
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_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 delete_from_model_and_list(const std::vector<ItemForDelete>& items_for_delete);
|
||||
void update_lock_icons_for_model();
|
||||
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
|
||||
void delete_all_objects_from_list();
|
||||
// Increase instances count
|
||||
@ -339,6 +346,8 @@ public:
|
||||
void init_objects();
|
||||
bool multiple_selection() 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;
|
||||
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; }
|
||||
@ -353,6 +362,9 @@ public:
|
||||
bool check_last_selection(wxString& msg_str);
|
||||
// correct current selections to avoid of the possible 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();
|
||||
void change_part_type();
|
||||
@ -388,7 +400,7 @@ public:
|
||||
void toggle_printable_state();
|
||||
|
||||
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();
|
||||
bool has_paint_on_segmentation();
|
||||
|
||||
|
@ -576,6 +576,35 @@ void ObjectManipulation::UpdateAndShow(const bool 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()
|
||||
{
|
||||
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
|
||||
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();
|
||||
m_new_move_label_string = L("Translate");
|
||||
m_new_rotate_label_string = L("Rotate");
|
||||
|
@ -64,6 +64,8 @@ public:
|
||||
const std::string& get_full_opt_name() const { return m_full_opt_name; }
|
||||
#endif // ENABLE_WORLD_COORDINATE
|
||||
|
||||
bool has_opt_key(const std::string& key) { return m_opt_key == key; }
|
||||
|
||||
private:
|
||||
double get_value();
|
||||
};
|
||||
@ -197,6 +199,10 @@ public:
|
||||
void Show(const bool show) override;
|
||||
bool IsShown() 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();
|
||||
bool use_colors() { return m_use_colors; }
|
||||
|
||||
|
@ -310,8 +310,8 @@ void GLGizmoBase::set_hover_id(int id)
|
||||
assert(!m_dragging);
|
||||
|
||||
// allow empty grabbers when not using grabbers but use hover_id - flatten, rotate
|
||||
if (!m_grabbers.empty() && id >= (int) m_grabbers.size())
|
||||
return;
|
||||
// if (!m_grabbers.empty() && id >= (int) m_grabbers.size())
|
||||
// return;
|
||||
|
||||
m_hover_id = id;
|
||||
on_set_hover_id();
|
||||
@ -404,14 +404,14 @@ bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) {
|
||||
|
||||
if (mouse_event.LeftDown()) {
|
||||
Selection &selection = m_parent.get_selection();
|
||||
if (!selection.is_empty() && m_hover_id != -1 &&
|
||||
(m_grabbers.empty() || m_hover_id < static_cast<int>(m_grabbers.size()))) {
|
||||
if (!selection.is_empty() && m_hover_id != -1 /* &&
|
||||
(m_grabbers.empty() || m_hover_id < static_cast<int>(m_grabbers.size()))*/) {
|
||||
selection.setup_cache();
|
||||
|
||||
m_dragging = true;
|
||||
for (auto &grabber : m_grabbers) grabber.dragging = false;
|
||||
if (!m_grabbers.empty() && m_hover_id < int(m_grabbers.size()))
|
||||
m_grabbers[m_hover_id].dragging = true;
|
||||
// if (!m_grabbers.empty() && m_hover_id < int(m_grabbers.size()))
|
||||
// m_grabbers[m_hover_id].dragging = true;
|
||||
|
||||
on_start_dragging();
|
||||
|
||||
|
@ -212,6 +212,9 @@ public:
|
||||
void unregister_raycasters_for_picking() { unregister_grabbers_for_picking(); on_unregister_raycasters_for_picking(); }
|
||||
#endif // ENABLE_RAYCAST_PICKING
|
||||
|
||||
virtual bool is_in_editing_mode() const { return false; }
|
||||
virtual bool is_selection_rectangle_dragging() const { return false; }
|
||||
|
||||
protected:
|
||||
virtual bool on_init() = 0;
|
||||
virtual void on_load(cereal::BinaryInputArchive& ar) {}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -2,55 +2,161 @@
|
||||
#define slic3r_GLGizmoCut_hpp_
|
||||
|
||||
#include "GLGizmoBase.hpp"
|
||||
#include "slic3r/GUI/GLSelectionRectangle.hpp"
|
||||
#include "slic3r/GUI/GLModel.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/ObjectID.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
enum class CutConnectorType : int;
|
||||
class ModelVolume;
|
||||
struct CutConnectorAttributes;
|
||||
|
||||
namespace GUI {
|
||||
class Selection;
|
||||
|
||||
class GLGizmoCut : public GLGizmoBase
|
||||
{
|
||||
static const double Offset;
|
||||
static const double Margin;
|
||||
enum class SLAGizmoEventType : unsigned char;
|
||||
|
||||
class GLGizmoCut3D : public GLGizmoBase
|
||||
{
|
||||
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_grabber_connection;
|
||||
Vec3d m_old_center;
|
||||
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
|
||||
GLModel m_cut_line;
|
||||
|
||||
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;
|
||||
GLModel contours;
|
||||
double cut_z{ 0.0 };
|
||||
Vec3d position{ Vec3d::Zero() };
|
||||
Vec3d shift{ Vec3d::Zero() };
|
||||
ObjectID object_id;
|
||||
int instance_idx{ -1 };
|
||||
std::vector<ObjectID> volumes_idxs;
|
||||
std::vector<Transform3d> volumes_trafos;
|
||||
unsigned int outside_cut_contour;
|
||||
unsigned int outside_bb;
|
||||
bool is_overlap;
|
||||
|
||||
void invalidate() {
|
||||
outside_cut_contour = 0;
|
||||
outside_bb = 0;
|
||||
is_overlap = false;
|
||||
}
|
||||
} 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:
|
||||
GLGizmoCut(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);
|
||||
GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
||||
|
||||
std::string get_tooltip() const override;
|
||||
bool unproject_on_cut_plane(const Vec2d& mouse_pos, 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>
|
||||
/// Drag of plane
|
||||
@ -59,29 +165,105 @@ public:
|
||||
/// <returns>Return True when use the information otherwise False.</returns>
|
||||
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:
|
||||
virtual 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); }
|
||||
virtual void on_save(cereal::BinaryOutputArchive& ar) const override { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); }
|
||||
virtual std::string on_get_name() const override;
|
||||
virtual void on_set_state() override;
|
||||
virtual bool on_is_activable() const override;
|
||||
virtual void on_start_dragging() override;
|
||||
virtual void on_dragging(const UpdateData& data) override;
|
||||
virtual void on_render() override;
|
||||
#if ENABLE_RAYCAST_PICKING
|
||||
bool on_init() override;
|
||||
void on_load(cereal::BinaryInputArchive&ar) override;
|
||||
void on_save(cereal::BinaryOutputArchive&ar) const override;
|
||||
std::string on_get_name() const override;
|
||||
void on_set_state() override;
|
||||
CommonGizmosDataID on_get_requirements() const override;
|
||||
void on_set_hover_id() override;
|
||||
bool on_is_activable() const override;
|
||||
bool on_is_selectable() const override;
|
||||
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_unregister_raycasters_for_picking() override;
|
||||
#else
|
||||
virtual void on_render_for_picking() override;
|
||||
#endif // ENABLE_RAYCAST_PICKING
|
||||
virtual void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||
void update_raycasters_for_picking();
|
||||
void set_volumes_picking_state(bool state);
|
||||
void update_raycasters_for_picking_transform();
|
||||
|
||||
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:
|
||||
void set_center(const Vec3d& center);
|
||||
bool render_combo(const std::string& label, const std::vector<std::string>& lines, size_t& selection_idx);
|
||||
bool render_double_input(const std::string& label, double& value_in);
|
||||
bool render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in);
|
||||
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);
|
||||
double calc_projection(const Linef3& mouse_ray) const;
|
||||
BoundingBoxf3 bounding_box() const;
|
||||
void update_contours();
|
||||
void set_center_pos(const Vec3d¢er_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
|
||||
|
@ -308,7 +308,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
|
||||
else {
|
||||
if (m_imgui->button(m_desc.at("reset_direction"))) {
|
||||
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::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")))
|
||||
m_c->object_clipper()->set_position(clp_dist, true);
|
||||
m_c->object_clipper()->set_position_by_ratio(clp_dist, true);
|
||||
|
||||
ImGui::Separator();
|
||||
if (m_imgui->button(m_desc.at("remove_all"))) {
|
||||
|
@ -478,19 +478,19 @@ bool GLGizmoHollow::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_pos
|
||||
if (action == SLAGizmoEventType::MouseWheelUp && control_down) {
|
||||
double pos = m_c->object_clipper()->get_position();
|
||||
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;
|
||||
}
|
||||
|
||||
if (action == SLAGizmoEventType::MouseWheelDown && control_down) {
|
||||
double pos = m_c->object_clipper()->get_position();
|
||||
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;
|
||||
}
|
||||
|
||||
if (action == SLAGizmoEventType::ResetClippingPlane) {
|
||||
m_c->object_clipper()->set_position(-1., false);
|
||||
m_c->object_clipper()->set_position_by_ratio(-1., false);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -885,7 +885,7 @@ RENDER_AGAIN:
|
||||
else {
|
||||
if (m_imgui->button(m_desc.at("reset_direction"))) {
|
||||
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);
|
||||
float clp_dist = m_c->object_clipper()->get_position();
|
||||
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
|
||||
bool show_sups = m_c->instances_hider()->are_supports_shown();
|
||||
|
@ -31,7 +31,7 @@ public:
|
||||
void data_changed() override;
|
||||
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
|
||||
void delete_selected_points();
|
||||
bool is_selection_rectangle_dragging() const {
|
||||
bool is_selection_rectangle_dragging() const override {
|
||||
return m_selection_rectangle.is_dragging();
|
||||
}
|
||||
|
||||
|
@ -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"));
|
||||
} else {
|
||||
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::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")))
|
||||
m_c->object_clipper()->set_position(clp_dist, true);
|
||||
m_c->object_clipper()->set_position_by_ratio(clp_dist, true);
|
||||
|
||||
ImGui::Separator();
|
||||
if (m_imgui->button(m_desc.at("remove_all"))) {
|
||||
|
@ -8,6 +8,7 @@
|
||||
#if ENABLE_LEGACY_OPENGL_REMOVAL
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
@ -79,7 +80,8 @@ std::string GLGizmoMove3D::on_get_name() 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()
|
||||
|
@ -537,7 +537,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
pos = action == SLAGizmoEventType::MouseWheelDown
|
||||
? std::max(0., 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;
|
||||
}
|
||||
else if (alt_down) {
|
||||
@ -573,7 +573,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
}
|
||||
|
||||
if (action == SLAGizmoEventType::ResetClippingPlane) {
|
||||
m_c->object_clipper()->set_position(-1., false);
|
||||
m_c->object_clipper()->set_position_by_ratio(-1., false);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "slic3r/GUI/Jobs/RotoptimizeJob.hpp"
|
||||
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
@ -867,7 +868,8 @@ std::string GLGizmoRotate3D::on_get_name() 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()
|
||||
|
@ -8,6 +8,7 @@
|
||||
#if ENABLE_LEGACY_OPENGL_REMOVAL
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
@ -103,6 +104,12 @@ bool GLGizmoScale3D::on_mouse(const wxMouseEvent &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()
|
||||
{
|
||||
#if ENABLE_WORLD_COORDINATE
|
||||
@ -161,7 +168,7 @@ std::string GLGizmoScale3D::on_get_name() const
|
||||
bool GLGizmoScale3D::on_is_activable() const
|
||||
{
|
||||
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()
|
||||
@ -448,7 +455,7 @@ void GLGizmoScale3D::on_render()
|
||||
// draw grabbers
|
||||
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
|
||||
// draw connections
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
@ -493,7 +500,7 @@ void GLGizmoScale3D::on_render()
|
||||
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
|
||||
// draw connections
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
@ -538,7 +545,7 @@ void GLGizmoScale3D::on_render()
|
||||
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
|
||||
// draw connections
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
|
@ -85,6 +85,7 @@ public:
|
||||
bool on_mouse(const wxMouseEvent &mouse_event) override;
|
||||
|
||||
void data_changed() override;
|
||||
void enable_ununiversal_scale(bool enable);
|
||||
protected:
|
||||
virtual bool on_init() override;
|
||||
virtual std::string on_get_name() const override;
|
||||
|
@ -156,7 +156,7 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
|
||||
else {
|
||||
if (m_imgui->button(m_desc.at("reset_direction"))) {
|
||||
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::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")))
|
||||
m_c->object_clipper()->set_position(clp_dist, true);
|
||||
m_c->object_clipper()->set_position_by_ratio(clp_dist, true);
|
||||
|
||||
ImGui::Separator();
|
||||
if (m_imgui->button(m_desc.at("remove_all"))) {
|
||||
|
@ -673,19 +673,19 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
if (action == SLAGizmoEventType::MouseWheelUp && control_down) {
|
||||
double pos = m_c->object_clipper()->get_position();
|
||||
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;
|
||||
}
|
||||
|
||||
if (action == SLAGizmoEventType::MouseWheelDown && control_down) {
|
||||
double pos = m_c->object_clipper()->get_position();
|
||||
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;
|
||||
}
|
||||
|
||||
if (action == SLAGizmoEventType::ResetClippingPlane) {
|
||||
m_c->object_clipper()->set_position(-1., false);
|
||||
m_c->object_clipper()->set_position_by_ratio(-1., false);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -972,7 +972,7 @@ RENDER_AGAIN:
|
||||
else {
|
||||
if (m_imgui->button(m_desc.at("reset_direction"))) {
|
||||
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);
|
||||
float clp_dist = m_c->object_clipper()->get_position();
|
||||
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("?")) {
|
||||
|
@ -62,8 +62,8 @@ public:
|
||||
void delete_selected_points(bool force = false);
|
||||
//ClippingPlane get_sla_clipping_plane() const;
|
||||
|
||||
bool is_in_editing_mode() const { return m_editing_mode; }
|
||||
bool is_selection_rectangle_dragging() const { return m_selection_rectangle.is_dragging(); }
|
||||
bool is_in_editing_mode() const override { return m_editing_mode; }
|
||||
bool is_selection_rectangle_dragging() const override { return m_selection_rectangle.is_dragging(); }
|
||||
bool has_backend_supports() const;
|
||||
void reslice_SLA_supports(bool postpone_error_messages = false) const;
|
||||
|
||||
|
@ -425,8 +425,11 @@ void ObjectClipper::render_cut() const
|
||||
if (m_clp_ratio == 0.)
|
||||
return;
|
||||
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 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;
|
||||
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));
|
||||
#if ENABLE_LEGACY_OPENGL_REMOVAL
|
||||
clipper->render_cut({ 1.0f, 0.37f, 0.0f, 1.0f });
|
||||
clipper->render_contour({ 1.f, 1.f, 1.f, 1.f});
|
||||
#else
|
||||
glsafe(::glPushMatrix());
|
||||
glsafe(::glColor3f(1.0f, 0.37f, 0.0f));
|
||||
clipper->render_cut();
|
||||
glsafe(::glPopMatrix());
|
||||
glsafe(::glColor3f(1.f, 1.f, 1.f));
|
||||
clipper->render_contour();
|
||||
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
|
||||
|
||||
++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();
|
||||
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();
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
@ -557,11 +600,13 @@ void SupportsClipper::render_cut() const
|
||||
|
||||
#if ENABLE_LEGACY_OPENGL_REMOVAL
|
||||
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
|
||||
glsafe(::glPushMatrix());
|
||||
glsafe(::glColor3f(1.0f, 0.f, 0.37f));
|
||||
m_clipper->render_cut();
|
||||
glsafe(::glPopMatrix());
|
||||
glsafe(::glColor3f(1.0f, 1.f, 1.f));
|
||||
m_clipper->render_contour();
|
||||
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
|
||||
}
|
||||
|
||||
|
@ -255,10 +255,19 @@ public:
|
||||
CommonGizmosDataID get_dependencies() const override { return CommonGizmosDataID::SelectionInfo; }
|
||||
#endif // NDEBUG
|
||||
|
||||
void set_position(double pos, bool keep_normal);
|
||||
void set_normal(const Vec3d& dir);
|
||||
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 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:
|
||||
@ -271,6 +280,7 @@ private:
|
||||
std::unique_ptr<ClippingPlane> m_clp;
|
||||
double m_clp_ratio = 0.;
|
||||
double m_active_inst_bb_radius = 0.;
|
||||
bool m_hide_clipped = true;
|
||||
};
|
||||
|
||||
|
||||
|
@ -99,7 +99,7 @@ bool GLGizmosManager::init()
|
||||
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 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 GLGizmoSlaSupports(m_parent, "sla_supports.svg", 6));
|
||||
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);
|
||||
else if (m_current == MmuSegmentation)
|
||||
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
|
||||
return false;
|
||||
}
|
||||
@ -505,7 +507,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
|
||||
#endif /* __APPLE__ */
|
||||
{
|
||||
// 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;
|
||||
|
||||
break;
|
||||
@ -549,7 +551,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
|
||||
case WXK_BACK:
|
||||
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;
|
||||
|
||||
break;
|
||||
@ -608,20 +610,11 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
|
||||
|
||||
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;
|
||||
bool is_rectangle_dragging = false;
|
||||
|
||||
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();
|
||||
}
|
||||
GLGizmoBase* gizmo = get_current();
|
||||
const bool is_editing = m_current == Hollow ? true : gizmo->is_in_editing_mode();
|
||||
const bool is_rectangle_dragging = gizmo->is_selection_rectangle_dragging();
|
||||
|
||||
if (keyCode == WXK_SHIFT)
|
||||
{
|
||||
@ -643,7 +636,7 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
|
||||
else if (evt.GetEventType() == wxEVT_KEY_DOWN)
|
||||
{
|
||||
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);
|
||||
processed = true;
|
||||
@ -651,8 +644,8 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
|
||||
else if (m_current == Cut)
|
||||
{
|
||||
auto do_move = [this, &processed](double delta_z) {
|
||||
GLGizmoCut* cut = dynamic_cast<GLGizmoCut*>(get_current());
|
||||
cut->set_cut_z(delta_z + cut->get_cut_z());
|
||||
GLGizmoCut3D* cut = dynamic_cast<GLGizmoCut3D*>(get_current());
|
||||
cut->shift_cut_z(delta_z);
|
||||
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_DOWN: case WXK_DOWN: { do_move(-1.0); break; }
|
||||
case WXK_SHIFT : case WXK_ALT: {
|
||||
processed = get_current()->is_in_editing_mode();
|
||||
}
|
||||
default: { break; }
|
||||
}
|
||||
} 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();
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
std::vector<size_t> selectable_idxs = get_selectable_idxs();
|
||||
|
@ -199,6 +199,7 @@ public:
|
||||
|
||||
EType get_current_type() const { return m_current; }
|
||||
GLGizmoBase* get_current() const;
|
||||
GLGizmoBase* get_gizmo(GLGizmosManager::EType type) const;
|
||||
EType get_gizmo_from_name(const std::string& gizmo_name) const;
|
||||
|
||||
bool is_running() const;
|
||||
|
@ -56,6 +56,11 @@ static const std::map<const wchar_t, std::string> font_icons = {
|
||||
{ImGui::PreferencesHoverButton, "notification_preferences_hover"},
|
||||
{ImGui::SliderFloatEditBtnIcon, "edit_button" },
|
||||
{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 = {
|
||||
|
@ -16,15 +16,27 @@
|
||||
|
||||
#include <igl/unproject.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
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)
|
||||
{
|
||||
if (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) {
|
||||
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) {
|
||||
m_mesh = &mesh;
|
||||
m_triangles_valid = false;
|
||||
m_triangles2d.resize(0);
|
||||
m_result.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,8 +63,7 @@ void MeshClipper::set_negative_mesh(const TriangleMesh& mesh)
|
||||
{
|
||||
if (m_negative_mesh != &mesh) {
|
||||
m_negative_mesh = &mesh;
|
||||
m_triangles_valid = false;
|
||||
m_triangles2d.resize(0);
|
||||
m_result.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,8 +73,7 @@ void MeshClipper::set_transformation(const Geometry::Transformation& trafo)
|
||||
{
|
||||
if (! m_trafo.get_matrix().isApprox(trafo.get_matrix())) {
|
||||
m_trafo = trafo;
|
||||
m_triangles_valid = false;
|
||||
m_triangles2d.resize(0);
|
||||
m_result.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,13 +83,9 @@ void MeshClipper::render_cut(const ColorRGBA& color)
|
||||
void MeshClipper::render_cut()
|
||||
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
|
||||
{
|
||||
if (! m_triangles_valid)
|
||||
if (! m_result)
|
||||
recalculate_triangles();
|
||||
|
||||
#if ENABLE_LEGACY_OPENGL_REMOVAL
|
||||
if (m_model.vertices_count() == 0 || m_model.indices_count() == 0)
|
||||
return;
|
||||
|
||||
GLShaderProgram* curr_shader = wxGetApp().get_current_shader();
|
||||
if (curr_shader != nullptr)
|
||||
curr_shader->stop_using();
|
||||
@ -91,8 +96,10 @@ void MeshClipper::render_cut()
|
||||
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());
|
||||
m_model.set_color(color);
|
||||
m_model.render();
|
||||
for (CutIsland& isl : m_result->cut_islands) {
|
||||
isl.model.set_color(isl.disabled ? ColorRGBA(1.f, 0.f, 0.f, 1.f) : color);
|
||||
isl.model.render();
|
||||
}
|
||||
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()
|
||||
{
|
||||
#if ENABLE_WORLD_COORDINATE
|
||||
const Transform3f instance_matrix_no_translation_no_scaling = m_trafo.get_rotation_matrix().cast<float>();
|
||||
#else
|
||||
const Transform3f& instance_matrix_no_translation_no_scaling = m_trafo.get_matrix(true,false,true).cast<float>();
|
||||
#endif // ENABLE_WORLD_COORDINATE
|
||||
// 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());
|
||||
m_result = ClipResult();
|
||||
|
||||
auto plane_mesh = Eigen::Hyperplane<double, 3>(m_plane.get_normal(), -m_plane.distance(Vec3d::Zero())).transform(m_trafo.get_matrix().inverse());
|
||||
const Vec3d up = plane_mesh.normal();
|
||||
const float height_mesh = -plane_mesh.offset();
|
||||
|
||||
// Now do the cutting
|
||||
MeshSlicingParams slicing_params;
|
||||
@ -137,6 +205,8 @@ void MeshClipper::recalculate_triangles()
|
||||
tr.rotate(q);
|
||||
tr = m_trafo.get_matrix() * tr;
|
||||
|
||||
m_result->trafo = tr;
|
||||
|
||||
if (m_limiting_plane != ClippingPlane::ClipsNothing())
|
||||
{
|
||||
// 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
|
||||
Transform3d tr2 = tr;
|
||||
tr2.pretranslate(0.002 * m_plane.get_normal().normalized());
|
||||
|
||||
|
||||
#if ENABLE_LEGACY_OPENGL_REMOVAL
|
||||
m_model.reset();
|
||||
std::vector<Vec2f> triangles2d;
|
||||
|
||||
for (const ExPolygon& exp : expolys) {
|
||||
triangles2d.clear();
|
||||
|
||||
m_result->cut_islands.push_back(CutIsland());
|
||||
CutIsland& isl = m_result->cut_islands.back();
|
||||
|
||||
if (m_fill_cut) {
|
||||
triangles2d = triangulate_expolygon_2f(exp, m_trafo.get_matrix().matrix().determinant() < 0.);
|
||||
GLModel::Geometry init_data;
|
||||
init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 };
|
||||
init_data.reserve_vertices(m_triangles2d.size());
|
||||
init_data.reserve_indices(m_triangles2d.size());
|
||||
init_data.reserve_vertices(triangles2d.size());
|
||||
init_data.reserve_indices(triangles2d.size());
|
||||
|
||||
// vertices + indices
|
||||
for (auto it = m_triangles2d.cbegin(); it != m_triangles2d.cend(); it = it + 3) {
|
||||
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 - m_triangles2d.cbegin();
|
||||
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())
|
||||
m_model.init_from(std::move(init_data));
|
||||
#else
|
||||
m_vertex_array.release_geometry();
|
||||
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);
|
||||
isl.model.init_from(std::move(init_data));
|
||||
}
|
||||
m_vertex_array.finalize_geometry(true);
|
||||
|
||||
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);
|
||||
}
|
||||
#else
|
||||
#error NOT IMPLEMENTED
|
||||
#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)
|
||||
#else
|
||||
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
|
||||
{
|
||||
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,
|
||||
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 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;
|
||||
}
|
||||
|
||||
if (i==hits.size() || (hits.size()-i) % 2 != 0) {
|
||||
// All hits are either clipped, or there is an odd number of unclipped
|
||||
// hits - meaning the nearest must be from inside the mesh.
|
||||
if (i==hits.size()) {
|
||||
// All hits are clipped.
|
||||
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;
|
||||
}
|
||||
|
||||
@ -302,6 +458,35 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d&
|
||||
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,
|
||||
const ClippingPlane* clipping_plane) const
|
||||
|
@ -14,6 +14,7 @@
|
||||
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
|
||||
|
||||
#include <cfloat>
|
||||
#include <optional>
|
||||
#if ENABLE_RAYCAST_PICKING
|
||||
#include <memory>
|
||||
#endif // ENABLE_RAYCAST_PICKING
|
||||
@ -80,6 +81,10 @@ public:
|
||||
class MeshClipper
|
||||
{
|
||||
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
|
||||
// This is supposed to be in world coordinates.
|
||||
void set_plane(const ClippingPlane& plane);
|
||||
@ -103,10 +108,16 @@ public:
|
||||
// be set in world coords.
|
||||
#if ENABLE_LEGACY_OPENGL_REMOVAL
|
||||
void render_cut(const ColorRGBA& color);
|
||||
void render_contour(const ColorRGBA& color);
|
||||
#else
|
||||
void render_cut();
|
||||
#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:
|
||||
void recalculate_triangles();
|
||||
|
||||
@ -115,13 +126,27 @@ private:
|
||||
const TriangleMesh* m_negative_mesh = nullptr;
|
||||
ClippingPlane m_plane;
|
||||
ClippingPlane m_limiting_plane = ClippingPlane::ClipsNothing();
|
||||
std::vector<Vec2f> m_triangles2d;
|
||||
#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
|
||||
#error NOT IMLEMENTED
|
||||
GLIndexedVertexArray m_vertex_array;
|
||||
#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,
|
||||
Vec3d& point, Vec3d& direction) const;
|
||||
static void line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
|
||||
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
|
||||
|
||||
// Given a mouse position, this returns true in case it is on the mesh.
|
||||
@ -159,12 +186,18 @@ public:
|
||||
const Vec2d& mouse_pos,
|
||||
const Transform3d& trafo, // how to get the mesh into world coords
|
||||
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
|
||||
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;
|
||||
|
||||
// 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
|
||||
// of indices of points that are visible (i.e. not cut by clipping plane
|
||||
// or obscured by part of the mesh.
|
||||
|
@ -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::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::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;
|
||||
}
|
||||
}
|
||||
|
@ -34,10 +34,19 @@ void ObjectDataViewModelNode::init_container()
|
||||
#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 LayerIcon[] = "edit_layers_some";
|
||||
static constexpr char WarningIcon[] = "exclamation";
|
||||
static constexpr char WarningManifoldIcon[] = "exclamation_manifold";
|
||||
static constexpr char LockIcon[] = "cut_";
|
||||
|
||||
struct InfoItemAtributes {
|
||||
std::string name;
|
||||
@ -48,6 +57,7 @@ const std::map<InfoItemType, InfoItemAtributes> INFO_ITEMS{
|
||||
// info_item Type info_item Name info_item BitmapName
|
||||
{ InfoItemType::CustomSupports, {L("Paint-on supports"), "fdm_supports_" }, },
|
||||
{ InfoItemType::CustomSeam, {L("Paint-on seam"), "seam_" }, },
|
||||
{ InfoItemType::CutConnectors, {L("Cut connectors"), "cut_connectors" }, },
|
||||
{ InfoItemType::MmuSegmentation, {L("Multimaterial painting"), "mmu_segmentation_"}, },
|
||||
{ InfoItemType::Sinking, {L("Sinking"), "sinking"}, },
|
||||
{ InfoItemType::VariableLayerHeight, {L("Variable layer height"), "layers"}, },
|
||||
@ -56,19 +66,15 @@ const std::map<InfoItemType, InfoItemAtributes> INFO_ITEMS{
|
||||
ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent,
|
||||
const wxString& sub_obj_name,
|
||||
Slic3r::ModelVolumeType type,
|
||||
const wxBitmapBundle& bmp,
|
||||
const wxString& extruder,
|
||||
const int idx/* = -1*/,
|
||||
const std::string& warning_icon_name /*= std::string*/) :
|
||||
const int idx/* = -1*/) :
|
||||
m_parent(parent),
|
||||
m_name(sub_obj_name),
|
||||
m_type(itVolume),
|
||||
m_volume_type(type),
|
||||
m_idx(idx),
|
||||
m_extruder(type == Slic3r::ModelVolumeType::MODEL_PART || type == Slic3r::ModelVolumeType::PARAMETER_MODIFIER ? extruder : ""),
|
||||
m_warning_icon_name(warning_icon_name)
|
||||
m_extruder(type == Slic3r::ModelVolumeType::MODEL_PART || type == Slic3r::ModelVolumeType::PARAMETER_MODIFIER ? extruder : "")
|
||||
{
|
||||
m_bmp = bmp;
|
||||
set_action_and_extruder_icons();
|
||||
init_container();
|
||||
}
|
||||
@ -173,13 +179,6 @@ void ObjectDataViewModelNode::set_printable_icon(PrintIndicator printable)
|
||||
*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()
|
||||
{
|
||||
m_bmp = m_empty_bmp;
|
||||
@ -327,6 +326,7 @@ ObjectDataViewModel::ObjectDataViewModel()
|
||||
m_volume_bmps = MenuFactory::get_volume_bitmaps();
|
||||
m_warning_bmp = *get_bmp_bundle(WarningIcon);
|
||||
m_warning_manifold_bmp = *get_bmp_bundle(WarningManifoldIcon);
|
||||
m_lock_bmp = *get_bmp_bundle(LockIcon);
|
||||
|
||||
for (auto item : INFO_ITEMS)
|
||||
m_info_bmps[item.first] = get_bmp_bundle(item.second.bmp_name);
|
||||
@ -340,19 +340,55 @@ ObjectDataViewModel::~ObjectDataViewModel()
|
||||
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;
|
||||
}
|
||||
|
||||
wxDataViewItem ObjectDataViewModel::Add(const wxString &name,
|
||||
const int extruder,
|
||||
const std::string& warning_icon_name/* = std::string()*/ )
|
||||
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);
|
||||
}
|
||||
|
||||
void ObjectDataViewModel::UpdateBitmapForNode(ObjectDataViewModelNode* node, const std::string& warning_icon_name, bool has_lock)
|
||||
{
|
||||
const wxString extruder_str = extruder == 0 ? _L("default") : wxString::Format("%d", extruder);
|
||||
auto root = new ObjectDataViewModelNode(name, extruder_str);
|
||||
node->SetWarningIconName(warning_icon_name);
|
||||
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
|
||||
root->SetWarningBitmap(GetWarningBitmap(warning_icon_name), warning_icon_name);
|
||||
UpdateBitmapForNode(root, warning_icon_name, has_lock);
|
||||
|
||||
m_objects.push_back(root);
|
||||
// notify control
|
||||
@ -365,42 +401,29 @@ wxDataViewItem ObjectDataViewModel::Add(const wxString &name,
|
||||
|
||||
wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent_item,
|
||||
const wxString &name,
|
||||
const int volume_idx,
|
||||
const Slic3r::ModelVolumeType volume_type,
|
||||
const std::string& warning_icon_name/* = std::string()*/,
|
||||
const int extruder/* = 0*/,
|
||||
const bool create_frst_child/* = true*/)
|
||||
const std::string& warning_icon_name,
|
||||
const wxString& extruder)
|
||||
{
|
||||
ObjectDataViewModelNode *root = static_cast<ObjectDataViewModelNode*>(parent_item.GetID());
|
||||
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
|
||||
int insert_position = get_root_idx(root, itLayerRoot);
|
||||
if (insert_position < 0)
|
||||
insert_position = get_root_idx(root, itInstanceRoot);
|
||||
|
||||
if (create_frst_child && root->m_volumes_cnt == 0)
|
||||
{
|
||||
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);
|
||||
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);
|
||||
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 (!warning_icon_name.empty() && warning_icon_name != root->m_warning_icon_name &&
|
||||
(root->m_warning_icon_name.empty() || root->m_warning_icon_name == WarningManifoldIcon) )
|
||||
root->SetWarningBitmap(GetWarningBitmap(warning_icon_name), warning_icon_name);
|
||||
if (!warning_icon_name.empty() && warning_icon_name != root->warning_icon_name() &&
|
||||
(!root->has_warning_icon() || root->warning_icon_name() == WarningManifoldIcon)) {
|
||||
root->SetWarningIconName(warning_icon_name);
|
||||
UpdateBitmapForNode(root);
|
||||
}
|
||||
|
||||
// notify control
|
||||
const wxDataViewItem child((void*)node);
|
||||
@ -598,14 +621,12 @@ wxDataViewItem ObjectDataViewModel::AddLayersRoot(const wxDataViewItem &parent_i
|
||||
|
||||
wxDataViewItem ObjectDataViewModel::AddLayersChild(const wxDataViewItem &parent_item,
|
||||
const t_layer_height_range& layer_range,
|
||||
const int extruder/* = 0*/,
|
||||
const wxString& extruder,
|
||||
const int index /* = -1*/)
|
||||
{
|
||||
ObjectDataViewModelNode *parent_node = static_cast<ObjectDataViewModelNode*>(parent_item.GetID());
|
||||
if (!parent_node) return wxDataViewItem(0);
|
||||
|
||||
wxString extruder_str = extruder == 0 ? _(L("default")) : wxString::Format("%d", extruder);
|
||||
|
||||
// get LayerRoot node
|
||||
ObjectDataViewModelNode *layer_root_node;
|
||||
wxDataViewItem layer_root_item;
|
||||
@ -622,7 +643,7 @@ wxDataViewItem ObjectDataViewModel::AddLayersChild(const wxDataViewItem &parent_
|
||||
}
|
||||
|
||||
// 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)
|
||||
layer_root_node->Append(layer_node);
|
||||
else
|
||||
@ -711,10 +732,7 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item)
|
||||
delete node_parent;
|
||||
ret_item = wxDataViewItem(obj_node);
|
||||
|
||||
#ifndef __WXGTK__
|
||||
if (obj_node->GetChildCount() == 0)
|
||||
obj_node->m_container = false;
|
||||
#endif //__WXGTK__
|
||||
obj_node->invalidate_container();
|
||||
ItemDeleted(ret_item, wxDataViewItem(node_parent));
|
||||
return ret_item;
|
||||
}
|
||||
@ -730,10 +748,7 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item)
|
||||
delete node_parent;
|
||||
ret_item = wxDataViewItem(obj_node);
|
||||
|
||||
#ifndef __WXGTK__
|
||||
if (obj_node->GetChildCount() == 0)
|
||||
obj_node->m_container = false;
|
||||
#endif //__WXGTK__
|
||||
obj_node->invalidate_container();
|
||||
ItemDeleted(ret_item, wxDataViewItem(node_parent));
|
||||
return ret_item;
|
||||
}
|
||||
@ -755,10 +770,7 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item)
|
||||
node_parent->m_volumes_cnt = 0;
|
||||
delete last_child_node;
|
||||
|
||||
#ifndef __WXGTK__
|
||||
if (node_parent->GetChildCount() == 0)
|
||||
node_parent->m_container = false;
|
||||
#endif //__WXGTK__
|
||||
node_parent->invalidate_container();
|
||||
ItemDeleted(parent, wxDataViewItem(last_child_node));
|
||||
|
||||
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
|
||||
if (node_parent) {
|
||||
#ifndef __WXGTK__
|
||||
if (node_parent->GetChildCount() == 0)
|
||||
node_parent->m_container = false;
|
||||
#endif //__WXGTK__
|
||||
node_parent->invalidate_container();
|
||||
ret_item = parent;
|
||||
}
|
||||
|
||||
@ -838,10 +847,7 @@ wxDataViewItem ObjectDataViewModel::DeleteLastInstance(const wxDataViewItem &par
|
||||
parent_node->set_printable_icon(last_inst_printable);
|
||||
ItemDeleted(parent_item, inst_root_item);
|
||||
ItemChanged(parent_item);
|
||||
#ifndef __WXGTK__
|
||||
if (parent_node->GetChildCount() == 0)
|
||||
parent_node->m_container = false;
|
||||
#endif //__WXGTK__
|
||||
parent_node->invalidate_container();
|
||||
}
|
||||
|
||||
// update object_node printable property
|
||||
@ -886,10 +892,7 @@ void ObjectDataViewModel::DeleteChildren(wxDataViewItem& parent)
|
||||
ItemDeleted(parent, item);
|
||||
}
|
||||
|
||||
// set m_containet to FALSE if parent has no child
|
||||
#ifndef __WXGTK__
|
||||
root->m_container = false;
|
||||
#endif //__WXGTK__
|
||||
root->invalidate_container();
|
||||
}
|
||||
|
||||
void ObjectDataViewModel::DeleteVolumeChildren(wxDataViewItem& parent)
|
||||
@ -919,11 +922,7 @@ void ObjectDataViewModel::DeleteVolumeChildren(wxDataViewItem& parent)
|
||||
ItemDeleted(parent, item);
|
||||
}
|
||||
root->m_volumes_cnt = 0;
|
||||
|
||||
// set m_containet to FALSE if parent has no child
|
||||
#ifndef __WXGTK__
|
||||
root->m_container = false;
|
||||
#endif //__WXGTK__
|
||||
root->invalidate_container();
|
||||
}
|
||||
|
||||
void ObjectDataViewModel::DeleteSettings(const wxDataViewItem& parent)
|
||||
@ -1681,6 +1680,7 @@ void ObjectDataViewModel::UpdateBitmaps()
|
||||
m_volume_bmps = MenuFactory::get_volume_bitmaps();
|
||||
m_warning_bmp = *get_bmp_bundle(WarningIcon);
|
||||
m_warning_manifold_bmp = *get_bmp_bundle(WarningManifoldIcon);
|
||||
m_lock_bmp = *get_bmp_bundle(LockIcon);
|
||||
|
||||
for (auto item : INFO_ITEMS)
|
||||
m_info_bmps[item.first] = get_bmp_bundle(item.second.bmp_name);
|
||||
@ -1699,10 +1699,8 @@ void ObjectDataViewModel::UpdateBitmaps()
|
||||
switch (node->m_type)
|
||||
{
|
||||
case itObject:
|
||||
if (node->m_bmp.IsOk()) node->m_bmp = GetWarningBitmap(node->m_warning_icon_name);
|
||||
break;
|
||||
case itVolume:
|
||||
node->m_bmp = GetVolumeIcon(node->m_volume_type, node->m_warning_icon_name);
|
||||
UpdateBitmapForNode(node);
|
||||
break;
|
||||
case itLayerRoot:
|
||||
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)
|
||||
{
|
||||
if (!item.IsOk())
|
||||
@ -1748,13 +1725,14 @@ void ObjectDataViewModel::AddWarningIcon(const wxDataViewItem& item, const std::
|
||||
ObjectDataViewModelNode *node = static_cast<ObjectDataViewModelNode*>(item.GetID());
|
||||
|
||||
if (node->GetType() & itObject) {
|
||||
node->SetWarningBitmap(GetWarningBitmap(warning_icon_name), warning_icon_name);
|
||||
UpdateBitmapForNode(node, warning_icon_name, node->has_lock());
|
||||
return;
|
||||
}
|
||||
|
||||
if (node->GetType() & itVolume) {
|
||||
node->SetWarningBitmap(GetVolumeIcon(node->GetVolumeType(), warning_icon_name), warning_icon_name);
|
||||
node->GetParent()->SetWarningBitmap(GetWarningBitmap(warning_icon_name), warning_icon_name);
|
||||
UpdateBitmapForNode(node, warning_icon_name, node->has_lock());
|
||||
if (ObjectDataViewModelNode* parent = node->GetParent())
|
||||
UpdateBitmapForNode(parent, warning_icon_name, parent->has_lock());
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -1769,12 +1747,9 @@ void ObjectDataViewModel::DeleteWarningIcon(const wxDataViewItem& item, const bo
|
||||
if (!node->GetBitmap().IsOk() || !(node->GetType() & (itVolume | itObject)))
|
||||
return;
|
||||
|
||||
if (node->GetType() & itVolume) {
|
||||
node->SetWarningBitmap(*m_volume_bmps[static_cast<int>(node->volume_type())], "");
|
||||
return;
|
||||
}
|
||||
node->SetWarningIconName(std::string());
|
||||
UpdateBitmapForNode(node);
|
||||
|
||||
node->SetWarningBitmap(wxNullBitmap, "");
|
||||
if (unmark_object)
|
||||
{
|
||||
wxDataViewItemArray children;
|
||||
@ -1801,6 +1776,26 @@ void ObjectDataViewModel::UpdateWarningIcon(const wxDataViewItem& item, const st
|
||||
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 Slic3r
|
||||
|
||||
|
@ -51,6 +51,7 @@ enum class InfoItemType
|
||||
Undef,
|
||||
CustomSupports,
|
||||
CustomSeam,
|
||||
CutConnectors,
|
||||
MmuSegmentation,
|
||||
Sinking,
|
||||
VariableLayerHeight
|
||||
@ -79,9 +80,10 @@ class ObjectDataViewModelNode
|
||||
PrintIndicator m_printable {piUndef};
|
||||
wxBitmapBundle m_printable_icon;
|
||||
std::string m_warning_icon_name{ "" };
|
||||
bool m_has_lock{false};
|
||||
|
||||
std::string m_action_icon_name = "";
|
||||
ModelVolumeType m_volume_type;
|
||||
ModelVolumeType m_volume_type{ -1 };
|
||||
InfoItemType m_info_item_type {InfoItemType::Undef};
|
||||
|
||||
public:
|
||||
@ -99,10 +101,8 @@ public:
|
||||
ObjectDataViewModelNode(ObjectDataViewModelNode* parent,
|
||||
const wxString& sub_obj_name,
|
||||
Slic3r::ModelVolumeType type,
|
||||
const wxBitmapBundle& bmp,
|
||||
const wxString& extruder,
|
||||
const int idx = -1,
|
||||
const std::string& warning_icon_name = std::string());
|
||||
const int idx = -1 );
|
||||
|
||||
ObjectDataViewModelNode(ObjectDataViewModelNode* parent,
|
||||
const t_layer_height_range& layer_range,
|
||||
@ -128,6 +128,8 @@ public:
|
||||
}
|
||||
|
||||
void init_container();
|
||||
void invalidate_container();
|
||||
|
||||
bool IsContainer() const
|
||||
{
|
||||
return m_container;
|
||||
@ -181,7 +183,8 @@ public:
|
||||
void SetVolumeType(ModelVolumeType type) { m_volume_type = type; }
|
||||
void SetBitmap(const wxBitmapBundle &icon) { m_bmp = icon; }
|
||||
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 wxString& GetName() const { return m_name; }
|
||||
ItemType GetType() const { return m_type; }
|
||||
@ -228,8 +231,6 @@ public:
|
||||
void set_extruder_icon();
|
||||
// Set printable icon for node
|
||||
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();
|
||||
bool update_settings_digest(const std::vector<std::string>& categories);
|
||||
@ -241,6 +242,8 @@ public:
|
||||
#endif /* NDEBUG */
|
||||
bool invalid() const { return m_idx < -1; }
|
||||
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:
|
||||
friend class ObjectDataViewModel;
|
||||
@ -262,6 +265,7 @@ class ObjectDataViewModel :public wxDataViewModel
|
||||
wxBitmapBundle m_empty_bmp;
|
||||
wxBitmapBundle m_warning_bmp;
|
||||
wxBitmapBundle m_warning_manifold_bmp;
|
||||
wxBitmapBundle m_lock_bmp;
|
||||
|
||||
wxDataViewCtrl* m_ctrl { nullptr };
|
||||
|
||||
@ -269,15 +273,16 @@ public:
|
||||
ObjectDataViewModel();
|
||||
~ObjectDataViewModel();
|
||||
|
||||
wxDataViewItem Add( const wxString &name,
|
||||
const int extruder,
|
||||
const std::string& warning_icon_name = std::string());
|
||||
wxDataViewItem AddObject( const wxString &name,
|
||||
const wxString& extruder,
|
||||
const std::string& warning_icon_name,
|
||||
const bool has_lock);
|
||||
wxDataViewItem AddVolumeChild( const wxDataViewItem &parent_item,
|
||||
const wxString &name,
|
||||
const int volume_idx,
|
||||
const Slic3r::ModelVolumeType volume_type,
|
||||
const std::string& warning_icon_name = std::string(),
|
||||
const int extruder = 0,
|
||||
const bool create_frst_child = true);
|
||||
const std::string& warning_icon_name,
|
||||
const wxString& extruder);
|
||||
wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item);
|
||||
wxDataViewItem AddInfoChild(const wxDataViewItem &parent_item, InfoItemType info_type);
|
||||
wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num);
|
||||
@ -285,7 +290,7 @@ public:
|
||||
wxDataViewItem AddLayersRoot(const wxDataViewItem &parent_item);
|
||||
wxDataViewItem AddLayersChild( const wxDataViewItem &parent_item,
|
||||
const t_layer_height_range& layer_range,
|
||||
const int extruder = 0,
|
||||
const wxString& extruder,
|
||||
const int index = -1);
|
||||
size_t GetItemIndexForFirstVolume(ObjectDataViewModelNode* node_parent);
|
||||
wxDataViewItem Delete(const wxDataViewItem &item);
|
||||
@ -390,6 +395,7 @@ public:
|
||||
void AddWarningIcon(const wxDataViewItem& item, const std::string& warning_name);
|
||||
void DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false);
|
||||
void UpdateWarningIcon(const wxDataViewItem& item, const std::string& warning_name);
|
||||
void UpdateLockIcon(const wxDataViewItem& item, bool has_lock);
|
||||
bool HasWarningIcon(const wxDataViewItem& item) const;
|
||||
t_layer_height_range GetLayerRangeByItem(const wxDataViewItem& item) const;
|
||||
|
||||
@ -403,7 +409,8 @@ private:
|
||||
wxDataViewItem AddInstanceRoot(const wxDataViewItem& parent_item);
|
||||
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);
|
||||
};
|
||||
|
||||
|
||||
|
@ -97,6 +97,7 @@
|
||||
#include "MsgDialog.hpp"
|
||||
#include "ProjectDirtyStateManager.hpp"
|
||||
#include "Gizmos/GLGizmoSimplify.hpp" // create suggestion notification
|
||||
#include "Gizmos/GLGizmoCut.hpp"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include "Gizmos/GLGizmosManager.hpp"
|
||||
@ -1792,7 +1793,7 @@ struct Plater::priv
|
||||
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_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);
|
||||
wxString get_export_file(GUI::FileType file_type);
|
||||
@ -1807,7 +1808,7 @@ struct Plater::priv
|
||||
void select_all();
|
||||
void deselect_all();
|
||||
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 reset();
|
||||
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
|
||||
|
||||
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();
|
||||
|
||||
@ -2801,9 +2802,10 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs& mode
|
||||
|
||||
notification_manager->close_notification_of_type(NotificationType::UpdatedItemsInfo);
|
||||
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);
|
||||
}
|
||||
|
||||
if (call_selection_changed) {
|
||||
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
|
||||
@ -2811,7 +2813,7 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs& mode
|
||||
wxGetApp().obj_list()->update_info_items(idx);
|
||||
|
||||
object_list_changed();
|
||||
|
||||
}
|
||||
this->schedule_background_process();
|
||||
|
||||
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");
|
||||
if (! model.objects[obj_idx]->name.empty())
|
||||
snapshot_label += ": " + wxString::FromUTF8(model.objects[obj_idx]->name.c_str());
|
||||
if (!obj->name.empty())
|
||||
snapshot_label += ": " + wxString::FromUTF8(obj->name.c_str());
|
||||
Plater::TakeSnapshot snapshot(q, snapshot_label);
|
||||
m_worker.cancel_all();
|
||||
|
||||
if (obj->is_cut())
|
||||
sidebar->obj_list()->invalidate_cut_info_for_object(obj_idx);
|
||||
|
||||
model.delete_object(obj_idx);
|
||||
update();
|
||||
object_list_changed();
|
||||
return true;
|
||||
}
|
||||
|
||||
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&)
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
@ -4481,7 +4505,7 @@ void Plater::priv::on_right_click(RBtnEvent& evt)
|
||||
selection.is_single_full_object() ||
|
||||
selection.is_multiple_full_instance();
|
||||
#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
|
||||
const bool is_part = selection.is_single_volume() || selection.is_single_modifier();
|
||||
#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
|
||||
{
|
||||
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
|
||||
@ -4768,16 +4793,19 @@ bool Plater::priv::layers_height_allowed() 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
|
||||
{
|
||||
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
|
||||
{
|
||||
if (sidebar->obj_list()->has_selected_cut_object())
|
||||
return false;
|
||||
|
||||
#if ENABLE_RELOAD_FROM_DISK_REWORK
|
||||
// collect selected reloadable ModelVolumes
|
||||
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
|
||||
{
|
||||
const int obj_idx = get_selected_object_idx();
|
||||
// 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?
|
||||
if (q->canvas3D()->get_gizmos_manager().get_current_type() ==
|
||||
GLGizmosManager::EType::Simplify)
|
||||
@ -4906,8 +4938,7 @@ bool Plater::priv::can_increase_instances() const
|
||||
|| q->canvas3D()->get_gizmos_manager().is_in_editing_mode())
|
||||
return false;
|
||||
|
||||
int obj_idx = get_selected_object_idx();
|
||||
return (0 <= obj_idx) && (obj_idx < (int)model.objects.size());
|
||||
return !sidebar->obj_list()->has_selected_cut_object();
|
||||
}
|
||||
|
||||
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())
|
||||
return false;
|
||||
|
||||
int obj_idx = get_selected_object_idx();
|
||||
return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && (model.objects[obj_idx]->instances.size() > 1);
|
||||
return !sidebar->obj_list()->has_selected_cut_object();
|
||||
}
|
||||
|
||||
bool Plater::priv::can_split_to_objects() const
|
||||
@ -5721,7 +5751,7 @@ void Plater::reset_with_confirm()
|
||||
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()
|
||||
{
|
||||
@ -5898,23 +5928,29 @@ void Plater::toggle_layers_editing(bool enable)
|
||||
canvas3D()->force_main_toolbar_left_action(canvas3D()->get_main_toolbar_item_id("layersediting"));
|
||||
}
|
||||
|
||||
void Plater::cut(size_t obj_idx, size_t instance_idx, 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");
|
||||
auto* object = p->model.objects[obj_idx];
|
||||
|
||||
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;
|
||||
const auto new_objects = object->cut(instance_idx, z, attributes);
|
||||
|
||||
remove(obj_idx);
|
||||
p->load_model_objects(new_objects);
|
||||
const auto new_objects = object->cut(instance_idx, cut_matrix, attributes);
|
||||
|
||||
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();
|
||||
size_t last_id = p->model.objects.size() - 1;
|
||||
|
@ -242,7 +242,7 @@ public:
|
||||
void remove(size_t obj_idx);
|
||||
void reset();
|
||||
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 increase_instances(size_t num = 1);
|
||||
void decrease_instances(size_t num = 1);
|
||||
@ -253,7 +253,7 @@ public:
|
||||
void convert_unit(ConversionType conv_type);
|
||||
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_stl_obj(bool extended = false, bool selection_only = false);
|
||||
|
@ -163,7 +163,7 @@ void SavePresetDialog::Item::update()
|
||||
if (m_valid_type == ValidationType::Valid && existing)
|
||||
{
|
||||
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
|
||||
info_line = _L("Save preset modifications to existing user profile");
|
||||
else
|
||||
|
@ -510,6 +510,28 @@ void Selection::volumes_changed(const std::vector<size_t> &map_volume_old_to_new
|
||||
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
|
||||
{
|
||||
if (m_type == SingleFullInstance)
|
||||
|
@ -320,6 +320,8 @@ public:
|
||||
bool is_single_volume() const { return m_type == SingleVolume; }
|
||||
bool is_multiple_volume() const { return m_type == MultipleVolume; }
|
||||
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_from_single_instance() const { return get_instance_idx() != -1; }
|
||||
bool is_from_single_object() const;
|
||||
|
@ -414,7 +414,7 @@ public:
|
||||
std::string get_left_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:
|
||||
void on_dpi_changed(const wxRect& suggested_rect) override;
|
||||
|
Loading…
Reference in New Issue
Block a user