OrcaSlicer latest
OrcaSlicer
Loading...
Searching...
No Matches
Slic3r::CadDocument Class Reference

#include </home/runner/work/OrcaSlicer/OrcaSlicer/src/libslic3r/CAD/CadDocument.hpp>

Collaboration diagram for Slic3r::CadDocument:

Classes

struct  MateOption
struct  DatumAxis
struct  DatumCoordSys
struct  Interference
struct  Snapshot

Public Member Functions

int add_sketch (SketchShape shape, const SketchPlane &plane, double width, double height, double radius, const std::string &name)
int add_sketch_profile (const SketchProfile &profile, const SketchPlane &plane, const std::string &name)
int add_sketch_entities (const std::vector< SketchEntity > &entities, const SketchPlane &plane, const std::string &name, const std::vector< SketchEntityConstraintDef > &constraints={})
int add_project_edges (int source_body, const std::vector< int > &edge_ids, int face, const SketchPlane &plane, const std::string &name)
int project_edges_into_sketch (int sketch_feature, int source_body, const std::vector< int > &edge_ids, int face)
int add_bridge (int sketch_ref, int ent_a, int end_a, int ent_b, int end_b, const std::string &name)
bool solve_sketch_feature (int index)
int add_extrude (int sketch_ref, double distance, bool symmetric, BooleanMode mode, const std::string &name)
int add_extrude_entities (const std::vector< SketchEntity > &entities, const SketchPlane &plane, double distance, bool symmetric, BooleanMode mode, const std::string &name)
int add_extrude_face (int src_face, double distance, bool symmetric, BooleanMode mode, const std::string &name)
int add_fillet (double radius, FaceGroup faces, const std::string &name)
int add_fillet (double radius, int edge_id, const std::string &name)
int add_fillet (double radius, const std::vector< int > &edge_ids, const std::string &name)
int add_chamfer (double distance, FaceGroup faces, const std::string &name)
int add_chamfer (double distance, int edge_id, const std::string &name)
int add_chamfer (double distance, const std::vector< int > &edge_ids, const std::string &name)
int add_hole (double diameter, double depth, bool through, double x, double y, const SketchPlane &plane, const std::string &name)
int add_hole_styled (double diameter, double depth, bool through, double x, double y, const SketchPlane &plane, int style, double cbore_diameter, double cbore_depth, double csink_diameter, double csink_angle, const std::string &standard, const std::string &name)
int add_hole_standard (const std::string &designation, int style, bool through, double depth, double x, double y, const SketchPlane &plane, const std::string &name)
int add_thread (double radius, double pitch, double height, double depth, bool internal, double x, double y, const SketchPlane &plane, const std::string &name)
int add_revolve (int sketch_ref, double angle, int axis, bool flip, BooleanMode mode, const std::string &name)
int add_revolve_entities (const std::vector< SketchEntity > &entities, const SketchPlane &plane, double angle, int axis, bool flip, BooleanMode mode, const std::string &name)
int add_pattern (bool circular, int count, double spacing, int dir, double angle_deg, int target_body, const std::string &name)
int add_pattern_on_curve (int count, int curve_sketch, int curve_entity, int target, const std::string &name)
int add_sweep (int profile_sketch_ref, int path_sketch_ref, BooleanMode mode, const std::string &name)
int add_loft (const std::vector< int > &profile_refs, bool ruled, BooleanMode mode, const std::string &name)
int add_surface_loft (const std::vector< int > &profile_refs, bool ruled, const std::string &name)
int add_surface_fill (int sketch_ref, const std::string &name)
int add_shell (double thickness, int face, int target_body, const std::string &name)
int add_rib (int sketch_ref, int entity, double thickness, double depth, int target_body, const std::string &name)
int add_draft (double angle, int face, int target_body, const std::string &name)
int add_boolean (BooleanMode op, int target_body, int tool_body, bool keep_tool, double tolerance, int target_face, int tool_face, const std::string &name)
int add_cut (const SketchPlane &plane, double offset, bool flip, bool keep_upper, bool keep_lower, int target_body, const std::string &name)
int add_split_by_face (int target_body, int face_body, int face, bool keep_upper, bool keep_lower, const std::string &name)
int add_mirror (const SketchPlane &plane, int target_body, BooleanMode mode, const std::string &name)
int add_transform (int target_body, const Vec3d &translate, const Vec3d &axis, const Vec3d &pivot, double angle_deg, bool copy, const std::string &name)
int add_thicken (int target_body, int face, double thickness, bool flip, const std::string &name)
int add_thicken_surface (int target_body, double thickness, bool flip, const std::string &name)
int add_surface_offset (int target_body, double offset, const std::string &name)
int add_delete_face (int target_body, const std::vector< int > &faces, const std::string &name)
int add_surface_extrude (int sketch_ref, double distance, const std::string &name)
int add_surface_revolve (int sketch_ref, double angle_deg, int axis, const std::string &name)
int add_plane (int base, double offset, double angle_tilt, int axis, const std::string &name)
int add_axis (AxisType axis_type, const std::string &name)
int add_coordsys (CoordSysType type, const Vec3d &point, const std::string &name)
int add_mate (int kind, int cs_a, int cs_b, double offset, double angle_deg, bool flip, const std::string &name)
std::vector< MateOption > mate_options (int cs_a, int cs_b) const
int add_helix (const SketchPlane &plane, double radius, double pitch, double height, bool left_handed, double taper_deg, const std::string &name)
TopoDS_Wire build_helix_wire (const CadFeature &f, std::string &err) const
std::vector< std::pair< std::string, SketchPlane > > resolve_datum_planes () const
bool plane_of_face (int body_idx, int face_idx, SketchPlane &out) const
std::vector< DatumAxis > resolve_datum_axes () const
std::vector< DatumCoordSys > resolve_datum_coordsys () const
void clear ()
bool recompute ()
std::string serialize_recipe () const
bool deserialize_recipe (const std::string &blob)
bool export_step (const std::string &path, const std::vector< Transform3d > &body_xforms, std::string &err) const
GeometryEngine::MassProps body_mass_properties (int body_index) const
std::vector< Interference > check_interference (double min_volume=1e-6) const
void checkpoint ()
void abandon_checkpoint ()
bool can_undo () const
bool can_redo () const
size_t undo_depth () const
size_t redo_depth () const
bool undo ()
bool redo ()
bool remove_feature (int index)
bool move_feature (int index, int delta)
bool set_feature_enabled (int index, bool enabled)
bool replace_feature (int index, const CadFeature &edited)
bool replace_sketch_extrude (int sketch_idx, int extrude_idx, const CadFeature &edited)
int body_touching_sketch (int sketch_ref) const
bool preview (const CadFeature &candidate, TriangleMesh &out_mesh, std::string &err) const
bool preview (const CadFeature &candidate, TriangleMesh &out_mesh, std::vector< TriangleMesh > &out_body_meshes, std::string &err) const
std::vector< std::pair< int, int > > faces_made_by (int index) const

Static Public Member Functions

static bool is_bindable_field (const std::string &field)
static bool produces_body (CadFeatureType t)
static bool is_sheet_shape (const TopoDS_Shape &s)

Public Attributes

std::vector< CadFeature > features
std::map< std::string, std::string > variables
std::vector< CadBody > bodies
TopoDS_Shape body
TriangleMesh display_mesh
std::vector< TriangleMesh > display_body_meshes
std::vector< int > display_tri_face
std::vector< int > display_tri_body
std::string error
std::vector< std::pair< int, std::string > > mate_conflicts
Vec3d modeling_origin {Vec3d::Zero()}
bool origin_from_recipe {false}
bool auto_close_loops {true}
double linear_deflection {0.003}
double angular_deflection {0.5}
uint64_t topo_generation {1}

Static Public Attributes

static constexpr uint32_t ORCA_CAD_RECIPE_VERSION = 6

Private Member Functions

TopoDS_Wire build_sketch_wire (const CadFeature &sketch, bool closed_only=false) const
TopoDS_Face build_sketch_face (const CadFeature &sketch) const
void apply_feature (TopoDS_Shape &result, bool &have_body, const TopoDS_Shape &context, const CadFeature &f) const
void route_feature (std::vector< CadBody > &bodies, const CadFeature &f) const
void bind_expressions ()
void replay_feature (size_t fi, std::vector< CadBody > &built)
void apply_boolean (std::vector< CadBody > &bodies, const CadFeature &f) const
void apply_cut (std::vector< CadBody > &bodies, const CadFeature &f) const
void apply_mirror (std::vector< CadBody > &bodies, const CadFeature &f) const
void apply_transform (std::vector< CadBody > &bodies, const CadFeature &f) const
void apply_thicken (std::vector< CadBody > &bodies, const CadFeature &f) const
void apply_thicken_surface (std::vector< CadBody > &bodies, const CadFeature &f) const
void apply_surface_offset (std::vector< CadBody > &bodies, const CadFeature &f) const
void apply_project (const std::vector< CadBody > &bodies, CadFeature &f) const
void apply_mate (std::vector< CadBody > &bodies, const CadFeature &f) const
void detect_mate_conflicts ()

Static Private Member Functions

static DatumCoordSys datum_frame (const std::vector< CadBody > &bodies, const CadFeature &f)

Private Attributes

std::vector< Snapshot > m_undo
std::vector< Snapshot > m_redo

Static Private Attributes

static constexpr size_t k_undo_cap = 200

Detailed Description


Class Documentation

◆ Slic3r::CadDocument::MateOption

struct Slic3r::CadDocument::MateOption
Collaboration diagram for Slic3r::CadDocument::MateOption:
Class Members
int kind {0}
string reason
bool viable {true}

◆ Slic3r::CadDocument::DatumAxis

struct Slic3r::CadDocument::DatumAxis
Collaboration diagram for Slic3r::CadDocument::DatumAxis:
Class Members
Vec3d direction {0,0,1}
string error
string name
Vec3d origin {0,0,0}

◆ Slic3r::CadDocument::DatumCoordSys

struct Slic3r::CadDocument::DatumCoordSys
Collaboration diagram for Slic3r::CadDocument::DatumCoordSys:
Class Members
string error
string name
Vec3d origin {0,0,0}
Vec3d x {1,0,0}
Vec3d y {0,1,0}

◆ Slic3r::CadDocument::Interference

struct Slic3r::CadDocument::Interference
Class Members
int body_a {-1}
int body_b {-1}
double volume {0}

◆ Slic3r::CadDocument::Snapshot

struct Slic3r::CadDocument::Snapshot
Collaboration diagram for Slic3r::CadDocument::Snapshot:
Class Members
vector< CadFeature > features
map< string, string > variables

Member Function Documentation

◆ abandon_checkpoint()

void Slic3r::CadDocument::abandon_checkpoint ( )
2057{
2058 if (!m_undo.empty())
2059 m_undo.pop_back();
2060}
std::vector< Snapshot > m_undo
Definition CadDocument.hpp:913

References m_undo.

◆ add_axis()

int Slic3r::CadDocument::add_axis ( AxisType axis_type,
const std::string & name )
1463{
1464 CadFeature f;
1465 f.type = CadFeatureType::Axis;
1466 f.name = name;
1467 f.axis_type = axis_type_;
1468 features.push_back(f);
1469 return int(features.size()) - 1;
1470}
std::vector< CadFeature > features
Definition CadDocument.hpp:539
static double f(double x, double z_sin, double z_cos, bool vertical, bool flip)
Definition FillGyroid.cpp:124
@ Axis
Definition CadDocument.hpp:26

References Slic3r::Axis, Slic3r::f(), and features.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_axis().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_boolean()

int Slic3r::CadDocument::add_boolean ( BooleanMode op,
int target_body,
int tool_body,
bool keep_tool,
double tolerance,
int target_face,
int tool_face,
const std::string & name )
1266{
1267 CadFeature f;
1269 f.name = name;
1270 f.mode = op;
1271 f.target_body = target_body;
1272 f.bool_tool_body = tool_body;
1273 f.bool_keep_tool = keep_tool;
1274 f.bool_tolerance = tolerance;
1275 f.bool_target_face = target_face;
1276 f.bool_tool_face = tool_face;
1277 features.push_back(f);
1278 return int(features.size()) - 1;
1279}
@ Boolean
Definition CadDocument.hpp:26

References Slic3r::Boolean, Slic3r::f(), and features.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_boolean().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_bridge()

int Slic3r::CadDocument::add_bridge ( int sketch_ref,
int ent_a,
int end_a,
int ent_b,
int end_b,
const std::string & name )
1435{
1436 (void)name;
1437 if (sketch_ref < 0 || sketch_ref >= int(features.size())
1438 || features[sketch_ref].type != CadFeatureType::Sketch)
1439 throw std::runtime_error("bridge: sketch_ref must refer to a Sketch feature");
1440 auto& ents = features[sketch_ref].entities;
1441 if (ent_a < 0 || ent_a >= int(ents.size()) || ent_b < 0 || ent_b >= int(ents.size()))
1442 throw std::runtime_error("bridge: entity index out of range in sketch");
1443 SketchEntity br = SketchEngine::make_bridge(ents[ent_a], end_a, ents[ent_b], end_b);
1444 ents.push_back(br);
1445 return int(ents.size()) - 1;
1446}
static SketchEntity make_bridge(const SketchEntity &a, int a_end, const SketchEntity &b, int b_end)
Definition SketchEngine.cpp:2081
@ Sketch
Definition CadDocument.hpp:26

References features, Slic3r::SketchEngine::make_bridge(), and Slic3r::Sketch.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_bridge().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_chamfer() [1/3]

int Slic3r::CadDocument::add_chamfer ( double distance,
const std::vector< int > & edge_ids,
const std::string & name )
997{
998 if (edge_ids.size() == 1) return add_chamfer(distance, edge_ids.front(), name);
999 CadFeature f;
1001 f.name = name;
1002 f.dressup_size = distance;
1003 f.dressup_edges = edge_ids;
1004 f.dressup_edge = edge_ids.empty() ? -1 : edge_ids.front();
1005 features.push_back(f);
1006 return int(features.size()) - 1;
1007}
int add_chamfer(double distance, FaceGroup faces, const std::string &name)
Definition CadDocument.cpp:974
float distance(SpiralVase::SpiralPoint a, SpiralVase::SpiralPoint b)
Definition SpiralVase.cpp:19
@ Chamfer
Definition CadDocument.hpp:26

References add_chamfer(), Slic3r::Chamfer, Slic3r::f(), and features.

Here is the call graph for this function:

◆ add_chamfer() [2/3]

int Slic3r::CadDocument::add_chamfer ( double distance,
FaceGroup faces,
const std::string & name )
975{
976 CadFeature f;
978 f.name = name;
979 f.dressup_size = distance;
980 f.face_group = faces;
981 features.push_back(f);
982 return int(features.size()) - 1;
983}

References Slic3r::Chamfer, Slic3r::f(), and features.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_chamfer(), and add_chamfer().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_chamfer() [3/3]

int Slic3r::CadDocument::add_chamfer ( double distance,
int edge_id,
const std::string & name )
986{
987 CadFeature f;
989 f.name = name;
990 f.dressup_size = distance;
991 f.dressup_edge = edge_id;
992 features.push_back(f);
993 return int(features.size()) - 1;
994}

References Slic3r::Chamfer, Slic3r::f(), and features.

Here is the call graph for this function:

◆ add_coordsys()

int Slic3r::CadDocument::add_coordsys ( CoordSysType type,
const Vec3d & point,
const std::string & name )
1473{
1474 CadFeature f;
1476 f.name = name;
1477 f.coordsys_type = type;
1478 f.coordsys_point = point;
1479 features.push_back(f);
1480 return int(features.size()) - 1;
1481}
@ CoordSys
Definition CadDocument.hpp:26

References Slic3r::CoordSys, Slic3r::f(), and features.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_coordsys().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_cut()

int Slic3r::CadDocument::add_cut ( const SketchPlane & plane,
double offset,
bool flip,
bool keep_upper,
bool keep_lower,
int target_body,
const std::string & name )
1284{
1285 CadFeature f;
1286 f.type = CadFeatureType::Cut;
1287 f.name = name;
1288 f.plane = plane;
1289 f.cut_offset = offset;
1290 f.cut_flip = flip;
1291 f.cut_keep_upper = keep_upper;
1292 f.cut_keep_lower = keep_lower;
1293 f.target_body = target_body;
1294 features.push_back(f);
1295 return int(features.size()) - 1;
1296}
Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, JoinType joinType, double miterLimit)
Definition ClipperUtils.cpp:582
@ Cut
Definition CadDocument.hpp:26

References Slic3r::Cut, Slic3r::f(), features, and Slic3r::offset().

Here is the call graph for this function:

◆ add_delete_face()

int Slic3r::CadDocument::add_delete_face ( int target_body,
const std::vector< int > & faces,
const std::string & name )
1241{
1242 CadFeature f;
1244 f.name = name;
1245 f.target_body = target_body;
1246 f.delete_faces = faces;
1247 features.push_back(f);
1248 return int(features.size()) - 1;
1249}
@ DeleteFace
Definition CadDocument.hpp:26

References Slic3r::DeleteFace, Slic3r::f(), and features.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_delete_face().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_draft()

int Slic3r::CadDocument::add_draft ( double angle,
int face,
int target_body,
const std::string & name )
1252{
1253 CadFeature f;
1254 f.type = CadFeatureType::Draft;
1255 f.name = name;
1256 f.draft_angle = angle;
1257 f.draft_face = face;
1258 f.target_body = target_body;
1259 features.push_back(f);
1260 return int(features.size()) - 1;
1261}
double angle(const Eigen::MatrixBase< Derived > &v1, const Eigen::MatrixBase< Derived2 > &v2)
Definition Point.hpp:138
@ Draft
Definition CadDocument.hpp:26

References Slic3r::angle(), Slic3r::Draft, Slic3r::f(), and features.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_draft().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_extrude()

int Slic3r::CadDocument::add_extrude ( int sketch_ref,
double distance,
bool symmetric,
BooleanMode mode,
const std::string & name )
892{
893 CadFeature f;
895 f.name = name;
896 f.sketch_ref = sketch_ref;
897 f.distance = distance;
898 f.symmetric = symmetric;
899 f.mode = mode;
900 features.push_back(f);
901 return int(features.size()) - 1;
902}
@ Extrude
Definition CadDocument.hpp:26

References Slic3r::Extrude, Slic3r::f(), and features.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_extrude().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_extrude_entities()

int Slic3r::CadDocument::add_extrude_entities ( const std::vector< SketchEntity > & entities,
const SketchPlane & plane,
double distance,
bool symmetric,
BooleanMode mode,
const std::string & name )
907{
908 // Self-contained extrude of a single loop: the entity subset lives on the feature
909 // itself (sketch_ref = -1), so build_sketch_wire(f) uses f.entities directly. The
910 // source sketch stays a separate feature, so its other loops remain selectable.
911 CadFeature f;
913 f.name = name;
914 f.sketch_ref = -1;
915 f.entities = entities;
916 f.plane = plane;
917 f.distance = distance;
918 f.symmetric = symmetric;
919 f.mode = mode;
920 features.push_back(f);
921 return int(features.size()) - 1;
922}

References Slic3r::Extrude, Slic3r::f(), and features.

Here is the call graph for this function:

◆ add_extrude_face()

int Slic3r::CadDocument::add_extrude_face ( int src_face,
double distance,
bool symmetric,
BooleanMode mode,
const std::string & name )
926{
927 CadFeature f;
929 f.name = name;
930 f.sketch_ref = -1;
931 f.extrude_src_face = src_face;
932 f.distance = distance;
933 f.symmetric = symmetric;
934 f.mode = mode;
935 features.push_back(f);
936 return int(features.size()) - 1;
937}

References Slic3r::Extrude, Slic3r::f(), and features.

Here is the call graph for this function:

◆ add_fillet() [1/3]

int Slic3r::CadDocument::add_fillet ( double radius,
const std::vector< int > & edge_ids,
const std::string & name )
962{
963 if (edge_ids.size() == 1) return add_fillet(radius, edge_ids.front(), name);
964 CadFeature f;
966 f.name = name;
967 f.dressup_size = radius;
968 f.dressup_edges = edge_ids;
969 f.dressup_edge = edge_ids.empty() ? -1 : edge_ids.front();
970 features.push_back(f);
971 return int(features.size()) - 1;
972}
int add_fillet(double radius, FaceGroup faces, const std::string &name)
Definition CadDocument.cpp:939
@ Fillet
Definition CadDocument.hpp:26

References add_fillet(), Slic3r::f(), features, and Slic3r::Fillet.

Here is the call graph for this function:

◆ add_fillet() [2/3]

int Slic3r::CadDocument::add_fillet ( double radius,
FaceGroup faces,
const std::string & name )
940{
941 CadFeature f;
943 f.name = name;
944 f.dressup_size = radius;
945 f.face_group = faces;
946 features.push_back(f);
947 return int(features.size()) - 1;
948}

References Slic3r::f(), features, and Slic3r::Fillet.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_fillet(), and add_fillet().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_fillet() [3/3]

int Slic3r::CadDocument::add_fillet ( double radius,
int edge_id,
const std::string & name )
951{
952 CadFeature f;
954 f.name = name;
955 f.dressup_size = radius;
956 f.dressup_edge = edge_id;
957 features.push_back(f);
958 return int(features.size()) - 1;
959}

References Slic3r::f(), features, and Slic3r::Fillet.

Here is the call graph for this function:

◆ add_helix()

int Slic3r::CadDocument::add_helix ( const SketchPlane & plane,
double radius,
double pitch,
double height,
bool left_handed,
double taper_deg,
const std::string & name )
1578{
1579 CadFeature f;
1580 f.type = CadFeatureType::Helix;
1581 f.name = name;
1582 f.plane = plane;
1583 f.helix_radius = radius;
1584 f.helix_pitch = pitch;
1585 f.helix_height = height;
1586 f.helix_left_handed = left_handed;
1587 f.helix_taper_deg = taper_deg;
1588 features.push_back(f);
1589 return int(features.size()) - 1;
1590}
coord_t height(const BoundingBox &box)
Definition Arrange.cpp:1185
@ Helix
Definition CadDocument.hpp:26

References Slic3r::f(), features, and Slic3r::Helix.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_helix().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_hole()

int Slic3r::CadDocument::add_hole ( double diameter,
double depth,
bool through,
double x,
double y,
const SketchPlane & plane,
const std::string & name )
1012{
1013 CadFeature f;
1014 f.type = CadFeatureType::Hole;
1015 f.name = name;
1016 f.plane = plane;
1017 f.hole_diameter = diameter;
1018 f.hole_depth = depth;
1019 f.hole_through = through;
1020 f.hole_x = x;
1021 f.hole_y = y;
1022 features.push_back(f);
1023 return int(features.size()) - 1;
1024}
@ Hole
Definition CadDocument.hpp:26

References Slic3r::f(), features, and Slic3r::Hole.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_hole().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_hole_standard()

int Slic3r::CadDocument::add_hole_standard ( const std::string & designation,
int style,
bool through,
double depth,
double x,
double y,
const SketchPlane & plane,
const std::string & name )
1088{
1089 const HoleStdEntry* e = hole_std_lookup(designation);
1090 if (e == nullptr)
1091 throw std::runtime_error(format(_u8L("unknown hole standard \"%1%\""), designation));
1092 return add_hole_styled(e->clearance, depth, through, x, y, plane, style,
1093 e->cbore_d, e->cbore_depth, e->csink_d, e->csink_angle, e->desig, name);
1094}
#define style
Definition WipeTowerDialog.cpp:101
int add_hole_styled(double diameter, double depth, bool through, double x, double y, const SketchPlane &plane, int style, double cbore_diameter, double cbore_depth, double csink_diameter, double csink_angle, const std::string &standard, const std::string &name)
Definition CadDocument.cpp:1060
static const HoleStdEntry * hole_std_lookup(const std::string &desig)
Definition CadDocument.cpp:1049
std::string format(const char *fmt, TArgs &&... args)
Definition format.hpp:45
#define _u8L(s)
Definition I18N.hpp:11

References _u8L, add_hole_styled(), Slic3r::HoleStdEntry::cbore_d, Slic3r::HoleStdEntry::cbore_depth, Slic3r::HoleStdEntry::clearance, Slic3r::HoleStdEntry::csink_angle, Slic3r::HoleStdEntry::csink_d, Slic3r::HoleStdEntry::desig, Slic3r::format(), Slic3r::hole_std_lookup(), and style.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_hole_standard().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_hole_styled()

int Slic3r::CadDocument::add_hole_styled ( double diameter,
double depth,
bool through,
double x,
double y,
const SketchPlane & plane,
int style,
double cbore_diameter,
double cbore_depth,
double csink_diameter,
double csink_angle,
const std::string & standard,
const std::string & name )
1065{
1066 CadFeature f;
1067 f.type = CadFeatureType::Hole;
1068 f.name = name;
1069 f.plane = plane;
1070 f.hole_diameter = diameter;
1071 f.hole_depth = depth;
1072 f.hole_through = through;
1073 f.hole_x = x;
1074 f.hole_y = y;
1075 f.hole_style = style;
1076 f.hole_cbore_diameter = cbore_diameter;
1077 f.hole_cbore_depth = cbore_depth;
1078 f.hole_csink_diameter = csink_diameter;
1079 f.hole_csink_angle = csink_angle;
1080 f.hole_standard = standard;
1081 features.push_back(f);
1082 return int(features.size()) - 1;
1083}

References Slic3r::f(), features, Slic3r::Hole, and style.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_hole_styled(), and add_hole_standard().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_loft()

int Slic3r::CadDocument::add_loft ( const std::vector< int > & profile_refs,
bool ruled,
BooleanMode mode,
const std::string & name )
1164{
1165 CadFeature f;
1166 f.type = CadFeatureType::Loft;
1167 f.name = name;
1168 f.loft_profile_refs = profile_refs;
1169 f.loft_ruled = ruled;
1170 f.mode = mode;
1171 features.push_back(f);
1172 return int(features.size()) - 1;
1173}
@ Loft
Definition CadDocument.hpp:26

References Slic3r::f(), features, and Slic3r::Loft.

Here is the call graph for this function:

◆ add_mate()

int Slic3r::CadDocument::add_mate ( int kind,
int cs_a,
int cs_b,
double offset,
double angle_deg,
bool flip,
const std::string & name )
1485{
1486 CadFeature f;
1487 f.type = CadFeatureType::Mate;
1488 f.name = name;
1489 f.mate_kind = kind;
1490 f.mate_cs_a = cs_a;
1491 f.mate_cs_b = cs_b;
1492 f.mate_offset = offset;
1493 f.mate_angle = angle_deg;
1494 f.mate_flip = flip;
1495 features.push_back(f);
1496 return int(features.size()) - 1;
1497}
@ Mate
Definition CadDocument.hpp:26

References Slic3r::f(), features, Slic3r::Mate, and Slic3r::offset().

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_mate().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_mirror()

int Slic3r::CadDocument::add_mirror ( const SketchPlane & plane,
int target_body,
BooleanMode mode,
const std::string & name )
1315{
1316 CadFeature f;
1318 f.name = name;
1319 f.plane = plane;
1320 f.target_body = target_body;
1321 f.mode = mode;
1322 features.push_back(f);
1323 return int(features.size()) - 1;
1324}
@ Mirror
Definition CadDocument.hpp:26

References Slic3r::f(), features, and Slic3r::Mirror.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_mirror().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_pattern()

int Slic3r::CadDocument::add_pattern ( bool circular,
int count,
double spacing,
int dir,
double angle_deg,
int target_body,
const std::string & name )
1177{
1178 CadFeature f;
1180 f.name = name;
1181 f.pattern_circular = circular;
1182 f.pattern_count = count;
1183 f.pattern_spacing = spacing;
1184 f.pattern_dir = dir;
1185 f.pattern_angle = angle_deg;
1186 f.pattern_inclusive = true;
1187 f.target_body = target_body;
1188 // The pattern's plane — its linear axes and the circular pattern's pivot — defaults to the
1189 // XY plane through the MODELING origin (the bed centre), like every other default plane in
1190 // the tab. Left at SketchPlane::XY() it pivoted about the bed's corner.
1191 f.plane = SketchPlane::XY();
1192 f.plane.origin += modeling_origin;
1193 features.push_back(f);
1194 return int(features.size()) - 1;
1195}
static unsigned int count
Definition AuxiliaryDataViewModel.cpp:246
Vec3d modeling_origin
Definition CadDocument.hpp:572
@ Pattern
Definition CadDocument.hpp:26
static SketchPlane XY()
Definition SketchEngine.hpp:57

