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

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

View File

@ -43,6 +43,14 @@ set(SLIC3R_GTK "2" CACHE STRING "GTK version to use with wxWidgets on Linux")
set(IS_CROSS_COMPILE FALSE)
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")

View File

@ -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

View File

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

After

Width:  |  Height:  |  Size: 919 B

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

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

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

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

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

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

After

Width:  |  Height:  |  Size: 915 B

View File

@ -32,6 +32,7 @@ src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp
src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp
src/slic3r/GUI/Gizmos/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

View File

@ -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));
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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);

View File

@ -77,6 +77,7 @@ const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/Prusa_Slicer_layer_config
const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt";
const std::string SLA_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());

View File

@ -464,12 +464,25 @@ static constexpr const double volume_threshold_inches = 9.0; // 9 = 3*3*3;
bool Model::looks_like_imperial_units() const
{
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)
return true;
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,34 +1204,355 @@ 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))
assert(is_cut());
for (const ModelVolume* v : this->volumes)
if (v->cut_info.is_connector)
return true;
return false;
}
indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes connector_attributes)
{
indexed_triangle_set connector_mesh;
int sectorCount {1};
switch (CutConnectorShape(connector_attributes.shape)) {
case CutConnectorShape::Triangle:
sectorCount = 3;
break;
case CutConnectorShape::Square:
sectorCount = 4;
break;
case CutConnectorShape::Circle:
sectorCount = 360;
break;
case CutConnectorShape::Hexagon:
sectorCount = 6;
break;
default:
break;
}
if (connector_attributes.style == CutConnectorStyle::Prizm)
connector_mesh = its_make_cylinder(1.0, 1.0, (2 * PI / sectorCount));
else if (connector_attributes.type == CutConnectorType::Plug)
connector_mesh = its_make_cone(1.0, 1.0, (2 * PI / sectorCount));
else
connector_mesh = its_make_frustum_dowel(1.0, 1.0, sectorCount);
return connector_mesh;
}
void ModelObject::apply_cut_connectors(const std::string& new_name)
{
if (cut_connectors.empty())
return;
using namespace Geometry;
size_t connector_id = cut_id.connectors_cnt();
for (const CutConnector& connector : cut_connectors) {
TriangleMesh mesh = TriangleMesh(get_connector_mesh(connector.attribs));
// Mesh will be centered when loading.
ModelVolume* new_volume = add_volume(std::move(mesh), ModelVolumeType::NEGATIVE_VOLUME);
// Transform the new modifier to be aligned inside the instance
new_volume->set_transformation(assemble_transform(connector.pos) * connector.rotation_m *
scale_transform(Vec3f(connector.radius, connector.radius, connector.height).cast<double>()));
new_volume->cut_info = { connector.attribs.type, connector.radius_tolerance, connector.height_tolerance };
new_volume->name = new_name + "-" + std::to_string(++connector_id);
}
cut_id.increase_connectors_cnt(cut_connectors.size());
// delete all connectors
cut_connectors.clear();
}
void ModelObject::invalidate_cut()
{
this->cut_id.invalidate();
for (ModelVolume* volume : this->volumes)
volume->invalidate_cut_info();
}
void ModelObject::synchronize_model_after_cut()
{
for (ModelObject* obj : m_model->objects) {
if (obj == this || obj->cut_id.is_equal(this->cut_id))
continue;
if (obj->is_cut() && obj->cut_id.has_same_id(this->cut_id))
obj->cut_id.copy(this->cut_id);
}
}
void ModelObject::apply_cut_attributes(ModelObjectCutAttributes attributes)
{
// we don't save cut information, if result will not contains all parts of initial object
if (!attributes.has(ModelObjectCutAttribute::KeepUpper) || !attributes.has(ModelObjectCutAttribute::KeepLower))
return;
if (cut_id.id().invalid())
cut_id.init();
{
int cut_obj_cnt = -1;
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) cut_obj_cnt++;
if (attributes.has(ModelObjectCutAttribute::KeepLower)) cut_obj_cnt++;
if (attributes.has(ModelObjectCutAttribute::CreateDowels)) cut_obj_cnt++;
if (cut_obj_cnt > 0)
cut_id.increase_check_sum(size_t(cut_obj_cnt));
}
}
void ModelObject::clone_for_cut(ModelObject** obj)
{
(*obj) = ModelObject::new_clone(*this);
(*obj)->set_model(nullptr);
(*obj)->sla_support_points.clear();
(*obj)->sla_drain_holes.clear();
(*obj)->sla_points_status = sla::PointsStatus::NoPoints;
(*obj)->clear_volumes();
(*obj)->input_file.clear();
}
void ModelVolume::reset_extra_facets()
{
this->supported_facets.reset();
this->seam_facets.reset();
this->mmu_segmentation_facets.reset();
}
void ModelVolume::apply_tolerance()
{
assert(cut_info.is_connector);
if (cut_info.is_processed)
return;
Vec3d sf = get_scaling_factor();
/*
// correct Z offset in respect to the new size
Vec3d pos = vol->get_offset();
pos[Z] += sf[Z] * 0.5 * vol->cut_info.height_tolerance;
vol->set_offset(pos);
*/
// make a "hole" wider
sf[X] *= 1. + double(cut_info.radius_tolerance);
sf[Y] *= 1. + double(cut_info.radius_tolerance);
// make a "hole" dipper
sf[Z] *= 1. + double(cut_info.height_tolerance);
set_scaling_factor(sf);
}
void ModelObject::process_connector_cut(ModelVolume* volume, ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
std::vector<ModelObject*>& dowels, Vec3d& local_dowels_displace)
{
assert(volume->cut_info.is_connector);
volume->cut_info.set_processed();
const auto volume_matrix = volume->get_matrix();
// ! Don't apply instance transformation for the conntectors.
// This transformation is already there
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
ModelVolume* vol = upper->add_volume(*volume);
vol->set_transformation(volume_matrix);
vol->apply_tolerance();
}
if (attributes.has(ModelObjectCutAttribute::KeepLower)) {
ModelVolume* vol = lower->add_volume(*volume);
vol->set_transformation(volume_matrix);
if (volume->cut_info.connector_type == CutConnectorType::Dowel)
vol->apply_tolerance();
else
// for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug
vol->set_type(ModelVolumeType::MODEL_PART);
}
if (volume->cut_info.connector_type == CutConnectorType::Dowel &&
attributes.has(ModelObjectCutAttribute::CreateDowels)) {
ModelObject* dowel{ nullptr };
// Clone the object to duplicate instances, materials etc.
clone_for_cut(&dowel);
// add one more solid part same as connector if this connector is a dowel
ModelVolume* vol = dowel->add_volume(*volume);
vol->set_type(ModelVolumeType::MODEL_PART);
// But discard rotation and Z-offset for this volume
vol->set_rotation(Vec3d::Zero());
vol->set_offset(Z, 0.0);
// Compute the displacement (in instance coordinates) to be applied to place the dowels
local_dowels_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(1.0, 1.0, 0.0));
dowels.push_back(dowel);
}
}
void ModelObject::process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower)
{
const auto volume_matrix = instance_matrix * volume->get_matrix();
// Modifiers are not cut, but we still need to add the instance transformation
// to the modifier volume transformation to preserve their shape properly.
volume->set_transformation(Geometry::Transformation(volume_matrix));
// Some logic for the negative volumes/connectors. Add only needed modifiers
auto bb = volume->mesh().transformed_bounding_box(inverse_cut_matrix * volume_matrix);
bool is_crossed_by_cut = bb.min[Z] <= 0 && bb.max[Z] >= 0;
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && (bb.min[Z] >= 0 || is_crossed_by_cut))
upper->add_volume(*volume);
if (attributes.has(ModelObjectCutAttribute::KeepLower) && (bb.max[Z] <= 0 || is_crossed_by_cut))
lower->add_volume(*volume);
}
static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelVolume* src_volume, const Transform3d& cut_matrix)
{
if (mesh.empty())
return;
mesh.transform(cut_matrix);
ModelVolume* vol = object->add_volume(mesh);
vol->name = src_volume->name;
// Don't copy the config's ID.
vol->config.assign_config(src_volume->config);
assert(vol->config.id().valid());
assert(vol->config.id() != src_volume->config.id());
vol->set_material(src_volume->material_id(), *src_volume->material());
vol->cut_info = src_volume->cut_info;
}
void ModelObject::process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, Vec3d& local_displace)
{
const auto volume_matrix = volume->get_matrix();
using namespace Geometry;
const Transformation cut_transformation = Transformation(cut_matrix);
const Transform3d invert_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * assemble_transform(-1 * cut_transformation.get_offset());
// Transform the mesh by the combined transformation matrix.
// Flip the triangles in case the composite transformation is left handed.
TriangleMesh mesh(volume->mesh());
mesh.transform(invert_cut_matrix * instance_matrix * volume_matrix, true);
volume->reset_mesh();
// Reset volume transformation except for offset
const Vec3d offset = volume->get_offset();
volume->set_transformation(Geometry::Transformation());
volume->set_offset(offset);
// Perform cut
TriangleMesh upper_mesh, lower_mesh;
{
indexed_triangle_set upper_its, lower_its;
cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its);
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
upper_mesh = TriangleMesh(upper_its);
if (attributes.has(ModelObjectCutAttribute::KeepLower))
lower_mesh = TriangleMesh(lower_its);
}
// Add required cut parts to the objects
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
add_cut_volume(upper_mesh, upper, volume, cut_matrix);
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) {
add_cut_volume(lower_mesh, lower, volume, cut_matrix);
// Compute the displacement (in instance coordinates) to be applied to place the upper parts
// The upper part displacement is set to half of the lower part bounding box
// this is done in hope at least a part of the upper part will always be visible and draggable
local_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-0.5, -0.5, 0.0));
}
}
static void invalidate_translations(ModelObject* object, const ModelInstance* src_instance)
{
if (!object->origin_translation.isApprox(Vec3d::Zero()) && src_instance->get_offset().isApprox(Vec3d::Zero())) {
object->center_around_origin();
object->translate_instances(-object->origin_translation);
object->origin_translation = Vec3d::Zero();
}
else {
object->invalidate_bounding_box();
object->center_around_origin();
}
}
static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix,
bool place_on_cut = false, bool flip = false, Vec3d local_displace = Vec3d::Zero())
{
using namespace Geometry;
// Reset instance transformation except offset and Z-rotation
for (size_t i = 0; i < object->instances.size(); ++i) {
auto& obj_instance = object->instances[i];
const Vec3d offset = obj_instance->get_offset();
const double rot_z = obj_instance->get_rotation().z();
obj_instance->set_transformation(Transformation());
const Vec3d displace = local_displace.isApprox(Vec3d::Zero()) ? Vec3d::Zero() :
assemble_transform(Vec3d::Zero(), obj_instance->get_rotation()) * local_displace;
obj_instance->set_offset(offset + displace);
Vec3d rotation = Vec3d::Zero();
if (!flip && !place_on_cut) {
if ( i != src_instance_idx)
rotation[Z] = rot_z;
}
else {
Transform3d rotation_matrix = Transform3d::Identity();
if (flip)
rotation_matrix = rotation_transform(PI * Vec3d::UnitX());
if (place_on_cut)
rotation_matrix = rotation_matrix * Transformation(cut_matrix).get_rotation_matrix().inverse();
if (i != src_instance_idx)
rotation_matrix = rotation_transform(rot_z * Vec3d::UnitZ()) * rotation_matrix;
rotation = Transformation(rotation_matrix).get_rotation();
}
obj_instance->set_rotation(rotation);
}
}
ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes)
{
if (!attributes.has(ModelObjectCutAttribute::KeepUpper) && !attributes.has(ModelObjectCutAttribute::KeepLower))
return {};
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 = attributes.has(ModelObjectCutAttribute::KeepUpper) ? ModelObject::new_clone(*this) : nullptr;
ModelObject* lower = attributes.has(ModelObjectCutAttribute::KeepLower) ? ModelObject::new_clone(*this) : nullptr;
ModelObject* upper{ nullptr };
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
clone_for_cut(&upper);
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();
}
ModelObject* lower{ nullptr };
if (attributes.has(ModelObjectCutAttribute::KeepLower))
clone_for_cut(&lower);
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();
}
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,
@ -1224,128 +1560,72 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttr
// in the transformation matrix and not applied to the mesh transform.
// const auto instance_matrix = instances[instance]->get_matrix(true);
const auto instance_matrix = Geometry::assemble_transform(
const auto instance_matrix = assemble_transform(
Vec3d::Zero(), // don't apply offset
instances[instance]->get_rotation().cwiseProduct(Vec3d(1.0, 1.0, 0.0)), // don't apply Z-rotation
instances[instance]->get_rotation(),
instances[instance]->get_scaling_factor(),
instances[instance]->get_mirror()
);
z -= instances[instance]->get_offset().z();
const Transformation cut_transformation = Transformation(cut_matrix);
const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * assemble_transform(-1. * cut_transformation.get_offset());
// Displacement (in instance coordinates) to be applied to place the upper parts
Vec3d local_displace = Vec3d::Zero();
Vec3d local_dowels_displace = Vec3d::Zero();
for (ModelVolume *volume : volumes) {
const auto volume_matrix = volume->get_matrix();
for (ModelVolume* volume : volumes) {
volume->reset_extra_facets();
volume->supported_facets.reset();
volume->seam_facets.reset();
volume->mmu_segmentation_facets.reset();
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(instance_matrix * volume_matrix));
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
upper->add_volume(*volume);
if (attributes.has(ModelObjectCutAttribute::KeepLower))
lower->add_volume(*volume);
}
else if (! volume->mesh().empty()) {
// Transform the mesh by the combined transformation matrix.
// Flip the triangles in case the composite transformation is left handed.
TriangleMesh mesh(volume->mesh());
mesh.transform(instance_matrix * volume_matrix, true);
volume->reset_mesh();
// Reset volume transformation except for offset
const Vec3d offset = volume->get_offset();
volume->set_transformation(Geometry::Transformation());
volume->set_offset(offset);
// Perform cut
TriangleMesh upper_mesh, lower_mesh;
{
indexed_triangle_set upper_its, lower_its;
cut_mesh(mesh.its, float(z), &upper_its, &lower_its);
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
upper_mesh = TriangleMesh(upper_its);
if (attributes.has(ModelObjectCutAttribute::KeepLower))
lower_mesh = TriangleMesh(lower_its);
}
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && ! upper_mesh.empty()) {
ModelVolume* vol = upper->add_volume(upper_mesh);
vol->name = volume->name;
// Don't copy the config's ID.
vol->config.assign_config(volume->config);
assert(vol->config.id().valid());
assert(vol->config.id() != volume->config.id());
vol->set_material(volume->material_id(), *volume->material());
}
if (attributes.has(ModelObjectCutAttribute::KeepLower) && ! lower_mesh.empty()) {
ModelVolume* vol = lower->add_volume(lower_mesh);
vol->name = volume->name;
// Don't copy the config's ID.
vol->config.assign_config(volume->config);
assert(vol->config.id().valid());
assert(vol->config.id() != volume->config.id());
vol->set_material(volume->material_id(), *volume->material());
// Compute the displacement (in instance coordinates) to be applied to place the upper parts
// The upper part displacement is set to half of the lower part bounding box
// this is done in hope at least a part of the upper part will always be visible and draggable
local_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-0.5, -0.5, 0.0));
}
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) {

View File

@ -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,8 +442,21 @@ 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);
void split(ModelObjectPtrs* new_objects);
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,
// then the scaling in world coordinate system is not representable by the Geometry::Transformation structure.
@ -377,6 +479,9 @@ public:
// Get count of errors in the mesh( or all object's meshes, if volume index isn't defined)
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);

