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

File diff suppressed because it is too large Load Diff

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.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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; }
}
@ -3463,11 +3691,34 @@ void ObjectList::update_selections()
if (sels.size() == 0 || m_selection_mode & smSettings)
m_selection_mode = smUndef;
select_items(sels);
// Scroll selected Item in the middle of an object list
ensure_current_item_visible();
if (fix_cut_selection(sels) || is_connectors_item_selected(sels)) {
m_prevent_list_events = true;
// If some part is selected, unselect all items except of selected parts of the current object
UnselectAll();
SetSelections(sels);
m_prevent_list_events = false;
// update object selection on Plater
if (!m_prevent_canvas_selection_update)
update_selections_on_canvas();
// to update the toolbar and info sizer
if (!GetSelection() || m_objects_model->GetItemType(GetSelection()) == itObject || is_connectors_item_selected()) {
auto event = SimpleEvent(EVT_OBJ_LIST_OBJECT_SELECT);
event.SetEventObject(this);
wxPostEvent(this, event);
}
part_selection_changed();
}
else {
select_items(sels);
// Scroll selected Item in the middle of an object list
ensure_current_item_visible();
}
}
void ObjectList::update_selections_on_canvas()
@ -3501,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; }

File diff suppressed because it is too large Load Diff

View File

@ -210,7 +210,10 @@ public:
#if ENABLE_RAYCAST_PICKING
void register_raycasters_for_picking() { register_grabbers_for_picking(); on_register_raycasters_for_picking(); }
void unregister_raycasters_for_picking() { unregister_grabbers_for_picking(); on_unregister_raycasters_for_picking(); }
#endif // ENABLE_RAYCAST_PICKING
#endif // ENABLE_RAYCAST_PICKING
virtual bool is_in_editing_mode() const { return false; }
virtual bool is_selection_rectangle_dragging() const { return false; }
protected:
virtual bool on_init() = 0;

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

File diff suppressed because it is too large Load Diff

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