References count, Slic3r::f(), features, modeling_origin, Slic3r::Pattern, and Slic3r::SketchPlane::XY().

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_pattern().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_pattern_on_curve()

int Slic3r::CadDocument::add_pattern_on_curve ( int count,
int curve_sketch,
int curve_entity,
int target,
const std::string & name )
1199{
1200 CadFeature f;
1202 f.name = name;
1203 f.pattern_count = count;
1204 f.pattern_curve_sketch = curve_sketch;
1205 f.pattern_curve_entity = curve_entity;
1206 f.target_body = target;
1207 f.pattern_circular = false;
1208 features.push_back(f);
1209 return int(features.size()) - 1;
1210}

References count, Slic3r::f(), features, and Slic3r::Pattern.

Here is the call graph for this function:

◆ add_plane()

int Slic3r::CadDocument::add_plane ( int base,
double offset,
double angle_tilt,
int axis,
const std::string & name )
1450{
1451 CadFeature f;
1452 f.type = CadFeatureType::Plane;
1453 f.name = name;
1454 f.plane_base = base;
1455 f.plane_offset = offset;
1456 f.plane_angle_tilt = angle_tilt;
1457 f.plane_axis = axis;
1458 features.push_back(f);
1459 return int(features.size()) - 1;
1460}
@ Plane
Definition CadDocument.hpp:26

References Slic3r::f(), features, Slic3r::offset(), and Slic3r::Plane.

Here is the call graph for this function:

◆ add_project_edges()

int Slic3r::CadDocument::add_project_edges ( int source_body,
const std::vector< int > & edge_ids,
int face,
const SketchPlane & plane,
const std::string & name )
1390{
1391 CadFeature f;
1393 f.name = name;
1394 f.project_source_body = source_body;
1395 f.project_edges = edge_ids;
1396 f.project_face = face;
1397 f.plane = plane;
1398 features.push_back(f);
1399 return int(features.size()) - 1;
1400}
@ Project
Definition CadDocument.hpp:26

References Slic3r::f(), features, and Slic3r::Project.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_project().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_revolve()

int Slic3r::CadDocument::add_revolve ( int sketch_ref,
double angle,
int axis,
bool flip,
BooleanMode mode,
const std::string & name )
1118{
1119 CadFeature f;
1121 f.name = name;
1122 f.sketch_ref = sketch_ref;
1123 f.revolve_angle = angle;
1124 f.revolve_axis = axis;
1125 f.flip = flip;
1126 f.mode = mode;
1127 features.push_back(f);
1128 return int(features.size()) - 1;
1129}
@ Revolve
Definition CadDocument.hpp:26

References Slic3r::angle(), Slic3r::f(), features, and Slic3r::Revolve.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_revolve().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_revolve_entities()

int Slic3r::CadDocument::add_revolve_entities ( const std::vector< SketchEntity > & entities,
const SketchPlane & plane,
double angle,
int axis,
bool flip,
BooleanMode mode,
const std::string & name )
1134{
1135 CadFeature f;
1137 f.name = name;
1138 f.sketch_ref = -1;
1139 f.entities = entities;
1140 f.plane = plane;
1141 f.revolve_angle = angle;
1142 f.revolve_axis = axis;
1143 f.flip = flip;
1144 f.mode = mode;
1145 features.push_back(f);
1146 return int(features.size()) - 1;
1147}

References Slic3r::angle(), Slic3r::f(), features, and Slic3r::Revolve.

Here is the call graph for this function:

◆ add_rib()

int Slic3r::CadDocument::add_rib ( int sketch_ref,
int entity,
double thickness,
double depth,
int target_body,
const std::string & name )
1226{
1227 CadFeature f;
1228 f.type = CadFeatureType::Rib;
1229 f.name = name;
1230 f.rib_sketch_ref = sketch_ref;
1231 f.rib_entity = entity;
1232 f.rib_thickness = thickness;
1233 f.rib_depth = depth;
1234 f.target_body = target_body;
1235 features.push_back(f);
1236 return int(features.size()) - 1;
1237}
@ Rib
Definition CadDocument.hpp:26

References Slic3r::f(), features, and Slic3r::Rib.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_rib().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_shell()

int Slic3r::CadDocument::add_shell ( double thickness,
int face,
int target_body,
const std::string & name )
1213{
1214 CadFeature f;
1215 f.type = CadFeatureType::Shell;
1216 f.name = name;
1217 f.shell_thickness = thickness;
1218 f.shell_face = face;
1219 f.target_body = target_body;
1220 features.push_back(f);
1221 return int(features.size()) - 1;
1222}
@ Shell
Definition CadDocument.hpp:26

References Slic3r::f(), features, and Slic3r::Shell.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_shell().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_sketch()

int Slic3r::CadDocument::add_sketch ( SketchShape shape,
const SketchPlane & plane,
double width,
double height,
double radius,
const std::string & name )
572{
573 CadFeature f;
575 f.name = name;
576 f.shape = shape;
577 f.plane = plane;
578 f.width = width;
579 f.height = height;
580 f.radius = radius;
581 features.push_back(f);
582 return int(features.size()) - 1;
583}
coord_t width(const BoundingBox &box)
Definition Arrange.cpp:1184

References Slic3r::f(), features, and Slic3r::Sketch.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_extrude(), and Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_revolve().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_sketch_entities()

int Slic3r::CadDocument::add_sketch_entities ( const std::vector< SketchEntity > & entities,
const SketchPlane & plane,
const std::string & name,
const std::vector< SketchEntityConstraintDef > & constraints = {} )
600{
601 CadFeature f;
603 f.name = name;
604 f.plane = plane;
605 f.entities = entities;
606 f.entity_constraints = constraints; // driving dimensions, solved by solve_sketch_feature
607 features.push_back(f);
608 return int(features.size()) - 1;
609}

References Slic3r::f(), features, and Slic3r::Sketch.

Here is the call graph for this function:

◆ add_sketch_profile()

int Slic3r::CadDocument::add_sketch_profile ( const SketchProfile & profile,
const SketchPlane & plane,
const std::string & name )
587{
588 CadFeature f;
590 f.name = name;
591 f.plane = plane;
592 f.profile = profile;
593 features.push_back(f);
594 return int(features.size()) - 1;
595}

References Slic3r::f(), features, and Slic3r::Sketch.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_extrude(), and Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_revolve().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_split_by_face()

int Slic3r::CadDocument::add_split_by_face ( int target_body,
int face_body,
int face,
bool keep_upper,
bool keep_lower,
const std::string & name )
1300{
1301 CadFeature f;
1302 f.type = CadFeatureType::Cut;
1303 f.name = name;
1304 f.target_body = target_body;
1305 f.cut_face_body = face_body;
1306 f.cut_face = face;
1307 f.cut_keep_upper = keep_upper;
1308 f.cut_keep_lower = keep_lower;
1309 features.push_back(f);
1310 return int(features.size()) - 1;
1311}

References Slic3r::Cut, Slic3r::f(), and features.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_split().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_surface_extrude()

int Slic3r::CadDocument::add_surface_extrude ( int sketch_ref,
double distance,
const std::string & name )
4678{
4679 CadFeature f;
4681 f.name = name;
4682 f.sketch_ref = sketch_ref;
4683 f.distance = distance;
4684 f.mode = BooleanMode::New;
4685 features.push_back(f);
4686 return int(features.size()) - 1;
4687}
@ New
Definition CadDocument.hpp:31
@ SurfaceExtrude
Definition CadDocument.hpp:26

References Slic3r::f(), features, Slic3r::New, and Slic3r::SurfaceExtrude.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_extrude().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_surface_fill()

int Slic3r::CadDocument::add_surface_fill ( int sketch_ref,
const std::string & name )
4715{
4716 CadFeature f;
4718 f.name = name;
4719 f.sketch_ref = sketch_ref;
4720 f.mode = BooleanMode::New;
4721 features.push_back(f);
4722 return int(features.size()) - 1;
4723}
@ SurfaceFill
Definition CadDocument.hpp:26

References Slic3r::f(), features, Slic3r::New, and Slic3r::SurfaceFill.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_fill().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_surface_loft()

int Slic3r::CadDocument::add_surface_loft ( const std::vector< int > & profile_refs,
bool ruled,
const std::string & name )
4703{
4704 CadFeature f;
4706 f.name = name;
4707 f.loft_profile_refs = profile_refs;
4708 f.loft_ruled = ruled;
4709 f.mode = BooleanMode::New;
4710 features.push_back(f);
4711 return int(features.size()) - 1;
4712}
@ SurfaceLoft
Definition CadDocument.hpp:26

References Slic3r::f(), features, Slic3r::New, and Slic3r::SurfaceLoft.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_loft().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_surface_offset()

int Slic3r::CadDocument::add_surface_offset ( int target_body,
double offset,
const std::string & name )
1372{
1373 CadFeature f;
1375 f.name = name;
1376 f.target_body = target_body;
1377 f.plane_offset = offset;
1378 f.mode = BooleanMode::New;
1379 features.push_back(f);
1380 return int(features.size()) - 1;
1381}
@ SurfaceOffset
Definition CadDocument.hpp:26

References Slic3r::f(), features, Slic3r::New, Slic3r::offset(), and Slic3r::SurfaceOffset.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_offset().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_surface_revolve()

int Slic3r::CadDocument::add_surface_revolve ( int sketch_ref,
double angle_deg,
int axis,
const std::string & name )
4690{
4691 CadFeature f;
4693 f.name = name;
4694 f.sketch_ref = sketch_ref;
4695 f.revolve_angle = angle_deg;
4696 f.revolve_axis = axis;
4697 f.mode = BooleanMode::New;
4698 features.push_back(f);
4699 return int(features.size()) - 1;
4700}
@ SurfaceRevolve
Definition CadDocument.hpp:26

References Slic3r::f(), features, Slic3r::New, and Slic3r::SurfaceRevolve.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_revolve().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_sweep()

int Slic3r::CadDocument::add_sweep ( int profile_sketch_ref,
int path_sketch_ref,
BooleanMode mode,
const std::string & name )
1151{
1152 CadFeature f;
1153 f.type = CadFeatureType::Sweep;
1154 f.name = name;
1155 f.sketch_ref = profile_sketch_ref;
1156 f.sweep_path_ref = path_sketch_ref;
1157 f.mode = mode;
1158 features.push_back(f);
1159 return int(features.size()) - 1;
1160}
@ Sweep
Definition CadDocument.hpp:26

References Slic3r::f(), features, and Slic3r::Sweep.

Here is the call graph for this function:

◆ add_thicken()

int Slic3r::CadDocument::add_thicken ( int target_body,
int face,
double thickness,
bool flip,
const std::string & name )
1345{
1346 CadFeature f;
1348 f.name = name;
1349 f.target_body = target_body;
1350 f.thicken_face = face;
1351 f.thicken_thickness = thickness;
1352 f.thicken_flip = flip;
1353 features.push_back(f);
1354 return int(features.size()) - 1;
1355}
@ Thicken
Definition CadDocument.hpp:26

References Slic3r::f(), features, and Slic3r::Thicken.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_thicken().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_thicken_surface()

int Slic3r::CadDocument::add_thicken_surface ( int target_body,
double thickness,
bool flip,
const std::string & name )
1359{
1360 CadFeature f;
1362 f.name = name;
1363 f.target_body = target_body;
1364 f.thicken_thickness = thickness;
1365 f.thicken_flip = flip;
1366 f.mode = BooleanMode::New;
1367 features.push_back(f);
1368 return int(features.size()) - 1;
1369}
@ ThickenSurface
Definition CadDocument.hpp:26

References Slic3r::f(), features, Slic3r::New, and Slic3r::ThickenSurface.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_thicken_surface().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ add_thread()

int Slic3r::CadDocument::add_thread ( double radius,
double pitch,
double height,
double depth,
bool internal,
double x,
double y,
const SketchPlane & plane,
const std::string & name )
1099{
1100 CadFeature f;
1102 f.name = name;
1103 f.plane = plane;
1104 f.thread_radius = radius;
1105 f.thread_pitch = pitch;
1106 f.thread_height = height;
1107 f.thread_depth = depth;
1108 f.thread_internal = internal;
1109 f.thread_x = x;
1110 f.thread_y = y;
1111 f.thread_major_nominal = true; // `radius` is the major (nominal) radius
1112 features.push_back(f);
1113 return int(features.size()) - 1;
1114}
@ Thread
Definition CadDocument.hpp:26

References Slic3r::f(), features, and Slic3r::Thread.

Here is the call graph for this function:

◆ add_transform()

int Slic3r::CadDocument::add_transform ( int target_body,
const Vec3d & translate,
const Vec3d & axis,
const Vec3d & pivot,
double angle_deg,
bool copy,
const std::string & name )
1329{
1330 CadFeature f;
1332 f.name = name;
1333 f.target_body = target_body;
1334 f.xf_translate = translate;
1335 f.xf_axis = axis;
1336 f.xf_pivot = pivot;
1337 f.xf_angle_deg = angle_deg;
1338 f.xf_copy = copy;
1339 features.push_back(f);
1340 return int(features.size()) - 1;
1341}
void translate(ExPolygonsWithIds &e, const Point &p)
Definition Emboss.cpp:1368
@ Transform
Definition CadDocument.hpp:26

References Slic3r::f(), features, Slic3r::Transform, and Slic3r::translate().

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_transform().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ apply_boolean()

void Slic3r::CadDocument::apply_boolean ( std::vector< CadBody > & bodies,
const CadFeature & f ) const
private
3241{
3242 const int nb = int(bodies.size());
3243 const int tgt = pick_body(f.target_body, nb);
3244 const int tool = f.bool_tool_body == -1 ? -1 : pick_body(f.bool_tool_body, nb);
3245 // Same rule as Cut, Mirror and Transform: an operation that cannot run says why, rather
3246 // than leaving the bodies as they were and reporting success.
3247 if (tgt < 0) throw std::runtime_error(_u8L("boolean: no target body"));
3248 if (tool < 0) throw std::runtime_error(_u8L("boolean: no tool body — pick a second body"));
3249 if (tgt == tool) throw std::runtime_error(_u8L("boolean: the target and the tool are the same body"));
3250 const TopoDS_Shape A = bodies[tgt].shape; // target survives
3251 const TopoDS_Shape B = bodies[tool].shape; // tool, consumed unless kept
3252 if (A.IsNull() || B.IsNull()) throw std::runtime_error(_u8L("boolean: a body has no geometry"));
3253
3254 NCollection_List<TopoDS_Shape> args, tools;
3255 args.Append(A);
3256 tools.Append(B);
3257 auto run = [&](BRepAlgoAPI_BooleanOperation& bop) -> TopoDS_Shape {
3258 bop.SetArguments(args);
3259 bop.SetTools(tools);
3260 if (f.bool_tolerance > 0.0) bop.SetFuzzyValue(f.bool_tolerance); // OCCT fuzzy: merge near-coincident faces
3261 bop.Build();
3262 if (!bop.IsDone()) throw std::runtime_error(_u8L("boolean operation failed"));
3263 return bop.Shape();
3264 };
3265 TopoDS_Shape result;
3266 switch (f.mode) {
3267 case BooleanMode::Add: { BRepAlgoAPI_Fuse op; result = run(op); break; } // union
3268 case BooleanMode::Cut: { BRepAlgoAPI_Cut op; result = run(op); break; } // target - tool
3269 case BooleanMode::Intersect: { BRepAlgoAPI_Common op; result = run(op); break; } // overlap
3270 default: throw std::runtime_error("boolean: choose Union, Subtract or Intersect"); // New means nothing between two bodies
3271 }
3272 if (result.IsNull()) throw std::runtime_error(_u8L("boolean produced an empty shape"));
3273
3274 bodies[tgt].shape = result;
3275 if (!f.bool_keep_tool) bodies.erase(bodies.begin() + tool); // consume the tool body
3276}
std::vector< CadBody > bodies
Definition CadDocument.hpp:552
AppActionRunResult run(const std::string &key, const std::string &param={})
Definition NativeCommands.cpp:668
static int pick_body(int ref, int nb)
Definition CadDocument.cpp:2090
@ Intersect
Definition CadDocument.hpp:31
@ Cut
Definition CadDocument.hpp:31
@ Add
Definition CadDocument.hpp:31

References _u8L, Slic3r::Add, bodies, Slic3r::Cut, Slic3r::f(), Slic3r::Intersect, and Slic3r::pick_body().

Referenced by route_feature().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ apply_cut()

void Slic3r::CadDocument::apply_cut ( std::vector< CadBody > & bodies,
const CadFeature & f ) const
private
3279{
3280 const int nb = int(bodies.size());
3281 if (nb == 0) throw std::runtime_error(_u8L("cut: no target body"));
3282 const int tgt = pick_body(f.target_body, nb);
3283 if (tgt < 0 || bodies[tgt].shape.IsNull()) throw std::runtime_error(_u8L("cut: no target body"));
3284
3285 if (!f.cut_keep_upper && !f.cut_keep_lower)
3286 throw std::runtime_error(_u8L("cut keeps nothing"));
3287
3288 SketchPlane cp;
3289 if (f.cut_face >= 0) {
3290 const int fb = f.cut_face_body == -1 ? tgt : pick_body(f.cut_face_body, nb);
3291 if (bodies[fb].shape.IsNull()) throw std::runtime_error(_u8L("cut: face body is empty"));
3292 TopoDS_Face fc = GeometryEngine::face_by_index(bodies[fb].shape, f.cut_face);
3293 if (fc.IsNull()) throw std::runtime_error(_u8L("cut: face not found"));
3294 cp = SketchPlane::from_face(fc);
3295 } else {
3296 cp = f.plane;
3297 }
3298 cp.origin += cp.normal * f.cut_offset;
3299 if (f.cut_flip) cp.normal = -cp.normal;
3300
3301 // Build a large square wire in the cut plane, centered at plane origin.
3302 const double L = 1.0e5;
3303 Vec3d x = cp.x_axis * L;
3304 Vec3d y = cp.y_axis * L;
3305 Vec3d o = cp.origin;
3306 auto p = [&](double sx, double sy) {
3307 Vec3d v = o + x * sx + y * sy;
3308 return gp_Pnt(v.x(), v.y(), v.z());
3309 };
3310 BRepBuilderAPI_MakePolygon poly;
3311 poly.Add(p( 1, 1));
3312 poly.Add(p( 1, -1));
3313 poly.Add(p(-1, -1));
3314 poly.Add(p(-1, 1));
3315 poly.Close();
3316 if (!poly.IsDone()) throw std::runtime_error(_u8L("cut: failed to build cut wire"));
3317 TopoDS_Wire wire = poly.Wire();
3318
3319 TopoDS_Shape upper_piece, lower_piece;
3320 const TopoDS_Shape& target = bodies[tgt].shape;
3321
3322 if (f.cut_keep_upper) {
3323 TopoDS_Shape upper_tool = SketchEngine::make_extrude(wire, cp, L, false, 0.0);
3324 BRepAlgoAPI_Common common(target, upper_tool);
3325 if (!common.IsDone()) throw std::runtime_error(_u8L("cut operation failed"));
3326 upper_piece = common.Shape();
3327 }
3328
3329 if (f.cut_keep_lower) {
3330 SketchPlane lp = cp;
3331 lp.normal = -lp.normal;
3332 TopoDS_Shape lower_tool = SketchEngine::make_extrude(wire, lp, L, false, 0.0);
3333 BRepAlgoAPI_Common common(target, lower_tool);
3334 if (!common.IsDone()) throw std::runtime_error(_u8L("cut operation failed"));
3335 lower_piece = common.Shape();
3336 }
3337
3338 if (f.cut_keep_upper && f.cut_keep_lower) {
3339 bodies[tgt].shape = upper_piece;
3340 bodies.insert(bodies.begin() + tgt + 1,
3341 CadBody{ lower_piece, bodies[tgt].name + " (2)" });
3342 } else if (f.cut_keep_upper) {
3343 bodies[tgt].shape = upper_piece;
3344 } else {
3345 bodies[tgt].shape = lower_piece;
3346 }
3347}
#define L(s)
Definition ExtrusionEntity.cpp:25
static TopoDS_Face face_by_index(const TopoDS_Shape &shape, int index)
Definition GeometryEngine.cpp:534
static TopoDS_Shape make_extrude(const TopoDS_Wire &wire, const SketchPlane &plane, double length, bool symmetric=false, double taper_deg=0.0)
Definition SketchEngine.cpp:179
void poly(std::vector< std::pair< Vec2d, Vec2d > > &out, std::initializer_list< Vec2d > p)
Definition DesignSketchTool.cpp:7751
Eigen::Matrix< double, 3, 1, Eigen::DontAlign > Vec3d
Definition Point.hpp:68
static SketchPlane from_face(const TopoDS_Face &face)
Definition SketchEngine.cpp:85

References _u8L, bodies, Slic3r::f(), Slic3r::GeometryEngine::face_by_index(), Slic3r::SketchPlane::from_face(), L, Slic3r::SketchEngine::make_extrude(), Slic3r::SketchPlane::normal, Slic3r::SketchPlane::origin, Slic3r::pick_body(), Slic3r::SketchPlane::x_axis, and Slic3r::SketchPlane::y_axis.

Referenced by route_feature().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ apply_feature()