View File

@ -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();

View File

@ -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;

View File

@ -301,6 +301,7 @@ indexed_triangle_set its_make_cube(double x, double y, double z);
indexed_triangle_set its_make_prism(float width, float length, float height);
indexed_triangle_set its_make_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);

View File

@ -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

View File

@ -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

View File

@ -33,7 +33,7 @@ wxString double_to_string(double const value, const int max_precision /*= 4*/)
// Style_NoTrailingZeroes does not work on OSX. It also does not work correctly with some locales on Windows.
// 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.

View File

@ -1367,6 +1367,8 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject
&& (instance_idx == -1 || vol->composite_id.instance_id == instance_idx)
&& (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;

View File

@ -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;
}

View File

@ -493,7 +493,9 @@ void MenuFactory::append_menu_items_add_volume(wxMenu* menu)
append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].first), "",
[](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

View File

@ -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);

View File

@ -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);
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) {
@ -2511,29 +2630,30 @@ void ObjectList::part_selection_changed()
case InfoItemType::CustomSeam:
case InfoItemType::MmuSegmentation:
{
GLGizmosManager::EType gizmo_type = info_type == InfoItemType::CustomSupports ? GLGizmosManager::EType::FdmSupports :
info_type == InfoItemType::CustomSeam ? GLGizmosManager::EType::Seam :
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);
}
else {
del_subobject_from_object(obj_idx, sub_obj_idx, type);
type == itVolume ? delete_volume_from_list(obj_idx, sub_obj_idx) :
delete_instance_from_list(obj_idx, sub_obj_idx);
}
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);
}
void ObjectList::delete_from_model_and_list(const std::vector<ItemForDelete>& items_for_delete)
bool ObjectList::delete_from_model_and_list(const ItemType type, const int obj_idx, const int sub_obj_idx)
{
if (type & (itObject | itVolume | itInstance)) {
if (type & itObject) {
bool was_cut = object(obj_idx)->is_cut();
if (del_object(obj_idx)) {
delete_object_from_list(obj_idx);
if (was_cut)
update_lock_icons_for_model();
return true;
}
return false;
}
if (del_subobject_from_object(obj_idx, sub_obj_idx, type)) {
type == itVolume ? delete_volume_from_list(obj_idx, sub_obj_idx) :
delete_instance_from_list(obj_idx, sub_obj_idx);
return true;
}
}
return false;
}
bool ObjectList::delete_from_model_and_list(const std::vector<ItemForDelete>& items_for_delete)
{
if (items_for_delete.empty())
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,10 +3692,33 @@ void ObjectList::update_selections()
if (sels.size() == 0 || m_selection_mode & smSettings)
m_selection_mode = smUndef;
select_items(sels);
if (fix_cut_selection(sels) || is_connectors_item_selected(sels)) {
m_prevent_list_events = true;
// Scroll selected Item in the middle of an object list
ensure_current_item_visible();
// 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,16 +3752,29 @@ void ObjectList::update_selections_on_canvas()
volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end());
}
else if (type == itInfo) {
// When selecting an info item, select one instance of the
// respective object - a gizmo may want to be opened.
int inst_idx = selection.get_instance_idx();
int scene_obj_idx = selection.get_object_idx();
mode = Selection::Instance;
// select first instance, unless an instance of the object is already selected
if (scene_obj_idx == -1 || inst_idx == -1 || scene_obj_idx != obj_idx)
inst_idx = 0;
std::vector<unsigned int> idxs = selection.get_volume_idxs_from_instance(obj_idx, inst_idx);
volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end());
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();
int scene_obj_idx = selection.get_object_idx();
mode = Selection::Instance;
// select first instance, unless an instance of the object is already selected
if (scene_obj_idx == -1 || inst_idx == -1 || scene_obj_idx != obj_idx)
inst_idx = 0;
std::vector<unsigned int> idxs = selection.get_volume_idxs_from_instance(obj_idx, inst_idx);
volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end());
}
}
else
{
@ -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;
}

