Cut WIP: Send to the cut() whole cut_matrix instead of cut_plane_pos and rotation angles
+ Fixed units inside input window + NotificationManager: Added info line for loaded object with cut parts + Next Code refactoring
This commit is contained in:
parent
b2d5fd72e9
commit
66e2c3b30a
@ -1381,11 +1381,13 @@ indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes conn
|
||||
return connector_mesh;
|
||||
}
|
||||
|
||||
void ModelObject::apply_cut_connectors(const std::string& name)
|
||||
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));
|
||||
@ -1393,15 +1395,11 @@ void ModelObject::apply_cut_connectors(const std::string& name)
|
||||
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(Geometry::assemble_transform(
|
||||
connector.pos,
|
||||
connector.rotation,
|
||||
Vec3d(connector.radius, connector.radius, connector.height),
|
||||
Vec3d::Ones()
|
||||
));
|
||||
new_volume->set_transformation(assemble_transform(connector.pos) * connector.rotation_m *
|
||||
scale_transform(Vec3f(connector.radius, connector.radius, connector.height).cast<double>()));
|
||||
|
||||
new_volume->cut_info = { true, connector.attribs.type, connector.radius_tolerance, connector.height_tolerance };
|
||||
new_volume->name = name + "-" + std::to_string(++connector_id);
|
||||
new_volume->name = new_name + "-" + std::to_string(++connector_id);
|
||||
}
|
||||
cut_id.increase_connectors_cnt(cut_connectors.size());
|
||||
|
||||
@ -1426,14 +1424,8 @@ void ModelObject::synchronize_model_after_cut()
|
||||
}
|
||||
}
|
||||
|
||||
ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const Vec3d& cut_rotation, ModelObjectCutAttributes attributes)
|
||||
void ModelObject::apply_cut_attributes(ModelObjectCutAttributes attributes)
|
||||
{
|
||||
if (!attributes.has(ModelObjectCutAttribute::KeepUpper) && !attributes.has(ModelObjectCutAttribute::KeepLower))
|
||||
return {};
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start";
|
||||
|
||||
// initiate/update cut attributes for object
|
||||
if (cut_id.id().invalid())
|
||||
cut_id.init();
|
||||
{
|
||||
@ -1444,25 +1436,241 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const
|
||||
if (cut_obj_cnt > 0)
|
||||
cut_id.increase_check_sum(size_t(cut_obj_cnt));
|
||||
}
|
||||
}
|
||||
|
||||
auto clone_obj = [this](ModelObject** obj) {
|
||||
(*obj) = ModelObject::new_clone(*this);
|
||||
(*obj)->set_model(nullptr);
|
||||
(*obj)->sla_support_points.clear();
|
||||
(*obj)->sla_drain_holes.clear();
|
||||
(*obj)->sla_points_status = sla::PointsStatus::NoPoints;
|
||||
(*obj)->clear_volumes();
|
||||
(*obj)->input_file.clear();
|
||||
};
|
||||
void ModelObject::clone_for_cut(ModelObject** obj)
|
||||
{
|
||||
(*obj) = ModelObject::new_clone(*this);
|
||||
(*obj)->set_model(nullptr);
|
||||
(*obj)->sla_support_points.clear();
|
||||
(*obj)->sla_drain_holes.clear();
|
||||
(*obj)->sla_points_status = sla::PointsStatus::NoPoints;
|
||||
(*obj)->clear_volumes();
|
||||
(*obj)->input_file.clear();
|
||||
}
|
||||
|
||||
void ModelVolume::apply_tolerance()
|
||||
{
|
||||
if (!cut_info.is_connector)
|
||||
return;
|
||||
|
||||
Vec3d sf = get_scaling_factor();
|
||||
/*
|
||||
// correct Z offset in respect to the new size
|
||||
Vec3d pos = vol->get_offset();
|
||||
pos[Z] += sf[Z] * 0.5 * vol->cut_info.height_tolerance;
|
||||
vol->set_offset(pos);
|
||||
*/
|
||||
// make a "hole" wider
|
||||
sf[X] *= 1. + double(cut_info.radius_tolerance);
|
||||
sf[Y] *= 1. + double(cut_info.radius_tolerance);
|
||||
|
||||
// make a "hole" dipper
|
||||
sf[Z] *= 1. + double(cut_info.height_tolerance);
|
||||
|
||||
set_scaling_factor(sf);
|
||||
}
|
||||
|
||||
void ModelObject::process_connector_cut(ModelVolume* volume, ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
|
||||
std::vector<ModelObject*>& dowels, Vec3d& local_dowels_displace)
|
||||
{
|
||||
volume->cut_info.discard();
|
||||
|
||||
const auto volume_matrix = volume->get_matrix();
|
||||
|
||||
// ! Don't apply instance transformation for the conntectors.
|
||||
// This transformation is already there
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
|
||||
ModelVolume* vol = upper->add_volume(*volume);
|
||||
vol->set_transformation(volume_matrix);
|
||||
vol->apply_tolerance();
|
||||
}
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower)) {
|
||||
ModelVolume* vol = lower->add_volume(*volume);
|
||||
vol->set_transformation(volume_matrix);
|
||||
|
||||
if (volume->cut_info.connector_type == CutConnectorType::Dowel)
|
||||
vol->apply_tolerance();
|
||||
else
|
||||
// for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug
|
||||
vol->set_type(ModelVolumeType::MODEL_PART);
|
||||
}
|
||||
if (volume->cut_info.connector_type == CutConnectorType::Dowel &&
|
||||
attributes.has(ModelObjectCutAttribute::CreateDowels)) {
|
||||
ModelObject* dowel{ nullptr };
|
||||
// Clone the object to duplicate instances, materials etc.
|
||||
clone_for_cut(&dowel);
|
||||
|
||||
// add one more solid part same as connector if this connector is a dowel
|
||||
ModelVolume* vol = dowel->add_volume(*volume);
|
||||
vol->set_type(ModelVolumeType::MODEL_PART);
|
||||
|
||||
// But discard rotation and Z-offset for this volume
|
||||
vol->set_rotation(Vec3d::Zero());
|
||||
vol->set_offset(Z, 0.0);
|
||||
|
||||
// Compute the displacement (in instance coordinates) to be applied to place the dowels
|
||||
local_dowels_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(1.0, 1.0, 0.0));
|
||||
|
||||
dowels.push_back(dowel);
|
||||
}
|
||||
}
|
||||
|
||||
void ModelObject::process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower)
|
||||
{
|
||||
const auto volume_matrix = 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(instance_matrix * volume_matrix));
|
||||
|
||||
// Some logic for the negative volumes/connectors. Add only needed modifiers
|
||||
auto bb = volume->mesh().transformed_bounding_box(inverse_cut_matrix * volume_matrix);
|
||||
bool is_crossed_by_cut = bb.min[Z] <= 0 && bb.max[Z] >= 0;
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && (bb.min[Z] >= 0 || is_crossed_by_cut))
|
||||
upper->add_volume(*volume);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && (bb.max[Z] <= 0 || is_crossed_by_cut))
|
||||
lower->add_volume(*volume);
|
||||
}
|
||||
|
||||
static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelVolume* src_volume, const Transform3d& cut_matrix)
|
||||
{
|
||||
if (mesh.empty())
|
||||
return;
|
||||
|
||||
mesh.transform(cut_matrix);
|
||||
ModelVolume* vol = object->add_volume(mesh);
|
||||
|
||||
vol->name = src_volume->name;
|
||||
// Don't copy the config's ID.
|
||||
vol->config.assign_config(src_volume->config);
|
||||
assert(vol->config.id().valid());
|
||||
assert(vol->config.id() != src_volume->config.id());
|
||||
vol->set_material(src_volume->material_id(), *src_volume->material());
|
||||
}
|
||||
|
||||
void ModelObject::process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, Vec3d& local_displace)
|
||||
{
|
||||
const auto volume_matrix = volume->get_matrix();
|
||||
|
||||
using namespace Geometry;
|
||||
|
||||
const Transformation cut_transformation = Transformation(cut_matrix);
|
||||
const Transform3d invert_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * assemble_transform(-1 * cut_transformation.get_offset());
|
||||
|
||||
// Transform the mesh by the combined transformation matrix.
|
||||
// Flip the triangles in case the composite transformation is left handed.
|
||||
TriangleMesh mesh(volume->mesh());
|
||||
mesh.transform(invert_cut_matrix * instance_matrix * volume_matrix, true);
|
||||
|
||||
volume->reset_mesh();
|
||||
// Reset volume transformation except for offset
|
||||
const Vec3d offset = volume->get_offset();
|
||||
volume->set_transformation(Geometry::Transformation());
|
||||
volume->set_offset(offset);
|
||||
|
||||
// Perform cut
|
||||
|
||||
TriangleMesh upper_mesh, lower_mesh;
|
||||
{
|
||||
indexed_triangle_set upper_its, lower_its;
|
||||
cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
upper_mesh = TriangleMesh(upper_its);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower))
|
||||
lower_mesh = TriangleMesh(lower_its);
|
||||
}
|
||||
|
||||
// Add required cut parts to the objects
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
add_cut_volume(upper_mesh, upper, volume, cut_matrix);
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) {
|
||||
add_cut_volume(lower_mesh, lower, volume, cut_matrix);
|
||||
|
||||
// Compute the displacement (in instance coordinates) to be applied to place the upper parts
|
||||
// The upper part displacement is set to half of the lower part bounding box
|
||||
// this is done in hope at least a part of the upper part will always be visible and draggable
|
||||
local_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-0.5, -0.5, 0.0));
|
||||
}
|
||||
}
|
||||
|
||||
static void invalidate_translations(ModelObject* object, const ModelInstance* src_instance)
|
||||
{
|
||||
if (!object->origin_translation.isApprox(Vec3d::Zero()) && src_instance->get_offset().isApprox(Vec3d::Zero())) {
|
||||
object->center_around_origin();
|
||||
object->translate_instances(-object->origin_translation);
|
||||
object->origin_translation = Vec3d::Zero();
|
||||
}
|
||||
else {
|
||||
object->invalidate_bounding_box();
|
||||
object->center_around_origin();
|
||||
}
|
||||
}
|
||||
|
||||
static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix,
|
||||
bool place_on_cut = false, bool flip = false, Vec3d local_displace = Vec3d::Zero())
|
||||
{
|
||||
using namespace Geometry;
|
||||
static Vec3d rotate_z180 = deg2rad(180.0) * Vec3d::UnitX();
|
||||
|
||||
// Reset instance transformation except offset and Z-rotation
|
||||
|
||||
for (size_t i = 0; i < object->instances.size(); ++i) {
|
||||
auto& obj_instance = object->instances[i];
|
||||
const Vec3d offset = obj_instance->get_offset();
|
||||
const double rot_z = obj_instance->get_rotation().z();
|
||||
|
||||
obj_instance->set_transformation(Transformation());
|
||||
|
||||
const Vec3d displace = local_displace.isApprox(Vec3d::Zero()) ? Vec3d::Zero() :
|
||||
assemble_transform(Vec3d::Zero(), obj_instance->get_rotation()) * local_displace;
|
||||
obj_instance->set_offset(offset + displace);
|
||||
|
||||
Vec3d rotation = Vec3d::Zero();
|
||||
if (!flip && !place_on_cut) {
|
||||
if ( i != src_instance_idx)
|
||||
rotation[Z] = rot_z;
|
||||
}
|
||||
else {
|
||||
Transform3d rotation_matrix = Transform3d::Identity();
|
||||
if (flip)
|
||||
rotation_matrix = rotation_transform(rotate_z180);
|
||||
|
||||
if (place_on_cut)
|
||||
rotation_matrix = rotation_matrix * Transformation(cut_matrix).get_rotation_matrix().inverse();
|
||||
|
||||
if (i != src_instance_idx)
|
||||
rotation_matrix = rotation_transform(rot_z * Vec3d::UnitZ()) * rotation_matrix;
|
||||
|
||||
rotation = Transformation(rotation_matrix).get_rotation();
|
||||
}
|
||||
|
||||
obj_instance->set_rotation(rotation);
|
||||
}
|
||||
}
|
||||
|
||||
ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes)
|
||||
{
|
||||
if (!attributes.has(ModelObjectCutAttribute::KeepUpper) && !attributes.has(ModelObjectCutAttribute::KeepLower))
|
||||
return {};
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start";
|
||||
|
||||
// apply cut attributes for object
|
||||
apply_cut_attributes(attributes);
|
||||
|
||||
// Clone the object to duplicate instances, materials etc.
|
||||
ModelObject* upper{ nullptr };
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
clone_obj(&upper);
|
||||
clone_for_cut(&upper);
|
||||
|
||||
ModelObject* lower{ nullptr };
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower))
|
||||
clone_obj(&lower);
|
||||
clone_for_cut(&lower);
|
||||
|
||||
std::vector<ModelObject*> dowels;
|
||||
|
||||
@ -1476,260 +1684,67 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Vec3d& cut_center, const
|
||||
// const auto instance_matrix = instances[instance]->get_matrix(true);
|
||||
const auto instance_matrix = assemble_transform(
|
||||
Vec3d::Zero(), // don't apply offset
|
||||
instances[instance]->get_rotation().cwiseProduct(Vec3d(1.0, 1.0, 1.0)),
|
||||
instances[instance]->get_rotation(),
|
||||
instances[instance]->get_scaling_factor(),
|
||||
instances[instance]->get_mirror()
|
||||
);
|
||||
|
||||
const auto cut_matrix = rotation_transform(cut_rotation).inverse() * assemble_transform(-cut_center);
|
||||
const auto invert_cut_matrix = assemble_transform(cut_center, cut_rotation);
|
||||
const Transformation cut_transformation = Transformation(cut_matrix);
|
||||
const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * assemble_transform(-1. * cut_transformation.get_offset());
|
||||
|
||||
// Displacement (in instance coordinates) to be applied to place the upper parts
|
||||
Vec3d local_displace = Vec3d::Zero();
|
||||
Vec3d local_dowels_displace = Vec3d::Zero();
|
||||
|
||||
Vec3d rotate_z180 = deg2rad(180.0) * Vec3d::UnitX();
|
||||
|
||||
auto apply_tolerance = [](ModelVolume * vol)
|
||||
{
|
||||
Vec3d sf = vol->get_scaling_factor();
|
||||
/*
|
||||
// correct Z offset in respect to the new size
|
||||
Vec3d pos = vol->get_offset();
|
||||
pos[Z] += sf[Z] * 0.5 * vol->cut_info.height_tolerance;
|
||||
vol->set_offset(pos);
|
||||
*/
|
||||
// make a "hole" wider
|
||||
sf[X] *= (1 + vol->cut_info.radius_tolerance);
|
||||
sf[Y] *= (1 + vol->cut_info.radius_tolerance);
|
||||
// make a "hole" dipper
|
||||
sf[Z] *= (1 + vol->cut_info.height_tolerance);
|
||||
vol->set_scaling_factor(sf);
|
||||
};
|
||||
|
||||
for (ModelVolume* volume : volumes) {
|
||||
const auto volume_matrix = volume->get_matrix();
|
||||
|
||||
volume->supported_facets.reset();
|
||||
volume->seam_facets.reset();
|
||||
volume->mmu_segmentation_facets.reset();
|
||||
|
||||
if (!volume->is_model_part()) {
|
||||
if (volume->cut_info.is_connector) {
|
||||
volume->cut_info.discard();
|
||||
|
||||
// ! Don't apply instance transformation for the conntectors.
|
||||
// This transformation is already there
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
|
||||
ModelVolume* vol = upper->add_volume(*volume);
|
||||
vol->set_transformation(volume_matrix);
|
||||
apply_tolerance(vol);
|
||||
}
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower)) {
|
||||
ModelVolume* vol = lower->add_volume(*volume);
|
||||
vol->set_transformation(volume_matrix);
|
||||
|
||||
if (volume->cut_info.connector_type == CutConnectorType::Dowel)
|
||||
apply_tolerance(vol);
|
||||
else
|
||||
// for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug
|
||||
vol->set_type(ModelVolumeType::MODEL_PART);
|
||||
}
|
||||
if (volume->cut_info.connector_type == CutConnectorType::Dowel &&
|
||||
attributes.has(ModelObjectCutAttribute::CreateDowels)) {
|
||||
ModelObject* dowel{ nullptr };
|
||||
// Clone the object to duplicate instances, materials etc.
|
||||
clone_obj(&dowel);
|
||||
|
||||
// add one more solid part same as connector if this connector is a dowel
|
||||
ModelVolume* vol = dowel->add_volume(*volume);
|
||||
vol->set_type(ModelVolumeType::MODEL_PART);
|
||||
|
||||
// But discard rotation and Z-offset for this volume
|
||||
vol->set_rotation(Vec3d::Zero());
|
||||
vol->set_offset(Z, 0.0);
|
||||
|
||||
// Compute the displacement (in instance coordinates) to be applied to place the dowels
|
||||
local_dowels_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(1.0, 1.0, 0.0));
|
||||
|
||||
dowels.push_back(dowel);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Modifiers are not cut, but we still need to add the instance transformation
|
||||
// to the modifier volume transformation to preserve their shape properly.
|
||||
volume->set_transformation(Geometry::Transformation(instance_matrix * volume_matrix));
|
||||
|
||||
// Some logic for the negative volumes/connectors. Add only needed modifiers
|
||||
auto bb = volume->mesh().transformed_bounding_box(cut_matrix * volume->get_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);
|
||||
}
|
||||
}
|
||||
else if (!volume->mesh().empty()) {
|
||||
// Transform the mesh by the combined transformation matrix.
|
||||
// Flip the triangles in case the composite transformation is left handed.
|
||||
TriangleMesh mesh(volume->mesh());
|
||||
mesh.transform(cut_matrix * instance_matrix* volume_matrix, true);
|
||||
|
||||
volume->reset_mesh();
|
||||
// Reset volume transformation except for offset
|
||||
const Vec3d offset = volume->get_offset();
|
||||
volume->set_transformation(Geometry::Transformation());
|
||||
volume->set_offset(offset);
|
||||
|
||||
// Perform cut
|
||||
TriangleMesh upper_mesh, lower_mesh;
|
||||
{
|
||||
indexed_triangle_set upper_its, lower_its;
|
||||
cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
upper_mesh = TriangleMesh(upper_its);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower))
|
||||
lower_mesh = TriangleMesh(lower_its);
|
||||
}
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper_mesh.empty()) {
|
||||
upper_mesh.transform(invert_cut_matrix);
|
||||
|
||||
ModelVolume* vol = upper->add_volume(upper_mesh);
|
||||
vol->name = volume->name;
|
||||
// Don't copy the config's ID.
|
||||
vol->config.assign_config(volume->config);
|
||||
assert(vol->config.id().valid());
|
||||
assert(vol->config.id() != volume->config.id());
|
||||
vol->set_material(volume->material_id(), *volume->material());
|
||||
}
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) {
|
||||
lower_mesh.transform(invert_cut_matrix);
|
||||
|
||||
ModelVolume* vol = lower->add_volume(lower_mesh);
|
||||
vol->name = volume->name;
|
||||
// Don't copy the config's ID.
|
||||
vol->config.assign_config(volume->config);
|
||||
assert(vol->config.id().valid());
|
||||
assert(vol->config.id() != volume->config.id());
|
||||
vol->set_material(volume->material_id(), *volume->material());
|
||||
|
||||
// Compute the displacement (in instance coordinates) to be applied to place the upper parts
|
||||
// The upper part displacement is set to half of the lower part bounding box
|
||||
// this is done in hope at least a part of the upper part will always be visible and draggable
|
||||
local_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-0.5, -0.5, 0.0));
|
||||
}
|
||||
if (volume->cut_info.is_connector)
|
||||
process_connector_cut(volume, attributes, upper, lower, dowels, local_dowels_displace);
|
||||
else
|
||||
process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, attributes, upper, lower);
|
||||
}
|
||||
else if (!volume->mesh().empty())
|
||||
process_solid_part_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, local_displace);
|
||||
}
|
||||
|
||||
// Post-process cut parts
|
||||
|
||||
ModelObjectPtrs res;
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && upper->volumes.size() > 0) {
|
||||
if (!upper->origin_translation.isApprox(Vec3d::Zero()) && instances[instance]->get_offset().isApprox(Vec3d::Zero())) {
|
||||
upper->center_around_origin();
|
||||
upper->translate_instances(-upper->origin_translation);
|
||||
upper->origin_translation = Vec3d::Zero();
|
||||
}
|
||||
else {
|
||||
upper->invalidate_bounding_box();
|
||||
upper->center_around_origin();
|
||||
}
|
||||
|
||||
// Reset instance transformation except offset and Z-rotation
|
||||
for (size_t i = 0; i < instances.size(); ++i) {
|
||||
auto& obj_instance = upper->instances[i];
|
||||
const Vec3d offset = obj_instance->get_offset();
|
||||
const double rot_z = obj_instance->get_rotation().z();
|
||||
const Vec3d displace = Geometry::assemble_transform(Vec3d::Zero(), obj_instance->get_rotation()) * local_displace;
|
||||
invalidate_translations(upper, instances[instance]);
|
||||
|
||||
obj_instance->set_transformation(Geometry::Transformation());
|
||||
obj_instance->set_offset(offset + displace);
|
||||
|
||||
Vec3d rotation = Vec3d::Zero();
|
||||
if (attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper)) {
|
||||
Transform3d trafo = rotation_transform(cut_rotation).inverse();
|
||||
if (i != instance)
|
||||
trafo = rotation_transform(rot_z * Vec3d::UnitZ()) * trafo;
|
||||
rotation = Transformation(trafo).get_rotation();
|
||||
}
|
||||
else if (attributes.has(ModelObjectCutAttribute::FlipUpper)) {
|
||||
rotation = rotate_z180;
|
||||
if (i != instance)
|
||||
rotation[Z] = rot_z;
|
||||
}
|
||||
else if (i != instance)
|
||||
rotation[Z] = rot_z/* * Vec3d::UnitZ()*/;
|
||||
obj_instance->set_rotation(rotation);
|
||||
}
|
||||
reset_instance_transformation(upper, instance, cut_matrix,
|
||||
attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper),
|
||||
attributes.has(ModelObjectCutAttribute::FlipUpper),
|
||||
local_displace);
|
||||
|
||||
res.push_back(upper);
|
||||
}
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && lower->volumes.size() > 0) {
|
||||
if (!lower->origin_translation.isApprox(Vec3d::Zero()) && instances[instance]->get_offset().isApprox(Vec3d::Zero())) {
|
||||
lower->center_around_origin();
|
||||
lower->translate_instances(-lower->origin_translation);
|
||||
lower->origin_translation = Vec3d::Zero();
|
||||
}
|
||||
else {
|
||||
lower->invalidate_bounding_box();
|
||||
lower->center_around_origin();
|
||||
}
|
||||
|
||||
// Reset instance transformation except offset and Z-rotation
|
||||
for (size_t i = 0; i < instances.size(); ++i) {
|
||||
auto& obj_instance = lower->instances[i];
|
||||
const Vec3d offset = obj_instance->get_offset();
|
||||
const double rot_z = obj_instance->get_rotation().z();
|
||||
obj_instance->set_transformation(Geometry::Transformation());
|
||||
obj_instance->set_offset(offset);
|
||||
invalidate_translations(lower, instances[instance]);
|
||||
|
||||
Vec3d rotation = Vec3d::Zero();
|
||||
if (attributes.has(ModelObjectCutAttribute::PlaceOnCutLower)) {
|
||||
Transform3d trafo = rotation_transform(rotate_z180) * rotation_transform(cut_rotation).inverse();
|
||||
if (i != instance)
|
||||
trafo = rotation_transform(rot_z * Vec3d::UnitZ()) * trafo;
|
||||
rotation = Transformation(trafo).get_rotation();
|
||||
}
|
||||
else if (attributes.has(ModelObjectCutAttribute::FlipLower)) {
|
||||
rotation = rotate_z180;
|
||||
if (i != instance)
|
||||
rotation[Z] = rot_z;
|
||||
}
|
||||
else if (i != instance)
|
||||
rotation[Z] = rot_z;
|
||||
obj_instance->set_rotation(rotation);
|
||||
}
|
||||
reset_instance_transformation(lower, instance, cut_matrix,
|
||||
attributes.has(ModelObjectCutAttribute::PlaceOnCutLower),
|
||||
attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) ? true : attributes.has(ModelObjectCutAttribute::FlipLower));
|
||||
|
||||
res.push_back(lower);
|
||||
}
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::CreateDowels) && dowels.size() > 0) {
|
||||
for (auto dowel : dowels) {
|
||||
if (!dowel->origin_translation.isApprox(Vec3d::Zero()) && instances[instance]->get_offset().isApprox(Vec3d::Zero())) {
|
||||
dowel->center_around_origin();
|
||||
dowel->translate_instances(-dowel->origin_translation);
|
||||
dowel->origin_translation = Vec3d::Zero();
|
||||
}
|
||||
else {
|
||||
dowel->invalidate_bounding_box();
|
||||
dowel->center_around_origin();
|
||||
}
|
||||
invalidate_translations(dowel, instances[instance]);
|
||||
|
||||
dowel->name += "-Dowel-" + dowel->volumes[0]->name;
|
||||
|
||||
// Reset instance transformation except offset and Z-rotation
|
||||
for (size_t i = 0; i < instances.size(); ++i) {
|
||||
auto& obj_instance = dowel->instances[i];
|
||||
const Vec3d offset = obj_instance->get_offset();
|
||||
Vec3d rotation = Vec3d::Zero();
|
||||
if (i != instance)
|
||||
rotation[Z] = obj_instance->get_rotation().z();
|
||||
|
||||
const Vec3d displace = Geometry::assemble_transform(Vec3d::Zero(), rotation) * local_dowels_displace;
|
||||
|
||||
obj_instance->set_transformation(Geometry::Transformation());
|
||||
obj_instance->set_offset(offset + displace);
|
||||
obj_instance->set_rotation(rotation);
|
||||
}
|
||||
reset_instance_transformation(dowel, instance, Transform3d::Identity(), false, false, local_dowels_displace);
|
||||
|
||||
local_dowels_displace += dowel->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-1.5, -1.5, 0.0));
|
||||
res.push_back(dowel);
|
||||
|
@ -271,7 +271,7 @@ struct CutConnectorAttributes
|
||||
struct CutConnector
|
||||
{
|
||||
Vec3d pos;
|
||||
Vec3d rotation;
|
||||
Transform3d rotation_m;
|
||||
float radius;
|
||||
float height;
|
||||
float radius_tolerance;// [0.f : 1.f]
|
||||
@ -279,22 +279,22 @@ struct CutConnector
|
||||
CutConnectorAttributes attribs;
|
||||
|
||||
CutConnector()
|
||||
: pos(Vec3d::Zero()), rotation(Vec3d::UnitZ()), radius(5.f), height(10.f), radius_tolerance(0.f), height_tolerance(0.1f)
|
||||
: pos(Vec3d::Zero()), rotation_m(Transform3d::Identity()), radius(5.f), height(10.f), radius_tolerance(0.f), height_tolerance(0.1f)
|
||||
{}
|
||||
|
||||
CutConnector(Vec3d p, Vec3d rot, float r, float h, float rt, float ht, CutConnectorAttributes attributes)
|
||||
: pos(p), rotation(rot), radius(r), height(h), radius_tolerance(rt), height_tolerance(ht), attribs(attributes)
|
||||
CutConnector(Vec3d p, Transform3d rot, float r, float h, float rt, float ht, CutConnectorAttributes attributes)
|
||||
: pos(p), rotation_m(rot), radius(r), height(h), radius_tolerance(rt), height_tolerance(ht), attribs(attributes)
|
||||
{}
|
||||
|
||||
CutConnector(const CutConnector& rhs) :
|
||||
CutConnector(rhs.pos, rhs.rotation, rhs.radius, rhs.height, rhs.radius_tolerance, rhs.height_tolerance, rhs.attribs) {}
|
||||
CutConnector(rhs.pos, rhs.rotation_m, rhs.radius, rhs.height, rhs.radius_tolerance, rhs.height_tolerance, rhs.attribs) {}
|
||||
|
||||
bool operator==(const CutConnector& other) const;
|
||||
|
||||
bool operator!=(const CutConnector& other) const { return !(other == (*this)); }
|
||||
|
||||
template<class Archive> inline void serialize(Archive& ar) {
|
||||
ar(pos, rotation, radius, height, radius_tolerance, height_tolerance, attribs);
|
||||
ar(pos, rotation_m, radius, height, radius_tolerance, height_tolerance, attribs);
|
||||
}
|
||||
};
|
||||
|
||||
@ -445,8 +445,16 @@ public:
|
||||
// invalidate cut state for this and related objects from the whole model
|
||||
void invalidate_cut();
|
||||
void synchronize_model_after_cut();
|
||||
ModelObjectPtrs cut(size_t instance, const Vec3d& cut_center, const Vec3d& cut_rotation, ModelObjectCutAttributes attributes);
|
||||
void split(ModelObjectPtrs* new_objects);
|
||||
void apply_cut_attributes(ModelObjectCutAttributes attributes);
|
||||
void clone_for_cut(ModelObject **obj);
|
||||
void process_connector_cut(ModelVolume* volume, ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
|
||||
std::vector<ModelObject*>& dowels, Vec3d& local_dowels_displace);
|
||||
void process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower);
|
||||
void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, Vec3d& local_displace);
|
||||
ModelObjectPtrs cut(size_t instance, const Transform3d&cut_matrix, ModelObjectCutAttributes attributes);
|
||||
void split(ModelObjectPtrs*new_objects);
|
||||
void merge();
|
||||
// Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees,
|
||||
// then the scaling in world coordinate system is not representable by the Geometry::Transformation structure.
|
||||
@ -760,6 +768,7 @@ public:
|
||||
bool is_support_blocker() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER; }
|
||||
bool is_support_modifier() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER || m_type == ModelVolumeType::SUPPORT_ENFORCER; }
|
||||
t_model_material_id material_id() const { return m_material_id; }
|
||||
void 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);
|
||||
|
@ -800,10 +800,9 @@ void GLGizmoCut3D::on_load(cereal::BinaryInputArchive& ar)
|
||||
{
|
||||
ar( m_keep_upper, m_keep_lower, m_rotate_lower, m_rotate_upper, m_hide_cut_plane, m_mode, m_connectors_editing,//m_selected,
|
||||
// m_connector_depth_ratio, m_connector_size, m_connector_mode, m_connector_type, m_connector_style, m_connector_shape_id,
|
||||
m_ar_plane_center, m_ar_rotations);
|
||||
m_ar_plane_center, m_rotation_m);
|
||||
|
||||
set_center_pos(m_ar_plane_center, true);
|
||||
m_rotation_m = rotation_transform(m_ar_rotations);
|
||||
|
||||
force_update_clipper_on_render = true;
|
||||
|
||||
@ -814,7 +813,7 @@ void GLGizmoCut3D::on_save(cereal::BinaryOutputArchive& ar) const
|
||||
{
|
||||
ar( m_keep_upper, m_keep_lower, m_rotate_lower, m_rotate_upper, m_hide_cut_plane, m_mode, m_connectors_editing,//m_selected,
|
||||
// m_connector_depth_ratio, m_connector_size, m_connector_mode, m_connector_type, m_connector_style, m_connector_shape_id,
|
||||
m_ar_plane_center, m_ar_rotations);
|
||||
m_ar_plane_center, m_start_dragging_m);
|
||||
}
|
||||
|
||||
std::string GLGizmoCut3D::on_get_name() const
|
||||
@ -829,7 +828,7 @@ void GLGizmoCut3D::on_set_state()
|
||||
|
||||
// initiate archived values
|
||||
m_ar_plane_center = m_plane_center;
|
||||
m_ar_rotations = Transformation(m_rotation_m).get_rotation();
|
||||
m_start_dragging_m = m_rotation_m;
|
||||
|
||||
m_parent.request_extra_frame();
|
||||
}
|
||||
@ -1119,8 +1118,6 @@ void GLGizmoCut3D::on_stop_dragging()
|
||||
m_angle_arc.reset();
|
||||
m_angle = 0.0;
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Rotate cut plane"), UndoRedo::SnapshotType::GizmoAction);
|
||||
m_ar_rotations = Transformation(m_rotation_m).get_rotation();
|
||||
|
||||
m_start_dragging_m = m_rotation_m;
|
||||
}
|
||||
else if (m_hover_id == Z) {
|
||||
@ -1436,7 +1433,7 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors)
|
||||
void GLGizmoCut3D::render_build_size()
|
||||
{
|
||||
double koef = m_imperial_units ? ObjectManipulation::mm_to_in : 1.0;
|
||||
std::string unit_str = m_imperial_units ? _u8L("inch") : _u8L("mm");
|
||||
wxString unit_str = " " + (m_imperial_units ? _L("in") : _L("mm"));
|
||||
const BoundingBoxf3 tbb = transformed_bounding_box();
|
||||
|
||||
Vec3d tbb_sz = tbb.size();
|
||||
@ -1710,6 +1707,31 @@ bool GLGizmoCut3D::can_perform_cut() const
|
||||
return tbb.contains(m_plane_center);
|
||||
}
|
||||
|
||||
void GLGizmoCut3D::apply_connectors_in_model(ModelObject* mo, const bool has_connectors, bool &create_dowels_as_separate_object)
|
||||
{
|
||||
if (has_connectors && m_connector_mode == CutConnectorMode::Manual) {
|
||||
m_selected.clear();
|
||||
|
||||
for (CutConnector&connector : mo->cut_connectors) {
|
||||
connector.rotation_m = m_rotation_m;
|
||||
|
||||
if (connector.attribs.type == CutConnectorType::Dowel) {
|
||||
if (connector.attribs.style == CutConnectorStyle::Prizm)
|
||||
connector.height *= 2;
|
||||
create_dowels_as_separate_object = true;
|
||||
}
|
||||
else {
|
||||
// culculate shift of the connector center regarding to the position on the cut plane
|
||||
Vec3d shifted_center = m_plane_center + Vec3d::UnitZ();
|
||||
rotate_vec3d_around_plane_center(shifted_center);
|
||||
Vec3d norm = (shifted_center - m_plane_center).normalized();
|
||||
connector.pos += norm * 0.5 * double(connector.height);
|
||||
}
|
||||
}
|
||||
mo->apply_cut_connectors(_u8L("Connector"));
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoCut3D::perform_cut(const Selection& selection)
|
||||
{
|
||||
const int instance_idx = selection.get_instance_idx();
|
||||
@ -1717,53 +1739,29 @@ void GLGizmoCut3D::perform_cut(const Selection& selection)
|
||||
|
||||
wxCHECK_RET(instance_idx >= 0 && object_idx >= 0, "GLGizmoCut: Invalid object selection");
|
||||
|
||||
// m_cut_z is the distance from the bed. Subtract possible SLA elevation.
|
||||
const GLVolume* first_glvolume = selection.get_first_volume();
|
||||
const double object_cut_z = m_plane_center.z() - first_glvolume->get_sla_shift_z();
|
||||
|
||||
const Vec3d& instance_offset = wxGetApp().plater()->model().objects[object_idx]->instances[instance_idx]->get_offset();
|
||||
|
||||
Vec3d cut_center_offset = m_plane_center - instance_offset;
|
||||
cut_center_offset[Z] -= first_glvolume->get_sla_shift_z();
|
||||
|
||||
Plater* plater = wxGetApp().plater();
|
||||
ModelObject* mo = plater->model().objects[object_idx];
|
||||
if (!mo)
|
||||
return;
|
||||
|
||||
// m_cut_z is the distance from the bed. Subtract possible SLA elevation.
|
||||
const double sla_shift_z = selection.get_first_volume()->get_sla_shift_z();
|
||||
const double object_cut_z = m_plane_center.z() - sla_shift_z;
|
||||
|
||||
const Vec3d instance_offset = mo->instances[instance_idx]->get_offset();
|
||||
Vec3d cut_center_offset = m_plane_center - instance_offset;
|
||||
cut_center_offset[Z] -= sla_shift_z;
|
||||
|
||||
bool create_dowels_as_separate_object = false;
|
||||
if (0.0 < object_cut_z && can_perform_cut()) {
|
||||
ModelObject* mo = plater->model().objects[object_idx];
|
||||
if(!mo)
|
||||
return;
|
||||
|
||||
Vec3d rotation = Transformation(m_rotation_m).get_rotation();
|
||||
|
||||
bool create_dowels_as_separate_object = false;
|
||||
const bool has_connectors = !mo->cut_connectors.empty();
|
||||
{
|
||||
Plater::TakeSnapshot snapshot(plater, _L("Cut by Plane"));
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Cut by Plane"));
|
||||
// update connectors pos as offset of its center before cut performing
|
||||
if (has_connectors && m_connector_mode == CutConnectorMode::Manual) {
|
||||
m_selected.clear();
|
||||
|
||||
for (CutConnector& connector : mo->cut_connectors) {
|
||||
connector.rotation = rotation;
|
||||
|
||||
if (connector.attribs.type == CutConnectorType::Dowel) {
|
||||
if (connector.attribs.style == CutConnectorStyle::Prizm)
|
||||
connector.height *= 2;
|
||||
create_dowels_as_separate_object = true;
|
||||
}
|
||||
else {
|
||||
// culculate shift of the connector center regarding to the position on the cut plane
|
||||
Vec3d shifted_center = m_plane_center + Vec3d::UnitZ();
|
||||
rotate_vec3d_around_plane_center(shifted_center);
|
||||
Vec3d norm = (shifted_center - m_plane_center).normalized();
|
||||
connector.pos += norm * 0.5 * double(connector.height);
|
||||
}
|
||||
}
|
||||
mo->apply_cut_connectors(_u8L("Connector"));
|
||||
}
|
||||
apply_connectors_in_model(mo, has_connectors, create_dowels_as_separate_object);
|
||||
}
|
||||
|
||||
plater->cut(object_idx, instance_idx, cut_center_offset, rotation,
|
||||
plater->cut(object_idx, instance_idx, assemble_transform(cut_center_offset) * m_rotation_m,
|
||||
only_if(has_connectors ? true : m_keep_upper, ModelObjectCutAttribute::KeepUpper) |
|
||||
only_if(has_connectors ? true : m_keep_lower, ModelObjectCutAttribute::KeepLower) |
|
||||
only_if(m_place_on_cut_upper, ModelObjectCutAttribute::PlaceOnCutUpper) |
|
||||
@ -1925,7 +1923,7 @@ bool GLGizmoCut3D::add_connector(CutConnectors& connectors, const Vec2d& mouse_p
|
||||
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add connector"), UndoRedo::SnapshotType::GizmoAction);
|
||||
|
||||
connectors.emplace_back(hit, Transformation(m_rotation_m).get_rotation(),
|
||||
connectors.emplace_back(hit, m_rotation_m,
|
||||
float(m_connector_size) * 0.5f, float(m_connector_depth_ratio),
|
||||
float(m_connector_size_tolerance) * 0.01f, float(m_connector_depth_ratio_tolerance) * 0.01f,
|
||||
CutConnectorAttributes( CutConnectorType(m_connector_type),
|
||||
|
@ -25,7 +25,6 @@ class GLGizmoCut3D : public GLGizmoBase
|
||||
|
||||
// archived values
|
||||
Vec3d m_ar_plane_center { Vec3d::Zero() };
|
||||
Vec3d m_ar_rotations { Vec3d::Zero() };
|
||||
|
||||
Vec3d m_plane_center{ Vec3d::Zero() };
|
||||
// data to check position of the cut palne center on gizmo activation
|
||||
@ -216,6 +215,7 @@ private:
|
||||
void render_connectors();
|
||||
|
||||
bool can_perform_cut() const;
|
||||
void apply_connectors_in_model(ModelObject* mo, const bool has_connectors, bool &create_dowels_as_separate_object);
|
||||
bool cut_line_processing() const;
|
||||
void discard_cut_line_processing();
|
||||
|
||||
|
@ -1228,6 +1228,7 @@ void NotificationManager::UpdatedItemsInfoNotification::add_type(InfoItemType ty
|
||||
case InfoItemType::MmuSegmentation: text += format(_L_PLURAL("%1$d object was loaded with multimaterial painting.", "%1$d objects were loaded with multimaterial painting.",(*it).second), (*it).second) + "\n"; break;
|
||||
case InfoItemType::VariableLayerHeight: text += format(_L_PLURAL("%1$d object was loaded with variable layer height.", "%1$d objects were loaded with variable layer height.", (*it).second), (*it).second) + "\n"; break;
|
||||
case InfoItemType::Sinking: text += format(_L_PLURAL("%1$d object was loaded with partial sinking.", "%1$d objects were loaded with partial sinking.", (*it).second), (*it).second) + "\n"; break;
|
||||
case InfoItemType::Cut: text += format(_L_PLURAL("%1$d object was loaded as a part of cut object.", "%1$d objects were loaded as parts of cut object", (*it).second), (*it).second) + "\n"; break;
|
||||
default: BOOST_LOG_TRIVIAL(error) << "Unknown InfoItemType: " << (*it).second; break;
|
||||
}
|
||||
}
|
||||
|
@ -5967,7 +5967,7 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, ModelObjectCut
|
||||
selection.add_object((unsigned int)(last_id - i), i == 0);
|
||||
}
|
||||
|
||||
void Slic3r::GUI::Plater::cut(size_t obj_idx, size_t instance_idx, const Vec3d& cut_center, const Vec3d& cut_rotation, ModelObjectCutAttributes attributes)
|
||||
void Slic3r::GUI::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];
|
||||
@ -5977,7 +5977,7 @@ void Slic3r::GUI::Plater::cut(size_t obj_idx, size_t instance_idx, const Vec3d&
|
||||
this->suppress_snapshots();
|
||||
wxBusyCursor wait;
|
||||
|
||||
const auto new_objects = object->cut(instance_idx, cut_center, cut_rotation, attributes);
|
||||
const auto new_objects = object->cut(instance_idx, cut_matrix, attributes);
|
||||
|
||||
model().delete_object(obj_idx);
|
||||
sidebar().obj_list()->delete_object_from_list(obj_idx);
|
||||
|
@ -256,7 +256,7 @@ public:
|
||||
void toggle_layers_editing(bool enable);
|
||||
|
||||
void cut(size_t obj_idx, size_t instance_idx, coordf_t z, ModelObjectCutAttributes attributes);
|
||||
void cut(size_t obj_idx, size_t instance_idx, const Vec3d& cut_center, const Vec3d& cut_rotation, ModelObjectCutAttributes attributes);
|
||||
void cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes);
|
||||
|
||||
void export_gcode(bool prefer_removable);
|
||||
void export_stl_obj(bool extended = false, bool selection_only = false);
|
||||
|
Loading…
Reference in New Issue
Block a user