void Slic3r::CadDocument::apply_feature ( TopoDS_Shape & result,
bool & have_body,
const TopoDS_Shape & context,
const CadFeature & f ) const
private
2578{
2579 switch (f.type) {
2581 return; // sketches carry no solid; consumed by an extrude
2583 return; // edges-to-sketch: consumed downstream, no solid body
2585 return; // helical curve; consumed by Sweep as a path (like Sketch)
2587 return; // body-body boolean is handled in route_feature/apply_boolean, never here
2589 // Imported B-rep (STEP): rigid data carried on the feature, not built from
2590 // parameters — adopt it as the new body (New-path: result starts empty).
2591 result = f.imported_solid;
2592 have_body = !result.IsNull();
2593 return;
2595 const bool sym = (f.extrude_end == ExtrudeEnd::Symmetric);
2596 const double signed_d = f.flip ? -f.distance : f.distance;
2597 TopoDS_Shape tool;
2598 if (f.extrude_src_face >= 0) {
2599 // The source face is read from `context` (the owner body), which for a New
2600 // face-extrude is the source solid while `result` is the empty new body.
2601 if (context.IsNull()) throw std::runtime_error(_u8L("face-extrude needs a body"));
2602 TopoDS_Face srcf = GeometryEngine::face_by_index(context, f.extrude_src_face);
2603 if (srcf.IsNull()) throw std::runtime_error("face-extrude: invalid face id");
2604 SketchPlane fpl = SketchPlane::from_face(srcf);
2605 // from_face takes the surface's geometric normal and IGNORES the topological
2606 // face orientation, so for a REVERSED face (e.g. the top cap of an extruded
2607 // prism) it points INTO the solid -> a default push would fuse to nothing.
2608 // Orient it outward so push/pull grows away from the material (Onshape default);
2609 // the Flip checkbox (signed_d) still lets the user drive it inward for a cut.
2610 if (srcf.Orientation() == TopAbs_REVERSED) fpl.normal = -fpl.normal;
2611 tool = SketchEngine::make_extrude_face(srcf, fpl, signed_d, sym);
2612 } else {
2613 // Use the referenced sketch when sketch_ref is a valid Sketch index,
2614 // otherwise fall back to f's own inline sketch params (this makes a
2615 // single self-contained candidate previewable).
2616 const CadFeature& sk = (f.sketch_ref >= 0 && f.sketch_ref < int(features.size())
2617 && (features[f.sketch_ref].type == CadFeatureType::Sketch
2618 || features[f.sketch_ref].type == CadFeatureType::Project))
2619 ? features[f.sketch_ref] : f;
2620 // Imported rigid art (Text/SVG) extrudes via the faces-with-holes path
2621 // (with its placement transform applied); otherwise build the sketch's planar
2622 // region (outer loop + holes) from entities/profile/shape.
2623 tool = !sk.imported_regions.empty()
2625 transform_regions(sk.imported_regions, sk.import_offset,
2626 sk.import_scale_x, sk.import_scale_y),
2627 sk.plane,
2628 f.extrude_end == ExtrudeEnd::ThroughAll ? 1e5 : signed_d,
2629 f.extrude_end == ExtrudeEnd::ThroughAll ? true : (sym || f.extrude_end == ExtrudeEnd::TwoSided))
2630 : [&]() {
2631 const TopoDS_Face profile = build_sketch_face(sk);
2632 // A tapered extrude offsets the profile; a holed profile would have to
2633 // offset its inner loops the opposite way, which is not implemented yet.
2634 auto taper = [&](double L) -> TopoDS_Shape {
2635 // Count the loops off the face we ALREADY built, rather than rebuilding
2636 // every wire from the entities a second time to ask how many there are.
2637 // It is also the more honest test: what matters is whether the profile
2638 // being extruded has holes, not what the entity list could produce.
2639 int nloops = 0;
2640 for (TopExp_Explorer ex(profile, TopAbs_WIRE); ex.More(); ex.Next()) ++nloops;
2641 if (nloops > 1)
2642 throw std::runtime_error(_u8L("tapered extrude of a sketch with holes is not supported yet"));
2643 return SketchEngine::make_extrude_taper(build_sketch_wire(sk, true), sk.plane, L, f.taper_deg);
2644 };
2645 TopoDS_Shape t;
2646 switch (f.extrude_end) {
2647 case ExtrudeEnd::Blind:
2648 t = (std::abs(f.taper_deg) > 1e-6)
2649 ? taper(signed_d)
2650 : SketchEngine::make_extrude(profile, sk.plane, signed_d, false);
2651 break;
2652 case ExtrudeEnd::Symmetric: t = SketchEngine::make_extrude(profile, sk.plane, f.distance, true); break;
2653 case ExtrudeEnd::TwoSided: t = SketchEngine::make_extrude_two_sided(profile, sk.plane, f.distance, f.distance2); break;
2654 case ExtrudeEnd::ThroughAll: t = SketchEngine::make_extrude(profile, sk.plane, 1.0e5, true); break;
2655 case ExtrudeEnd::UpToFace: {
2656 const TopoDS_Face tgt = GeometryEngine::face_by_index(context, f.up_to_face);
2657 double L = signed_d;
2658 if (!tgt.IsNull()) {
2659 const Vec3d c = GeometryEngine::face_centroid_world(tgt);
2660 L = (c - sk.plane.origin).dot(sk.plane.normal);
2661 }
2662 t = (std::abs(f.taper_deg) > 1e-6)
2663 ? taper(L)
2664 : SketchEngine::make_extrude(profile, sk.plane, L, false);
2665 break;
2666 }
2667 case ExtrudeEnd::UpToVertex: {
2668 const double L = (f.up_to_point - sk.plane.origin).dot(sk.plane.normal);
2669 t = SketchEngine::make_extrude(profile, sk.plane, L, false);
2670 break;
2671 }
2672 default: t = SketchEngine::make_extrude(profile, sk.plane, signed_d, false); break;
2673 }
2674 return t;
2675 }();
2676 }
2677 // New / first-of-a-body => result becomes the tool (route_feature sends New extrudes
2678 // here with an empty result, so a face-extrude New builds a fresh body from the source
2679 // face in `context` without touching it). Add/Cut/Intersect boolean into `result`.
2680 if (!have_body || f.mode == BooleanMode::New) {
2681 result = tool;
2682 have_body = true;
2683 } else if (f.mode == BooleanMode::Add) {
2684 BRepAlgoAPI_Fuse fuse(result, tool);
2685 if (!fuse.IsDone()) throw std::runtime_error(_u8L("fuse failed"));
2686 result = fuse.Shape();
2687 } else if (f.mode == BooleanMode::Cut) {
2688 BRepAlgoAPI_Cut cut(result, tool);
2689 if (!cut.IsDone()) throw std::runtime_error(_u8L("cut failed"));
2690 result = cut.Shape();
2691 } else if (f.mode == BooleanMode::Intersect) {
2692 BRepAlgoAPI_Common common(result, tool);
2693 if (!common.IsDone()) throw std::runtime_error(_u8L("intersect failed"));
2694 result = common.Shape();
2695 }
2696 break;
2697 }
2698 case CadFeatureType::Revolve: {
2699 // Resolve the profile sketch like Extrude: referenced Sketch when valid,
2700 // else this feature's own inline entities/profile (self-contained candidate).
2701 const CadFeature& sk = (f.sketch_ref >= 0 && f.sketch_ref < int(features.size())
2702 && (features[f.sketch_ref].type == CadFeatureType::Sketch
2703 || features[f.sketch_ref].type == CadFeatureType::Project))
2704 ? features[f.sketch_ref] : f;
2705 TopoDS_Wire wire = build_sketch_wire(sk, true);
2706 const double ang = f.flip ? -f.revolve_angle : f.revolve_angle;
2707 TopoDS_Shape tool = SketchEngine::make_revolve(wire, revolve_axis_of(f, sk), ang);
2708 if (!have_body || f.mode == BooleanMode::New) {
2709 result = tool;
2710 have_body = true;
2711 } else if (f.mode == BooleanMode::Add) {
2712 BRepAlgoAPI_Fuse fuse(result, tool);
2713 if (!fuse.IsDone()) throw std::runtime_error(_u8L("fuse failed"));
2714 result = fuse.Shape();
2715 } else if (f.mode == BooleanMode::Cut) {
2716 BRepAlgoAPI_Cut cut(result, tool);
2717 if (!cut.IsDone()) throw std::runtime_error(_u8L("cut failed"));
2718 result = cut.Shape();
2719 } else if (f.mode == BooleanMode::Intersect) {
2720 BRepAlgoAPI_Common common(result, tool);
2721 if (!common.IsDone()) throw std::runtime_error(_u8L("intersect failed"));
2722 result = common.Shape();
2723 }
2724 break;
2725 }
2726 case CadFeatureType::SurfaceExtrude: {
2727 if (f.sketch_ref < 0 || f.sketch_ref >= int(features.size()))
2728 throw std::runtime_error("surface-extrude: bad sketch ref");
2729 const CadFeature& sk = features[f.sketch_ref];
2730 if (sk.type != CadFeatureType::Sketch && sk.type != CadFeatureType::Project)
2731 throw std::runtime_error("surface-extrude: ref is not a sketch");
2732 TopoDS_Wire wire = build_sketch_wire(sk);
2733 if (wire.IsNull()) throw std::runtime_error(_u8L("surface-extrude: empty profile"));
2734 gp_Dir nrm(sk.plane.normal.x(), sk.plane.normal.y(), sk.plane.normal.z());
2735 try {
2736 result = SketchEngine::make_prism(wire, gp_Vec(nrm.XYZ() * f.distance));
2737 } catch (const std::exception& e) {
2738 throw std::runtime_error(format(_u8L("surface-extrude: %1%"), *e.what() ? e.what() : _u8L("prism failed")));
2739 }
2740 have_body = true;
2741 break;
2742 }
2743 case CadFeatureType::SurfaceRevolve: {
2744 if (f.sketch_ref < 0 || f.sketch_ref >= int(features.size()))
2745 throw std::runtime_error("surface-revolve: bad sketch ref");
2746 const CadFeature& sk = features[f.sketch_ref];
2747 if (sk.type != CadFeatureType::Sketch && sk.type != CadFeatureType::Project)
2748 throw std::runtime_error("surface-revolve: ref is not a sketch");
2749 TopoDS_Wire wire = build_sketch_wire(sk);
2750 if (wire.IsNull()) throw std::runtime_error(_u8L("surface-revolve: empty profile"));
2751 gp_Ax1 axis = revolve_axis_of(f, sk);
2752 // Same angle rules as the solid Revolve (flip reverses it, and a negative sweep is a
2753 // positive one about the reversed axis — MakeRevol wants (0, 2 pi]); this surface
2754 // version used to ignore both.
2755 double ang = (f.flip ? -f.revolve_angle : f.revolve_angle) * M_PI / 180.0;
2756 if (ang < 0) { axis.Reverse(); ang = -ang; }
2757 BRepPrimAPI_MakeRevol rev(wire, axis, ang, false);
2758 if (!rev.IsDone()) throw std::runtime_error(_u8L("surface-revolve: revolve failed"));
2759 result = rev.Shape(); have_body = true;
2760 break;
2761 }
2762 case CadFeatureType::SurfaceLoft: {
2763 std::vector<TopoDS_Wire> profiles;
2764 for (int ref : f.loft_profile_refs) {
2765 if (ref < 0 || ref >= int(features.size())
2766 || (features[ref].type != CadFeatureType::Sketch
2767 && features[ref].type != CadFeatureType::Project))
2768 continue;
2769 profiles.push_back(build_sketch_wire(features[ref]));
2770 }
2771 if (profiles.size() < 2)
2772 throw std::runtime_error(_u8L("surface-loft needs 2+ valid profile sketches"));
2773 TopoDS_Shape skin = SketchEngine::make_loft_surface(profiles, f.loft_ruled);
2774 if (skin.IsNull()) throw std::runtime_error(_u8L("surface-loft: loft failed"));
2775 result = skin; have_body = true;
2776 break;
2777 }
2778 case CadFeatureType::SurfaceFill: {
2779 if (f.sketch_ref < 0 || f.sketch_ref >= int(features.size()))
2780 throw std::runtime_error("surface-fill: bad sketch ref");
2781 const CadFeature& sk = features[f.sketch_ref];
2782 if (sk.type != CadFeatureType::Sketch && sk.type != CadFeatureType::Project)
2783 throw std::runtime_error("surface-fill: ref is not a sketch");
2784 TopoDS_Wire wire = build_sketch_wire(sk);
2785 if (wire.IsNull()) throw std::runtime_error(_u8L("surface-fill: empty boundary"));
2786 BRepOffsetAPI_MakeFilling fill;
2787 int nedges = 0;
2788 for (TopExp_Explorer ex(wire, TopAbs_EDGE); ex.More(); ex.Next()) {
2789 fill.Add(TopoDS::Edge(ex.Current()), GeomAbs_C0);
2790 ++nedges;
2791 }
2792 if (nedges == 0) throw std::runtime_error(_u8L("surface-fill: boundary has no edges"));
2793 fill.Build();
2794 if (!fill.IsDone()) throw std::runtime_error(_u8L("surface-fill: fill failed"));
2795 TopoDS_Shape face = fill.Shape();
2796 if (face.IsNull()) throw std::runtime_error(_u8L("surface-fill: produced no geometry"));
2797 result = face; have_body = true;
2798 break;
2799 }
2800 case CadFeatureType::Sweep: {
2801 const CadFeature& sk = (f.sketch_ref >= 0 && f.sketch_ref < int(features.size())
2802 && (features[f.sketch_ref].type == CadFeatureType::Sketch
2803 || features[f.sketch_ref].type == CadFeatureType::Project))
2804 ? features[f.sketch_ref] : f;
2805 if (f.sweep_path_ref < 0 || f.sweep_path_ref >= int(features.size()))
2806 throw std::runtime_error(_u8L("sweep needs a valid path reference"));
2807 const CadFeature& path_feat = features[f.sweep_path_ref];
2808 TopoDS_Wire path;
2809 if (path_feat.type == CadFeatureType::Helix) {
2810 std::string helix_err;
2811 path = make_helix_spine(path_feat, helix_err);
2812 if (path.IsNull()) throw std::runtime_error(format(_u8L("helix path: %1%"), helix_err));
2813 } else if (path_feat.type == CadFeatureType::Sketch) {
2814 path = build_sketch_wire(path_feat);
2815 } else {
2816 throw std::runtime_error(_u8L("sweep path must be a sketch or helix"));
2817 }
2818 TopoDS_Wire profile = build_sketch_wire(sk, true);
2819 TopoDS_Shape tool = SketchEngine::make_sweep(profile, path);
2820 if (!have_body || f.mode == BooleanMode::New) {
2821 result = tool;
2822 have_body = true;
2823 } else if (f.mode == BooleanMode::Add) {
2824 BRepAlgoAPI_Fuse fuse(result, tool);
2825 if (!fuse.IsDone()) throw std::runtime_error(_u8L("fuse failed"));
2826 result = fuse.Shape();
2827 } else if (f.mode == BooleanMode::Cut) {
2828 BRepAlgoAPI_Cut cut(result, tool);
2829 if (!cut.IsDone()) throw std::runtime_error(_u8L("cut failed"));
2830 result = cut.Shape();
2831 } else if (f.mode == BooleanMode::Intersect) {
2832 BRepAlgoAPI_Common common(result, tool);
2833 if (!common.IsDone()) throw std::runtime_error(_u8L("intersect failed"));
2834 result = common.Shape();
2835 }
2836 break;
2837 }
2838 case CadFeatureType::Loft: {
2839 // Loft through 2+ closed profile Sketches, in recipe order. Each profile builds
2840 // a wire via build_sketch_wire (so it keeps its own plane); make_loft skins them.
2841 std::vector<TopoDS_Wire> profiles;
2842 for (int ref : f.loft_profile_refs) {
2843 if (ref < 0 || ref >= int(features.size())
2844 || (features[ref].type != CadFeatureType::Sketch
2845 && features[ref].type != CadFeatureType::Project))
2846 continue;
2847 profiles.push_back(build_sketch_wire(features[ref], true));
2848 }
2849 if (profiles.size() < 2)
2850 throw std::runtime_error(_u8L("loft needs 2+ valid profile sketches"));
2851 TopoDS_Shape tool = SketchEngine::make_loft(profiles, f.loft_ruled);
2852 if (!have_body || f.mode == BooleanMode::New) {
2853 result = tool;
2854 have_body = true;
2855 } else if (f.mode == BooleanMode::Add) {
2856 BRepAlgoAPI_Fuse fuse(result, tool);
2857 if (!fuse.IsDone()) throw std::runtime_error(_u8L("fuse failed"));
2858 result = fuse.Shape();
2859 } else if (f.mode == BooleanMode::Cut) {
2860 BRepAlgoAPI_Cut cut(result, tool);
2861 if (!cut.IsDone()) throw std::runtime_error(_u8L("cut failed"));
2862 result = cut.Shape();
2863 } else if (f.mode == BooleanMode::Intersect) {
2864 BRepAlgoAPI_Common common(result, tool);
2865 if (!common.IsDone()) throw std::runtime_error(_u8L("intersect failed"));
2866 result = common.Shape();
2867 }
2868 break;
2869 }
2870 case CadFeatureType::Pattern: {
2871 // Pattern along a curve: when pattern_curve_sketch >= 0 this mode takes
2872 // precedence over linear/circular. Copies are placed at equal-parameter points
2873 // along the referenced sketch entity, translated by (P_i - P_0).
2874 if (f.pattern_curve_sketch >= 0) {
2875 if (f.pattern_curve_sketch >= (int)features.size())
2876 throw std::runtime_error("pattern-on-curve: bad sketch ref");
2877 const CadFeature& gs = features[f.pattern_curve_sketch];
2878 if (gs.type != CadFeatureType::Sketch)
2879 throw std::runtime_error("pattern-on-curve: ref is not a sketch");
2880 if (f.pattern_curve_entity < 0 || f.pattern_curve_entity >= (int)gs.entities.size())
2881 throw std::runtime_error("pattern-on-curve: bad entity");
2882 if (!have_body) throw std::runtime_error(_u8L("pattern needs a body"));
2883 const SketchEntity& gc = gs.entities[f.pattern_curve_entity];
2884 const int n = std::max(1, f.pattern_count);
2885 const TopoDS_Shape seed = result;
2886 Vec3d p0 = gs.plane.to_world(sample_entity_2d(gc, 0.0));
2887 for (int i = 1; i < n; ++i) {
2888 double t = double(i) / double(n - 1 > 0 ? n - 1 : 1);
2889 Vec3d pi = gs.plane.to_world(sample_entity_2d(gc, t));
2890 Vec3d d = pi - p0;
2891 gp_Trsf trsf;
2892 trsf.SetTranslation(gp_Vec(d.x(), d.y(), d.z()));
2893 TopoDS_Shape copy = BRepBuilderAPI_Transform(seed, trsf, true).Shape();
2894 BRepAlgoAPI_Fuse fuse(result, copy);
2895 if (!fuse.IsDone()) throw std::runtime_error(_u8L("pattern fuse failed"));
2896 result = fuse.Shape();
2897 }
2898 break;
2899 }
2900 // Replicate the target body. Each copy is a rigid gp_Trsf of the seed, all
2901 // fused into one body. Linear: i*spacing along plane axis pattern_dir
2902 // (0=X,1=Y). Circular: i*(angle/count) about the plane normal through the
2903 // plane origin (so a seed offset from the origin orbits the axis).
2904 if (!have_body) throw std::runtime_error(_u8L("pattern needs a body"));
2905 const int n = std::max(1, f.pattern_count);
2906 const TopoDS_Shape seed = result;
2907 for (int i = 1; i < n; ++i) {
2908 gp_Trsf trsf;
2909 if (f.pattern_circular) {
2910 Vec3d o = f.plane.to_world(Vec2d(0, 0));
2911 gp_Ax1 ax(gp_Pnt(o.x(), o.y(), o.z()),
2912 gp_Dir(f.plane.normal.x(), f.plane.normal.y(), f.plane.normal.z()));
2913 const bool full = std::abs(std::abs(f.pattern_angle) - 360.0) < 1e-9;
2914 const double div = (f.pattern_inclusive && !full && n > 1) ? double(n - 1) : double(n);
2915 const double step = (f.pattern_angle * M_PI / 180.0) / div;
2916 trsf.SetRotation(ax, step * i);
2917 } else {
2918 const Vec3d& d = (f.pattern_dir == 1) ? f.plane.y_axis : f.plane.x_axis;
2919 trsf.SetTranslation(gp_Vec(d.x() * f.pattern_spacing * i,
2920 d.y() * f.pattern_spacing * i,
2921 d.z() * f.pattern_spacing * i));
2922 }
2923 TopoDS_Shape copy = BRepBuilderAPI_Transform(seed, trsf, true).Shape();
2924 BRepAlgoAPI_Fuse fuse(result, copy);
2925 if (!fuse.IsDone()) throw std::runtime_error(_u8L("pattern fuse failed"));
2926 result = fuse.Shape();
2927 }
2928 break;
2929 }
2930 case CadFeatureType::Rib: {
2931 if (!have_body) throw std::runtime_error(_u8L("rib needs a body"));
2932 if (f.rib_sketch_ref < 0 || f.rib_sketch_ref >= (int)features.size())
2933 throw std::runtime_error("rib: bad sketch ref");
2934 const CadFeature& sk = features[f.rib_sketch_ref];
2935 // Project counts as sketch-like here exactly as it does for Extrude, SurfaceExtrude
2936 // and SurfaceRevolve: it carries plane + Line entities, which is all a rib reads.
2937 // Ribbing along a projected body edge is otherwise unreachable.
2938 if (sk.type != CadFeatureType::Sketch && sk.type != CadFeatureType::Project)
2939 throw std::runtime_error("rib: ref is not a sketch");
2940 if (f.rib_entity < 0 || f.rib_entity >= (int)sk.entities.size())
2941 throw std::runtime_error("rib: bad entity");
2942 const SketchEntity& ln = sk.entities[f.rib_entity];
2943 if (ln.type != SketchEntity::Type::Line)
2944 throw std::runtime_error(_u8L("rib: entity must be a line")); // ponytail: line-only for now
2945 // Thin rectangle centred on the line, in the sketch plane: offset both endpoints by
2946 // +/-thickness/2 along the in-plane perpendicular of the line direction.
2947 const SketchPlane& pl = sk.plane;
2948 Vec2d a = ln.p0, b = ln.p1;
2949 Vec2d dir = (b - a); double L = dir.norm();
2950 if (L < 1e-9) throw std::runtime_error(_u8L("rib: degenerate line"));
2951 dir /= L;
2952 Vec2d perp(-dir.y(), dir.x());
2953 double h = f.rib_thickness * 0.5;
2954 Vec2d q0 = a + perp*h, q1 = b + perp*h, q2 = b - perp*h, q3 = a - perp*h;
2955 auto w3 = [&](const Vec2d& p){ Vec3d w = pl.to_world(p); return gp_Pnt(w.x(),w.y(),w.z()); };
2956 BRepBuilderAPI_MakePolygon poly(w3(q0), w3(q1), w3(q2), w3(q3), true);
2957 if (!poly.IsDone()) throw std::runtime_error(_u8L("rib: profile failed"));
2958 TopoDS_Shape wall = SketchEngine::make_extrude(poly.Wire(), pl, f.rib_depth, false, 0.0);
2959 if (wall.IsNull()) throw std::runtime_error(_u8L("rib: extrude failed"));
2960 BRepAlgoAPI_Fuse fuse(result, wall);
2961 if (!fuse.IsDone()) throw std::runtime_error(_u8L("rib: fuse failed"));
2962 result = fuse.Shape();
2963 break;
2964 }
2965 case CadFeatureType::Fillet:
2966 if (!have_body) throw std::runtime_error(_u8L("fillet needs a body"));
2967 if (f.dressup_edge >= 0 || !f.dressup_edges.empty())
2968 result = GeometryEngine::apply_fillet(result, f.dressup_size, f.dressup_edge_ids());
2969 else
2970 result = GeometryEngine::apply_fillet(result, f.dressup_size, f.face_group);
2971 break;
2972 case CadFeatureType::Chamfer:
2973 if (!have_body) throw std::runtime_error(_u8L("chamfer needs a body"));
2974 if (f.dressup_edge >= 0 || !f.dressup_edges.empty())
2975 result = GeometryEngine::apply_chamfer(result, f.dressup_size, f.dressup_edge_ids());
2976 else
2977 result = GeometryEngine::apply_chamfer(result, f.dressup_size, f.face_group);
2978 break;
2979 case CadFeatureType::Hole: {
2980 if (!have_body) throw std::runtime_error(_u8L("hole needs a body"));
2981 // Circle wire centered at the positioned point on the plane
2982 Vec3d c = f.plane.to_world(Vec2d(f.hole_x, f.hole_y));
2983 gp_Pnt o(c.x(), c.y(), c.z());
2984 gp_Dir n(f.plane.normal.x(), f.plane.normal.y(), f.plane.normal.z());
2985 gp_Circ circ(gp_Ax2(o, n), f.hole_diameter * 0.5);
2986 TopoDS_Edge e = BRepBuilderAPI_MakeEdge(circ).Edge();
2987 BRepBuilderAPI_MakeWire wm(e);
2988 if (!wm.IsDone()) throw std::runtime_error(_u8L("hole wire failed"));
2989 // Through = symmetric huge cut (passes fully through any body);
2990 // Blind = +normal extrude of hole_depth into the body.
2991 TopoDS_Shape tool = f.hole_through
2992 ? SketchEngine::make_extrude(wm.Wire(), f.plane, 1.0e5, true, 0.0)
2993 : SketchEngine::make_extrude(wm.Wire(), f.plane, f.hole_depth, false, 0.0);
2994 BRepAlgoAPI_Cut cut(result, tool);
2995 if (!cut.IsDone()) throw std::runtime_error(_u8L("hole cut failed"));
2996 result = cut.Shape();
2997
2998 // Enlarge the entry for a screw head (style 1 = counterbore, 2 = countersink).
2999 if (f.hole_style == 1 && f.hole_cbore_diameter > f.hole_diameter
3000 && f.hole_cbore_depth > 1e-6) {
3001 gp_Ax2 cbax(o, n);
3002 TopoDS_Shape cb = BRepPrimAPI_MakeCylinder(cbax, f.hole_cbore_diameter * 0.5,
3003 f.hole_cbore_depth).Shape();
3004 BRepAlgoAPI_Cut cbc(result, cb);
3005 if (cbc.IsDone() && !cbc.Shape().IsNull()) result = cbc.Shape();
3006 } else if (f.hole_style == 2 && f.hole_csink_diameter > f.hole_diameter) {
3007 const double Rmaj = f.hole_csink_diameter * 0.5;
3008 const double Rmin = f.hole_diameter * 0.5;
3009 const double half = f.hole_csink_angle * 0.5 * M_PI / 180.0;
3010 const double h = (Rmaj - Rmin) / std::tan(half);
3011 if (h > 1e-6) {
3012 gp_Ax2 csax(o, n);
3013 TopoDS_Shape cs = BRepPrimAPI_MakeCone(csax, Rmaj, Rmin, h).Shape();
3014 BRepAlgoAPI_Cut csc(result, cs);
3015 if (csc.IsDone() && !csc.Shape().IsNull()) result = csc.Shape();
3016 }
3017 }
3018 break;
3019 }
3020 case CadFeatureType::Thread: {
3021 // Reject degenerate parameters that make OCCT's helical sweep / boolean unstable (a tiny
3022 // pitch, depth >= half-pitch, an enormous turn count, depth eating the whole wall), and
3023 // SAY which: a thread that quietly did nothing read, for an internal one, as "the tool
3024 // does not intersect the body", and for an external one as nothing at all.
3025 {
3026 const double R = f.thread_radius, P = f.thread_pitch, H = f.thread_height, D = f.thread_depth;
3027 // ISO external thread depth is ~0.61*P, so allow up to 0.7*P (0.49 wrongly rejected
3028 // every real thread -> nothing rendered). Still bound it well under a full pitch.
3029 // R >= 0.5 (not >): M1, the smallest size in the standards table, is exactly 0.5.
3030 const char* why = R < 0.5 ? L("thread: the diameter must be at least 1 mm")
3031 : P <= 0.1 ? L("thread: the pitch must be more than 0.1 mm")
3032 : (D <= 1e-3 || D >= 0.7 * P) ? L("thread: the depth must be between 0 and 0.7 x the pitch")
3033 : D >= 0.45 * R ? L("thread: the depth is too large for this diameter")
3034 : H <= 0.5 * P ? L("thread: the length must be more than half a pitch")
3035 : H / P >= 400.0 ? L("thread: too many turns (length / pitch must stay under 400)")
3036 : nullptr;
3037 // Legacy recipes keep their old outcome (the body left as it was) so a project that
3038 // opened before still opens; every thread made now reports the reason instead.
3039 if (why != nullptr) {
3040 if (f.thread_major_nominal) throw std::runtime_error(Slic3r::I18N::translate(why));
3041 break;
3042 }
3043 }
3044 // Axis at the positioned point on the plane; +normal = thread rise.
3045 Vec3d c3 = f.plane.to_world(Vec2d(f.thread_x, f.thread_y));
3046 gp_Pnt c(c3.x(), c3.y(), c3.z());
3047 gp_Dir zdir(f.plane.normal.x(), f.plane.normal.y(), f.plane.normal.z());
3048 gp_Dir xdir(f.plane.x_axis.x(), f.plane.x_axis.y(), f.plane.x_axis.z());
3049 gp_Ax3 ax3(c, zdir, xdir);
3050 gp_Ax2 ax2(c, zdir, xdir);
3051 const double R = f.thread_radius, D = f.thread_depth;
3052 // Root the V CLEARLY inside the material (a real overlap, not a tangency) so the boolean
3053 // has clean intersections — near-coincident faces are what make OCCT's fuse/cut unstable.
3054 const double over = std::min(std::max(D, 0.25), R * 0.4);
3055
3056 // Sweep a V profile along the helix (guarded — never fatal). `helix_r` is the radius the
3057 // helix runs at: the profile is placed relative to it.
3058 auto sweep = [&](double helix_r, const TopoDS_Wire& prof, TopoDS_Shape& out) {
3059 try {
3060 TopoDS_Wire spine = make_helix_wire(ax3, helix_r, f.thread_pitch, f.thread_height);
3061 // MakePipeShell with a FIXED BINORMAL = cylinder axis keeps the V-profile's
3062 // orientation constant along the helix (axial edge always parallel to the axis).
3063 // The plain MakePipe used a Frenet frame that TWISTED the profile around the helix.
3064 BRepOffsetAPI_MakePipeShell pipe(spine);
3065 pipe.SetMode(zdir);
3066 pipe.Add(prof);
3067 pipe.Build();
3068 if (pipe.IsDone() && pipe.MakeSolid()) out = pipe.Shape();
3069 } catch (const Standard_Failure&) { // on OCCT >= 8 it derives from std::exception: first
3070 } catch (const std::exception&) {
3071 }
3072 return !out.IsNull();
3073 };
3074 auto cut_from = [](TopoDS_Shape& target, const TopoDS_Shape& tool) {
3075 BRepAlgoAPI_Cut cut(target, tool);
3076 if (cut.IsDone() && !cut.Shape().IsNull()) target = cut.Shape();
3077 };
3078
3079 if (f.thread_major_nominal) {
3080 if (f.thread_internal) {
3081 // Tapped hole: bore to the minor radius R - D, then cut the groove out to the
3082 // major radius R. The bore sits 10 um inside the minor radius so that, threading
3083 // an existing tap-drill hole, it does not coincide with that hole's wall.
3084 if (!have_body) throw std::runtime_error(_u8L("internal thread needs a body"));
3085 const double bore_r = R - D - 0.01;
3086 cut_from(result, BRepPrimAPI_MakeCylinder(ax2, bore_r, f.thread_height).Shape());
3087 TopoDS_Shape groove;
3088 if (sweep(R, make_thread_v(c, xdir, zdir, R - D - over, R, f.thread_pitch), groove))
3089 cut_from(result, groove);
3090 } else {
3091 // External thread: the crests are at R. A groove is cut INTO the rod down to
3092 // R - D; with no body yet, the rod is made first.
3093 if (!have_body || result.IsNull()) {
3094 result = BRepPrimAPI_MakeCylinder(ax2, R, f.thread_height).Shape();
3095 have_body = true;
3096 }
3097 TopoDS_Shape groove;
3098 if (sweep(R, make_thread_v(c, xdir, zdir, R + over, R - D, f.thread_pitch), groove))
3099 cut_from(result, groove);
3100 }
3101 break;
3102 }
3103
3104 // ---- legacy recipes (thread_major_nominal == false): built exactly as they always were.
3105 TopoDS_Shape ridge;
3106 const bool have_ridge = sweep(R, make_thread_profile(c, xdir, zdir, f.thread_radius,
3107 f.thread_pitch, f.thread_depth,
3108 f.thread_internal), ridge);
3109 if (f.thread_internal) {
3110 if (!have_body) throw std::runtime_error(_u8L("internal thread needs a body"));
3111 // Tapped bore: cut a clean pocket at radius - depth, then carve the helical groove
3112 // outward into its wall.
3113 const double bore_r = std::max(0.5, f.thread_radius - f.thread_depth);
3114 TopoDS_Shape bore = BRepPrimAPI_MakeCylinder(ax2, bore_r, f.thread_height).Shape();
3115 try {
3116 BRepAlgoAPI_Cut cut_bore(result, bore);
3117 if (cut_bore.IsDone() && !cut_bore.Shape().IsNull())
3118 result = cut_bore.Shape();
3119 } catch (const std::exception&) { /* keep existing bore */ }
3120 if (have_ridge) cut_from(result, ridge);
3121 } else {
3122 // External thread: FUSE the helical ridge onto the existing body, or onto a
3123 // standalone rod when there is none.
3124 if (have_body && !result.IsNull()) {
3125 if (have_ridge) {
3126 BRepAlgoAPI_Fuse fuse(result, ridge);
3127 if (fuse.IsDone() && !fuse.Shape().IsNull()) result = fuse.Shape();
3128 }
3129 } else {
3130 TopoDS_Shape rod = BRepPrimAPI_MakeCylinder(ax2, f.thread_radius,
3131 f.thread_height).Shape();
3132 if (have_ridge) {
3133 BRepAlgoAPI_Fuse fuse(rod, ridge);
3134 if (fuse.IsDone()) rod = fuse.Shape();
3135 }
3136 result = rod;
3137 have_body = true;
3138 }
3139 }
3140 break;
3141 }
3142 case CadFeatureType::Shell: {
3143 if (!have_body) throw std::runtime_error(_u8L("shell needs a body"));
3144 // Hollow the body to a wall thickness; the picked face (if any) is removed so the
3145 // shell is open there. MakeThickSolidByJoin with a NEGATIVE offset shells inward.
3146 NCollection_List<TopoDS_Shape> remove;
3147 if (f.shell_face >= 0) {
3148 TopoDS_Face fc = GeometryEngine::face_by_index(result, f.shell_face);
3149 if (!fc.IsNull()) remove.Append(fc);
3150 }
3151 BRepOffsetAPI_MakeThickSolid mts;
3152 mts.MakeThickSolidByJoin(result, remove, -std::abs(f.shell_thickness), 1.0e-3);
3153 mts.Build();
3154 if (!mts.IsDone()) throw std::runtime_error(_u8L("shell failed"));
3155 result = mts.Shape();
3156 if (result.IsNull()) throw std::runtime_error(_u8L("shell produced no geometry"));
3157 break;
3158 }
3159 case CadFeatureType::Draft: {
3160 if (!have_body) throw std::runtime_error(_u8L("draft needs a body"));
3161 if (f.draft_face < 0) throw std::runtime_error(_u8L("draft needs a picked face"));
3162 TopoDS_Face fc = GeometryEngine::face_by_index(result, f.draft_face);
3163 if (fc.IsNull()) throw std::runtime_error(_u8L("draft: face not found"));
3164 // Neutral plane = horizontal plane through the body's bbox bottom, pull direction +Z.
3165 // The face pivots about the line where it meets the neutral plane and tilts by the angle.
3166 // ponytail: neutral plane / pull direction fixed to world up; pick-based neutral plane
3167 // deferred (same as the datum-plane pick types, dgv).
3168 Bnd_Box bb; BRepBndLib::Add(result, bb);
3169 double xmin, ymin, zmin, xmax, ymax, zmax;
3170 bb.Get(xmin, ymin, zmin, xmax, ymax, zmax);
3171 gp_Dir pull(0, 0, 1);
3172 gp_Pln neutral(gp_Pnt(0, 0, zmin), pull);
3173 BRepOffsetAPI_DraftAngle draft(result);
3174 draft.Add(fc, pull, f.draft_angle * M_PI / 180.0, neutral);
3175 if (!draft.AddDone())
3176 throw std::runtime_error(_u8L("draft: face cannot be drafted (is it parallel to the base?)"));
3177 draft.Build();
3178 if (!draft.IsDone()) throw std::runtime_error(_u8L("draft failed"));
3179 result = draft.Shape();
3180 if (result.IsNull()) throw std::runtime_error(_u8L("draft produced no geometry"));
3181 break;
3182 }
3183 case CadFeatureType::DeleteFace: {
3184 if (!have_body) throw std::runtime_error(_u8L("delete_face needs a body"));
3185 if (f.delete_faces.empty()) throw std::runtime_error(_u8L("delete_face needs at least one face"));
3186 BRepAlgoAPI_Defeaturing df;
3187 df.SetShape(result);
3188 for (int fi : f.delete_faces) {
3189 TopoDS_Face fc = GeometryEngine::face_by_index(result, fi);
3190 if (fc.IsNull()) throw std::runtime_error(_u8L("delete_face: face not found"));
3191 df.AddFaceToRemove(fc);
3192 }
3193 df.Build();
3194 if (!df.IsDone()) throw std::runtime_error(_u8L("delete_face failed"));
3195 result = df.Shape();
3196 if (result.IsNull()) throw std::runtime_error(_u8L("delete_face produced no geometry"));
3197 break;
3198 }
3199 }
3200}
#define M_PI
Definition ExtrusionSimulator.cpp:31
static TopoDS_Shape make_extrude_regions(const std::vector< std::vector< std::vector< Vec2d > > > &regions, const SketchPlane &plane, double length, bool symmetric=false)
Definition SketchEngine.cpp:261
static TopoDS_Shape make_extrude_face(const TopoDS_Face &face, const SketchPlane &plane, double length, bool symmetric=false, double taper_deg=0.0)
Definition SketchEngine.cpp:254
std::string translate(const std::string &s)
Definition I18N.hpp:18
static gp_Ax1 revolve_axis_of(const CadFeature &f, const CadFeature &sk)
Definition CadDocument.cpp:2476
Eigen::Matrix< typename Derived::Scalar, 2, 1, Eigen::DontAlign > perp(const Eigen::MatrixBase< Derived > &v)
Definition Point.hpp:130
static TopoDS_Wire make_thread_v(const gp_Pnt &origin, const gp_Dir &xdir, const gp_Dir &zdir, double base_r, double apex_r, double pitch)
Definition CadDocument.cpp:193
ImportRegions transform_regions(const ImportRegions &src, const Vec2d &offset, double scale_x, double scale_y)
Definition SketchImport.cpp:148
static Vec2d sample_entity_2d(const SketchEntity &e, double t)
Definition CadDocument.cpp:543
@ pull
On-demand fetch via REST API (blocking call).
Definition IPrinterAgent.hpp:59
static TopoDS_Wire make_thread_profile(const gp_Pnt &origin, const gp_Dir &xdir, const gp_Dir &zdir, double radius, double pitch, double depth, bool internal)
Definition CadDocument.cpp:205
Eigen::Matrix< double, 2, 1, Eigen::DontAlign > Vec2d
Definition Point.hpp:67
T dot(const boost::geometry::model::d2::point_xy< T > &v1, const boost::geometry::model::d2::point_xy< T > &v2)
Definition ExtrusionSimulator.cpp:154
static TopoDS_Wire make_helix_spine(const CadFeature &f, std::string &err)
Definition CadDocument.cpp:135
@ Import
Definition CadDocument.hpp:26
@ TwoSided
Definition CadDocument.hpp:33
@ ThroughAll
Definition CadDocument.hpp:33
@ Symmetric
Definition CadDocument.hpp:33
static TopoDS_Wire make_helix_wire(const gp_Ax3 &axis, double radius, double pitch, double height)
Definition CadDocument.cpp:118
void step(Coord &crd, const Dir d, const long n=1)
Definition MarchingSquares.hpp:213