View File

@ -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();

View File

@ -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");

View File

@ -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; }

View File

@ -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();

View File

@ -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

View File

@ -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 perform_cut(const Selection& selection);
double calc_projection(const Linef3& mouse_ray) const;
BoundingBoxf3 bounding_box() const;
void update_contours();
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);
void set_center_pos(const Vec3d&center_pos, bool force = false);
bool update_bb();
void init_picking_models();
void init_rendering_items();
void render_clipper_cut();
void clear_selection();
void reset_connectors();
void init_connector_shapes();
void update_connector_shape();
void validate_connector_settings();
bool process_cut_line(SLAGizmoEventType action, const Vec2d& mouse_position);
};
} // namespace GUI

View File

@ -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"))) {

View File

@ -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();

View File

@ -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();
}

View File

@ -476,7 +476,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
m_imgui->text(m_desc.at("clipping_of_view"));
} 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"))) {

View File

@ -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()

View File

@ -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;
}

View File

@ -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()

View File

@ -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

View File

@ -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;

View File

@ -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"))) {

View File

@ -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("?")) {

View File

@ -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;

View File

@ -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
}

View File

@ -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;
};

View File

@ -99,7 +99,7 @@ bool GLGizmosManager::init()
m_gizmos.emplace_back(new GLGizmoScale3D(m_parent, "scale.svg", 1));
m_gizmos.emplace_back(new 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();

View File

@ -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;

View File

@ -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 = {

View File

@ -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;
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());
for (const ExPolygon& exp : expolys) {
triangles2d.clear();
// vertices + indices
for (auto it = m_triangles2d.cbegin(); it != m_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();
init_data.add_triangle((unsigned int)idx, (unsigned int)idx + 1, (unsigned int)idx + 2);
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(triangles2d.size());
init_data.reserve_indices(triangles2d.size());
// vertices + indices
for (auto it = triangles2d.cbegin(); it != triangles2d.cend(); it = it + 3) {
init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast<float>(), (Vec3f)up.cast<float>());
init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast<float>(), (Vec3f)up.cast<float>());
init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast<float>(), (Vec3f)up.cast<float>());
const size_t idx = it - triangles2d.cbegin();
init_data.add_triangle((unsigned int)idx, (unsigned int)idx + 1, (unsigned int)idx + 2);
}
if (!init_data.is_empty())
isl.model.init_from(std::move(init_data));
}
if (m_contour_width != 0. && ! exp.contour.empty()) {
triangles2d.clear();
// The contours must not scale with the object. Check the scale factor
// in the respective directions, create a scaled copy of the ExPolygon
// offset it and then unscale the result again.
Transform3d t = tr;
t.translation() = Vec3d::Zero();
double scale_x = (t * Vec3d::UnitX()).norm();
double scale_y = (t * Vec3d::UnitY()).norm();
// To prevent overflow after scaling, downscale the input if needed:
double extra_scale = 1.;
int32_t limit = int32_t(std::min(std::numeric_limits<coord_t>::max() / (2. * scale_x), std::numeric_limits<coord_t>::max() / (2. * scale_y)));
int32_t max_coord = 0;
for (const Point& pt : exp.contour)
max_coord = std::max(max_coord, std::max(std::abs(pt.x()), std::abs(pt.y())));
if (max_coord + m_contour_width >= limit)
extra_scale = 0.9 * double(limit) / max_coord;
ExPolygon exp_copy = exp;
if (extra_scale != 1.)
exp_copy.scale(extra_scale);
exp_copy.scale(scale_x, scale_y);
ExPolygons expolys_exp = offset_ex(exp_copy, scale_(m_contour_width));
expolys_exp = diff_ex(expolys_exp, ExPolygons({exp_copy}));
for (ExPolygon& e : expolys_exp) {
e.scale(1./scale_x, 1./scale_y);
if (extra_scale != 1.)
e.scale(1./extra_scale);
}
triangles2d = triangulate_expolygons_2f(expolys_exp, m_trafo.get_matrix().matrix().determinant() < 0.);
GLModel::Geometry init_data = GLModel::Geometry();
init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 };
init_data.reserve_vertices(triangles2d.size());
init_data.reserve_indices(triangles2d.size());
// vertices + indices
for (auto it = triangles2d.cbegin(); it != triangles2d.cend(); it = it + 3) {
init_data.add_vertex((Vec3f)(tr2 * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast<float>(), (Vec3f)up.cast<float>());
init_data.add_vertex((Vec3f)(tr2 * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast<float>(), (Vec3f)up.cast<float>());
init_data.add_vertex((Vec3f)(tr2 * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast<float>(), (Vec3f)up.cast<float>());
const size_t idx = it - triangles2d.cbegin();
init_data.add_triangle((unsigned short)idx, (unsigned short)idx + 1, (unsigned short)idx + 2);
}
if (!init_data.is_empty())
isl.model_expanded.init_from(std::move(init_data));
}
isl.expoly = std::move(exp);
isl.expoly_bb = get_extents(exp);
}
if (!init_data.is_empty())
m_model.init_from(std::move(init_data));
#else
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);
}
m_vertex_array.finalize_geometry(true);
#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