@ -1,485 +1,487 @@
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
#include "GLGizmoMove.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#if ENABLE_WORLD_COORDINATE
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
#endif // ENABLE_WORLD_COORDINATE
#if ENABLE_LEGACY_OPENGL_REMOVAL
#include "slic3r/GUI/Plater.hpp"
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
#include <GL/glew.h>
#include <wx/utils.h>
namespace Slic3r {
namespace GUI {
const double GLGizmoMove3D::Offset = 10.0;
GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, sprite_id)
{}
std::string GLGizmoMove3D::get_tooltip() const
{
#if ENABLE_WORLD_COORDINATE
if (m_hover_id == 0)
return "X: " + format(m_displacement.x(), 2);
else if (m_hover_id == 1)
return "Y: " + format(m_displacement.y(), 2);
else if (m_hover_id == 2)
return "Z: " + format(m_displacement.z(), 2);
else
return "";
#else
const Selection& selection = m_parent.get_selection();
const bool show_position = selection.is_single_full_instance();
const Vec3d& position = selection.get_bounding_box().center();
if (m_hover_id == 0 || m_grabbers[0].dragging)
return "X: " + format(show_position ? position.x() : m_displacement.x(), 2);
else if (m_hover_id == 1 || m_grabbers[1].dragging)
return "Y: " + format(show_position ? position.y() : m_displacement.y(), 2);
else if (m_hover_id == 2 || m_grabbers[2].dragging)
return "Z: " + format(show_position ? position.z() : m_displacement.z(), 2);
else
return "";
#endif // ENABLE_WORLD_COORDINATE
}
bool GLGizmoMove3D::on_mouse(const wxMouseEvent &mouse_event) {
return use_grabbers(mouse_event);
}
void GLGizmoMove3D::data_changed() {
m_grabbers[2].enabled = !m_parent.get_selection().is_wipe_tower();
}
bool GLGizmoMove3D::on_init()
{
for (int i = 0; i < 3; ++i) {
m_grabbers.push_back(Grabber());
m_grabbers.back().extensions = GLGizmoBase::EGrabberExtension::PosZ;
}
m_grabbers[0].angles = { 0.0, 0.5 * double(PI), 0.0 };
m_grabbers[1].angles = { -0.5 * double(PI), 0.0, 0.0 };
m_shortcut_key = WXK_CONTROL_M;
return true;
}
std::string GLGizmoMove3D::on_get_name() const
{
return _u8L("Move");
}
bool GLGizmoMove3D::on_is_activable() const
{
return !m_parent.get_selection().is_empty();
}
void GLGizmoMove3D::on_start_dragging()
{
assert(m_hover_id != -1);
m_displacement = Vec3d::Zero();
#if ENABLE_WORLD_COORDINATE
const Selection& selection = m_parent.get_selection();
const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type();
if (coordinates_type == ECoordinatesType::World)
m_starting_drag_position = m_center + m_grabbers[m_hover_id].center;
else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) {
const GLVolume& v = *selection.get_first_volume();
m_starting_drag_position = m_center + v.get_instance_transformation().get_rotation_matrix() * v.get_volume_transformation().get_rotation_matrix() * m_grabbers[m_hover_id].center;
}
else {
const GLVolume& v = *selection.get_first_volume();
m_starting_drag_position = m_center + v.get_instance_transformation().get_rotation_matrix() * m_grabbers[m_hover_id].center;
}
m_starting_box_center = m_center;
m_starting_box_bottom_center = m_center;
m_starting_box_bottom_center.z() = m_bounding_box.min.z();
#else
const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box();
m_starting_drag_position = m_grabbers[m_hover_id].center;
m_starting_box_center = box.center();
m_starting_box_bottom_center = box.center();
m_starting_box_bottom_center.z() = box.min.z();
#endif // ENABLE_WORLD_COORDINATE
}
void GLGizmoMove3D::on_stop_dragging()
{
m_parent.do_move(L("Gizmo-Move"));
m_displacement = Vec3d::Zero();
}
void GLGizmoMove3D::on_dragging(const UpdateData& data)
{
if (m_hover_id == 0)
m_displacement.x() = calc_projection(data);
else if (m_hover_id == 1)
m_displacement.y() = calc_projection(data);
else if (m_hover_id == 2)
m_displacement.z() = calc_projection(data);
Selection &selection = m_parent.get_selection();
#if ENABLE_WORLD_COORDINATE
TransformationType trafo_type;
trafo_type.set_relative();
switch (wxGetApp().obj_manipul()->get_coordinates_type())
{
case ECoordinatesType::Instance: { trafo_type.set_instance(); break; }
case ECoordinatesType::Local: { trafo_type.set_local(); break; }
default: { break; }
}
selection.translate(m_displacement, trafo_type);
#else
selection.translate(m_displacement);
#endif // ENABLE_WORLD_COORDINATE
}
void GLGizmoMove3D::on_render()
{
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
glsafe(::glEnable(GL_DEPTH_TEST));
#if ENABLE_WORLD_COORDINATE
#if !ENABLE_LEGACY_OPENGL_REMOVAL
glsafe(::glPushMatrix());
#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
calc_selection_box_and_center();
#if ENABLE_LEGACY_OPENGL_REMOVAL
const Transform3d base_matrix = local_transform(m_parent.get_selection());
for (int i = 0; i < 3; ++i) {
m_grabbers[i].matrix = base_matrix;
}
#else
transform_to_local(m_parent.get_selection());
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
const Vec3d zero = Vec3d::Zero();
const Vec3d half_box_size = 0.5 * m_bounding_box.size();
// x axis
m_grabbers[0].center = { half_box_size.x() + Offset, 0.0, 0.0 };
m_grabbers[0].color = AXES_COLOR[0];
// y axis
m_grabbers[1].center = { 0.0, half_box_size.y() + Offset, 0.0 };
m_grabbers[1].color = AXES_COLOR[1];
// z axis
m_grabbers[2].center = { 0.0, 0.0, half_box_size.z() + Offset };
m_grabbers[2].color = AXES_COLOR[2];
#else
const Selection& selection = m_parent.get_selection();
const BoundingBoxf3& box = selection.get_bounding_box();
const Vec3d& center = box.center();
// x axis
m_grabbers[0].center = { box.max.x() + Offset, center.y(), center.z() };
m_grabbers[0].color = AXES_COLOR[0];
// y axis
m_grabbers[1].center = { center.x(), box.max.y() + Offset, center.z() };
m_grabbers[1].color = AXES_COLOR[1];
// z axis
m_grabbers[2].center = { center.x(), center.y(), box.max.z() + Offset };
m_grabbers[2].color = AXES_COLOR[2];
#endif // ENABLE_WORLD_COORDINATE
#if ENABLE_GL_CORE_PROFILE
if (!OpenGLManager::get_gl_info().is_core_profile())
#endif // ENABLE_GL_CORE_PROFILE
glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f));
#if ENABLE_LEGACY_OPENGL_REMOVAL
#if ENABLE_WORLD_COORDINATE
auto render_grabber_connection = [this, &zero](unsigned int id) {
#else
auto render_grabber_connection = [this, &center](unsigned int id) {
#endif // ENABLE_WORLD_COORDINATE
if (m_grabbers[id].enabled) {
#if ENABLE_WORLD_COORDINATE
if (!m_grabber_connections[id].model.is_initialized() || !m_grabber_connections[id].old_center.isApprox(m_grabbers[id].center)) {
m_grabber_connections[id].old_center = m_grabbers[id].center;
#else
if (!m_grabber_connections[id].model.is_initialized() || !m_grabber_connections[id].old_center.isApprox(center)) {
m_grabber_connections[id].old_center = center;
#endif // ENABLE_WORLD_COORDINATE
m_grabber_connections[id].model.reset();
GLModel::Geometry init_data;
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 };
init_data.color = AXES_COLOR[id];
init_data.reserve_vertices(2);
init_data.reserve_indices(2);
// vertices
#if ENABLE_WORLD_COORDINATE
init_data.add_vertex((Vec3f)zero.cast<float>());
#else
init_data.add_vertex((Vec3f)center.cast<float>());
#endif // ENABLE_WORLD_COORDINATE
init_data.add_vertex((Vec3f)m_grabbers[id].center.cast<float>());
// indices
init_data.add_line(0, 1);
m_grabber_connections[id].model.init_from(std::move(init_data));
}
m_grabber_connections[id].model.render();
}
};
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
if (m_hover_id == -1) {
#if ENABLE_LEGACY_OPENGL_REMOVAL
#if ENABLE_GL_CORE_PROFILE
GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat");
#else
GLShaderProgram* shader = wxGetApp().get_shader("flat");
#endif // ENABLE_GL_CORE_PROFILE
if (shader != nullptr) {
shader->start_using();
const Camera& camera = wxGetApp().plater()->get_camera();
#if ENABLE_WORLD_COORDINATE
shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix);
#else
shader->set_uniform("view_model_matrix", camera.get_view_matrix());
#endif // ENABLE_WORLD_COORDINATE
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
#if ENABLE_GL_CORE_PROFILE
const std::array<int, 4>& viewport = camera.get_viewport();
shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3])));
shader->set_uniform("width", 0.25f);
shader->set_uniform("gap_size", 0.0f);
#endif // ENABLE_GL_CORE_PROFILE
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
// draw axes
for (unsigned int i = 0; i < 3; ++i) {
#if ENABLE_LEGACY_OPENGL_REMOVAL
render_grabber_connection(i);
#else
if (m_grabbers[i].enabled) {
glsafe(::glColor4fv(AXES_COLOR[i].data()));
::glBegin(GL_LINES);
#if ENABLE_WORLD_COORDINATE
::glVertex3dv(zero.data());
#else
::glVertex3dv(center.data());
#endif // ENABLE_WORLD_COORDINATE
::glVertex3dv(m_grabbers[i].center.data());
glsafe(::glEnd());
}
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
}
#if ENABLE_LEGACY_OPENGL_REMOVAL
shader->stop_using();
}
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
// draw grabbers
#if ENABLE_WORLD_COORDINATE
render_grabbers(m_bounding_box);
#else
render_grabbers(box);
#endif // ENABLE_WORLD_COORDINATE
}
else {
// draw axis
#if ENABLE_LEGACY_OPENGL_REMOVAL
#if ENABLE_GL_CORE_PROFILE
GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat");
#else
GLShaderProgram* shader = wxGetApp().get_shader("flat");
#endif // ENABLE_GL_CORE_PROFILE
if (shader != nullptr) {
shader->start_using();
const Camera& camera = wxGetApp().plater()->get_camera();
#if ENABLE_WORLD_COORDINATE
shader->set_uniform("view_model_matrix", camera.get_view_matrix()* base_matrix);
#else
shader->set_uniform("view_model_matrix", camera.get_view_matrix());
#endif // ENABLE_WORLD_COORDINATE
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
#if ENABLE_GL_CORE_PROFILE
const std::array<int, 4>& viewport = camera.get_viewport();
shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3])));
shader->set_uniform("width", 0.5f);
shader->set_uniform("gap_size", 0.0f);
#endif // ENABLE_GL_CORE_PROFILE
render_grabber_connection(m_hover_id);
shader->stop_using();
}
shader = wxGetApp().get_shader("gouraud_light");
#else
glsafe(::glColor4fv(AXES_COLOR[m_hover_id].data()));
::glBegin(GL_LINES);
#if ENABLE_WORLD_COORDINATE
::glVertex3dv(zero.data());
#else
::glVertex3dv(center.data());
#endif // ENABLE_WORLD_COORDINATE
::glVertex3dv(m_grabbers[m_hover_id].center.data());
glsafe(::glEnd());
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
if (shader != nullptr) {
shader->start_using();
shader->set_uniform("emission_factor", 0.1f);
// draw grabber
#if ENABLE_WORLD_COORDINATE
const Vec3d box_size = m_bounding_box.size();
#else
const Vec3d box_size = box.size();
#endif // ENABLE_WORLD_COORDINATE
const float mean_size = (float)((box_size.x() + box_size.y() + box_size.z()) / 3.0);
m_grabbers[m_hover_id].render(true, mean_size);
shader->stop_using();
}
}
#if ENABLE_WORLD_COORDINATE
#if !ENABLE_LEGACY_OPENGL_REMOVAL
glsafe(::glPopMatrix());
#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
#endif // ENABLE_WORLD_COORDINATE
}
#if ENABLE_RAYCAST_PICKING
void GLGizmoMove3D::on_register_raycasters_for_picking()
{
// the gizmo grabbers are rendered on top of the scene, so the raytraced picker should take it into account
m_parent.set_raycaster_gizmos_on_top(true);
}
void GLGizmoMove3D::on_unregister_raycasters_for_picking()
{
m_parent.set_raycaster_gizmos_on_top(false);
}
#else
void GLGizmoMove3D::on_render_for_picking()
{
glsafe(::glDisable(GL_DEPTH_TEST));
#if ENABLE_WORLD_COORDINATE
#if ENABLE_LEGACY_OPENGL_REMOVAL
const Transform3d base_matrix = local_transform(m_parent.get_selection());
for (int i = 0; i < 3; ++i) {
m_grabbers[i].matrix = base_matrix;
}
#else
glsafe(::glPushMatrix());
transform_to_local(m_parent.get_selection());
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
render_grabbers_for_picking(m_bounding_box);
#if ENABLE_LEGACY_OPENGL_REMOVAL
#else
glsafe(::glPopMatrix());
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
#else
const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box();
render_grabbers_for_picking(box);
#endif // ENABLE_WORLD_COORDINATE
}
#endif // ENABLE_RAYCAST_PICKING
double GLGizmoMove3D::calc_projection(const UpdateData& data) const
{
double projection = 0.0;
const Vec3d starting_vec = m_starting_drag_position - m_starting_box_center;
const double len_starting_vec = starting_vec.norm();
if (len_starting_vec != 0.0) {
const Vec3d mouse_dir = data.mouse_ray.unit_vector();
// finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position
// use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form
// in our case plane normal and ray direction are the same (orthogonal view)
// when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal
const Vec3d inters = data.mouse_ray.a + (m_starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir;
// vector from the starting position to the found intersection
const Vec3d inters_vec = inters - m_starting_drag_position;
// finds projection of the vector along the staring direction
projection = inters_vec.dot(starting_vec.normalized());
}
if (wxGetKeyState(WXK_SHIFT))
projection = m_snap_step * (double)std::round(projection / m_snap_step);
return projection;
}
#if ENABLE_WORLD_COORDINATE
#if ENABLE_LEGACY_OPENGL_REMOVAL
Transform3d GLGizmoMove3D::local_transform(const Selection& selection) const
{
Transform3d ret = Geometry::assemble_transform(m_center);
if (!wxGetApp().obj_manipul()->is_world_coordinates()) {
const GLVolume& v = *selection.get_first_volume();
Transform3d orient_matrix = v.get_instance_transformation().get_rotation_matrix();
if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates())
orient_matrix = orient_matrix * v.get_volume_transformation().get_rotation_matrix();
ret = ret * orient_matrix;
}
return ret;
}
#else
void GLGizmoMove3D::transform_to_local(const Selection& selection) const
{
glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z()));
if (!wxGetApp().obj_manipul()->is_world_coordinates()) {
const GLVolume& v = *selection.get_first_volume();
Transform3d orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true);
if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates())
orient_matrix = orient_matrix * v.get_volume_transformation().get_matrix(true, false, true, true);
glsafe(::glMultMatrixd(orient_matrix.data()));
}
}
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
void GLGizmoMove3D::calc_selection_box_and_center()
{
const Selection& selection = m_parent.get_selection();
const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type();
if (coordinates_type == ECoordinatesType::World) {
m_bounding_box = selection.get_bounding_box();
m_center = m_bounding_box.center();
}
else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) {
const GLVolume& v = *selection.get_first_volume();
m_bounding_box = v.transformed_convex_hull_bounding_box(
v.get_instance_transformation().get_scaling_factor_matrix() * v.get_volume_transformation().get_scaling_factor_matrix());
m_center = v.world_matrix() * m_bounding_box.center();
}
else {
m_bounding_box.reset();
const Selection::IndicesList& ids = selection.get_volume_idxs();
for (unsigned int id : ids) {
const GLVolume& v = *selection.get_volume(id);
m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix()));
}
const Geometry::Transformation inst_trafo = selection.get_first_volume()->get_instance_transformation();
m_bounding_box = m_bounding_box.transformed(inst_trafo.get_scaling_factor_matrix());
m_center = inst_trafo.get_matrix_no_scaling_factor() * m_bounding_box.center();
}
}
#endif // ENABLE_WORLD_COORDINATE
} // namespace GUI
} // namespace Slic3r
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
#include "GLGizmoMove.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#if ENABLE_WORLD_COORDINATE
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
#endif // ENABLE_WORLD_COORDINATE
#if ENABLE_LEGACY_OPENGL_REMOVAL
#include "slic3r/GUI/Plater.hpp"
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
#include "libslic3r/Model.hpp"
#include <GL/glew.h>
#include <wx/utils.h>
namespace Slic3r {
namespace GUI {
const double GLGizmoMove3D::Offset = 10.0;
GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, sprite_id)
{}
std::string GLGizmoMove3D::get_tooltip() const
{
#if ENABLE_WORLD_COORDINATE
if (m_hover_id == 0)
return "X: " + format(m_displacement.x(), 2);
else if (m_hover_id == 1)
return "Y: " + format(m_displacement.y(), 2);
else if (m_hover_id == 2)
return "Z: " + format(m_displacement.z(), 2);
else
return "";
#else
const Selection& selection = m_parent.get_selection();
const bool show_position = selection.is_single_full_instance();
const Vec3d& position = selection.get_bounding_box().center();
if (m_hover_id == 0 || m_grabbers[0].dragging)
return "X: " + format(show_position ? position.x() : m_displacement.x(), 2);
else if (m_hover_id == 1 || m_grabbers[1].dragging)
return "Y: " + format(show_position ? position.y() : m_displacement.y(), 2);
else if (m_hover_id == 2 || m_grabbers[2].dragging)
return "Z: " + format(show_position ? position.z() : m_displacement.z(), 2);
else
return "";
#endif // ENABLE_WORLD_COORDINATE
}
bool GLGizmoMove3D::on_mouse(const wxMouseEvent &mouse_event) {
return use_grabbers(mouse_event);
}
void GLGizmoMove3D::data_changed() {
m_grabbers[2].enabled = !m_parent.get_selection().is_wipe_tower();
}
bool GLGizmoMove3D::on_init()
{
for (int i = 0; i < 3; ++i) {
m_grabbers.push_back(Grabber());
m_grabbers.back().extensions = GLGizmoBase::EGrabberExtension::PosZ;
}
m_grabbers[0].angles = { 0.0, 0.5 * double(PI), 0.0 };
m_grabbers[1].angles = { -0.5 * double(PI), 0.0, 0.0 };
m_shortcut_key = WXK_CONTROL_M;
return true;
}
std::string GLGizmoMove3D::on_get_name() const
{
return _u8L("Move");
}
bool GLGizmoMove3D::on_is_activable() const
{
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()
{
assert(m_hover_id != -1);
m_displacement = Vec3d::Zero();
#if ENABLE_WORLD_COORDINATE
const Selection& selection = m_parent.get_selection();
const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type();
if (coordinates_type == ECoordinatesType::World)
m_starting_drag_position = m_center + m_grabbers[m_hover_id].center;
else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) {
const GLVolume& v = *selection.get_first_volume();
m_starting_drag_position = m_center + v.get_instance_transformation().get_rotation_matrix() * v.get_volume_transformation().get_rotation_matrix() * m_grabbers[m_hover_id].center;
}
else {
const GLVolume& v = *selection.get_first_volume();
m_starting_drag_position = m_center + v.get_instance_transformation().get_rotation_matrix() * m_grabbers[m_hover_id].center;
}
m_starting_box_center = m_center;
m_starting_box_bottom_center = m_center;
m_starting_box_bottom_center.z() = m_bounding_box.min.z();
#else
const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box();
m_starting_drag_position = m_grabbers[m_hover_id].center;
m_starting_box_center = box.center();
m_starting_box_bottom_center = box.center();
m_starting_box_bottom_center.z() = box.min.z();
#endif // ENABLE_WORLD_COORDINATE
}
void GLGizmoMove3D::on_stop_dragging()
{
m_parent.do_move(L("Gizmo-Move"));
m_displacement = Vec3d::Zero();
}
void GLGizmoMove3D::on_dragging(const UpdateData& data)
{
if (m_hover_id == 0)
m_displacement.x() = calc_projection(data);
else if (m_hover_id == 1)
m_displacement.y() = calc_projection(data);
else if (m_hover_id == 2)
m_displacement.z() = calc_projection(data);
Selection &selection = m_parent.get_selection();
#if ENABLE_WORLD_COORDINATE
TransformationType trafo_type;
trafo_type.set_relative();
switch (wxGetApp().obj_manipul()->get_coordinates_type())
{
case ECoordinatesType::Instance: { trafo_type.set_instance(); break; }
case ECoordinatesType::Local: { trafo_type.set_local(); break; }
default: { break; }
}
selection.translate(m_displacement, trafo_type);
#else
selection.translate(m_displacement);
#endif // ENABLE_WORLD_COORDINATE
}
void GLGizmoMove3D::on_render()
{
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
glsafe(::glEnable(GL_DEPTH_TEST));
#if ENABLE_WORLD_COORDINATE
#if !ENABLE_LEGACY_OPENGL_REMOVAL
glsafe(::glPushMatrix());
#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
calc_selection_box_and_center();
#if ENABLE_LEGACY_OPENGL_REMOVAL
const Transform3d base_matrix = local_transform(m_parent.get_selection());
for (int i = 0; i < 3; ++i) {
m_grabbers[i].matrix = base_matrix;
}
#else
transform_to_local(m_parent.get_selection());
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
const Vec3d zero = Vec3d::Zero();
const Vec3d half_box_size = 0.5 * m_bounding_box.size();
// x axis
m_grabbers[0].center = { half_box_size.x() + Offset, 0.0, 0.0 };
m_grabbers[0].color = AXES_COLOR[0];
// y axis
m_grabbers[1].center = { 0.0, half_box_size.y() + Offset, 0.0 };
m_grabbers[1].color = AXES_COLOR[1];
// z axis
m_grabbers[2].center = { 0.0, 0.0, half_box_size.z() + Offset };
m_grabbers[2].color = AXES_COLOR[2];
#else
const Selection& selection = m_parent.get_selection();
const BoundingBoxf3& box = selection.get_bounding_box();
const Vec3d& center = box.center();
// x axis
m_grabbers[0].center = { box.max.x() + Offset, center.y(), center.z() };
m_grabbers[0].color = AXES_COLOR[0];
// y axis
m_grabbers[1].center = { center.x(), box.max.y() + Offset, center.z() };
m_grabbers[1].color = AXES_COLOR[1];
// z axis
m_grabbers[2].center = { center.x(), center.y(), box.max.z() + Offset };
m_grabbers[2].color = AXES_COLOR[2];
#endif // ENABLE_WORLD_COORDINATE
#if ENABLE_GL_CORE_PROFILE
if (!OpenGLManager::get_gl_info().is_core_profile())
#endif // ENABLE_GL_CORE_PROFILE
glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f));
#if ENABLE_LEGACY_OPENGL_REMOVAL
#if ENABLE_WORLD_COORDINATE
auto render_grabber_connection = [this, &zero](unsigned int id) {
#else
auto render_grabber_connection = [this, &center](unsigned int id) {
#endif // ENABLE_WORLD_COORDINATE
if (m_grabbers[id].enabled) {
#if ENABLE_WORLD_COORDINATE
if (!m_grabber_connections[id].model.is_initialized() || !m_grabber_connections[id].old_center.isApprox(m_grabbers[id].center)) {
m_grabber_connections[id].old_center = m_grabbers[id].center;
#else
if (!m_grabber_connections[id].model.is_initialized() || !m_grabber_connections[id].old_center.isApprox(center)) {
m_grabber_connections[id].old_center = center;
#endif // ENABLE_WORLD_COORDINATE
m_grabber_connections[id].model.reset();
GLModel::Geometry init_data;
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 };
init_data.color = AXES_COLOR[id];
init_data.reserve_vertices(2);
init_data.reserve_indices(2);
// vertices
#if ENABLE_WORLD_COORDINATE
init_data.add_vertex((Vec3f)zero.cast<float>());
#else
init_data.add_vertex((Vec3f)center.cast<float>());
#endif // ENABLE_WORLD_COORDINATE
init_data.add_vertex((Vec3f)m_grabbers[id].center.cast<float>());
// indices
init_data.add_line(0, 1);
m_grabber_connections[id].model.init_from(std::move(init_data));
}
m_grabber_connections[id].model.render();
}
};
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
if (m_hover_id == -1) {
#if ENABLE_LEGACY_OPENGL_REMOVAL
#if ENABLE_GL_CORE_PROFILE
GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat");
#else
GLShaderProgram* shader = wxGetApp().get_shader("flat");
#endif // ENABLE_GL_CORE_PROFILE
if (shader != nullptr) {
shader->start_using();
const Camera& camera = wxGetApp().plater()->get_camera();
#if ENABLE_WORLD_COORDINATE
shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix);
#else
shader->set_uniform("view_model_matrix", camera.get_view_matrix());
#endif // ENABLE_WORLD_COORDINATE
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
#if ENABLE_GL_CORE_PROFILE
const std::array<int, 4>& viewport = camera.get_viewport();
shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3])));
shader->set_uniform("width", 0.25f);
shader->set_uniform("gap_size", 0.0f);
#endif // ENABLE_GL_CORE_PROFILE
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
// draw axes
for (unsigned int i = 0; i < 3; ++i) {
#if ENABLE_LEGACY_OPENGL_REMOVAL
render_grabber_connection(i);
#else
if (m_grabbers[i].enabled) {
glsafe(::glColor4fv(AXES_COLOR[i].data()));
::glBegin(GL_LINES);
#if ENABLE_WORLD_COORDINATE
::glVertex3dv(zero.data());
#else
::glVertex3dv(center.data());
#endif // ENABLE_WORLD_COORDINATE
::glVertex3dv(m_grabbers[i].center.data());
glsafe(::glEnd());
}
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
}
#if ENABLE_LEGACY_OPENGL_REMOVAL
shader->stop_using();
}
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
// draw grabbers
#if ENABLE_WORLD_COORDINATE
render_grabbers(m_bounding_box);
#else
render_grabbers(box);
#endif // ENABLE_WORLD_COORDINATE
}
else {
// draw axis
#if ENABLE_LEGACY_OPENGL_REMOVAL
#if ENABLE_GL_CORE_PROFILE
GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat");
#else
GLShaderProgram* shader = wxGetApp().get_shader("flat");
#endif // ENABLE_GL_CORE_PROFILE
if (shader != nullptr) {
shader->start_using();
const Camera& camera = wxGetApp().plater()->get_camera();
#if ENABLE_WORLD_COORDINATE
shader->set_uniform("view_model_matrix", camera.get_view_matrix()* base_matrix);
#else
shader->set_uniform("view_model_matrix", camera.get_view_matrix());
#endif // ENABLE_WORLD_COORDINATE
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
#if ENABLE_GL_CORE_PROFILE
const std::array<int, 4>& viewport = camera.get_viewport();
shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3])));
shader->set_uniform("width", 0.5f);
shader->set_uniform("gap_size", 0.0f);
#endif // ENABLE_GL_CORE_PROFILE
render_grabber_connection(m_hover_id);
shader->stop_using();
}
shader = wxGetApp().get_shader("gouraud_light");
#else
glsafe(::glColor4fv(AXES_COLOR[m_hover_id].data()));
::glBegin(GL_LINES);
#if ENABLE_WORLD_COORDINATE
::glVertex3dv(zero.data());
#else
::glVertex3dv(center.data());
#endif // ENABLE_WORLD_COORDINATE
::glVertex3dv(m_grabbers[m_hover_id].center.data());
glsafe(::glEnd());
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
if (shader != nullptr) {
shader->start_using();
shader->set_uniform("emission_factor", 0.1f);
// draw grabber
#if ENABLE_WORLD_COORDINATE
const Vec3d box_size = m_bounding_box.size();
#else
const Vec3d box_size = box.size();
#endif // ENABLE_WORLD_COORDINATE
const float mean_size = (float)((box_size.x() + box_size.y() + box_size.z()) / 3.0);
m_grabbers[m_hover_id].render(true, mean_size);
shader->stop_using();
}
}
#if ENABLE_WORLD_COORDINATE
#if !ENABLE_LEGACY_OPENGL_REMOVAL
glsafe(::glPopMatrix());
#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
#endif // ENABLE_WORLD_COORDINATE
}
#if ENABLE_RAYCAST_PICKING
void GLGizmoMove3D::on_register_raycasters_for_picking()
{
// the gizmo grabbers are rendered on top of the scene, so the raytraced picker should take it into account
m_parent.set_raycaster_gizmos_on_top(true);
}
void GLGizmoMove3D::on_unregister_raycasters_for_picking()
{
m_parent.set_raycaster_gizmos_on_top(false);
}
#else
void GLGizmoMove3D::on_render_for_picking()
{
glsafe(::glDisable(GL_DEPTH_TEST));
#if ENABLE_WORLD_COORDINATE
#if ENABLE_LEGACY_OPENGL_REMOVAL
const Transform3d base_matrix = local_transform(m_parent.get_selection());
for (int i = 0; i < 3; ++i) {
m_grabbers[i].matrix = base_matrix;
}
#else
glsafe(::glPushMatrix());
transform_to_local(m_parent.get_selection());
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
render_grabbers_for_picking(m_bounding_box);
#if ENABLE_LEGACY_OPENGL_REMOVAL
#else
glsafe(::glPopMatrix());
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
#else
const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box();
render_grabbers_for_picking(box);
#endif // ENABLE_WORLD_COORDINATE
}
#endif // ENABLE_RAYCAST_PICKING
double GLGizmoMove3D::calc_projection(const UpdateData& data) const
{
double projection = 0.0;
const Vec3d starting_vec = m_starting_drag_position - m_starting_box_center;
const double len_starting_vec = starting_vec.norm();
if (len_starting_vec != 0.0) {
const Vec3d mouse_dir = data.mouse_ray.unit_vector();
// finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position
// use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form
// in our case plane normal and ray direction are the same (orthogonal view)
// when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal
const Vec3d inters = data.mouse_ray.a + (m_starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir;
// vector from the starting position to the found intersection
const Vec3d inters_vec = inters - m_starting_drag_position;
// finds projection of the vector along the staring direction
projection = inters_vec.dot(starting_vec.normalized());
}
if (wxGetKeyState(WXK_SHIFT))
projection = m_snap_step * (double)std::round(projection / m_snap_step);
return projection;
}
#if ENABLE_WORLD_COORDINATE
#if ENABLE_LEGACY_OPENGL_REMOVAL
Transform3d GLGizmoMove3D::local_transform(const Selection& selection) const
{
Transform3d ret = Geometry::assemble_transform(m_center);
if (!wxGetApp().obj_manipul()->is_world_coordinates()) {
const GLVolume& v = *selection.get_first_volume();
Transform3d orient_matrix = v.get_instance_transformation().get_rotation_matrix();
if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates())
orient_matrix = orient_matrix * v.get_volume_transformation().get_rotation_matrix();
ret = ret * orient_matrix;
}
return ret;
}
#else
void GLGizmoMove3D::transform_to_local(const Selection& selection) const
{
glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z()));
if (!wxGetApp().obj_manipul()->is_world_coordinates()) {
const GLVolume& v = *selection.get_first_volume();
Transform3d orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true);
if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates())
orient_matrix = orient_matrix * v.get_volume_transformation().get_matrix(true, false, true, true);
glsafe(::glMultMatrixd(orient_matrix.data()));
}
}
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
void GLGizmoMove3D::calc_selection_box_and_center()
{
const Selection& selection = m_parent.get_selection();
const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type();
if (coordinates_type == ECoordinatesType::World) {
m_bounding_box = selection.get_bounding_box();
m_center = m_bounding_box.center();
}
else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) {
const GLVolume& v = *selection.get_first_volume();
m_bounding_box = v.transformed_convex_hull_bounding_box(
v.get_instance_transformation().get_scaling_factor_matrix() * v.get_volume_transformation().get_scaling_factor_matrix());
m_center = v.world_matrix() * m_bounding_box.center();
}
else {
m_bounding_box.reset();
const Selection::IndicesList& ids = selection.get_volume_idxs();
for (unsigned int id : ids) {
const GLVolume& v = *selection.get_volume(id);
m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix()));
}
const Geometry::Transformation inst_trafo = selection.get_first_volume()->get_instance_transformation();
m_bounding_box = m_bounding_box.transformed(inst_trafo.get_scaling_factor_matrix());
m_center = inst_trafo.get_matrix_no_scaling_factor() * m_bounding_box.center();
}
}
#endif // ENABLE_WORLD_COORDINATE
} // namespace GUI
} // namespace Slic3r

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