References _u8L, Slic3r::Add, Slic3r::GeometryEngine::apply_chamfer(), Slic3r::GeometryEngine::apply_fillet(), Slic3r::Blind, Slic3r::Boolean, build_sketch_face(), build_sketch_wire(), Slic3r::Chamfer, Slic3r::Cut, Slic3r::DeleteFace, Slic3r::dot(), Slic3r::Draft, Slic3r::CadFeature::entities, Slic3r::Extrude, Slic3r::f(), Slic3r::GeometryEngine::face_by_index(), Slic3r::GeometryEngine::face_centroid_world(), features, Slic3r::Fillet, Slic3r::format(), Slic3r::SketchPlane::from_face(), Slic3r::Helix, Slic3r::Hole, Slic3r::Import, Slic3r::CadFeature::import_offset, Slic3r::CadFeature::import_scale_x, Slic3r::CadFeature::import_scale_y, Slic3r::CadFeature::imported_regions, Slic3r::Intersect, L, Slic3r::SketchEntity::Line, Slic3r::Loft, M_PI, Slic3r::SketchEngine::make_extrude(), Slic3r::SketchEngine::make_extrude_face(), Slic3r::SketchEngine::make_extrude_regions(), Slic3r::SketchEngine::make_extrude_taper(), Slic3r::SketchEngine::make_extrude_two_sided(), Slic3r::make_helix_spine(), Slic3r::make_helix_wire(), Slic3r::SketchEngine::make_loft(), Slic3r::SketchEngine::make_loft_surface(), Slic3r::SketchEngine::make_prism(), Slic3r::SketchEngine::make_revolve(), Slic3r::SketchEngine::make_sweep(), Slic3r::make_thread_profile(), Slic3r::make_thread_v(), Slic3r::New, Slic3r::SketchPlane::normal, Slic3r::SketchPlane::origin, Slic3r::P, Slic3r::SketchEntity::p0, Slic3r::SketchEntity::p1, Slic3r::Pattern, Slic3r::perp(), Slic3r::CadFeature::plane, Slic3r::Project, Slic3r::pull, Slic3r::Revolve, Slic3r::revolve_axis_of(), Slic3r::Rib, Slic3r::sample_entity_2d(), Slic3r::Shell, Slic3r::Sketch, Slic3r::SurfaceExtrude, Slic3r::SurfaceFill, Slic3r::SurfaceLoft, Slic3r::SurfaceRevolve, Slic3r::Sweep, Slic3r::Symmetric, Slic3r::Thread, Slic3r::ThroughAll, Slic3r::SketchPlane::to_world(), Slic3r::transform_regions(), Slic3r::I18N::translate(), Slic3r::TwoSided, Slic3r::CadFeature::type, Slic3r::SketchEntity::type, Slic3r::UpToFace, and Slic3r::UpToVertex.

Referenced by route_feature().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ apply_mate()

void Slic3r::CadDocument::apply_mate ( std::vector< CadBody > & bodies,
const CadFeature & f ) const
private
3793{
3794 const int nc = int(features.size());
3795 if (f.mate_cs_a < 0 || f.mate_cs_a >= nc)
3796 throw std::runtime_error("mate: mate_cs_a out of range");
3797 if (f.mate_cs_b < 0 || f.mate_cs_b >= nc)
3798 throw std::runtime_error("mate: mate_cs_b out of range");
3799
3800 const CadFeature& fa = features[f.mate_cs_a];
3801 const CadFeature& fb = features[f.mate_cs_b];
3802 if (fa.type != CadFeatureType::CoordSys || !fa.enabled)
3803 throw std::runtime_error("mate: mate_cs_a is not a valid CoordSys feature");
3804 if (fb.type != CadFeatureType::CoordSys || !fb.enabled)
3805 throw std::runtime_error("mate: mate_cs_b is not a valid CoordSys feature");
3806
3807 CadDocument::DatumCoordSys A = datum_frame(bodies, fa);
3808 CadDocument::DatumCoordSys B = datum_frame(bodies, fb);
3809 if (!A.error.empty()) throw std::runtime_error(format(_u8L("mate: %1%"), A.error));
3810 if (!B.error.empty()) throw std::runtime_error(format(_u8L("mate: %1%"), B.error));
3811
3812 const int tgt_body = fb.coordsys_body;
3813 if (tgt_body < 0)
3814 throw std::runtime_error("mate: mate_cs_b has no associated body");
3815 if (tgt_body >= int(bodies.size()) || bodies[tgt_body].shape.IsNull())
3816 throw std::runtime_error("mate: target body out of range or null");
3817
3818 Vec3d xA(A.x), yA(A.y), oA(A.origin);
3819 Vec3d zA = xA.cross(yA).normalized();
3820 Vec3d xB(B.x), yB(B.y), oB(B.origin);
3821 Vec3d zB = xB.cross(yB).normalized();
3822
3823 auto make_4x4 = [&](const Vec3d& x, const Vec3d& y, const Vec3d& z, const Vec3d& o) {
3824 gp_Trsf T;
3825 T.SetValues(x.x(), y.x(), z.x(), o.x(),
3826 x.y(), y.y(), z.y(), o.y(),
3827 x.z(), y.z(), z.z(), o.z());
3828 return T;
3829 };
3830 gp_Trsf M_A = make_4x4(xA, yA, zA, oA);
3831 gp_Trsf M_B = make_4x4(xB, yB, zB, oB);
3832
3833 gp_Trsf F;
3834 if (f.mate_flip) {
3835 // Rx(pi): flip y→-y, z→-z
3836 F.SetValues(1, 0, 0, 0,
3837 0, -1, 0, 0,
3838 0, 0, -1, 0);
3839 }
3840
3841 if (f.mate_kind < 0 || f.mate_kind > 4)
3842 throw std::runtime_error("mate: unknown mate type " + std::to_string(f.mate_kind));
3843 gp_Trsf T;
3844 if (f.mate_kind == 0) {
3845 // Fastened: T = M_A * Rz(mate_angle) * Tz(mate_offset) * F * M_B^-1
3846 gp_Trsf Rz;
3847 Rz.SetRotation(gp_Ax1(gp_Pnt(0,0,0), gp_Dir(0,0,1)), f.mate_angle * M_PI / 180.0);
3848 gp_Trsf Tz;
3849 Tz.SetTranslation(gp_Vec(0, 0, f.mate_offset));
3850 gp_Trsf M_B_inv = M_B.Inverted();
3851 T = M_A * Rz * Tz * F * M_B_inv;
3852 } else {
3853 Vec3d z_target = f.mate_flip ? -zA : zA;
3854
3855 // Minimum-rotation helper: compute R that rotates zB onto z_target
3856 // about an axis through oB. Reused by Planar / Revolute / Cylindrical.
3857 auto make_z_align = [&](const Vec3d& zsrc, const Vec3d& zdst) -> gp_Trsf {
3858 double ddot = zsrc.dot(zdst);
3859 Vec3d rot_axis;
3860 double rot_angle = 0;
3861 if (ddot <= -0.9999) {
3862 Vec3d ref = (std::abs(zsrc.z()) < 0.9) ? Vec3d(0, 0, 1) : Vec3d(1, 0, 0);
3863 rot_axis = zsrc.cross(ref).normalized();
3864 rot_angle = M_PI;
3865 } else {
3866 rot_axis = zsrc.cross(zdst);
3867 if (rot_axis.squaredNorm() > 1e-18) {
3868 rot_axis.normalize();
3869 rot_angle = std::acos(std::max(-1.0, std::min(1.0, ddot)));
3870 }
3871 }
3872 gp_Trsf R;
3873 if (rot_angle > 1e-12) {
3874 R.SetRotation(gp_Ax1(gp_Pnt(oB.x(), oB.y(), oB.z()),
3875 gp_Dir(rot_axis.x(), rot_axis.y(), rot_axis.z())),
3876 rot_angle);
3877 }
3878 return R;
3879 };
3880
3881 if (f.mate_kind == 1 || f.mate_kind == 2 || f.mate_kind == 4) {
3882 // Planar (1) / Revolute (2) / Cylindrical (4):
3883 // all share the same minimum-rotation z-alignment.
3884 gp_Trsf R_align = make_z_align(zB, z_target);
3885
3886 gp_Trsf Rz_about_target;
3887 if (std::abs(f.mate_angle) > 1e-12) {
3888 Rz_about_target.SetRotation(
3889 gp_Ax1(gp_Pnt(oB.x(), oB.y(), oB.z()),
3890 gp_Dir(z_target.x(), z_target.y(), z_target.z())),
3891 f.mate_angle * M_PI / 180.0);
3892 }
3893 gp_Trsf R = Rz_about_target * R_align;
3894
3895 // Translation: depends on which DOFs are constrained
3896 Vec3d trans;
3897 if (f.mate_kind == 1) {
3898 // Planar: normal distance becomes mate_offset
3899 double d = (oB - oA).dot(zA);
3900 trans = zA * (f.mate_offset - d);
3901 } else if (f.mate_kind == 2) {
3902 // Revolute: full position on the axis line
3903 trans = (oA + zA * f.mate_offset) - oB;
3904 } else {
3905 // Cylindrical (4): fix perpendicular, preserve axial
3906 double axial = (oB - oA).dot(zA);
3907 trans = (oA + zA * (axial + f.mate_offset)) - oB;
3908 }
3909
3910 T.SetValues(1, 0, 0, trans.x(),
3911 0, 1, 0, trans.y(),
3912 0, 0, 1, trans.z());
3913 T = T * R;
3914 } else {
3915 // Slider (mate_kind == 3): full orientation alignment,
3916 // fix perpendicular position, preserve axial translation.
3917 Vec3d x_target = xA;
3918 Vec3d y_target = f.mate_flip ? -yA : yA;
3919
3920 // R = target * B^T (both bases orthonormal)
3921 double r11 = x_target.x() * xB.x() + y_target.x() * yB.x() + z_target.x() * zB.x();
3922 double r12 = x_target.x() * xB.y() + y_target.x() * yB.y() + z_target.x() * zB.y();
3923 double r13 = x_target.x() * xB.z() + y_target.x() * yB.z() + z_target.x() * zB.z();
3924 double r21 = x_target.y() * xB.x() + y_target.y() * yB.x() + z_target.y() * zB.x();
3925 double r22 = x_target.y() * xB.y() + y_target.y() * yB.y() + z_target.y() * zB.y();
3926 double r23 = x_target.y() * xB.z() + y_target.y() * yB.z() + z_target.y() * zB.z();
3927 double r31 = x_target.z() * xB.x() + y_target.z() * yB.x() + z_target.z() * zB.x();
3928 double r32 = x_target.z() * xB.y() + y_target.z() * yB.y() + z_target.z() * zB.y();
3929 double r33 = x_target.z() * xB.z() + y_target.z() * yB.z() + z_target.z() * zB.z();
3930
3931 // Build rotation about oB: R_full * p = R * (p - oB) + oB
3932 double tx = oB.x() - (r11 * oB.x() + r12 * oB.y() + r13 * oB.z());
3933 double ty = oB.y() - (r21 * oB.x() + r22 * oB.y() + r23 * oB.z());
3934 double tz = oB.z() - (r31 * oB.x() + r32 * oB.y() + r33 * oB.z());
3935 gp_Trsf R_full;
3936 R_full.SetValues(r11, r12, r13, tx,
3937 r21, r22, r23, ty,
3938 r31, r32, r33, tz);
3939
3940 double axial = (oB - oA).dot(zA);
3941 Vec3d trans = (oA + zA * (axial + f.mate_offset)) - oB;
3942
3943 T.SetValues(1, 0, 0, trans.x(),
3944 0, 1, 0, trans.y(),
3945 0, 0, 1, trans.z());
3946 T = T * R_full;
3947 }
3948 }
3949
3950 BRepBuilderAPI_Transform xform(bodies[tgt_body].shape, T, true /*copy*/);
3951 if (!xform.IsDone()) throw std::runtime_error(_u8L("mate: transform failed"));
3952 bodies[tgt_body].shape = xform.Shape();
3953}
static DatumCoordSys datum_frame(const std::vector< CadBody > &bodies, const CadFeature &f)
Definition CadDocument.cpp:1936
@ F
Definition libslic3r.h:145

References _u8L, bodies, Slic3r::CoordSys, Slic3r::CadFeature::coordsys_body, datum_frame(), Slic3r::dot(), Slic3r::CadFeature::enabled, Slic3r::CadDocument::DatumCoordSys::error, Slic3r::F, Slic3r::f(), features, Slic3r::format(), M_PI, Slic3r::CadDocument::DatumCoordSys::origin, Slic3r::CadFeature::type, Slic3r::CadDocument::DatumCoordSys::x, and Slic3r::CadDocument::DatumCoordSys::y.

Referenced by route_feature().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ apply_mirror()

void Slic3r::CadDocument::apply_mirror ( std::vector< CadBody > & bodies,
const CadFeature & f ) const
private
3350{
3351 const int nb = int(bodies.size());
3352 if (nb == 0) throw std::runtime_error(_u8L("mirror: no target body"));
3353 const int tgt = pick_body(f.target_body, nb);
3354 if (tgt < 0 || bodies[tgt].shape.IsNull()) throw std::runtime_error(_u8L("mirror: no target body"));
3355
3356 const TopoDS_Shape& src = bodies[tgt].shape;
3357
3358 gp_Trsf trsf;
3359 trsf.SetMirror(gp_Ax2(gp_Pnt(f.plane.origin.x(), f.plane.origin.y(), f.plane.origin.z()),
3360 gp_Dir(f.plane.normal.x(), f.plane.normal.y(), f.plane.normal.z())));
3361 BRepBuilderAPI_Transform xform(src, trsf, true /*copy*/);
3362 if (!xform.IsDone()) throw std::runtime_error(_u8L("mirror: transform failed"));
3363 TopoDS_Shape mirrored = xform.Shape();
3364
3365 // A mirror reverses orientation — verify the result has positive volume.
3366 {
3367 GProp_GProps props;
3368 BRepGProp::VolumeProperties(mirrored, props);
3369 if (props.Mass() <= 0.0) {
3370 // Flip orientation to get a valid forward solid.
3371 mirrored.Reverse();
3372 BRepGProp::VolumeProperties(mirrored, props);
3373 if (props.Mass() <= 0.0)
3374 throw std::runtime_error(_u8L("mirror: result has zero or negative volume"));
3375 }
3376 }
3377
3378 switch (f.mode) {
3379 case BooleanMode::Add: {
3380 BRepAlgoAPI_Fuse fuse(src, mirrored);
3381 if (!fuse.IsDone()) throw std::runtime_error(_u8L("mirror fuse failed"));
3382 bodies[tgt].shape = fuse.Shape();
3383 break;
3384 }
3385 case BooleanMode::New: {
3386 if (!f.mirror_keep_original)
3387 bodies.erase(bodies.begin() + tgt); // replace: the mirrored copy takes the source slot
3388 bodies.push_back({mirrored, f.name.empty() ? _u8L("Mirror") : f.name});
3389 break;
3390 }
3391 default:
3392 throw std::runtime_error("mirror: mode must be New or Add");
3393 }
3394}

References _u8L, Slic3r::Add, bodies, Slic3r::f(), Slic3r::New, and Slic3r::pick_body().

Referenced by route_feature().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ apply_project()

void Slic3r::CadDocument::apply_project ( const std::vector< CadBody > & bodies,
CadFeature & f ) const
private
3655{
3656 f.entities.clear();
3657 const int nb = int(bodies.size());
3658 if (nb == 0) throw std::runtime_error(_u8L("project: no source body"));
3659 const int src = pick_body(f.project_source_body, nb);
3660 if (src < 0 || bodies[src].shape.IsNull()) throw std::runtime_error(_u8L("project: source body is empty"));
3661 const TopoDS_Shape& shape = bodies[src].shape;
3662
3663 std::vector<TopoDS_Edge> edges;
3664 if (!f.project_edges.empty()) {
3665 for (int id : f.project_edges) {
3666 TopoDS_Edge e = GeometryEngine::edge_by_index(shape, id);
3667 if (e.IsNull()) throw std::runtime_error(_u8L("project: edge not found"));
3668 edges.push_back(e);
3669 }
3670 } else if (f.project_face >= 0) {
3671 TopoDS_Face fc = GeometryEngine::face_by_index(shape, f.project_face);
3672 if (fc.IsNull()) throw std::runtime_error(_u8L("project: face not found"));
3674 } else {
3675 // No face and no explicit selection means "all edges" — the state the Project card
3676 // starts in, and its label says so. Edges perpendicular to the target plane collapse
3677 // to a point when projected; they are dropped below rather than emitted as
3678 // zero-length lines.
3679 edges = GeometryEngine::edges_of(shape);
3680 }
3681 if (edges.empty()) throw std::runtime_error(_u8L("project: no edges to project"));
3682
3683 project_edges_to_entities(edges, f.plane, /*construction=*/false, f.entities);
3684
3685 if (f.entities.empty()) throw std::runtime_error(_u8L("project: produced no entities"));
3686}
static TopoDS_Edge edge_by_index(const TopoDS_Shape &shape, int index)
Definition GeometryEngine.cpp:717
static std::vector< TopoDS_Edge > edges_of_face(const TopoDS_Face &face)
Definition GeometryEngine.cpp:565
static std::vector< TopoDS_Edge > edges_of(const TopoDS_Shape &shape)
Definition GeometryEngine.cpp:554
static void project_edges_to_entities(const std::vector< TopoDS_Edge > &edges, const SketchPlane &plane, bool construction, std::vector< SketchEntity > &out)
Definition CadDocument.cpp:3585

References _u8L, bodies, Slic3r::GeometryEngine::edge_by_index(), Slic3r::GeometryEngine::edges_of(), Slic3r::GeometryEngine::edges_of_face(), Slic3r::f(), Slic3r::GeometryEngine::face_by_index(), Slic3r::pick_body(), and Slic3r::project_edges_to_entities().

Referenced by replay_feature().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ apply_surface_offset()

void Slic3r::CadDocument::apply_surface_offset ( std::vector< CadBody > & bodies,
const CadFeature & f ) const
private
3564{
3565 const int nb = int(bodies.size());
3566 if (nb == 0) throw std::runtime_error(_u8L("surface-offset: no target body"));
3567 const int tgt = pick_body(f.target_body, nb);
3568 if (tgt < 0 || bodies[tgt].shape.IsNull()) throw std::runtime_error(_u8L("surface-offset: no target body"));
3569 if (!is_sheet_shape(bodies[tgt].shape)) throw std::runtime_error(_u8L("surface-offset: target is not a sheet body"));
3570 const double d = f.plane_offset;
3571 if (std::abs(d) < 1e-9) throw std::runtime_error(_u8L("surface-offset: zero offset"));
3572
3573 BRepOffsetAPI_MakeOffsetShape mos;
3574 mos.PerformBySimple(bodies[tgt].shape, d);
3575 if (!mos.IsDone()) throw std::runtime_error(_u8L("surface-offset: failed"));
3576 TopoDS_Shape off_shape = mos.Shape();
3577 if (off_shape.IsNull()) throw std::runtime_error(_u8L("surface-offset: produced no geometry"));
3578
3579 bodies.push_back({off_shape, f.name.empty() ? _u8L("Surface Offset") : f.name});
3580}
static bool is_sheet_shape(const TopoDS_Shape &s)
Definition CadDocument.cpp:4759

References _u8L, bodies, Slic3r::f(), is_sheet_shape(), and Slic3r::pick_body().

Referenced by route_feature().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ apply_thicken()

void Slic3r::CadDocument::apply_thicken ( std::vector< CadBody > & bodies,
const CadFeature & f ) const
private
3426{
3427 const int nb = int(bodies.size());
3428 if (nb == 0) throw std::runtime_error(_u8L("thicken: no target body"));
3429 const int tgt = pick_body(f.target_body, nb);
3430 if (tgt < 0 || bodies[tgt].shape.IsNull()) throw std::runtime_error(_u8L("thicken: no target body"));
3431
3432 TopoDS_Face fc = GeometryEngine::face_by_index(bodies[tgt].shape, f.thicken_face);
3433 if (fc.IsNull()) throw std::runtime_error(_u8L("thicken: face not found"));
3434 if (std::abs(f.thicken_thickness) < 1e-9) throw std::runtime_error(_u8L("thicken: thickness is zero"));
3435
3436 TopoDS_Shell shell;
3437 BRep_Builder bb;
3438 bb.MakeShell(shell);
3439 bb.Add(shell, fc);
3440
3441 const double off = f.thicken_flip ? -std::abs(f.thicken_thickness)
3442 : std::abs(f.thicken_thickness);
3443 BRepOffsetAPI_MakeThickSolid mts;
3444 mts.MakeThickSolidBySimple(shell, off);
3445 mts.Build();
3446 if (!mts.IsDone()) throw std::runtime_error(_u8L("thicken: failed"));
3447 TopoDS_Shape solid = mts.Shape();
3448 if (solid.IsNull()) throw std::runtime_error(_u8L("thicken: produced no geometry"));
3449
3450 // MakeThickSolidBySimple may produce a reversed solid. Ensure positive volume.
3451 {
3452 GProp_GProps props;
3453 BRepGProp::VolumeProperties(solid, props);
3454 if (props.Mass() < 0.0) solid.Reverse();
3455 }
3456
3457 bodies.push_back({solid, f.name.empty() ? _u8L("Thicken") : f.name});
3458}

References _u8L, bodies, Slic3r::f(), Slic3r::GeometryEngine::face_by_index(), and Slic3r::pick_body().

Referenced by route_feature().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ apply_thicken_surface()