View File

@ -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.

View File

@ -1228,6 +1228,7 @@ void NotificationManager::UpdatedItemsInfoNotification::add_type(InfoItemType ty
case InfoItemType::MmuSegmentation: text += format(_L_PLURAL("%1$d object was loaded with multimaterial painting.", "%1$d objects were loaded with multimaterial painting.",(*it).second), (*it).second) + "\n"; break;
case InfoItemType::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;
}
}

View File

@ -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;
}
std::string scaled_bitmap_name = std::string();
if (node->has_warning_icon())
scaled_bitmap_name += node->warning_icon_name();
if (node->has_lock())
scaled_bitmap_name += LockIcon;
if (is_volume_node)
scaled_bitmap_name += std::to_string(vol_type);
scaled_bitmap_name += (wxGetApp().dark_mode() ? "-dm" : "-lm");
wxBitmapBundle* bmp = m_bitmap_cache->find_bndl(scaled_bitmap_name);
if (!bmp) {
std::vector<wxBitmapBundle*> bmps;
if (node->has_warning_icon())
bmps.emplace_back(node->warning_icon_name() == WarningIcon ? &m_warning_bmp : &m_warning_manifold_bmp);
if (node->has_lock())
bmps.emplace_back(&m_lock_bmp);
if (is_volume_node)
bmps.emplace_back(m_volume_bmps[vol_type]);
bmp = m_bitmap_cache->insert_bndl(scaled_bitmap_name, bmps);
}
node->SetBitmap(*bmp);
}
wxDataViewItem ObjectDataViewModel::Add(const wxString &name,
const int extruder,
const std::string& warning_icon_name/* = std::string()*/ )
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

View File

@ -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,7 +128,9 @@ public:
}
void init_container();
bool IsContainer() const
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);
@ -240,7 +241,9 @@ public:
bool valid();
#endif /* NDEBUG */
bool invalid() const { return m_idx < -1; }
bool has_warning_icon() const { return !m_warning_icon_name.empty(); }
bool has_warning_icon() const { return !m_warning_icon_name.empty(); }
bool has_lock() const { return m_has_lock; }
const std::string& warning_icon_name() const { return m_warning_icon_name; }
private:
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);
};

View File

@ -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,17 +2802,18 @@ 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);
}
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 (const size_t idx : obj_idxs)
wxGetApp().obj_list()->update_info_items(idx);
object_list_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
for (const size_t idx : obj_idxs)
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];
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;

View File

@ -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);

View File

@ -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

View File

@ -510,6 +510,28 @@ void Selection::volumes_changed(const std::vector<size_t> &map_volume_old_to_new
this->set_bounding_boxes_dirty();
}
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)

View File

@ -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;

View File

@ -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;