File diff suppressed because it is too large Load Diff

View File

@ -1,233 +1,233 @@
#ifndef slic3r_GLGizmoRotate_hpp_
#define slic3r_GLGizmoRotate_hpp_
#include "GLGizmoBase.hpp"
namespace Slic3r {
namespace GUI {
class Selection;
class GLGizmoRotate : public GLGizmoBase
{
static const float Offset;
static const unsigned int AngleResolution;
static const unsigned int ScaleStepsCount;
static const float ScaleStepRad;
static const unsigned int ScaleLongEvery;
static const float ScaleLongTooth;
static const unsigned int SnapRegionsCount;
static const float GrabberOffset;
public:
enum Axis : unsigned char
{
X=0,
Y=1,
Z=2
};
private:
Axis m_axis;
double m_angle{ 0.0 };
Vec3d m_center{ Vec3d::Zero() };
float m_radius{ 0.0f };
float m_snap_coarse_in_radius{ 0.0f };
float m_snap_coarse_out_radius{ 0.0f };
float m_snap_fine_in_radius{ 0.0f };
float m_snap_fine_out_radius{ 0.0f };
#if ENABLE_WORLD_COORDINATE
BoundingBoxf3 m_bounding_box;
Transform3d m_orient_matrix{ Transform3d::Identity() };
#endif // ENABLE_WORLD_COORDINATE
#if ENABLE_LEGACY_OPENGL_REMOVAL
GLModel m_circle;
GLModel m_scale;
GLModel m_snap_radii;
GLModel m_reference_radius;
GLModel m_angle_arc;
struct GrabberConnection
{
GLModel model;
Vec3d old_center{ Vec3d::Zero() };
};
GrabberConnection m_grabber_connection;
float m_old_radius{ 0.0f };
float m_old_hover_radius{ 0.0f };
float m_old_angle{ 0.0f };
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
ColorRGBA m_drag_color;
ColorRGBA m_highlight_color;
public:
GLGizmoRotate(GLCanvas3D& parent, Axis axis);
virtual ~GLGizmoRotate() = default;
double get_angle() const { return m_angle; }
void set_angle(double angle);
std::string get_tooltip() const override;
void start_dragging();
void stop_dragging();
void enable_grabber();
void disable_grabber();
void set_highlight_color(const ColorRGBA &color);
/// <summary>
/// Postpone to Grabber for move
/// Detect move of object by dragging
/// </summary>
/// <param name="mouse_event">Keep information about mouse click</param>
/// <returns>Return True when use the information otherwise False.</returns>
bool on_mouse(const wxMouseEvent &mouse_event) override;
void dragging(const UpdateData &data);
protected:
bool on_init() override;
std::string on_get_name() const override { return ""; }
void on_start_dragging() override;
void on_dragging(const UpdateData &data) override;
void on_render() override;
#if !ENABLE_RAYCAST_PICKING
void on_render_for_picking() override;
#endif // !ENABLE_RAYCAST_PICKING
private:
#if ENABLE_LEGACY_OPENGL_REMOVAL
void render_circle(const ColorRGBA& color, bool radius_changed);
void render_scale(const ColorRGBA& color, bool radius_changed);
void render_snap_radii(const ColorRGBA& color, bool radius_changed);
void render_reference_radius(const ColorRGBA& color, bool radius_changed);
void render_angle_arc(const ColorRGBA& color, bool radius_changed);
void render_grabber_connection(const ColorRGBA& color, bool radius_changed);
#else
void render_circle() const;
void render_scale() const;
void render_snap_radii() const;
void render_reference_radius() const;
void render_angle() const;
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
void render_grabber(const BoundingBoxf3& box);
#if ENABLE_LEGACY_OPENGL_REMOVAL
Transform3d local_transform(const Selection& selection) const;
#else
void transform_to_local(const Selection& selection) const;
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
// returns the intersection of the mouse ray with the plane perpendicular to the gizmo axis, in local coordinate
#if ENABLE_WORLD_COORDINATE
Vec3d mouse_position_in_local_plane(const Linef3& mouse_ray) const;
#else
Vec3d mouse_position_in_local_plane(const Linef3& mouse_ray, const Selection& selection) const;
#endif // ENABLE_WORLD_COORDINATE
#if ENABLE_WORLD_COORDINATE
void init_data_from_selection(const Selection& selection);
#endif // ENABLE_WORLD_COORDINATE
};
class GLGizmoRotate3D : public GLGizmoBase
{
std::array<GLGizmoRotate, 3> m_gizmos;
public:
GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
Vec3d get_rotation() const { return Vec3d(m_gizmos[X].get_angle(), m_gizmos[Y].get_angle(), m_gizmos[Z].get_angle()); }
void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation.x()); m_gizmos[Y].set_angle(rotation.y()); m_gizmos[Z].set_angle(rotation.z()); }
std::string get_tooltip() const override {
std::string tooltip = m_gizmos[X].get_tooltip();
if (tooltip.empty())
tooltip = m_gizmos[Y].get_tooltip();
if (tooltip.empty())
tooltip = m_gizmos[Z].get_tooltip();
return tooltip;
}
/// <summary>
/// Postpone to Rotation
/// </summary>
/// <param name="mouse_event">Keep information about mouse click</param>
/// <returns>Return True when use the information otherwise False.</returns>
bool on_mouse(const wxMouseEvent &mouse_event) override;
void data_changed() override;
protected:
bool on_init() override;
std::string on_get_name() const override;
void on_set_state() override {
for (GLGizmoRotate& g : m_gizmos)
g.set_state(m_state);
}
void on_set_hover_id() override {
for (int i = 0; i < 3; ++i)
m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1);
}
void on_enable_grabber(unsigned int id) override {
if (id < 3)
m_gizmos[id].enable_grabber();
}
void on_disable_grabber(unsigned int id) override {
if (id < 3)
m_gizmos[id].disable_grabber();
}
bool on_is_activable() const override;
void on_start_dragging() override;
void on_stop_dragging() override;
void on_dragging(const UpdateData &data) override;
void on_render() override;
#if ENABLE_RAYCAST_PICKING
virtual void on_register_raycasters_for_picking() override;
virtual void on_unregister_raycasters_for_picking() override;
#else
void on_render_for_picking() override {
for (GLGizmoRotate& g : m_gizmos) {
g.render_for_picking();
}
}
#endif // ENABLE_RAYCAST_PICKING
void on_render_input_window(float x, float y, float bottom_limit) override;
private:
class RotoptimzeWindow
{
ImGuiWrapper *m_imgui = nullptr;
public:
struct State {
float accuracy = 1.f;
int method_id = 0;
};
struct Alignment { float x, y, bottom_limit; };
RotoptimzeWindow(ImGuiWrapper * imgui,
State & state,
const Alignment &bottom_limit);
~RotoptimzeWindow();
RotoptimzeWindow(const RotoptimzeWindow&) = delete;
RotoptimzeWindow(RotoptimzeWindow &&) = delete;
RotoptimzeWindow& operator=(const RotoptimzeWindow &) = delete;
RotoptimzeWindow& operator=(RotoptimzeWindow &&) = delete;
};
RotoptimzeWindow::State m_rotoptimizewin_state = {};
void load_rotoptimize_state();
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmoRotate_hpp_
#ifndef slic3r_GLGizmoRotate_hpp_
#define slic3r_GLGizmoRotate_hpp_
#include "GLGizmoBase.hpp"
namespace Slic3r {
namespace GUI {
class Selection;
class GLGizmoRotate : public GLGizmoBase
{
static const float Offset;
static const unsigned int AngleResolution;
static const unsigned int ScaleStepsCount;
static const float ScaleStepRad;
static const unsigned int ScaleLongEvery;
static const float ScaleLongTooth;
static const unsigned int SnapRegionsCount;
static const float GrabberOffset;
public:
enum Axis : unsigned char
{
X=0,
Y=1,
Z=2
};
private:
Axis m_axis;
double m_angle{ 0.0 };
Vec3d m_center{ Vec3d::Zero() };
float m_radius{ 0.0f };
float m_snap_coarse_in_radius{ 0.0f };
float m_snap_coarse_out_radius{ 0.0f };
float m_snap_fine_in_radius{ 0.0f };
float m_snap_fine_out_radius{ 0.0f };
#if ENABLE_WORLD_COORDINATE
BoundingBoxf3 m_bounding_box;
Transform3d m_orient_matrix{ Transform3d::Identity() };
#endif // ENABLE_WORLD_COORDINATE
#if ENABLE_LEGACY_OPENGL_REMOVAL
GLModel m_circle;
GLModel m_scale;
GLModel m_snap_radii;
GLModel m_reference_radius;
GLModel m_angle_arc;
struct GrabberConnection
{
GLModel model;
Vec3d old_center{ Vec3d::Zero() };
};
GrabberConnection m_grabber_connection;
float m_old_radius{ 0.0f };
float m_old_hover_radius{ 0.0f };
float m_old_angle{ 0.0f };
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
ColorRGBA m_drag_color;
ColorRGBA m_highlight_color;
public:
GLGizmoRotate(GLCanvas3D& parent, Axis axis);
virtual ~GLGizmoRotate() = default;
double get_angle() const { return m_angle; }
void set_angle(double angle);
std::string get_tooltip() const override;
void start_dragging();
void stop_dragging();
void enable_grabber();
void disable_grabber();
void set_highlight_color(const ColorRGBA &color);
/// <summary>
/// Postpone to Grabber for move
/// Detect move of object by dragging
/// </summary>
/// <param name="mouse_event">Keep information about mouse click</param>
/// <returns>Return True when use the information otherwise False.</returns>
bool on_mouse(const wxMouseEvent &mouse_event) override;
void dragging(const UpdateData &data);
protected:
bool on_init() override;
std::string on_get_name() const override { return ""; }
void on_start_dragging() override;
void on_dragging(const UpdateData &data) override;
void on_render() override;
#if !ENABLE_RAYCAST_PICKING
void on_render_for_picking() override;
#endif // !ENABLE_RAYCAST_PICKING
private:
#if ENABLE_LEGACY_OPENGL_REMOVAL
void render_circle(const ColorRGBA& color, bool radius_changed);
void render_scale(const ColorRGBA& color, bool radius_changed);
void render_snap_radii(const ColorRGBA& color, bool radius_changed);
void render_reference_radius(const ColorRGBA& color, bool radius_changed);
void render_angle_arc(const ColorRGBA& color, bool radius_changed);
void render_grabber_connection(const ColorRGBA& color, bool radius_changed);
#else
void render_circle() const;
void render_scale() const;
void render_snap_radii() const;
void render_reference_radius() const;
void render_angle() const;
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
void render_grabber(const BoundingBoxf3& box);
#if ENABLE_LEGACY_OPENGL_REMOVAL
Transform3d local_transform(const Selection& selection) const;
#else
void transform_to_local(const Selection& selection) const;
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
// returns the intersection of the mouse ray with the plane perpendicular to the gizmo axis, in local coordinate
#if ENABLE_WORLD_COORDINATE
Vec3d mouse_position_in_local_plane(const Linef3& mouse_ray) const;
#else
Vec3d mouse_position_in_local_plane(const Linef3& mouse_ray, const Selection& selection) const;
#endif // ENABLE_WORLD_COORDINATE
#if ENABLE_WORLD_COORDINATE
void init_data_from_selection(const Selection& selection);
#endif // ENABLE_WORLD_COORDINATE
};
class GLGizmoRotate3D : public GLGizmoBase
{
std::array<GLGizmoRotate, 3> m_gizmos;
public:
GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
Vec3d get_rotation() const { return Vec3d(m_gizmos[X].get_angle(), m_gizmos[Y].get_angle(), m_gizmos[Z].get_angle()); }
void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation.x()); m_gizmos[Y].set_angle(rotation.y()); m_gizmos[Z].set_angle(rotation.z()); }
std::string get_tooltip() const override {
std::string tooltip = m_gizmos[X].get_tooltip();
if (tooltip.empty())
tooltip = m_gizmos[Y].get_tooltip();
if (tooltip.empty())
tooltip = m_gizmos[Z].get_tooltip();
return tooltip;
}
/// <summary>
/// Postpone to Rotation
/// </summary>
/// <param name="mouse_event">Keep information about mouse click</param>
/// <returns>Return True when use the information otherwise False.</returns>
bool on_mouse(const wxMouseEvent &mouse_event) override;
void data_changed() override;
protected:
bool on_init() override;
std::string on_get_name() const override;
void on_set_state() override {
for (GLGizmoRotate& g : m_gizmos)
g.set_state(m_state);
}
void on_set_hover_id() override {
for (int i = 0; i < 3; ++i)
m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1);
}
void on_enable_grabber(unsigned int id) override {
if (id < 3)
m_gizmos[id].enable_grabber();
}
void on_disable_grabber(unsigned int id) override {
if (id < 3)
m_gizmos[id].disable_grabber();
}
bool on_is_activable() const override;
void on_start_dragging() override;
void on_stop_dragging() override;
void on_dragging(const UpdateData &data) override;
void on_render() override;
#if ENABLE_RAYCAST_PICKING
virtual void on_register_raycasters_for_picking() override;
virtual void on_unregister_raycasters_for_picking() override;
#else
void on_render_for_picking() override {
for (GLGizmoRotate& g : m_gizmos) {
g.render_for_picking();
}
}
#endif // ENABLE_RAYCAST_PICKING
void on_render_input_window(float x, float y, float bottom_limit) override;
private:
class RotoptimzeWindow
{
ImGuiWrapper *m_imgui = nullptr;
public:
struct State {
float accuracy = 1.f;
int method_id = 0;
};
struct Alignment { float x, y, bottom_limit; };
RotoptimzeWindow(ImGuiWrapper * imgui,
State & state,
const Alignment &bottom_limit);
~RotoptimzeWindow();
RotoptimzeWindow(const RotoptimzeWindow&) = delete;
RotoptimzeWindow(RotoptimzeWindow &&) = delete;
RotoptimzeWindow& operator=(const RotoptimzeWindow &) = delete;
RotoptimzeWindow& operator=(RotoptimzeWindow &&) = delete;
};
RotoptimzeWindow::State m_rotoptimizewin_state = {};
void load_rotoptimize_state();
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmoRotate_hpp_

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;