void Slic3r::CadDocument::apply_thicken_surface ( std::vector< CadBody > & bodies,
const CadFeature & f ) const
private
3461{
3462 const int nb = int(bodies.size());
3463 if (nb == 0) throw std::runtime_error(_u8L("thicken-surface: no target body"));
3464 const int tgt = pick_body(f.target_body, nb);
3465 if (tgt < 0 || bodies[tgt].shape.IsNull()) throw std::runtime_error(_u8L("thicken-surface: no target body"));
3466 if (!is_sheet_shape(bodies[tgt].shape)) throw std::runtime_error(_u8L("thicken-surface: target is not a sheet body"));
3467 if (std::abs(f.thicken_thickness) < 1e-9) throw std::runtime_error(_u8L("thicken-surface: thickness is zero"));
3468
3469 const double off = f.thicken_flip ? -std::abs(f.thicken_thickness)
3470 : std::abs(f.thicken_thickness);
3471 const TopoDS_Shape& sheet = bodies[tgt].shape;
3472
3473 // MakeThickSolidBySimple offsets each face along its own normal and sews the result; it never
3474 // extends neighbours to meet, so at every edge where two faces join at an angle the corner
3475 // material is simply absent. A flat sheet is therefore exact while a 4-walled box is not
3476 // (measured: 29648 against the 44000 that (60^2-50^2)*40 requires).
3477 //
3478 // ByJoin is the call that mitres those corners, and it is what the Shell feature already uses
3479 // successfully a few hundred lines up — but it CANNOT be handed an open sheet. In OCCT,
3480 // BRepOffset_MakeOffset::MakeThickSolid builds the solid only inside `if (!myFaces.IsEmpty())`
3481 // (BRepOffset_MakeOffset.cxx:1115): with no closing faces it stops after the offset shell and
3482 // returns that. So it reports IsDone() and a non-null shape containing no TopAbs_SOLID at all
3483 // — which is exactly how two earlier attempts failed, and why no parameter could have fixed it.
3484 //
3485 // So close the sheet first, then use the working call: cap the free rims, sew into a closed
3486 // shell, make a solid, and hollow it inward passing the caps as the faces to remove. The caps
3487 // come back off, leaving the wall.
3488 // A SINGLE face has no edge shared with a neighbour, so there is no corner to mitre and
3489 // BySimple is already exact on it (flat 60x60 by 5 -> 18000.000). It also has a free
3490 // boundary, so "does it have free wires" is NOT the question to ask here — capping a lone
3491 // face with its own rim sews a zero-thickness shell and the offset then measures 6000.
3492 int n_faces = 0;
3493 for (TopExp_Explorer fe(sheet, TopAbs_FACE); fe.More(); fe.Next()) ++n_faces;
3494
3495 NCollection_List<TopoDS_Shape> caps;
3496 if (n_faces > 1) {
3497 ShapeAnalysis_FreeBounds fb(sheet);
3498 for (TopExp_Explorer we(fb.GetClosedWires(), TopAbs_WIRE); we.More(); we.Next()) {
3499 BRepBuilderAPI_MakeFace mk(TopoDS::Wire(we.Current()));
3500 if (!mk.IsDone())
3501 throw std::runtime_error(_u8L("thicken-surface: cannot cap the sheet rim "
3502 "(is it planar?)"));
3503 caps.Append(mk.Face());
3504 }
3505 }
3506
3507 TopoDS_Shape solid;
3508 if (caps.IsEmpty()) {
3509 BRepOffsetAPI_MakeThickSolid mts;
3510 mts.MakeThickSolidBySimple(sheet, off);
3511 mts.Build();
3512 if (!mts.IsDone()) throw std::runtime_error(_u8L("thicken-surface: failed"));
3513 solid = mts.Shape();
3514 } else {
3515 BRepBuilderAPI_Sewing sewer(1.0e-3);
3516 sewer.Add(sheet);
3517 for (NCollection_List<TopoDS_Shape>::Iterator it(caps); it.More(); it.Next())
3518 sewer.Add(it.Value());
3519 sewer.Perform();
3520
3521 TopoDS_Shell closed_shell;
3522 for (TopExp_Explorer se(sewer.SewedShape(), TopAbs_SHELL); se.More(); se.Next()) {
3523 if (!closed_shell.IsNull())
3524 throw std::runtime_error(_u8L("thicken-surface: the capped sheet split into "
3525 "more than one shell"));
3526 closed_shell = TopoDS::Shell(se.Current());
3527 }
3528 if (closed_shell.IsNull() || !BRep_Tool::IsClosed(closed_shell))
3529 throw std::runtime_error(_u8L("thicken-surface: the capped sheet is not closed"));
3530
3531 // A shell sewn from an extruded sheet carries no guarantee that its faces point outward,
3532 // and BRepBuilderAPI_MakeSolid does not fix that. Offsetting an inside-out solid sends the
3533 // wall the wrong way: measured bbox 70x70x40 for an inward offset on a 60x60 box, with a
3534 // volume larger than its own bounding box because the result then overlaps itself. A
3535 // negative mass IS the inverted orientation, so use it as the test.
3536 TopoDS_Solid capped = BRepBuilderAPI_MakeSolid(closed_shell).Solid();
3537 {
3538 GProp_GProps vp;
3539 BRepGProp::VolumeProperties(capped, vp);
3540 if (vp.Mass() < 0.0) capped.Reverse();
3541 }
3542
3543 BRepOffsetAPI_MakeThickSolid mts;
3544 mts.MakeThickSolidByJoin(capped, caps, -std::abs(off), 1.0e-3);
3545 mts.Build();
3546 if (!mts.IsDone()) throw std::runtime_error(_u8L("thicken-surface: failed"));
3547 solid = mts.Shape();
3548 }
3549 if (solid.IsNull()) throw std::runtime_error(_u8L("thicken-surface: produced no geometry"));
3550 // IsDone() is NOT a success test here — the failed attempts had IsDone() true and no solid.
3551 if (!TopExp_Explorer(solid, TopAbs_SOLID).More())
3552 throw std::runtime_error(_u8L("thicken-surface: produced a shell, not a solid"));
3553
3554 {
3555 GProp_GProps props;
3556 BRepGProp::VolumeProperties(solid, props);
3557 if (props.Mass() < 0.0) solid.Reverse();
3558 }
3559
3560 bodies.push_back({solid, f.name.empty() ? _u8L("Thicken Surface") : f.name});
3561}

References _u8L, bodies, Slic3r::f(), is_sheet_shape(), and Slic3r::pick_body().

Referenced by route_feature().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ apply_transform()

void Slic3r::CadDocument::apply_transform ( std::vector< CadBody > & bodies,
const CadFeature & f ) const
private
3397{
3398 const int nb = int(bodies.size());
3399 if (nb == 0) throw std::runtime_error(_u8L("transform: no target body"));
3400 const int tgt = pick_body(f.target_body, nb);
3401 if (tgt < 0 || bodies[tgt].shape.IsNull()) throw std::runtime_error(_u8L("transform: no target body"));
3402
3403 gp_Trsf rot;
3404 if (std::abs(f.xf_angle_deg) > 1e-12) {
3405 if (f.xf_axis.norm() < 1e-9)
3406 throw std::runtime_error(_u8L("transform: rotation axis is degenerate"));
3407 rot.SetRotation(gp_Ax1(gp_Pnt(f.xf_pivot.x(), f.xf_pivot.y(), f.xf_pivot.z()),
3408 gp_Dir(f.xf_axis.x(), f.xf_axis.y(), f.xf_axis.z())),
3409 f.xf_angle_deg * M_PI / 180.0);
3410 }
3411 gp_Trsf tr;
3412 tr.SetTranslation(gp_Vec(f.xf_translate.x(), f.xf_translate.y(), f.xf_translate.z()));
3413 const gp_Trsf trsf = tr * rot; // rotate first, then translate
3414
3415 BRepBuilderAPI_Transform xform(bodies[tgt].shape, trsf, true /*copy*/);
3416 if (!xform.IsDone()) throw std::runtime_error(_u8L("transform: failed"));
3417 TopoDS_Shape moved = xform.Shape();
3418
3419 if (f.xf_copy)
3420 bodies.push_back({moved, f.name.empty() ? _u8L("Transform") : f.name});
3421 else
3422 bodies[tgt].shape = moved;
3423}

References _u8L, bodies, Slic3r::f(), M_PI, and Slic3r::pick_body().

Referenced by route_feature().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ bind_expressions()

void Slic3r::CadDocument::bind_expressions ( )
private
4037{
4038 std::map<std::string, double> varvals = evaluate_variables(variables);
4039 for (CadFeature& f : features)
4040 for (const auto& [field, e] : f.expr)
4041 assign_field(f, field, eval_expr(e, varvals));
4042}
std::map< std::string, std::string > variables
Definition CadDocument.hpp:542
static double eval_expr(const std::string &src, const std::map< std::string, double > &vars)
Definition CadDocument.cpp:244
static void assign_field(CadFeature &f, const std::string &field, double value)
Definition CadDocument.cpp:465
static std::map< std::string, double > evaluate_variables(const std::map< std::string, std::string > &variables)
Definition CadDocument.cpp:417

References Slic3r::assign_field(), Slic3r::eval_expr(), Slic3r::evaluate_variables(), Slic3r::f(), features, and variables.

Referenced by faces_made_by(), and recompute().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ body_mass_properties()

GeometryEngine::MassProps Slic3r::CadDocument::body_mass_properties ( int body_index) const
4672{
4673 if (body_index < 0 || body_index >= int(bodies.size())) return {};
4674 return GeometryEngine::mass_properties(bodies[body_index].shape);
4675}
static MassProps mass_properties(const TopoDS_Shape &shape)
Definition GeometryEngine.cpp:482

References bodies, and Slic3r::GeometryEngine::mass_properties().

Here is the call graph for this function:

◆ body_touching_sketch()

int Slic3r::CadDocument::body_touching_sketch ( int sketch_ref) const
2541{
2542 if (sketch_ref < 0 || sketch_ref >= int(features.size())
2543 || features[sketch_ref].type != CadFeatureType::Sketch)
2544 return -1;
2545 // A query that only picks a default: any failure answers "touches nothing".
2546 try {
2547 const TopoDS_Face face = build_sketch_face(features[sketch_ref]);
2548 if (face.IsNull()) return -1;
2549 for (int i = int(bodies.size()) - 1; i >= 0; --i) { // newest first: the likeliest target
2550 const TopoDS_Shape& b = bodies[i].shape;
2551 if (b.IsNull() || is_sheet_shape(b)) continue;
2552 BRepExtrema_DistShapeShape d(face, b);
2553 if (d.IsDone() && d.Value() <= 1e-4)
2554 return i;
2555 }
2556 } catch (const Standard_Failure&) { // before std::exception: on OCCT >= 8 it derives from it
2557 } catch (const std::exception&) {
2558 }
2559 return -1;
2560}
TopoDS_Face build_sketch_face(const CadFeature &sketch) const
Definition CadDocument.cpp:2562

References bodies, build_sketch_face(), features, is_sheet_shape(), and Slic3r::Sketch.

Here is the call graph for this function:

◆ build_helix_wire()

TopoDS_Wire Slic3r::CadDocument::build_helix_wire ( const CadFeature & f,
std::string & err ) const
1593{
1594 return make_helix_spine(f, err);
1595}

References Slic3r::f(), and Slic3r::make_helix_spine().

Here is the call graph for this function:

◆ build_sketch_face()

TopoDS_Face Slic3r::CadDocument::build_sketch_face ( const CadFeature & sketch) const
private
2563{
2564 if (!sketch.entities.empty()) {
2565 const std::vector<TopoDS_Wire> loops = SketchEngine::entities_to_wires(sketch.entities, sketch.plane, true);
2566 if (loops.empty())
2567 // Same courtesy as build_sketch_wire: name WHERE the sketch is open. Extrude
2568 // reaches its failure through here, not through build_sketch_wire, so without
2569 // this the most common way to hit an open profile is also the least informative.
2570 throw std::runtime_error(open_loop_message(sketch));
2571 return SketchEngine::wires_to_face(loops, sketch.plane);
2572 }
2573 return BRepBuilderAPI_MakeFace(build_sketch_wire(sketch, true)).Face();
2574}
TopoDS_Wire build_sketch_wire(const CadFeature &sketch, bool closed_only=false) const
Definition CadDocument.cpp:2494
static TopoDS_Face wires_to_face(const std::vector< TopoDS_Wire > &wires, const SketchPlane &plane)
Definition SketchEngine.cpp:930
static std::vector< TopoDS_Wire > entities_to_wires(const std::vector< SketchEntity > &entities, const SketchPlane &plane, bool closed_only=false)
Definition SketchEngine.cpp:562
static std::string open_loop_message(const CadFeature &sketch, const std::string &head=_u8L("sketch entities do not form a closed loop"))
Definition CadDocument.cpp:2458

References build_sketch_wire(), Slic3r::CadFeature::entities, Slic3r::SketchEngine::entities_to_wires(), Slic3r::open_loop_message(), Slic3r::CadFeature::plane, and Slic3r::SketchEngine::wires_to_face().

Referenced by apply_feature(), and body_touching_sketch().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ build_sketch_wire()

TopoDS_Wire Slic3r::CadDocument::build_sketch_wire ( const CadFeature & sketch,
bool closed_only = false ) const
private
2495{
2496 if (!sketch.entities.empty()) {
2497 TopoDS_Wire w = SketchEngine::entities_to_wire(sketch.entities, sketch.plane, closed_only);
2498 if (!w.IsNull()) return w;
2499 // An entity sketch that yields no wire is an ERROR, not a cue to fall through. The
2500 // legacy tail of this function ends in a default rectangle built from width/height,
2501 // which for an entity sketch are whatever they happened to be initialised to — so a
2502 // sketch entities_to_wire cannot handle (a circle coexisting with a line, two circles:
2503 // 88v) used to extrude into a box the user never drew, silently. Failing here
2504 // costs the caller an error message; falling through cost them wrong geometry that
2505 // looked deliberate. The legacy profile/shape paths below are still reached by sketches
2506 // that legitimately carry no entities at all.
2507 throw std::runtime_error(
2508 open_loop_message(sketch,
2509 _u8L("sketch has entities but they do not form a single closed wire — a closed "
2510 "entity (circle/ellipse) combined with other entities, or several closed "
2511 "entities, is not supported yet")));
2512 }
2513 if (!sketch.profile.points.empty()) {
2514 SketchProfile prof = sketch.profile;
2515 prof.closed = true; // extrude needs a closed wire
2516 TopoDS_Wire w = prof.to_occt_wire(sketch.plane);
2517 if (w.IsNull()) throw std::runtime_error(_u8L("sketch profile wire failed"));
2518 return w;
2519 }
2520 if (sketch.shape == SketchShape::Circle) {
2521 gp_Pnt o(sketch.plane.origin.x(), sketch.plane.origin.y(), sketch.plane.origin.z());
2522 gp_Dir n(sketch.plane.normal.x(), sketch.plane.normal.y(), sketch.plane.normal.z());
2523 gp_Circ circ(gp_Ax2(o, n), sketch.radius);
2524 TopoDS_Edge e = BRepBuilderAPI_MakeEdge(circ).Edge();
2525 BRepBuilderAPI_MakeWire wm(e);
2526 if (!wm.IsDone()) throw std::runtime_error(_u8L("circle wire failed"));
2527 return wm.Wire();
2528 }
2529 // Rectangle centered on the plane origin
2530 SketchProfile prof;
2531 double hw = sketch.width * 0.5, hh = sketch.height * 0.5;
2532 prof.points.push_back(Vec2d(-hw, -hh));
2533 prof.points.push_back(Vec2d( hw, -hh));
2534 prof.points.push_back(Vec2d( hw, hh));
2535 prof.points.push_back(Vec2d(-hw, hh));
2536 prof.closed = true;
2537 return prof.to_occt_wire(sketch.plane);
2538}
static TopoDS_Wire entities_to_wire(const std::vector< SketchEntity > &entities, const SketchPlane &plane, bool closed_only=false)
Definition SketchEngine.cpp:862
@ Circle
Definition CadDocument.hpp:27

References _u8L, Slic3r::Circle, Slic3r::SketchProfile::closed, Slic3r::CadFeature::entities, Slic3r::SketchEngine::entities_to_wire(), Slic3r::CadFeature::height, Slic3r::SketchPlane::normal, Slic3r::open_loop_message(), Slic3r::SketchPlane::origin, Slic3r::CadFeature::plane, Slic3r::SketchProfile::points, Slic3r::CadFeature::profile, Slic3r::CadFeature::radius, Slic3r::CadFeature::shape, Slic3r::SketchProfile::to_occt_wire(), and Slic3r::CadFeature::width.

Referenced by apply_feature(), and build_sketch_face().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ can_redo()

bool Slic3r::CadDocument::can_redo ( ) const
inline
809{ return !m_redo.empty(); }
std::vector< Snapshot > m_redo
Definition CadDocument.hpp:914

References m_redo.

◆ can_undo()

bool Slic3r::CadDocument::can_undo ( ) const
inline
808{ return !m_undo.empty(); }

References m_undo.

◆ check_interference()

std::vector< CadDocument::Interference > Slic3r::CadDocument::check_interference ( double min_volume = 1e-6) const
4726{
4727 std::vector<Interference> out;
4728 const int n = int(bodies.size());
4729
4730 for (int i = 0; i < n; ++i) {
4731 if (bodies[i].shape.IsNull() || is_sheet_shape(bodies[i].shape)) continue;
4732 for (int j = i + 1; j < n; ++j) {
4733 if (bodies[j].shape.IsNull() || is_sheet_shape(bodies[j].shape)) continue;
4734
4735 double v = 0;
4736 // A boolean that fails on one pair must not lose the report for the others.
4737 try {
4738 BRepAlgoAPI_Common common(bodies[i].shape, bodies[j].shape);
4739 common.Build();
4740 if (!common.IsDone()) continue;
4741 const TopoDS_Shape s = common.Shape();
4742 if (s.IsNull()) continue;
4743 GProp_GProps props;
4744 BRepGProp::VolumeProperties(s, props);
4745 v = std::abs(props.Mass());
4746 } catch (const Standard_Failure&) {
4747 continue;
4748 }
4749
4750 // Bodies that merely touch share a face and enclose no volume, so the
4751 // threshold is what separates contact from interference.
4752 if (v > min_volume) out.push_back({i, j, v});
4753 }
4754 }
4755 return out;
4756}

References bodies, and is_sheet_shape().

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_check_interference().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ checkpoint()

void Slic3r::CadDocument::checkpoint ( )
2049{
2050 m_undo.push_back(Snapshot{features, variables}); // snapshot the pre-mutation recipe
2051 m_redo.clear(); // any new action invalidates the redo branch
2052 if (m_undo.size() > k_undo_cap)
2053 m_undo.erase(m_undo.begin());
2054}
static constexpr size_t k_undo_cap
Definition CadDocument.hpp:915
Definition CadDocument.hpp:909

