mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-07-26 04:34:23 +08:00
Merge branch 'master' of https://github.com/Prusa-Development/PrusaSlicerPrivate into et_spe1784_binary_gcode
This commit is contained in:
commit
d9d771c268
1219
cmake/modules/FindwxWidgets.cmake
Normal file
1219
cmake/modules/FindwxWidgets.cmake
Normal file
File diff suppressed because it is too large
Load Diff
13
resources/icons/snap.svg
Normal file
13
resources/icons/snap.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="16px" height="16px" viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||
<g>
|
||||
<path fill="#808080" d="M9,15H7c-0.5522847,0-1-0.4477148-1-1V5c0-0.5522847,0.4477153-1,1-1h2c0.5522852,0,1,0.4477153,1,1v9
|
||||
C10,14.5522852,9.5522852,15,9,15z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-miterlimit="5" d="M1.5,11.5h2v-8c0-1.1045694,0.8954306-2,2-2h5c1.1045694,0,2,0.8954306,2,2v8h2"/>
|
||||
</g>
|
||||
<line fill="none" stroke="#808080" stroke-linecap="round" stroke-miterlimit="10" x1="1.5" y1="14.5" x2="14.5" y2="14.5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 846 B |
@ -49,26 +49,12 @@ if (SLIC3R_GUI)
|
||||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
set (wxWidgets_CONFIG_OPTIONS "--toolkit=gtk${SLIC3R_GTK}")
|
||||
find_package(wxWidgets 3.2 QUIET COMPONENTS base core adv html gl)
|
||||
if (NOT wxWidgets_FOUND)
|
||||
message(FATAL_ERROR "Could not find wxWidgets >= 3.2")
|
||||
endif ()
|
||||
|
||||
if (NOT "${wxWidgets_USE_FILE}" STREQUAL "")
|
||||
include(${wxWidgets_USE_FILE})
|
||||
endif ()
|
||||
else ()
|
||||
find_package(wxWidgets 3.2 COMPONENTS html adv gl core base)
|
||||
if (NOT wxWidgets_FOUND)
|
||||
message(STATUS "Trying to find wxWidgets in CONFIG mode...")
|
||||
find_package(wxWidgets 3.2 CONFIG REQUIRED COMPONENTS html adv gl core base)
|
||||
slic3r_remap_configs(wx::wxhtml wx::wxadv wx::wxgl wx::wxcore wx::wxbase RelWithDebInfo Release)
|
||||
else ()
|
||||
if (NOT "${wxWidgets_USE_FILE}" STREQUAL "")
|
||||
include(${wxWidgets_USE_FILE})
|
||||
endif ()
|
||||
endif ()
|
||||
endif ()
|
||||
find_package(wxWidgets 3.2 MODULE REQUIRED COMPONENTS base core adv html gl)
|
||||
|
||||
include(${wxWidgets_USE_FILE})
|
||||
|
||||
slic3r_remap_configs(wx::wxhtml wx::wxadv wx::wxgl wx::wxcore wx::wxbase RelWithDebInfo Release)
|
||||
|
||||
if(UNIX)
|
||||
message(STATUS "wx-config path: ${wxWidgets_CONFIG_EXECUTABLE}")
|
||||
|
@ -40,6 +40,7 @@
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
#include "libslic3r/GCode/PostProcessor.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/CutUtils.hpp"
|
||||
#include "libslic3r/ModelArrange.hpp"
|
||||
#include "libslic3r/Platform.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
@ -437,8 +438,11 @@ int CLI::run(int argc, char **argv)
|
||||
}
|
||||
#else
|
||||
// model.objects.front()->cut(0, m_config.opt_float("cut"), ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::FlipLower);
|
||||
model.objects.front()->cut(0, Geometry::translation_transform(m_config.opt_float("cut") * Vec3d::UnitZ()),
|
||||
Cut cut(model.objects.front(), 0, Geometry::translation_transform(m_config.opt_float("cut") * Vec3d::UnitZ()),
|
||||
ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::PlaceOnCutUpper);
|
||||
auto cut_objects = cut.perform_with_plane();
|
||||
for (ModelObject* obj : cut_objects)
|
||||
model.add_object(*obj);
|
||||
#endif
|
||||
model.delete_object(size_t(0));
|
||||
}
|
||||
|
@ -152,6 +152,7 @@ namespace ImGui
|
||||
// const wchar_t MmuSegmentationMarker = 0x1F;
|
||||
const wchar_t PlugMarker = 0x1C;
|
||||
const wchar_t DowelMarker = 0x1D;
|
||||
const wchar_t SnapMarker = 0x1E;
|
||||
// Do not forget use following letters only in wstring
|
||||
const wchar_t DocumentationButton = 0x2600;
|
||||
const wchar_t DocumentationHoverButton = 0x2601;
|
||||
|
@ -206,6 +206,8 @@ set(SLIC3R_SOURCES
|
||||
BlacklistedLibraryCheck.hpp
|
||||
LocalesUtils.cpp
|
||||
LocalesUtils.hpp
|
||||
CutUtils.cpp
|
||||
CutUtils.hpp
|
||||
Model.cpp
|
||||
Model.hpp
|
||||
ModelArrange.hpp
|
||||
|
645
src/libslic3r/CutUtils.cpp
Normal file
645
src/libslic3r/CutUtils.cpp
Normal file
@ -0,0 +1,645 @@
|
||||
|
||||
#include "CutUtils.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include "Model.hpp"
|
||||
#include "TriangleMeshSlicer.hpp"
|
||||
#include "TriangleSelector.hpp"
|
||||
#include "ObjectID.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
using namespace Geometry;
|
||||
|
||||
static void apply_tolerance(ModelVolume* vol)
|
||||
{
|
||||
ModelVolume::CutInfo& cut_info = vol->cut_info;
|
||||
|
||||
assert(cut_info.is_connector);
|
||||
if (!cut_info.is_processed)
|
||||
return;
|
||||
|
||||
Vec3d sf = vol->get_scaling_factor();
|
||||
|
||||
// make a "hole" wider
|
||||
sf[X] += double(cut_info.radius_tolerance);
|
||||
sf[Y] += double(cut_info.radius_tolerance);
|
||||
|
||||
// make a "hole" dipper
|
||||
sf[Z] += double(cut_info.height_tolerance);
|
||||
|
||||
vol->set_scaling_factor(sf);
|
||||
|
||||
// correct offset in respect to the new depth
|
||||
Vec3d rot_norm = rotation_transform(vol->get_rotation()) * Vec3d::UnitZ();
|
||||
if (rot_norm.norm() != 0.0)
|
||||
rot_norm.normalize();
|
||||
|
||||
double z_offset = 0.5 * static_cast<double>(cut_info.height_tolerance);
|
||||
if (cut_info.connector_type == CutConnectorType::Plug ||
|
||||
cut_info.connector_type == CutConnectorType::Snap)
|
||||
z_offset -= 0.05; // add small Z offset to better preview
|
||||
|
||||
vol->set_offset(vol->get_offset() + rot_norm * z_offset);
|
||||
}
|
||||
|
||||
static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelVolume* src_volume, const Transform3d& cut_matrix, const std::string& suffix = {}, ModelVolumeType type = ModelVolumeType::MODEL_PART)
|
||||
{
|
||||
if (mesh.empty())
|
||||
return;
|
||||
|
||||
mesh.transform(cut_matrix);
|
||||
ModelVolume* vol = object->add_volume(mesh);
|
||||
vol->set_type(type);
|
||||
|
||||
vol->name = src_volume->name + suffix;
|
||||
// Don't copy the config's ID.
|
||||
vol->config.assign_config(src_volume->config);
|
||||
assert(vol->config.id().valid());
|
||||
assert(vol->config.id() != src_volume->config.id());
|
||||
vol->set_material(src_volume->material_id(), *src_volume->material());
|
||||
vol->cut_info = src_volume->cut_info;
|
||||
}
|
||||
|
||||
static void process_volume_cut( ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh)
|
||||
{
|
||||
const auto volume_matrix = volume->get_matrix();
|
||||
|
||||
const Transformation cut_transformation = Transformation(cut_matrix);
|
||||
const Transform3d invert_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1 * cut_transformation.get_offset());
|
||||
|
||||
// Transform the mesh by the combined transformation matrix.
|
||||
// Flip the triangles in case the composite transformation is left handed.
|
||||
TriangleMesh mesh(volume->mesh());
|
||||
mesh.transform(invert_cut_matrix * instance_matrix * volume_matrix, true);
|
||||
|
||||
indexed_triangle_set upper_its, lower_its;
|
||||
cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
upper_mesh = TriangleMesh(upper_its);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower))
|
||||
lower_mesh = TriangleMesh(lower_its);
|
||||
}
|
||||
|
||||
static void process_connector_cut( ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
|
||||
std::vector<ModelObject*>& dowels)
|
||||
{
|
||||
assert(volume->cut_info.is_connector);
|
||||
volume->cut_info.set_processed();
|
||||
|
||||
const auto volume_matrix = volume->get_matrix();
|
||||
|
||||
// ! Don't apply instance transformation for the conntectors.
|
||||
// This transformation is already there
|
||||
if (volume->cut_info.connector_type != CutConnectorType::Dowel) {
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
|
||||
ModelVolume* vol = nullptr;
|
||||
if (volume->cut_info.connector_type == CutConnectorType::Snap) {
|
||||
TriangleMesh mesh = TriangleMesh(its_make_cylinder(1.0, 1.0, PI / 180.));
|
||||
|
||||
vol = upper->add_volume(std::move(mesh));
|
||||
vol->set_transformation(volume->get_transformation());
|
||||
vol->set_type(ModelVolumeType::NEGATIVE_VOLUME);
|
||||
|
||||
vol->cut_info = volume->cut_info;
|
||||
vol->name = volume->name;
|
||||
}
|
||||
else
|
||||
vol = upper->add_volume(*volume);
|
||||
|
||||
vol->set_transformation(volume_matrix);
|
||||
apply_tolerance(vol);
|
||||
}
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower)) {
|
||||
ModelVolume* vol = lower->add_volume(*volume);
|
||||
vol->set_transformation(volume_matrix);
|
||||
// for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug
|
||||
vol->set_type(ModelVolumeType::MODEL_PART);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (attributes.has(ModelObjectCutAttribute::CreateDowels)) {
|
||||
ModelObject* dowel{ nullptr };
|
||||
// Clone the object to duplicate instances, materials etc.
|
||||
volume->get_object()->clone_for_cut(&dowel);
|
||||
|
||||
// add one more solid part same as connector if this connector is a dowel
|
||||
ModelVolume* vol = dowel->add_volume(*volume);
|
||||
vol->set_type(ModelVolumeType::MODEL_PART);
|
||||
|
||||
// But discard rotation and Z-offset for this volume
|
||||
vol->set_rotation(Vec3d::Zero());
|
||||
vol->set_offset(Z, 0.0);
|
||||
|
||||
dowels.push_back(dowel);
|
||||
}
|
||||
|
||||
// Cut the dowel
|
||||
apply_tolerance(volume);
|
||||
|
||||
// Perform cut
|
||||
TriangleMesh upper_mesh, lower_mesh;
|
||||
process_volume_cut(volume, Transform3d::Identity(), cut_matrix, attributes, upper_mesh, lower_mesh);
|
||||
|
||||
// add small Z offset to better preview
|
||||
upper_mesh.translate((-0.05 * Vec3d::UnitZ()).cast<float>());
|
||||
lower_mesh.translate((0.05 * Vec3d::UnitZ()).cast<float>());
|
||||
|
||||
// Add cut parts to the related objects
|
||||
add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A", volume->type());
|
||||
add_cut_volume(lower_mesh, lower, volume, cut_matrix, "_B", volume->type());
|
||||
}
|
||||
}
|
||||
|
||||
static void process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower)
|
||||
{
|
||||
const auto volume_matrix = instance_matrix * volume->get_matrix();
|
||||
|
||||
// Modifiers are not cut, but we still need to add the instance transformation
|
||||
// to the modifier volume transformation to preserve their shape properly.
|
||||
volume->set_transformation(Transformation(volume_matrix));
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) {
|
||||
upper->add_volume(*volume);
|
||||
return;
|
||||
}
|
||||
|
||||
// Some logic for the negative volumes/connectors. Add only needed modifiers
|
||||
auto bb = volume->mesh().transformed_bounding_box(inverse_cut_matrix * volume_matrix);
|
||||
bool is_crossed_by_cut = bb.min[Z] <= 0 && bb.max[Z] >= 0;
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && (bb.min[Z] >= 0 || is_crossed_by_cut))
|
||||
upper->add_volume(*volume);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && (bb.max[Z] <= 0 || is_crossed_by_cut))
|
||||
lower->add_volume(*volume);
|
||||
}
|
||||
|
||||
static void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower)
|
||||
{
|
||||
// Perform cut
|
||||
TriangleMesh upper_mesh, lower_mesh;
|
||||
process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh);
|
||||
|
||||
// Add required cut parts to the objects
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) {
|
||||
add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A");
|
||||
if (!lower_mesh.empty()) {
|
||||
add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B");
|
||||
upper->volumes.back()->cut_info.is_from_upper = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
add_cut_volume(upper_mesh, upper, volume, cut_matrix);
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty())
|
||||
add_cut_volume(lower_mesh, lower, volume, cut_matrix);
|
||||
}
|
||||
|
||||
static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx,
|
||||
const Transform3d& cut_matrix = Transform3d::Identity(),
|
||||
bool place_on_cut = false, bool flip = false)
|
||||
{
|
||||
// Reset instance transformation except offset and Z-rotation
|
||||
|
||||
for (size_t i = 0; i < object->instances.size(); ++i) {
|
||||
auto& obj_instance = object->instances[i];
|
||||
const double rot_z = obj_instance->get_rotation().z();
|
||||
|
||||
Transformation inst_trafo = Transformation(obj_instance->get_transformation().get_matrix_no_scaling_factor());
|
||||
// add respect to mirroring
|
||||
if (obj_instance->is_left_handed())
|
||||
inst_trafo = inst_trafo * Transformation(scale_transform(Vec3d(-1, 1, 1)));
|
||||
|
||||
obj_instance->set_transformation(inst_trafo);
|
||||
|
||||
Vec3d rotation = Vec3d::Zero();
|
||||
if (!flip && !place_on_cut) {
|
||||
if ( i != src_instance_idx)
|
||||
rotation[Z] = rot_z;
|
||||
}
|
||||
else {
|
||||
Transform3d rotation_matrix = Transform3d::Identity();
|
||||
if (flip)
|
||||
rotation_matrix = rotation_transform(PI * Vec3d::UnitX());
|
||||
|
||||
if (place_on_cut)
|
||||
rotation_matrix = rotation_matrix * Transformation(cut_matrix).get_rotation_matrix().inverse();
|
||||
|
||||
if (i != src_instance_idx)
|
||||
rotation_matrix = rotation_transform(rot_z * Vec3d::UnitZ()) * rotation_matrix;
|
||||
|
||||
rotation = Transformation(rotation_matrix).get_rotation();
|
||||
}
|
||||
|
||||
obj_instance->set_rotation(rotation);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Cut::Cut(const ModelObject* object, int instance, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes/*= ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepAsParts*/)
|
||||
: m_instance(instance), m_cut_matrix(cut_matrix), m_attributes(attributes)
|
||||
{
|
||||
m_model = Model();
|
||||
if (object)
|
||||
m_model.add_object(*object);
|
||||
}
|
||||
|
||||
void Cut::post_process(ModelObject* object, ModelObjectPtrs& cut_object_ptrs, bool keep, bool place_on_cut, bool flip)
|
||||
{
|
||||
if (!object) return;
|
||||
|
||||
if (keep && !object->volumes.empty()) {
|
||||
reset_instance_transformation(object, m_instance, m_cut_matrix, place_on_cut, flip);
|
||||
cut_object_ptrs.push_back(object);
|
||||
}
|
||||
else
|
||||
m_model.objects.push_back(object); // will be deleted in m_model.clear_objects();
|
||||
}
|
||||
|
||||
void Cut::post_process(ModelObject* upper, ModelObject* lower, ModelObjectPtrs& cut_object_ptrs)
|
||||
{
|
||||
post_process(upper, cut_object_ptrs,
|
||||
m_attributes.has(ModelObjectCutAttribute::KeepUpper),
|
||||
m_attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper),
|
||||
m_attributes.has(ModelObjectCutAttribute::FlipUpper));
|
||||
|
||||
post_process(lower, cut_object_ptrs,
|
||||
m_attributes.has(ModelObjectCutAttribute::KeepLower),
|
||||
m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower),
|
||||
m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || m_attributes.has(ModelObjectCutAttribute::FlipLower));
|
||||
}
|
||||
|
||||
|
||||
void Cut::finalize(const ModelObjectPtrs& objects)
|
||||
{
|
||||
//clear model from temporarry objects
|
||||
m_model.clear_objects();
|
||||
|
||||
// add to model result objects
|
||||
m_model.objects = objects;
|
||||
}
|
||||
|
||||
|
||||
const ModelObjectPtrs& Cut::perform_with_plane()
|
||||
{
|
||||
if (!m_attributes.has(ModelObjectCutAttribute::KeepUpper) && !m_attributes.has(ModelObjectCutAttribute::KeepLower)) {
|
||||
m_model.clear_objects();
|
||||
return m_model.objects;
|
||||
}
|
||||
|
||||
ModelObject* mo = m_model.objects.front();
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start";
|
||||
|
||||
// Clone the object to duplicate instances, materials etc.
|
||||
ModelObject* upper{ nullptr };
|
||||
if (m_attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
mo->clone_for_cut(&upper);
|
||||
|
||||
ModelObject* lower{ nullptr };
|
||||
if (m_attributes.has(ModelObjectCutAttribute::KeepLower) && !m_attributes.has(ModelObjectCutAttribute::KeepAsParts))
|
||||
mo->clone_for_cut(&lower);
|
||||
|
||||
std::vector<ModelObject*> dowels;
|
||||
|
||||
// Because transformations are going to be applied to meshes directly,
|
||||
// we reset transformation of all instances and volumes,
|
||||
// except for translation and Z-rotation on instances, which are preserved
|
||||
// in the transformation matrix and not applied to the mesh transform.
|
||||
|
||||
const auto instance_matrix = mo->instances[m_instance]->get_transformation().get_matrix_no_offset();
|
||||
const Transformation cut_transformation = Transformation(m_cut_matrix);
|
||||
const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1. * cut_transformation.get_offset());
|
||||
|
||||
for (ModelVolume* volume : mo->volumes) {
|
||||
volume->reset_extra_facets();
|
||||
|
||||
if (!volume->is_model_part()) {
|
||||
if (volume->cut_info.is_processed)
|
||||
process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, m_attributes, upper, lower);
|
||||
else
|
||||
process_connector_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower, dowels);
|
||||
}
|
||||
else if (!volume->mesh().empty())
|
||||
process_solid_part_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower);
|
||||
}
|
||||
|
||||
// Post-process cut parts
|
||||
|
||||
if (m_attributes.has(ModelObjectCutAttribute::KeepAsParts) && upper->volumes.empty()) {
|
||||
m_model = Model();
|
||||
m_model.objects.push_back(upper);
|
||||
return m_model.objects;
|
||||
}
|
||||
|
||||
ModelObjectPtrs cut_object_ptrs;
|
||||
|
||||
if (m_attributes.has(ModelObjectCutAttribute::KeepAsParts) && !upper->volumes.empty()) {
|
||||
reset_instance_transformation(upper, m_instance, m_cut_matrix);
|
||||
cut_object_ptrs.push_back(upper);
|
||||
}
|
||||
else {
|
||||
// Delete all modifiers which are not intersecting with solid parts bounding box
|
||||
auto delete_extra_modifiers = [this](ModelObject* mo) {
|
||||
if (!mo) return;
|
||||
const BoundingBoxf3 obj_bb = mo->instance_bounding_box(m_instance);
|
||||
const Transform3d inst_matrix = mo->instances[m_instance]->get_transformation().get_matrix();
|
||||
|
||||
for (int i = int(mo->volumes.size()) - 1; i >= 0; --i)
|
||||
if (const ModelVolume* vol = mo->volumes[i];
|
||||
!vol->is_model_part() && !vol->is_cut_connector()) {
|
||||
auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix());
|
||||
if (!obj_bb.intersects(bb))
|
||||
mo->delete_volume(i);
|
||||
}
|
||||
};
|
||||
|
||||
post_process(upper, lower, cut_object_ptrs);
|
||||
delete_extra_modifiers(upper);
|
||||
delete_extra_modifiers(lower);
|
||||
|
||||
if (m_attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) {
|
||||
for (auto dowel : dowels) {
|
||||
reset_instance_transformation(dowel, m_instance);
|
||||
dowel->name += "-Dowel-" + dowel->volumes[0]->name;
|
||||
cut_object_ptrs.push_back(dowel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end";
|
||||
|
||||
finalize(cut_object_ptrs);
|
||||
|
||||
return m_model.objects;
|
||||
}
|
||||
|
||||
static void distribute_modifiers_from_object(ModelObject* from_obj, const int instance_idx, ModelObject* to_obj1, ModelObject* to_obj2)
|
||||
{
|
||||
auto obj1_bb = to_obj1 ? to_obj1->instance_bounding_box(instance_idx) : BoundingBoxf3();
|
||||
auto obj2_bb = to_obj2 ? to_obj2->instance_bounding_box(instance_idx) : BoundingBoxf3();
|
||||
const Transform3d inst_matrix = from_obj->instances[instance_idx]->get_transformation().get_matrix();
|
||||
|
||||
for (ModelVolume* vol : from_obj->volumes)
|
||||
if (!vol->is_model_part()) {
|
||||
auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix());
|
||||
// Don't add modifiers which are not intersecting with solid parts
|
||||
if (obj1_bb.intersects(bb))
|
||||
to_obj1->add_volume(*vol);
|
||||
if (obj2_bb.intersects(bb))
|
||||
to_obj2->add_volume(*vol);
|
||||
}
|
||||
}
|
||||
|
||||
static void merge_solid_parts_inside_object(ModelObjectPtrs& objects)
|
||||
{
|
||||
for (ModelObject* mo : objects) {
|
||||
TriangleMesh mesh;
|
||||
// Merge all SolidPart but not Connectors
|
||||
for (const ModelVolume* mv : mo->volumes) {
|
||||
if (mv->is_model_part() && !mv->is_cut_connector()) {
|
||||
TriangleMesh m = mv->mesh();
|
||||
m.transform(mv->get_matrix());
|
||||
mesh.merge(m);
|
||||
}
|
||||
}
|
||||
if (!mesh.empty()) {
|
||||
ModelVolume* new_volume = mo->add_volume(mesh);
|
||||
new_volume->name = mo->name;
|
||||
// Delete all merged SolidPart but not Connectors
|
||||
for (int i = int(mo->volumes.size()) - 2; i >= 0; --i) {
|
||||
const ModelVolume* mv = mo->volumes[i];
|
||||
if (mv->is_model_part() && !mv->is_cut_connector())
|
||||
mo->delete_volume(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const ModelObjectPtrs& Cut::perform_by_contour(std::vector<Part> parts, int dowels_count)
|
||||
{
|
||||
ModelObject* cut_mo = m_model.objects.front();
|
||||
|
||||
// Clone the object to duplicate instances, materials etc.
|
||||
ModelObject* upper{ nullptr };
|
||||
if (m_attributes.has(ModelObjectCutAttribute::KeepUpper)) cut_mo->clone_for_cut(&upper);
|
||||
ModelObject* lower{ nullptr };
|
||||
if (m_attributes.has(ModelObjectCutAttribute::KeepLower)) cut_mo->clone_for_cut(&lower);
|
||||
|
||||
const size_t cut_parts_cnt = parts.size();
|
||||
bool has_modifiers = false;
|
||||
|
||||
// Distribute SolidParts to the Upper/Lower object
|
||||
for (size_t id = 0; id < cut_parts_cnt; ++id) {
|
||||
if (parts[id].is_modifier)
|
||||
has_modifiers = true; // modifiers will be added later to the related parts
|
||||
else if (ModelObject* obj = (parts[id].selected ? upper : lower))
|
||||
obj->add_volume(*(cut_mo->volumes[id]));
|
||||
}
|
||||
|
||||
if (has_modifiers) {
|
||||
// Distribute Modifiers to the Upper/Lower object
|
||||
distribute_modifiers_from_object(cut_mo, m_instance, upper, lower);
|
||||
}
|
||||
|
||||
ModelObjectPtrs cut_object_ptrs;
|
||||
|
||||
ModelVolumePtrs& volumes = cut_mo->volumes;
|
||||
if (volumes.size() == cut_parts_cnt) {
|
||||
// Means that object is cut without connectors
|
||||
|
||||
// Just add Upper and Lower objects to cut_object_ptrs
|
||||
post_process(upper, lower, cut_object_ptrs);
|
||||
}
|
||||
else if (volumes.size() > cut_parts_cnt) {
|
||||
// Means that object is cut with connectors
|
||||
|
||||
// All volumes are distributed to Upper / Lower object,
|
||||
// So we don’t need them anymore
|
||||
for (size_t id = 0; id < cut_parts_cnt; id++)
|
||||
delete* (volumes.begin() + id);
|
||||
volumes.erase(volumes.begin(), volumes.begin() + cut_parts_cnt);
|
||||
|
||||
// Perform cut just to get connectors
|
||||
Cut cut(cut_mo, m_instance, m_cut_matrix, m_attributes);
|
||||
const ModelObjectPtrs& cut_connectors_obj = cut.perform_with_plane();
|
||||
assert(dowels_count > 0 ? cut_connectors_obj.size() >= 3 : cut_connectors_obj.size() == 2);
|
||||
|
||||
// Connectors from upper object
|
||||
for (const ModelVolume* volume : cut_connectors_obj[0]->volumes)
|
||||
upper->add_volume(*volume, volume->type());
|
||||
|
||||
// Connectors from lower object
|
||||
for (const ModelVolume* volume : cut_connectors_obj[1]->volumes)
|
||||
lower->add_volume(*volume, volume->type());
|
||||
|
||||
// Add Upper and Lower objects to cut_object_ptrs
|
||||
post_process(upper, lower, cut_object_ptrs);
|
||||
|
||||
// Add Dowel-connectors as separate objects to cut_object_ptrs
|
||||
if (cut_connectors_obj.size() >= 3)
|
||||
for (size_t id = 2; id < cut_connectors_obj.size(); id++)
|
||||
cut_object_ptrs.push_back(cut_connectors_obj[id]);
|
||||
}
|
||||
|
||||
// Now merge all model parts together:
|
||||
merge_solid_parts_inside_object(cut_object_ptrs);
|
||||
|
||||
finalize(cut_object_ptrs);
|
||||
|
||||
return m_model.objects;
|
||||
}
|
||||
|
||||
|
||||
const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Transform3d& rotation_m, bool keep_as_parts/* = false*/)
|
||||
{
|
||||
ModelObject* cut_mo = m_model.objects.front();
|
||||
|
||||
// Clone the object to duplicate instances, materials etc.
|
||||
ModelObject* upper{ nullptr };
|
||||
cut_mo->clone_for_cut(&upper);
|
||||
ModelObject* lower{ nullptr };
|
||||
cut_mo->clone_for_cut(&lower);
|
||||
|
||||
const double groove_half_depth = 0.5 * double(groove.depth);
|
||||
|
||||
Model tmp_model_for_cut = Model();
|
||||
|
||||
Model tmp_model = Model();
|
||||
tmp_model.add_object(*cut_mo);
|
||||
ModelObject* tmp_object = tmp_model.objects.front();
|
||||
|
||||
auto add_volumes_from_cut = [](ModelObject* object, const ModelObjectCutAttribute attribute, const Model& tmp_model_for_cut) {
|
||||
const auto& volumes = tmp_model_for_cut.objects.front()->volumes;
|
||||
for (const ModelVolume* volume : volumes)
|
||||
if (volume->is_model_part()) {
|
||||
if ((attribute == ModelObjectCutAttribute::KeepUpper && volume->is_from_upper()) ||
|
||||
(attribute != ModelObjectCutAttribute::KeepUpper && !volume->is_from_upper())) {
|
||||
ModelVolume* new_vol = object->add_volume(*volume);
|
||||
new_vol->reset_from_upper();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto cut = [this, add_volumes_from_cut]
|
||||
(ModelObject* object, const Transform3d& cut_matrix, const ModelObjectCutAttribute add_volumes_attribute, Model& tmp_model_for_cut) {
|
||||
Cut cut(object, m_instance, cut_matrix);
|
||||
|
||||
tmp_model_for_cut = Model();
|
||||
tmp_model_for_cut.add_object(*cut.perform_with_plane().front());
|
||||
assert(!tmp_model_for_cut.objects.empty());
|
||||
|
||||
object->clear_volumes();
|
||||
add_volumes_from_cut(object, add_volumes_attribute, tmp_model_for_cut);
|
||||
reset_instance_transformation(object, m_instance);
|
||||
};
|
||||
|
||||
// cut by upper plane
|
||||
|
||||
const Transform3d cut_matrix_upper = translation_transform(rotation_m * (groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix;
|
||||
{
|
||||
cut(tmp_object, cut_matrix_upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
|
||||
add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
|
||||
}
|
||||
|
||||
// cut by lower plane
|
||||
|
||||
const Transform3d cut_matrix_lower = translation_transform(rotation_m * (-groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix;
|
||||
{
|
||||
cut(tmp_object, cut_matrix_lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
|
||||
add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
|
||||
}
|
||||
|
||||
// cut middle part with 2 angles and add parts to related upper/lower objects
|
||||
|
||||
const double h_side_shift = 0.5 * double(groove.width + groove.depth / tan(groove.flaps_angle));
|
||||
|
||||
// cut by angle1 plane
|
||||
{
|
||||
const Transform3d cut_matrix_angle1 = translation_transform(rotation_m * (-h_side_shift * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle));
|
||||
|
||||
cut(tmp_object, cut_matrix_angle1, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
|
||||
add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
|
||||
}
|
||||
|
||||
// cut by angle2 plane
|
||||
{
|
||||
const Transform3d cut_matrix_angle2 = translation_transform(rotation_m * (h_side_shift * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle));
|
||||
|
||||
cut(tmp_object, cut_matrix_angle2, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
|
||||
add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
|
||||
}
|
||||
|
||||
// apply tolerance to the middle part
|
||||
{
|
||||
const double h_groove_shift_tolerance = groove_half_depth - (double)groove.depth_tolerance;
|
||||
|
||||
const Transform3d cut_matrix_lower_tolerance = translation_transform(rotation_m * (-h_groove_shift_tolerance * Vec3d::UnitZ())) * m_cut_matrix;
|
||||
cut(tmp_object, cut_matrix_lower_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
|
||||
|
||||
const double h_side_shift_tolerance = h_side_shift - 0.5 * double(groove.width_tolerance);
|
||||
|
||||
const Transform3d cut_matrix_angle1_tolerance = translation_transform(rotation_m * (-h_side_shift_tolerance * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle));
|
||||
cut(tmp_object, cut_matrix_angle1_tolerance, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
|
||||
|
||||
const Transform3d cut_matrix_angle2_tolerance = translation_transform(rotation_m * (h_side_shift_tolerance * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle));
|
||||
cut(tmp_object, cut_matrix_angle2_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
|
||||
}
|
||||
|
||||
// this part can be added to the upper object now
|
||||
add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
|
||||
|
||||
ModelObjectPtrs cut_object_ptrs;
|
||||
|
||||
if (keep_as_parts) {
|
||||
// add volumes from lower object to the upper, but mark them as a lower
|
||||
const auto& volumes = lower->volumes;
|
||||
for (const ModelVolume* volume : volumes) {
|
||||
ModelVolume* new_vol = upper->add_volume(*volume);
|
||||
new_vol->cut_info.is_from_upper = false;
|
||||
}
|
||||
|
||||
// add modifiers
|
||||
for (const ModelVolume* volume : cut_mo->volumes)
|
||||
if (!volume->is_model_part())
|
||||
upper->add_volume(*volume);
|
||||
|
||||
cut_object_ptrs.push_back(upper);
|
||||
|
||||
// add lower object to the cut_object_ptrs just to correct delete it from the Model destructor and avoid memory leaks
|
||||
cut_object_ptrs.push_back(lower);
|
||||
}
|
||||
else {
|
||||
// add modifiers if object has any
|
||||
for (const ModelVolume* volume : cut_mo->volumes)
|
||||
if (!volume->is_model_part()) {
|
||||
distribute_modifiers_from_object(cut_mo, m_instance, upper, lower);
|
||||
break;
|
||||
}
|
||||
|
||||
assert(!upper->volumes.empty() && !lower->volumes.empty());
|
||||
|
||||
// Add Upper and Lower parts to cut_object_ptrs
|
||||
|
||||
post_process(upper, lower, cut_object_ptrs);
|
||||
|
||||
// Now merge all model parts together:
|
||||
merge_solid_parts_inside_object(cut_object_ptrs);
|
||||
}
|
||||
|
||||
finalize(cut_object_ptrs);
|
||||
|
||||
return m_model.objects;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
66
src/libslic3r/CutUtils.hpp
Normal file
66
src/libslic3r/CutUtils.hpp
Normal file
@ -0,0 +1,66 @@
|
||||
#ifndef slic3r_CutUtils_hpp_
|
||||
#define slic3r_CutUtils_hpp_
|
||||
|
||||
#include "enum_bitmask.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "Model.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
using ModelObjectPtrs = std::vector<ModelObject*>;
|
||||
|
||||
enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, KeepAsParts, FlipUpper, FlipLower, PlaceOnCutUpper, PlaceOnCutLower, CreateDowels, InvalidateCutInfo };
|
||||
using ModelObjectCutAttributes = enum_bitmask<ModelObjectCutAttribute>;
|
||||
ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute);
|
||||
|
||||
|
||||
class Cut {
|
||||
|
||||
Model m_model;
|
||||
int m_instance;
|
||||
const Transform3d m_cut_matrix;
|
||||
ModelObjectCutAttributes m_attributes;
|
||||
|
||||
void post_process(ModelObject* object, ModelObjectPtrs& objects, bool keep, bool place_on_cut, bool flip);
|
||||
void post_process(ModelObject* upper_object, ModelObject* lower_object, ModelObjectPtrs& objects);
|
||||
void finalize(const ModelObjectPtrs& objects);
|
||||
|
||||
public:
|
||||
|
||||
Cut(const ModelObject* object, int instance, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes = ModelObjectCutAttribute::KeepUpper |
|
||||
ModelObjectCutAttribute::KeepLower |
|
||||
ModelObjectCutAttribute::KeepAsParts );
|
||||
~Cut() { m_model.clear_objects(); }
|
||||
|
||||
struct Groove
|
||||
{
|
||||
float depth{ 0.f };
|
||||
float width{ 0.f };
|
||||
float flaps_angle{ 0.f };
|
||||
float angle{ 0.f };
|
||||
float depth_init{ 0.f };
|
||||
float width_init{ 0.f };
|
||||
float flaps_angle_init{ 0.f };
|
||||
float angle_init{ 0.f };
|
||||
float depth_tolerance{ 0.1f };
|
||||
float width_tolerance{ 0.1f };
|
||||
};
|
||||
|
||||
struct Part
|
||||
{
|
||||
bool selected;
|
||||
bool is_modifier;
|
||||
};
|
||||
|
||||
const ModelObjectPtrs& perform_with_plane();
|
||||
const ModelObjectPtrs& perform_by_contour(std::vector<Part> parts, int dowels_count);
|
||||
const ModelObjectPtrs& perform_with_groove(const Groove& groove, const Transform3d& rotation_m, bool keep_as_parts = false);
|
||||
|
||||
}; // namespace Cut
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_CutUtils_hpp_ */
|
@ -146,67 +146,68 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
|
||||
std::vector<float> angles_for_curvature(points.size());
|
||||
std::vector<float> distances_for_curvature(points.size());
|
||||
|
||||
for (int point_idx = 0; point_idx < int(points.size()); ++point_idx) {
|
||||
for (size_t point_idx = 0; point_idx < points.size(); ++point_idx) {
|
||||
ExtendedPoint &a = points[point_idx];
|
||||
ExtendedPoint &prev = points[point_idx > 0 ? point_idx - 1 : point_idx];
|
||||
size_t prev = prev_idx_modulo(point_idx, points.size());
|
||||
size_t next = next_idx_modulo(point_idx, points.size());
|
||||
|
||||
int prev_point_idx = point_idx;
|
||||
while (prev_point_idx > 0) {
|
||||
prev_point_idx--;
|
||||
if ((a.position - points[prev_point_idx].position).squaredNorm() > EPSILON) {
|
||||
break;
|
||||
}
|
||||
int iter_limit = points.size();
|
||||
while ((a.position - points[prev].position).squaredNorm() < 1 && iter_limit > 0) {
|
||||
prev = prev_idx_modulo(prev, points.size());
|
||||
iter_limit--;
|
||||
}
|
||||
|
||||
int next_point_index = point_idx;
|
||||
while (next_point_index < int(points.size()) - 1) {
|
||||
next_point_index++;
|
||||
if ((a.position - points[next_point_index].position).squaredNorm() > EPSILON) {
|
||||
break;
|
||||
}
|
||||
while ((a.position - points[next].position).squaredNorm() < 1 && iter_limit > 0) {
|
||||
next = next_idx_modulo(next, points.size());
|
||||
iter_limit--;
|
||||
}
|
||||
|
||||
distances_for_curvature[point_idx] = (prev.position - a.position).norm();
|
||||
if (prev_point_idx != point_idx && next_point_index != point_idx) {
|
||||
float alfa = angle(a.position - points[prev_point_idx].position, points[next_point_index].position - a.position);
|
||||
angles_for_curvature[point_idx] = alfa;
|
||||
} // else keep zero
|
||||
distances_for_curvature[point_idx] = (points[prev].position - a.position).norm();
|
||||
float alfa = angle(a.position - points[prev].position, points[next].position - a.position);
|
||||
angles_for_curvature[point_idx] = alfa;
|
||||
}
|
||||
|
||||
for (float window_size : {3.0f, 9.0f, 16.0f}) {
|
||||
size_t tail_point = 0;
|
||||
float tail_window_acc = 0;
|
||||
float tail_angle_acc = 0;
|
||||
if (std::accumulate(distances_for_curvature.begin(), distances_for_curvature.end(), 0) > EPSILON)
|
||||
for (float window_size : {3.0f, 9.0f, 16.0f}) {
|
||||
size_t tail_point = 0;
|
||||
float tail_window_acc = 0;
|
||||
float tail_angle_acc = 0;
|
||||
|
||||
size_t head_point = 0;
|
||||
float head_window_acc = 0;
|
||||
float head_angle_acc = 0;
|
||||
size_t head_point = 0;
|
||||
float head_window_acc = 0;
|
||||
float head_angle_acc = 0;
|
||||
|
||||
for (int point_idx = 0; point_idx < int(points.size()); ++point_idx) {
|
||||
if (point_idx > 0) {
|
||||
tail_window_acc += distances_for_curvature[point_idx - 1];
|
||||
tail_angle_acc += angles_for_curvature[point_idx - 1];
|
||||
head_window_acc -= distances_for_curvature[point_idx - 1];
|
||||
head_angle_acc -= angles_for_curvature[point_idx - 1];
|
||||
}
|
||||
while (tail_window_acc > window_size * 0.5 && int(tail_point) < point_idx) {
|
||||
tail_window_acc -= distances_for_curvature[tail_point];
|
||||
tail_angle_acc -= angles_for_curvature[tail_point];
|
||||
tail_point++;
|
||||
}
|
||||
for (size_t point_idx = 0; point_idx < points.size(); ++point_idx) {
|
||||
if (point_idx == 0) {
|
||||
while (tail_window_acc < window_size * 0.5) {
|
||||
tail_window_acc += distances_for_curvature[tail_point];
|
||||
tail_angle_acc += angles_for_curvature[tail_point];
|
||||
tail_point = prev_idx_modulo(tail_point, points.size());
|
||||
}
|
||||
}
|
||||
while (tail_window_acc - distances_for_curvature[next_idx_modulo(tail_point, points.size())] > window_size * 0.5) {
|
||||
tail_point = next_idx_modulo(tail_point, points.size());
|
||||
tail_window_acc -= distances_for_curvature[tail_point];
|
||||
tail_angle_acc -= angles_for_curvature[tail_point];
|
||||
}
|
||||
|
||||
while (head_window_acc < window_size * 0.5 && int(head_point) < int(points.size()) - 1) {
|
||||
head_window_acc += distances_for_curvature[head_point];
|
||||
head_angle_acc += angles_for_curvature[head_point];
|
||||
head_point++;
|
||||
}
|
||||
while (head_window_acc < window_size * 0.5) {
|
||||
head_point = next_idx_modulo(head_point, points.size());
|
||||
head_window_acc += distances_for_curvature[head_point];
|
||||
head_angle_acc += angles_for_curvature[head_point];
|
||||
}
|
||||
|
||||
float curvature = (tail_angle_acc + head_angle_acc) / (tail_window_acc + head_window_acc);
|
||||
if (std::abs(curvature) > std::abs(points[point_idx].curvature)) {
|
||||
points[point_idx].curvature = curvature;
|
||||
float curvature = (tail_angle_acc + head_angle_acc) / window_size;
|
||||
if (std::abs(curvature) > std::abs(points[point_idx].curvature)) {
|
||||
points[point_idx].curvature = curvature;
|
||||
}
|
||||
|
||||
tail_window_acc += distances_for_curvature[point_idx];
|
||||
tail_angle_acc += angles_for_curvature[point_idx];
|
||||
head_window_acc -= distances_for_curvature[point_idx];
|
||||
head_angle_acc -= angles_for_curvature[point_idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
@ -1280,64 +1280,6 @@ bool ModelObject::has_connectors() const
|
||||
return false;
|
||||
}
|
||||
|
||||
indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes connector_attributes)
|
||||
{
|
||||
indexed_triangle_set connector_mesh;
|
||||
|
||||
int sectorCount {1};
|
||||
switch (CutConnectorShape(connector_attributes.shape)) {
|
||||
case CutConnectorShape::Triangle:
|
||||
sectorCount = 3;
|
||||
break;
|
||||
case CutConnectorShape::Square:
|
||||
sectorCount = 4;
|
||||
break;
|
||||
case CutConnectorShape::Circle:
|
||||
sectorCount = 360;
|
||||
break;
|
||||
case CutConnectorShape::Hexagon:
|
||||
sectorCount = 6;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (connector_attributes.style == CutConnectorStyle::Prism)
|
||||
connector_mesh = its_make_cylinder(1.0, 1.0, (2 * PI / sectorCount));
|
||||
else if (connector_attributes.type == CutConnectorType::Plug)
|
||||
connector_mesh = its_make_frustum(1.0, 1.0, (2 * PI / sectorCount));
|
||||
else
|
||||
connector_mesh = its_make_frustum_dowel(1.0, 1.0, sectorCount);
|
||||
|
||||
return connector_mesh;
|
||||
}
|
||||
|
||||
void ModelObject::apply_cut_connectors(const std::string& new_name)
|
||||
{
|
||||
if (cut_connectors.empty())
|
||||
return;
|
||||
|
||||
using namespace Geometry;
|
||||
|
||||
size_t connector_id = cut_id.connectors_cnt();
|
||||
for (const CutConnector& connector : cut_connectors) {
|
||||
TriangleMesh mesh = TriangleMesh(get_connector_mesh(connector.attribs));
|
||||
// Mesh will be centered when loading.
|
||||
ModelVolume* new_volume = add_volume(std::move(mesh), ModelVolumeType::NEGATIVE_VOLUME);
|
||||
|
||||
// Transform the new modifier to be aligned inside the instance
|
||||
new_volume->set_transformation(translation_transform(connector.pos) * connector.rotation_m *
|
||||
scale_transform(Vec3f(connector.radius, connector.radius, connector.height).cast<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();
|
||||
@ -1390,297 +1332,6 @@ void ModelVolume::reset_extra_facets()
|
||||
this->mmu_segmentation_facets.reset();
|
||||
}
|
||||
|
||||
void ModelVolume::apply_tolerance()
|
||||
{
|
||||
assert(cut_info.is_connector);
|
||||
if (!cut_info.is_processed)
|
||||
return;
|
||||
|
||||
Vec3d sf = get_scaling_factor();
|
||||
|
||||
// make a "hole" wider
|
||||
sf[X] += double(cut_info.radius_tolerance);
|
||||
sf[Y] += double(cut_info.radius_tolerance);
|
||||
|
||||
// make a "hole" dipper
|
||||
sf[Z] += double(cut_info.height_tolerance);
|
||||
|
||||
set_scaling_factor(sf);
|
||||
|
||||
// correct offset in respect to the new depth
|
||||
Vec3d rot_norm = Geometry::rotation_transform(get_rotation()) * Vec3d::UnitZ();
|
||||
if (rot_norm.norm() != 0.0)
|
||||
rot_norm.normalize();
|
||||
|
||||
double z_offset = 0.5 * static_cast<double>(cut_info.height_tolerance);
|
||||
if (cut_info.connector_type == CutConnectorType::Plug)
|
||||
z_offset -= 0.05; // add small Z offset to better preview
|
||||
|
||||
set_offset(get_offset() + rot_norm * z_offset);
|
||||
}
|
||||
|
||||
static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelVolume* src_volume, const Transform3d& cut_matrix, const std::string& suffix = {}, ModelVolumeType type = ModelVolumeType::MODEL_PART)
|
||||
{
|
||||
if (mesh.empty())
|
||||
return;
|
||||
|
||||
mesh.transform(cut_matrix);
|
||||
ModelVolume* vol = object->add_volume(mesh);
|
||||
vol->set_type(type);
|
||||
|
||||
vol->name = src_volume->name + suffix;
|
||||
// Don't copy the config's ID.
|
||||
vol->config.assign_config(src_volume->config);
|
||||
assert(vol->config.id().valid());
|
||||
assert(vol->config.id() != src_volume->config.id());
|
||||
vol->set_material(src_volume->material_id(), *src_volume->material());
|
||||
vol->cut_info = src_volume->cut_info;
|
||||
}
|
||||
|
||||
void ModelObject::process_connector_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
|
||||
std::vector<ModelObject*>& dowels)
|
||||
{
|
||||
assert(volume->cut_info.is_connector);
|
||||
volume->cut_info.set_processed();
|
||||
|
||||
const auto volume_matrix = volume->get_matrix();
|
||||
|
||||
// ! Don't apply instance transformation for the conntectors.
|
||||
// This transformation is already there
|
||||
if (volume->cut_info.connector_type != CutConnectorType::Dowel) {
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
|
||||
ModelVolume* vol = upper->add_volume(*volume);
|
||||
vol->set_transformation(volume_matrix);
|
||||
vol->apply_tolerance();
|
||||
}
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower)) {
|
||||
ModelVolume* vol = lower->add_volume(*volume);
|
||||
vol->set_transformation(volume_matrix);
|
||||
// for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug
|
||||
vol->set_type(ModelVolumeType::MODEL_PART);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (attributes.has(ModelObjectCutAttribute::CreateDowels)) {
|
||||
ModelObject* dowel{ nullptr };
|
||||
// Clone the object to duplicate instances, materials etc.
|
||||
clone_for_cut(&dowel);
|
||||
|
||||
// add one more solid part same as connector if this connector is a dowel
|
||||
ModelVolume* vol = dowel->add_volume(*volume);
|
||||
vol->set_type(ModelVolumeType::MODEL_PART);
|
||||
|
||||
// But discard rotation and Z-offset for this volume
|
||||
vol->set_rotation(Vec3d::Zero());
|
||||
vol->set_offset(Z, 0.0);
|
||||
|
||||
dowels.push_back(dowel);
|
||||
}
|
||||
|
||||
// Cut the dowel
|
||||
volume->apply_tolerance();
|
||||
|
||||
// Perform cut
|
||||
TriangleMesh upper_mesh, lower_mesh;
|
||||
process_volume_cut(volume, Transform3d::Identity(), cut_matrix, attributes, upper_mesh, lower_mesh);
|
||||
|
||||
// add small Z offset to better preview
|
||||
upper_mesh.translate((-0.05 * Vec3d::UnitZ()).cast<float>());
|
||||
lower_mesh.translate((0.05 * Vec3d::UnitZ()).cast<float>());
|
||||
|
||||
// Add cut parts to the related objects
|
||||
add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A", volume->type());
|
||||
add_cut_volume(lower_mesh, lower, volume, cut_matrix, "_B", volume->type());
|
||||
}
|
||||
}
|
||||
|
||||
void ModelObject::process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower)
|
||||
{
|
||||
const auto volume_matrix = instance_matrix * volume->get_matrix();
|
||||
|
||||
// Modifiers are not cut, but we still need to add the instance transformation
|
||||
// to the modifier volume transformation to preserve their shape properly.
|
||||
volume->set_transformation(Geometry::Transformation(volume_matrix));
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) {
|
||||
upper->add_volume(*volume);
|
||||
return;
|
||||
}
|
||||
|
||||
// Some logic for the negative volumes/connectors. Add only needed modifiers
|
||||
auto bb = volume->mesh().transformed_bounding_box(inverse_cut_matrix * volume_matrix);
|
||||
bool is_crossed_by_cut = bb.min[Z] <= 0 && bb.max[Z] >= 0;
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && (bb.min[Z] >= 0 || is_crossed_by_cut))
|
||||
upper->add_volume(*volume);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && (bb.max[Z] <= 0 || is_crossed_by_cut))
|
||||
lower->add_volume(*volume);
|
||||
}
|
||||
|
||||
void ModelObject::process_volume_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh)
|
||||
{
|
||||
const auto volume_matrix = volume->get_matrix();
|
||||
|
||||
using namespace Geometry;
|
||||
|
||||
const Transformation cut_transformation = Transformation(cut_matrix);
|
||||
const Transform3d invert_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1 * cut_transformation.get_offset());
|
||||
|
||||
// Transform the mesh by the combined transformation matrix.
|
||||
// Flip the triangles in case the composite transformation is left handed.
|
||||
TriangleMesh mesh(volume->mesh());
|
||||
mesh.transform(invert_cut_matrix * instance_matrix * volume_matrix, true);
|
||||
|
||||
indexed_triangle_set upper_its, lower_its;
|
||||
cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
upper_mesh = TriangleMesh(upper_its);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower))
|
||||
lower_mesh = TriangleMesh(lower_its);
|
||||
}
|
||||
void ModelObject::process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower)
|
||||
{
|
||||
// Perform cut
|
||||
TriangleMesh upper_mesh, lower_mesh;
|
||||
process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh);
|
||||
|
||||
// Add required cut parts to the objects
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) {
|
||||
add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A");
|
||||
add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B");
|
||||
return;
|
||||
}
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
add_cut_volume(upper_mesh, upper, volume, cut_matrix);
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty())
|
||||
add_cut_volume(lower_mesh, lower, volume, cut_matrix);
|
||||
}
|
||||
|
||||
void ModelObject::reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix,
|
||||
bool place_on_cut/* = false*/, bool flip/* = false*/)
|
||||
{
|
||||
using namespace Geometry;
|
||||
|
||||
// Reset instance transformation except offset and Z-rotation
|
||||
|
||||
for (size_t i = 0; i < object->instances.size(); ++i) {
|
||||
auto& obj_instance = object->instances[i];
|
||||
const double rot_z = obj_instance->get_rotation().z();
|
||||
|
||||
Transformation inst_trafo = Transformation(obj_instance->get_transformation().get_matrix_no_scaling_factor());
|
||||
if (obj_instance->is_left_handed())
|
||||
inst_trafo = inst_trafo * Transformation(scale_transform(Vec3d(-1, 1, 1)));
|
||||
|
||||
obj_instance->set_transformation(inst_trafo);
|
||||
|
||||
Vec3d rotation = Vec3d::Zero();
|
||||
if (!flip && !place_on_cut) {
|
||||
if ( i != src_instance_idx)
|
||||
rotation[Z] = rot_z;
|
||||
}
|
||||
else {
|
||||
Transform3d rotation_matrix = Transform3d::Identity();
|
||||
if (flip)
|
||||
rotation_matrix = rotation_transform(PI * Vec3d::UnitX());
|
||||
|
||||
if (place_on_cut)
|
||||
rotation_matrix = rotation_matrix * Transformation(cut_matrix).get_rotation_matrix().inverse();
|
||||
|
||||
if (i != src_instance_idx)
|
||||
rotation_matrix = rotation_transform(rot_z * Vec3d::UnitZ()) * rotation_matrix;
|
||||
|
||||
rotation = Transformation(rotation_matrix).get_rotation();
|
||||
}
|
||||
|
||||
obj_instance->set_rotation(rotation);
|
||||
}
|
||||
}
|
||||
|
||||
ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes)
|
||||
{
|
||||
if (!attributes.has(ModelObjectCutAttribute::KeepUpper) && !attributes.has(ModelObjectCutAttribute::KeepLower))
|
||||
return {};
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start";
|
||||
|
||||
// Clone the object to duplicate instances, materials etc.
|
||||
ModelObject* upper{ nullptr };
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
clone_for_cut(&upper);
|
||||
|
||||
ModelObject* lower{ nullptr };
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !attributes.has(ModelObjectCutAttribute::KeepAsParts))
|
||||
clone_for_cut(&lower);
|
||||
|
||||
std::vector<ModelObject*> dowels;
|
||||
|
||||
using namespace Geometry;
|
||||
|
||||
// Because transformations are going to be applied to meshes directly,
|
||||
// we reset transformation of all instances and volumes,
|
||||
// except for translation and Z-rotation on instances, which are preserved
|
||||
// in the transformation matrix and not applied to the mesh transform.
|
||||
|
||||
// const auto instance_matrix = instances[instance]->get_matrix(true);
|
||||
const auto instance_matrix = instances[instance]->get_transformation().get_matrix_no_offset();
|
||||
const Transformation cut_transformation = Transformation(cut_matrix);
|
||||
const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1. * cut_transformation.get_offset());
|
||||
|
||||
for (ModelVolume* volume : volumes) {
|
||||
volume->reset_extra_facets();
|
||||
|
||||
if (!volume->is_model_part()) {
|
||||
if (volume->cut_info.is_processed)
|
||||
process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, attributes, upper, lower);
|
||||
else
|
||||
process_connector_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, dowels);
|
||||
}
|
||||
else if (!volume->mesh().empty())
|
||||
process_solid_part_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower);
|
||||
}
|
||||
|
||||
// Post-process cut parts
|
||||
|
||||
ModelObjectPtrs res;
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepAsParts) && !upper->volumes.empty()) {
|
||||
reset_instance_transformation(upper, instance, cut_matrix);
|
||||
res.push_back(upper);
|
||||
}
|
||||
else {
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper->volumes.empty()) {
|
||||
reset_instance_transformation(upper, instance, cut_matrix,
|
||||
attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper),
|
||||
attributes.has(ModelObjectCutAttribute::FlipUpper));
|
||||
res.push_back(upper);
|
||||
}
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower->volumes.empty()) {
|
||||
reset_instance_transformation(lower, instance, cut_matrix,
|
||||
attributes.has(ModelObjectCutAttribute::PlaceOnCutLower),
|
||||
attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || attributes.has(ModelObjectCutAttribute::FlipLower));
|
||||
res.push_back(lower);
|
||||
}
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) {
|
||||
for (auto dowel : dowels) {
|
||||
reset_instance_transformation(dowel, instance, Transform3d::Identity());
|
||||
dowel->name += "-Dowel-" + dowel->volumes[0]->name;
|
||||
res.push_back(dowel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end";
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare TriangleMeshes by Bounding boxes (mainly for sort)
|
||||
|
@ -224,6 +224,7 @@ private:
|
||||
enum class CutConnectorType : int {
|
||||
Plug
|
||||
, Dowel
|
||||
, Snap
|
||||
, Undef
|
||||
};
|
||||
|
||||
@ -316,10 +317,6 @@ enum class ModelVolumeType : int {
|
||||
SUPPORT_ENFORCER,
|
||||
};
|
||||
|
||||
enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, KeepAsParts, FlipUpper, FlipLower, PlaceOnCutUpper, PlaceOnCutLower, CreateDowels, InvalidateCutInfo };
|
||||
using ModelObjectCutAttributes = enum_bitmask<ModelObjectCutAttribute>;
|
||||
ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute);
|
||||
|
||||
// A printable object, possibly having multiple print volumes (each with its own set of parameters and materials),
|
||||
// and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials.
|
||||
// Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed,
|
||||
@ -461,29 +458,12 @@ public:
|
||||
size_t materials_count() const;
|
||||
size_t facets_count() const;
|
||||
size_t parts_count() const;
|
||||
static indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes);
|
||||
void apply_cut_connectors(const std::string& name);
|
||||
// invalidate cut state for this object and its connectors/volumes
|
||||
void invalidate_cut();
|
||||
// delete volumes which are marked as connector for this object
|
||||
void delete_connectors();
|
||||
void clone_for_cut(ModelObject **obj);
|
||||
|
||||
private:
|
||||
void process_connector_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
|
||||
std::vector<ModelObject*>& dowels);
|
||||
void process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower);
|
||||
void process_volume_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh);
|
||||
void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower);
|
||||
public:
|
||||
static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix,
|
||||
bool place_on_cut = false, bool flip = false);
|
||||
|
||||
ModelObjectPtrs cut(size_t instance, const Transform3d&cut_matrix, ModelObjectCutAttributes attributes);
|
||||
void split(ModelObjectPtrs*new_objects);
|
||||
void merge();
|
||||
// Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees,
|
||||
@ -777,6 +757,7 @@ public:
|
||||
// It contains information about connetors
|
||||
struct CutInfo
|
||||
{
|
||||
bool is_from_upper{ true };
|
||||
bool is_connector{ false };
|
||||
bool is_processed{ true };
|
||||
CutConnectorType connector_type{ CutConnectorType::Plug };
|
||||
@ -794,6 +775,7 @@ public:
|
||||
|
||||
void set_processed() { is_processed = true; }
|
||||
void invalidate() { is_connector = false; }
|
||||
void reset_from_upper() { is_from_upper = true; }
|
||||
|
||||
template<class Archive> inline void serialize(Archive& ar) {
|
||||
ar(is_connector, is_processed, connector_type, radius_tolerance, height_tolerance);
|
||||
@ -801,6 +783,9 @@ public:
|
||||
};
|
||||
CutInfo cut_info;
|
||||
|
||||
bool is_from_upper() const { return cut_info.is_from_upper; }
|
||||
void reset_from_upper() { cut_info.reset_from_upper(); }
|
||||
|
||||
bool is_cut_connector() const { return cut_info.is_processed && cut_info.is_connector; }
|
||||
void invalidate_cut_info() { cut_info.invalidate(); }
|
||||
|
||||
@ -846,7 +831,6 @@ public:
|
||||
bool is_the_only_one_part() const; // behave like an object
|
||||
t_model_material_id material_id() const { return m_material_id; }
|
||||
void reset_extra_facets();
|
||||
void apply_tolerance();
|
||||
void set_material_id(t_model_material_id material_id);
|
||||
ModelMaterial* material() const;
|
||||
void set_material(t_model_material_id material_id, const ModelMaterial &material);
|
||||
|
@ -678,7 +678,9 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
|
||||
void PresetBundle::export_selections(AppConfig &config)
|
||||
{
|
||||
assert(this->printers.get_edited_preset().printer_technology() != ptFFF || extruders_filaments.size() >= 1);
|
||||
assert(this->printers.get_edited_preset().printer_technology() != ptFFF || extruders_filaments.size() > 1 || filaments.get_selected_preset().alias == extruders_filaments.front().get_selected_preset()->alias);
|
||||
// #ysFIXME_delete_after_test !All filament selections are always saved in extruder_filaments (for MM and SM printers),
|
||||
// so there is no need to control a correspondence between filaments and extruders_filaments
|
||||
//assert(this->printers.get_edited_preset().printer_technology() != ptFFF || extruders_filaments.size() > 1 || filaments.get_selected_preset().alias == extruders_filaments.front().get_selected_preset()->alias);
|
||||
config.clear_section("presets");
|
||||
config.set("presets", "print", prints.get_selected_preset_name());
|
||||
config.set("presets", "filament", extruders_filaments.front().get_selected_preset_name());
|
||||
|
@ -1539,21 +1539,33 @@ void PrintObject::discover_vertical_shells()
|
||||
// Finally expand the infill a bit to remove tiny gaps between solid infill and the other regions.
|
||||
narrow_sparse_infill_region_radius - tiny_overlap_radius, ClipperLib::jtSquare);
|
||||
|
||||
Polygons object_volume;
|
||||
Polygons internal_volume;
|
||||
{
|
||||
Polygons shrinked_bottom_slice = idx_layer > 0 ? to_polygons(m_layers[idx_layer - 1]->lslices) : Polygons{};
|
||||
Polygons shrinked_upper_slice = (idx_layer + 1) < m_layers.size() ?
|
||||
to_polygons(m_layers[idx_layer + 1]->lslices) :
|
||||
Polygons{};
|
||||
internal_volume = intersection(shrinked_bottom_slice, shrinked_upper_slice);
|
||||
object_volume = intersection(shrinked_bottom_slice, shrinked_upper_slice);
|
||||
internal_volume = closing(polygonsInternal, SCALED_EPSILON);
|
||||
}
|
||||
|
||||
// The opening operation may cause scattered tiny drops on the smooth parts of the model, filter them out
|
||||
// The regularization operation may cause scattered tiny drops on the smooth parts of the model, filter them out
|
||||
// If the region checks both following conditions, it is removed:
|
||||
// 1. the area is very small,
|
||||
// OR the area is quite small and it is fully wrapped in model (not visible)
|
||||
// the in-model condition is there due to small sloping surfaces, e.g. top of the hull of the benchy
|
||||
// 2. the area does not fully cover an internal polygon
|
||||
// This is there mainly for a very thin parts, where the solid layers would be missing if the part area is quite small
|
||||
regularized_shell.erase(std::remove_if(regularized_shell.begin(), regularized_shell.end(),
|
||||
[&min_perimeter_infill_spacing, &internal_volume](const ExPolygon &p) {
|
||||
return p.area() < min_perimeter_infill_spacing * scaled(1.5) ||
|
||||
(p.area() < min_perimeter_infill_spacing * scaled(8.0) &&
|
||||
diff(to_polygons(p), internal_volume).empty());
|
||||
[&internal_volume, &min_perimeter_infill_spacing,
|
||||
&object_volume](const ExPolygon &p) {
|
||||
return (p.area() < min_perimeter_infill_spacing * scaled(1.5) ||
|
||||
(p.area() < min_perimeter_infill_spacing * scaled(8.0) &&
|
||||
diff(to_polygons(p), object_volume).empty())) &&
|
||||
diff(internal_volume,
|
||||
expand(to_polygons(p), min_perimeter_infill_spacing))
|
||||
.size() >= internal_volume.size();
|
||||
}),
|
||||
regularized_shell.end());
|
||||
}
|
||||
|
@ -1262,6 +1262,127 @@ indexed_triangle_set its_make_frustum_dowel(double radius, double h, int sectorC
|
||||
return mesh;
|
||||
}
|
||||
|
||||
indexed_triangle_set its_make_snap(double r, double h, float space_proportion, float bulge_proportion)
|
||||
{
|
||||
const float radius = (float)r;
|
||||
const float height = (float)h;
|
||||
const size_t sectors_cnt = 10; //(float)fa;
|
||||
const float halfPI = 0.5f * (float)PI;
|
||||
|
||||
const float space_len = space_proportion * radius;
|
||||
|
||||
const float b_len = radius;
|
||||
const float m_len = (1 + bulge_proportion) * radius;
|
||||
const float t_len = 0.5f * radius;
|
||||
|
||||
const float b_height = 0.f;
|
||||
const float m_height = 0.5f * height;
|
||||
const float t_height = height;
|
||||
|
||||
const float b_angle = acos(space_len/b_len);
|
||||
const float t_angle = acos(space_len/t_len);
|
||||
|
||||
const float b_angle_step = b_angle / (float)sectors_cnt;
|
||||
const float t_angle_step = t_angle / (float)sectors_cnt;
|
||||
|
||||
const Vec2f b_vec = Eigen::Vector2f(0, b_len);
|
||||
const Vec2f t_vec = Eigen::Vector2f(0, t_len);
|
||||
|
||||
|
||||
auto add_side_vertices = [b_vec, t_vec, b_height, m_height, t_height](std::vector<stl_vertex>& vertices, float b_angle, float t_angle, const Vec2f& m_vec) {
|
||||
Vec2f b_pt = Eigen::Rotation2Df(b_angle) * b_vec;
|
||||
Vec2f m_pt = Eigen::Rotation2Df(b_angle) * m_vec;
|
||||
Vec2f t_pt = Eigen::Rotation2Df(t_angle) * t_vec;
|
||||
|
||||
vertices.emplace_back(Vec3f(b_pt(0), b_pt(1), b_height));
|
||||
vertices.emplace_back(Vec3f(m_pt(0), m_pt(1), m_height));
|
||||
vertices.emplace_back(Vec3f(t_pt(0), t_pt(1), t_height));
|
||||
};
|
||||
|
||||
auto add_side_facets = [](std::vector<stl_triangle_vertex_indices>& facets, int vertices_cnt, int frst_id, int scnd_id) {
|
||||
int id = vertices_cnt - 1;
|
||||
|
||||
facets.emplace_back(frst_id, id - 2, id - 5);
|
||||
|
||||
facets.emplace_back(id - 2, id - 1, id - 5);
|
||||
facets.emplace_back(id - 1, id - 4, id - 5);
|
||||
facets.emplace_back(id - 4, id - 1, id);
|
||||
facets.emplace_back(id, id - 3, id - 4);
|
||||
|
||||
facets.emplace_back(id, scnd_id, id - 3);
|
||||
};
|
||||
|
||||
const float f = (b_len - m_len) / m_len; // Flattening
|
||||
|
||||
auto get_m_len = [b_len, f](float angle) {
|
||||
const float rad_sqr = b_len * b_len;
|
||||
const float sin_sqr = sin(angle) * sin(angle);
|
||||
const float f_sqr = (1-f)*(1-f);
|
||||
return sqrtf(rad_sqr / (1 + (1 / f_sqr - 1) * sin_sqr));
|
||||
};
|
||||
|
||||
auto add_sub_mesh = [add_side_vertices, add_side_facets, get_m_len,
|
||||
b_height, t_height, b_angle, t_angle, b_angle_step, t_angle_step]
|
||||
(indexed_triangle_set& mesh, float center_x, float angle_rotation, int frst_vertex_id) {
|
||||
auto& vertices = mesh.vertices;
|
||||
auto& facets = mesh.indices;
|
||||
|
||||
// 2 special vertices, top and bottom center, rest are relative to this
|
||||
vertices.emplace_back(Vec3f(center_x, 0.f, b_height));
|
||||
vertices.emplace_back(Vec3f(center_x, 0.f, t_height));
|
||||
|
||||
float b_angle_start = angle_rotation - b_angle;
|
||||
float t_angle_start = angle_rotation - t_angle;
|
||||
const float b_angle_stop = angle_rotation + b_angle;
|
||||
|
||||
const int frst_id = frst_vertex_id;
|
||||
const int scnd_id = frst_id + 1;
|
||||
|
||||
// add first side vertices and internal facets
|
||||
{
|
||||
const Vec2f m_vec = Eigen::Vector2f(0, get_m_len(b_angle_start));
|
||||
add_side_vertices(vertices, b_angle_start, t_angle_start, m_vec);
|
||||
|
||||
int id = (int)vertices.size() - 1;
|
||||
|
||||
facets.emplace_back(frst_id, id - 2, id - 1);
|
||||
facets.emplace_back(frst_id, id - 1, id);
|
||||
facets.emplace_back(frst_id, id, scnd_id);
|
||||
}
|
||||
|
||||
// add d side vertices and facets
|
||||
while (!is_approx(b_angle_start, b_angle_stop)) {
|
||||
b_angle_start += b_angle_step;
|
||||
t_angle_start += t_angle_step;
|
||||
|
||||
const Vec2f m_vec = Eigen::Vector2f(0, get_m_len(b_angle_start));
|
||||
add_side_vertices(vertices, b_angle_start, t_angle_start, m_vec);
|
||||
|
||||
add_side_facets(facets, (int)vertices.size(), frst_id, scnd_id);
|
||||
}
|
||||
|
||||
// add last internal facets to close the mesh
|
||||
{
|
||||
int id = (int)vertices.size() - 1;
|
||||
|
||||
facets.emplace_back(frst_id, scnd_id, id);
|
||||
facets.emplace_back(frst_id, id, id - 1);
|
||||
facets.emplace_back(frst_id, id - 1, id - 2);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
indexed_triangle_set mesh;
|
||||
|
||||
mesh.vertices.reserve(2 * (3 * (2 * sectors_cnt + 1) + 2));
|
||||
mesh.indices.reserve(2 * (6 * 2 * sectors_cnt + 6));
|
||||
|
||||
add_sub_mesh(mesh, -space_len, halfPI , 0);
|
||||
add_sub_mesh(mesh, space_len, 3 * halfPI, (int)mesh.vertices.size());
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
indexed_triangle_set its_convex_hull(const std::vector<Vec3f> &pts)
|
||||
{
|
||||
std::vector<Vec3f> dst_vertices;
|
||||
|
@ -321,6 +321,7 @@ indexed_triangle_set its_make_frustum(double r, double h, double fa=(2*PI/360
|
||||
indexed_triangle_set its_make_frustum_dowel(double r, double h, int sectorCount);
|
||||
indexed_triangle_set its_make_pyramid(float base, float height);
|
||||
indexed_triangle_set its_make_sphere(double radius, double fa);
|
||||
indexed_triangle_set its_make_snap(double r, double h, float space_proportion = 0.25f, float bulge_proportion = 0.125f);
|
||||
|
||||
indexed_triangle_set its_convex_hull(const std::vector<Vec3f> &pts);
|
||||
inline indexed_triangle_set its_convex_hull(const indexed_triangle_set &its) { return its_convex_hull(its.vertices); }
|
||||
|
@ -2757,6 +2757,11 @@ bool TickCodeInfo::add_tick(const int tick, Type type, const int extruder, doubl
|
||||
|
||||
bool TickCodeInfo::edit_tick(std::set<TickCode>::iterator it, double print_z)
|
||||
{
|
||||
// Save previously value of the tick before the call a Dialog from get_... functions,
|
||||
// otherwise a background process can change ticks values and current iterator wouldn't be valid for the moment of a Dialog close
|
||||
// and PS will crash (see https://github.com/prusa3d/PrusaSlicer/issues/10941)
|
||||
TickCode changed_tick = *it;
|
||||
|
||||
std::string edited_value;
|
||||
if (it->type == ColorChange)
|
||||
edited_value = get_new_color(it->color);
|
||||
@ -2768,7 +2773,10 @@ bool TickCodeInfo::edit_tick(std::set<TickCode>::iterator it, double print_z)
|
||||
if (edited_value.empty())
|
||||
return false;
|
||||
|
||||
TickCode changed_tick = *it;
|
||||
// Update iterator. For this moment its value can be invalid
|
||||
if (it = ticks.find(changed_tick); it == ticks.end())
|
||||
return false;
|
||||
|
||||
if (it->type == ColorChange) {
|
||||
if (it->color == edited_value)
|
||||
return false;
|
||||
|
@ -588,6 +588,8 @@ bool TextCtrl::value_was_changed()
|
||||
case coFloatOrPercent:
|
||||
case coFloatsOrPercents:
|
||||
return boost::any_cast<std::string>(m_value) != boost::any_cast<std::string>(val);
|
||||
case coPoints:
|
||||
return boost::any_cast<std::vector<Vec2d>>(m_value) != boost::any_cast<std::vector<Vec2d>>(val);
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
|
@ -2689,6 +2689,9 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||
if (curr_gizmo != nullptr)
|
||||
curr_gizmo->unregister_raycasters_for_picking();
|
||||
m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::Gizmo);
|
||||
if (curr_gizmo != nullptr && !m_selection.is_empty())
|
||||
curr_gizmo->register_raycasters_for_picking();
|
||||
m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::FallbackGizmo);
|
||||
if (curr_gizmo != nullptr && !m_selection.is_empty())
|
||||
curr_gizmo->register_raycasters_for_picking();
|
||||
|
||||
@ -3597,11 +3600,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
update_sequential_clearance(true);
|
||||
}
|
||||
}
|
||||
else if (evt.LeftUp() &&
|
||||
m_gizmos.get_current_type() == GLGizmosManager::EType::Scale &&
|
||||
m_gizmos.get_current()->get_state() == GLGizmoBase::EState::On) {
|
||||
wxGetApp().obj_list()->selection_changed();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@ -4071,7 +4069,7 @@ void GLCanvas3D::do_move(const std::string& snapshot_type)
|
||||
model_object->invalidate_bounding_box();
|
||||
}
|
||||
}
|
||||
else if (v->is_wipe_tower)
|
||||
else if (m_selection.is_wipe_tower() && v->is_wipe_tower)
|
||||
// Move a wipe tower proxy.
|
||||
wipe_tower_origin = v->get_volume_offset();
|
||||
}
|
||||
@ -5749,6 +5747,7 @@ void GLCanvas3D::_picking_pass()
|
||||
break;
|
||||
}
|
||||
case SceneRaycaster::EType::Gizmo:
|
||||
case SceneRaycaster::EType::FallbackGizmo:
|
||||
{
|
||||
const Size& cnv_size = get_canvas_size();
|
||||
const bool inside = 0 <= m_mouse.position.x() && m_mouse.position.x() < cnv_size.get_width() &&
|
||||
@ -5781,6 +5780,7 @@ void GLCanvas3D::_picking_pass()
|
||||
{
|
||||
case SceneRaycaster::EType::Bed: { object_type = "Bed"; break; }
|
||||
case SceneRaycaster::EType::Gizmo: { object_type = "Gizmo element"; break; }
|
||||
case SceneRaycaster::EType::FallbackGizmo: { object_type = "Gizmo2 element"; break; }
|
||||
case SceneRaycaster::EType::Volume:
|
||||
{
|
||||
if (m_volumes.volumes[hit.raycaster_id]->is_wipe_tower)
|
||||
@ -5835,6 +5835,8 @@ void GLCanvas3D::_picking_pass()
|
||||
add_strings_row_to_table("Volumes", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text));
|
||||
sprintf(buf, "%d (%d)", (int)m_scene_raycaster.gizmos_count(), (int)m_scene_raycaster.active_gizmos_count());
|
||||
add_strings_row_to_table("Gizmo elements", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text));
|
||||
sprintf(buf, "%d (%d)", (int)m_scene_raycaster.fallback_gizmos_count(), (int)m_scene_raycaster.active_fallback_gizmos_count());
|
||||
add_strings_row_to_table("Gizmo2 elements", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text));
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
@ -5852,6 +5854,20 @@ void GLCanvas3D::_picking_pass()
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<SceneRaycasterItem>>* gizmo2_raycasters = m_scene_raycaster.get_raycasters(SceneRaycaster::EType::FallbackGizmo);
|
||||
if (gizmo2_raycasters != nullptr && !gizmo2_raycasters->empty()) {
|
||||
ImGui::Separator();
|
||||
imgui.text("Gizmo2 raycasters IDs:");
|
||||
if (ImGui::BeginTable("Gizmo2Raycasters", 3)) {
|
||||
for (size_t i = 0; i < gizmo2_raycasters->size(); ++i) {
|
||||
add_strings_row_to_table(std::to_string(i), ImGuiWrapper::COL_ORANGE_LIGHT,
|
||||
std::to_string(SceneRaycaster::decode_id(SceneRaycaster::EType::FallbackGizmo, (*gizmo2_raycasters)[i]->get_id())), ImGui::GetStyleColorVec4(ImGuiCol_Text),
|
||||
to_string(Geometry::Transformation((*gizmo2_raycasters)[i]->get_transform()).get_offset()), ImGui::GetStyleColorVec4(ImGuiCol_Text));
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
imgui.end();
|
||||
#endif // ENABLE_RAYCAST_PICKING_DEBUG
|
||||
}
|
||||
|
@ -503,6 +503,11 @@ void ObjectManipulation::Show(const bool show)
|
||||
}
|
||||
m_word_local_combo->Show(show_world_local_combo);
|
||||
m_empty_str->Show(!show_world_local_combo);
|
||||
|
||||
m_skew_label->Show(m_show_skew);
|
||||
m_reset_skew_button->Show(m_show_skew);
|
||||
|
||||
m_parent->Layout();
|
||||
}
|
||||
}
|
||||
|
||||
@ -795,15 +800,22 @@ void ObjectManipulation::update_reset_buttons_visibility()
|
||||
m_mirror_warning_bitmap->SetBitmap(show_mirror ? m_manifold_warning_bmp.bmp() : wxNullBitmap);
|
||||
m_mirror_warning_bitmap->SetMinSize(show_mirror ? m_manifold_warning_bmp.GetSize() : wxSize(0, 0));
|
||||
m_mirror_warning_bitmap->SetToolTip(show_mirror ? _L("Left handed") : "");
|
||||
m_reset_skew_button->Show(show_skew);
|
||||
m_skew_label->Show(show_skew);
|
||||
|
||||
// Because of CallAfter we need to layout sidebar after Show/hide of reset buttons one more time
|
||||
Sidebar& panel = wxGetApp().sidebar();
|
||||
if (!panel.IsFrozen()) {
|
||||
panel.Freeze();
|
||||
panel.Layout();
|
||||
panel.Thaw();
|
||||
if (m_show_skew == show_skew)
|
||||
get_sizer()->Layout();
|
||||
else {
|
||||
// Call sidebar layout only if it's really needed,
|
||||
// it means, when we show/hide additional line for skew information
|
||||
m_show_skew = show_skew;
|
||||
m_reset_skew_button->Show(m_show_skew);
|
||||
m_skew_label->Show(m_show_skew);
|
||||
// Because of CallAfter we need to layout sidebar after Show/hide of reset buttons one more time
|
||||
Sidebar& panel = wxGetApp().sidebar();
|
||||
if (!panel.IsFrozen()) {
|
||||
panel.Freeze();
|
||||
panel.Layout();
|
||||
panel.Thaw();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -169,6 +169,7 @@ private:
|
||||
bool m_is_enabled { true };
|
||||
bool m_is_enabled_size_and_scale { true };
|
||||
|
||||
bool m_show_skew { false };
|
||||
|
||||
public:
|
||||
ObjectManipulation(wxWindow* parent);
|
||||
|
@ -134,7 +134,12 @@ GalleryDialog::GalleryDialog(wxWindow* parent) :
|
||||
}
|
||||
|
||||
GalleryDialog::~GalleryDialog()
|
||||
{
|
||||
{
|
||||
// From wxWidgets docs:
|
||||
// The method void wxListCtrl::SetImageList(wxImageList* imageList, int which)
|
||||
// does not take ownership of the image list, you have to delete it yourself.
|
||||
if (m_image_list)
|
||||
delete m_image_list;
|
||||
}
|
||||
|
||||
int GalleryDialog::show(bool show_from_menu)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -7,12 +7,14 @@
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/CutUtils.hpp"
|
||||
#include "imgui/imgui.h"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
enum class CutConnectorType : int;
|
||||
class ModelVolume;
|
||||
class GLShaderProgram;
|
||||
struct CutConnectorAttributes;
|
||||
|
||||
namespace GUI {
|
||||
@ -29,6 +31,9 @@ class GLGizmoCut3D : public GLGizmoBase
|
||||
Y,
|
||||
Z,
|
||||
CutPlane,
|
||||
CutPlaneZRotation,
|
||||
CutPlaneXMove,
|
||||
CutPlaneYMove,
|
||||
Count,
|
||||
};
|
||||
|
||||
@ -54,6 +59,7 @@ class GLGizmoCut3D : public GLGizmoBase
|
||||
double m_radius{ 0.0 };
|
||||
double m_grabber_radius{ 0.0 };
|
||||
double m_grabber_connection_len{ 0.0 };
|
||||
Vec3d m_cut_plane_start_move_pos {Vec3d::Zero()};
|
||||
|
||||
double m_snap_coarse_in_radius{ 0.0 };
|
||||
double m_snap_coarse_out_radius{ 0.0 };
|
||||
@ -78,6 +84,7 @@ class GLGizmoCut3D : public GLGizmoBase
|
||||
PickingModel m_plane;
|
||||
PickingModel m_sphere;
|
||||
PickingModel m_cone;
|
||||
PickingModel m_cube;
|
||||
std::map<CutConnectorAttributes, PickingModel> m_shapes;
|
||||
std::vector<std::shared_ptr<SceneRaycasterItem>> m_raycasters;
|
||||
|
||||
@ -111,6 +118,16 @@ class GLGizmoCut3D : public GLGizmoBase
|
||||
bool m_rotate_upper{ false };
|
||||
bool m_rotate_lower{ false };
|
||||
|
||||
// Input params for cut with tongue and groove
|
||||
Cut::Groove m_groove;
|
||||
bool m_groove_editing { false };
|
||||
|
||||
bool m_is_slider_editing_done { false };
|
||||
|
||||
// Input params for cut with snaps
|
||||
float m_snap_bulge_proportion{ 0.15f };
|
||||
float m_snap_space_proportion{ 0.3f };
|
||||
|
||||
bool m_hide_cut_plane{ false };
|
||||
bool m_connectors_editing{ false };
|
||||
bool m_cut_plane_as_circle{ false };
|
||||
@ -127,7 +144,6 @@ class GLGizmoCut3D : public GLGizmoBase
|
||||
|
||||
float m_contour_width{ 0.4f };
|
||||
float m_cut_plane_radius_koef{ 1.5f };
|
||||
bool m_is_contour_changed{ false };
|
||||
float m_shortcut_label_width{ -1.f };
|
||||
|
||||
mutable std::vector<bool> m_selected; // which pins are currently selected
|
||||
@ -139,10 +155,14 @@ class GLGizmoCut3D : public GLGizmoBase
|
||||
bool m_was_cut_plane_dragged { false };
|
||||
bool m_was_contour_selected { false };
|
||||
|
||||
// Vertices of the groove used to detection if groove is valid
|
||||
std::vector<Vec3d> m_groove_vertices;
|
||||
|
||||
class PartSelection {
|
||||
public:
|
||||
PartSelection() = default;
|
||||
PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx, const Vec3d& center, const Vec3d& normal, const CommonGizmosDataObjects::ObjectClipper& oc);
|
||||
PartSelection(const ModelObject* mo, int instance_idx_in);
|
||||
~PartSelection() { m_model.clear_objects(); }
|
||||
|
||||
struct Part {
|
||||
@ -161,6 +181,8 @@ class GLGizmoCut3D : public GLGizmoBase
|
||||
const std::vector<Part>& parts() const { return m_parts; }
|
||||
const std::vector<size_t>* get_ignored_contours_ptr() const { return (valid() ? &m_ignored_contours : nullptr); }
|
||||
|
||||
std::vector<Cut::Part> get_cut_parts();
|
||||
|
||||
private:
|
||||
Model m_model;
|
||||
int m_instance_idx;
|
||||
@ -171,6 +193,8 @@ class GLGizmoCut3D : public GLGizmoBase
|
||||
|
||||
std::vector<Vec3d> m_contour_points; // Debugging
|
||||
std::vector<std::vector<Vec3d>> m_debug_pts; // Debugging
|
||||
|
||||
void add_object(const ModelObject* object);
|
||||
};
|
||||
|
||||
PartSelection m_part_selection;
|
||||
@ -180,7 +204,8 @@ class GLGizmoCut3D : public GLGizmoBase
|
||||
|
||||
enum class CutMode {
|
||||
cutPlanar
|
||||
, cutGrig
|
||||
, cutTongueAndGroove
|
||||
//, cutGrig
|
||||
//,cutRadial
|
||||
//,cutModular
|
||||
};
|
||||
@ -190,7 +215,7 @@ class GLGizmoCut3D : public GLGizmoBase
|
||||
, Manual
|
||||
};
|
||||
|
||||
// std::vector<std::string> m_modes;
|
||||
std::vector<std::string> m_modes;
|
||||
size_t m_mode{ size_t(CutMode::cutPlanar) };
|
||||
|
||||
std::vector<std::string> m_connector_modes;
|
||||
@ -215,7 +240,7 @@ public:
|
||||
GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
||||
|
||||
std::string get_tooltip() const override;
|
||||
bool unproject_on_cut_plane(const Vec2d& mouse_pos, Vec3d& pos, Vec3d& pos_world, bool respect_disabled_contour = true);
|
||||
bool unproject_on_cut_plane(const Vec2d& mouse_pos, Vec3d& pos, Vec3d& pos_world, bool respect_contours = true);
|
||||
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
|
||||
|
||||
bool is_in_editing_mode() const override { return m_connectors_editing; }
|
||||
@ -249,8 +274,8 @@ protected:
|
||||
bool on_is_activable() const override;
|
||||
bool on_is_selectable() const override;
|
||||
Vec3d mouse_position_in_local_plane(GrabberID axis, const Linef3&mouse_ray) const;
|
||||
void dragging_grabber_z(const GLGizmoBase::UpdateData &data);
|
||||
void dragging_grabber_xy(const GLGizmoBase::UpdateData &data);
|
||||
void dragging_grabber_move(const GLGizmoBase::UpdateData &data);
|
||||
void dragging_grabber_rotation(const GLGizmoBase::UpdateData &data);
|
||||
void dragging_connector(const GLGizmoBase::UpdateData &data);
|
||||
void on_dragging(const UpdateData&data) override;
|
||||
void on_start_dragging() override;
|
||||
@ -275,6 +300,8 @@ protected:
|
||||
void add_horizontal_scaled_interval(float interval);
|
||||
void add_horizontal_shift(float shift);
|
||||
void render_color_marker(float size, const ImU32& color);
|
||||
void render_groove_float_input(const std::string &label, float &in_val, const float &init_val, float &in_tolerance);
|
||||
void render_groove_angle_input(const std::string &label, float &in_val, const float &init_val, float min_val, float max_val);
|
||||
void render_cut_plane_input_window(CutConnectors &connectors);
|
||||
void init_input_window_data(CutConnectors &connectors);
|
||||
void render_input_window_warning() const;
|
||||
@ -290,6 +317,8 @@ protected:
|
||||
void set_volumes_picking_state(bool state);
|
||||
void update_raycasters_for_picking_transform();
|
||||
|
||||
void update_plane_model();
|
||||
|
||||
void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||
|
||||
bool wants_enter_leave_snapshots() const override { return true; }
|
||||
@ -301,10 +330,12 @@ protected:
|
||||
Transform3d get_cut_matrix(const Selection& selection);
|
||||
|
||||
private:
|
||||
void set_center(const Vec3d& center, bool update_tbb = false);
|
||||
bool render_combo(const std::string& label, const std::vector<std::string>& lines, int& selection_idx);
|
||||
void set_center(const Vec3d¢er, bool update_tbb = false);
|
||||
void switch_to_mode(size_t new_mode);
|
||||
bool render_cut_mode_combo();
|
||||
bool render_combo(const std::string&label, const std::vector<std::string>&lines, int&selection_idx);
|
||||
bool render_double_input(const std::string& label, double& value_in);
|
||||
bool render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in);
|
||||
bool render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in, float min_val = -0.1f, float max_tolerance = -0.1f);
|
||||
void render_move_center_input(int axis);
|
||||
void render_connect_mode_radio_button(CutConnectorMode mode);
|
||||
bool render_reset_button(const std::string& label_id, const std::string& tooltip) const;
|
||||
@ -314,16 +345,18 @@ private:
|
||||
void render_connectors();
|
||||
|
||||
bool can_perform_cut() const;
|
||||
bool has_valid_groove() const;
|
||||
bool has_valid_contour() const;
|
||||
void apply_connectors_in_model(ModelObject* mo, int &dowels_count);
|
||||
bool cut_line_processing() const;
|
||||
void discard_cut_line_processing();
|
||||
|
||||
void apply_color_clip_plane_colors();
|
||||
void render_cut_plane();
|
||||
static void render_model(GLModel& model, const ColorRGBA& color, Transform3d view_model_matrix);
|
||||
void render_line(GLModel& line_model, const ColorRGBA& color, Transform3d view_model_matrix, float width);
|
||||
void render_rotation_snapping(GrabberID axis, const ColorRGBA& color);
|
||||
void render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix);
|
||||
void render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix, double line_len_koef = 1.0);
|
||||
void render_cut_plane_grabbers();
|
||||
void render_cut_line();
|
||||
void perform_cut(const Selection&selection);
|
||||
@ -339,6 +372,13 @@ private:
|
||||
void validate_connector_settings();
|
||||
bool process_cut_line(SLAGizmoEventType action, const Vec2d& mouse_position);
|
||||
void check_and_update_connectors_state();
|
||||
|
||||
void toggle_model_objects_visibility();
|
||||
|
||||
indexed_triangle_set its_make_groove_plane();
|
||||
|
||||
indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes);
|
||||
void apply_cut_connectors(ModelObject* mo, const std::string& connector_name);
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
|
@ -99,6 +99,12 @@ void GLGizmoScale3D::enable_ununiversal_scale(bool enable)
|
||||
m_grabbers[i].enabled = enable;
|
||||
}
|
||||
|
||||
void GLGizmoScale3D::on_set_state()
|
||||
{
|
||||
if (m_state == On)
|
||||
wxGetApp().obj_list()->selection_changed();
|
||||
}
|
||||
|
||||
void GLGizmoScale3D::data_changed(bool is_serializing) {
|
||||
set_scale(Vec3d::Ones());
|
||||
}
|
||||
|
@ -74,6 +74,8 @@ protected:
|
||||
virtual void on_register_raycasters_for_picking() override;
|
||||
virtual void on_unregister_raycasters_for_picking() override;
|
||||
|
||||
void on_set_state() override;
|
||||
|
||||
private:
|
||||
void render_grabbers_connection(unsigned int id_1, unsigned int id_2, const ColorRGBA& color);
|
||||
|
||||
|
@ -67,6 +67,7 @@ static const std::map<const wchar_t, std::string> font_icons = {
|
||||
{ImGui::InfoMarkerSmall , "notification_info" },
|
||||
{ImGui::PlugMarker , "plug" },
|
||||
{ImGui::DowelMarker , "dowel" },
|
||||
{ImGui::SnapMarker , "snap" },
|
||||
};
|
||||
|
||||
static const std::map<const wchar_t, std::string> font_icons_large = {
|
||||
@ -639,6 +640,8 @@ bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float
|
||||
m_last_slider_status.edited = ImGui::IsItemEdited();
|
||||
m_last_slider_status.clicked = ImGui::IsItemClicked();
|
||||
m_last_slider_status.deactivated_after_edit = ImGui::IsItemDeactivatedAfterEdit();
|
||||
if (!m_last_slider_status.can_take_snapshot)
|
||||
m_last_slider_status.can_take_snapshot = ImGui::IsItemClicked();
|
||||
|
||||
if (!tooltip.empty() && ImGui::IsItemHovered())
|
||||
this->tooltip(into_u8(tooltip).c_str(), max_tooltip_width);
|
||||
|
@ -49,6 +49,11 @@ public:
|
||||
bool edited { false };
|
||||
bool clicked { false };
|
||||
bool deactivated_after_edit { false };
|
||||
// flag to indicate possibility to take snapshot from the slider value
|
||||
// It's used from Gizmos to take snapshots just from the very beginning of the editing
|
||||
bool can_take_snapshot { false };
|
||||
// When Undo/Redo snapshot is taken, then call this function
|
||||
void invalidate_snapshot() { can_take_snapshot = false; }
|
||||
};
|
||||
|
||||
ImGuiWrapper();
|
||||
@ -80,6 +85,7 @@ public:
|
||||
ImVec2 get_item_spacing() const;
|
||||
float get_slider_float_height() const;
|
||||
const LastSliderStatus& get_last_slider_status() const { return m_last_slider_status; }
|
||||
LastSliderStatus& get_last_slider_status() { return m_last_slider_status; }
|
||||
|
||||
void set_next_window_pos(float x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f);
|
||||
void set_next_window_bg_alpha(float alpha);
|
||||
|
@ -465,14 +465,17 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d&
|
||||
|
||||
|
||||
|
||||
bool MeshRaycaster::is_valid_intersection(Vec3d point, Vec3d direction, const Transform3d& trafo) const
|
||||
bool MeshRaycaster::intersects_line(Vec3d point, Vec3d direction, const Transform3d& trafo) const
|
||||
{
|
||||
point = trafo.inverse() * point;
|
||||
Transform3d trafo_inv = trafo.inverse();
|
||||
Vec3d to = trafo_inv * (point + direction);
|
||||
point = trafo_inv * point;
|
||||
direction = (to-point).normalized();
|
||||
|
||||
std::vector<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();
|
||||
return !hits.empty() || !neg_hits.empty();
|
||||
}
|
||||
|
||||
|
||||
|
@ -187,7 +187,9 @@ public:
|
||||
|
||||
const AABBMesh &get_aabb_mesh() const { return m_emesh; }
|
||||
|
||||
bool is_valid_intersection(Vec3d point, Vec3d direction, const Transform3d& trafo) const;
|
||||
// Given a point and direction in world coords, returns whether the respective line
|
||||
// intersects the mesh if it is transformed into world by trafo.
|
||||
bool intersects_line(Vec3d point, Vec3d direction, const Transform3d& trafo) const;
|
||||
|
||||
// Given a vector of points in woorld coordinates, this returns vector
|
||||
// of indices of points that are visible (i.e. not cut by clipping plane
|
||||
|
@ -1280,9 +1280,11 @@ void Sidebar::show_info_sizer()
|
||||
{
|
||||
Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
|
||||
ModelObjectPtrs objects = p->plater->model().objects;
|
||||
int obj_idx = selection.get_object_idx();
|
||||
const int obj_idx = selection.get_object_idx();
|
||||
const int inst_idx = selection.get_instance_idx();
|
||||
|
||||
if (m_mode < comExpert || objects.empty() || obj_idx < 0 || int(objects.size()) <= obj_idx ||
|
||||
inst_idx < 0 || int(objects[obj_idx]->instances.size()) <= inst_idx ||
|
||||
objects[obj_idx]->volumes.empty() || // hack to avoid crash when deleting the last object on the bed
|
||||
(selection.is_single_full_object() && objects[obj_idx]->instances.size()> 1) ||
|
||||
!(selection.is_single_full_instance() || selection.is_single_volume())) {
|
||||
@ -1292,9 +1294,6 @@ void Sidebar::show_info_sizer()
|
||||
|
||||
const ModelObject* model_object = objects[obj_idx];
|
||||
|
||||
int inst_idx = selection.get_instance_idx();
|
||||
assert(inst_idx >= 0);
|
||||
|
||||
bool imperial_units = wxGetApp().app_config->get_bool("use_inches");
|
||||
double koef = imperial_units ? ObjectManipulation::mm_to_in : 1.0f;
|
||||
|
||||
@ -6417,20 +6416,7 @@ void Plater::toggle_layers_editing(bool enable)
|
||||
canvas3D()->force_main_toolbar_left_action(canvas3D()->get_main_toolbar_item_id("layersediting"));
|
||||
}
|
||||
|
||||
void Plater::cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes)
|
||||
{
|
||||
wxCHECK_RET(obj_idx < p->model.objects.size(), "obj_idx out of bounds");
|
||||
auto* object = p->model.objects[obj_idx];
|
||||
|
||||
wxCHECK_RET(instance_idx < object->instances.size(), "instance_idx out of bounds");
|
||||
|
||||
wxBusyCursor wait;
|
||||
|
||||
const auto new_objects = object->cut(instance_idx, cut_matrix, attributes);
|
||||
cut(obj_idx, new_objects);
|
||||
}
|
||||
|
||||
void Plater::cut(size_t obj_idx, const ModelObjectPtrs& new_objects)
|
||||
void Plater::apply_cut_object_to_model(size_t obj_idx, const ModelObjectPtrs& new_objects)
|
||||
{
|
||||
model().delete_object(obj_idx);
|
||||
sidebar().obj_list()->delete_object_from_list(obj_idx);
|
||||
@ -7232,7 +7218,9 @@ void Plater::force_filament_cb_update()
|
||||
|
||||
// Update preset comboboxes on sidebar and filaments tab
|
||||
p->sidebar->update_presets(Preset::TYPE_FILAMENT);
|
||||
wxGetApp().get_tab(Preset::TYPE_FILAMENT)->select_preset(wxGetApp().preset_bundle->filaments.get_selected_preset_name());
|
||||
|
||||
TabFilament* tab = dynamic_cast<TabFilament*>(wxGetApp().get_tab(Preset::TYPE_FILAMENT));
|
||||
tab->select_preset(wxGetApp().preset_bundle->extruders_filaments[tab->get_active_extruder()].get_selected_preset_name());
|
||||
}
|
||||
|
||||
void Plater::force_print_bed_update()
|
||||
|
@ -268,8 +268,7 @@ public:
|
||||
void convert_unit(ConversionType conv_type);
|
||||
void toggle_layers_editing(bool enable);
|
||||
|
||||
void cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes);
|
||||
void cut(size_t init_obj_idx, const ModelObjectPtrs& cut_objects);
|
||||
void apply_cut_object_to_model(size_t init_obj_idx, const ModelObjectPtrs& cut_objects);
|
||||
|
||||
void export_gcode(bool prefer_removable);
|
||||
void export_stl_obj(bool extended = false, bool selection_only = false);
|
||||
|
@ -40,6 +40,7 @@ std::shared_ptr<SceneRaycasterItem> SceneRaycaster::add_raycaster(EType type, in
|
||||
case EType::Bed: { return m_bed.emplace_back(std::make_shared<SceneRaycasterItem>(encode_id(type, id), raycaster, trafo, use_back_faces)); }
|
||||
case EType::Volume: { return m_volumes.emplace_back(std::make_shared<SceneRaycasterItem>(encode_id(type, id), raycaster, trafo, use_back_faces)); }
|
||||
case EType::Gizmo: { return m_gizmos.emplace_back(std::make_shared<SceneRaycasterItem>(encode_id(type, id), raycaster, trafo, use_back_faces)); }
|
||||
case EType::FallbackGizmo: { return m_fallback_gizmos.emplace_back(std::make_shared<SceneRaycasterItem>(encode_id(type, id), raycaster, trafo, use_back_faces)); }
|
||||
default: { assert(false); return nullptr; }
|
||||
};
|
||||
}
|
||||
@ -62,6 +63,7 @@ void SceneRaycaster::remove_raycasters(EType type)
|
||||
case EType::Bed: { m_bed.clear(); break; }
|
||||
case EType::Volume: { m_volumes.clear(); break; }
|
||||
case EType::Gizmo: { m_gizmos.clear(); break; }
|
||||
case EType::FallbackGizmo: { m_fallback_gizmos.clear(); break; }
|
||||
default: { break; }
|
||||
};
|
||||
}
|
||||
@ -86,6 +88,12 @@ void SceneRaycaster::remove_raycaster(std::shared_ptr<SceneRaycasterItem> item)
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (auto it = m_fallback_gizmos.begin(); it != m_fallback_gizmos.end(); ++it) {
|
||||
if (*it == item) {
|
||||
m_fallback_gizmos.erase(it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Camera& camera, const ClippingPlane* clipping_plane) const
|
||||
@ -174,6 +182,9 @@ SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Came
|
||||
if (!m_gizmos.empty())
|
||||
test_raycasters(EType::Gizmo, mouse_pos, camera, ret);
|
||||
|
||||
if (!m_fallback_gizmos.empty() && !ret.is_valid())
|
||||
test_raycasters(EType::FallbackGizmo, mouse_pos, camera, ret);
|
||||
|
||||
if (!m_gizmos_on_top || !ret.is_valid()) {
|
||||
if (camera.is_looking_downward() && !m_bed.empty())
|
||||
test_raycasters(EType::Bed, mouse_pos, camera, ret);
|
||||
@ -241,6 +252,14 @@ size_t SceneRaycaster::active_gizmos_count() const {
|
||||
}
|
||||
return count;
|
||||
}
|
||||
size_t SceneRaycaster::active_fallback_gizmos_count() const {
|
||||
size_t count = 0;
|
||||
for (const auto& g : m_fallback_gizmos) {
|
||||
if (g->is_active())
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
#endif // ENABLE_RAYCAST_PICKING_DEBUG
|
||||
|
||||
std::vector<std::shared_ptr<SceneRaycasterItem>>* SceneRaycaster::get_raycasters(EType type)
|
||||
@ -251,6 +270,7 @@ std::vector<std::shared_ptr<SceneRaycasterItem>>* SceneRaycaster::get_raycasters
|
||||
case EType::Bed: { ret = &m_bed; break; }
|
||||
case EType::Volume: { ret = &m_volumes; break; }
|
||||
case EType::Gizmo: { ret = &m_gizmos; break; }
|
||||
case EType::FallbackGizmo: { ret = &m_fallback_gizmos; break; }
|
||||
default: { break; }
|
||||
}
|
||||
assert(ret != nullptr);
|
||||
@ -265,6 +285,7 @@ const std::vector<std::shared_ptr<SceneRaycasterItem>>* SceneRaycaster::get_rayc
|
||||
case EType::Bed: { ret = &m_bed; break; }
|
||||
case EType::Volume: { ret = &m_volumes; break; }
|
||||
case EType::Gizmo: { ret = &m_gizmos; break; }
|
||||
case EType::FallbackGizmo: { ret = &m_fallback_gizmos; break; }
|
||||
default: { break; }
|
||||
}
|
||||
assert(ret != nullptr);
|
||||
@ -278,6 +299,7 @@ int SceneRaycaster::base_id(EType type)
|
||||
case EType::Bed: { return int(EIdBase::Bed); }
|
||||
case EType::Volume: { return int(EIdBase::Volume); }
|
||||
case EType::Gizmo: { return int(EIdBase::Gizmo); }
|
||||
case EType::FallbackGizmo: { return int(EIdBase::FallbackGizmo); }
|
||||
default: { break; }
|
||||
};
|
||||
|
||||
|
@ -42,14 +42,16 @@ public:
|
||||
None,
|
||||
Bed,
|
||||
Volume,
|
||||
Gizmo
|
||||
Gizmo,
|
||||
FallbackGizmo // Is used for gizmo grabbers which will be hit after all grabbers of Gizmo type
|
||||
};
|
||||
|
||||
enum class EIdBase
|
||||
{
|
||||
Bed = 0,
|
||||
Volume = 1000,
|
||||
Gizmo = 1000000
|
||||
Gizmo = 1000000,
|
||||
FallbackGizmo = 2000000
|
||||
};
|
||||
|
||||
struct HitResult
|
||||
@ -66,6 +68,7 @@ private:
|
||||
std::vector<std::shared_ptr<SceneRaycasterItem>> m_bed;
|
||||
std::vector<std::shared_ptr<SceneRaycasterItem>> m_volumes;
|
||||
std::vector<std::shared_ptr<SceneRaycasterItem>> m_gizmos;
|
||||
std::vector<std::shared_ptr<SceneRaycasterItem>> m_fallback_gizmos;
|
||||
|
||||
// When set to true, if checking gizmos returns a valid hit,
|
||||
// the search is not performed on other types
|
||||
@ -99,9 +102,11 @@ public:
|
||||
size_t beds_count() const { return m_bed.size(); }
|
||||
size_t volumes_count() const { return m_volumes.size(); }
|
||||
size_t gizmos_count() const { return m_gizmos.size(); }
|
||||
size_t fallback_gizmos_count() const { return m_fallback_gizmos.size(); }
|
||||
size_t active_beds_count() const;
|
||||
size_t active_volumes_count() const;
|
||||
size_t active_gizmos_count() const;
|
||||
size_t active_fallback_gizmos_count() const;
|
||||
#endif // ENABLE_RAYCAST_PICKING_DEBUG
|
||||
|
||||
static int decode_id(EType type, int id);
|
||||
|
Loading…
x
Reference in New Issue
Block a user