mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-19 15:03:05 +00:00
Every version bump so far has permanently orphaned every project saved before it. deserialize_recipe refused anything that was not exactly the current version, and with no migration path v2 and v3 projects are unopenable today — the 3MF still carries the mesh, so the user gets a frozen solid and no feature history, which is the whole point of the subsystem silently absent. The cause was the shape of the data, not the gate. save/load is one flat symmetric list of ~90 fields with no framing, so a reader has no way to know where a feature ends unless it agrees on every field. Each feature is now written as its own cereal stream behind a length prefix, and the same few lines handle both directions of mismatch. Older file, newer build: the sub-stream ends early, the read throws, and the fields already assigned are kept while the rest default — cereal assigns sequentially, so a mid-list throw leaves the earlier fields set, and that is what makes this work. Newer file, older build: the sub-stream holds more bytes than the reader knows; it reads what it knows and stops, and the outer stream is untouched because the length prefix was consumed in full. A field a project predates is not a corrupt project, so neither case is an error. v4 keeps its own pre-framing flat path and opens exactly as before — cad_recipe_v4.bin is untouched and now serves as the witness for that. v2 and v3 stay refused, by name: their field lists no longer exist in this code. This fixes the future, not the past, and the comment says so rather than implying otherwise. From here a new field only needs appending to save/load — no bump, no orphaned projects. That removes the cost that had blocked snaporca-44m and snaporca-dgv. The helix round-trip test was reading the blob back flat, reaching into the format instead of through it; framing necessarily breaks that, so it now goes through deserialize_recipe, which is a stronger assertion than it made before. Every field check it carried is unchanged. Tests: four new [CadDocument][recipe] cases, including the one the change exists for — a deliberately truncated feature blob must LOAD, keeping what it could read. Suite 177 -> 181 cases, 2366 -> 2411 assertions. snaporca-2txy.
755 lines
47 KiB
C++
755 lines
47 KiB
C++
#ifndef slic3r_CadDocument_hpp_
|
|
#define slic3r_CadDocument_hpp_
|
|
|
|
#include "TriangleMesh.hpp"
|
|
#include "SketchEngine.hpp"
|
|
#include "GeometryEngine.hpp" // FaceGroup
|
|
#include "Color.hpp" // ColorRGBA (per-body display colour override)
|
|
|
|
#include <TopoDS_Shape.hxx>
|
|
#include <TopoDS_Wire.hxx>
|
|
#include <cereal/cereal.hpp>
|
|
#include <cereal/types/vector.hpp>
|
|
#include <cereal/types/string.hpp>
|
|
#include <map>
|
|
#include <cereal/types/map.hpp>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <utility>
|
|
|
|
namespace Slic3r {
|
|
|
|
enum class CadFeatureType { Sketch, Extrude, Fillet, Chamfer, Hole, Thread, Shell, Revolve, Sweep, Pattern, Plane, Loft, Draft, Import, Boolean, Cut, Mirror, Axis, CoordSys, Helix, Transform, Thicken, Project, DeleteFace, Rib, SurfaceExtrude, SurfaceRevolve, ThickenSurface, SurfaceOffset, SurfaceLoft, SurfaceFill, Mate };
|
|
enum class SketchShape { Rectangle, Circle };
|
|
enum class PlaneType { Offset, Angle, Midplane, Tangent, TwoEdges, Coincident };
|
|
enum class AxisType { TwoPoints, FaceNormal, CylinderCenterline, PlaneIntersection, AlongEdge };
|
|
enum class CoordSysType { PointWorld, FaceAndDirection };
|
|
enum class BooleanMode { New, Add, Cut, Intersect };
|
|
|
|
enum class ExtrudeEnd { Blind, Symmetric, TwoSided, ThroughAll, UpToFace, UpToVertex };
|
|
|
|
// Serialize a TopoDS_Shape to/from a BRep string (declared before CadFeature so its
|
|
// inline cereal save()/load() can resolve these non-dependent calls).
|
|
std::string brep_to_string(const TopoDS_Shape& s);
|
|
TopoDS_Shape brep_from_string(const std::string& d);
|
|
|
|
struct CadFeature {
|
|
CadFeatureType type{CadFeatureType::Sketch};
|
|
std::string name;
|
|
bool enabled{true};
|
|
|
|
// Sketch params (centered on the plane origin)
|
|
SketchShape shape{SketchShape::Rectangle};
|
|
SketchPlane plane{SketchPlane::XY()};
|
|
double width{20};
|
|
double height{20};
|
|
double radius{10};
|
|
|
|
// Real 2D sketch geometry (Onshape-style). When non-empty this takes
|
|
// precedence over the shape/width/height/radius enum path in build_sketch_wire.
|
|
SketchProfile profile;
|
|
|
|
// Onshape-style multi-entity sketch geometry. When non-empty this takes
|
|
// precedence over both `profile` and the shape-enum path in build_sketch_wire.
|
|
std::vector<SketchEntity> entities;
|
|
|
|
// 2D geometric constraints on `profile` (point indices). Solved in place.
|
|
std::vector<SketchConstraintDef> constraints;
|
|
|
|
// Onshape-style constraints on `entities` (Fase 4.2). Solved in place against
|
|
// entity endpoints. Used when `entities` is non-empty (the legacy `constraints`
|
|
// vector applies only to the `profile` path).
|
|
std::vector<SketchEntityConstraintDef> entity_constraints;
|
|
|
|
// Imported rigid 2D art (Text glyphs / SVG vector paths) as filled regions.
|
|
// Each region: contour[0] = outer loop, contour[1..] = holes; points in
|
|
// plane (u,v) millimetres. Rendered as a sketch overlay and extruded via a
|
|
// faces-with-holes path (SketchEngine::make_extrude_regions) — deliberately
|
|
// NOT solver entities, so imported art contributes zero DoF and never
|
|
// pollutes the constraint solver / DoF readout. When non-empty it takes
|
|
// precedence over the entities/profile/shape paths in the Extrude case.
|
|
std::vector<std::vector<std::vector<Vec2d>>> imported_regions;
|
|
|
|
// Imported rigid 3D B-rep solid (STEP). When the feature type is Import this carries
|
|
// the OCCT shape verbatim — it is adopted as a base body in route_feature (no parametric
|
|
// recipe). Downstream face/edge features (fillet/chamfer/cut/shell/...) act on it like any
|
|
// other body. TopoDS_Shape is a cheap handle, so copying it through recompute/checkpoint
|
|
// snapshots is cheap. In-session only for now (no BRep serialization yet).
|
|
TopoDS_Shape imported_solid;
|
|
|
|
// Non-destructive placement transform for imported_regions (Text/SVG),
|
|
// applied at display + extrude time as
|
|
// p -> (p.x*import_scale_x + import_offset.x, p.y*import_scale_y + import_offset.y).
|
|
// Lets the art be moved / enlarged / stretched (independent X/Y) repeatedly
|
|
// without re-vectorising. Identity = no change.
|
|
Vec2d import_offset{0, 0};
|
|
double import_scale_x{1.0};
|
|
double import_scale_y{1.0};
|
|
// Text/SVG dropped ONTO a solid face (centred on it): the extrude then defaults to an
|
|
// inward Cut (engraving) targeting `import_face_body`. False = free art on a plane.
|
|
bool import_on_face{false};
|
|
int import_face_body{-1};
|
|
|
|
// Extrude params
|
|
int sketch_ref{-1}; // index into features[] of the consumed sketch
|
|
double distance{10};
|
|
bool symmetric{false};
|
|
BooleanMode mode{BooleanMode::New};
|
|
ExtrudeEnd extrude_end{ExtrudeEnd::Blind};
|
|
double distance2{0}; // second-side depth for TwoSided
|
|
double taper_deg{0}; // draft angle (C4-part2)
|
|
bool flip{false}; // reverse the extrude direction (negate plane normal)
|
|
int up_to_face{-1}; // target solid-face id for UpToFace (C4-part2)
|
|
int extrude_src_face{-1}; // global face id on the current body to extrude as a profile; -1 = use sketch wire
|
|
Vec3d up_to_point{0,0,0}; // target for UpToVertex (C4-part2)
|
|
// Multi-body target: which body (index into CadDocument::bodies) this feature acts on.
|
|
// -1 = auto (last body). A New extrude appends a fresh body; Add/Cut/Intersect, dress-up,
|
|
// hole and face-extrude(non-New) mutate bodies[target]; face-extrude reads its source
|
|
// face from bodies[target] too. The source-face owner for face-extrude lives here.
|
|
int target_body{-1};
|
|
|
|
// Dress-up params (Fillet/Chamfer) — applied to the current body in order
|
|
double dressup_size{1.0}; // fillet radius or chamfer distance
|
|
FaceGroup face_group{FaceGroup::All};
|
|
int dressup_edge{-1}; // global edge id for edge-targeted fillet/chamfer; -1 = use face_group
|
|
|
|
// Hole params (positioned circular cut into the current body)
|
|
double hole_diameter{5};
|
|
double hole_depth{10};
|
|
bool hole_through{true}; // true = symmetric through-cut, ignores hole_depth
|
|
double hole_x{0}; // position on the plane (plane u/x axis)
|
|
double hole_y{0}; // position on the plane (plane v/y axis)
|
|
|
|
// Hole standards library (extends the plain bore above).
|
|
// hole_style: 0 = simple, 1 = counterbore, 2 = countersink.
|
|
int hole_style{0};
|
|
double hole_cbore_diameter{0}; // counterbore cylinder diameter (mm), style==1
|
|
double hole_cbore_depth{0}; // counterbore depth from the entry face (mm), style==1
|
|
double hole_csink_diameter{0}; // countersink major diameter at entry face (mm), style==2
|
|
double hole_csink_angle{90}; // countersink included angle (degrees), style==2
|
|
std::string hole_standard; // provenance only, e.g. "M6" / "1/4-20"; not used by geometry
|
|
|
|
// Thread params (helical thread about the plane normal at a positioned point)
|
|
double thread_radius{5}; // nominal cylinder radius
|
|
double thread_pitch{2}; // axial advance per turn
|
|
double thread_height{10}; // total axial length
|
|
double thread_depth{1}; // radial crest depth of the thread profile
|
|
bool thread_internal{false}; // false = external threaded rod (New body);
|
|
// true = tapped bore cut into the current body
|
|
double thread_x{0}; // axis position on the plane (u/x axis)
|
|
double thread_y{0}; // axis position on the plane (v/y axis)
|
|
|
|
// Shell params (hollow the current body to a wall thickness, removing one open face)
|
|
double shell_thickness{2}; // wall thickness (inward offset)
|
|
int shell_face{-1}; // global face id to remove (open the shell); -1 = none
|
|
|
|
// Draft params (taper a single solid face about a neutral plane = body bbox bottom, pull +Z)
|
|
int draft_face{-1}; // global face id to draft; -1 = none
|
|
double draft_angle{5}; // draft angle in degrees (signed: + leans the face inward)
|
|
|
|
// Revolve params (sweep a profile about an in-plane axis through the plane origin).
|
|
// Reuses sketch_ref / entities (profile), flip (direction), mode (boolean) and
|
|
// target_body. revolve_axis: 0 = plane X axis, 1 = plane Y axis.
|
|
double revolve_angle{360}; // sweep angle in degrees (1..360)
|
|
int revolve_axis{0}; // 0 = plane X, 1 = plane Y
|
|
|
|
// Sweep: profile carried by sketch_ref / entities (like Extrude); the spine is a
|
|
// second Sketch referenced by sweep_path_ref (an open or closed wire). Reuses
|
|
// mode (boolean) and target_body.
|
|
int sweep_path_ref{-1}; // index into features[] of the path Sketch
|
|
|
|
// Loft: build a solid through 2+ closed profile Sketches (loft_profile_refs, in
|
|
// order, each on its own plane). loft_ruled=false → smooth sections, true → ruled.
|
|
// Reuses mode (boolean) and target_body.
|
|
std::vector<int> loft_profile_refs; // ordered indices into features[] of profile Sketches
|
|
bool loft_ruled{false};
|
|
|
|
// Pattern: replicate the target body, copies fused into it. pattern_circular=false
|
|
// → linear (pattern_count instances spaced pattern_spacing along plane axis
|
|
// pattern_dir: 0=X, 1=Y); true → circular (pattern_count instances over
|
|
// pattern_angle° total about the plane normal through the plane origin, so a seed
|
|
// offset from the origin orbits the axis). Reuses target_body + plane.
|
|
bool pattern_circular{false};
|
|
int pattern_count{3}; // total instances incl. the seed (>=1)
|
|
double pattern_spacing{20}; // linear step (mm)
|
|
int pattern_dir{0}; // linear direction: 0 = plane X, 1 = plane Y
|
|
double pattern_angle{360}; // circular total angle (degrees)
|
|
|
|
// Pattern along a curve: when pattern_curve_sketch >= 0 this mode takes precedence over
|
|
// linear/circular. Copies are placed at equal-parameter points along entity
|
|
// pattern_curve_entity of sketch pattern_curve_sketch, translated by (P_i - P_0).
|
|
int pattern_curve_sketch{-1}; // feature index of the Sketch holding the guide curve
|
|
int pattern_curve_entity{-1}; // entity index of the guide curve within that sketch
|
|
|
|
// Parametric bindings: field-member-name -> expression string. On recompute() each entry
|
|
// is evaluated against the document variables and written into the named numeric field
|
|
// BEFORE geometry runs. Empty (the common case) means the feature uses its literal fields.
|
|
std::map<std::string, std::string> expr;
|
|
|
|
// Datum/reference plane: a derived SketchPlane the document offers as a selectable
|
|
// sketch plane (no solid). plane_base selects the reference (0=XY,1=XZ,2=YZ, or 3+N
|
|
// = the Nth earlier datum plane); plane_offset shifts along the base normal;
|
|
// plane_angle tilts plane_angle° about the base axis plane_axis (0=base X, 1=base Y).
|
|
int plane_base{0};
|
|
double plane_offset{20};
|
|
double plane_angle_tilt{0}; // degrees (named *_tilt to avoid clash w/ revolve)
|
|
int plane_axis{0}; // tilt axis: 0 = base X, 1 = base Y
|
|
PlaneType plane_type{PlaneType::Offset};
|
|
int plane_face_body{-1};
|
|
int plane_face{-1};
|
|
int plane_face2_body{-1};
|
|
int plane_face2{-1};
|
|
int plane_edge_body{-1};
|
|
int plane_edge{-1};
|
|
int plane_edge2_body{-1};
|
|
int plane_edge2{-1};
|
|
double plane_u_size{60};
|
|
double plane_v_size{60};
|
|
|
|
// Boolean: combine two EXISTING bodies. `mode` reuses BooleanMode (Add = union,
|
|
// Cut = subtract tool from target, Intersect = keep overlap; New unused). `target_body`
|
|
// is the body that survives (result written back to it); `bool_tool_body` is the other
|
|
// operand, consumed (erased) unless `bool_keep_tool`. `bool_tolerance` = OCCT fuzzy value
|
|
// (0 = exact). Per-face merge: when both bool_target_face/bool_tool_face are set, the tool
|
|
// is first snapped so those two faces are coincident (gap closed within bool_tolerance),
|
|
// then the boolean welds them and coplanar faces are unified into one clean face.
|
|
int bool_tool_body{-1};
|
|
bool bool_keep_tool{false};
|
|
double bool_tolerance{0.0};
|
|
int bool_target_face{-1}; // global face id on the target body to mate (-1 = none)
|
|
int bool_tool_face{-1}; // global face id on the tool body to mate (-1 = none)
|
|
|
|
// Cut: split one target body with a plane, keeping the upper half, lower half, or both.
|
|
// Reuses `plane` for the cut plane and `target_body` for which body is cut.
|
|
double cut_offset{0.0}; // offset along the cut-plane normal (mm)
|
|
bool cut_flip{false}; // flip the normal => swaps which side is "upper"
|
|
bool cut_keep_upper{true}; // keep the +normal half
|
|
bool cut_keep_lower{false}; // keep the -normal half (both => split into two bodies)
|
|
|
|
// Mirror: reflect a body about a plane. Reuses `plane` (mirror plane, as Cut does),
|
|
// `target_body` (body to mirror), and `mode` (New = separate mirrored copy,
|
|
// Add = fuse the mirror back into the source). mirror_keep_original decides whether
|
|
// the source body survives when mode is New.
|
|
bool mirror_keep_original{true};
|
|
|
|
// Datum axis: reference line (no solid). Construction params stored; resolve_datum_axes()
|
|
// computes the world-space origin + unit direction on demand.
|
|
AxisType axis_type{AxisType::TwoPoints};
|
|
Vec3d axis_p1{0, 0, 0};
|
|
Vec3d axis_p2{0, 0, 10};
|
|
int axis_body{-1};
|
|
int axis_face{-1};
|
|
int axis_edge{-1};
|
|
int axis_plane_a{-1};
|
|
int axis_plane_b{-1};
|
|
|
|
// Datum coordinate system (no solid). Stored as point + two orthonormal axes.
|
|
CoordSysType coordsys_type{CoordSysType::PointWorld};
|
|
Vec3d coordsys_point{0, 0, 0};
|
|
int coordsys_body{-1};
|
|
int coordsys_face{-1};
|
|
int coordsys_edge{-1};
|
|
Vec3d coordsys_x_hint{1, 0, 0};
|
|
|
|
// Fingerprint of the face this connector was bound to, for drift detection. -1 = not yet
|
|
// recorded (an old recipe, or a connector that has never resolved).
|
|
//
|
|
// Surface TYPE and EDGE COUNT specifically, because they survive every legitimate edit:
|
|
// Transform moves the body, Draft tilts the face, a dimension change resizes it, and none
|
|
// of those change either value. Centroid, area and normal all fail that test — see the
|
|
// issue. The cost is that a slide from one planar 4-edge face to another planar 4-edge face
|
|
// is invisible; a detector that never cries wolf is worth more here than a total one.
|
|
int coordsys_face_kind{-1}; // GeomAbs_SurfaceType as int
|
|
int coordsys_face_edges{-1}; // number of edges bounding the face
|
|
|
|
// Helix curve params (consumed as a sweep path to build springs/coils/augers).
|
|
// Axis = plane normal through plane origin. pitch = axial rise per full turn.
|
|
// left_handed flips the winding direction. taper_deg != 0 gives a conical helix.
|
|
double helix_radius{10};
|
|
double helix_pitch{5};
|
|
double helix_height{20};
|
|
bool helix_left_handed{false};
|
|
double helix_taper_deg{0};
|
|
|
|
// Transform feature: rigid move/rotate of an existing body. Rotation is applied
|
|
// first (about xf_axis through xf_pivot), then the translation.
|
|
Vec3d xf_translate{0, 0, 0};
|
|
Vec3d xf_axis{0, 0, 1};
|
|
Vec3d xf_pivot{0, 0, 0};
|
|
double xf_angle_deg{0};
|
|
bool xf_copy{false}; // true: keep the original, append the moved copy as a new body
|
|
|
|
// Thicken feature: offset one face of an existing body into a new thin solid body.
|
|
// The face belongs to `target_body`; the offset runs along the face normal.
|
|
int thicken_face{-1}; // global face id on the target body; -1 = invalid
|
|
double thicken_thickness{2}; // wall thickness (always used as |value|)
|
|
bool thicken_flip{false}; // true: offset against the face normal
|
|
|
|
// Cut-by-face: when cut_face >= 0, apply_cut derives the cut plane from this face
|
|
// (via SketchPlane::from_face) instead of the base `plane`. cut_offset / cut_flip
|
|
// still apply along the derived normal.
|
|
int cut_face_body{-1}; // body owning the face; -1 = the target body
|
|
int cut_face{-1}; // global face id to cut along; -1 = use `plane`
|
|
|
|
// Project feature: convert edges of an existing solid into sketch entities on `plane`.
|
|
int project_source_body{-1}; // body owning the edges; -1 = last body
|
|
std::vector<int> project_edges; // global edge ids to project; empty => use project_face
|
|
int project_face{-1}; // if project_edges empty, project every edge of this face
|
|
|
|
// Direct edit: faces to remove (global face indices into target_body's shape),
|
|
// healed via BRepAlgoAPI_Defeaturing.
|
|
std::vector<int> delete_faces;
|
|
|
|
// Rib: a thin wall grown from an open sketch line, fused to the body.
|
|
int rib_sketch_ref{-1}; // feature index of the Sketch holding the profile
|
|
int rib_entity{-1}; // index of the open Line entity within that sketch
|
|
double rib_thickness{2}; // wall thickness (mm), centred on the line
|
|
double rib_depth{10}; // extrude distance along the sketch-plane normal (mm)
|
|
|
|
// --- Mate (assembly) ---
|
|
// 0 Fastened — all 6 DOF fixed: B's frame is driven onto A's exactly.
|
|
// 1 Planar — z axes aligned, normal distance set to mate_offset; in-plane position free.
|
|
// 2 Revolute — axes collinear, position on the axis fixed; rotation about z free.
|
|
// 3 Slider — orientation fully fixed, perpendicular position fixed; axial slide free.
|
|
// 4 Cylindrical— axes collinear, perpendicular fixed; both spin and axial slide free.
|
|
// A "free" DOF is preserved from the body's current placement, not zeroed.
|
|
int mate_kind{0};
|
|
int mate_cs_a{-1}; // feature index of the FIXED CoordSys (mate connector A)
|
|
int mate_cs_b{-1}; // feature index of the CoordSys on the body that MOVES
|
|
double mate_offset{0}; // translation along A's z, mm
|
|
double mate_angle{0}; // rotation about A's z, degrees
|
|
bool mate_flip{false}; // oppose the two z axes (face-to-face)
|
|
|
|
template<class Archive>
|
|
void save(Archive& ar) const {
|
|
std::string brep = (type == CadFeatureType::Import) ? brep_to_string(imported_solid) : std::string();
|
|
ar(type, name, enabled, shape, plane, width, height, radius,
|
|
profile, entities, constraints, entity_constraints, imported_regions,
|
|
import_offset, import_scale_x, import_scale_y, import_on_face, import_face_body,
|
|
sketch_ref, distance, symmetric, mode, extrude_end, distance2, taper_deg, flip,
|
|
up_to_face, extrude_src_face, up_to_point, target_body,
|
|
dressup_size, face_group, dressup_edge,
|
|
hole_diameter, hole_depth, hole_through, hole_x, hole_y,
|
|
thread_radius, thread_pitch, thread_height, thread_depth, thread_internal, thread_x, thread_y,
|
|
shell_thickness, shell_face,
|
|
draft_face, draft_angle,
|
|
revolve_angle, revolve_axis,
|
|
sweep_path_ref, loft_profile_refs, loft_ruled,
|
|
pattern_circular, pattern_count, pattern_spacing, pattern_dir, pattern_angle,
|
|
plane_base, plane_offset, plane_angle_tilt, plane_axis,
|
|
bool_tool_body, bool_keep_tool, bool_tolerance, bool_target_face, bool_tool_face,
|
|
cut_offset, cut_flip, cut_keep_upper, cut_keep_lower,
|
|
brep,
|
|
plane_type, plane_face_body, plane_face, plane_face2_body, plane_face2,
|
|
plane_edge_body, plane_edge, plane_edge2_body, plane_edge2, plane_u_size, plane_v_size,
|
|
mirror_keep_original,
|
|
axis_type, axis_p1, axis_p2, axis_body, axis_face, axis_edge, axis_plane_a, axis_plane_b,
|
|
coordsys_type, coordsys_point, coordsys_body, coordsys_face, coordsys_edge, coordsys_x_hint,
|
|
helix_radius, helix_pitch, helix_height, helix_left_handed, helix_taper_deg,
|
|
xf_translate, xf_axis, xf_pivot, xf_angle_deg, xf_copy,
|
|
thicken_face, thicken_thickness, thicken_flip,
|
|
cut_face_body, cut_face,
|
|
project_source_body, project_edges, project_face,
|
|
delete_faces,
|
|
hole_style, hole_cbore_diameter, hole_cbore_depth,
|
|
hole_csink_diameter, hole_csink_angle, hole_standard,
|
|
rib_sketch_ref, rib_entity, rib_thickness, rib_depth,
|
|
pattern_curve_sketch, pattern_curve_entity,
|
|
expr,
|
|
mate_kind, mate_cs_a, mate_cs_b, mate_offset, mate_angle, mate_flip,
|
|
coordsys_face_kind, coordsys_face_edges);
|
|
}
|
|
template<class Archive>
|
|
void load(Archive& ar) {
|
|
std::string brep;
|
|
ar(type, name, enabled, shape, plane, width, height, radius,
|
|
profile, entities, constraints, entity_constraints, imported_regions,
|
|
import_offset, import_scale_x, import_scale_y, import_on_face, import_face_body,
|
|
sketch_ref, distance, symmetric, mode, extrude_end, distance2, taper_deg, flip,
|
|
up_to_face, extrude_src_face, up_to_point, target_body,
|
|
dressup_size, face_group, dressup_edge,
|
|
hole_diameter, hole_depth, hole_through, hole_x, hole_y,
|
|
thread_radius, thread_pitch, thread_height, thread_depth, thread_internal, thread_x, thread_y,
|
|
shell_thickness, shell_face,
|
|
draft_face, draft_angle,
|
|
revolve_angle, revolve_axis,
|
|
sweep_path_ref, loft_profile_refs, loft_ruled,
|
|
pattern_circular, pattern_count, pattern_spacing, pattern_dir, pattern_angle,
|
|
plane_base, plane_offset, plane_angle_tilt, plane_axis,
|
|
bool_tool_body, bool_keep_tool, bool_tolerance, bool_target_face, bool_tool_face,
|
|
cut_offset, cut_flip, cut_keep_upper, cut_keep_lower,
|
|
brep,
|
|
plane_type, plane_face_body, plane_face, plane_face2_body, plane_face2,
|
|
plane_edge_body, plane_edge, plane_edge2_body, plane_edge2, plane_u_size, plane_v_size,
|
|
mirror_keep_original,
|
|
axis_type, axis_p1, axis_p2, axis_body, axis_face, axis_edge, axis_plane_a, axis_plane_b,
|
|
coordsys_type, coordsys_point, coordsys_body, coordsys_face, coordsys_edge, coordsys_x_hint,
|
|
helix_radius, helix_pitch, helix_height, helix_left_handed, helix_taper_deg,
|
|
xf_translate, xf_axis, xf_pivot, xf_angle_deg, xf_copy,
|
|
thicken_face, thicken_thickness, thicken_flip,
|
|
cut_face_body, cut_face,
|
|
project_source_body, project_edges, project_face,
|
|
delete_faces,
|
|
hole_style, hole_cbore_diameter, hole_cbore_depth,
|
|
hole_csink_diameter, hole_csink_angle, hole_standard,
|
|
rib_sketch_ref, rib_entity, rib_thickness, rib_depth,
|
|
pattern_curve_sketch, pattern_curve_entity,
|
|
expr,
|
|
mate_kind, mate_cs_a, mate_cs_b, mate_offset, mate_angle, mate_flip,
|
|
coordsys_face_kind, coordsys_face_edges);
|
|
imported_solid = brep_from_string(brep);
|
|
}
|
|
};
|
|
|
|
// Serialize a TopoDS_Shape to/from a BRep string for cereal persistence.
|
|
std::string brep_to_string(const TopoDS_Shape& s);
|
|
TopoDS_Shape brep_from_string(const std::string& d);
|
|
|
|
// One independent solid in a multi-body document.
|
|
struct CadBody {
|
|
TopoDS_Shape shape;
|
|
std::string name;
|
|
// Per-body display colour override (Color tool). When has_color is false the GUI
|
|
// falls back to the auto body-index palette. Carried across recompute() by body index.
|
|
bool has_color{false};
|
|
ColorRGBA color;
|
|
// Index into `features` of the feature that CREATED this body, or -1. A body is a
|
|
// recomputed result, so without this there is no way back to its maker and "delete this
|
|
// body" cannot be expressed at all — the GUI could only answer "select the FEATURE that
|
|
// created this body". Stamped in one place, the recompute loop; see the note there for
|
|
// why a single "still unset?" test is sufficient and stays correct for new feature types.
|
|
int source_feature{-1};
|
|
};
|
|
|
|
// OCCT-only feature tree backing the Design tab. No GUI dependencies (lives in libslic3r).
|
|
class CadDocument {
|
|
public:
|
|
std::vector<CadFeature> features;
|
|
// Named document variables: name -> expression. Evaluated topologically each recompute();
|
|
// an expression may reference other variables. Feature `expr` bindings resolve against these.
|
|
std::map<std::string, std::string> variables;
|
|
// Multi-body result of the last replay. A "New" extrude appends a body; other ops
|
|
// mutate a target body. Empty after a failed/empty recompute.
|
|
std::vector<CadBody> bodies;
|
|
TopoDS_Shape body; // compound of all bodies (1 body => that body) — display/compat
|
|
TriangleMesh display_mesh; // tessellation of all bodies, concatenated (picking)
|
|
std::vector<TriangleMesh> display_body_meshes; // one mesh per body, in `bodies` order (per-body color)
|
|
std::vector<int> display_tri_face; // per-triangle face id WITHIN its source body
|
|
std::vector<int> display_tri_body; // per-triangle source body index (into bodies)
|
|
std::string error; // last recompute error ("" = ok)
|
|
|
|
// Mate diagnostics, refilled by every recompute(). Non-fatal by design: the
|
|
// document still evaluates — this only names what the user should look at.
|
|
// .first = index of the offending Mate feature, .second = human-readable reason.
|
|
// NOT "over-constraint" — this kernel has no solver, so there is no DOF analysis
|
|
// behind these; they are graph facts about which mate drives which body.
|
|
std::vector<std::pair<int, std::string>> mate_conflicts;
|
|
|
|
// Modeling origin: the world point the default XY/XZ/YZ planes pass through. The GUI sets this
|
|
// to the bed centre so sketches/datums land in the middle of the bed (not the bed corner =
|
|
// world 0). Not serialized — the GUI re-applies it from the live bed on every tab show.
|
|
Vec3d modeling_origin{Vec3d::Zero()};
|
|
|
|
// Tessellation quality, matched to Orca's OWN STEP importer (Format/STEP.hpp defaults:
|
|
// linear 0.003, angular 0.5 rad) so a body modelled here reaches the screen at the same
|
|
// density as the identical body imported through Prepare. It was 0.01 linear — 3.3x coarser
|
|
// than anything else in the app, which is why curved faces read as faceted next to an
|
|
// imported part. Angular already matched. Same BRepMesh_IncrementalMesh call, same GLVolume
|
|
// path, same shaders: the renderer was never the difference, the mesh fed to it was.
|
|
double linear_deflection{0.003};
|
|
double angular_deflection{0.5};
|
|
|
|
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);
|
|
// Onshape-style multi-entity sketch: stores the entity list verbatim. When
|
|
// non-empty it takes precedence over profile/enum in build_sketch_wire.
|
|
int add_sketch_entities(const std::vector<SketchEntity>& entities,
|
|
const SketchPlane& plane, const std::string& name,
|
|
const std::vector<SketchEntityConstraintDef>& constraints = {});
|
|
// Project edges of source_body onto plane, producing a sketch feature whose
|
|
// entities are (re)derived on every recompute.
|
|
int add_project_edges(int source_body, const std::vector<int>& edge_ids, int face,
|
|
const SketchPlane& plane, const std::string& name);
|
|
// Append a bridging BSpline entity connecting endpoint `end_a` of entity `ent_a` to
|
|
// endpoint `end_b` of entity `ent_b`, both within sketch feature `sketch_ref`. Returns
|
|
// the new entity's index within that sketch's entities vector. Non-parametric: computed
|
|
// once from the current endpoints (does not auto-follow later solver moves).
|
|
int add_bridge(int sketch_ref, int ent_a, int end_a, int ent_b, int end_b,
|
|
const std::string& name);
|
|
// Solve features[index]'s sketch constraints, writing solved coordinates back
|
|
// into its profile.points. No-op (returns true) if the feature has no
|
|
// constraints. Returns false if index is invalid / not a Sketch / solve fails.
|
|
bool solve_sketch_feature(int index);
|
|
int add_extrude(int sketch_ref, double distance, bool symmetric,
|
|
BooleanMode mode, const std::string& name);
|
|
// Extrude a single loop given directly as entities (sketch_ref = -1, plane carried).
|
|
int add_extrude_entities(const std::vector<SketchEntity>& entities,
|
|
const SketchPlane& plane, double distance, bool symmetric,
|
|
BooleanMode mode, const std::string& name);
|
|
// Extrude an existing solid FACE (global face id on the body) as the profile.
|
|
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_chamfer(double distance, FaceGroup faces, const std::string& name);
|
|
int add_chamfer(double distance, int edge_id, 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);
|
|
// Self-contained revolve of a single loop given directly as entities (sketch_ref=-1).
|
|
int add_revolve_entities(const std::vector<SketchEntity>& entities,
|
|
const SketchPlane& plane, double angle, int axis, bool flip,
|
|
BooleanMode mode, const std::string& name);
|
|
// Sweep the profile Sketch (profile_sketch_ref) along the path Sketch (path_sketch_ref).
|
|
int add_pattern(bool circular, int count, double spacing, int dir,
|
|
double angle_deg, int target_body, const std::string& name);
|
|
// Pattern `count` copies of `target` along entity `curve_entity` of sketch `curve_sketch`.
|
|
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);
|
|
// Loft through the ordered profile Sketches (each a closed wire on its own plane).
|
|
int add_loft(const std::vector<int>& profile_refs, bool ruled, BooleanMode mode,
|
|
const std::string& name);
|
|
// Skin 2+ profile sketches open (no end caps) -> a sheet body.
|
|
int add_surface_loft(const std::vector<int>& profile_refs, bool ruled, const std::string& name);
|
|
// Fill sketch sketch_ref's closed boundary wire with a smooth face -> a one-face sheet body.
|
|
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);
|
|
// Grow a thin rib wall (thickness, depth) from the open Line entity `entity` inside sketch
|
|
// feature `sketch_ref`, fused to `target_body`. Returns the new feature index.
|
|
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);
|
|
// Boolean between two existing bodies. op reuses BooleanMode (Add=union, Cut=subtract,
|
|
// Intersect=common; New invalid). target survives, tool is consumed unless keep_tool.
|
|
// tolerance = OCCT fuzzy value; target_face/tool_face (-1 = none) drive the per-face snap+merge.
|
|
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);
|
|
// Plane Cut (Onshape split-by-plane): trim target_body by the plane (origin offset along
|
|
// its normal by `offset`, normal flipped iff `flip`). keep_upper/keep_lower select the
|
|
// +normal / -normal half; both => the body is split into two coexisting bodies.
|
|
int add_cut(const SketchPlane& plane, double offset, bool flip,
|
|
bool keep_upper, bool keep_lower, int target_body, const std::string& name);
|
|
// Split target_body along the plane of face `face` (owned by face_body, -1 = target).
|
|
// keep_upper/keep_lower select which half survives; both => split into two bodies.
|
|
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);
|
|
// Rigid body transform: rotate `angle_deg` about `axis` through `pivot`, then translate.
|
|
// copy=true keeps the source body and appends the transformed one as a new body.
|
|
int add_transform(int target_body, const Vec3d& translate, const Vec3d& axis,
|
|
const Vec3d& pivot, double angle_deg, bool copy, const std::string& name);
|
|
// Offset face `face` of `target_body` by `thickness` along its normal, producing a new
|
|
// thin solid appended as a new body. flip=true offsets against the normal.
|
|
int add_thicken(int target_body, int face, double thickness, bool flip, const std::string& name);
|
|
// Thicken an entire SHEET body's shell into a solid.
|
|
int add_thicken_surface(int target_body, double thickness, bool flip, const std::string& name);
|
|
// Offset a SHEET body's shell by a signed distance, producing another SHEET body.
|
|
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);
|
|
// Datum plane: derived from base (0=XY/1=XZ/2=YZ/3+N=Nth earlier datum), offset
|
|
// along its normal, optional tilt about a base axis. Produces no solid.
|
|
int add_plane(int base, double offset, double angle_tilt, int axis,
|
|
const std::string& name);
|
|
// Datum axis: construction method axis_type determines which ref fields are read.
|
|
int add_axis(AxisType axis_type, const std::string& name);
|
|
// Datum coordinate system.
|
|
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);
|
|
|
|
// Which mate types apply to a connector pair, as reported to the viewport palette.
|
|
struct MateOption {
|
|
int kind{0}; // 0..4, the five mate types in CadDocument.hpp:308-314
|
|
bool viable{true};
|
|
std::string reason; // empty when viable; why not, when not
|
|
};
|
|
// ALWAYS all five entries, ALWAYS in kind order. Never filtered: the caller dims what is
|
|
// not viable rather than hiding it, so the list must be stable in length and order between
|
|
// calls. Pure query over existing data — records nothing, mutates nothing.
|
|
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);
|
|
// Build the helix wire from a Helix feature's params (exposed for tests).
|
|
TopoDS_Wire build_helix_wire(const CadFeature& f, std::string& err) const;
|
|
// Every datum plane currently in the recipe, in feature order, as (name, plane).
|
|
// Used by the GUI to populate plane pickers (after the 3 base planes).
|
|
std::vector<std::pair<std::string, SketchPlane>> resolve_datum_planes() const;
|
|
|
|
// World-space sketch plane lying on a body's PLANAR face, so a face picked in the viewport can
|
|
// be sketched on directly — no datum plane in between and nothing to choose from a list.
|
|
// Returns false when the indices don't resolve or the face isn't planar (a cylinder or a fillet
|
|
// has no single plane, and guessing one from a mid-parameter normal would silently sketch on a
|
|
// tangent). Same derivation the Coincident datum method uses, shared so the two cannot drift.
|
|
bool plane_of_face(int body_idx, int face_idx, SketchPlane& out) const;
|
|
// Resolved datum axes in feature order. axis_err is non-empty if construction failed.
|
|
struct DatumAxis { std::string name; Vec3d origin{0,0,0}; Vec3d direction{0,0,1};
|
|
std::string error; };
|
|
std::vector<DatumAxis> resolve_datum_axes() const;
|
|
// Resolved datum coordinate systems. X/Y unit, orthonormal (Z = X.cross(Y)).
|
|
struct DatumCoordSys { std::string name; Vec3d origin{0,0,0}; Vec3d x{1,0,0};
|
|
Vec3d y{0,1,0}; std::string error; };
|
|
std::vector<DatumCoordSys> resolve_datum_coordsys() const;
|
|
void clear();
|
|
bool recompute(); // replay features -> body + display_mesh; false on error
|
|
|
|
// CadRecipe serialization contract:
|
|
// - v1 blobs are deliberately not loadable; there is no migration path by design
|
|
// - append fields ONLY at the end of save/load, never reorder (golden fixture enforces this)
|
|
// Bumped every time the bodies are rebuilt, i.e. every time the face and edge MAPS change.
|
|
// Global face/edge ids are indices into TopExp::MapShapes and mean nothing across a rebuild,
|
|
// so any caller holding an id from an earlier state is holding a wrong one. This is the
|
|
// handle that lets it find out instead of silently addressing the wrong edge.
|
|
//
|
|
// Session-scoped and deliberately NOT serialized: an id is only meaningful within the run
|
|
// that produced it, so persisting the counter would imply a promise across loads that the
|
|
// ids themselves cannot keep.
|
|
uint64_t topo_generation{1};
|
|
|
|
// v5: every feature is length-framed, so a reader can stop early on an older file and skip
|
|
// the tail of a newer one. This is the LAST version that has to break anything — from here a
|
|
// new field only needs appending to save/load, with no bump and no orphaned projects.
|
|
// v4 is still read, by the pre-framing flat path, so existing projects keep opening.
|
|
static constexpr uint32_t SNAPORCA_CAD_RECIPE_VERSION = 5;
|
|
std::string serialize_recipe() const;
|
|
bool deserialize_recipe(const std::string& blob);
|
|
|
|
// Export every body to a STEP file as native B-rep (not mesh). body_xforms is the
|
|
// per-body display transform (Move gizmo); when supplied the bodies are written at
|
|
// those positions so the STEP matches what Commit ships. false + err on failure.
|
|
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;
|
|
|
|
// One overlapping pair of solid bodies. Indices are into `bodies`, a_ < b_.
|
|
struct Interference { int body_a{-1}; int body_b{-1}; double volume{0}; };
|
|
// Every pair of solid bodies whose intersection encloses more than min_volume (mm^3).
|
|
// Reports only — mutates nothing, so mates and placements are unaffected by calling it.
|
|
// Sheet bodies are skipped: an intersection involving one encloses no volume.
|
|
std::vector<Interference> check_interference(double min_volume = 1e-6) const;
|
|
|
|
// ponytail: derived from the OCCT shape type; no stored flag, bodies aren't serialized anyway.
|
|
static bool is_sheet_shape(const TopoDS_Shape& s); // true if TopExp finds no TopAbs_SOLID
|
|
|
|
// Undo/redo of the feature recipe (Onshape-style Ctrl+Z). The caller marks a
|
|
// user-action boundary by calling checkpoint() BEFORE the mutation(s) for that
|
|
// action (add/delete/move/replace, or a direct features edit). undo()/redo() then
|
|
// restore the snapshot and recompute(). Because everything else (bodies/meshes/
|
|
// body) is derived by recompute(), snapshotting `features` alone is a complete,
|
|
// exact history; one checkpoint == one Ctrl+Z step.
|
|
void checkpoint(); // snapshot `features` for undo + invalidate redo
|
|
bool can_undo() const { return !m_undo.empty(); }
|
|
bool can_redo() const { return !m_redo.empty(); }
|
|
size_t undo_depth() const { return m_undo.size(); }
|
|
size_t redo_depth() const { return m_redo.size(); }
|
|
bool undo(); // restore the previous feature list + recompute(); false if no history
|
|
bool redo(); // re-apply the most recently undone change; false if none
|
|
|
|
// Feature-tree editing (Onshape-style). All are transactional: they snapshot
|
|
// features, mutate, recompute(), and roll back to the snapshot (re-recomputing)
|
|
// if the result is invalid — so a failed edit never leaves a broken body.
|
|
//
|
|
// remove_feature: erase features[index]; deleting a Sketch cascades to the
|
|
// Extrude(s) that consume it; surviving sketch_ref indices are remapped.
|
|
// move_feature: shift features[index] by delta (-1 up / +1 down), clamped;
|
|
// sketch_ref indices of the two swapped slots are remapped.
|
|
// replace_feature: overwrite features[index] with `edited` (its name and, for
|
|
// an Extrude, its sketch_ref are preserved from the original).
|
|
bool remove_feature(int index);
|
|
bool move_feature(int index, int delta);
|
|
bool replace_feature(int index, const CadFeature& edited);
|
|
// replace_sketch_extrude: a box is two linked features (Sketch + Extrude);
|
|
// overwrite both slots from one `edited` candidate (sketch params ->
|
|
// features[sketch_idx], extrude params -> features[extrude_idx]), keeping
|
|
// each slot's name/type and the sketch_ref link. Transactional like above.
|
|
bool replace_sketch_extrude(int sketch_idx, int extrude_idx, const CadFeature& edited);
|
|
|
|
// Apply ONE candidate feature on top of the current committed body and
|
|
// tessellate the result into out_mesh, WITHOUT modifying features/body/
|
|
// display_mesh. Returns false (with err set) if the candidate is invalid.
|
|
// Used by the Design tab to show a translucent ghost before Confirm.
|
|
bool preview(const CadFeature& candidate, TriangleMesh& out_mesh, std::string& err) const;
|
|
// Same, but also returns the per-body meshes (in `bodies` order; the candidate may append
|
|
// one), so the GUI can apply its display-only per-body Move transforms to the ghost and keep
|
|
// it overlaid on the moved body instead of floating back at the untransformed origin.
|
|
bool preview(const CadFeature& candidate, TriangleMesh& out_mesh,
|
|
std::vector<TriangleMesh>& out_body_meshes, std::string& err) const;
|
|
|
|
private:
|
|
TopoDS_Wire build_sketch_wire(const CadFeature& sketch) const;
|
|
// The planar region an Extrude sweeps: the sketch's outer loop with its inner loops as
|
|
// holes. Falls back to a face over build_sketch_wire() for the legacy profile/shape paths,
|
|
// which have no concept of a second loop.
|
|
TopoDS_Face build_sketch_face(const CadFeature& sketch) const;
|
|
// Apply a single feature to (result, have_body), throwing std::runtime_error on
|
|
// failure. `context` is the body whose faces/edges the feature reads (face-extrude
|
|
// source, up-to-face target, dress-up, hole) — it differs from `result` only when the
|
|
// feature builds a NEW body from an existing one (face-extrude New). Shared by route.
|
|
void apply_feature(TopoDS_Shape& result, bool& have_body,
|
|
const TopoDS_Shape& context, const CadFeature& f) const;
|
|
// Route one feature into the bodies list: resolve its target body, decide whether it
|
|
// starts a new body (empty list, or an Extrude with mode New) vs mutates an existing
|
|
// one, then apply_feature. Shared by recompute() (replay all) and preview() (candidate).
|
|
void route_feature(std::vector<CadBody>& bodies, const CadFeature& f) const;
|
|
// Boolean between two existing bodies: resolve target + tool, optionally snap the tool so
|
|
// the picked faces mate, run the OCCT op (with fuzzy tolerance), write the result back to the
|
|
// target and erase the consumed tool. Mutates the bodies vector directly (unlike apply_feature,
|
|
// which works on a single result shape). Throws std::runtime_error on a failed op.
|
|
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;
|
|
static DatumCoordSys datum_frame(const std::vector<CadBody>& bodies, const CadFeature& f);
|
|
void apply_mate(std::vector<CadBody>& bodies, const CadFeature& f) const;
|
|
void detect_mate_conflicts(); // refills mate_conflicts from the feature list alone
|
|
|
|
// Undo/redo stacks of recipe snapshots. checkpoint() pushes onto m_undo and clears
|
|
// m_redo; undo()/redo() shuffle the current state between them. Capped so a long
|
|
// session can't grow unbounded.
|
|
//
|
|
// The snapshot MUST carry `variables` as well as `features`: a caller that sets a bad
|
|
// variable, sees recompute() fail and calls undo() to roll it back would otherwise be
|
|
// left with the bad variable still in the document, so every later recompute fails —
|
|
// the exact corruption the checkpoint/undo pattern exists to prevent. Not serialized,
|
|
// so this changes no on-disk format.
|
|
struct Snapshot {
|
|
std::vector<CadFeature> features;
|
|
std::map<std::string, std::string> variables;
|
|
};
|
|
std::vector<Snapshot> m_undo;
|
|
std::vector<Snapshot> m_redo;
|
|
static constexpr size_t k_undo_cap = 200;
|
|
};
|
|
|
|
} // namespace Slic3r
|
|
|
|
#endif // slic3r_CadDocument_hpp_
|