References features, k_undo_cap, m_redo, m_undo, and variables.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_axis(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_boolean(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_bridge(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_chamfer(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_coordsys(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_delete_face(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_draft(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_extrude(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_fillet(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_helix(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_hole(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_hole_standard(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_hole_styled(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_mate(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_mirror(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_pattern(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_project(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_revolve(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_rib(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_set_feature_expr(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_set_variable(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_shell(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_split(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_extrude(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_fill(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_loft(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_offset(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_revolve(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_thicken(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_thicken_surface(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_transform(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::import_mesh(), and Slic3r::GUI::anonymous_namespace{McpControl.cpp}::import_step().

Here is the caller graph for this function:

◆ clear()

void Slic3r::CadDocument::clear ( )
2027{
2028 features.clear();
2029 body = TopoDS_Shape();
2030 bodies.clear(); // multibody result — must clear too (else solids linger)
2031 display_mesh = TriangleMesh{};
2032 display_body_meshes.clear();
2033 display_tri_face.clear();
2034 display_tri_body.clear();
2035 origin_from_recipe = false;
2036 auto_close_loops = true;
2037 // Variables are document state like the features: left behind, the previous project's
2038 // variables were written into the next project's recipe.
2039 variables.clear();
2040 error.clear();
2041 mate_conflicts.clear();
2042 ++topo_generation; // every face/edge id handed out before this is stale now
2043 // A cleared document is a fresh start with no history.
2044 m_undo.clear();
2045 m_redo.clear();
2046}
std::string error
Definition CadDocument.hpp:558
TopoDS_Shape body
Definition CadDocument.hpp:553
uint64_t topo_generation
Definition CadDocument.hpp:765
TriangleMesh display_mesh
Definition CadDocument.hpp:554
bool origin_from_recipe
Definition CadDocument.hpp:573
bool auto_close_loops
Definition CadDocument.hpp:578
std::vector< TriangleMesh > display_body_meshes
Definition CadDocument.hpp:555
std::vector< int > display_tri_body
Definition CadDocument.hpp:557
std::vector< int > display_tri_face
Definition CadDocument.hpp:556
std::vector< std::pair< int, std::string > > mate_conflicts
Definition CadDocument.hpp:565

References auto_close_loops, bodies, body, display_body_meshes, display_mesh, display_tri_body, display_tri_face, error, features, m_redo, m_undo, mate_conflicts, origin_from_recipe, topo_generation, and variables.

◆ datum_frame()

CadDocument::DatumCoordSys Slic3r::CadDocument::datum_frame ( const std::vector< CadBody > & bodies,
const CadFeature & f )
staticprivate
1937{
1938 CadDocument::DatumCoordSys ds;
1939 ds.name = f.name;
1940
1941 auto resolve_face = [&](int body_idx, int face_idx) -> TopoDS_Face {
1942 if (face_idx < 0 || body_idx < 0 || body_idx >= int(bodies.size()))
1943 return TopoDS_Face();
1944 return GeometryEngine::face_by_index(bodies[body_idx].shape, face_idx);
1945 };
1946 auto resolve_edge = [&](int body_idx, int edge_idx,
1947 Vec3d& p0, Vec3d& dir) -> bool {
1948 if (edge_idx < 0 || body_idx < 0 || body_idx >= int(bodies.size()))
1949 return false;
1950 TopoDS_Edge e = GeometryEngine::edge_by_index(bodies[body_idx].shape, edge_idx);
1951 if (e.IsNull()) return false;
1953 if (pts.size() < 2) return false;
1954 p0 = pts.front();
1955 dir = (pts.back() - pts.front()).normalized();
1956 return true;
1957 };
1958
1959 switch (f.coordsys_type) {
1961 ds.origin = f.coordsys_point;
1962 ds.x = Vec3d(1, 0, 0);
1963 ds.y = Vec3d(0, 1, 0);
1964 break;
1965 }
1967 TopoDS_Face fc = resolve_face(f.coordsys_body, f.coordsys_face);
1968 Vec3d p0, edge_dir;
1969 bool have_edge = resolve_edge(f.coordsys_body, f.coordsys_edge, p0, edge_dir);
1970 if (fc.IsNull()) { ds.error = _u8L("face not found"); break; }
1973 // Tentative X: an explicit edge reference wins. Failing that, derive X from the
1974 // face's OWN first usable edge, so the frame rotates with the body. Reading it from
1975 // coordsys_x_hint — a world constant — meant a face-only connector could not encode
1976 // spin about its own normal: Z followed the body, X did not, so Fastened and Slider
1977 // claimed to fix an orientation the frame could not see. The hint survives only as a
1978 // last resort, for faces that offer no usable direction (a full circular edge has
1979 // coincident endpoints, and a cylinder's seam projects to nothing in-plane).
1980 Vec3d X_tent = Vec3d::Zero();
1981 if (have_edge) {
1982 X_tent = edge_dir;
1983 } else {
1984 for (const TopoDS_Edge& fe : GeometryEngine::edges_of_face(fc)) {
1985 auto pts = GeometryEngine::sample_edge_world(fe);
1986 if (pts.size() < 2) continue;
1987 Vec3d d = pts.back() - pts.front();
1988 if (d.squaredNorm() < 1e-18) continue; // closed edge: endpoints coincide
1989 Vec3d in_plane = d - Z * Z.dot(d); // drop any out-of-plane component
1990 if (in_plane.squaredNorm() < 1e-18) continue;
1991 X_tent = in_plane;
1992 break;
1993 }
1994 if (X_tent.squaredNorm() < 1e-18) X_tent = f.coordsys_x_hint;
1995 }
1996 if (X_tent.squaredNorm() < 1e-18) { ds.error = _u8L("zero-length direction"); break; }
1997 X_tent.normalize();
1998 // Gram-Schmidt: ensure orthonormal, right-handed frame.
1999 // Y = Z x X_tent, X = Y x Z (this makes X perpendicular to Z, not X_tent)
2000 Vec3d Y = Z.cross(X_tent);
2001 if (Y.squaredNorm() < 1e-12) {
2002 // Edge/hint is parallel to Z -> X is degenerate; fall back to world X/Y orthonormalised.
2003 Vec3d ref = (std::abs(Z.z()) < 0.9) ? Vec3d(0, 0, 1) : Vec3d(1, 0, 0);
2004 Y = Z.cross(ref);
2005 if (Y.squaredNorm() < 1e-12) Y = Z.cross(Vec3d(0, 1, 0));
2006 }
2007 Y.normalize();
2008 ds.x = Y.cross(Z).normalized();
2009 ds.y = Y;
2010 break;
2011 }
2012 }
2013 return ds;
2014}
static Vec3d face_normal_world(const TopoDS_Face &face)
Definition GeometryEngine.cpp:627
static std::vector< Vec3d > sample_edge_world(const TopoDS_Edge &edge, double chord_tol=0.05)
Definition GeometryEngine.cpp:575
static Vec3d face_centroid_world(const TopoDS_Face &face)
Definition GeometryEngine.cpp:619
@ PointWorld
Definition CadDocument.hpp:30
@ FaceAndDirection
Definition CadDocument.hpp:30
@ Y
Definition libslic3r.h:142
@ Z
Definition libslic3r.h:143

References _u8L, bodies, Slic3r::GeometryEngine::edge_by_index(), Slic3r::GeometryEngine::edges_of_face(), Slic3r::CadDocument::DatumCoordSys::error, Slic3r::f(), Slic3r::GeometryEngine::face_by_index(), Slic3r::GeometryEngine::face_centroid_world(), Slic3r::GeometryEngine::face_normal_world(), Slic3r::FaceAndDirection, Slic3r::CadDocument::DatumCoordSys::name, Slic3r::CadDocument::DatumCoordSys::origin, Slic3r::PointWorld, Slic3r::GeometryEngine::sample_edge_world(), Slic3r::CadDocument::DatumCoordSys::x, Slic3r::Y, Slic3r::CadDocument::DatumCoordSys::y, and Slic3r::Z.

Referenced by apply_mate(), and resolve_datum_coordsys().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ deserialize_recipe()

bool Slic3r::CadDocument::deserialize_recipe ( const std::string & blob)
4496{
4497 error.clear();
4498 try {
4499 std::istringstream iss(blob);
4500 cereal::BinaryInputArchive ar(iss);
4501 uint32_t v;
4502 ar(v);
4503 if (v > ORCA_CAD_RECIPE_VERSION) {
4504 error = format(_u8L("saved with a newer version of the Design tab (format v%1%, this build reads up to v%2%)"),
4506 return false;
4507 }
4508 // The framed layout has been byte-identical since v5 (v6 advanced the stamp without
4509 // touching the bytes), so every version from 5 up is read below. A future bump that DOES
4510 // change the framing must exclude itself there — this is what forces that decision
4511 // instead of letting a v7 blob be silently misread by the v5 reader.
4512 static_assert(ORCA_CAD_RECIPE_VERSION <= 6,
4513 "recipe version bumped: confirm the new version still uses the v5 framing");
4514 if (v >= 5) {
4515 // Framed path: every feature is a length-prefixed self-contained cereal stream, so
4516 // the same four lines handle BOTH directions of mismatch. Older file, newer build:
4517 // the sub-stream ends early, fa(f) throws, and the fields already assigned are kept
4518 // (cereal assigns sequentially, so a mid-list throw leaves the earlier fields set)
4519 // while the rest default. Newer file, older build: the sub-stream has MORE bytes
4520 // than this build knows how to read; it reads what it knows and simply stops, and
4521 // the outer stream is unaffected because the length prefix was consumed in full.
4522 features.clear();
4523 uint32_t count;
4524 ar(count);
4525 for (uint32_t i = 0; i < count; ++i) {
4526 uint32_t len;
4527 ar(len);
4528 std::string buf(len, '\0');
4529 if (len > 0)
4530 ar(cereal::binary_data(&buf[0], len)); // consume EXACTLY len bytes from the outer stream
4531 CadFeature f;
4532 try {
4533 std::istringstream fs(buf);
4534 cereal::BinaryInputArchive fa(fs);
4535 fa(f);
4536 } catch (...) {
4537 // An OLDER file: the blob ran out before this build's field list did. Everything read
4538 // so far is kept and the remaining fields stay at their defaults. Deliberately NOT an
4539 // error — a field a project predates is not a corrupt project.
4540 }
4541 features.push_back(f);
4542 }
4543 variables.clear();
4544 uint32_t vlen;
4545 ar(vlen);
4546 std::string vbuf(vlen, '\0');
4547 if (vlen > 0)
4548 ar(cereal::binary_data(&vbuf[0], vlen));
4549 try {
4550 std::istringstream vs(vbuf);
4551 cereal::BinaryInputArchive va(vs);
4552 va(variables);
4553 } catch (...) {
4554 // Variables blob predates this build: keep what was read, default the rest.
4555 }
4556 // Body names, if this project carries them. A project written before the block
4557 // simply ends here, so the read throws and there are no names — not an error.
4558 std::map<uint32_t, std::string> named;
4559 try {
4560 uint32_t blen;
4561 ar(blen);
4562 std::string bbuf(blen, '\0');
4563 if (blen > 0)
4564 ar(cereal::binary_data(&bbuf[0], blen));
4565 std::istringstream bs(bbuf);
4566 cereal::BinaryInputArchive ba(bs);
4567 ba(named);
4568 } catch (...) {
4569 named.clear();
4570 }
4571 // Document extras (origin, colours), when the project has them. The origin must be
4572 // in place BEFORE the rebuild: datum planes are resolved against it.
4573 std::map<uint32_t, std::array<float, 4>> colours;
4574 try {
4575 uint32_t xlen;
4576 ar(xlen);
4577 std::string xbuf(xlen, '\0');
4578 if (xlen > 0)
4579 ar(cereal::binary_data(&xbuf[0], xlen));
4580 std::istringstream xs(xbuf);
4581 cereal::BinaryInputArchive xa(xs);
4582 double ox = 0, oy = 0, oz = 0;
4583 bool weld = true;
4584 xa(ox, oy, oz, colours, weld);
4585 modeling_origin = Vec3d(ox, oy, oz);
4586 origin_from_recipe = true;
4587 auto_close_loops = weld;
4588 } catch (...) {
4589 colours.clear(); // older project: the caller's origin stands
4590 }
4591 // AFTER the rebuild, never before: recompute() replaces the bodies vector wholesale.
4592 const bool ok = recompute();
4593 for (const auto& kv : named)
4594 if (kv.first < bodies.size()) {
4595 bodies[kv.first].has_user_name = true;
4596 bodies[kv.first].user_name = kv.second;
4597 }
4598 for (const auto& kv : colours)
4599 if (kv.first < bodies.size()) {
4600 bodies[kv.first].has_color = true;
4601 bodies[kv.first].color = ColorRGBA(kv.second[0], kv.second[1], kv.second[2], kv.second[3]);
4602 }
4603 return ok;
4604 }
4605 if (v == 4) {
4606 // Pre-framing flat path: v4 projects keep opening, read with the FROZEN v4 field list
4607 // (CadFeature::load_flat_v4) so fields appended to the framed layout since cannot
4608 // shift every byte after them.
4609 cereal::size_type n = 0;
4610 ar(cereal::make_size_tag(n));
4611 features.assign(size_t(n), CadFeature{});
4612 for (CadFeature& f : features) f.load_flat_v4(ar);
4613 ar(variables);
4614 return recompute();
4615 }
4616 // v < 4: the field lists for v2/v3 no longer exist in this code, so those files
4617 // cannot be recovered here. This fix is for the future, not the past.
4618 error = format(_u8L("saved with an older version of the Design tab (format v%1%); this project cannot be opened by this build"), v);
4619 return false;
4620 } catch (const std::exception& e) {
4621 error = *e.what() ? format(_u8L("CAD data could not be read: %1%"), e.what())
4622 : _u8L("CAD data could not be read");
4623 return false;
4624 } catch (...) {
4625 error = _u8L("CAD data could not be read");
4626 return false;
4627 }
4628}
bool recompute()
Definition CadDocument.cpp:4069
static constexpr uint32_t ORCA_CAD_RECIPE_VERSION
Definition CadDocument.hpp:773

References _u8L, auto_close_loops, bodies, count, error, Slic3r::f(), features, Slic3r::format(), modeling_origin, ORCA_CAD_RECIPE_VERSION, origin_from_recipe, recompute(), and variables.

Here is the call graph for this function:

◆ detect_mate_conflicts()

void Slic3r::CadDocument::detect_mate_conflicts ( )
private
3689{
3690 mate_conflicts.clear();
3691
3692 const int n = int(features.size());
3693 std::map<int, int> first_driver; // body -> feature index of first mate that drives it
3694 std::map<int, std::vector<int>> graph; // dst -> list of src (body indices)
3695
3696 for (int fi = 0; fi < n; ++fi) {
3697 const CadFeature& f = features[fi];
3698 if (f.type != CadFeatureType::Mate || !f.enabled) continue;
3699
3700 if (f.mate_cs_a < 0 || f.mate_cs_a >= n) continue;
3701 if (f.mate_cs_b < 0 || f.mate_cs_b >= n) continue;
3702 const CadFeature& fa = features[f.mate_cs_a];
3703 const CadFeature& fb = features[f.mate_cs_b];
3704 if (fa.type != CadFeatureType::CoordSys || !fa.enabled) continue;
3705 if (fb.type != CadFeatureType::CoordSys || !fb.enabled) continue;
3706
3707 const int src = fa.coordsys_body;
3708 const int dst = fb.coordsys_body;
3709 if (dst < 0) continue; // apply_mate already reports this one
3710
3711 // (a) duplicate target: a second mate driving the same body
3712 auto it = first_driver.find(dst);
3713 if (it != first_driver.end()) {
3714 int first_fi = it->second;
3715 std::string first_name = features[first_fi].name.empty() ? _u8L("Mate") : features[first_fi].name;
3716 std::string this_name = f.name.empty() ? _u8L("Mate") : f.name;
3717 mate_conflicts.push_back({fi,
3718 format(_u8L("Body %1% is already positioned by \"%2%\" (feature %3%), so \"%4%\" overrides it"),
3719 dst + 1, first_name, first_fi + 1, this_name)});
3720 } else {
3721 first_driver[dst] = fi;
3722 }
3723
3724 // (b) self-mate or cycle detection
3725 if (src >= 0 && dst >= 0) {
3726 if (src == dst) {
3727 mate_conflicts.push_back({fi,
3728 format(_u8L("This mate positions Body %1% against itself"), dst + 1)});
3729 } else {
3730 graph[dst].push_back(src);
3731 }
3732 }
3733 }
3734
3735 // DFS cycle detection on the graph built above.
3736 // ponytail: iterative DFS avoids recursion depth issues on long chains.
3737 // Colour: 0 = unvisited, 1 = in-progress (grey), 2 = done (black).
3738 std::map<int, int> colour;
3739 struct Frame { int node; size_t next; };
3740 std::vector<Frame> stack;
3741
3742 for (const auto& [start, _] : graph) {
3743 if (colour[start] == 2) continue;
3744 stack.clear();
3745 stack.push_back({start, 0});
3746 colour[start] = 1;
3747 while (!stack.empty()) {
3748 Frame& top = stack.back();
3749 auto git = graph.find(top.node);
3750 if (git == graph.end() || top.next >= git->second.size()) {
3751 colour[top.node] = 2;
3752 stack.pop_back();
3753 continue;
3754 }
3755 int child = git->second[top.next++];
3756 if (colour[child] == 1) {
3757 // Back edge found — find the mate whose (dst==top.node, src==child).
3758 for (int fi = 0; fi < n; ++fi) {
3759 const CadFeature& f = features[fi];
3760 if (f.type != CadFeatureType::Mate || !f.enabled) continue;
3761 if (f.mate_cs_a < 0 || f.mate_cs_a >= n) continue;
3762 if (f.mate_cs_b < 0 || f.mate_cs_b >= n) continue;
3763 const CadFeature& fa2 = features[f.mate_cs_a];
3764 const CadFeature& fb2 = features[f.mate_cs_b];
3765 if (fa2.type != CadFeatureType::CoordSys || !fa2.enabled) continue;
3766 if (fb2.type != CadFeatureType::CoordSys || !fb2.enabled) continue;
3767 int sd = fb2.coordsys_body;
3768 int ss = fa2.coordsys_body;
3769 if (sd == top.node && ss == child) {
3770 mate_conflicts.push_back({fi,
3771 // Worded for ANY cycle length: "leads back" is true transitively,
3772 // where "depends back on" would be a lie for a 3+ body chain.
3773 format(_u8L("Mate chain is circular: Body %1% depends on Body %2%, which leads "
3774 "back to Body %1% — the result depends on feature order"),
3775 top.node + 1, child + 1)});
3776 break;
3777 }
3778 }
3779 continue;
3780 }
3781 if (colour[child] == 0) {
3782 colour[child] = 1;
3783 stack.push_back({child, 0});
3784 }
3785 }
3786 }
3787
3788 // Deliberately NOT in scope: computing the numeric disagreement between two mates
3789 // ("Mate3 puts it at X=10, Mate7 at X=15"). That needs speculative per-mate evaluation.
3790}
#define _(s)
Definition 3mf.cpp:306

References _, _u8L, Slic3r::CoordSys, Slic3r::CadFeature::coordsys_body, Slic3r::CadFeature::enabled, Slic3r::f(), features, Slic3r::format(), Slic3r::Mate, mate_conflicts, and Slic3r::CadFeature::type.

Referenced by recompute().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ export_step()

bool Slic3r::CadDocument::export_step ( const std::string & path,
const std::vector< Transform3d > & body_xforms,
std::string & err ) const
4633{
4634 err.clear();
4635 if (bodies.empty()) { err = _u8L("nothing to export"); return false; }
4636 try {
4637 // Compound every body at its displayed (Move-gizmo) position so the STEP matches
4638 // what Commit ships. Move transforms are rigid, so gp_Trsf::SetValues is valid.
4639 BRep_Builder bld;
4640 TopoDS_Compound comp;
4641 bld.MakeCompound(comp);
4642 for (size_t i = 0; i < bodies.size(); ++i) {
4643 if (bodies[i].shape.IsNull()) continue;
4644 TopoDS_Shape s = bodies[i].shape;
4645 if (i < body_xforms.size() && !body_xforms[i].isApprox(Transform3d::Identity())) {
4646 const Transform3d& m = body_xforms[i];
4647 gp_Trsf t;
4648 t.SetValues(m(0,0), m(0,1), m(0,2), m(0,3),
4649 m(1,0), m(1,1), m(1,2), m(1,3),
4650 m(2,0), m(2,1), m(2,2), m(2,3));
4651 s = BRepBuilderAPI_Transform(s, t, true).Shape();
4652 }
4653 bld.Add(comp, s);
4654 }
4655 STEPControl_Writer writer;
4656 if (writer.Transfer(comp, STEPControl_AsIs) != IFSelect_RetDone) {
4657 err = _u8L("STEP transfer failed");
4658 return false;
4659 }
4660 if (writer.Write(path.c_str()) != IFSelect_RetDone) {
4661 err = _u8L("cannot write STEP file");
4662 return false;
4663 }
4664 } catch (const std::exception& e) {
4665 err = *e.what() ? e.what() : _u8L("OCCT failed to write STEP");
4666 return false;
4667 }
4668 return true;
4669}
Eigen::Transform< double, 3, Eigen::Affine, Eigen::DontAlign > Transform3d
Definition Point.hpp:96

References _u8L, and bodies.

◆ faces_made_by()

std::vector< std::pair< int, int > > Slic3r::CadDocument::faces_made_by ( int index) const
4341{
4342 std::vector<std::pair<int, int>> out;
4343 if (index < 0 || index >= int(features.size()) || !features[index].enabled
4344 || !produces_body(features[index].type) || bodies.empty())
4345 return out;
4346
4347 // Replay up to the feature, then the feature alone on a copy, so a body it left alone is the
4348 // same shape on both sides. On a scratch document, as a replay settles references in the
4349 // features it runs; it reads only the features, the variables and the process-wide weld rule.
4350 CadDocument tmp;
4351 tmp.features = features;
4352 tmp.variables = variables;
4353 std::vector<CadBody> before, after;
4354 try {
4356 tmp.bind_expressions();
4357 for (size_t fi = 0; fi < size_t(index); ++fi)
4358 tmp.replay_feature(fi, before);
4359 after = before;
4360 tmp.replay_feature(size_t(index), after);
4361 } catch (...) { // OCCT's Standard_Failure as well as std::exception
4362 return out;
4363 }
4364
4365 // The bodies the step made or replaced. A face on any other body was on the boundary before
4366 // the step too, so only these are probed after it.
4367 std::vector<int> changed;
4368 std::vector<CadBody> changed_bodies;
4369 for (int j = 0; j < int(after.size()); ++j)
4370 if (std::none_of(before.begin(), before.end(), [&](const CadBody& w) { return w.shape.IsSame(after[j].shape); })) {
4371 changed.push_back(j);
4372 changed_bodies.push_back(after[j]);
4373 }
4374 const std::vector<FaceProbe> was = face_probes(before), now = face_probes(changed_bodies);
4375 for (int b = 0; b < int(bodies.size()); ++b) {
4376 int id = 0;
4377 for (TopExp_Explorer e(bodies[b].shape, TopAbs_FACE); e.More(); e.Next(), ++id) {
4378 gp_Pnt p;
4379 gp_Dir n;
4380 try {
4381 if (interior_point(TopoDS::Face(e.Current()), p, n) && on_boundary(now, p, n) && !on_boundary(was, p, n))
4382 out.emplace_back(b, id);
4383 } catch (const Standard_Failure&) {
4384 // A face that cannot be sampled is left out, not the whole answer.
4385 }
4386 }
4387 }
4388 if (!out.empty())
4389 return out;
4390
4391 // No face of its own: answer with every face of each body the step changed, found again in
4392 // the finished model by the id a body keeps across the rest of the history.
4393 for (int j : changed) {
4394 const int b = find_body(bodies, body_id_of(after, j));
4395 if (b < 0) continue;
4396 const int n = GeometryEngine::face_count(bodies[b].shape);
4397 for (int id = 0; id < n; ++id)
4398 out.emplace_back(b, id);
4399 }
4400 return out;
4401}
static bool produces_body(CadFeatureType t)
Definition CadDocument.cpp:520
static int face_count(const TopoDS_Shape &shape)
Definition GeometryEngine.cpp:526
bool on_boundary(const std::vector< FaceProbe > &probes, const gp_Pnt &p, const gp_Dir &n)
Definition CadDocument.cpp:4311
std::vector< FaceProbe > face_probes(const std::vector< CadBody > &bodies)
Definition CadDocument.cpp:4250
bool interior_point(const TopoDS_Face &face, gp_Pnt &p, gp_Dir &n)
Definition CadDocument.cpp:4279
void set_sketch_auto_close(bool on)
Definition SketchEngine.cpp:81
static int find_body(const std::vector< CadBody > &v, const CadFeature::BodyId &id)
Definition CadDocument.cpp:2138
static CadFeature::BodyId body_id_of(const std::vector< CadBody > &v, int i)
Definition CadDocument.cpp:2129

References auto_close_loops, bind_expressions(), bodies, Slic3r::body_id_of(), Slic3r::GeometryEngine::face_count(), features, Slic3r::find_body(), produces_body(), replay_feature(), Slic3r::set_sketch_auto_close(), and variables.

Here is the call graph for this function:

◆ is_bindable_field()

bool Slic3r::CadDocument::is_bindable_field ( const std::string & field)
static
532{
533 CadFeature probe;
534 try { assign_field(probe, field, 0.0); return true; }
535 catch (const std::exception&) { return false; }
536}

References Slic3r::assign_field().

Referenced by Slic3r::GUI::DesignPanel::on_set_expr().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ is_sheet_shape()

bool Slic3r::CadDocument::is_sheet_shape ( const TopoDS_Shape & s)
static
4760{
4761 return !TopExp_Explorer(s, TopAbs_SOLID).More();
4762}

Referenced by apply_surface_offset(), apply_thicken_surface(), body_touching_sketch(), check_interference(), Slic3r::GUI::DesignPanel::offer_selection_kind(), Slic3r::GUI::DesignPanel::populate_sheet_body_choices(), and Slic3r::GUI::DesignPanel::show_offer_menu().

Here is the caller graph for this function:

◆ mate_options()

std::vector< CadDocument::MateOption > Slic3r::CadDocument::mate_options ( int cs_a,
int cs_b ) const
1500{
1501 std::vector<MateOption> out(5);
1502 for (int k = 0; k < 5; ++k) out[k].kind = k;
1503
1504 auto is_connector = [&](int idx) -> bool {
1505 return idx >= 0 && idx < int(features.size()) &&
1506 features[idx].type == CadFeatureType::CoordSys &&
1507 features[idx].enabled;
1508 };
1509 if (!is_connector(cs_a) || !is_connector(cs_b)) {
1510 const char side = is_connector(cs_a) ? 'B' : 'A';
1511 const std::string reason = format(_u8L("connector %1% is not a coordinate system"), side);
1512 for (auto& o : out) { o.viable = false; o.reason = reason; }
1513 return out;
1514 }
1515 if (cs_a == cs_b) {
1516 for (auto& o : out) { o.viable = false; o.reason = _u8L("a mate needs two different connectors"); }
1517 return out;
1518 }
1519
1520 // B is the connector on the body that MOVES, so B must belong to one; A is the fixed
1521 // reference and needs no body. Without this every kind was offered on a Point(world)
1522 // connector and the mate only failed at RECOMPUTE, with "mate_cs_b has no associated body" —
1523 // one step too late, after the feature already existed in the tree. The same two conditions
1524 // the apply path throws on are checked here, so the offer and the kernel cannot disagree.
1525 const int body_b = features[cs_b].coordsys_body;
1526 if (body_b < 0) {
1527 for (auto& o : out) {
1528 o.viable = false;
1529 o.reason = _u8L("connector B is not attached to a body — a mate moves B's body");
1530 }
1531 return out;
1532 }
1533 if (body_b >= int(bodies.size()) || bodies[body_b].shape.IsNull()) {
1534 for (auto& o : out) {
1535 o.viable = false;
1536 o.reason = _u8L("connector B's body no longer exists");
1537 }
1538 return out;
1539 }
1540
1541 const int ka = features[cs_a].coordsys_face_kind;
1542 const int kb = features[cs_b].coordsys_face_kind;
1543
1544 // Planar: needs a flat face at both ends.
1545 const bool plan_bad_a = ka >= 0 && ka != GeomAbs_Plane;
1546 const bool plan_bad_b = kb >= 0 && kb != GeomAbs_Plane;
1547 if (plan_bad_a || plan_bad_b) {
1548 out[1].viable = false;
1549 if (plan_bad_a && plan_bad_b)
1550 out[1].reason = _u8L("needs a flat face at both ends — both connectors are on curved faces");
1551 else
1552 out[1].reason = format(_u8L("needs a flat face at both ends — connector %1% is on a curved face"),
1553 plan_bad_a ? 'A' : 'B');
1554 }
1555
1556 // Revolute and Cylindrical: need a cylindrical face at both ends.
1557 for (int k : {2, 4}) {
1558 const bool ax_bad_a = ka >= 0 && ka != GeomAbs_Cylinder;
1559 const bool ax_bad_b = kb >= 0 && kb != GeomAbs_Cylinder;
1560 if (!ax_bad_a && !ax_bad_b) continue;
1561 out[k].viable = false;
1562 if (ax_bad_a && ax_bad_b)
1563 out[k].reason = (ka == GeomAbs_Plane && kb == GeomAbs_Plane)
1564 ? _u8L("needs a cylindrical face at both ends — both connectors are on flat faces")
1565 : _u8L("needs a cylindrical face at both ends — both connectors are on non-cylindrical faces");
1566 else
1567 out[k].reason = format((ax_bad_a ? ka : kb) == GeomAbs_Plane
1568 ? _u8L("needs a cylindrical face at both ends — connector %1% is on a flat face")
1569 : _u8L("needs a cylindrical face at both ends — connector %1% is on a curved face"),
1570 ax_bad_a ? 'A' : 'B');
1571 }
1572
1573 return out;
1574}

References _u8L, bodies, Slic3r::CoordSys, features, and Slic3r::format().

Here is the call graph for this function:

◆ move_feature()

bool Slic3r::CadDocument::move_feature ( int index,
int delta )
2356{
2357 if (index < 0 || index >= int(features.size()))
2358 return false;
2359 int target = index + delta;
2360 if (target < 0 || target >= int(features.size()))
2361 return true; // clamped at the ends — no-op, not a failure
2362
2363 std::vector<CadFeature> snapshot = features;
2365 [index, target](int i) { return i == index ? target : i == target ? index : i; },
2366 [this](int i) { return features[i].enabled; });
2367 std::swap(features[index], features[target]);
2368
2369 // The two slots traded places: fix every feature reference that pointed at either —
2370 // a mate's connectors as well as sketch_ref, for the reason given in remove_feature().
2371 auto swap_ref = [index, target](int& ref) {
2372 if (ref == index) ref = target;
2373 else if (ref == target) ref = index;
2374 };
2375 for (auto& f : features)
2376 for_each_feature_ref(f, swap_ref);
2377
2378 // History runs top to bottom: a feature may only consume what comes BEFORE it. Moving a
2379 // consumer above its sketch (or a sketch below its consumer) would make it read whatever the
2380 // input held the last time round — or, for a Project, nothing. Refuse the move and say so.
2381 for (int fi = 0; fi < int(features.size()); ++fi) {
2382 CadFeature& f = features[fi];
2383 std::string bad;
2384 auto check = [&](int& ref) { if (ref >= fi && bad.empty()) bad = features[ref].name; };
2385 check(f.sketch_ref); check(f.sweep_path_ref); check(f.pattern_curve_sketch); check(f.rib_sketch_ref);
2386 for (int& r : f.loft_profile_refs) check(r);
2387 if (!bad.empty()) {
2388 // TRN Feature reorder error. %1% is the moved feature, %2% the feature it depends on.
2389 error = format(_u8L("\"%1%\" uses \"%2%\", so it cannot come before it"), f.name, bad);
2390 features.swap(snapshot);
2391 return false;
2392 }
2393 }
2394
2395 return commit_or_rollback(*this, snapshot);
2396}
static void for_each_feature_ref(CadFeature &f, Visit &&visit)
Definition CadDocument.cpp:2278
static void stage_history_change(std::vector< CadFeature > &features, const std::function< int(int)> &map_feature, const std::function< bool(int)> &enabled_after)
Definition CadDocument.cpp:2181
static bool commit_or_rollback(CadDocument &doc, std::vector< CadFeature > &snapshot)
Definition CadDocument.cpp:2236
bool check(ModelVolumeType volume_type)
Definition GLGizmoEmboss.cpp:515

References _u8L, Slic3r::commit_or_rollback(), error, Slic3r::f(), features, Slic3r::for_each_feature_ref(), Slic3r::format(), and Slic3r::stage_history_change().

Here is the call graph for this function:

◆ plane_of_face()

bool Slic3r::CadDocument::plane_of_face ( int body_idx,
int face_idx,
SketchPlane & out ) const
1640{
1641 if (face_idx < 0 || body_idx < 0 || body_idx >= int(bodies.size())) return false;
1642 const TopoDS_Face f = GeometryEngine::face_by_index(bodies[body_idx].shape, face_idx);
1643 if (f.IsNull()) return false;
1644 // Planar only. face_normal_world evaluates the normal at the mid parameter, which on a cylinder
1645 // or a fillet is a tangent plane at one arbitrary point — usable for a datum offset, wrong as a
1646 // sketch plane. Refuse rather than sketch somewhere the user did not point at.
1647 BRepAdaptor_Surface surf(f);
1648 if (surf.GetType() != GeomAbs_Plane) return false;
1650 return true;
1651}
static SketchPlane frame_from(const Vec3d &origin, const Vec3d &normal)
Definition CadDocument.cpp:1623

References bodies, Slic3r::f(), Slic3r::GeometryEngine::face_by_index(), Slic3r::GeometryEngine::face_centroid_world(), Slic3r::GeometryEngine::face_normal_world(), and Slic3r::frame_from().

Here is the call graph for this function:

◆ preview() [1/2]

bool Slic3r::CadDocument::preview ( const CadFeature & candidate,
TriangleMesh & out_mesh,
std::string & err ) const
4229{
4230 std::vector<TriangleMesh> ignore;
4231 return preview(candidate, out_mesh, ignore, err);
4232}
bool preview(const CadFeature &candidate, TriangleMesh &out_mesh, std::string &err) const
Definition CadDocument.cpp:4228

References preview().

Referenced by preview().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ preview() [2/2]

bool Slic3r::CadDocument::preview ( const CadFeature & candidate,
TriangleMesh & out_mesh,
std::vector< TriangleMesh > & out_body_meshes,
std::string & err ) const
4200{
4201 err.clear();
4202 out_body_meshes.clear();
4203 std::vector<CadBody> tmp = bodies; // start from the current committed bodies
4204 try {
4205 route_feature(tmp, candidate); // candidate may append a new body or mutate one
4206 } catch (const std::exception& e) {
4207 err = *e.what() ? e.what() : _u8L("OCCT operation failed");
4208 return false;
4209 } catch (...) {
4210 err = _u8L("unknown geometry error");
4211 return false;
4212 }
4213 if (tmp.empty()) {
4214 err = _u8L("preview produced no geometry");
4215 return false;
4216 }
4217 // Tessellate per body (same path as recompute) so the GUI can re-apply its display-only
4218 // per-body Move transforms to the ghost; out_mesh is the merged whole.
4219 std::vector<int> tf, tb;
4220 out_mesh = tessellate_bodies(tmp, tf, tb, out_body_meshes, linear_deflection, angular_deflection);
4221 if (out_mesh.its.indices.empty()) {
4222 err = _u8L("preview produced an empty mesh");
4223 return false;
4224 }
4225 return true;
4226}
double linear_deflection
Definition CadDocument.hpp:586
void route_feature(std::vector< CadBody > &bodies, const CadFeature &f) const
Definition CadDocument.cpp:3965
double angular_deflection
Definition CadDocument.hpp:587
static TriangleMesh tessellate_bodies(const std::vector< CadBody > &bodies, std::vector< int > &tri_face, std::vector< int > &tri_body, std::vector< TriangleMesh > &body_meshes, double lin, double ang)
Definition CadDocument.cpp:3217

References _u8L, angular_deflection, bodies, Slic3r::TriangleMesh::its, linear_deflection, route_feature(), and Slic3r::tessellate_bodies().

Here is the call graph for this function:

◆ produces_body()

bool Slic3r::CadDocument::produces_body ( CadFeatureType t)
static
521{
522 switch (t) {
525 return false;
526 default:
527 return true;
528 }
529}

References Slic3r::Axis, Slic3r::CoordSys, Slic3r::Helix, Slic3r::Plane, Slic3r::Project, and Slic3r::Sketch.

Referenced by Slic3r::GUI::DesignPanel::can_zoom_to_feature(), Slic3r::commit_or_rollback(), faces_made_by(), and Slic3r::GUI::DesignPanel::feature_faces().

Here is the caller graph for this function:

◆ project_edges_into_sketch()

int Slic3r::CadDocument::project_edges_into_sketch ( int sketch_feature,
int source_body,
const std::vector< int > & edge_ids,
int face )
1404{
1405 if (sketch_feature < 0 || sketch_feature >= int(features.size())) return -1;
1406 CadFeature& sk = features[sketch_feature];
1407 if (sk.type != CadFeatureType::Sketch && sk.type != CadFeatureType::Project) return -1;
1408 if (source_body < 0 || source_body >= int(bodies.size())
1409 || bodies[source_body].shape.IsNull()) return -1;
1410 const TopoDS_Shape& shape = bodies[source_body].shape;
1411
1412 std::vector<TopoDS_Edge> edges;
1413 if (!edge_ids.empty()) {
1414 for (int id : edge_ids) {
1415 TopoDS_Edge e = GeometryEngine::edge_by_index(shape, id);
1416 if (e.IsNull()) return -1;
1417 edges.push_back(e);
1418 }
1419 } else if (face >= 0) {
1420 TopoDS_Face fc = GeometryEngine::face_by_index(shape, face);
1421 if (fc.IsNull()) return -1;
1423 } else {
1424 edges = GeometryEngine::edges_of(shape);
1425 }
1426 if (edges.empty()) return -1;
1427
1428 const size_t before = sk.entities.size();
1429 project_edges_to_entities(edges, sk.plane, /*construction=*/true, sk.entities);
1430 return int(sk.entities.size() - before);
1431}

References bodies, Slic3r::GeometryEngine::edge_by_index(), Slic3r::GeometryEngine::edges_of(), Slic3r::GeometryEngine::edges_of_face(), Slic3r::CadFeature::entities, Slic3r::GeometryEngine::face_by_index(), features, Slic3r::CadFeature::plane, Slic3r::Project, Slic3r::project_edges_to_entities(), Slic3r::Sketch, and Slic3r::CadFeature::type.

Here is the call graph for this function:

◆ recompute()

bool Slic3r::CadDocument::recompute ( )
4070{
4071 error.clear();
4072 set_sketch_auto_close(auto_close_loops); // this document's weld rule, not the last one's
4074 std::vector<CadBody> built;
4075 // Did any feature in this document even ASK for a solid? A document made only of sketches
4076 // and datums has nothing to build, and that is a legitimate state — it is every document
4077 // between drawing the first profile and extruding it. Reporting it as a failure is what
4078 // made a sketch-only design unsaveable AND unopenable: DesignPanel::recompute_guarded syncs
4079 // the 3MF recipe only "on success", so nothing was written, and deserialize_recipe ends with
4080 // `return recompute()`, so a project that did carry a recipe was refused on load with
4081 // "Could not restore the CAD model" while its features sat correctly in the list. mtav.
4082 const bool any_solid_feature = std::any_of(features.begin(), features.end(), [](const CadFeature& f) {
4083 return f.enabled && produces_body(f.type);
4084 });
4085 try {
4087 for (size_t fi = 0; fi < features.size(); ++fi)
4088 replay_feature(fi, built);
4089 } catch (const std::exception& e) {
4090 error = *e.what() ? e.what() : _u8L("OCCT operation failed");
4091 return false;
4092 } catch (...) {
4093 error = _u8L("unknown geometry error");
4094 return false;
4095 }
4096 if (built.empty() && any_solid_feature) { error = _u8L("no solid-producing features"); return false; }
4097
4098 // A feature that leaves a body with a null shape must fail loudly. Until this existed,
4099 // recompute() returned true and the document kept advertising the body: describe_scene
4100 // counted it, error was empty, and only mass_properties on that specific body revealed
4101 // anything was wrong. Returning false hands the caller its normal rollback path, so the
4102 // operation that destroyed the body is undone rather than committed.
4103 for (size_t i = 0; i < built.size(); ++i) {
4104 if (!built[i].shape.IsNull()) continue;
4105 const int src = built[i].source_feature;
4106 // source_feature is -1 for a body no feature claims. "feature 0" would be a lie, and
4107 // this message exists precisely to be trusted about which feature to look at.
4108 const std::string fname =
4109 (src < 0 || src >= int(features.size()))
4110 ? _u8L("an unidentified feature")
4111 : (features[src].name.empty() ? format(_u8L("feature %1%"), src + 1)
4112 : features[src].name);
4113 error = format(_u8L("body %1% was destroyed by %2% (the operation produced an empty shape)"), i + 1, fname);
4114 return false;
4115 }
4116
4117 // recompute() replaces the bodies vector wholesale, which would drop any per-body
4118 // colour override (Color tool) and the name the user gave a body. They are carried forward
4119 // by the body's IDENTITY — the feature that made it and which of its bodies it is — not by
4120 // its index: a Boolean consuming its tool, a Mirror replacing its source or a Cut splitting
4121 // one body into two all shift every later index, and by index the colour and the name
4122 // moved to a different body.
4123 for (size_t i = 0; i < bodies.size(); ++i) {
4124 if (!bodies[i].has_color && !bodies[i].has_user_name) continue;
4125 const int j = find_body(built, body_id_of(bodies, int(i)));
4126 if (j < 0) continue;
4127 if (bodies[i].has_color) {
4128 built[j].has_color = true;
4129 built[j].color = bodies[i].color;
4130 }
4131 if (bodies[i].has_user_name) {
4132 built[j].has_user_name = true;
4133 built[j].user_name = bodies[i].user_name;
4134 }
4135 }
4136 bodies = std::move(built);
4137 // Datum references index the FINISHED body list; settle them against it, and close every
4138 // pending re-point now that both kinds have been through settle_body_refs.
4139 try {
4140 for (CadFeature& f : features) {
4141 if (!f.enabled) continue; // a hidden feature keeps its pending re-point for when it returns
4142 settle_body_refs(f, bodies, false);
4143 f.body_refs_pending = false;
4144 }
4145 } catch (const std::exception& e) {
4146 error = e.what();
4147 return false;
4148 }
4149 // The face and edge maps have just been rebuilt, so every global id handed out before this
4150 // point now means something else. Bump here rather than in each mutator: this is the single
4151 // line where the topology is actually replaced, so it cannot be forgotten by a new feature
4152 // type the way a per-mutator bump would be.
4154
4155 // Face-drift fingerprint for FaceAndDirection CoordSys connectors. This runs AFTER the
4156 // bodies are final and APPENDS to mate_conflicts (detect_mate_conflicts() cleared it at
4157 // the top of recompute() and must not run again here). A mismatch is a WARNING, not an
4158 // error: a legitimate Draft on a mated face renumbers nothing but a real renumber after a
4159 // dress-up silently points the connector at a different face, and that must not abort.
4160 for (int fi = 0; fi < int(features.size()); ++fi) {
4161 CadFeature& f = features[fi];
4162 if (!f.enabled) continue;
4163 if (f.type != CadFeatureType::CoordSys) continue;
4164 if (f.coordsys_type != CoordSysType::FaceAndDirection) continue;
4165 if (f.coordsys_body < 0 || f.coordsys_body >= int(bodies.size())) continue;
4166 TopoDS_Face face = GeometryEngine::face_by_index(bodies[f.coordsys_body].shape, f.coordsys_face);
4167 if (face.IsNull()) continue; // "face not found" is already reported by datum_frame()
4168 const int kind = int(BRepAdaptor_Surface(face).GetType());
4169 const int edges = int(GeometryEngine::edges_of_face(face).size());
4170 if (f.coordsys_face_kind < 0) {
4171 // No fingerprint yet (old recipe, or a connector never resolved): adopt the state
4172 // the user last saw. Self-heals old recipes on first load, and gives both existing
4173 // writers the fingerprint for free.
4174 f.coordsys_face_kind = kind;
4175 f.coordsys_face_edges = edges;
4176 continue;
4177 }
4178 if (f.coordsys_face_kind != kind || f.coordsys_face_edges != edges) {
4179 mate_conflicts.emplace_back(int(fi),
4180 format(_u8L("Connector \"%1%\" may have moved to a different face (an upstream edit "
4181 "renumbered this body's faces)"), f.name));
4182 }
4183 }
4184
4189 if (display_mesh.its.indices.empty() && any_solid_feature) {
4190 // Empty only because there are no bodies to tessellate is the same legitimate state as
4191 // above: a sketch-only document has nothing to draw as a solid, and that is not a fault.
4192 error = _u8L("tessellation produced an empty mesh");
4193 return false;
4194 }
4195 return true;
4196}
void bind_expressions()
Definition CadDocument.cpp:4036
void detect_mate_conflicts()
Definition CadDocument.cpp:3688
void replay_feature(size_t fi, std::vector< CadBody > &built)
Definition CadDocument.cpp:4044
static TopoDS_Shape compound_of(const std::vector< CadBody > &bodies)
Definition CadDocument.cpp:3204
static void settle_body_refs(CadFeature &f, const std::vector< CadBody > &v, bool running)
Definition CadDocument.cpp:2150

References _u8L, angular_deflection, auto_close_loops, bind_expressions(), bodies, body, Slic3r::body_id_of(), Slic3r::compound_of(), Slic3r::CoordSys, detect_mate_conflicts(), display_body_meshes, display_mesh, display_tri_body, display_tri_face, Slic3r::GeometryEngine::edges_of_face(), error, Slic3r::f(), Slic3r::GeometryEngine::face_by_index(), Slic3r::FaceAndDirection, features, Slic3r::find_body(), Slic3r::format(), linear_deflection, mate_conflicts, replay_feature(), Slic3r::set_sketch_auto_close(), Slic3r::settle_body_refs(), Slic3r::tessellate_bodies(), and topo_generation.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_axis(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_boolean(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_bridge(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_chamfer(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_coordsys(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_delete_face(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_draft(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_extrude(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_fillet(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_hole(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_hole_standard(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_hole_styled(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_mate(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_mirror(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_pattern(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_project(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_revolve(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_rib(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_set_feature_expr(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_set_variable(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_shell(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_split(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_extrude(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_fill(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_loft(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_offset(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_revolve(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_thicken(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_thicken_surface(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_transform(), Slic3r::commit_or_rollback(), deserialize_recipe(), Slic3r::GUI::DesignPanel::fill_body_choice(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::import_mesh(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::import_step(), Slic3r::GUI::DesignPanel::populate_body_choices(), redo(), and undo().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ redo()

bool Slic3r::CadDocument::redo ( )
2075{
2076 if (m_redo.empty())
2077 return false;
2078 m_undo.push_back(Snapshot{std::move(features), std::move(variables)});
2079 features = std::move(m_redo.back().features);
2080 variables = std::move(m_redo.back().variables);
2081 m_redo.pop_back();
2082 recompute();
2083 return true;
2084}

References features, m_redo, m_undo, recompute(), and variables.

Here is the call graph for this function:

◆ redo_depth()

size_t Slic3r::CadDocument::redo_depth ( ) const
inline
811{ return m_redo.size(); }

References m_redo.

◆ remove_feature()

bool Slic3r::CadDocument::remove_feature ( int index)
2291{
2292 if (index < 0 || index >= int(features.size()))
2293 return false;
2294
2295 std::vector<CadFeature> snapshot = features;
2296
2297 // Deleting a Sketch cascades to every feature that consumes it AS ITS PROFILE — the
2298 // reason is the original one ("a dangling Extrude would have no wire"), and it applies
2299 // unchanged to Revolve, Sweep, Rib and the Surface* family, which all read the profile
2300 // through sketch_ref, plus the sweep spine and the rib line. Testing the FIELD rather
2301 // than the type is what makes that true without a list to keep up to date.
2302 //
2303 // pattern_curve_sketch and loft_profile_refs are deliberately NOT cascaded: a Pattern or
2304 // a Loft that loses one of several inputs is degraded, not meaningless, so those refs go
2305 // to -1 in the remap below and the feature survives. A lone Sketch is harmless either way.
2306 std::vector<int> remove{index};
2307 if (features[index].type == CadFeatureType::Sketch) {
2308 for (int j = 0; j < int(features.size()); ++j) {
2309 const CadFeature& c = features[j];
2310 if (c.sketch_ref == index || c.sweep_path_ref == index || c.rib_sketch_ref == index)
2311 remove.push_back(j);
2312 }
2313 }
2314 std::sort(remove.begin(), remove.end());
2315 remove.erase(std::unique(remove.begin(), remove.end()), remove.end());
2316
2318 [&remove](int i) {
2319 if (std::binary_search(remove.begin(), remove.end(), i)) return -1;
2320 int shift = 0;
2321 for (int r : remove) if (r < i) ++shift;
2322 return i - shift;
2323 },
2324 [this](int i) { return features[i].enabled; });
2325
2326 // Erase high-to-low so earlier indices stay valid.
2327 for (auto it = remove.rbegin(); it != remove.rend(); ++it)
2328 features.erase(features.begin() + *it);
2329
2330 // Remap surviving feature references through the deletions: subtract the count of
2331 // removed indices that sat before it; orphaned refs (target removed) -> -1.
2332 //
2333 // This must cover EVERY field holding a feature index, not just sketch_ref. A mate's two
2334 // connectors are such indices, and leaving them behind slid them onto whatever features
2335 // landed on those slots — silently, because recompute() only rejects out-of-range and
2336 // non-CoordSys targets, and a shifted index usually lands on the assembly's other CoordSys.
2337 auto remap = [&remove](int& ref) {
2338 if (ref < 0)
2339 return;
2340 if (std::binary_search(remove.begin(), remove.end(), ref)) {
2341 ref = -1;
2342 } else {
2343 int shift = 0;
2344 for (int r : remove)
2345 if (r < ref) ++shift;
2346 ref -= shift;
2347 }
2348 };
2349 for (auto& f : features)
2350 for_each_feature_ref(f, remap);
2351
2352 return commit_or_rollback(*this, snapshot);
2353}

References Slic3r::commit_or_rollback(), Slic3r::f(), features, Slic3r::for_each_feature_ref(), Slic3r::CadFeature::rib_sketch_ref, Slic3r::Sketch, Slic3r::CadFeature::sketch_ref, Slic3r::stage_history_change(), and Slic3r::CadFeature::sweep_path_ref.

Here is the call graph for this function:

◆ replace_feature()

bool Slic3r::CadDocument::replace_feature ( int index,
const CadFeature & edited )
2410{
2411 if (index < 0 || index >= int(features.size()))
2412 return false;
2413
2414 std::vector<CadFeature> snapshot = features;
2415
2416 // Preserve identity (name) and the structural link (sketch_ref) from the
2417 // original; only the user-editable parameters come from `edited`.
2418 CadFeature f = edited;
2419 f.name = features[index].name;
2420 f.type = features[index].type;
2421 if (f.type == CadFeatureType::Extrude)
2422 f.sketch_ref = features[index].sketch_ref;
2423 features[index] = f;
2424
2425 return commit_or_rollback(*this, snapshot);
2426}

References Slic3r::commit_or_rollback(), Slic3r::Extrude, Slic3r::f(), and features.

Referenced by Slic3r::GUI::DesignPanel::refresh_preview().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ replace_sketch_extrude()

bool Slic3r::CadDocument::replace_sketch_extrude ( int sketch_idx,
int extrude_idx,
const CadFeature & edited )
2430{
2431 if (sketch_idx < 0 || sketch_idx >= int(features.size())) return false;
2432 if (extrude_idx < 0 || extrude_idx >= int(features.size())) return false;
2433
2434 std::vector<CadFeature> snapshot = features;
2435
2436 // A box in the tree is two linked features: the Sketch consumes the profile
2437 // params (shape/plane/width/height/radius), the Extrude consumes the solid
2438 // params (distance/symmetric/mode). `edited` carries all of them; split it
2439 // back into the two slots, preserving each slot's name/type and the link.
2440 CadFeature& sk = features[sketch_idx];
2441 sk.shape = edited.shape;
2442 sk.plane = edited.plane;
2443 sk.width = edited.width;
2444 sk.height = edited.height;
2445 sk.radius = edited.radius;
2446
2447 CadFeature& ex = features[extrude_idx];
2448 ex.distance = edited.distance;
2449 ex.symmetric = edited.symmetric;
2450 ex.mode = edited.mode;
2451
2452 return commit_or_rollback(*this, snapshot);
2453}

References Slic3r::commit_or_rollback(), Slic3r::CadFeature::distance, features, Slic3r::CadFeature::height, Slic3r::CadFeature::mode, Slic3r::CadFeature::plane, Slic3r::CadFeature::radius, Slic3r::CadFeature::shape, Slic3r::CadFeature::symmetric, and Slic3r::CadFeature::width.

Here is the call graph for this function:

◆ replay_feature()

void Slic3r::CadDocument::replay_feature ( size_t fi,
std::vector< CadBody > & built )
private
4045{
4046 CadFeature& f = features[fi];
4047 if (!f.enabled) return;
4048 if (f.type == CadFeatureType::Sketch) return; // consumed by an extrude
4049 if (f.type == CadFeatureType::Helix) return; // consumed by Sweep as a path
4050 if (f.type == CadFeatureType::Plane) return; // datum: no solid, derived on demand
4051 if (f.type == CadFeatureType::Axis) return; // datum axis
4052 if (f.type == CadFeatureType::CoordSys) return; // datum coordinate system
4053 settle_body_refs(f, built, true);
4054 if (f.type == CadFeatureType::Project) { apply_project(built, f); }
4055 else { route_feature(built, f); }
4056 // Record which feature made each body. "Still unset?" is the whole rule, and it is
4057 // sufficient because of an invariant worth stating: NO feature ever replaces a whole
4058 // CadBody. Every in-place op writes only `.shape` (boolean, cut, mirror-fuse,
4059 // transform, dress-up — checked, all 8 sites), so an existing body keeps the stamp it
4060 // was born with; a consumed body is erased outright, taking its stamp with it; and
4061 // the only bodies still at -1 here are the ones THIS feature just pushed. That also
4062 // means a feature type added later needs no change here, as long as it keeps to the
4063 // same invariant.
4064 for (CadBody& b : built)
4065 if (b.source_feature < 0)
4066 b.source_feature = int(fi);
4067}
void apply_project(const std::vector< CadBody > &bodies, CadFeature &f) const
Definition CadDocument.cpp:3654

References apply_project(), Slic3r::Axis, Slic3r::CoordSys, Slic3r::f(), features, Slic3r::Helix, Slic3r::Plane, Slic3r::Project, route_feature(), Slic3r::settle_body_refs(), and Slic3r::Sketch.

Referenced by faces_made_by(), and recompute().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ resolve_datum_axes()

std::vector< CadDocument::DatumAxis > Slic3r::CadDocument::resolve_datum_axes ( ) const
1819{
1820 std::vector<DatumAxis> out;
1821 // For PlaneIntersection we need the already-resolved datum planes.
1822 std::vector<std::pair<std::string, SketchPlane>> datum_planes = resolve_datum_planes();
1823
1824 auto resolve_face = [&](int body_idx, int face_idx) -> TopoDS_Face {
1825 if (face_idx < 0 || body_idx < 0 || body_idx >= int(bodies.size()))
1826 return TopoDS_Face();
1827 return GeometryEngine::face_by_index(bodies[body_idx].shape, face_idx);
1828 };
1829 auto resolve_edge = [&](int body_idx, int edge_idx,
1830 Vec3d& p0, Vec3d& dir) -> bool {
1831 if (edge_idx < 0 || body_idx < 0 || body_idx >= int(bodies.size()))
1832 return false;
1833 TopoDS_Edge e = GeometryEngine::edge_by_index(bodies[body_idx].shape, edge_idx);
1834 if (e.IsNull()) return false;
1836 if (pts.size() < 2) return false;
1837 p0 = pts.front();
1838 dir = (pts.back() - pts.front()).normalized();
1839 return true;
1840 };
1841
1842 for (const CadFeature& f : features) {
1843 if (f.type != CadFeatureType::Axis || !f.enabled) continue;
1844
1845 DatumAxis da;
1846 da.name = f.name;
1847 switch (f.axis_type) {
1848 case AxisType::TwoPoints: {
1849 Vec3d dir = f.axis_p2 - f.axis_p1;
1850 if (dir.squaredNorm() < 1e-18) { da.error = _u8L("two points are coincident"); break; }
1851 da.origin = f.axis_p1;
1852 da.direction = dir.normalized();
1853 break;
1854 }
1855 case AxisType::FaceNormal: {
1856 TopoDS_Face fc = resolve_face(f.axis_body, f.axis_face);
1857 if (fc.IsNull()) { da.error = _u8L("face not found"); break; }
1859 da.direction = GeometryEngine::face_normal_world(fc);
1860 break;
1861 }
1863 TopoDS_Face fc = resolve_face(f.axis_body, f.axis_face);
1864 if (fc.IsNull()) { da.error = _u8L("face not found"); break; }
1865 GeometryEngine::CylinderFace cyl = GeometryEngine::cylinder_of_face(fc);
1866 if (!cyl.ok) { da.error = _u8L("face is not a cylinder"); break; }
1867 da.origin = cyl.base;
1868 da.direction = cyl.axis;
1869 break;
1870 }
1871 case AxisType::AlongEdge: {
1872 Vec3d p0, dir;
1873 if (!resolve_edge(f.axis_body, f.axis_edge, p0, dir)) {
1874 da.error = _u8L("edge not found"); break;
1875 }
1876 da.origin = p0;
1877 da.direction = dir;
1878 break;
1879 }
1881 // Same reference encoding as CadFeature::plane_base, because the GUI fills these
1882 // two fields from populate_plane_choices() — whose rows are XY/XZ/YZ followed by
1883 // the datum planes — and stores the row verbatim. Indexing datum_planes[ref]
1884 // directly, as this did, is off by three: picking XY resolved to datum plane 0 and
1885 // picking the first datum ran off the end with "plane ref not found", so the
1886 // PlaneIntersection axis type could not work at all. Two base planes are also a
1887 // perfectly ordinary way to define an axis (XY x XZ = the X axis), so they must
1888 // resolve rather than be rejected as out of scope.
1889 auto base_plane = [&](int ref, Vec3d& origin, Vec3d& normal) -> bool {
1890 SketchPlane p;
1891 if (ref == 0) { p = SketchPlane::XY(); p.origin += modeling_origin; }
1892 else if (ref == 1) { p = SketchPlane::XZ(); p.origin += modeling_origin; }
1893 else if (ref == 2) { p = SketchPlane::YZ(); p.origin += modeling_origin; }
1894 else if (ref >= 3 && ref - 3 < int(datum_planes.size()))
1895 p = datum_planes[ref - 3].second; // datums are already world-space
1896 else
1897 return false;
1898 origin = p.origin;
1899 normal = p.normal;
1900 return true;
1901 };
1902 bool ok0 = base_plane(f.axis_plane_a, da.origin, da.direction); // direction reused as normal0
1903 Vec3d origin1, normal1;
1904 bool ok1 = base_plane(f.axis_plane_b, origin1, normal1);
1905 if (!ok0 || !ok1) { da.error = _u8L("plane ref not found"); break; }
1906 // Direction = cross product of the two plane normals.
1907 Vec3d dir = da.direction.cross(normal1); // da.direction was normal0
1908 if (dir.squaredNorm() < 1e-18) {
1909 da.error = _u8L("planes are parallel (no intersection)"); break;
1910 }
1911 dir.normalize();
1912 // Find a point on the intersection line: closest points between two planes.
1913 // Project origin of plane A onto the intersection line.
1914 Vec3d n0 = da.direction; // normal of plane A (stored temporarily)
1915 Vec3d n1 = normal1;
1916 Vec3d p0 = da.origin;
1917 Vec3d p1 = origin1;
1918 // Solve for point on line of intersection using vector formula.
1919 double d0 = n0.dot(p0);
1920 double d1 = n1.dot(p1);
1921 double n0n1 = n0.dot(n1);
1922 double det = 1.0 - n0n1 * n0n1;
1923 if (std::abs(det) < 1e-18) { da.error = _u8L("planes are parallel (no intersection)"); break; }
1924 double t0 = (d0 - d1 * n0n1) / det;
1925 double t1 = (d1 - d0 * n0n1) / det;
1926 da.origin = n0 * t0 + n1 * t1;
1927 da.direction = dir;
1928 break;
1929 }
1930 }
1931 out.push_back(da);
1932 }
1933 return out;
1934}
std::string name
Definition CadDocument.hpp:744
std::vector< std::pair< std::string, SketchPlane > > resolve_datum_planes() const
Definition CadDocument.cpp:1653
Definition CadDocument.hpp:744
static CylinderFace cylinder_of_face(const TopoDS_Face &face)
Definition GeometryEngine.cpp:639
Point normal(Point pt, double scale)
Definition TreeSupport.cpp:112
@ FaceNormal
Definition CadDocument.hpp:29
@ TwoPoints
Definition CadDocument.hpp:29
@ CylinderCenterline
Definition CadDocument.hpp:29
@ PlaneIntersection
Definition CadDocument.hpp:29
@ AlongEdge
Definition CadDocument.hpp:29
static SketchPlane YZ()
Definition SketchEngine.hpp:62
static SketchPlane XZ()
Definition SketchEngine.hpp:61

References _u8L, Slic3r::AlongEdge, Slic3r::Axis, Slic3r::GeometryEngine::CylinderFace::axis, Slic3r::GeometryEngine::CylinderFace::base, bodies, Slic3r::GeometryEngine::cylinder_of_face(), Slic3r::CylinderCenterline, Slic3r::CadDocument::DatumAxis::direction, Slic3r::GeometryEngine::edge_by_index(), Slic3r::CadDocument::DatumAxis::error, Slic3r::f(), Slic3r::GeometryEngine::face_by_index(), Slic3r::GeometryEngine::face_centroid_world(), Slic3r::GeometryEngine::face_normal_world(), Slic3r::FaceNormal, features, modeling_origin, Slic3r::CadDocument::DatumAxis::name, Slic3r::normal(), Slic3r::SketchPlane::normal, Slic3r::GeometryEngine::CylinderFace::ok, Slic3r::CadDocument::DatumAxis::origin, Slic3r::SketchPlane::origin, Slic3r::PlaneIntersection, resolve_datum_planes(), Slic3r::GeometryEngine::sample_edge_world(), Slic3r::TwoPoints, Slic3r::SketchPlane::XY(), Slic3r::SketchPlane::XZ(), and Slic3r::SketchPlane::YZ().

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_axis().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ resolve_datum_coordsys()

std::vector< CadDocument::DatumCoordSys > Slic3r::CadDocument::resolve_datum_coordsys ( ) const
2017{
2018 std::vector<DatumCoordSys> out;
2019 for (const CadFeature& f : features) {
2020 if (f.type != CadFeatureType::CoordSys || !f.enabled) continue;
2021 out.push_back(datum_frame(bodies, f));
2022 }
2023 return out;
2024}

References bodies, Slic3r::CoordSys, datum_frame(), Slic3r::f(), and features.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_coordsys().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ resolve_datum_planes()

std::vector< std::pair< std::string, SketchPlane > > Slic3r::CadDocument::resolve_datum_planes ( ) const
1654{
1655 std::vector<std::pair<std::string, SketchPlane>> out;
1656 for (const CadFeature& f : features) {
1657 if (f.type != CadFeatureType::Plane || !f.enabled) continue;
1658
1659 // Resolve base reference plane. The default XY/XZ/YZ planes pass through the modeling
1660 // origin (bed centre); datum bases (>=3) are already in world coords from earlier passes.
1661 SketchPlane base;
1662 bool base_missing = false;
1663 if (f.plane_base == 1) { base = SketchPlane::XZ(); base.origin += modeling_origin; }
1664 else if (f.plane_base == 2) { base = SketchPlane::YZ(); base.origin += modeling_origin; }
1665 else if (f.plane_base >= 3) {
1666 const int di = f.plane_base - 3;
1667 if (di < int(out.size())) base = out[di].second;
1668 else {
1669 // The datum it was built on is gone or hidden. Fall back to XY through the
1670 // modeling origin like the other defaults, and SAY so in the plane's name —
1671 // this list is what the GUI shows — instead of quietly becoming world XY.
1672 base = SketchPlane::XY(); base.origin += modeling_origin;
1673 base_missing = true;
1674 }
1675 }
1676 else { base = SketchPlane::XY(); base.origin += modeling_origin; }
1677
1678 // --- Resolve refs from bodies ---
1679 auto resolve_face = [&](int body_idx, int face_idx) -> TopoDS_Face {
1680 if (face_idx < 0 || body_idx < 0 || body_idx >= int(bodies.size()))
1681 return TopoDS_Face();
1682 return GeometryEngine::face_by_index(bodies[body_idx].shape, face_idx);
1683 };
1684 auto resolve_edge = [&](int body_idx, int edge_idx,
1685 Vec3d& p0, Vec3d& dir) -> bool {
1686 if (edge_idx < 0 || body_idx < 0 || body_idx >= int(bodies.size()))
1687 return false;
1688 TopoDS_Edge e = GeometryEngine::edge_by_index(bodies[body_idx].shape, edge_idx);
1689 if (e.IsNull()) return false;
1691 if (pts.size() < 2) return false;
1692 p0 = pts.front();
1693 dir = (pts.back() - pts.front()).normalized();
1694 return true;
1695 };
1696
1697 // Face A
1698 TopoDS_Face faceA = resolve_face(f.plane_face_body, f.plane_face);
1699 SketchPlane faceA_plane;
1700 bool has_faceA = false;
1701 if (!faceA.IsNull()) {
1702 faceA_plane = frame_from(
1705 has_faceA = true;
1706 }
1707
1708 // Face B
1709 TopoDS_Face faceB = resolve_face(f.plane_face2_body, f.plane_face2);
1710 SketchPlane faceB_plane;
1711 bool has_faceB = false;
1712 if (!faceB.IsNull()) {
1713 faceB_plane = frame_from(
1716 has_faceB = true;
1717 }
1718
1719 // Edge A
1720 Vec3d eA_p0, eA_dir;
1721 bool has_edgeA = resolve_edge(f.plane_edge_body, f.plane_edge, eA_p0, eA_dir);
1722
1723 // Edge B
1724 Vec3d eB_p0, eB_dir;
1725 bool has_edgeB = resolve_edge(f.plane_edge2_body, f.plane_edge2, eB_p0, eB_dir);
1726
1727 // --- Dispatch on plane_type ---
1728 auto fallback_offset = [&]() {
1729 return offset_angle_plane(base, f.plane_offset, f.plane_angle_tilt, f.plane_axis);
1730 };
1731
1732 SketchPlane result;
1733 switch (f.plane_type) {
1734
1735 case PlaneType::Offset: {
1736 // From a picked face: pure offset along its normal. From a base/datum plane:
1737 // offset + the legacy tilt-about-axis (keeps the old Offset/Tilt controls live).
1738 if (has_faceA)
1739 result = frame_from(faceA_plane.origin + faceA_plane.normal * f.plane_offset,
1740 faceA_plane.normal);
1741 else
1742 result = offset_angle_plane(base, f.plane_offset, f.plane_angle_tilt, f.plane_axis);
1743 break;
1744 }
1745
1746 case PlaneType::Coincident: {
1747 result = has_faceA ? faceA_plane : base;
1748 break;
1749 }
1750
1751 case PlaneType::Angle: {
1752 if (!has_edgeA) { result = fallback_offset(); break; }
1753 const SketchPlane& ref = has_faceA ? faceA_plane : base;
1754 Vec3d n0 = ref.normal - eA_dir * ref.normal.dot(eA_dir);
1755 if (n0.squaredNorm() < 1e-12) {
1756 Vec3d perp = (std::abs(eA_dir.z()) < 0.9) ? Vec3d(0, 0, 1) : Vec3d(1, 0, 0);
1757 n0 = perp - eA_dir * perp.dot(eA_dir);
1758 }
1759 n0.normalize();
1760 const double a = f.plane_angle_tilt * M_PI / 180.0;
1761 Vec3d n_rot = n0 * std::cos(a) + eA_dir.cross(n0) * std::sin(a)
1762 + eA_dir * (eA_dir.dot(n0)) * (1.0 - std::cos(a));
1763 result = frame_from(eA_p0, n_rot);
1764 break;
1765 }
1766
1767 case PlaneType::Midplane: {
1768 if (!has_faceA || !has_faceB) { result = fallback_offset(); break; }
1769 Vec3d origin = 0.5 * (faceA_plane.origin + faceB_plane.origin);
1770 Vec3d nB = (faceA_plane.normal.dot(faceB_plane.normal) >= 0)
1771 ? faceB_plane.normal : -faceB_plane.normal;
1772 Vec3d normal = (faceA_plane.normal + nB).normalized();
1773 result = frame_from(origin, normal);
1774 break;
1775 }
1776
1777 case PlaneType::Tangent: {
1778 if (!has_faceA) { result = fallback_offset(); break; }
1779 GeometryEngine::CylinderFace cyl = GeometryEngine::cylinder_of_face(faceA);
1780 if (!cyl.ok) { result = fallback_offset(); break; }
1781 Vec3d refdir = (std::abs(cyl.axis.z()) < 0.9) ? Vec3d(0, 0, 1) : Vec3d(1, 0, 0);
1782 refdir = refdir - cyl.axis * refdir.dot(cyl.axis);
1783 refdir.normalize();
1784 const double theta = f.plane_angle_tilt * M_PI / 180.0;
1785 Vec3d r = refdir * std::cos(theta) + cyl.axis.cross(refdir) * std::sin(theta);
1786 Vec3d touch = cyl.base + r * cyl.radius;
1787 result = frame_from(touch, r);
1788 break;
1789 }
1790
1791 case PlaneType::TwoEdges: {
1792 if (!has_edgeA) { result = fallback_offset(); break; }
1793 if (!has_edgeB) { result = fallback_offset(); break; }
1794 Vec3d cross = eA_dir.cross(eB_dir);
1795 if (cross.squaredNorm() > 1e-12) {
1796 result = frame_from(eA_p0, cross.normalized());
1797 } else {
1798 Vec3d v = eA_dir.cross(eB_p0 - eA_p0);
1799 if (v.squaredNorm() > 1e-12) {
1800 result = frame_from(eA_p0, v.normalized());
1801 } else {
1802 result = fallback_offset();
1803 }
1804 }
1805 break;
1806 }
1807
1808 }
1809
1810 // TRN Datum plane entry in the reference plane list. %1% is the plane name.
1811 out.emplace_back(base_missing ? format(_u8L("%1% (base plane missing)"), f.name) : f.name, result);
1812 }
1813 return out;
1814}
@ TwoEdges
Definition CadDocument.hpp:28
@ Angle
Definition CadDocument.hpp:28
@ Tangent
Definition CadDocument.hpp:28
@ Coincident
Definition CadDocument.hpp:28
@ Midplane
Definition CadDocument.hpp:28
@ Offset
Definition CadDocument.hpp:28
static SketchPlane offset_angle_plane(const SketchPlane &base, double offset, double angle_deg, int axis)
Definition CadDocument.cpp:1599
T cross(const boost::geometry::model::d2::point_xy< T > &v1, const boost::geometry::model::d2::point_xy< T > &v2)
Definition ExtrusionSimulator.cpp:168

References _u8L, Slic3r::Angle, Slic3r::GeometryEngine::CylinderFace::axis, Slic3r::GeometryEngine::CylinderFace::base, bodies, Slic3r::Coincident, Slic3r::cross(), Slic3r::GeometryEngine::cylinder_of_face(), Slic3r::GeometryEngine::edge_by_index(), Slic3r::f(), Slic3r::GeometryEngine::face_by_index(), Slic3r::GeometryEngine::face_centroid_world(), Slic3r::GeometryEngine::face_normal_world(), features, Slic3r::format(), Slic3r::frame_from(), M_PI, Slic3r::Midplane, modeling_origin, Slic3r::normal(), Slic3r::SketchPlane::normal, Slic3r::Offset, Slic3r::offset_angle_plane(), Slic3r::GeometryEngine::CylinderFace::ok, Slic3r::SketchPlane::origin, Slic3r::perp(), Slic3r::Plane, Slic3r::GeometryEngine::CylinderFace::radius, Slic3r::GeometryEngine::sample_edge_world(), Slic3r::Tangent, Slic3r::TwoEdges, Slic3r::SketchPlane::XY(), Slic3r::SketchPlane::XZ(), and Slic3r::SketchPlane::YZ().

Referenced by resolve_datum_axes().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ route_feature()

void Slic3r::CadDocument::route_feature ( std::vector< CadBody > & bodies,
const CadFeature & f ) const
private
3966{
3967 if (f.type == CadFeatureType::Plane) return; // datum plane: not part of the body pipeline
3968 if (f.type == CadFeatureType::Axis) return; // datum axis
3969 if (f.type == CadFeatureType::CoordSys) return; // datum coordinate system
3970 if (f.type == CadFeatureType::Helix) return; // helical curve; consumed by Sweep
3971 if (f.type == CadFeatureType::Boolean) { apply_boolean(bodies, f); return; } // body-body op
3972 if (f.type == CadFeatureType::Cut) { apply_cut(bodies, f); return; } // plane-split body
3973 if (f.type == CadFeatureType::Mirror) { apply_mirror(bodies, f); return; } // mirror body about plane
3974 if (f.type == CadFeatureType::Transform) { apply_transform(bodies, f); return; } // move/rotate body
3975 if (f.type == CadFeatureType::Mate) { apply_mate(bodies, f); return; } // assembly mate
3976 if (f.type == CadFeatureType::Thicken) { apply_thicken(bodies, f); return; } // face -> plate
3978 if (f.type == CadFeatureType::SurfaceOffset) { apply_surface_offset(bodies, f); return; }
3979 if (f.type == CadFeatureType::Project) return; // sketch-like: consumed downstream, no body
3980 // Resolve the target body: explicit target_body when valid, else the last body.
3981 const int t = pick_body(f.target_body, int(bodies.size()));
3982 const TopoDS_Shape context = (t >= 0) ? bodies[t].shape : TopoDS_Shape();
3983 // A New extrude (or the very first solid feature) starts a fresh body; everything else
3984 // mutates the target body in place.
3985 const bool starts_new = bodies.empty()
3986 || f.type == CadFeatureType::Import // an imported solid is always its own base body
3990 || ((f.type == CadFeatureType::Extrude || f.type == CadFeatureType::Revolve
3991 || f.type == CadFeatureType::Sweep || f.type == CadFeatureType::Loft)
3992 && f.mode == BooleanMode::New);
3993
3994 if (starts_new) {
3995 TopoDS_Shape result; // empty -> apply_feature fills it (New path)
3996 bool have_body = false;
3997 apply_feature(result, have_body, context, f);
3998 if (have_body && !result.IsNull())
3999 bodies.push_back({ result, f.name.empty() ? _u8L("Body") : f.name });
4000 } else {
4001 if (t < 0) throw std::runtime_error(_u8L("feature needs a body"));
4002 TopoDS_Shape result = bodies[t].shape; // shallow handle; apply_feature mutates it
4003 bool have_body = true;
4004 // A subtraction whose tool misses the target is a legal boolean that removes nothing, so
4005 // OCCT reports IsDone() and the feature lands in the recipe reporting success. A caller —
4006 // an agent especially — then has no signal at all that the hole it asked for was never
4007 // drilled: same body, same volume, ok:true. Measure the volume across the op and refuse
4008 // the no-op. Only for removals: every other feature type may legitimately leave the volume
4009 // alone (a Transform certainly does). daf.
4010 const bool removes = f.type == CadFeatureType::Hole
4011 || f.type == CadFeatureType::Thread
4012 || ((f.type == CadFeatureType::Extrude || f.type == CadFeatureType::Revolve
4013 || f.type == CadFeatureType::Sweep || f.type == CadFeatureType::Loft)
4014 && f.mode == BooleanMode::Cut);
4015 const double before = removes ? solid_volume(result) : 0.0;
4016 apply_feature(result, have_body, context, f);
4017 if (removes && before > 0.0) {
4018 const double after = solid_volume(result);
4019 // Relative tolerance: a cut that shaves a numerically invisible sliver is a miss too,
4020 // and an absolute epsilon would be wrong across the mm-to-metre range of real parts.
4021 if (after >= before - 1e-9 * std::max(1.0, before))
4022 throw std::runtime_error(
4023 f.type == CadFeatureType::Hole ?
4024 _u8L("hole removed no material — the tool does not intersect the target body"
4025 " (coordinates are in the sketch plane's frame, not world)") :
4026 f.type == CadFeatureType::Thread ?
4027 _u8L("thread removed no material — the tool does not intersect the target body"
4028 " (coordinates are in the sketch plane's frame, not world)") :
4029 _u8L("cut removed no material — the tool does not intersect the target body"
4030 " (coordinates are in the sketch plane's frame, not world)"));
4031 }
4032 bodies[t].shape = result;
4033 }
4034}
void apply_thicken_surface(std::vector< CadBody > &bodies, const CadFeature &f) const
Definition CadDocument.cpp:3460
void apply_transform(std::vector< CadBody > &bodies, const CadFeature &f) const
Definition CadDocument.cpp:3396
void apply_mirror(std::vector< CadBody > &bodies, const CadFeature &f) const
Definition CadDocument.cpp:3349
void apply_thicken(std::vector< CadBody > &bodies, const CadFeature &f) const
Definition CadDocument.cpp:3425
void apply_boolean(std::vector< CadBody > &bodies, const CadFeature &f) const
Definition CadDocument.cpp:3240
void apply_surface_offset(std::vector< CadBody > &bodies, const CadFeature &f) const
Definition CadDocument.cpp:3563
void apply_mate(std::vector< CadBody > &bodies, const CadFeature &f) const
Definition CadDocument.cpp:3792
void apply_cut(std::vector< CadBody > &bodies, const CadFeature &f) const
Definition CadDocument.cpp:3278
void apply_feature(TopoDS_Shape &result, bool &have_body, const TopoDS_Shape &context, const CadFeature &f) const
Definition CadDocument.cpp:2576
static double solid_volume(const TopoDS_Shape &s)
Definition CadDocument.cpp:3957

References _u8L, apply_boolean(), apply_cut(), apply_feature(), apply_mate(), apply_mirror(), apply_surface_offset(), apply_thicken(), apply_thicken_surface(), apply_transform(), Slic3r::Axis, bodies, Slic3r::Boolean, Slic3r::CoordSys, Slic3r::Cut, Slic3r::Extrude, Slic3r::f(), Slic3r::Helix, Slic3r::Hole, Slic3r::Import, Slic3r::Loft, Slic3r::Mate, Slic3r::Mirror, Slic3r::New, Slic3r::pick_body(), Slic3r::Plane, Slic3r::Project, Slic3r::Revolve, Slic3r::solid_volume(), Slic3r::SurfaceExtrude, Slic3r::SurfaceFill, Slic3r::SurfaceLoft, Slic3r::SurfaceOffset, Slic3r::SurfaceRevolve, Slic3r::Sweep, Slic3r::Thicken, Slic3r::ThickenSurface, Slic3r::Thread, and Slic3r::Transform.

Referenced by preview(), and replay_feature().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ serialize_recipe()

std::string Slic3r::CadDocument::serialize_recipe ( ) const
4422{
4423 std::ostringstream oss;
4424 {
4425 cereal::BinaryOutputArchive ar(oss);
4426 uint32_t v = ORCA_CAD_RECIPE_VERSION;
4427 ar(v);
4428 uint32_t n = static_cast<uint32_t>(features.size());
4429 ar(n);
4430 for (const CadFeature& f : features) {
4431 std::ostringstream fos;
4432 {
4433 cereal::BinaryOutputArchive fa(fos);
4434 fa(f);
4435 }
4436 std::string fb = fos.str();
4437 uint32_t len = static_cast<uint32_t>(fb.size());
4438 ar(len);
4439 ar(cereal::binary_data(fb.data(), fb.size()));
4440 }
4441 std::ostringstream vos;
4442 {
4443 cereal::BinaryOutputArchive va(vos);
4444 va(variables);
4445 }
4446 std::string vb = vos.str();
4447 uint32_t vlen = static_cast<uint32_t>(vb.size());
4448 ar(vlen);
4449 ar(cereal::binary_data(vb.data(), vb.size()));
4450 // BODY NAMES, appended after the variables block. Bodies are not serialised — they are
4451 // recomputed — so a name the user gave one has nowhere else to live, and without this it
4452 // would survive a recompute (see the carry-over in recompute()) but not a save.
4453 //
4454 // APPENDED RATHER THAN VERSION-BUMPED, on purpose: a build that predates this block
4455 // reads features and variables, returns, and never looks at the trailing bytes, so its
4456 // projects still open here AND this build's projects still open there. A version bump
4457 // would have made every project written today unreadable by yesterday's build for the
4458 // sake of one optional field. Written as (index, name) pairs so an unnamed body costs
4459 // nothing.
4460 // A map, not a vector of pairs: cereal's map support is already included here and its
4461 // pair support is not, and one more include for one more field is not worth it.
4462 std::map<uint32_t, std::string> named;
4463 for (uint32_t i = 0; i < bodies.size(); ++i)
4464 if (bodies[i].has_user_name && !bodies[i].user_name.empty())
4465 named[i] = bodies[i].user_name;
4466 std::ostringstream bos;
4467 {
4468 cereal::BinaryOutputArchive ba(bos);
4469 ba(named);
4470 }
4471 std::string bb = bos.str();
4472 uint32_t blen = static_cast<uint32_t>(bb.size());
4473 ar(blen);
4474 ar(cereal::binary_data(bb.data(), bb.size()));
4475 // DOCUMENT EXTRAS, appended after the body names on the same terms (older builds stop
4476 // reading before it): the modeling origin, and the body colours. A colour set with the
4477 // Color tool used to live only until the project was closed.
4478 std::map<uint32_t, std::array<float, 4>> colours;
4479 for (uint32_t i = 0; i < bodies.size(); ++i)
4480 if (bodies[i].has_color)
4481 colours[i] = { bodies[i].color.r(), bodies[i].color.g(), bodies[i].color.b(), bodies[i].color.a() };
4482 std::ostringstream xos;
4483 {
4484 cereal::BinaryOutputArchive xa(xos);
4486 }
4487 std::string xb = xos.str();
4488 uint32_t xlen = static_cast<uint32_t>(xb.size());
4489 ar(xlen);
4490 ar(cereal::binary_data(xb.data(), xb.size()));
4491 }
4492 return oss.str();
4493}

References auto_close_loops, bodies, Slic3r::f(), features, modeling_origin, ORCA_CAD_RECIPE_VERSION, and variables.

Here is the call graph for this function:

◆ set_feature_enabled()

bool Slic3r::CadDocument::set_feature_enabled ( int index,
bool enabled )
2399{
2400 if (index < 0 || index >= int(features.size())) return false;
2401 if (features[index].enabled == enabled) return true;
2402 std::vector<CadFeature> snapshot = features;
2403 stage_history_change(features, [](int i) { return i; },
2404 [this, index, enabled](int i) { return i == index ? enabled : features[i].enabled; });
2405 features[index].enabled = enabled;
2406 return commit_or_rollback(*this, snapshot);
2407}

References Slic3r::commit_or_rollback(), features, and Slic3r::stage_history_change().

Here is the call graph for this function:

◆ solve_sketch_feature()

bool Slic3r::CadDocument::solve_sketch_feature ( int index)
837{
838 if (index < 0 || index >= int(features.size())) return false;
839 CadFeature& f = features[index];
840 if (f.type != CadFeatureType::Sketch) return false;
841
842 // Onshape-style entity sketches solve against entity endpoints (Fase 4.2).
843 if (!f.entities.empty())
844 return solve_sketch_entities(f.entities, f.entity_constraints);
845
846 if (f.constraints.empty()) return true;
847
848 // Legacy point-profile path. Every index is checked before the solver sees it: an unset
849 // (-1) or stale index made get_point/residuals read m_vars[2*-1]. A constraint type this
850 // solver cannot express is a failure, not a silent no-op, and the points are written back
851 // only when the solve succeeded — the entity path's rule (SketchSolver.cpp) too.
852 const int np = int(f.profile.points.size());
853 auto ok_idx = [np](std::initializer_list<int> ids) {
854 for (int i : ids) if (i < 0 || i >= np) return false;
855 return true;
856 };
857 SketchConstraints sc;
858 for (const Vec2d& p : f.profile.points)
859 sc.add_point(p.x(), p.y());
860
861 for (const SketchConstraintDef& c : f.constraints) {
862 bool valid = true;
863 switch (c.type) {
864 case SketchConstraintType::Fix: if ((valid = ok_idx({c.a}))) sc.fix_point(c.a); break;
865 case SketchConstraintType::Coincident: if ((valid = ok_idx({c.a, c.b}))) sc.coincident(c.a, c.b); break;
866 case SketchConstraintType::Horizontal: if ((valid = ok_idx({c.a, c.b}))) sc.horizontal(c.a, c.b); break;
867 case SketchConstraintType::Vertical: if ((valid = ok_idx({c.a, c.b}))) sc.vertical(c.a, c.b); break;
868 case SketchConstraintType::Distance: if ((valid = ok_idx({c.a, c.b}))) sc.distance(c.a, c.b, c.value); break;
869 case SketchConstraintType::LockX: if ((valid = ok_idx({c.a}))) sc.lock_x(c.a, c.value); break;
870 case SketchConstraintType::LockY: if ((valid = ok_idx({c.a}))) sc.lock_y(c.a, c.value); break;
871 case SketchConstraintType::EqualLength: if ((valid = ok_idx({c.a, c.b, c.c, c.d}))) sc.equal_length(c.a, c.b, c.c, c.d); break;
872 case SketchConstraintType::Parallel: if ((valid = ok_idx({c.a, c.b, c.c, c.d}))) sc.parallel(c.a, c.b, c.c, c.d); break;
873 case SketchConstraintType::Perpendicular:if ((valid = ok_idx({c.a, c.b, c.c, c.d}))) sc.perpendicular(c.a, c.b, c.c, c.d); break;
874 // The legacy solver implements these too; they used to be dropped without a word.
875 case SketchConstraintType::Midpoint: if ((valid = ok_idx({c.a, c.b, c.c}))) sc.midpoint(c.a, c.b, c.c); break;
876 case SketchConstraintType::Symmetric: if ((valid = ok_idx({c.a, c.b, c.c, c.d}))) sc.symmetric(c.a, c.b, c.c, c.d); break;
877 case SketchConstraintType::Angle: if ((valid = ok_idx({c.a, c.b, c.c, c.d}))) sc.angle(c.a, c.b, c.c, c.d, c.value); break;
878 case SketchConstraintType::PointOnLine: if ((valid = ok_idx({c.a, c.b, c.c}))) sc.point_line_distance(c.a, c.b, c.c, c.value); break;
879 default: valid = false; break; // entity-only types
880 }
881 if (!valid) return false;
882 }
883
884 if (!sc.solve()) return false;
885 for (size_t i = 0; i < f.profile.points.size(); ++i)
886 f.profile.points[i] = sc.get_point(int(i));
887 return true;
888}
@ Vertical
Definition SketchEngine.hpp:101
@ LockY
Definition SketchEngine.hpp:102
@ Distance
Definition SketchEngine.hpp:101
@ Fix
Definition SketchEngine.hpp:101
@ Angle
Definition SketchEngine.hpp:104
@ PointOnLine
Definition SketchEngine.hpp:106
@ EqualLength
Definition SketchEngine.hpp:102
@ Midpoint
Definition SketchEngine.hpp:104
@ Coincident
Definition SketchEngine.hpp:101
@ Parallel
Definition SketchEngine.hpp:102
@ Horizontal
Definition SketchEngine.hpp:101
@ Perpendicular
Definition SketchEngine.hpp:102
@ LockX
Definition SketchEngine.hpp:102
@ Symmetric
Definition SketchEngine.hpp:104
bool solve_sketch_entities(std::vector< SketchEntity > &entities, const std::vector< SketchEntityConstraintDef > &constraints)
Definition CadDocument.cpp:616

References Slic3r::SketchConstraintDef::a, Slic3r::SketchConstraints::add_point(), Slic3r::Angle, Slic3r::SketchConstraints::angle(), Slic3r::SketchConstraintDef::b, Slic3r::SketchConstraintDef::c, Slic3r::Coincident, Slic3r::SketchConstraints::coincident(), Slic3r::SketchConstraintDef::d, Slic3r::Distance, Slic3r::SketchConstraints::distance(), Slic3r::SketchConstraints::equal_length(), Slic3r::EqualLength, Slic3r::f(), features, Slic3r::Fix, Slic3r::SketchConstraints::fix_point(), Slic3r::SketchConstraints::get_point(), Slic3r::Horizontal, Slic3r::SketchConstraints::horizontal(), Slic3r::SketchConstraints::lock_x(), Slic3r::SketchConstraints::lock_y(), Slic3r::LockX, Slic3r::LockY, Slic3r::Midpoint, Slic3r::SketchConstraints::midpoint(), Slic3r::Parallel, Slic3r::SketchConstraints::parallel(), Slic3r::Perpendicular, Slic3r::SketchConstraints::perpendicular(), Slic3r::SketchConstraints::point_line_distance(), Slic3r::PointOnLine, Slic3r::Sketch, Slic3r::SketchConstraints::solve(), Slic3r::solve_sketch_entities(), Slic3r::Symmetric, Slic3r::SketchConstraints::symmetric(), Slic3r::SketchConstraintDef::type, Slic3r::SketchConstraintDef::value, Slic3r::Vertical, and Slic3r::SketchConstraints::vertical().

Here is the call graph for this function:

◆ undo()

bool Slic3r::CadDocument::undo ( )
2063{
2064 if (m_undo.empty())
2065 return false;
2066 m_redo.push_back(Snapshot{std::move(features), std::move(variables)}); // current state becomes redoable
2067 features = std::move(m_undo.back().features);
2068 variables = std::move(m_undo.back().variables);
2069 m_undo.pop_back();
2070 recompute(); // benign-empty (only a sketch / empty doc) is a valid undo target
2071 return true;
2072}

References features, m_redo, m_undo, recompute(), and variables.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_boolean(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_bridge(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_chamfer(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_delete_face(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_draft(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_extrude(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_fillet(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_hole(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_hole_standard(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_hole_styled(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_mate(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_mirror(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_pattern(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_project(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_revolve(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_rib(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_set_feature_expr(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_set_variable(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_shell(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_split(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_extrude(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_fill(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_loft(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_offset(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_revolve(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_thicken(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_thicken_surface(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_transform(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::import_mesh(), and Slic3r::GUI::anonymous_namespace{McpControl.cpp}::import_step().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ undo_depth()

size_t Slic3r::CadDocument::undo_depth ( ) const
inline
810{ return m_undo.size(); }

References m_undo.

Member Data Documentation

◆ angular_deflection

double Slic3r::CadDocument::angular_deflection {0.5}
587{0.5};

Referenced by preview(), and recompute().

◆ auto_close_loops

bool Slic3r::CadDocument::auto_close_loops {true}

◆ bodies

std::vector<CadBody> Slic3r::CadDocument::bodies

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_boolean(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_bridge(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_chamfer(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_delete_face(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_draft(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_extrude(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_fillet(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_helix(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_hole(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_hole_standard(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_hole_styled(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_mate(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_mirror(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_pattern(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_project(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_revolve(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_rib(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_shell(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_split(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_extrude(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_fill(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_loft(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_offset(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_revolve(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_thicken(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_thicken_surface(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_transform(), apply_boolean(), apply_cut(), apply_mate(), apply_mirror(), apply_project(), apply_surface_offset(), apply_thicken(), apply_thicken_surface(), apply_transform(), body_mass_properties(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::body_shape(), body_touching_sketch(), check_interference(), clear(), Slic3r::commit_or_rollback(), datum_frame(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::describe_scene(), deserialize_recipe(), export_step(), faces_made_by(), Slic3r::GUI::DesignPanel::fill_body_choice(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::import_mesh(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::import_step(), mate_options(), plane_of_face(), Slic3r::GUI::DesignPanel::populate_body_choices(), preview(), project_edges_into_sketch(), recompute(), resolve_datum_axes(), resolve_datum_coordsys(), resolve_datum_planes(), route_feature(), serialize_recipe(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::target_body_arg(), and Slic3r::GUI::anonymous_namespace{McpControl.cpp}::validate_against().

◆ body

TopoDS_Shape Slic3r::CadDocument::body

◆ display_body_meshes

◆ display_mesh

TriangleMesh Slic3r::CadDocument::display_mesh

◆ display_tri_body

std::vector<int> Slic3r::CadDocument::display_tri_body

◆ display_tri_face

std::vector<int> Slic3r::CadDocument::display_tri_face

◆ error

std::string Slic3r::CadDocument::error

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_axis(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_boolean(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_bridge(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_chamfer(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_coordsys(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_delete_face(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_draft(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_extrude(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_fillet(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_helix(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_hole(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_hole_standard(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_hole_styled(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_mate(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_mirror(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_pattern(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_project(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_revolve(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_rib(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_set_feature_expr(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_set_variable(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_shell(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_split(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_extrude(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_fill(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_loft(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_offset(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_surface_revolve(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_thicken(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_thicken_surface(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_transform(), clear(), Slic3r::commit_or_rollback(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::describe_scene(), deserialize_recipe(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::import_mesh(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::import_step(), move_feature(), recompute(), and Slic3r::GUI::DesignPanel::refresh_preview().

◆ features

std::vector<CadFeature> Slic3r::CadDocument::features

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_axis(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_chamfer(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_coordsys(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_extrude(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_fillet(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_mirror(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_pattern(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_set_feature_expr(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_sketch_commit(), add_axis(), add_boolean(), add_bridge(), add_chamfer(), add_chamfer(), add_chamfer(), add_coordsys(), add_cut(), add_delete_face(), add_draft(), add_extrude(), add_extrude_entities(), add_extrude_face(), add_fillet(), add_fillet(), add_fillet(), add_helix(), add_hole(), add_hole_styled(), add_loft(), add_mate(), add_mirror(), add_pattern(), add_pattern_on_curve(), add_plane(), add_project_edges(), add_revolve(), add_revolve_entities(), add_rib(), add_shell(), add_sketch(), add_sketch_entities(), add_sketch_profile(), add_split_by_face(), add_surface_extrude(), add_surface_fill(), add_surface_loft(), add_surface_offset(), add_surface_revolve(), add_sweep(), add_thicken(), add_thicken_surface(), add_thread(), add_transform(), apply_feature(), apply_mate(), bind_expressions(), body_touching_sketch(), checkpoint(), clear(), Slic3r::commit_or_rollback(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::describe_scene(), deserialize_recipe(), detect_mate_conflicts(), faces_made_by(), Slic3r::GUI::DesignPanel::fill_body_choice(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::import_mesh(), Slic3r::GUI::anonymous_namespace{McpControl.cpp}::import_step(), mate_options(), move_feature(), Slic3r::GUI::DesignPanel::populate_body_choices(), project_edges_into_sketch(), recompute(), redo(), remove_feature(), replace_feature(), replace_sketch_extrude(), replay_feature(), resolve_datum_axes(), resolve_datum_coordsys(), resolve_datum_planes(), serialize_recipe(), set_feature_enabled(), solve_sketch_feature(), and undo().

◆ k_undo_cap

size_t Slic3r::CadDocument::k_undo_cap = 200
staticconstexprprivate

Referenced by checkpoint().

◆ linear_deflection

double Slic3r::CadDocument::linear_deflection {0.003}
586{0.003};

Referenced by preview(), and recompute().

◆ m_redo

std::vector<Snapshot> Slic3r::CadDocument::m_redo
private

◆ m_undo

std::vector<Snapshot> Slic3r::CadDocument::m_undo
private

◆ mate_conflicts

std::vector<std::pair<int, std::string> > Slic3r::CadDocument::mate_conflicts

◆ modeling_origin

◆ ORCA_CAD_RECIPE_VERSION

uint32_t Slic3r::CadDocument::ORCA_CAD_RECIPE_VERSION = 6
staticconstexpr

◆ origin_from_recipe

bool Slic3r::CadDocument::origin_from_recipe {false}
573{false}; // modeling_origin came from the loaded project

Referenced by clear(), and deserialize_recipe().

◆ topo_generation

◆ variables


The documentation for this class was generated from the following files: