Orca-Cad: port SnapOrca Design (parametric CAD tab) onto mainline OrcaSlicer

Grafts the sketch-first CAD environment from snaporca-cad onto the mainline
OrcaSlicer/OrcaSlicer base (vs snaporca's Snapmaker/OrcaSlicer base):
- 133 new files: CadDocument/SketchEngine/GeometryEngine/SketchConstraints/
  SketchSolver/SketchInference/ThreadStandards + vendored libslvs solver;
  DesignPanel/DesignCanvas/DesignSketchTool/SketchInlineEditor GUI; GLGizmo
  Primitive/Sketch; 75 design icons; Catch2 tests.
- Integration hooks ported to mainline's diverged versions: Design tab in
  MainFrame, embedded design viewport + sketch overlay + per-canvas chrome
  suppression in GLCanvas3D/PartPlate, gizmo registration, Plater accessors,
  CMake wiring (libslvs subdir, CAD sources, OCCT ModelingAlgorithms=ON).

Structural integration complete; build verification pending.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
This commit is contained in:
Tommaso Bianchi
2026-06-28 12:40:38 +02:00
co-authored by Claude Opus 4.8
parent 449a4cf9fc
commit 0f4060c0a9
146 changed files with 34845 additions and 17 deletions
+23
View File
@@ -20,6 +20,9 @@ endif()
option(BUILD_SHARED_LIBS "Build shared libs" OFF)
# Vendored SolveSpace constraint solver (2D sketch solver backbone).
add_subdirectory(slvs)
set(lisbslic3r_sources
AABBMesh.cpp
AABBMesh.hpp
@@ -275,6 +278,8 @@ set(lisbslic3r_sources
Geometry/VoronoiUtils.cpp
Geometry/VoronoiUtils.hpp
Geometry/VoronoiVisualUtils.hpp
GeometryEngine.cpp
GeometryEngine.hpp
Int128.hpp
KDTreeIndirect.hpp
Layer.cpp
@@ -385,6 +390,20 @@ set(lisbslic3r_sources
SLA/JobController.hpp
SLA/Pad.cpp
SLA/Pad.hpp
SketchEngine.cpp
SketchEngine.hpp
SketchConstraints.cpp
SketchConstraints.hpp
SketchSolver.cpp
SketchSolver.hpp
SketchInference.cpp
SketchInference.hpp
SketchImport.cpp
SketchImport.hpp
CadDocument.cpp
CadDocument.hpp
ThreadStandards.cpp
ThreadStandards.hpp
SLAPrint.cpp
SLAPrint.hpp
SLAPrintSteps.cpp
@@ -546,6 +565,9 @@ find_package(JPEG REQUIRED)
find_package(draco REQUIRED)
set(OCCT_LIBS
TKFillet
TKOffset
TKBool
TKXDESTEP
TKSTEP
TKSTEP209
@@ -591,6 +613,7 @@ target_link_libraries(libslic3r
clipper
Clipper2
draco::draco
libslvs
glu-libtess
JPEG::JPEG
libslic3r_cgal
File diff suppressed because it is too large Load Diff
+355
View File
@@ -0,0 +1,355 @@
#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 <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 };
enum class SketchShape { Rectangle, Circle };
enum class BooleanMode { New, Add, Cut, Intersect };
enum class ExtrudeEnd { Blind, Symmetric, TwoSided, ThroughAll, UpToFace, UpToVertex };
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)
// 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)
// 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
// 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)
};
// 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;
};
// OCCT-only feature tree backing the Design tab. No GUI dependencies (lives in libslic3r).
class CadDocument {
public:
std::vector<CadFeature> features;
// 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)
double linear_deflection{0.01};
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 = {});
// 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_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);
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);
int add_shell(double thickness, int face, 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);
// 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);
// 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;
void clear();
bool recompute(); // replay features -> body + display_mesh; false on error
// 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;
// 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;
// Undo/redo stacks of feature-list 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.
std::vector<std::vector<CadFeature>> m_undo;
std::vector<std::vector<CadFeature>> m_redo;
static constexpr size_t k_undo_cap = 200;
};
} // namespace Slic3r
#endif // slic3r_CadDocument_hpp_
+442
View File
@@ -0,0 +1,442 @@
#include "GeometryEngine.hpp"
#include <BRepMesh_IncrementalMesh.hxx>
#include <BRep_Tool.hxx>
#include <BRepAdaptor_Surface.hxx>
#include <BRepLProp_SLProps.hxx>
#include <gp_Cylinder.hxx>
#include <BRepFilletAPI_MakeFillet.hxx>
#include <BRepFilletAPI_MakeChamfer.hxx>
#include <stdexcept>
#include <TopExp_Explorer.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Face.hxx>
#include <TopoDS_Edge.hxx>
#include <TopExp.hxx>
#include <TopTools.hxx>
#include <TopTools_IndexedMapOfShape.hxx>
#include <Poly_Triangulation.hxx>
#include <gp_Ax2.hxx>
#include <gp_Dir.hxx>
#include <gp_Pnt.hxx>
#include <BRepGProp.hxx>
#include <GProp_GProps.hxx>
#include <GeomLProp_SLProps.hxx>
#include <BRepAdaptor_Curve.hxx>
#include <GCPnts_TangentialDeflection.hxx>
#include <STEPControl_Reader.hxx>
#include <IFSelect_ReturnStatus.hxx>
#include <Standard_Failure.hxx>
namespace Slic3r {
// ---- STEP import (B-rep, not mesh) ----
std::vector<TopoDS_Shape> GeometryEngine::read_step_solids(const std::string& path, std::string& err)
{
err.clear();
std::vector<TopoDS_Shape> out;
try {
STEPControl_Reader reader;
if (reader.ReadFile(path.c_str()) != IFSelect_RetDone) {
err = "cannot read STEP file";
return out;
}
reader.TransferRoots();
const TopoDS_Shape shape = reader.OneShape();
if (shape.IsNull()) { err = "STEP file has no geometry"; return out; }
// One body per top-level solid; fall back to the whole shape (shells/faces) if none.
for (TopExp_Explorer ex(shape, TopAbs_SOLID); ex.More(); ex.Next())
out.push_back(ex.Current());
if (out.empty())
out.push_back(shape);
} catch (const Standard_Failure& e) {
err = e.GetMessageString() ? e.GetMessageString() : "OCCT failed to read STEP";
out.clear();
}
return out;
}
// ---- Primitive creation ----
TopoDS_Solid GeometryEngine::make_primitive(const PrimitiveParams& params)
{
switch (params.type) {
case PrimitiveType::Box:
return BRepPrimAPI_MakeBox(gp_Pnt(-params.box_w/2, -params.box_d/2, 0),
params.box_w, params.box_d, params.box_h).Solid();
case PrimitiveType::Cylinder:
return BRepPrimAPI_MakeCylinder(gp_Ax2(gp_Pnt(0,0,0), gp_Dir(0,0,1)),
params.cyl_radius, params.cyl_height).Solid();
case PrimitiveType::Sphere:
return BRepPrimAPI_MakeSphere(gp_Pnt(0,0,params.sph_radius), params.sph_radius).Solid();
case PrimitiveType::Cone:
return BRepPrimAPI_MakeCone(gp_Ax2(gp_Pnt(0,0,0), gp_Dir(0,0,1)),
params.cone_r1, params.cone_r2, params.cone_height).Solid();
case PrimitiveType::Torus:
return BRepPrimAPI_MakeTorus(gp_Ax2(gp_Pnt(0,0,params.torus_r2), gp_Dir(0,0,1)),
params.torus_r1, params.torus_r2).Solid();
default:
return BRepPrimAPI_MakeBox(gp_Pnt(-10,-10,0), 20,20,20).Solid();
}
}
// ---- Face classification ----
FaceGroup GeometryEngine::classify_face(const TopoDS_Face& face, const TopoDS_Shape& /*solid*/)
{
try {
BRepAdaptor_Surface surf(face);
if (surf.GetType() == GeomAbs_Plane) {
// Sample normal at center UV
double u = (surf.FirstUParameter() + surf.LastUParameter()) / 2.0;
double v = (surf.FirstVParameter() + surf.LastVParameter()) / 2.0;
gp_Pnt pt; gp_Vec du, dv;
surf.D1(u, v, pt, du, dv);
gp_Dir n = du.Crossed(dv);
if (face.Orientation() == TopAbs_REVERSED) n.Reverse();
if (n.Z() > 0.7) return FaceGroup::Top;
if (n.Z() < -0.7) return FaceGroup::Bottom;
return FaceGroup::Lateral;
}
} catch (...) {}
return FaceGroup::Lateral;
}
// ---- Edge collection ----
std::vector<TopoDS_Edge> GeometryEngine::collect_edges(const TopoDS_Shape& solid, FaceGroup target)
{
std::vector<TopoDS_Edge> result;
if (target == FaceGroup::All) {
for (TopExp_Explorer exp(solid, TopAbs_EDGE); exp.More(); exp.Next())
result.push_back(TopoDS::Edge(exp.Current()));
return result;
}
// Build edge-to-face map once
TopTools_IndexedDataMapOfShapeListOfShape edgeFaceMap;
TopExp::MapShapesAndAncestors(solid, TopAbs_EDGE, TopAbs_FACE, edgeFaceMap);
for (TopExp_Explorer edgeExp(solid, TopAbs_EDGE); edgeExp.More(); edgeExp.Next()) {
const TopoDS_Edge& edge = TopoDS::Edge(edgeExp.Current());
if (!edgeFaceMap.Contains(edge)) continue;
const TopTools_ListOfShape& faces = edgeFaceMap.FindFromKey(edge);
bool include = false;
for (auto it = faces.begin(); it != faces.end(); ++it) {
FaceGroup fg = classify_face(TopoDS::Face(*it), solid);
if (target == FaceGroup::Top && fg == FaceGroup::Top) { include = true; break; }
if (target == FaceGroup::Bottom && fg == FaceGroup::Bottom) { include = true; break; }
if (target == FaceGroup::Lateral && fg == FaceGroup::Lateral) { include = true; break; }
}
if (!include && target == FaceGroup::Top) {
for (auto it = faces.begin(); it != faces.end(); ++it) {
if (classify_face(TopoDS::Face(*it), solid) == FaceGroup::Top) { include = true; break; }
}
}
if (!include && target == FaceGroup::Bottom) {
for (auto it = faces.begin(); it != faces.end(); ++it) {
if (classify_face(TopoDS::Face(*it), solid) == FaceGroup::Bottom) { include = true; break; }
}
}
if (target == FaceGroup::Lateral && !include) {
int lateralCount = 0;
for (auto it = faces.begin(); it != faces.end(); ++it) {
if (classify_face(TopoDS::Face(*it), solid) == FaceGroup::Lateral) ++lateralCount;
}
if (lateralCount >= 2) include = true;
}
if (include) result.push_back(edge);
}
return result;
}
// ---- Fillet/Chamfer ----
TopoDS_Shape GeometryEngine::apply_fillet(const TopoDS_Shape& solid, double radius, FaceGroup faces)
{
if (radius <= 0.001) return solid;
std::vector<TopoDS_Edge> edges = collect_edges(solid, faces);
if (edges.empty()) return solid;
BRepFilletAPI_MakeFillet fillet(solid);
for (const auto& edge : edges)
fillet.Add(radius, edge);
fillet.Build();
// A too-large radius (e.g. >= half the smallest spanned dimension) makes the
// operation degenerate; OCCT leaves IsDone() false. Report it instead of
// silently returning the unfilleted solid (which reads as a false success).
if (!fillet.IsDone()) throw std::runtime_error("fillet radius too large for this geometry");
return fillet.Shape();
}
TopoDS_Shape GeometryEngine::apply_chamfer(const TopoDS_Shape& solid, double distance, FaceGroup faces)
{
if (distance <= 0.001) return solid;
std::vector<TopoDS_Edge> edges = collect_edges(solid, faces);
if (edges.empty()) return solid;
BRepFilletAPI_MakeChamfer chamfer(solid);
for (const auto& edge : edges)
chamfer.Add(distance, edge); // symmetric chamfer
chamfer.Build();
if (!chamfer.IsDone()) throw std::runtime_error("chamfer distance too large for this geometry");
return chamfer.Shape();
}
TopoDS_Shape GeometryEngine::apply_fillet(const TopoDS_Shape& solid, double radius, int edge_id)
{
if (radius <= 0.001) return solid;
TopoDS_Edge edge = edge_by_index(solid, edge_id);
if (edge.IsNull()) throw std::runtime_error("apply_fillet: invalid edge id");
BRepFilletAPI_MakeFillet mk(solid);
mk.Add(radius, edge);
mk.Build();
if (!mk.IsDone()) throw std::runtime_error("apply_fillet: OCCT fillet failed");
return mk.Shape();
}
TopoDS_Shape GeometryEngine::apply_chamfer(const TopoDS_Shape& solid, double distance, int edge_id)
{
if (distance <= 0.001) return solid;
TopoDS_Edge edge = edge_by_index(solid, edge_id);
if (edge.IsNull()) throw std::runtime_error("apply_chamfer: invalid edge id");
BRepFilletAPI_MakeChamfer mk(solid);
mk.Add(distance, edge);
mk.Build();
if (!mk.IsDone()) throw std::runtime_error("apply_chamfer: OCCT chamfer failed");
return mk.Shape();
}
// ---- Tessellation ----
TriangleMesh GeometryEngine::tessellate(const TopoDS_Shape& shape,
double linear_deflection,
double angular_deflection)
{
BRepMesh_IncrementalMesh mesh(shape, linear_deflection, false, angular_deflection, true);
int nbNodes = 0, nbTri = 0;
for (TopExp_Explorer exp(shape, TopAbs_FACE); exp.More(); exp.Next()) {
TopLoc_Location loc;
Handle(Poly_Triangulation) tri = BRep_Tool::Triangulation(TopoDS::Face(exp.Current()), loc);
if (!tri.IsNull()) { nbNodes += tri->NbNodes(); nbTri += tri->NbTriangles(); }
}
if (nbTri == 0 || nbNodes == 0) return TriangleMesh{};
stl_file stl;
stl.stats.type = inmemory;
stl.stats.number_of_facets = (uint32_t)nbTri;
stl.stats.original_num_facets = stl.stats.number_of_facets;
stl_allocate(&stl);
std::vector<Vec3f> pts; pts.reserve(nbNodes);
int ndOff = 0, trOff = 0;
for (TopExp_Explorer exp(shape, TopAbs_FACE); exp.More(); exp.Next()) {
const TopoDS_Shape& F = exp.Current();
TopLoc_Location loc;
Handle(Poly_Triangulation) tri = BRep_Tool::Triangulation(TopoDS::Face(F), loc);
if (tri.IsNull()) continue;
gp_Trsf T = loc.Transformation();
for (int i = 1; i <= tri->NbNodes(); ++i) {
gp_Pnt p = tri->Node(i); p.Transform(T);
pts.emplace_back(Vec3f(p.X(), p.Y(), p.Z()));
}
auto orient = exp.Current().Orientation();
int ids[3];
for (int i = 1; i <= tri->NbTriangles(); ++i) {
Poly_Triangle t = tri->Triangle(i); t.Get(ids[0], ids[1], ids[2]);
if (orient == TopAbs_REVERSED) std::swap(ids[1], ids[2]);
stl_facet f;
f.vertex[0] = pts[ids[0]+ndOff-1].cast<float>();
f.vertex[1] = pts[ids[1]+ndOff-1].cast<float>();
f.vertex[2] = pts[ids[2]+ndOff-1].cast<float>();
f.extra[0]=0; f.extra[1]=0;
stl_normal n; stl_calculate_normal(n,&f); stl_normalize_vector(n);
f.normal=n; stl.facet_start[trOff+i-1]=f;
}
ndOff += tri->NbNodes(); trOff += tri->NbTriangles();
}
TriangleMesh result; result.from_stl(stl); return result;
}
std::string GeometryEngine::primitive_name(PrimitiveType type)
{
switch (type) {
case PrimitiveType::Box: return "Box";
case PrimitiveType::Cylinder: return "Cylinder";
case PrimitiveType::Sphere: return "Sphere";
case PrimitiveType::Cone: return "Cone";
case PrimitiveType::Torus: return "Torus";
default: return "Unknown";
}
}
// ---- Topology accessors ----
int GeometryEngine::face_count(const TopoDS_Shape& shape)
{
int n = 0;
for (TopExp_Explorer e(shape, TopAbs_FACE); e.More(); e.Next())
++n;
return n;
}
TopoDS_Face GeometryEngine::face_by_index(const TopoDS_Shape& shape, int index)
{
if (index < 0) return TopoDS_Face();
int ordinal = 0;
for (TopExp_Explorer e(shape, TopAbs_FACE); e.More(); e.Next()) {
if (ordinal == index)
return TopoDS::Face(e.Current());
++ordinal;
}
return TopoDS_Face();
}
std::vector<TopoDS_Edge> GeometryEngine::edges_of_face(const TopoDS_Face& face)
{
std::vector<TopoDS_Edge> result;
TopTools_IndexedMapOfShape map;
TopExp::MapShapes(face, TopAbs_EDGE, map);
for (int i = 1; i <= map.Extent(); ++i)
result.push_back(TopoDS::Edge(map(i)));
return result;
}
std::vector<Vec3d> GeometryEngine::sample_edge_world(const TopoDS_Edge& edge, double chord_tol)
{
if (BRep_Tool::Degenerated(edge))
return {};
BRepAdaptor_Curve curve(edge);
GCPnts_TangentialDeflection disc(curve, 0.1, chord_tol);
std::vector<Vec3d> pts;
if (disc.NbPoints() >= 2) {
for (int i = 1; i <= disc.NbPoints(); ++i) {
gp_Pnt p = disc.Value(i);
pts.emplace_back(p.X(), p.Y(), p.Z());
}
} else {
gp_Pnt p0 = curve.Value(curve.FirstParameter());
gp_Pnt p1 = curve.Value(curve.LastParameter());
pts.emplace_back(p0.X(), p0.Y(), p0.Z());
pts.emplace_back(p1.X(), p1.Y(), p1.Z());
}
return pts;
}
Vec3d GeometryEngine::face_centroid_world(const TopoDS_Face& face)
{
GProp_GProps props;
BRepGProp::SurfaceProperties(face, props);
gp_Pnt c = props.CentreOfMass();
return Vec3d(c.X(), c.Y(), c.Z());
}
Vec3d GeometryEngine::face_normal_world(const TopoDS_Face& face)
{
BRepAdaptor_Surface surf(face);
const double u = 0.5 * (surf.FirstUParameter() + surf.LastUParameter());
const double v = 0.5 * (surf.FirstVParameter() + surf.LastVParameter());
BRepLProp_SLProps props(surf, u, v, 1, 1e-6);
gp_Dir n(0.0, 0.0, 1.0);
if (props.IsNormalDefined()) n = props.Normal();
if (face.Orientation() == TopAbs_REVERSED) n.Reverse(); // outward (account for face winding)
return Vec3d(n.X(), n.Y(), n.Z());
}
GeometryEngine::CylinderFace GeometryEngine::cylinder_of_face(const TopoDS_Face& face)
{
CylinderFace cf;
if (face.IsNull()) return cf;
BRepAdaptor_Surface surf(face);
if (surf.GetType() != GeomAbs_Cylinder) return cf;
const gp_Cylinder cyl = surf.Cylinder();
const gp_Ax1 ax = cyl.Axis();
const Vec3d axis(ax.Direction().X(), ax.Direction().Y(), ax.Direction().Z());
const Vec3d apt (ax.Location().X(), ax.Location().Y(), ax.Location().Z());
cf.radius = cyl.Radius();
// Axial extent: V is the axial parameter on a cylinder; bound the face's two ends and
// order them so `axis` points base -> top.
const double umid = 0.5 * (surf.FirstUParameter() + surf.LastUParameter());
const gp_Pnt e0 = surf.Value(umid, surf.FirstVParameter());
const gp_Pnt e1 = surf.Value(umid, surf.LastVParameter());
double t0 = (Vec3d(e0.X(), e0.Y(), e0.Z()) - apt).dot(axis);
double t1 = (Vec3d(e1.X(), e1.Y(), e1.Z()) - apt).dot(axis);
if (t1 < t0) std::swap(t0, t1);
cf.base = apt + axis * t0;
cf.axis = axis;
cf.height = t1 - t0;
// Internal (bore) vs external: compare the face's outward normal at its centre to the
// outward radial direction. A bore's normal points toward the axis (dot < 0).
const gp_Pnt sp = surf.Value(umid, 0.5 * (surf.FirstVParameter() + surf.LastVParameter()));
const Vec3d S(sp.X(), sp.Y(), sp.Z());
const Vec3d axpt = cf.base + axis * (S - cf.base).dot(axis);
const Vec3d radial = (S - axpt).normalized();
cf.internal = face_normal_world(face).dot(radial) < 0.0;
cf.ok = true;
return cf;
}
bool GeometryEngine::face_plane_bounds(const TopoDS_Face& face, const Vec3d& origin,
const Vec3d& x_axis, const Vec3d& y_axis,
double& umin, double& umax, double& vmin, double& vmax)
{
umin = vmin = 1e30; umax = vmax = -1e30;
bool any = false;
for (TopExp_Explorer ex(face, TopAbs_VERTEX); ex.More(); ex.Next()) {
const gp_Pnt p = BRep_Tool::Pnt(TopoDS::Vertex(ex.Current()));
const Vec3d P(p.X(), p.Y(), p.Z());
const double u = (P - origin).dot(x_axis);
const double v = (P - origin).dot(y_axis);
umin = std::min(umin, u); umax = std::max(umax, u);
vmin = std::min(vmin, v); vmax = std::max(vmax, v);
any = true;
}
return any;
}
int GeometryEngine::edge_count(const TopoDS_Shape& shape)
{
TopTools_IndexedMapOfShape map;
TopExp::MapShapes(shape, TopAbs_EDGE, map);
return map.Extent();
}
TopoDS_Edge GeometryEngine::edge_by_index(const TopoDS_Shape& shape, int index)
{
TopTools_IndexedMapOfShape map;
TopExp::MapShapes(shape, TopAbs_EDGE, map);
if (index < 0 || index >= map.Extent())
return TopoDS_Edge();
return TopoDS::Edge(map(index + 1));
}
int GeometryEngine::edge_index_of(const TopoDS_Shape& shape, const TopoDS_Edge& edge)
{
TopTools_IndexedMapOfShape map;
TopExp::MapShapes(shape, TopAbs_EDGE, map);
int idx = map.FindIndex(edge);
return (idx > 0) ? (idx - 1) : -1;
}
} // namespace Slic3r
+121
View File
@@ -0,0 +1,121 @@
#ifndef slic3r_GeometryEngine_hpp_
#define slic3r_GeometryEngine_hpp_
#include "TriangleMesh.hpp"
#include <BRepPrimAPI_MakeBox.hxx>
#include <BRepPrimAPI_MakeCylinder.hxx>
#include <BRepPrimAPI_MakeSphere.hxx>
#include <BRepPrimAPI_MakeCone.hxx>
#include <BRepPrimAPI_MakeTorus.hxx>
#include <gp_Ax2.hxx>
#include <TopoDS_Solid.hxx>
#include <TopoDS_Face.hxx>
#include <TopoDS_Edge.hxx>
#include <vector>
#include <string>
namespace Slic3r {
enum class PrimitiveType { Box, Cylinder, Sphere, Cone, Torus, COUNT };
enum class DressUpType { Fillet, Chamfer };
enum class FaceGroup { Top, Bottom, Lateral, All };
struct PrimitiveParams {
PrimitiveType type{PrimitiveType::Box};
double box_w{20}, box_h{20}, box_d{20};
double cyl_radius{10}, cyl_height{20};
double sph_radius{10};
double cone_r1{10}, cone_r2{5}, cone_height{20};
double torus_r1{10}, torus_r2{3};
// Dress-up
bool dressup_enabled{false};
DressUpType dressup_type{DressUpType::Fillet};
FaceGroup dressup_faces{FaceGroup::All};
double dressup_radius{1.0}; // fillet radius
double dressup_chamfer_dist{1.0}; // chamfer distance (symmetric)
// Mesh quality
double linear_deflection{0.01};
double angular_deflection{0.5};
template<class Archive>
void serialize(Archive& ar) {
ar(type, box_w, box_h, box_d, cyl_radius, cyl_height, sph_radius,
cone_r1, cone_r2, cone_height, torus_r1, torus_r2,
dressup_enabled, dressup_type, dressup_faces, dressup_radius, dressup_chamfer_dist,
linear_deflection, angular_deflection);
}
};
class GeometryEngine
{
public:
static TopoDS_Solid make_primitive(const PrimitiveParams& params);
// Read a STEP file into its top-level solids (one TopoDS_Shape per solid; falls back to
// the whole shape if it contains no closed solids). Reuses OCCT's STEPControl_Reader,
// already linked via Format/STEP.cpp — no new dependency. err is set on failure (empty result).
static std::vector<TopoDS_Shape> read_step_solids(const std::string& path, std::string& err);
static TopoDS_Shape apply_fillet(const TopoDS_Shape& solid, double radius,
FaceGroup faces = FaceGroup::All);
static TopoDS_Shape apply_fillet(const TopoDS_Shape& solid, double radius,
int edge_id);
static TopoDS_Shape apply_chamfer(const TopoDS_Shape& solid, double distance,
FaceGroup faces = FaceGroup::All);
static TopoDS_Shape apply_chamfer(const TopoDS_Shape& solid, double distance,
int edge_id);
static TriangleMesh tessellate(const TopoDS_Shape& shape,
double linear_deflection = 0.01,
double angular_deflection = 0.5);
static std::string primitive_name(PrimitiveType type);
// Topology accessors for in-viewport face/edge picking (Design tab). Face index is the
// TopExp_Explorer(shape, TopAbs_FACE) ordinal — identical to SketchEngine::tessellate's
// per-triangle face id, so a picked triangle's id maps back to a face here.
static TopoDS_Face face_by_index(const TopoDS_Shape& shape, int index); // null if out of range
static int face_count(const TopoDS_Shape& shape);
static std::vector<TopoDS_Edge> edges_of_face(const TopoDS_Face& face);
// Centre of mass (world) of a face — used to compute the extrude length for "up to face".
static Vec3d face_centroid_world(const TopoDS_Face& face);
// Outward unit normal of a face at its UV midpoint (orientation-aware) — for the shell gizmo.
static Vec3d face_normal_world(const TopoDS_Face& face);
// Sample an edge into a world-space polyline (>=2 pts) for pick-distance + highlight.
static std::vector<Vec3d> sample_edge_world(const TopoDS_Edge& edge, double chord_tol = 0.05);
// 0-based edge index into TopExp::MapShapes(shape, TopAbs_EDGE, map).
static int edge_count(const TopoDS_Shape& shape);
static TopoDS_Edge edge_by_index(const TopoDS_Shape& shape, int index);
static int edge_index_of(const TopoDS_Shape& shape, const TopoDS_Edge& edge);
// Analysis of a cylindrical face for the Thread tool (a hole bore or a cylinder's lateral
// surface): axis (base at the lower axial end + unit direction), radius, axial extent, and
// whether it is a bore (face normal points toward the axis = internal thread). ok=false if
// the face is not a cylinder.
struct CylinderFace {
bool ok{false};
Vec3d base{0, 0, 0};
Vec3d axis{0, 0, 1};
double radius{0};
double height{0};
bool internal{false};
};
static CylinderFace cylinder_of_face(const TopoDS_Face& face);
// Plane-coordinate (u,v) bounding box of a face's vertices, measured from `origin` along
// `x_axis`/`y_axis`. Lets the Hole tool dimension the hole from the face SIDES (umin/vmin =
// two adjacent edges) instead of from the centre. Returns false if the face has no vertices.
static bool face_plane_bounds(const TopoDS_Face& face, const Vec3d& origin,
const Vec3d& x_axis, const Vec3d& y_axis,
double& umin, double& umax, double& vmin, double& vmax);
private:
static std::vector<TopoDS_Edge> collect_edges(const TopoDS_Shape& solid, FaceGroup faces);
static FaceGroup classify_face(const TopoDS_Face& face, const TopoDS_Shape& solid);
};
} // namespace Slic3r
#endif // slic3r_GeometryEngine_hpp_
+307
View File
@@ -0,0 +1,307 @@
#include "SketchConstraints.hpp"
#include <Eigen/Dense>
#include <cmath>
namespace Slic3r {
int SketchConstraints::add_point(double x, double y)
{
m_vars.push_back(x);
m_vars.push_back(y);
return static_cast<int>(m_vars.size() / 2) - 1;
}
void SketchConstraints::set_point(int id, double x, double y)
{
size_t idx = 2 * id;
m_vars[idx] = x;
m_vars[idx + 1] = y;
}
Vec2d SketchConstraints::get_point(int id) const
{
size_t idx = 2 * id;
return Vec2d(m_vars[idx], m_vars[idx + 1]);
}
int SketchConstraints::point_count() const
{
return static_cast<int>(m_vars.size() / 2);
}
void SketchConstraints::fix_point(int id)
{
size_t idx = 2 * id;
Con c;
c.type = FIX_POINT;
c.a = id;
c.b = c.c = c.d = 0;
c.k0 = m_vars[idx];
c.k1 = m_vars[idx + 1];
m_cons.push_back(c);
}
void SketchConstraints::coincident(int a, int b)
{
Con c;
c.type = COINCIDENT;
c.a = a; c.b = b; c.c = c.d = 0;
c.k0 = c.k1 = 0;
m_cons.push_back(c);
}
void SketchConstraints::horizontal(int a, int b)
{
Con c;
c.type = HORIZONTAL;
c.a = a; c.b = b; c.c = c.d = 0;
c.k0 = c.k1 = 0;
m_cons.push_back(c);
}
void SketchConstraints::vertical(int a, int b)
{
Con c;
c.type = VERTICAL;
c.a = a; c.b = b; c.c = c.d = 0;
c.k0 = c.k1 = 0;
m_cons.push_back(c);
}
void SketchConstraints::distance(int a, int b, double d)
{
Con c;
c.type = DISTANCE;
c.a = a; c.b = b; c.c = c.d = 0;
c.k0 = d; c.k1 = 0;
m_cons.push_back(c);
}
void SketchConstraints::lock_x(int id, double x)
{
Con c;
c.type = LOCK_X;
c.a = id;
c.b = c.c = c.d = 0;
c.k0 = x; c.k1 = 0;
m_cons.push_back(c);
}
void SketchConstraints::lock_y(int id, double y)
{
Con c;
c.type = LOCK_Y;
c.a = id;
c.b = c.c = c.d = 0;
c.k0 = y; c.k1 = 0;
m_cons.push_back(c);
}
void SketchConstraints::equal_length(int a, int b, int c, int d)
{
Con con;
con.type = EQUAL_LENGTH;
con.a = a; con.b = b; con.c = c; con.d = d;
con.k0 = con.k1 = 0;
m_cons.push_back(con);
}
void SketchConstraints::parallel(int a, int b, int c, int d)
{
Con con;
con.type = PARALLEL;
con.a = a; con.b = b; con.c = c; con.d = d;
con.k0 = con.k1 = 0;
m_cons.push_back(con);
}
void SketchConstraints::perpendicular(int a, int b, int c, int d)
{
Con con;
con.type = PERPENDICULAR;
con.a = a; con.b = b; con.c = c; con.d = d;
con.k0 = con.k1 = 0;
m_cons.push_back(con);
}
void SketchConstraints::midpoint(int m, int a, int b)
{
Con con;
con.type = MIDPOINT;
con.a = m; con.b = a; con.c = b; con.d = -1;
con.k0 = con.k1 = 0;
m_cons.push_back(con);
}
void SketchConstraints::symmetric(int a, int b, int c, int d)
{
Con con;
con.type = SYMMETRIC;
con.a = a; con.b = b; con.c = c; con.d = d;
con.k0 = con.k1 = 0;
m_cons.push_back(con);
}
void SketchConstraints::angle(int a, int b, int c, int d, double radians)
{
Con con;
con.type = ANGLE;
con.a = a; con.b = b; con.c = c; con.d = d;
con.k0 = radians; con.k1 = 0;
m_cons.push_back(con);
}
void SketchConstraints::point_line_distance(int p, int a, int b, double dist)
{
Con con;
con.type = PT_LINE_DIST;
con.a = p; con.b = a; con.c = b; con.d = -1;
con.k0 = dist; con.k1 = 0;
m_cons.push_back(con);
}
Eigen::VectorXd SketchConstraints::residuals(const std::vector<double>& v) const
{
auto X = [&](int i) { return v[2 * i]; };
auto Y = [&](int i) { return v[2 * i + 1]; };
std::vector<double> res;
for (const auto& c : m_cons) {
switch (c.type) {
case FIX_POINT:
res.push_back(X(c.a) - c.k0);
res.push_back(Y(c.a) - c.k1);
break;
case COINCIDENT:
res.push_back(X(c.a) - X(c.b));
res.push_back(Y(c.a) - Y(c.b));
break;
case HORIZONTAL:
res.push_back(Y(c.a) - Y(c.b));
break;
case VERTICAL:
res.push_back(X(c.a) - X(c.b));
break;
case DISTANCE:
res.push_back(std::hypot(X(c.a) - X(c.b), Y(c.a) - Y(c.b)) - c.k0);
break;
case LOCK_X:
res.push_back(X(c.a) - c.k0);
break;
case LOCK_Y:
res.push_back(Y(c.a) - c.k0);
break;
case EQUAL_LENGTH:
res.push_back(std::hypot(X(c.a) - X(c.b), Y(c.a) - Y(c.b)) -
std::hypot(X(c.c) - X(c.d), Y(c.c) - Y(c.d)));
break;
case PARALLEL:
res.push_back((X(c.b) - X(c.a)) * (Y(c.d) - Y(c.c)) -
(Y(c.b) - Y(c.a)) * (X(c.d) - X(c.c)));
break;
case PERPENDICULAR:
res.push_back((X(c.b) - X(c.a)) * (X(c.d) - X(c.c)) +
(Y(c.b) - Y(c.a)) * (Y(c.d) - Y(c.c)));
break;
case MIDPOINT:
res.push_back(X(c.a) - 0.5 * (X(c.b) + X(c.c)));
res.push_back(Y(c.a) - 0.5 * (Y(c.b) + Y(c.c)));
break;
case SYMMETRIC: {
const double abx = X(c.b) - X(c.a), aby = Y(c.b) - Y(c.a);
const double cdx = X(c.d) - X(c.c), cdy = Y(c.d) - Y(c.c);
res.push_back(abx * cdx + aby * cdy);
const double mx = 0.5 * (X(c.a) + X(c.b));
const double my = 0.5 * (Y(c.a) + Y(c.b));
res.push_back((mx - X(c.c)) * cdy - (my - Y(c.c)) * cdx);
break;
}
case ANGLE: {
const double ux = X(c.b) - X(c.a), uy = Y(c.b) - Y(c.a);
const double wx = X(c.d) - X(c.c), wy = Y(c.d) - Y(c.c);
const double cross = ux * wy - uy * wx;
const double dot = ux * wx + uy * wy;
res.push_back(std::atan2(cross, dot) - c.k0);
break;
}
case PT_LINE_DIST: {
const double bx = X(c.b), by = Y(c.b);
const double cx = X(c.c), cy = Y(c.c);
const double L = std::hypot(cx - bx, cy - by);
const double num = (X(c.a) - bx) * (cy - by) - (Y(c.a) - by) * (cx - bx);
res.push_back((L > 1e-12 ? std::abs(num) / L : 0.0) - c.k0);
break;
}
}
}
Eigen::VectorXd r(static_cast<Eigen::Index>(res.size()));
for (size_t i = 0; i < res.size(); ++i)
r(static_cast<Eigen::Index>(i)) = res[i];
return r;
}
Eigen::MatrixXd SketchConstraints::jacobian(const std::vector<double>& v) const
{
int m = static_cast<int>(residuals(v).size());
int n = static_cast<int>(v.size());
Eigen::MatrixXd J(m, n);
const double eps = 1e-7;
std::vector<double> vp = v;
std::vector<double> vm = v;
for (int j = 0; j < n; ++j) {
vp[j] = v[j] + eps;
vm[j] = v[j] - eps;
Eigen::VectorXd rp = residuals(vp);
Eigen::VectorXd rm = residuals(vm);
vp[j] = v[j];
vm[j] = v[j];
J.col(j) = (rp - rm) / (2.0 * eps);
}
return J;
}
bool SketchConstraints::solve(int max_iter, double tol)
{
if (m_cons.empty()) return true;
double lambda = 1e-3;
Eigen::VectorXd r = residuals(m_vars);
for (int it = 0; it < max_iter; ++it) {
double rn = r.norm();
if (rn < tol) return true;
Eigen::MatrixXd J = jacobian(m_vars);
Eigen::MatrixXd A = J.transpose() * J;
Eigen::VectorXd g = J.transpose() * r;
bool stepped = false;
for (int t = 0; t < 12; ++t) {
Eigen::MatrixXd Ad = A;
for (int i = 0; i < Ad.rows(); ++i)
Ad(i, i) += lambda * (1.0 + Ad(i, i));
Eigen::VectorXd dx = Ad.ldlt().solve(-g);
std::vector<double> cand = m_vars;
for (size_t i = 0; i < cand.size(); ++i)
cand[i] += dx[static_cast<Eigen::Index>(i)];
Eigen::VectorXd rc = residuals(cand);
if (rc.norm() < rn) {
m_vars = cand;
r = rc;
lambda = std::max(lambda * 0.4, 1e-12);
stepped = true;
break;
}
lambda *= 3.0;
}
if (!stepped) break;
}
return r.norm() < tol * 100;
}
double SketchConstraints::residual_norm() const
{
return residuals(m_vars).norm();
}
} // namespace Slic3r
+68
View File
@@ -0,0 +1,68 @@
#ifndef slic3r_SketchConstraints_hpp_
#define slic3r_SketchConstraints_hpp_
#include "libslic3r/Point.hpp"
#include <vector>
#include <Eigen/Dense>
namespace Slic3r {
class SketchConstraints {
public:
int add_point(double x, double y);
void set_point(int id, double x, double y);
Vec2d get_point(int id) const;
int point_count() const;
void fix_point(int id);
void coincident(int a, int b);
void horizontal(int a, int b);
void vertical(int a, int b);
void distance(int a, int b, double d);
void lock_x(int id, double x);
void lock_y(int id, double y);
void equal_length(int a, int b, int c, int d);
void parallel(int a, int b, int c, int d);
void perpendicular(int a, int b, int c, int d);
void midpoint(int m, int a, int b);
void symmetric(int a, int b, int c, int d);
void angle(int a, int b, int c, int d, double radians);
void point_line_distance(int p, int a, int b, double dist);
bool solve(int max_iter = 200, double tol = 1e-10);
double residual_norm() const;
private:
std::vector<double> m_vars;
enum ConType : int {
FIX_POINT = 0,
COINCIDENT,
HORIZONTAL,
VERTICAL,
DISTANCE,
LOCK_X,
LOCK_Y,
EQUAL_LENGTH,
PARALLEL,
PERPENDICULAR,
MIDPOINT,
SYMMETRIC,
ANGLE,
PT_LINE_DIST
};
struct Con {
int type;
int a, b, c, d;
double k0, k1;
};
std::vector<Con> m_cons;
Eigen::VectorXd residuals(const std::vector<double>& v) const;
Eigen::MatrixXd jacobian(const std::vector<double>& v) const;
};
} // namespace Slic3r
#endif // slic3r_SketchConstraints_hpp_
File diff suppressed because it is too large Load Diff
+257
View File
@@ -0,0 +1,257 @@
#ifndef slic3r_SketchEngine_hpp_
#define slic3r_SketchEngine_hpp_
#include "TriangleMesh.hpp"
#include "libslic3r/Point.hpp"
#include "GeometryEngine.hpp"
#include <gp_Pln.hxx>
#include <gp_Ax3.hxx>
#include <TopoDS_Wire.hxx>
#include <TopoDS_Shape.hxx>
#include <TopoDS_Face.hxx>
#include <vector>
namespace Slic3r {
struct SketchSegment {
enum Type { Line, Arc, Circle, Rectangle, Polygon };
Type type{Line};
Vec2d p0{0,0}, p1{0,0};
Vec2d center{0,0};
double radius{0}, start_angle{0}, end_angle{0};
std::vector<Vec2d> points;
template<class Archive>
void serialize(Archive& ar) { ar(type, p0, p1, center, radius, start_angle, end_angle, points); }
};
struct SketchEntity {
enum class Type { Line, Arc, Circle, Point, Ellipse, EllipseArc, BSpline };
Type type{Type::Line};
Vec2d p0{0,0}; // Line: start; Arc/EllipseArc: start; Circle/Point/Ellipse: center; BSpline: first pole
Vec2d p1{0,0}; // Line: end; Arc/EllipseArc: end; (unused for Circle/Point/Ellipse); BSpline: last pole
Vec2d center{0,0}; // Arc/Circle/Ellipse(Arc) center
double radius{0}; // Circle/Arc radius; Ellipse(Arc): semi-major axis (a)
double start_angle{0}; // Arc sweep start; Ellipse(Arc): parametric start angle (radians)
double end_angle{0}; // Arc sweep end; Ellipse(Arc): parametric end angle
bool construction{false};
double rminor{0}; // Ellipse(Arc): semi-minor axis (b)
double rotation{0}; // Ellipse(Arc): major-axis angle phi (radians, about center)
std::vector<Vec2d> ctrl; // BSpline: control points (poles); p0/p1 mirror first/last pole
template<class Archive>
void serialize(Archive& ar) {
// Append-only: rminor/rotation added for Ellipse(Arc) (P2 Tier-B.1); ctrl for BSpline (B.2).
ar(type, p0, p1, center, radius, start_angle, end_angle, construction, rminor, rotation, ctrl);
}
};
struct SketchPlane {
Vec3d origin{0,0,0};
Vec3d normal{0,0,1};
Vec3d x_axis{1,0,0};
Vec3d y_axis{0,1,0};
gp_Pln to_occt() const;
static SketchPlane from_face(const TopoDS_Face& face);
static SketchPlane XY() { return {}; }
static SketchPlane XZ() { return {{0,0,0}, {0,1,0}, {1,0,0}, {0,0,1}}; }
static SketchPlane YZ() { return {{0,0,0}, {1,0,0}, {0,1,0}, {0,0,1}}; }
Vec2d project(const Vec3d& ray_origin, const Vec3d& ray_dir) const;
Vec3d to_world(const Vec2d& pt) const;
template<class Archive>
void serialize(Archive& ar) { ar(origin, normal, x_axis, y_axis); }
};
struct SketchProfile {
std::vector<Vec2d> points;
bool closed{false};
bool is_closed(double tolerance = 0.5) const;
bool try_close(double tolerance = 0.5);
void clear() { points.clear(); closed = false; }
TopoDS_Wire to_occt_wire(const SketchPlane& plane) const;
template<class Archive>
void serialize(Archive& ar) { ar(points, closed); }
};
enum class SketchConstraintType {
Fix, Coincident, Horizontal, Vertical, Distance,
LockX, LockY, EqualLength, Parallel, Perpendicular,
Concentric,
Tangent, Midpoint, Symmetric, Angle,
Radius, Diameter,
PointOnLine, // a point lies on a line (or at signed perpendicular distance `value`)
PointOnObject // a point lies on an entity edge (line -> PT_ON_LINE, circle -> PT_ON_CIRCLE)
};
// Constraint on a SketchProfile, referencing profile point indices (a,b,c,d).
// `value` carries the target for Distance/LockX/LockY (ignored otherwise).
struct SketchConstraintDef {
SketchConstraintType type{SketchConstraintType::Coincident};
int a{-1}, b{-1}, c{-1}, d{-1};
double value{0.0};
template<class Archive> void serialize(Archive& ar) { ar(type, a, b, c, d, value); }
};
// Which point of an entity a constraint reference names.
// P0 = SketchEntity::p0 (Line start / Point position)
// P1 = SketchEntity::p1 (Line end)
// Center = SketchEntity::center (Arc/Circle center)
enum class SketchPointRole { P0, P1, Center };
// Constraint on coexisting SketchEntity objects (Fase 4.2). Each reference is an
// (entity index, point role) pair. Point-form constraints
// (Fix/Coincident/Horizontal/Vertical/Distance/LockX/LockY) use refs A and B as
// individual points. Segment-form constraints (Parallel/Perpendicular/EqualLength)
// use entity indices `ea`/`eb` as whole line segments (their P0->P1); roles are
// ignored for those. `value` carries the target for Distance/LockX/LockY.
struct SketchEntityConstraintDef {
SketchConstraintType type{SketchConstraintType::Coincident};
int ea{-1}, eb{-1}; // entity indices
SketchPointRole ra{SketchPointRole::P0}; // role within ea
SketchPointRole rb{SketchPointRole::P0}; // role within eb
double value{0.0};
int ec{-1}; // third entity ref (Symmetric axis)
SketchPointRole rc{SketchPointRole::P0}; // role within ec
template<class Archive> void serialize(Archive& ar) { ar(type, ea, eb, ra, rb, value, ec, rc); }
};
// Solve a bare entity list in place against entity-form constraints. Shared by
// CadDocument::solve_sketch_feature (committed features) and the in-session GUI
// sketch tool (live solving as dimensions/constraints are added). Returns true on
// convergence; an empty constraint list is a no-op that returns true.
bool solve_sketch_entities(std::vector<SketchEntity>& entities,
const std::vector<SketchEntityConstraintDef>& constraints);
struct SketchParams {
// Extrude/Revolve
double extrude_len{10}; bool extrude_sym{false}; double extrude_taper{0};
double revolve_deg{360};
bool is_pocket{false}; // cut into selected object instead of new
// Dress-up
bool dressup_enabled{false};
DressUpType dressup_type{DressUpType::Fillet};
FaceGroup dressup_faces{FaceGroup::All};
double dressup_radius{1.0};
double dressup_chamfer_dist{1.0};
// Mesh
double linear_deflection{0.01};
template<class Archive>
void serialize(Archive& ar) {
ar(extrude_len, extrude_sym, extrude_taper, revolve_deg, is_pocket,
dressup_enabled, dressup_type, dressup_faces, dressup_radius, dressup_chamfer_dist,
linear_deflection);
}
};
class SketchEngine
{
public:
static TopoDS_Shape make_extrude(const TopoDS_Wire& wire, const SketchPlane& plane,
double length, bool symmetric = false, double taper_deg = 0.0);
// Asymmetric two-sided prism: extrude the wire's face by `up` along +normal and `down`
// along -normal, fused into one solid. up/down are non-negative magnitudes.
// Tapered (draft) extrude of a planar wire: the top profile is the base wire offset in its
// plane by length*tan(taper_deg), lofted from base to top. Falls back to a straight prism on
// any failure (self-intersecting offset / loft error). taper_deg>0 widens the top.
static TopoDS_Shape make_extrude_taper(const TopoDS_Wire& wire, const SketchPlane& plane,
double length, double taper_deg);
static TopoDS_Shape make_extrude_two_sided(const TopoDS_Wire& wire, const SketchPlane& plane,
double up, double down);
static TopoDS_Shape make_extrude_face(const TopoDS_Face& face, const SketchPlane& plane,
double length, bool symmetric = false, double taper_deg = 0.0);
// Extrude a set of imported rigid regions (Text/SVG). Each region is
// contour[0]=outer loop + contour[1..]=hole loops, in plane (u,v) mm. Builds
// one planar face-with-holes per region, extrudes it, and fuses all region
// solids into a single shape. Empty/degenerate contours are skipped.
static TopoDS_Shape make_extrude_regions(
const std::vector<std::vector<std::vector<Vec2d>>>& regions,
const SketchPlane& plane, double length, bool symmetric = false);
// Revolve a planar profile wire about an axis lying in the sketch plane and
// passing through the plane origin: axis_sel 0 = plane X axis, 1 = plane Y axis.
// A negative angle_deg sweeps the opposite direction (Flip). The profile must
// lie to one side of the axis (Onshape rule); a straddling profile self-intersects.
static TopoDS_Shape make_revolve(const TopoDS_Wire& wire, const SketchPlane& plane,
double angle_deg = 360.0, int axis_sel = 0);
// Sweep a planar profile wire along a path (spine) wire. The profile is turned
// into a face and swept with BRepOffsetAPI_MakePipe, which keeps the profile
// perpendicular to the spine along its length. The path may be open or closed;
// for a clean solid the path's first point should sit on/near the profile plane.
static TopoDS_Shape make_sweep(const TopoDS_Wire& profile, const TopoDS_Wire& path);
// Loft a solid through 2+ closed profile wires (each on its own plane), in the
// given order. ruled=true => straight (ruled) sections; false => smooth (C2).
static TopoDS_Shape make_loft(const std::vector<TopoDS_Wire>& profiles, bool ruled);
static TopoDS_Shape make_pocket(const TopoDS_Wire& wire, const SketchPlane& plane,
const TopoDS_Shape& target, double depth);
static TriangleMesh tessellate(const TopoDS_Shape& shape,
double linear_deflection = 0.01,
double angular_deflection = 0.5);
static TriangleMesh tessellate(const TopoDS_Shape& shape,
std::vector<int>& tri_face,
double linear_deflection = 0.01,
double angular_deflection = 0.5);
static TopoDS_Wire entities_to_wire(const std::vector<SketchEntity>& entities,
const SketchPlane& plane);
static std::vector<SketchEntity> mirror_entities(
const std::vector<SketchEntity>& src, const Vec2d& a, const Vec2d& b);
static std::vector<SketchEntity> offset_entities(
const std::vector<SketchEntity>& src, double d);
// Rigid-transform array. Returns the (count-1) copies for instance i=1..count-1
// (the originals in `src` are NOT included). Each copy i is `src` rigidly
// transformed by: rotate by i*angle_step about `pivot`, then translate by i*step.
// Rectangular/linear array: angle_step = 0, step = spacing*direction (pivot unused).
// Polar array: step = (0,0), angle_step = sweep/count, pivot = centre.
// Orientation-preserving, so arc/ellipse parametric angles shift by i*angle_step.
static std::vector<SketchEntity> array_entities(
const std::vector<SketchEntity>& src, int count,
const Vec2d& step, double angle_step, const Vec2d& pivot);
// General affine transform (move / rotate / scale), applied IN PLACE: returns
// the SAME entities (same count and order), each mapped by
// p -> pivot + scale * R(angle) * (p - pivot) + move
// (radii scale by |scale|; arc/ellipse parametric/rotation angles shift by
// `angle`). Unlike array_entities this mutates the subjects rather than adding
// copies. Move: angle=0, scale=1. Rotate-in-place: move=(0,0), scale=1,
// pivot=centroid. Scale: angle=0.
static std::vector<SketchEntity> transform_entities(
const std::vector<SketchEntity>& src,
const Vec2d& move, double angle, double scale, const Vec2d& pivot);
static bool fillet_lines(const SketchEntity& a, const SketchEntity& b, double r,
SketchEntity& a_out, SketchEntity& b_out, SketchEntity& arc_out);
// Symmetric chamfer between two lines meeting at a corner: trims each line back
// by setback distance `d` from the shared corner and returns the connecting
// straight segment (seg_out) in place of the corner. a_out/b_out are the trimmed
// lines; seg_out goes seg_out.p0 (on a) -> seg_out.p1 (on b). False if the lines
// are parallel or `d` overruns either line.
static bool chamfer_lines(const SketchEntity& a, const SketchEntity& b, double d,
SketchEntity& a_out, SketchEntity& b_out, SketchEntity& seg_out);
static bool trim_entity(SketchEntity& e, const std::vector<SketchEntity>& others,
const Vec2d& pick);
static bool extend_entity(SketchEntity& e, const std::vector<SketchEntity>& others,
const Vec2d& pick);
};
} // namespace Slic3r
#endif // slic3r_SketchEngine_hpp_
+145
View File
@@ -0,0 +1,145 @@
#include "SketchImport.hpp"
#include "Emboss.hpp"
#include "NSVGUtils.hpp"
#include "ExPolygon.hpp"
#include "TextConfiguration.hpp" // FontProp
#include "libslic3r.h" // SCALING_FACTOR
#include "Utils.hpp" // resources_dir
#include <algorithm>
#include <limits>
namespace Slic3r {
// Convert one ExPolygon (outer contour + CW holes) into an ImportRegion,
// mapping each integer Point to plane (u,v) mm via `to_mm`.
template<class ToMm>
static ImportRegion expoly_to_region(const ExPolygon& ex, ToMm to_mm)
{
auto contour_pts = [&](const Polygon& poly) {
std::vector<Vec2d> c;
c.reserve(poly.points.size());
for (const Point& p : poly.points)
c.push_back(to_mm(p));
return c;
};
ImportRegion region;
region.push_back(contour_pts(ex.contour));
for (const Polygon& h : ex.holes)
region.push_back(contour_pts(h));
return region;
}
// Shift all regions so their common bounding-box centre sits on the origin
// (Onshape/typical CAD insert places imported art centred on the sketch).
static void center_regions(ImportRegions& regs)
{
double lo_x = std::numeric_limits<double>::max();
double lo_y = std::numeric_limits<double>::max();
double hi_x = -std::numeric_limits<double>::max();
double hi_y = -std::numeric_limits<double>::max();
bool any = false;
for (const auto& region : regs)
for (const auto& contour : region)
for (const Vec2d& p : contour) {
lo_x = std::min(lo_x, p.x()); hi_x = std::max(hi_x, p.x());
lo_y = std::min(lo_y, p.y()); hi_y = std::max(hi_y, p.y());
any = true;
}
if (!any) return;
const Vec2d c(0.5 * (lo_x + hi_x), 0.5 * (lo_y + hi_y));
for (auto& region : regs)
for (auto& contour : region)
for (Vec2d& p : contour)
p -= c;
}
static std::string default_font_path()
{
return resources_dir() + "/fonts/HarmonyOS_Sans_SC_Regular.ttf";
}
ImportRegions text_to_regions(const std::string& utf8, double size_mm,
const std::string& font_path)
{
if (utf8.empty() || size_mm <= 0.0)
return {};
const std::string path = font_path.empty() ? default_font_path() : font_path;
std::unique_ptr<Emboss::FontFile> ff = Emboss::create_font_file(path.c_str());
if (!ff)
return {};
Emboss::FontFileWithCache fwc(std::move(ff));
if (!fwc.has_value())
return {};
FontProp prop(static_cast<float>(size_mm)); // per_glyph=false
HealedExPolygons healed = Emboss::text2shapes(fwc, utf8.c_str(), prop);
if (healed.expolygons.empty())
return {};
// Shape points are integers scaled by 1/SHAPE_SCALE in font units;
// get_text_shape_scale collapses (size_in_mm / unit_per_em) * SHAPE_SCALE
// into a single mm-per-shape-unit factor. FreeType y is up already.
const double s = Emboss::get_text_shape_scale(prop, *fwc.font_file);
auto to_mm = [s](const Point& p) { return Vec2d(p.x() * s, p.y() * s); };
ImportRegions regs;
regs.reserve(healed.expolygons.size());
for (const ExPolygon& ex : healed.expolygons)
regs.push_back(expoly_to_region(ex, to_mm));
center_regions(regs);
return regs;
}
ImportRegions svg_to_regions(const std::string& svg_path, double scale)
{
if (svg_path.empty() || scale <= 0.0)
return {};
NSVGimage_ptr image = nsvgParseFromFile(svg_path, "mm", 96.0f);
if (!image)
return {};
// A filled shape that also carries a stroke would import the stroke as a
// thick outline band wrapped around the fill (the reported "too large line
// width"). For CAD import the fill silhouette is what's wanted, so drop the
// stroke on any shape that has a fill; stroke-only line art is kept.
for (NSVGshape* s = image->shapes; s != nullptr; s = s->next)
if (s->fill.type != NSVG_PAINT_NONE)
s->stroke.type = NSVG_PAINT_NONE;
// tesselation tolerance is in image (mm) scale; 0.3 mm keeps curves smooth
// without exploding the contour count. is_y_negative (default) flips SVG's
// y-down to the sketch's y-up.
NSVGLineParams param(0.3);
ExPolygonsWithIds ids = create_shape_with_ids(*image, param);
// NSVG points are integers scaled by 1/SCALING_FACTOR (param.scale default):
// mm = point * SCALING_FACTOR, then the user scale factor.
const double s = SCALING_FACTOR * scale;
auto to_mm = [s](const Point& p) { return Vec2d(p.x() * s, p.y() * s); };
ImportRegions regs;
for (const ExPolygonsWithId& w : ids)
for (const ExPolygon& ex : w.expoly)
regs.push_back(expoly_to_region(ex, to_mm));
center_regions(regs);
return regs;
}
ImportRegions transform_regions(const ImportRegions& src, const Vec2d& offset,
double scale_x, double scale_y)
{
ImportRegions out = src;
for (auto& region : out)
for (auto& contour : region)
for (Vec2d& p : contour)
p = Vec2d(p.x() * scale_x + offset.x(), p.y() * scale_y + offset.y());
return out;
}
} // namespace Slic3r
+37
View File
@@ -0,0 +1,37 @@
#ifndef slic3r_SketchImport_hpp_
#define slic3r_SketchImport_hpp_
#include "Point.hpp" // Vec2d
#include <string>
#include <vector>
namespace Slic3r {
// A rigid imported region: contour[0] = outer loop, contour[1..] = holes;
// points in plane (u,v) millimetres. The nested vector type matches
// CadFeature::imported_regions exactly, so results assign directly.
using ImportRegion = std::vector<std::vector<Vec2d>>;
using ImportRegions = std::vector<ImportRegion>;
// Vectorize UTF-8 text into filled regions (mm), centred on the origin.
// `size_mm` is the cap/line height. `font_path` empty -> a bundled default
// font (resources/fonts). Returns an empty vector on any failure.
ImportRegions text_to_regions(const std::string& utf8, double size_mm,
const std::string& font_path = std::string());
// Parse an SVG file's filled paths into regions (mm), centred on the origin.
// `scale` multiplies the authored size (1.0 = as authored). Returns an empty
// vector on any failure.
ImportRegions svg_to_regions(const std::string& svg_path, double scale = 1.0);
// Apply an axis-aligned placement transform to regions:
// p -> ( p.x * scale_x + offset.x, p.y * scale_y + offset.y )
// Used to move / enlarge / stretch imported art non-destructively (the
// feature keeps the centred source regions + this transform).
ImportRegions transform_regions(const ImportRegions& src, const Vec2d& offset,
double scale_x, double scale_y);
} // namespace Slic3r
#endif // slic3r_SketchImport_hpp_
+120
View File
@@ -0,0 +1,120 @@
#include "SketchInference.hpp"
#include <cmath>
namespace Slic3r {
// Candidate target collected during the scan; we keep the closest within each
// priority tier and resolve ties by tier then distance.
namespace {
struct Cand {
InferenceSnap::Kind kind{InferenceSnap::Kind::None};
int entity{-1};
SketchPointRole role{SketchPointRole::P0};
Vec2d point{0, 0};
double dist{0.0};
};
// Lower number = higher priority.
int tier(InferenceSnap::Kind k)
{
switch (k) {
case InferenceSnap::Kind::Endpoint: return 0;
case InferenceSnap::Kind::Center: return 1;
case InferenceSnap::Kind::Origin: return 2;
case InferenceSnap::Kind::Midpoint: return 3;
case InferenceSnap::Kind::OnEdge: return 4;
default: return 9;
}
}
} // namespace
InferenceSnap infer_point_snap(const std::vector<SketchEntity>& entities,
const Vec2d& query, double tol,
bool include_origin)
{
Cand best;
best.kind = InferenceSnap::Kind::None;
best.point = query;
auto offer = [&](InferenceSnap::Kind k, int ent, SketchPointRole r, const Vec2d& q) {
const double d = (q - query).norm();
if (d > tol) return;
const bool better = (best.kind == InferenceSnap::Kind::None) ||
(tier(k) < tier(best.kind)) ||
(tier(k) == tier(best.kind) && d < best.dist);
if (better) { best.kind = k; best.entity = ent; best.role = r; best.point = q; best.dist = d; }
};
for (size_t i = 0; i < entities.size(); ++i) {
const SketchEntity& e = entities[i];
const int ei = int(i);
switch (e.type) {
case SketchEntity::Type::Line: {
offer(InferenceSnap::Kind::Endpoint, ei, SketchPointRole::P0, e.p0);
offer(InferenceSnap::Kind::Endpoint, ei, SketchPointRole::P1, e.p1);
offer(InferenceSnap::Kind::Midpoint, ei, SketchPointRole::P0, 0.5 * (e.p0 + e.p1));
// Projection onto the segment interior (PointOnObject candidate).
const Vec2d d = e.p1 - e.p0;
const double L2 = d.squaredNorm();
if (L2 > 1e-12) {
double t = (query - e.p0).dot(d) / L2;
if (t > 0.02 && t < 0.98)
offer(InferenceSnap::Kind::OnEdge, ei, SketchPointRole::P0, e.p0 + t * d);
}
break;
}
case SketchEntity::Type::Arc:
offer(InferenceSnap::Kind::Endpoint, ei, SketchPointRole::P0, e.p0);
offer(InferenceSnap::Kind::Endpoint, ei, SketchPointRole::P1, e.p1);
offer(InferenceSnap::Kind::Center, ei, SketchPointRole::Center, e.center);
break;
case SketchEntity::Type::Circle: {
offer(InferenceSnap::Kind::Center, ei, SketchPointRole::Center, e.center);
// Nearest point on the circle rim (PointOnObject candidate).
const Vec2d v = query - e.center;
const double n = v.norm();
if (n > 1e-9 && e.radius > 1e-9)
offer(InferenceSnap::Kind::OnEdge, ei, SketchPointRole::Center,
e.center + v * (e.radius / n));
break;
}
case SketchEntity::Type::Point:
offer(InferenceSnap::Kind::Endpoint, ei, SketchPointRole::P0, e.p0);
break;
case SketchEntity::Type::EllipseArc:
offer(InferenceSnap::Kind::Endpoint, ei, SketchPointRole::P0, e.p0);
offer(InferenceSnap::Kind::Endpoint, ei, SketchPointRole::P1, e.p1);
offer(InferenceSnap::Kind::Center, ei, SketchPointRole::Center, e.center);
break;
case SketchEntity::Type::Ellipse:
offer(InferenceSnap::Kind::Center, ei, SketchPointRole::Center, e.center);
break;
case SketchEntity::Type::BSpline:
// Endpoints (first/last pole) snap for loop closure.
offer(InferenceSnap::Kind::Endpoint, ei, SketchPointRole::P0, e.p0);
offer(InferenceSnap::Kind::Endpoint, ei, SketchPointRole::P1, e.p1);
break;
}
}
if (include_origin)
offer(InferenceSnap::Kind::Origin, -1, SketchPointRole::P0, Vec2d(0, 0));
InferenceSnap r;
r.kind = best.kind; r.entity = best.entity; r.role = best.role; r.point = best.point;
return r;
}
std::optional<SketchConstraintType>
infer_axis_constraint(const Vec2d& anchor, const Vec2d& tip, double ang_tol_rad)
{
const Vec2d d = tip - anchor;
if (d.squaredNorm() < 1e-12) return std::nullopt;
const double ang = std::atan2(std::abs(d.y()), std::abs(d.x())); // 0=horizontal, pi/2=vertical
if (ang <= ang_tol_rad) return SketchConstraintType::Horizontal;
if (ang >= M_PI / 2.0 - ang_tol_rad) return SketchConstraintType::Vertical;
return std::nullopt;
}
} // namespace Slic3r
+44
View File
@@ -0,0 +1,44 @@
#ifndef slic3r_SketchInference_hpp_
#define slic3r_SketchInference_hpp_
#include "SketchEngine.hpp"
#include <vector>
#include <optional>
#include <cmath>
namespace Slic3r {
// Result of snapping a free cursor point onto the most relevant inference target
// among the committed sketch entities and the sketch origin. This is the backbone
// that lets geometry self-constrain as it is drawn: the GUI records the returned
// target at click time and, once the entity it belongs to exists, emits the
// matching constraint (Coincident onto an endpoint/centre, Fix onto the origin,
// PointOnObject onto an edge) so the relation survives a re-solve.
struct InferenceSnap {
enum class Kind { None, Endpoint, Center, Midpoint, OnEdge, Origin };
Kind kind{Kind::None};
int entity{-1}; // hit entity index (-1 = origin/none)
SketchPointRole role{SketchPointRole::P0}; // which point of `entity` (Endpoint/Center)
Vec2d point{0, 0}; // snapped coordinate (== query when None)
bool snapped() const { return kind != Kind::None; }
};
// Snap `query` onto the best inference target within `tol` plane units. Priority,
// highest first: Endpoint, Center, Origin, Midpoint, OnEdge. Construction entities
// participate (you constrain to them too). Returns {None, query} when nothing is in
// range. Pure — no GUI / GL dependencies, so it is unit-testable in libslic3r.
InferenceSnap infer_point_snap(const std::vector<SketchEntity>& entities,
const Vec2d& query, double tol,
bool include_origin = true);
// Relational inference for an in-progress segment anchor->tip. If its direction is
// within `ang_tol_rad` of an axis, returns Horizontal or Vertical (the constraint to
// auto-emit on the committed segment); std::nullopt otherwise. Degenerate (near-zero
// length) segments return nullopt.
std::optional<SketchConstraintType>
infer_axis_constraint(const Vec2d& anchor, const Vec2d& tip, double ang_tol_rad = 3.0 * M_PI / 180.0);
} // namespace Slic3r
#endif // slic3r_SketchInference_hpp_
+361
View File
@@ -0,0 +1,361 @@
#include "SketchSolver.hpp"
#include <slvs.h>
#include <cmath>
#include <cstring>
#include <unordered_map>
namespace Slic3r {
using CT = SketchConstraintType;
using Role = SketchPointRole;
namespace {
constexpr Slvs_hGroup G_FIXED = 1; // workplane / reference: held constant
constexpr Slvs_hGroup G_SK = 2; // sketch geometry: the group we solve
// Per-entity slvs handles. p0/p1/center are point2d entity handles; prim is the
// line/arc/circle entity; rparam is the circle radius param.
struct Slots {
Slvs_hEntity prim{0}, p0{0}, p1{0}, center{0};
Slvs_hParam rparam{0};
std::vector<Slvs_hEntity> pts; // BSpline control points (point2d handles)
};
struct Build {
std::vector<Slvs_Param> params;
std::vector<Slvs_Entity> ents;
std::vector<Slvs_Constraint> cons;
Slvs_hParam ph{0};
Slvs_hEntity eh{0};
Slvs_hConstraint ch{0};
Slvs_hEntity wp{0}, normal{0};
Slvs_hParam P(Slvs_hGroup g, double v) { params.push_back(Slvs_MakeParam(++ph, g, v)); return ph; }
Slvs_hEntity E(Slvs_Entity e) { ents.push_back(e); return e.h; }
Slvs_hEntity pt2d(Slvs_hGroup g, double u, double v)
{ return E(Slvs_MakePoint2d(++eh, g, wp, P(g, u), P(g, v))); }
// Generic constraint (entityC unused by Slvs_MakeConstraint — set it manually below).
void C(int type, double val, Slvs_hEntity ptA, Slvs_hEntity ptB,
Slvs_hEntity eA, Slvs_hEntity eB, Slvs_hEntity eC = 0, int other = 0)
{
Slvs_Constraint c = Slvs_MakeConstraint(++ch, G_SK, type, wp, val, ptA, ptB, eA, eB);
c.entityC = eC;
c.other = other;
cons.push_back(c);
}
};
inline int role_idx(Role r) { return int(r); }
} // namespace
static SketchSolveResult solve_impl(std::vector<SketchEntity>& entities,
const std::vector<SketchEntityConstraintDef>& constraints,
int dragged_ei, Role dragged_role)
{
SketchSolveResult out;
if (constraints.empty()) { out.ok = true; out.dof = -1; return out; }
Build b;
// ---- Fixed 2D XY workplane (origin at 0,0,0; identity normal) -------------------
Slvs_hEntity origin = b.E(Slvs_MakePoint3d(++b.eh, G_FIXED,
b.P(G_FIXED, 0.0), b.P(G_FIXED, 0.0), b.P(G_FIXED, 0.0)));
double qw, qx, qy, qz;
Slvs_MakeQuaternion(1, 0, 0, 0, 1, 0, &qw, &qx, &qy, &qz);
b.normal = b.E(Slvs_MakeNormal3d(++b.eh, G_FIXED,
b.P(G_FIXED, qw), b.P(G_FIXED, qx), b.P(G_FIXED, qy), b.P(G_FIXED, qz)));
b.wp = b.E(Slvs_MakeWorkplane(++b.eh, G_FIXED, origin, b.normal));
// ---- Entities -------------------------------------------------------------------
std::vector<Slots> slot(entities.size());
for (size_t i = 0; i < entities.size(); ++i) {
const SketchEntity& e = entities[i];
Slots s;
switch (e.type) {
case SketchEntity::Type::Line:
s.p0 = b.pt2d(G_SK, e.p0.x(), e.p0.y());
s.p1 = b.pt2d(G_SK, e.p1.x(), e.p1.y());
s.prim = b.E(Slvs_MakeLineSegment(++b.eh, G_SK, b.wp, s.p0, s.p1));
break;
case SketchEntity::Type::Point:
s.p0 = b.pt2d(G_SK, e.p0.x(), e.p0.y());
break;
case SketchEntity::Type::Circle: {
s.center = b.pt2d(G_SK, e.center.x(), e.center.y());
s.p0 = s.center; // p0 mirrors centre for circles
s.rparam = b.P(G_SK, e.radius > 1e-9 ? e.radius : 1.0);
Slvs_hEntity dist = b.E(Slvs_MakeDistance(++b.eh, G_SK, b.wp, s.rparam));
s.prim = b.E(Slvs_MakeCircle(++b.eh, G_SK, b.wp, s.center, b.normal, dist));
break;
}
case SketchEntity::Type::Arc:
s.center = b.pt2d(G_SK, e.center.x(), e.center.y());
s.p0 = b.pt2d(G_SK, e.p0.x(), e.p0.y()); // start
s.p1 = b.pt2d(G_SK, e.p1.x(), e.p1.y()); // end
s.prim = b.E(Slvs_MakeArcOfCircle(++b.eh, G_SK, b.wp, b.normal, s.center, s.p0, s.p1));
break;
// libslvs has no conic entity (scope note): register the ellipse's defining
// points only (center + arc endpoints) so center/endpoint constraints solve;
// the a/b/phi shape params pass through unsolved.
case SketchEntity::Type::Ellipse:
s.center = b.pt2d(G_SK, e.center.x(), e.center.y());
s.p0 = s.center; // p0 mirrors centre (circle convention)
break;
case SketchEntity::Type::EllipseArc:
s.center = b.pt2d(G_SK, e.center.x(), e.center.y());
s.p0 = b.pt2d(G_SK, e.p0.x(), e.p0.y()); // start
s.p1 = b.pt2d(G_SK, e.p1.x(), e.p1.y()); // end
break;
// No native slvs curve for an arbitrary-degree spline: register the control
// poles as point2d so endpoints (and any pole-targeted constraint) solve. The
// OCCT curve is rebuilt from the solved poles. p0/p1 mirror first/last pole so
// Coincident at the spline ends closes loops just like a Line.
case SketchEntity::Type::BSpline:
s.pts.reserve(e.ctrl.size());
for (const Vec2d& cp : e.ctrl)
s.pts.push_back(b.pt2d(G_SK, cp.x(), cp.y()));
if (!s.pts.empty()) { s.p0 = s.pts.front(); s.p1 = s.pts.back(); }
break;
}
slot[i] = s;
}
auto valid = [&](int ei) { return ei >= 0 && ei < int(entities.size()); };
auto ptOf = [&](int ei, Role r) -> Slvs_hEntity {
if (!valid(ei)) return 0;
const Slots& s = slot[ei];
switch (r) {
case Role::P0: return s.p0;
case Role::P1: return s.p1;
case Role::Center: return s.center ? s.center : s.p0;
}
return 0;
};
auto primOf = [&](int ei) -> Slvs_hEntity { return valid(ei) ? slot[ei].prim : 0; };
auto coordOf = [&](int ei, Role r) -> Vec2d {
if (!valid(ei)) return Vec2d(0, 0);
const SketchEntity& e = entities[ei];
switch (r) { case Role::P0: return e.p0; case Role::P1: return e.p1; case Role::Center: return e.center; }
return e.p0;
};
// A fixed reference point at (x,y) — used to pin coordinates (Fix / LockX / LockY).
auto fixedRef = [&](double x, double y) -> Slvs_hEntity { return b.pt2d(G_FIXED, x, y); };
// ---- Constraints ----------------------------------------------------------------
for (const auto& c : constraints) {
// Robustness: never feed libslvs a null handle. A constraint that references an
// entity which produced no solver primitive (Point/Ellipse/EllipseArc/BSpline get
// no `prim`) or no point for the requested role would make Slvs FindById abort the
// whole process. Skip such a constraint instead of crashing.
bool ref_ok = true;
switch (c.type) {
case CT::Coincident: case CT::Horizontal: case CT::Vertical: case CT::Distance:
ref_ok = ptOf(c.ea, c.ra) && ptOf(c.eb, c.rb); break;
case CT::Concentric:
ref_ok = ptOf(c.ea, Role::Center) && ptOf(c.eb, Role::Center); break;
case CT::Fix: case CT::LockX: case CT::LockY:
ref_ok = ptOf(c.ea, c.ra) != 0; break;
case CT::EqualLength: case CT::Parallel: case CT::Perpendicular:
case CT::Angle: case CT::Tangent:
ref_ok = primOf(c.ea) && primOf(c.eb); break;
case CT::Radius: case CT::Diameter:
ref_ok = primOf(c.ea) != 0; break;
case CT::Midpoint:
ref_ok = ptOf(c.ea, c.ra) && primOf(c.eb); break;
case CT::Symmetric:
ref_ok = ptOf(c.ea, c.ra) && ptOf(c.eb, c.rb) && primOf(c.ec); break;
case CT::PointOnLine: case CT::PointOnObject:
ref_ok = ptOf(c.ea, c.ra) && primOf(c.eb); break;
}
if (!ref_ok) continue;
switch (c.type) {
case CT::Coincident:
b.C(SLVS_C_POINTS_COINCIDENT, 0, ptOf(c.ea, c.ra), ptOf(c.eb, c.rb), 0, 0);
break;
case CT::Concentric:
b.C(SLVS_C_POINTS_COINCIDENT, 0, ptOf(c.ea, Role::Center), ptOf(c.eb, Role::Center), 0, 0);
break;
case CT::Horizontal:
b.C(SLVS_C_HORIZONTAL, 0, ptOf(c.ea, c.ra), ptOf(c.eb, c.rb), 0, 0);
break;
case CT::Vertical:
b.C(SLVS_C_VERTICAL, 0, ptOf(c.ea, c.ra), ptOf(c.eb, c.rb), 0, 0);
break;
case CT::Distance:
b.C(SLVS_C_PT_PT_DISTANCE, c.value, ptOf(c.ea, c.ra), ptOf(c.eb, c.rb), 0, 0);
break;
case CT::Fix: {
const Vec2d p = coordOf(c.ea, c.ra);
b.C(SLVS_C_POINTS_COINCIDENT, 0, ptOf(c.ea, c.ra), fixedRef(p.x(), p.y()), 0, 0);
break;
}
case CT::LockX: {
const Vec2d p = coordOf(c.ea, c.ra);
b.C(SLVS_C_VERTICAL, 0, ptOf(c.ea, c.ra), fixedRef(c.value, p.y()), 0, 0);
break;
}
case CT::LockY: {
const Vec2d p = coordOf(c.ea, c.ra);
b.C(SLVS_C_HORIZONTAL, 0, ptOf(c.ea, c.ra), fixedRef(p.x(), c.value), 0, 0);
break;
}
case CT::EqualLength:
b.C(SLVS_C_EQUAL_LENGTH_LINES, 0, 0, 0, primOf(c.ea), primOf(c.eb));
break;
case CT::Parallel:
b.C(SLVS_C_PARALLEL, 0, 0, 0, primOf(c.ea), primOf(c.eb));
break;
case CT::Perpendicular:
b.C(SLVS_C_PERPENDICULAR, 0, 0, 0, primOf(c.ea), primOf(c.eb));
break;
case CT::Midpoint:
b.C(SLVS_C_AT_MIDPOINT, 0, ptOf(c.ea, c.ra), 0, primOf(c.eb), 0);
break;
case CT::Symmetric:
// ptA, ptB symmetric about the axis line (ec).
b.C(SLVS_C_SYMMETRIC_LINE, 0, ptOf(c.ea, c.ra), ptOf(c.eb, c.rb), primOf(c.ec), 0);
break;
case CT::Angle:
// model stores radians; slvs angle is in degrees.
b.C(SLVS_C_ANGLE, c.value * 180.0 / M_PI, 0, 0, primOf(c.ea), primOf(c.eb));
break;
case CT::Radius:
b.C(SLVS_C_DIAMETER, 2.0 * c.value, 0, 0, primOf(c.ea), 0);
break;
case CT::Diameter:
b.C(SLVS_C_DIAMETER, c.value, 0, 0, primOf(c.ea), 0);
break;
case CT::Tangent: {
const bool aCurve = valid(c.ea) && entities[c.ea].type != SketchEntity::Type::Line;
const bool bCurve = valid(c.eb) && entities[c.eb].type != SketchEntity::Type::Line;
if (aCurve && bCurve)
b.C(SLVS_C_CURVE_CURVE_TANGENT, 0, 0, 0, primOf(c.ea), primOf(c.eb));
else {
const Slvs_hEntity arc = aCurve ? primOf(c.ea) : primOf(c.eb);
const Slvs_hEntity line = aCurve ? primOf(c.eb) : primOf(c.ea);
b.C(SLVS_C_ARC_LINE_TANGENT, 0, 0, 0, arc, line);
}
break;
}
case CT::PointOnLine:
if (std::abs(c.value) < 1e-9)
b.C(SLVS_C_PT_ON_LINE, 0, ptOf(c.ea, c.ra), 0, primOf(c.eb), 0);
else
b.C(SLVS_C_PT_LINE_DISTANCE, std::abs(c.value), ptOf(c.ea, c.ra), 0, primOf(c.eb), 0);
break;
case CT::PointOnObject:
// Point (ea,ra) lies on entity edge eb: a circle rim -> PT_ON_CIRCLE,
// otherwise the segment line -> PT_ON_LINE.
if (valid(c.eb) && entities[c.eb].type == SketchEntity::Type::Circle)
b.C(SLVS_C_PT_ON_CIRCLE, 0, ptOf(c.ea, c.ra), 0, primOf(c.eb), 0);
else
b.C(SLVS_C_PT_ON_LINE, 0, ptOf(c.ea, c.ra), 0, primOf(c.eb), 0);
break;
}
}
// ---- Solve ----------------------------------------------------------------------
Slvs_System sys;
std::memset(&sys, 0, sizeof(sys));
sys.param = b.params.data(); sys.params = int(b.params.size());
sys.entity = b.ents.data(); sys.entities = int(b.ents.size());
sys.constraint = b.cons.data(); sys.constraints = int(b.cons.size());
std::vector<Slvs_hConstraint> failed(b.cons.size() + 1, 0);
sys.failed = failed.data();
sys.faileds = int(failed.size());
sys.calculateFaileds = 1;
// Drag pin: feed the dragged point's two params into sys.dragged[] so the solver
// favours keeping that point at the cursor and re-solves the rest around it.
if (dragged_ei >= 0) {
const Slvs_hEntity h = ptOf(dragged_ei, dragged_role);
for (const Slvs_Entity& en : b.ents)
if (en.h == h) { sys.dragged[0] = en.param[0]; sys.dragged[1] = en.param[1]; break; }
}
Slvs_Solve(&sys, G_SK);
out.result = sys.result;
out.dof = sys.dof;
out.ok = (sys.result == SLVS_RESULT_OKAY);
// Map solved param handles -> values, then read points back.
std::unordered_map<Slvs_hParam, double> pv;
pv.reserve(sys.params * 2);
for (int i = 0; i < sys.params; ++i) pv[sys.param[i].h] = sys.param[i].val;
std::unordered_map<Slvs_hEntity, const Slvs_Entity*> byH;
byH.reserve(sys.entities * 2);
for (int i = 0; i < sys.entities; ++i) byH[sys.entity[i].h] = &sys.entity[i];
auto coord = [&](Slvs_hEntity h) -> Vec2d {
auto it = byH.find(h);
if (it == byH.end()) return Vec2d(0, 0);
return Vec2d(pv[it->second->param[0]], pv[it->second->param[1]]);
};
// Map failed constraint handles back to indices into `constraints`.
if (!out.ok && sys.faileds > 0) {
std::unordered_map<Slvs_hConstraint, int> chToIdx;
// constraint handles were assigned in order starting after the fixed group; the
// i-th sketch constraint in b.cons has handle = its position. Rebuild by scanning.
for (size_t k = 0; k < b.cons.size(); ++k) chToIdx[b.cons[k].h] = int(k);
for (int i = 0; i < sys.faileds; ++i) {
auto it = chToIdx.find(failed[i]);
if (it != chToIdx.end() && it->second < int(constraints.size()))
out.bad.push_back(it->second);
}
}
// ---- Read solved geometry back --------------------------------------------------
for (size_t i = 0; i < entities.size(); ++i) {
SketchEntity& e = entities[i];
const Slots& s = slot[i];
if (s.p0) e.p0 = coord(s.p0);
if (s.p1) e.p1 = coord(s.p1);
if (s.center) e.center = coord(s.center);
if (e.type == SketchEntity::Type::BSpline) {
for (size_t k = 0; k < s.pts.size() && k < e.ctrl.size(); ++k)
e.ctrl[k] = coord(s.pts[k]);
if (!e.ctrl.empty()) { e.p0 = e.ctrl.front(); e.p1 = e.ctrl.back(); }
} else if (e.type == SketchEntity::Type::Circle) {
if (s.rparam) { auto it = pv.find(s.rparam); if (it != pv.end()) e.radius = it->second; }
e.p0 = e.center;
} else if (e.type == SketchEntity::Type::Arc && s.center) {
// Reflow arc angles from solved centre + endpoints, preserving sweep sign.
const double old_sweep = e.end_angle - e.start_angle;
const double ns = std::atan2(e.p0.y() - e.center.y(), e.p0.x() - e.center.x());
const double ne = std::atan2(e.p1.y() - e.center.y(), e.p1.x() - e.center.x());
double sweep = ne - ns;
const double TWO_PI = 2.0 * M_PI;
while (sweep <= -TWO_PI) sweep += TWO_PI;
while (sweep >= TWO_PI) sweep -= TWO_PI;
if (old_sweep >= 0.0 && sweep < 0.0) sweep += TWO_PI;
if (old_sweep < 0.0 && sweep > 0.0) sweep -= TWO_PI;
e.start_angle = ns;
e.end_angle = ns + sweep;
e.radius = 0.5 * ((e.p0 - e.center).norm() + (e.p1 - e.center).norm());
}
}
return out;
}
SketchSolveResult sketch_solve(std::vector<SketchEntity>& entities,
const std::vector<SketchEntityConstraintDef>& constraints)
{
return solve_impl(entities, constraints, -1, Role::P0);
}
SketchSolveResult sketch_solve_drag(std::vector<SketchEntity>& entities,
const std::vector<SketchEntityConstraintDef>& constraints,
int dragged_ei, SketchPointRole dragged_role)
{
return solve_impl(entities, constraints, dragged_ei, dragged_role);
}
} // namespace Slic3r
+37
View File
@@ -0,0 +1,37 @@
#ifndef slic3r_SketchSolver_hpp_
#define slic3r_SketchSolver_hpp_
// Bridge from SnapOrca's SketchEntity / SketchEntityConstraintDef model onto the
// vendored SolveSpace constraint solver (src/libslic3r/slvs, libslvs). Replaces the
// hand-rolled SketchConstraints: full constraint set, real DoF counting, and
// over-constrained (bad-constraint) detection. Solves on a fixed 2D XY workplane.
#include "SketchEngine.hpp"
#include <vector>
namespace Slic3r {
struct SketchSolveResult {
bool ok{false}; // solver converged & consistent
int dof{-1}; // remaining degrees of freedom (>0 under-constrained)
int result{0}; // raw SLVS_RESULT_* code
std::vector<int> bad; // indices (into `constraints`) of conflicting constraints
};
// Solve `constraints` over `entities` in place (writes solved coordinates back into the
// entities; arc angles are reflowed preserving sweep direction). No-op success when
// `constraints` is empty.
SketchSolveResult sketch_solve(std::vector<SketchEntity>& entities,
const std::vector<SketchEntityConstraintDef>& constraints);
// Drag-aware solve: pins the (dragged_ei, dragged_role) point's parameters via the
// solver's `dragged[]` priority list so the solver keeps that point where the cursor
// placed it (caller must have moved it first) and moves the OTHER free geometry to
// re-satisfy the constraints. dragged_ei < 0 behaves identically to sketch_solve.
SketchSolveResult sketch_solve_drag(std::vector<SketchEntity>& entities,
const std::vector<SketchEntityConstraintDef>& constraints,
int dragged_ei, SketchPointRole dragged_role);
} // namespace Slic3r
#endif
+95
View File
@@ -0,0 +1,95 @@
#include "ThreadStandards.hpp"
namespace Slic3r {
// Imperial helpers: convert nominal inch diameter / threads-per-inch to mm.
static constexpr double IN = 25.4;
static inline double tpi_pitch(double tpi) { return IN / tpi; }
const std::vector<ThreadSpec>& thread_standards()
{
using S = ThreadSpec::Series;
static const std::vector<ThreadSpec> table = {
// --- ISO metric, coarse pitch (ISO 261 preferred series) ---
{"M1", 1.0, 0.25, S::MetricCoarse},
{"M1.2", 1.2, 0.25, S::MetricCoarse},
{"M1.6", 1.6, 0.35, S::MetricCoarse},
{"M2", 2.0, 0.40, S::MetricCoarse},
{"M2.5", 2.5, 0.45, S::MetricCoarse},
{"M3", 3.0, 0.50, S::MetricCoarse},
{"M4", 4.0, 0.70, S::MetricCoarse},
{"M5", 5.0, 0.80, S::MetricCoarse},
{"M6", 6.0, 1.00, S::MetricCoarse},
{"M8", 8.0, 1.25, S::MetricCoarse},
{"M10", 10.0, 1.50, S::MetricCoarse},
{"M12", 12.0, 1.75, S::MetricCoarse},
{"M14", 14.0, 2.00, S::MetricCoarse},
{"M16", 16.0, 2.00, S::MetricCoarse},
{"M20", 20.0, 2.50, S::MetricCoarse},
{"M24", 24.0, 3.00, S::MetricCoarse},
{"M30", 30.0, 3.50, S::MetricCoarse},
{"M36", 36.0, 4.00, S::MetricCoarse},
{"M42", 42.0, 4.50, S::MetricCoarse},
{"M48", 48.0, 5.00, S::MetricCoarse},
{"M56", 56.0, 5.50, S::MetricCoarse},
{"M64", 64.0, 6.00, S::MetricCoarse},
// --- ISO metric, common fine pitches (ISO 261 fine series) ---
{"M8x1", 8.0, 1.00, S::MetricFine},
{"M10x1.25", 10.0, 1.25, S::MetricFine},
{"M10x1", 10.0, 1.00, S::MetricFine},
{"M12x1.5", 12.0, 1.50, S::MetricFine},
{"M12x1.25", 12.0, 1.25, S::MetricFine},
{"M16x1.5", 16.0, 1.50, S::MetricFine},
{"M20x1.5", 20.0, 1.50, S::MetricFine},
{"M24x2", 24.0, 2.00, S::MetricFine},
// --- Unified National Coarse (UTS / ASME B1.1) ---
{"#1-64 UNC", 0.073 * IN, tpi_pitch(64), S::UNC},
{"#2-56 UNC", 0.086 * IN, tpi_pitch(56), S::UNC},
{"#3-48 UNC", 0.099 * IN, tpi_pitch(48), S::UNC},
{"#4-40 UNC", 0.112 * IN, tpi_pitch(40), S::UNC},
{"#5-40 UNC", 0.125 * IN, tpi_pitch(40), S::UNC},
{"#6-32 UNC", 0.138 * IN, tpi_pitch(32), S::UNC},
{"#8-32 UNC", 0.164 * IN, tpi_pitch(32), S::UNC},
{"#10-24 UNC", 0.190 * IN, tpi_pitch(24), S::UNC},
{"#12-24 UNC", 0.216 * IN, tpi_pitch(24), S::UNC},
{"1/4-20 UNC", 0.250 * IN, tpi_pitch(20), S::UNC},
{"5/16-18 UNC", 0.3125 * IN, tpi_pitch(18), S::UNC},
{"3/8-16 UNC", 0.375 * IN, tpi_pitch(16), S::UNC},
{"7/16-14 UNC", 0.4375 * IN, tpi_pitch(14), S::UNC},
{"1/2-13 UNC", 0.500 * IN, tpi_pitch(13), S::UNC},
{"9/16-12 UNC", 0.5625 * IN, tpi_pitch(12), S::UNC},
{"5/8-11 UNC", 0.625 * IN, tpi_pitch(11), S::UNC},
{"3/4-10 UNC", 0.750 * IN, tpi_pitch(10), S::UNC},
{"7/8-9 UNC", 0.875 * IN, tpi_pitch(9), S::UNC},
{"1-8 UNC", 1.000 * IN, tpi_pitch(8), S::UNC},
// --- Unified National Fine (UTS / ASME B1.1) ---
{"#2-64 UNF", 0.086 * IN, tpi_pitch(64), S::UNF},
{"#4-48 UNF", 0.112 * IN, tpi_pitch(48), S::UNF},
{"#6-40 UNF", 0.138 * IN, tpi_pitch(40), S::UNF},
{"#8-36 UNF", 0.164 * IN, tpi_pitch(36), S::UNF},
{"#10-32 UNF", 0.190 * IN, tpi_pitch(32), S::UNF},
{"1/4-28 UNF", 0.250 * IN, tpi_pitch(28), S::UNF},
{"5/16-24 UNF", 0.3125 * IN, tpi_pitch(24), S::UNF},
{"3/8-24 UNF", 0.375 * IN, tpi_pitch(24), S::UNF},
{"7/16-20 UNF", 0.4375 * IN, tpi_pitch(20), S::UNF},
{"1/2-20 UNF", 0.500 * IN, tpi_pitch(20), S::UNF},
{"9/16-18 UNF", 0.5625 * IN, tpi_pitch(18), S::UNF},
{"5/8-18 UNF", 0.625 * IN, tpi_pitch(18), S::UNF},
{"3/4-16 UNF", 0.750 * IN, tpi_pitch(16), S::UNF},
{"1-12 UNF", 1.000 * IN, tpi_pitch(12), S::UNF},
};
return table;
}
const ThreadSpec* find_thread_standard(const std::string& name)
{
for (const ThreadSpec& s : thread_standards())
if (s.name == name)
return &s;
return nullptr;
}
} // namespace Slic3r
+39
View File
@@ -0,0 +1,39 @@
#ifndef slic3r_ThreadStandards_hpp_
#define slic3r_ThreadStandards_hpp_
#include <string>
#include <vector>
namespace Slic3r {
// Canonical mechanical thread specifications (ISO metric + Unified imperial).
// All dimensions are stored in millimetres so the CAD kernel can consume them
// directly. The profile is the common 60deg V shared by ISO 261/965 and ASME
// B1.1 (UTS), so the cut/ridge depth used by the Design-tab Thread tool is the
// basic external thread height h = 0.6134 * pitch, and the internal (tapped)
// minor diameter is D1 = D - 1.0825 * pitch (= D - 2*5H/8).
struct ThreadSpec {
enum class Series { MetricCoarse, MetricFine, UNC, UNF };
std::string name; // designation, e.g. "M6", "1/4-20 UNC"
double major_diameter_mm; // nominal (crest) diameter
double pitch_mm; // axial advance per turn
Series series;
// 60deg basic external thread height (radial crest-to-root engagement).
double thread_depth_mm() const { return 0.6134 * pitch_mm; }
// Internal/tapped minor (tap-drill) diameter for the same nominal thread.
double minor_diameter_mm() const { return major_diameter_mm - 1.0825 * pitch_mm; }
bool imperial() const { return series == Series::UNC || series == Series::UNF; }
};
// Full ordered table (metric coarse, metric fine, UNC, UNF) for GUI listing.
const std::vector<ThreadSpec>& thread_standards();
// Exact case-sensitive designation lookup; nullptr if not a known standard.
const ThreadSpec* find_thread_standard(const std::string& name);
} // namespace Slic3r
#endif
+30
View File
@@ -0,0 +1,30 @@
# Vendored SolveSpace geometric constraint solver (libslvs), isolated solver core.
# Source: github.com/JacobStoren/SolveSpaceLib (extraction of solvespace.com libslvs).
# GPLv3 — compatible with this AGPL fork. Built as a self-contained STATIC lib; only
# include/slvs.h (+ SolveSpaceSystem.h C++ wrapper) is exposed to libslic3r.
# No external deps: it ships its own dense linear solver (no Eigen).
add_library(libslvs STATIC
constrainteq.cpp
entity.cpp
expr.cpp
system.cpp
util.cpp
platform/unixutil.cpp
lib.cpp
SolveSpaceSystem.cpp)
target_compile_definitions(libslvs PRIVATE -DLIBRARY)
# Public API headers (include/slvs.h, include/SolveSpaceSystem.h) propagate to consumers;
# the internal solvespace headers stay PRIVATE so they never leak into libslic3r TUs.
target_include_directories(libslvs
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
# libslic3r is linked into shared targets → PIC required. Silence warnings for this
# vendored code (it predates the project's -Wall/-Werror=return-type cleanliness).
set_target_properties(libslvs PROPERTIES POSITION_INDEPENDENT_CODE ON)
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
target_compile_options(libslvs PRIVATE -w -fno-strict-aliasing)
endif()
+675
View File
@@ -0,0 +1,675 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+177
View File
@@ -0,0 +1,177 @@
#define EXPORT_DLL
#include "SolveSpaceSystem.h"
#include <assert.h>
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
SolveSpaceSystem::SolveSpaceSystem()
: m_paramsMemory (new std::vector<Slvs_Param> ())
, m_entityMemory (new std::vector<Slvs_Entity> ())
, m_constraintMemory (new std::vector<Slvs_Constraint> ())
, m_failedConstrMemory(new std::vector<Slvs_hConstraint>())
{
m_paramsMemory ->reserve(100);
m_entityMemory ->reserve(100);
m_constraintMemory->reserve(100);
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
Slvs_hParam SolveSpaceSystem::addParam(Slvs_Param parameter)
{
parameter.h = static_cast<Slvs_hParam>(m_paramsMemory->size()+1);
m_paramsMemory->push_back(parameter);
m_slvsSystem.param = m_paramsMemory->data();
m_slvsSystem.params = static_cast<int>(m_paramsMemory->size());
return parameter.h;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
Slvs_hEntity SolveSpaceSystem::addEntity(Slvs_Entity entity)
{
entity.h = static_cast<Slvs_hEntity>(m_entityMemory->size()+1);
m_entityMemory->push_back(entity);
m_slvsSystem.entity = m_entityMemory->data();
m_slvsSystem.entities = static_cast<int>(m_entityMemory->size());
return entity.h;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
Slvs_hConstraint SolveSpaceSystem::addConstr(Slvs_Constraint constr)
{
constr.h = static_cast<Slvs_hConstraint>(m_constraintMemory->size()+1);
m_constraintMemory->push_back(constr);
m_slvsSystem.constraint = m_constraintMemory->data();
m_slvsSystem.constraints = static_cast<int>(m_constraintMemory->size());
return constr.h;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
SolveSpaceSystem::ResultStatus SolveSpaceSystem::solve(Slvs_hGroup groupId, bool reportFailedConstraints /*= true*/)
{
m_failedConstrMemory->resize(m_constraintMemory->size());
m_slvsSystem.failed = m_failedConstrMemory->data();
m_slvsSystem.faileds = static_cast<int>(m_failedConstrMemory->size());
m_slvsSystem.calculateFaileds = reportFailedConstraints;
Slvs_Solve(&m_slvsSystem, groupId);
m_failedConstrMemory->resize(m_slvsSystem.faileds);
return static_cast<ResultStatus>(m_slvsSystem.result);
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double SolveSpaceSystem::parameterValue(Slvs_hParam paramId)
{
return (*m_paramsMemory)[paramId-1].val;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void SolveSpaceSystem::setParameterValue(Slvs_hParam paramId, double value)
{
(*m_paramsMemory)[paramId-1].val = value;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::tuple< std::valarray<double>,
std::valarray<double>,
std::valarray<double> > SolveSpaceSystem::orientationMx(Slvs_hEntity normalIn3dEntityId)
{
Slvs_Entity e_CS = (*m_entityMemory)[normalIn3dEntityId -1];
if ( e_CS.type == SLVS_E_NORMAL_IN_3D )
{
std::valarray<double> quat ={ 0.0, 0.0, 0.0, 0.0 };
quat[0] = parameterValue(e_CS.param[0]);
quat[1] = parameterValue(e_CS.param[1]);
quat[2] = parameterValue(e_CS.param[2]);
quat[3] = parameterValue(e_CS.param[3]);
std::valarray<double> Ex ={ 0.0,0.0,0.0 };
std::valarray<double> Ey ={ 0.0,0.0,0.0 };
std::valarray<double> Ez ={ 0.0,0.0,0.0 };
Slvs_QuaternionU(quat[0], quat[1], quat[2], quat[3],
&Ex[0], &Ex[1], &Ex[2]);
Slvs_QuaternionV(quat[0], quat[1], quat[2], quat[3],
&Ey[0], &Ey[1], &Ey[2]);
Slvs_QuaternionN(quat[0], quat[1], quat[2], quat[3],
&Ez[0], &Ez[1], &Ez[2]);
return std::make_tuple(Ex, Ey, Ez);
}
assert(false);
return std::make_tuple(std::valarray<double>(), std::valarray<double>(), std::valarray<double>());
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::valarray<double> SolveSpaceSystem::global3DPos(Slvs_hEntity pointEntityId)
{
std::valarray<double> point ={ 0.0,0.0,0.0 };
Slvs_Entity pointEntity = (*m_entityMemory)[pointEntityId -1];
if ( pointEntity.type == SLVS_E_POINT_IN_2D )
{
std::valarray<double> locPoint ={ 0.0,0.0,0.0 };
locPoint[0] = parameterValue(pointEntity.param[0]);
locPoint[1] = parameterValue(pointEntity.param[1]);
Slvs_Entity e_Plane = (*m_entityMemory)[pointEntity.wrkpl - 1];
std::valarray<double> origin = global3DPos(e_Plane.point[0]);
auto mx = orientationMx(e_Plane.normal);
point = origin + std::get<0>(mx)*locPoint[0] + std::get<1>(mx)*locPoint[1];
}
else if ( pointEntity.type == SLVS_E_POINT_IN_3D )
{
point[0] = parameterValue(pointEntity.param[0]);
point[1] = parameterValue(pointEntity.param[1]);
point[2] = parameterValue(pointEntity.param[2]);
}
return point;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
Slvs_Constraint& SolveSpaceSystem::constraint(Slvs_hConstraint constraintId)
{
return (*m_constraintMemory)[constraintId-1];
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<Slvs_hConstraint> SolveSpaceSystem::failedConstraints() const
{
return (*m_failedConstrMemory);
}
+799
View File
@@ -0,0 +1,799 @@
//-----------------------------------------------------------------------------
// Given a constraint, generate one or more equations in our symbolic algebra
// system to represent that constraint; also various geometric helper
// functions for that.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
const hConstraint ConstraintBase::NO_CONSTRAINT = { 0 };
bool ConstraintBase::HasLabel() const {
switch(type) {
case Type::PT_LINE_DISTANCE:
case Type::PT_PLANE_DISTANCE:
case Type::PT_FACE_DISTANCE:
case Type::PT_PT_DISTANCE:
case Type::PROJ_PT_DISTANCE:
case Type::DIAMETER:
case Type::LENGTH_RATIO:
case Type::LENGTH_DIFFERENCE:
case Type::ANGLE:
case Type::COMMENT:
return true;
default:
return false;
}
}
ExprVector ConstraintBase::VectorsParallel3d(ExprVector a, ExprVector b, hParam p) {
return a.Minus(b.ScaledBy(Expr::From(p)));
}
Expr *ConstraintBase::PointLineDistance(hEntity wrkpl, hEntity hpt, hEntity hln)
{
EntityBase *ln = SK.GetEntity(hln);
EntityBase *a = SK.GetEntity(ln->point[0]);
EntityBase *b = SK.GetEntity(ln->point[1]);
EntityBase *p = SK.GetEntity(hpt);
if(wrkpl.v == EntityBase::FREE_IN_3D.v) {
ExprVector ep = p->PointGetExprs();
ExprVector ea = a->PointGetExprs();
ExprVector eb = b->PointGetExprs();
ExprVector eab = ea.Minus(eb);
Expr *m = eab.Magnitude();
return ((eab.Cross(ea.Minus(ep))).Magnitude())->Div(m);
} else {
Expr *ua, *va, *ub, *vb;
a->PointGetExprsInWorkplane(wrkpl, &ua, &va);
b->PointGetExprsInWorkplane(wrkpl, &ub, &vb);
Expr *du = ua->Minus(ub);
Expr *dv = va->Minus(vb);
Expr *u, *v;
p->PointGetExprsInWorkplane(wrkpl, &u, &v);
Expr *m = ((du->Square())->Plus(dv->Square()))->Sqrt();
Expr *proj = (dv->Times(ua->Minus(u)))->Minus(
(du->Times(va->Minus(v))));
return proj->Div(m);
}
}
Expr *ConstraintBase::PointPlaneDistance(ExprVector p, hEntity hpl) {
ExprVector n;
Expr *d;
SK.GetEntity(hpl)->WorkplaneGetPlaneExprs(&n, &d);
return (p.Dot(n))->Minus(d);
}
Expr *ConstraintBase::Distance(hEntity wrkpl, hEntity hpa, hEntity hpb) {
EntityBase *pa = SK.GetEntity(hpa);
EntityBase *pb = SK.GetEntity(hpb);
ssassert(pa->IsPoint() && pb->IsPoint(),
"Expected two points to measure projected distance between");
if(wrkpl.v == EntityBase::FREE_IN_3D.v) {
// This is true distance
ExprVector ea, eb, eab;
ea = pa->PointGetExprs();
eb = pb->PointGetExprs();
eab = ea.Minus(eb);
return eab.Magnitude();
} else {
// This is projected distance, in the given workplane.
Expr *au, *av, *bu, *bv;
pa->PointGetExprsInWorkplane(wrkpl, &au, &av);
pb->PointGetExprsInWorkplane(wrkpl, &bu, &bv);
Expr *du = au->Minus(bu);
Expr *dv = av->Minus(bv);
return ((du->Square())->Plus(dv->Square()))->Sqrt();
}
}
//-----------------------------------------------------------------------------
// Return the cosine of the angle between two vectors. If a workplane is
// specified, then it's the cosine of their projections into that workplane.
//-----------------------------------------------------------------------------
Expr *ConstraintBase::DirectionCosine(hEntity wrkpl,
ExprVector ae, ExprVector be)
{
if(wrkpl.v == EntityBase::FREE_IN_3D.v) {
Expr *mags = (ae.Magnitude())->Times(be.Magnitude());
return (ae.Dot(be))->Div(mags);
} else {
EntityBase *w = SK.GetEntity(wrkpl);
ExprVector u = w->Normal()->NormalExprsU();
ExprVector v = w->Normal()->NormalExprsV();
Expr *ua = u.Dot(ae);
Expr *va = v.Dot(ae);
Expr *ub = u.Dot(be);
Expr *vb = v.Dot(be);
Expr *maga = (ua->Square()->Plus(va->Square()))->Sqrt();
Expr *magb = (ub->Square()->Plus(vb->Square()))->Sqrt();
Expr *dot = (ua->Times(ub))->Plus(va->Times(vb));
return dot->Div(maga->Times(magb));
}
}
ExprVector ConstraintBase::PointInThreeSpace(hEntity workplane,
Expr *u, Expr *v)
{
EntityBase *w = SK.GetEntity(workplane);
ExprVector ub = w->Normal()->NormalExprsU();
ExprVector vb = w->Normal()->NormalExprsV();
ExprVector ob = w->WorkplaneGetOffsetExprs();
return (ub.ScaledBy(u)).Plus(vb.ScaledBy(v)).Plus(ob);
}
void ConstraintBase::ModifyToSatisfy() {
if(type == Type::ANGLE) {
Vector a = SK.GetEntity(entityA)->VectorGetNum();
Vector b = SK.GetEntity(entityB)->VectorGetNum();
if(other) a = a.ScaledBy(-1);
if(workplane.v != EntityBase::FREE_IN_3D.v) {
a = a.ProjectVectorInto(workplane);
b = b.ProjectVectorInto(workplane);
}
double c = (a.Dot(b))/(a.Magnitude() * b.Magnitude());
valA = acos(c)*180/PI;
} else if(type == Type::PT_ON_LINE) {
EntityBase *eln = SK.GetEntity(entityA);
EntityBase *ea = SK.GetEntity(eln->point[0]);
EntityBase *eb = SK.GetEntity(eln->point[1]);
EntityBase *ep = SK.GetEntity(ptA);
ExprVector exp = ep->PointGetExprsInWorkplane(workplane);
ExprVector exa = ea->PointGetExprsInWorkplane(workplane);
ExprVector exb = eb->PointGetExprsInWorkplane(workplane);
ExprVector exba = exb.Minus(exa);
SK.GetParam(valP)->val = exba.Dot(exp.Minus(exa))->Eval() / exba.Dot(exba)->Eval();
} else {
// We'll fix these ones up by looking at their symbolic equation;
// that means no extra work.
IdList<Equation,hEquation> l = {};
// Generate the equations even if this is a reference dimension
GenerateEquations(&l, /*forReference=*/true);
ssassert(l.n == 1, "Expected constraint to generate a single equation");
// These equations are written in the form f(...) - d = 0, where
// d is the value of the valA.
valA += (l.elem[0].e)->Eval();
l.Clear();
}
}
void ConstraintBase::AddEq(IdList<Equation,hEquation> *l, Expr *expr, int index) const
{
Equation eq;
eq.e = expr;
eq.h = h.equation(index);
l->Add(&eq);
}
void ConstraintBase::AddEq(IdList<Equation,hEquation> *l, const ExprVector &v,
int baseIndex) const {
AddEq(l, v.x, baseIndex);
AddEq(l, v.y, baseIndex + 1);
if(workplane.v == EntityBase::FREE_IN_3D.v) {
AddEq(l, v.z, baseIndex + 2);
}
}
void ConstraintBase::Generate(IdList<Param,hParam> *l) {
switch(type) {
case Type::PARALLEL:
case Type::CUBIC_LINE_TANGENT:
// Add new parameter only when we operate in 3d space
if(workplane.v != EntityBase::FREE_IN_3D.v) break;
// fallthrough
case Type::SAME_ORIENTATION:
case Type::PT_ON_LINE: {
Param p = {};
valP = h.param(0);
p.h = valP;
l->Add(&p);
break;
}
default:
break;
}
}
void ConstraintBase::GenerateEquations(IdList<Equation,hEquation> *l,
bool forReference) const {
if(reference && !forReference) return;
Expr *exA = Expr::From(valA);
switch(type) {
case Type::PT_PT_DISTANCE:
AddEq(l, Distance(workplane, ptA, ptB)->Minus(exA), 0);
return;
case Type::PROJ_PT_DISTANCE: {
ExprVector pA = SK.GetEntity(ptA)->PointGetExprs(),
pB = SK.GetEntity(ptB)->PointGetExprs(),
dp = pB.Minus(pA);
ExprVector pp = SK.GetEntity(entityA)->VectorGetExprs();
pp = pp.WithMagnitude(Expr::From(1.0));
AddEq(l, (dp.Dot(pp))->Minus(exA), 0);
return;
}
case Type::PT_LINE_DISTANCE:
AddEq(l,
PointLineDistance(workplane, ptA, entityA)->Minus(exA), 0);
return;
case Type::PT_PLANE_DISTANCE: {
ExprVector pt = SK.GetEntity(ptA)->PointGetExprs();
AddEq(l, (PointPlaneDistance(pt, entityA))->Minus(exA), 0);
return;
}
case Type::PT_FACE_DISTANCE: {
ExprVector pt = SK.GetEntity(ptA)->PointGetExprs();
EntityBase *f = SK.GetEntity(entityA);
ExprVector p0 = f->FaceGetPointExprs();
ExprVector n = f->FaceGetNormalExprs();
AddEq(l, (pt.Minus(p0)).Dot(n)->Minus(exA), 0);
return;
}
case Type::EQUAL_LENGTH_LINES: {
EntityBase *a = SK.GetEntity(entityA);
EntityBase *b = SK.GetEntity(entityB);
AddEq(l, Distance(workplane, a->point[0], a->point[1])->Minus(
Distance(workplane, b->point[0], b->point[1])), 0);
return;
}
// These work on distance squared, since the pt-line distances are
// signed, and we want the absolute value.
case Type::EQ_LEN_PT_LINE_D: {
EntityBase *forLen = SK.GetEntity(entityA);
Expr *d1 = Distance(workplane, forLen->point[0], forLen->point[1]);
Expr *d2 = PointLineDistance(workplane, ptA, entityB);
AddEq(l, (d1->Square())->Minus(d2->Square()), 0);
return;
}
case Type::EQ_PT_LN_DISTANCES: {
Expr *d1 = PointLineDistance(workplane, ptA, entityA);
Expr *d2 = PointLineDistance(workplane, ptB, entityB);
AddEq(l, (d1->Square())->Minus(d2->Square()), 0);
return;
}
case Type::LENGTH_RATIO: {
EntityBase *a = SK.GetEntity(entityA);
EntityBase *b = SK.GetEntity(entityB);
Expr *la = Distance(workplane, a->point[0], a->point[1]);
Expr *lb = Distance(workplane, b->point[0], b->point[1]);
AddEq(l, (la->Div(lb))->Minus(exA), 0);
return;
}
case Type::LENGTH_DIFFERENCE: {
EntityBase *a = SK.GetEntity(entityA);
EntityBase *b = SK.GetEntity(entityB);
Expr *la = Distance(workplane, a->point[0], a->point[1]);
Expr *lb = Distance(workplane, b->point[0], b->point[1]);
AddEq(l, (la->Minus(lb))->Minus(exA), 0);
return;
}
case Type::DIAMETER: {
EntityBase *circle = SK.GetEntity(entityA);
Expr *r = circle->CircleGetRadiusExpr();
AddEq(l, (r->Times(Expr::From(2)))->Minus(exA), 0);
return;
}
case Type::EQUAL_RADIUS: {
EntityBase *c1 = SK.GetEntity(entityA);
EntityBase *c2 = SK.GetEntity(entityB);
AddEq(l, (c1->CircleGetRadiusExpr())->Minus(
c2->CircleGetRadiusExpr()), 0);
return;
}
case Type::EQUAL_LINE_ARC_LEN: {
EntityBase *line = SK.GetEntity(entityA),
*arc = SK.GetEntity(entityB);
// Get the line length
ExprVector l0 = SK.GetEntity(line->point[0])->PointGetExprs(),
l1 = SK.GetEntity(line->point[1])->PointGetExprs();
Expr *ll = (l1.Minus(l0)).Magnitude();
// And get the arc radius, and the cosine of its angle
EntityBase *ao = SK.GetEntity(arc->point[0]),
*as = SK.GetEntity(arc->point[1]),
*af = SK.GetEntity(arc->point[2]);
ExprVector aos = (as->PointGetExprs()).Minus(ao->PointGetExprs()),
aof = (af->PointGetExprs()).Minus(ao->PointGetExprs());
Expr *r = aof.Magnitude();
ExprVector n = arc->Normal()->NormalExprsN();
ExprVector u = aos.WithMagnitude(Expr::From(1.0));
ExprVector v = n.Cross(u);
// so in our new csys, we start at (1, 0, 0)
Expr *costheta = aof.Dot(u)->Div(r);
Expr *sintheta = aof.Dot(v)->Div(r);
double thetas, thetaf, dtheta;
arc->ArcGetAngles(&thetas, &thetaf, &dtheta);
Expr *theta;
if(dtheta < 3*PI/4) {
theta = costheta->ACos();
} else if(dtheta < 5*PI/4) {
// As the angle crosses pi, cos theta is not invertible;
// so use the sine to stop blowing up
theta = Expr::From(PI)->Minus(sintheta->ASin());
} else {
theta = (Expr::From(2*PI))->Minus(costheta->ACos());
}
// And write the equation; r*theta = L
AddEq(l, (r->Times(theta))->Minus(ll), 0);
return;
}
case Type::POINTS_COINCIDENT: {
EntityBase *a = SK.GetEntity(ptA);
EntityBase *b = SK.GetEntity(ptB);
if(workplane.v == EntityBase::FREE_IN_3D.v) {
ExprVector pa = a->PointGetExprs();
ExprVector pb = b->PointGetExprs();
AddEq(l, pa.x->Minus(pb.x), 0);
AddEq(l, pa.y->Minus(pb.y), 1);
AddEq(l, pa.z->Minus(pb.z), 2);
} else {
Expr *au, *av;
Expr *bu, *bv;
a->PointGetExprsInWorkplane(workplane, &au, &av);
b->PointGetExprsInWorkplane(workplane, &bu, &bv);
AddEq(l, au->Minus(bu), 0);
AddEq(l, av->Minus(bv), 1);
}
return;
}
case Type::PT_IN_PLANE:
// This one works the same, whether projected or not.
AddEq(l, PointPlaneDistance(
SK.GetEntity(ptA)->PointGetExprs(), entityA), 0);
return;
case Type::PT_ON_FACE: {
// a plane, n dot (p - p0) = 0
ExprVector p = SK.GetEntity(ptA)->PointGetExprs();
EntityBase *f = SK.GetEntity(entityA);
ExprVector p0 = f->FaceGetPointExprs();
ExprVector n = f->FaceGetNormalExprs();
AddEq(l, (p.Minus(p0)).Dot(n), 0);
return;
}
case Type::PT_ON_LINE: {
EntityBase *ln = SK.GetEntity(entityA);
EntityBase *a = SK.GetEntity(ln->point[0]);
EntityBase *b = SK.GetEntity(ln->point[1]);
EntityBase *p = SK.GetEntity(ptA);
ExprVector ep = p->PointGetExprsInWorkplane(workplane);
ExprVector ea = a->PointGetExprsInWorkplane(workplane);
ExprVector eb = b->PointGetExprsInWorkplane(workplane);
ExprVector ptOnLine = ea.Plus(eb.Minus(ea).ScaledBy(Expr::From(valP)));
ExprVector eq = ptOnLine.Minus(ep);
AddEq(l, eq);
return;
}
case Type::PT_ON_CIRCLE: {
// This actually constrains the point to lie on the cylinder.
EntityBase *circle = SK.GetEntity(entityA);
ExprVector center = SK.GetEntity(circle->point[0])->PointGetExprs();
ExprVector pt = SK.GetEntity(ptA)->PointGetExprs();
EntityBase *normal = SK.GetEntity(circle->normal);
ExprVector u = normal->NormalExprsU(),
v = normal->NormalExprsV();
Expr *du = (center.Minus(pt)).Dot(u),
*dv = (center.Minus(pt)).Dot(v);
Expr *r = circle->CircleGetRadiusExpr();
AddEq(l, du->Square()->Plus(dv->Square())->Sqrt()->Minus(r), 0);
return;
}
case Type::AT_MIDPOINT:
if(workplane.v == EntityBase::FREE_IN_3D.v) {
EntityBase *ln = SK.GetEntity(entityA);
ExprVector a = SK.GetEntity(ln->point[0])->PointGetExprs();
ExprVector b = SK.GetEntity(ln->point[1])->PointGetExprs();
ExprVector m = (a.Plus(b)).ScaledBy(Expr::From(0.5));
if(ptA.v) {
ExprVector p = SK.GetEntity(ptA)->PointGetExprs();
AddEq(l, (m.x)->Minus(p.x), 0);
AddEq(l, (m.y)->Minus(p.y), 1);
AddEq(l, (m.z)->Minus(p.z), 2);
} else {
AddEq(l, PointPlaneDistance(m, entityB), 0);
}
} else {
EntityBase *ln = SK.GetEntity(entityA);
EntityBase *a = SK.GetEntity(ln->point[0]);
EntityBase *b = SK.GetEntity(ln->point[1]);
Expr *au, *av, *bu, *bv;
a->PointGetExprsInWorkplane(workplane, &au, &av);
b->PointGetExprsInWorkplane(workplane, &bu, &bv);
Expr *mu = Expr::From(0.5)->Times(au->Plus(bu));
Expr *mv = Expr::From(0.5)->Times(av->Plus(bv));
if(ptA.v) {
EntityBase *p = SK.GetEntity(ptA);
Expr *pu, *pv;
p->PointGetExprsInWorkplane(workplane, &pu, &pv);
AddEq(l, pu->Minus(mu), 0);
AddEq(l, pv->Minus(mv), 1);
} else {
ExprVector m = PointInThreeSpace(workplane, mu, mv);
AddEq(l, PointPlaneDistance(m, entityB), 0);
}
}
return;
case Type::SYMMETRIC:
if(workplane.v == EntityBase::FREE_IN_3D.v) {
EntityBase *plane = SK.GetEntity(entityA);
EntityBase *ea = SK.GetEntity(ptA);
EntityBase *eb = SK.GetEntity(ptB);
ExprVector a = ea->PointGetExprs();
ExprVector b = eb->PointGetExprs();
// The midpoint of the line connecting the symmetric points
// lies on the plane of the symmetry.
ExprVector m = (a.Plus(b)).ScaledBy(Expr::From(0.5));
AddEq(l, PointPlaneDistance(m, plane->h), 0);
// And projected into the plane of symmetry, the points are
// coincident.
Expr *au, *av, *bu, *bv;
ea->PointGetExprsInWorkplane(plane->h, &au, &av);
eb->PointGetExprsInWorkplane(plane->h, &bu, &bv);
AddEq(l, au->Minus(bu), 1);
AddEq(l, av->Minus(bv), 2);
} else {
EntityBase *plane = SK.GetEntity(entityA);
EntityBase *a = SK.GetEntity(ptA);
EntityBase *b = SK.GetEntity(ptB);
Expr *au, *av, *bu, *bv;
a->PointGetExprsInWorkplane(workplane, &au, &av);
b->PointGetExprsInWorkplane(workplane, &bu, &bv);
Expr *mu = Expr::From(0.5)->Times(au->Plus(bu));
Expr *mv = Expr::From(0.5)->Times(av->Plus(bv));
ExprVector m = PointInThreeSpace(workplane, mu, mv);
AddEq(l, PointPlaneDistance(m, plane->h), 0);
// Construct a vector within the workplane that is normal
// to the symmetry pane's normal (i.e., that lies in the
// plane of symmetry). The line connecting the points is
// perpendicular to that constructed vector.
EntityBase *w = SK.GetEntity(workplane);
ExprVector u = w->Normal()->NormalExprsU();
ExprVector v = w->Normal()->NormalExprsV();
ExprVector pa = a->PointGetExprs();
ExprVector pb = b->PointGetExprs();
ExprVector n;
Expr *d;
plane->WorkplaneGetPlaneExprs(&n, &d);
AddEq(l, (n.Cross(u.Cross(v))).Dot(pa.Minus(pb)), 1);
}
return;
case Type::SYMMETRIC_HORIZ:
case Type::SYMMETRIC_VERT: {
ssassert(workplane.v != Entity::FREE_IN_3D.v,
"Unexpected horizontal/vertical symmetric constraint in 3d");
EntityBase *a = SK.GetEntity(ptA);
EntityBase *b = SK.GetEntity(ptB);
Expr *au, *av, *bu, *bv;
a->PointGetExprsInWorkplane(workplane, &au, &av);
b->PointGetExprsInWorkplane(workplane, &bu, &bv);
if(type == Type::SYMMETRIC_HORIZ) {
AddEq(l, av->Minus(bv), 0);
AddEq(l, au->Plus(bu), 1);
} else {
AddEq(l, au->Minus(bu), 0);
AddEq(l, av->Plus(bv), 1);
}
return;
}
case Type::SYMMETRIC_LINE: {
EntityBase *pa = SK.GetEntity(ptA);
EntityBase *pb = SK.GetEntity(ptB);
Expr *pau, *pav, *pbu, *pbv;
pa->PointGetExprsInWorkplane(workplane, &pau, &pav);
pb->PointGetExprsInWorkplane(workplane, &pbu, &pbv);
EntityBase *ln = SK.GetEntity(entityA);
EntityBase *la = SK.GetEntity(ln->point[0]);
EntityBase *lb = SK.GetEntity(ln->point[1]);
Expr *lau, *lav, *lbu, *lbv;
la->PointGetExprsInWorkplane(workplane, &lau, &lav);
lb->PointGetExprsInWorkplane(workplane, &lbu, &lbv);
Expr *dpu = pbu->Minus(pau), *dpv = pbv->Minus(pav);
Expr *dlu = lbu->Minus(lau), *dlv = lbv->Minus(lav);
// The line through the points is perpendicular to the line
// of symmetry.
AddEq(l, (dlu->Times(dpu))->Plus(dlv->Times(dpv)), 0);
// And the signed distances of the points to the line are
// equal in magnitude and opposite in sign, so sum to zero
Expr *dista = (dlv->Times(lau->Minus(pau)))->Minus(
(dlu->Times(lav->Minus(pav))));
Expr *distb = (dlv->Times(lau->Minus(pbu)))->Minus(
(dlu->Times(lav->Minus(pbv))));
AddEq(l, dista->Plus(distb), 1);
return;
}
case Type::HORIZONTAL:
case Type::VERTICAL: {
ssassert(workplane.v != Entity::FREE_IN_3D.v,
"Unexpected horizontal/vertical constraint in 3d");
hEntity ha, hb;
if(entityA.v) {
EntityBase *e = SK.GetEntity(entityA);
ha = e->point[0];
hb = e->point[1];
} else {
ha = ptA;
hb = ptB;
}
EntityBase *a = SK.GetEntity(ha);
EntityBase *b = SK.GetEntity(hb);
Expr *au, *av, *bu, *bv;
a->PointGetExprsInWorkplane(workplane, &au, &av);
b->PointGetExprsInWorkplane(workplane, &bu, &bv);
AddEq(l, (type == Type::HORIZONTAL) ? av->Minus(bv) : au->Minus(bu), 0);
return;
}
case Type::SAME_ORIENTATION: {
EntityBase *a = SK.GetEntity(entityA);
EntityBase *b = SK.GetEntity(entityB);
ExprVector au = a->NormalExprsU(),
an = a->NormalExprsN();
ExprVector bu = b->NormalExprsU(),
bv = b->NormalExprsV(),
bn = b->NormalExprsN();
ExprVector eq = VectorsParallel3d(an, bn, valP);
AddEq(l, eq.x, 0);
AddEq(l, eq.y, 1);
AddEq(l, eq.z, 2);
Expr *d1 = au.Dot(bv);
Expr *d2 = au.Dot(bu);
// Allow either orientation for the coordinate system, depending
// on how it was drawn.
if(fabs(d1->Eval()) < fabs(d2->Eval())) {
AddEq(l, d1, 3);
} else {
AddEq(l, d2, 3);
}
return;
}
case Type::PERPENDICULAR:
case Type::ANGLE: {
EntityBase *a = SK.GetEntity(entityA);
EntityBase *b = SK.GetEntity(entityB);
ExprVector ae = a->VectorGetExprs();
ExprVector be = b->VectorGetExprs();
if(other) ae = ae.ScaledBy(Expr::From(-1));
Expr *c = DirectionCosine(workplane, ae, be);
if(type == Type::ANGLE) {
// The direction cosine is equal to the cosine of the
// specified angle
Expr *rads = exA->Times(Expr::From(PI/180)),
*rc = rads->Cos();
double arc = fabs(rc->Eval());
// avoid false detection of inconsistent systems by gaining
// up as the difference in dot products gets small at small
// angles; doubles still have plenty of precision, only
// problem is that rank test
Expr *mult = Expr::From(arc > 0.99 ? 0.01/(1.00001 - arc) : 1);
AddEq(l, (c->Minus(rc))->Times(mult), 0);
} else {
// The dot product (and therefore the direction cosine)
// is equal to zero, perpendicular.
AddEq(l, c, 0);
}
return;
}
case Type::EQUAL_ANGLE: {
EntityBase *a = SK.GetEntity(entityA);
EntityBase *b = SK.GetEntity(entityB);
EntityBase *c = SK.GetEntity(entityC);
EntityBase *d = SK.GetEntity(entityD);
ExprVector ae = a->VectorGetExprs();
ExprVector be = b->VectorGetExprs();
ExprVector ce = c->VectorGetExprs();
ExprVector de = d->VectorGetExprs();
if(other) ae = ae.ScaledBy(Expr::From(-1));
Expr *cab = DirectionCosine(workplane, ae, be);
Expr *ccd = DirectionCosine(workplane, ce, de);
AddEq(l, cab->Minus(ccd), 0);
return;
}
case Type::ARC_LINE_TANGENT: {
EntityBase *arc = SK.GetEntity(entityA);
EntityBase *line = SK.GetEntity(entityB);
ExprVector ac = SK.GetEntity(arc->point[0])->PointGetExprs();
ExprVector ap =
SK.GetEntity(arc->point[other ? 2 : 1])->PointGetExprs();
ExprVector ld = line->VectorGetExprs();
// The line is perpendicular to the radius
AddEq(l, ld.Dot(ac.Minus(ap)), 0);
return;
}
case Type::CUBIC_LINE_TANGENT: {
EntityBase *cubic = SK.GetEntity(entityA);
EntityBase *line = SK.GetEntity(entityB);
ExprVector a;
if(other) {
a = cubic->CubicGetFinishTangentExprs();
} else {
a = cubic->CubicGetStartTangentExprs();
}
ExprVector b = line->VectorGetExprs();
if(workplane.v == EntityBase::FREE_IN_3D.v) {
ExprVector eq = VectorsParallel3d(a, b, valP);
AddEq(l, eq);
} else {
EntityBase *w = SK.GetEntity(workplane);
ExprVector wn = w->Normal()->NormalExprsN();
AddEq(l, (a.Cross(b)).Dot(wn), 0);
}
return;
}
case Type::CURVE_CURVE_TANGENT: {
bool parallel = true;
int i;
ExprVector dir[2];
for(i = 0; i < 2; i++) {
EntityBase *e = SK.GetEntity((i == 0) ? entityA : entityB);
bool oth = (i == 0) ? other : other2;
if(e->type == Entity::Type::ARC_OF_CIRCLE) {
ExprVector center, endpoint;
center = SK.GetEntity(e->point[0])->PointGetExprs();
endpoint =
SK.GetEntity(e->point[oth ? 2 : 1])->PointGetExprs();
dir[i] = endpoint.Minus(center);
// We're using the vector from the center of the arc to
// an endpoint; so that's normal to the tangent, not
// parallel.
parallel = !parallel;
} else if(e->type == Entity::Type::CUBIC) { // BRANCH_ALWAYS_TAKEN
if(oth) {
dir[i] = e->CubicGetFinishTangentExprs();
} else {
dir[i] = e->CubicGetStartTangentExprs();
}
} else {
ssassert(false, "Unexpected entity types for CURVE_CURVE_TANGENT");
}
}
if(parallel) {
EntityBase *w = SK.GetEntity(workplane);
ExprVector wn = w->Normal()->NormalExprsN();
AddEq(l, ((dir[0]).Cross(dir[1])).Dot(wn), 0);
} else {
AddEq(l, (dir[0]).Dot(dir[1]), 0);
}
return;
}
case Type::PARALLEL: {
EntityBase *ea = SK.GetEntity(entityA), *eb = SK.GetEntity(entityB);
ExprVector a = ea->VectorGetExprsInWorkplane(workplane);
ExprVector b = eb->VectorGetExprsInWorkplane(workplane);
if(workplane.v == EntityBase::FREE_IN_3D.v) {
ExprVector eq = VectorsParallel3d(a, b, valP);
AddEq(l, eq);
} else {
// We use expressions written in workplane csys, so we can assume the workplane
// normal is (0, 0, 1). We can write the equation as:
// Expr *eq = a.Cross(b).Dot(ExprVector::From(0.0, 0.0, 1.0));
// but this will just result in elimination of x and y terms after dot product.
// We can only use the z expression:
// Expr *eq = a.Cross(b).z;
// but it's more efficient to write it in the terms of pseudo-scalar product:
Expr *eq = (a.x->Times(b.y))->Minus(a.y->Times(b.x));
AddEq(l, eq, 0);
}
return;
}
case Type::WHERE_DRAGGED: {
EntityBase *ep = SK.GetEntity(ptA);
if(workplane.v == EntityBase::FREE_IN_3D.v) {
ExprVector ev = ep->PointGetExprs();
Vector v = ep->PointGetNum();
AddEq(l, ev.x->Minus(Expr::From(v.x)), 0);
AddEq(l, ev.y->Minus(Expr::From(v.y)), 1);
AddEq(l, ev.z->Minus(Expr::From(v.z)), 2);
} else {
Expr *u, *v;
ep->PointGetExprsInWorkplane(workplane, &u, &v);
AddEq(l, u->Minus(Expr::From(u->Eval())), 0);
AddEq(l, v->Minus(Expr::From(v->Eval())), 1);
}
return;
}
case Type::COMMENT:
return;
}
ssassert(false, "Unexpected constraint ID");
}
+578
View File
@@ -0,0 +1,578 @@
//-----------------------------------------------------------------------------
// Data structures used frequently in the program, various kinds of vectors
// (of real numbers, not symbolic algebra stuff) and our templated lists.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#ifndef __DSC_H
#define __DSC_H
#include "solvespace.h"
class Vector;
class Vector4;
class Point2d;
class hEntity;
class hParam;
class Quaternion {
public:
// a + (vx)*i + (vy)*j + (vz)*k
double w, vx, vy, vz;
static const Quaternion IDENTITY;
static Quaternion From(double w, double vx, double vy, double vz);
static Quaternion From(hParam w, hParam vx, hParam vy, hParam vz);
static Quaternion From(Vector u, Vector v);
static Quaternion From(Vector axis, double dtheta);
Quaternion Plus(Quaternion b) const;
Quaternion Minus(Quaternion b) const;
Quaternion ScaledBy(double s) const;
double Magnitude() const;
Quaternion WithMagnitude(double s) const;
// Call a rotation matrix [ u' v' n' ]'; this returns the first and
// second rows, where that matrix is generated by this quaternion
Vector RotationU() const;
Vector RotationV() const;
Vector RotationN() const;
Vector Rotate(Vector p) const;
Quaternion ToThe(double p) const;
Quaternion Inverse() const;
Quaternion Times(Quaternion b) const;
Quaternion Mirror() const;
};
class Vector {
public:
double x, y, z;
static Vector From(double x, double y, double z);
static Vector From(hParam x, hParam y, hParam z);
static Vector AtIntersectionOfPlanes(Vector n1, double d1,
Vector n2, double d2);
static Vector AtIntersectionOfLines(Vector a0, Vector a1,
Vector b0, Vector b1,
bool *skew,
double *pa=NULL, double *pb=NULL);
static Vector AtIntersectionOfPlaneAndLine(Vector n, double d,
Vector p0, Vector p1,
bool *parallel);
static Vector AtIntersectionOfPlanes(Vector na, double da,
Vector nb, double db,
Vector nc, double dc, bool *parallel);
static void ClosestPointBetweenLines(Vector pa, Vector da,
Vector pb, Vector db,
double *ta, double *tb);
double Element(int i) const;
bool Equals(Vector v, double tol=LENGTH_EPS) const;
bool EqualsExactly(Vector v) const;
Vector Plus(Vector b) const;
Vector Minus(Vector b) const;
Vector Negated() const;
Vector Cross(Vector b) const;
double DirectionCosineWith(Vector b) const;
double Dot(Vector b) const;
Vector Normal(int which) const;
Vector RotatedAbout(Vector orig, Vector axis, double theta) const;
Vector RotatedAbout(Vector axis, double theta) const;
Vector DotInToCsys(Vector u, Vector v, Vector n) const;
Vector ScaleOutOfCsys(Vector u, Vector v, Vector n) const;
double DistanceToLine(Vector p0, Vector dp) const;
double DistanceToPlane(Vector normal, Vector origin) const;
bool OnLineSegment(Vector a, Vector b, double tol=LENGTH_EPS) const;
Vector ClosestPointOnLine(Vector p0, Vector deltal) const;
double Magnitude() const;
double MagSquared() const;
Vector WithMagnitude(double s) const;
Vector ScaledBy(double s) const;
Vector ProjectInto(hEntity wrkpl) const;
Vector ProjectVectorInto(hEntity wrkpl) const;
double DivPivoting(Vector delta) const;
Vector ClosestOrtho() const;
void MakeMaxMin(Vector *maxv, Vector *minv) const;
Vector ClampWithin(double minv, double maxv) const;
static bool BoundingBoxesDisjoint(Vector amax, Vector amin,
Vector bmax, Vector bmin);
static bool BoundingBoxIntersectsLine(Vector amax, Vector amin,
Vector p0, Vector p1, bool asSegment);
bool OutsideAndNotOn(Vector maxv, Vector minv) const;
Vector InPerspective(Vector u, Vector v, Vector n,
Vector origin, double cameraTan) const;
Point2d Project2d(Vector u, Vector v) const;
Point2d ProjectXy() const;
Vector4 Project4d() const;
};
struct VectorHash {
size_t operator()(const Vector &v) const;
};
struct VectorPred {
bool operator()(Vector a, Vector b) const;
};
class Vector4 {
public:
double w, x, y, z;
static Vector4 From(double w, double x, double y, double z);
static Vector4 From(double w, Vector v3);
static Vector4 Blend(Vector4 a, Vector4 b, double t);
Vector4 Plus(Vector4 b) const;
Vector4 Minus(Vector4 b) const;
Vector4 ScaledBy(double s) const;
Vector PerspectiveProject() const;
};
class Point2d {
public:
double x, y;
static Point2d From(double x, double y);
static Point2d FromPolar(double r, double a);
Point2d Plus(const Point2d &b) const;
Point2d Minus(const Point2d &b) const;
Point2d ScaledBy(double s) const;
double DivPivoting(Point2d delta) const;
double Dot(Point2d p) const;
double DistanceTo(const Point2d &p) const;
double DistanceToLine(const Point2d &p0, const Point2d &dp, bool asSegment) const;
double DistanceToLineSigned(const Point2d &p0, const Point2d &dp, bool asSegment) const;
double Angle() const;
double AngleTo(const Point2d &p) const;
double Magnitude() const;
double MagSquared() const;
Point2d WithMagnitude(double v) const;
Point2d Normal() const;
bool Equals(Point2d v, double tol=LENGTH_EPS) const;
};
// A simple list
template <class T>
class List {
public:
T *elem;
int n;
int elemsAllocated;
void ReserveMore(int howMuch) {
if(n + howMuch > elemsAllocated) {
elemsAllocated = n + howMuch;
T *newElem = (T *)MemAlloc((size_t)elemsAllocated*sizeof(elem[0]));
for(int i = 0; i < n; i++) {
new(&newElem[i]) T(std::move(elem[i]));
elem[i].~T();
}
MemFree(elem);
elem = newElem;
}
}
void AllocForOneMore() {
if(n >= elemsAllocated) {
ReserveMore((elemsAllocated + 32)*2 - n);
}
}
void Add(const T *t) {
AllocForOneMore();
new(&elem[n++]) T(*t);
}
void AddToBeginning(const T *t) {
AllocForOneMore();
new(&elem[n]) T();
std::move_backward(elem, elem + 1, elem + n + 1);
elem[0] = *t;
n++;
}
T *First() {
return (n == 0) ? NULL : &(elem[0]);
}
const T *First() const {
return (n == 0) ? NULL : &(elem[0]);
}
T *NextAfter(T *prev) {
if(!prev) return NULL;
if(prev - elem == (n - 1)) return NULL;
return prev + 1;
}
const T *NextAfter(const T *prev) const {
if(!prev) return NULL;
if(prev - elem == (n - 1)) return NULL;
return prev + 1;
}
T *begin() { return &elem[0]; }
T *end() { return &elem[n]; }
const T *begin() const { return &elem[0]; }
const T *end() const { return &elem[n]; }
void ClearTags() {
int i;
for(i = 0; i < n; i++) {
elem[i].tag = 0;
}
}
void Clear() {
for(int i = 0; i < n; i++)
elem[i].~T();
if(elem) MemFree(elem);
elem = NULL;
n = elemsAllocated = 0;
}
void RemoveTagged() {
int src, dest;
dest = 0;
for(src = 0; src < n; src++) {
if(elem[src].tag) {
// this item should be deleted
} else {
if(src != dest) {
elem[dest] = elem[src];
}
dest++;
}
}
for(int i = dest; i < n; i++)
elem[i].~T();
n = dest;
// and elemsAllocated is untouched, because we didn't resize
}
void RemoveLast(int cnt) {
ssassert(n >= cnt, "Removing more elements than the list contains");
for(int i = n - cnt; i < n; i++)
elem[i].~T();
n -= cnt;
// and elemsAllocated is untouched, same as in RemoveTagged
}
void Reverse() {
int i;
for(i = 0; i < (n/2); i++) {
swap(elem[i], elem[(n-1)-i]);
}
}
};
// A list, where each element has an integer identifier. The list is kept
// sorted by that identifier, and items can be looked up in log n time by
// id.
template <class T, class H>
class IdList {
public:
T *elem;
int n;
int elemsAllocated;
uint32_t MaximumId() {
if(n == 0) {
return 0;
} else {
return elem[n - 1].h.v;
}
}
H AddAndAssignId(T *t) {
t->h.v = (MaximumId() + 1);
Add(t);
return t->h;
}
void ReserveMore(int howMuch) {
if(n + howMuch > elemsAllocated) {
elemsAllocated = n + howMuch;
T *newElem = (T *)MemAlloc((size_t)elemsAllocated*sizeof(elem[0]));
for(int i = 0; i < n; i++) {
new(&newElem[i]) T(std::move(elem[i]));
elem[i].~T();
}
MemFree(elem);
elem = newElem;
}
}
void Add(T *t) {
if(n >= elemsAllocated) {
ReserveMore((elemsAllocated + 32)*2 - n);
}
int first = 0, last = n;
// We know that we must insert within the closed interval [first,last]
while(first != last) {
int mid = (first + last)/2;
H hm = elem[mid].h;
ssassert(hm.v != t->h.v, "Handle isn't unique");
if(hm.v > t->h.v) {
last = mid;
} else if(hm.v < t->h.v) {
first = mid + 1;
}
}
int i = first;
new(&elem[n]) T();
std::move_backward(elem + i, elem + n, elem + n + 1);
elem[i] = *t;
n++;
}
T *FindById(H h) {
T *t = FindByIdNoOops(h);
ssassert(t != NULL, "Cannot find handle");
return t;
}
int IndexOf(H h) {
int first = 0, last = n-1;
while(first <= last) {
int mid = (first + last)/2;
H hm = elem[mid].h;
if(hm.v > h.v) {
last = mid-1; // and first stays the same
} else if(hm.v < h.v) {
first = mid+1; // and last stays the same
} else {
return mid;
}
}
return -1;
}
T *FindByIdNoOops(H h) {
int first = 0, last = n-1;
while(first <= last) {
int mid = (first + last)/2;
H hm = elem[mid].h;
if(hm.v > h.v) {
last = mid-1; // and first stays the same
} else if(hm.v < h.v) {
first = mid+1; // and last stays the same
} else {
return &(elem[mid]);
}
}
return NULL;
}
T *First() {
return (n == 0) ? NULL : &(elem[0]);
}
T *NextAfter(T *prev) {
if(!prev) return NULL;
if(prev - elem == (n - 1)) return NULL;
return prev + 1;
}
T *begin() { return &elem[0]; }
T *end() { return &elem[n]; }
const T *begin() const { return &elem[0]; }
const T *end() const { return &elem[n]; }
void ClearTags() {
int i;
for(i = 0; i < n; i++) {
elem[i].tag = 0;
}
}
void Tag(H h, int tag) {
int i;
for(i = 0; i < n; i++) {
if(elem[i].h.v == h.v) {
elem[i].tag = tag;
}
}
}
void RemoveTagged() {
int src, dest;
dest = 0;
for(src = 0; src < n; src++) {
if(elem[src].tag) {
// this item should be deleted
elem[src].Clear();
} else {
if(src != dest) {
elem[dest] = elem[src];
}
dest++;
}
}
for(int i = dest; i < n; i++)
elem[i].~T();
n = dest;
// and elemsAllocated is untouched, because we didn't resize
}
void RemoveById(H h) {
ClearTags();
FindById(h)->tag = 1;
RemoveTagged();
}
void MoveSelfInto(IdList<T,H> *l) {
l->Clear();
*l = *this;
elemsAllocated = n = 0;
elem = NULL;
}
void DeepCopyInto(IdList<T,H> *l) {
l->Clear();
l->elem = (T *)MemAlloc(elemsAllocated * sizeof(elem[0]));
for(int i = 0; i < n; i++)
new(&l->elem[i]) T(elem[i]);
l->elemsAllocated = elemsAllocated;
l->n = n;
}
void Clear() {
for(int i = 0; i < n; i++) {
elem[i].Clear();
elem[i].~T();
}
elemsAllocated = n = 0;
if(elem) MemFree(elem);
elem = NULL;
}
};
class BandedMatrix {
public:
enum {
MAX_UNKNOWNS = 16,
RIGHT_OF_DIAG = 1,
LEFT_OF_DIAG = 2
};
double A[MAX_UNKNOWNS][MAX_UNKNOWNS];
double B[MAX_UNKNOWNS];
double X[MAX_UNKNOWNS];
int n;
void Solve();
};
#define RGBi(r, g, b) RgbaColor::From((r), (g), (b))
#define RGBf(r, g, b) RgbaColor::FromFloat((float)(r), (float)(g), (float)(b))
// Note: sizeof(class RgbaColor) should be exactly 4
//
class RgbaColor {
public:
uint8_t red, green, blue, alpha;
float redF() const { return (float)red / 255.0f; }
float greenF() const { return (float)green / 255.0f; }
float blueF() const { return (float)blue / 255.0f; }
float alphaF() const { return (float)alpha / 255.0f; }
bool IsEmpty() const { return alpha == 0; }
bool Equals(RgbaColor c) const {
return
c.red == red &&
c.green == green &&
c.blue == blue &&
c.alpha == alpha;
}
RgbaColor WithAlpha(uint8_t newAlpha) const {
RgbaColor color = *this;
color.alpha = newAlpha;
return color;
}
uint32_t ToPackedIntBGRA() const {
return
blue |
(uint32_t)(green << 8) |
(uint32_t)(red << 16) |
(uint32_t)((255 - alpha) << 24);
}
uint32_t ToPackedInt() const {
return
red |
(uint32_t)(green << 8) |
(uint32_t)(blue << 16) |
(uint32_t)((255 - alpha) << 24);
}
uint32_t ToARGB32() const {
return
blue |
(uint32_t)(green << 8) |
(uint32_t)(red << 16) |
(uint32_t)(alpha << 24);
}
static RgbaColor From(int r, int g, int b, int a = 255) {
RgbaColor c;
c.red = (uint8_t)r;
c.green = (uint8_t)g;
c.blue = (uint8_t)b;
c.alpha = (uint8_t)a;
return c;
}
static RgbaColor FromFloat(float r, float g, float b, float a = 1.0) {
return From(
(int)(255.1f * r),
(int)(255.1f * g),
(int)(255.1f * b),
(int)(255.1f * a));
}
static RgbaColor FromPackedInt(uint32_t rgba) {
return From(
(int)((rgba) & 0xff),
(int)((rgba >> 8) & 0xff),
(int)((rgba >> 16) & 0xff),
(int)(255 - ((rgba >> 24) & 0xff)));
}
static RgbaColor FromPackedIntBGRA(uint32_t bgra) {
return From(
(int)((bgra >> 16) & 0xff),
(int)((bgra >> 8) & 0xff),
(int)((bgra) & 0xff),
(int)(255 - ((bgra >> 24) & 0xff)));
}
};
struct RgbaColorCompare {
bool operator()(RgbaColor a, RgbaColor b) const {
return a.ToARGB32() < b.ToARGB32();
}
};
class BBox {
public:
Vector minp;
Vector maxp;
static BBox From(const Vector &p0, const Vector &p1);
Vector GetOrigin() const;
Vector GetExtents() const;
void Include(const Vector &v, double r = 0.0);
bool Overlaps(const BBox &b1) const;
bool Contains(const Point2d &p, double r = 0.0) const;
};
#endif
+875
View File
@@ -0,0 +1,875 @@
//-----------------------------------------------------------------------------
// The implementation of our entities in the symbolic algebra system, methods
// to return a symbolic representation of the entity (line by its endpoints,
// circle by center and radius, etc.).
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
const hEntity EntityBase::FREE_IN_3D = { 0 };
const hEntity EntityBase::NO_ENTITY = { 0 };
bool EntityBase::HasVector() const {
switch(type) {
case Type::LINE_SEGMENT:
case Type::NORMAL_IN_3D:
case Type::NORMAL_IN_2D:
case Type::NORMAL_N_COPY:
case Type::NORMAL_N_ROT:
case Type::NORMAL_N_ROT_AA:
return true;
default:
return false;
}
}
ExprVector EntityBase::VectorGetExprsInWorkplane(hEntity wrkpl) const {
switch(type) {
case Type::LINE_SEGMENT:
return (SK.GetEntity(point[0])->PointGetExprsInWorkplane(wrkpl)).Minus(
SK.GetEntity(point[1])->PointGetExprsInWorkplane(wrkpl));
case Type::NORMAL_IN_3D:
case Type::NORMAL_IN_2D:
case Type::NORMAL_N_COPY:
case Type::NORMAL_N_ROT:
case Type::NORMAL_N_ROT_AA: {
ExprVector ev = NormalExprsN();
if(wrkpl.v == EntityBase::FREE_IN_3D.v) {
return ev;
}
// Get the offset and basis vectors for this weird exotic csys.
EntityBase *w = SK.GetEntity(wrkpl);
ExprVector wu = w->Normal()->NormalExprsU();
ExprVector wv = w->Normal()->NormalExprsV();
// Get our coordinates in three-space, and project them into that
// coordinate system.
ExprVector result;
result.x = ev.Dot(wu);
result.y = ev.Dot(wv);
result.z = Expr::From(0.0);
return result;
}
default: ssassert(false, "Unexpected entity type");
}
}
ExprVector EntityBase::VectorGetExprs() const {
return VectorGetExprsInWorkplane(EntityBase::FREE_IN_3D);
}
Vector EntityBase::VectorGetNum() const {
switch(type) {
case Type::LINE_SEGMENT:
return (SK.GetEntity(point[0])->PointGetNum()).Minus(
SK.GetEntity(point[1])->PointGetNum());
case Type::NORMAL_IN_3D:
case Type::NORMAL_IN_2D:
case Type::NORMAL_N_COPY:
case Type::NORMAL_N_ROT:
case Type::NORMAL_N_ROT_AA:
return NormalN();
default: ssassert(false, "Unexpected entity type");
}
}
Vector EntityBase::VectorGetRefPoint() const {
switch(type) {
case Type::LINE_SEGMENT:
return ((SK.GetEntity(point[0])->PointGetNum()).Plus(
SK.GetEntity(point[1])->PointGetNum())).ScaledBy(0.5);
case Type::NORMAL_IN_3D:
case Type::NORMAL_IN_2D:
case Type::NORMAL_N_COPY:
case Type::NORMAL_N_ROT:
case Type::NORMAL_N_ROT_AA:
return SK.GetEntity(point[0])->PointGetNum();
default: ssassert(false, "Unexpected entity type");
}
}
Vector EntityBase::VectorGetStartPoint() const {
switch(type) {
case Type::LINE_SEGMENT:
return SK.GetEntity(point[1])->PointGetNum();
case Type::NORMAL_IN_3D:
case Type::NORMAL_IN_2D:
case Type::NORMAL_N_COPY:
case Type::NORMAL_N_ROT:
case Type::NORMAL_N_ROT_AA:
return SK.GetEntity(point[0])->PointGetNum();
default: ssassert(false, "Unexpected entity type");
}
}
bool EntityBase::IsCircle() const {
return (type == Type::CIRCLE) || (type == Type::ARC_OF_CIRCLE);
}
Expr *EntityBase::CircleGetRadiusExpr() const {
if(type == Type::CIRCLE) {
return SK.GetEntity(distance)->DistanceGetExpr();
} else if(type == Type::ARC_OF_CIRCLE) {
return Constraint::Distance(workplane, point[0], point[1]);
} else ssassert(false, "Unexpected entity type");
}
double EntityBase::CircleGetRadiusNum() const {
if(type == Type::CIRCLE) {
return SK.GetEntity(distance)->DistanceGetNum();
} else if(type == Type::ARC_OF_CIRCLE) {
Vector c = SK.GetEntity(point[0])->PointGetNum();
Vector pa = SK.GetEntity(point[1])->PointGetNum();
return (pa.Minus(c)).Magnitude();
} else ssassert(false, "Unexpected entity type");
}
void EntityBase::ArcGetAngles(double *thetaa, double *thetab, double *dtheta) const {
ssassert(type == Type::ARC_OF_CIRCLE, "Unexpected entity type");
Quaternion q = Normal()->NormalGetNum();
Vector u = q.RotationU(), v = q.RotationV();
Vector c = SK.GetEntity(point[0])->PointGetNum();
Vector pa = SK.GetEntity(point[1])->PointGetNum();
Vector pb = SK.GetEntity(point[2])->PointGetNum();
Point2d c2 = c.Project2d(u, v);
Point2d pa2 = (pa.Project2d(u, v)).Minus(c2);
Point2d pb2 = (pb.Project2d(u, v)).Minus(c2);
*thetaa = atan2(pa2.y, pa2.x);
*thetab = atan2(pb2.y, pb2.x);
*dtheta = *thetab - *thetaa;
// If the endpoints are coincident, call it a full arc, not a zero arc;
// useful concept to have when splitting
while(*dtheta < 1e-6) *dtheta += 2*PI;
while(*dtheta > (2*PI)) *dtheta -= 2*PI;
}
Vector EntityBase::CubicGetStartNum() const {
return SK.GetEntity(point[0])->PointGetNum();
}
Vector EntityBase::CubicGetFinishNum() const {
return SK.GetEntity(point[3+extraPoints])->PointGetNum();
}
ExprVector EntityBase::CubicGetStartTangentExprs() const {
ExprVector pon = SK.GetEntity(point[0])->PointGetExprs(),
poff = SK.GetEntity(point[1])->PointGetExprs();
return (pon.Minus(poff));
}
ExprVector EntityBase::CubicGetFinishTangentExprs() const {
ExprVector pon = SK.GetEntity(point[3+extraPoints])->PointGetExprs(),
poff = SK.GetEntity(point[2+extraPoints])->PointGetExprs();
return (pon.Minus(poff));
}
Vector EntityBase::CubicGetStartTangentNum() const {
Vector pon = SK.GetEntity(point[0])->PointGetNum(),
poff = SK.GetEntity(point[1])->PointGetNum();
return (pon.Minus(poff));
}
Vector EntityBase::CubicGetFinishTangentNum() const {
Vector pon = SK.GetEntity(point[3+extraPoints])->PointGetNum(),
poff = SK.GetEntity(point[2+extraPoints])->PointGetNum();
return (pon.Minus(poff));
}
bool EntityBase::IsWorkplane() const {
return (type == Type::WORKPLANE);
}
ExprVector EntityBase::WorkplaneGetOffsetExprs() const {
return SK.GetEntity(point[0])->PointGetExprs();
}
Vector EntityBase::WorkplaneGetOffset() const {
return SK.GetEntity(point[0])->PointGetNum();
}
void EntityBase::WorkplaneGetPlaneExprs(ExprVector *n, Expr **dn) const {
if(type == Type::WORKPLANE) {
*n = Normal()->NormalExprsN();
ExprVector p0 = SK.GetEntity(point[0])->PointGetExprs();
// The plane is n dot (p - p0) = 0, or
// n dot p - n dot p0 = 0
// so dn = n dot p0
*dn = p0.Dot(*n);
} else ssassert(false, "Unexpected entity type");
}
bool EntityBase::IsDistance() const {
return (type == Type::DISTANCE) ||
(type == Type::DISTANCE_N_COPY);
}
double EntityBase::DistanceGetNum() const {
if(type == Type::DISTANCE) {
return SK.GetParam(param[0])->val;
} else if(type == Type::DISTANCE_N_COPY) {
return numDistance;
} else ssassert(false, "Unexpected entity type");
}
Expr *EntityBase::DistanceGetExpr() const {
if(type == Type::DISTANCE) {
return Expr::From(param[0]);
} else if(type == Type::DISTANCE_N_COPY) {
return Expr::From(numDistance);
} else ssassert(false, "Unexpected entity type");
}
void EntityBase::DistanceForceTo(double v) {
if(type == Type::DISTANCE) {
(SK.GetParam(param[0]))->val = v;
} else if(type == Type::DISTANCE_N_COPY) {
// do nothing, it's locked
} else ssassert(false, "Unexpected entity type");
}
EntityBase *EntityBase::Normal() const {
return SK.GetEntity(normal);
}
bool EntityBase::IsPoint() const {
switch(type) {
case Type::POINT_IN_3D:
case Type::POINT_IN_2D:
case Type::POINT_N_COPY:
case Type::POINT_N_TRANS:
case Type::POINT_N_ROT_TRANS:
case Type::POINT_N_ROT_AA:
return true;
default:
return false;
}
}
bool EntityBase::IsNormal() const {
switch(type) {
case Type::NORMAL_IN_3D:
case Type::NORMAL_IN_2D:
case Type::NORMAL_N_COPY:
case Type::NORMAL_N_ROT:
case Type::NORMAL_N_ROT_AA:
return true;
default: return false;
}
}
Quaternion EntityBase::NormalGetNum() const {
Quaternion q;
switch(type) {
case Type::NORMAL_IN_3D:
q = Quaternion::From(param[0], param[1], param[2], param[3]);
break;
case Type::NORMAL_IN_2D: {
EntityBase *wrkpl = SK.GetEntity(workplane);
EntityBase *norm = SK.GetEntity(wrkpl->normal);
q = norm->NormalGetNum();
break;
}
case Type::NORMAL_N_COPY:
q = numNormal;
break;
case Type::NORMAL_N_ROT:
q = Quaternion::From(param[0], param[1], param[2], param[3]);
q = q.Times(numNormal);
break;
case Type::NORMAL_N_ROT_AA: {
q = GetAxisAngleQuaternion(0);
q = q.Times(numNormal);
break;
}
default: ssassert(false, "Unexpected entity type");
}
return q;
}
void EntityBase::NormalForceTo(Quaternion q) {
switch(type) {
case Type::NORMAL_IN_3D:
SK.GetParam(param[0])->val = q.w;
SK.GetParam(param[1])->val = q.vx;
SK.GetParam(param[2])->val = q.vy;
SK.GetParam(param[3])->val = q.vz;
break;
case Type::NORMAL_IN_2D:
case Type::NORMAL_N_COPY:
// There's absolutely nothing to do; these are locked.
break;
case Type::NORMAL_N_ROT: {
Quaternion qp = q.Times(numNormal.Inverse());
SK.GetParam(param[0])->val = qp.w;
SK.GetParam(param[1])->val = qp.vx;
SK.GetParam(param[2])->val = qp.vy;
SK.GetParam(param[3])->val = qp.vz;
break;
}
case Type::NORMAL_N_ROT_AA:
// Not sure if I'll bother implementing this one
break;
default: ssassert(false, "Unexpected entity type");
}
}
Vector EntityBase::NormalU() const {
return NormalGetNum().RotationU();
}
Vector EntityBase::NormalV() const {
return NormalGetNum().RotationV();
}
Vector EntityBase::NormalN() const {
return NormalGetNum().RotationN();
}
ExprVector EntityBase::NormalExprsU() const {
return NormalGetExprs().RotationU();
}
ExprVector EntityBase::NormalExprsV() const {
return NormalGetExprs().RotationV();
}
ExprVector EntityBase::NormalExprsN() const {
return NormalGetExprs().RotationN();
}
ExprQuaternion EntityBase::NormalGetExprs() const {
ExprQuaternion q;
switch(type) {
case Type::NORMAL_IN_3D:
q = ExprQuaternion::From(param[0], param[1], param[2], param[3]);
break;
case Type::NORMAL_IN_2D: {
EntityBase *wrkpl = SK.GetEntity(workplane);
EntityBase *norm = SK.GetEntity(wrkpl->normal);
q = norm->NormalGetExprs();
break;
}
case Type::NORMAL_N_COPY:
q = ExprQuaternion::From(numNormal);
break;
case Type::NORMAL_N_ROT: {
ExprQuaternion orig = ExprQuaternion::From(numNormal);
q = ExprQuaternion::From(param[0], param[1], param[2], param[3]);
q = q.Times(orig);
break;
}
case Type::NORMAL_N_ROT_AA: {
ExprQuaternion orig = ExprQuaternion::From(numNormal);
q = GetAxisAngleQuaternionExprs(0);
q = q.Times(orig);
break;
}
default: ssassert(false, "Unexpected entity type");
}
return q;
}
void EntityBase::PointForceParamTo(Vector p) {
switch(type) {
case Type::POINT_IN_3D:
SK.GetParam(param[0])->val = p.x;
SK.GetParam(param[1])->val = p.y;
SK.GetParam(param[2])->val = p.z;
break;
case Type::POINT_IN_2D:
SK.GetParam(param[0])->val = p.x;
SK.GetParam(param[1])->val = p.y;
break;
default: ssassert(false, "Unexpected entity type");
}
}
void EntityBase::PointForceTo(Vector p) {
switch(type) {
case Type::POINT_IN_3D:
SK.GetParam(param[0])->val = p.x;
SK.GetParam(param[1])->val = p.y;
SK.GetParam(param[2])->val = p.z;
break;
case Type::POINT_IN_2D: {
EntityBase *c = SK.GetEntity(workplane);
p = p.Minus(c->WorkplaneGetOffset());
SK.GetParam(param[0])->val = p.Dot(c->Normal()->NormalU());
SK.GetParam(param[1])->val = p.Dot(c->Normal()->NormalV());
break;
}
case Type::POINT_N_TRANS: {
if(timesApplied == 0) break;
Vector trans = (p.Minus(numPoint)).ScaledBy(1.0/timesApplied);
SK.GetParam(param[0])->val = trans.x;
SK.GetParam(param[1])->val = trans.y;
SK.GetParam(param[2])->val = trans.z;
break;
}
case Type::POINT_N_ROT_TRANS: {
// Force only the translation; leave the rotation unchanged. But
// remember that we're working with respect to the rotated
// point.
Vector trans = p.Minus(PointGetQuaternion().Rotate(numPoint));
SK.GetParam(param[0])->val = trans.x;
SK.GetParam(param[1])->val = trans.y;
SK.GetParam(param[2])->val = trans.z;
break;
}
case Type::POINT_N_ROT_AA: {
// Force only the angle; the axis and center of rotation stay
Vector offset = Vector::From(param[0], param[1], param[2]);
Vector normal = Vector::From(param[4], param[5], param[6]);
Vector u = normal.Normal(0), v = normal.Normal(1);
Vector po = p.Minus(offset), numo = numPoint.Minus(offset);
double thetap = atan2(v.Dot(po), u.Dot(po));
double thetan = atan2(v.Dot(numo), u.Dot(numo));
double thetaf = (thetap - thetan);
double thetai = (SK.GetParam(param[3])->val)*timesApplied*2;
double dtheta = thetaf - thetai;
// Take the smallest possible change in the actual step angle,
// in order to avoid jumps when you cross from +pi to -pi
while(dtheta < -PI) dtheta += 2*PI;
while(dtheta > PI) dtheta -= 2*PI;
SK.GetParam(param[3])->val = (thetai + dtheta)/(timesApplied*2);
break;
}
case Type::POINT_N_COPY:
// Nothing to do; it's a static copy
break;
default: ssassert(false, "Unexpected entity type");
}
}
Vector EntityBase::PointGetNum() const {
Vector p;
switch(type) {
case Type::POINT_IN_3D:
p = Vector::From(param[0], param[1], param[2]);
break;
case Type::POINT_IN_2D: {
EntityBase *c = SK.GetEntity(workplane);
Vector u = c->Normal()->NormalU();
Vector v = c->Normal()->NormalV();
p = u.ScaledBy(SK.GetParam(param[0])->val);
p = p.Plus(v.ScaledBy(SK.GetParam(param[1])->val));
p = p.Plus(c->WorkplaneGetOffset());
break;
}
case Type::POINT_N_TRANS: {
Vector trans = Vector::From(param[0], param[1], param[2]);
p = numPoint.Plus(trans.ScaledBy(timesApplied));
break;
}
case Type::POINT_N_ROT_TRANS: {
Vector offset = Vector::From(param[0], param[1], param[2]);
Quaternion q = PointGetQuaternion();
p = q.Rotate(numPoint);
p = p.Plus(offset);
break;
}
case Type::POINT_N_ROT_AA: {
Vector offset = Vector::From(param[0], param[1], param[2]);
Quaternion q = PointGetQuaternion();
p = numPoint.Minus(offset);
p = q.Rotate(p);
p = p.Plus(offset);
break;
}
case Type::POINT_N_COPY:
p = numPoint;
break;
default: ssassert(false, "Unexpected entity type");
}
return p;
}
ExprVector EntityBase::PointGetExprs() const {
ExprVector r;
switch(type) {
case Type::POINT_IN_3D:
r = ExprVector::From(param[0], param[1], param[2]);
break;
case Type::POINT_IN_2D: {
EntityBase *c = SK.GetEntity(workplane);
ExprVector u = c->Normal()->NormalExprsU();
ExprVector v = c->Normal()->NormalExprsV();
r = c->WorkplaneGetOffsetExprs();
r = r.Plus(u.ScaledBy(Expr::From(param[0])));
r = r.Plus(v.ScaledBy(Expr::From(param[1])));
break;
}
case Type::POINT_N_TRANS: {
ExprVector orig = ExprVector::From(numPoint);
ExprVector trans = ExprVector::From(param[0], param[1], param[2]);
r = orig.Plus(trans.ScaledBy(Expr::From(timesApplied)));
break;
}
case Type::POINT_N_ROT_TRANS: {
ExprVector orig = ExprVector::From(numPoint);
ExprVector trans = ExprVector::From(param[0], param[1], param[2]);
ExprQuaternion q =
ExprQuaternion::From(param[3], param[4], param[5], param[6]);
orig = q.Rotate(orig);
r = orig.Plus(trans);
break;
}
case Type::POINT_N_ROT_AA: {
ExprVector orig = ExprVector::From(numPoint);
ExprVector trans = ExprVector::From(param[0], param[1], param[2]);
ExprQuaternion q = GetAxisAngleQuaternionExprs(3);
orig = orig.Minus(trans);
orig = q.Rotate(orig);
r = orig.Plus(trans);
break;
}
case Type::POINT_N_COPY:
r = ExprVector::From(numPoint);
break;
default: ssassert(false, "Unexpected entity type");
}
return r;
}
void EntityBase::PointGetExprsInWorkplane(hEntity wrkpl, Expr **u, Expr **v) const {
if(type == Type::POINT_IN_2D && workplane.v == wrkpl.v) {
// They want our coordinates in the form that we've written them,
// very nice.
*u = Expr::From(param[0]);
*v = Expr::From(param[1]);
} else {
// Get the offset and basis vectors for this weird exotic csys.
EntityBase *w = SK.GetEntity(wrkpl);
ExprVector wp = w->WorkplaneGetOffsetExprs();
ExprVector wu = w->Normal()->NormalExprsU();
ExprVector wv = w->Normal()->NormalExprsV();
// Get our coordinates in three-space, and project them into that
// coordinate system.
ExprVector ev = PointGetExprs();
ev = ev.Minus(wp);
*u = ev.Dot(wu);
*v = ev.Dot(wv);
}
}
ExprVector EntityBase::PointGetExprsInWorkplane(hEntity wrkpl) const {
if(wrkpl.v == Entity::FREE_IN_3D.v) {
return PointGetExprs();
}
ExprVector r;
PointGetExprsInWorkplane(wrkpl, &r.x, &r.y);
r.z = Expr::From(0.0);
return r;
}
void EntityBase::PointForceQuaternionTo(Quaternion q) {
ssassert(type == Type::POINT_N_ROT_TRANS, "Unexpected entity type");
SK.GetParam(param[3])->val = q.w;
SK.GetParam(param[4])->val = q.vx;
SK.GetParam(param[5])->val = q.vy;
SK.GetParam(param[6])->val = q.vz;
}
Quaternion EntityBase::GetAxisAngleQuaternion(int param0) const {
Quaternion q;
double theta = timesApplied*SK.GetParam(param[param0+0])->val;
double s = sin(theta), c = cos(theta);
q.w = c;
q.vx = s*SK.GetParam(param[param0+1])->val;
q.vy = s*SK.GetParam(param[param0+2])->val;
q.vz = s*SK.GetParam(param[param0+3])->val;
return q;
}
ExprQuaternion EntityBase::GetAxisAngleQuaternionExprs(int param0) const {
ExprQuaternion q;
Expr *theta = Expr::From(timesApplied)->Times(
Expr::From(param[param0+0]));
Expr *c = theta->Cos(), *s = theta->Sin();
q.w = c;
q.vx = s->Times(Expr::From(param[param0+1]));
q.vy = s->Times(Expr::From(param[param0+2]));
q.vz = s->Times(Expr::From(param[param0+3]));
return q;
}
Quaternion EntityBase::PointGetQuaternion() const {
Quaternion q;
if(type == Type::POINT_N_ROT_AA) {
q = GetAxisAngleQuaternion(3);
} else if(type == Type::POINT_N_ROT_TRANS) {
q = Quaternion::From(param[3], param[4], param[5], param[6]);
} else ssassert(false, "Unexpected entity type");
return q;
}
bool EntityBase::IsFace() const {
switch(type) {
case Type::FACE_NORMAL_PT:
case Type::FACE_XPROD:
case Type::FACE_N_ROT_TRANS:
case Type::FACE_N_TRANS:
case Type::FACE_N_ROT_AA:
return true;
default:
return false;
}
}
ExprVector EntityBase::FaceGetNormalExprs() const {
ExprVector r;
if(type == Type::FACE_NORMAL_PT) {
Vector v = Vector::From(numNormal.vx, numNormal.vy, numNormal.vz);
r = ExprVector::From(v.WithMagnitude(1));
} else if(type == Type::FACE_XPROD) {
ExprVector vc = ExprVector::From(param[0], param[1], param[2]);
ExprVector vn =
ExprVector::From(numNormal.vx, numNormal.vy, numNormal.vz);
r = vc.Cross(vn);
r = r.WithMagnitude(Expr::From(1.0));
} else if(type == Type::FACE_N_ROT_TRANS) {
// The numerical normal vector gets the rotation; the numerical
// normal has magnitude one, and the rotation doesn't change that,
// so there's no need to fix it up.
r = ExprVector::From(numNormal.vx, numNormal.vy, numNormal.vz);
ExprQuaternion q =
ExprQuaternion::From(param[3], param[4], param[5], param[6]);
r = q.Rotate(r);
} else if(type == Type::FACE_N_TRANS) {
r = ExprVector::From(numNormal.vx, numNormal.vy, numNormal.vz);
} else if(type == Type::FACE_N_ROT_AA) {
r = ExprVector::From(numNormal.vx, numNormal.vy, numNormal.vz);
ExprQuaternion q = GetAxisAngleQuaternionExprs(3);
r = q.Rotate(r);
} else ssassert(false, "Unexpected entity type");
return r;
}
Vector EntityBase::FaceGetNormalNum() const {
Vector r;
if(type == Type::FACE_NORMAL_PT) {
r = Vector::From(numNormal.vx, numNormal.vy, numNormal.vz);
} else if(type == Type::FACE_XPROD) {
Vector vc = Vector::From(param[0], param[1], param[2]);
Vector vn = Vector::From(numNormal.vx, numNormal.vy, numNormal.vz);
r = vc.Cross(vn);
} else if(type == Type::FACE_N_ROT_TRANS) {
// The numerical normal vector gets the rotation
r = Vector::From(numNormal.vx, numNormal.vy, numNormal.vz);
Quaternion q = Quaternion::From(param[3], param[4], param[5], param[6]);
r = q.Rotate(r);
} else if(type == Type::FACE_N_TRANS) {
r = Vector::From(numNormal.vx, numNormal.vy, numNormal.vz);
} else if(type == Type::FACE_N_ROT_AA) {
r = Vector::From(numNormal.vx, numNormal.vy, numNormal.vz);
Quaternion q = GetAxisAngleQuaternion(3);
r = q.Rotate(r);
} else ssassert(false, "Unexpected entity type");
return r.WithMagnitude(1);
}
ExprVector EntityBase::FaceGetPointExprs() const {
ExprVector r;
if(type == Type::FACE_NORMAL_PT) {
r = SK.GetEntity(point[0])->PointGetExprs();
} else if(type == Type::FACE_XPROD) {
r = ExprVector::From(numPoint);
} else if(type == Type::FACE_N_ROT_TRANS) {
// The numerical point gets the rotation and translation.
ExprVector trans = ExprVector::From(param[0], param[1], param[2]);
ExprQuaternion q =
ExprQuaternion::From(param[3], param[4], param[5], param[6]);
r = ExprVector::From(numPoint);
r = q.Rotate(r);
r = r.Plus(trans);
} else if(type == Type::FACE_N_TRANS) {
ExprVector trans = ExprVector::From(param[0], param[1], param[2]);
r = ExprVector::From(numPoint);
r = r.Plus(trans.ScaledBy(Expr::From(timesApplied)));
} else if(type == Type::FACE_N_ROT_AA) {
ExprVector trans = ExprVector::From(param[0], param[1], param[2]);
ExprQuaternion q = GetAxisAngleQuaternionExprs(3);
r = ExprVector::From(numPoint);
r = r.Minus(trans);
r = q.Rotate(r);
r = r.Plus(trans);
} else ssassert(false, "Unexpected entity type");
return r;
}
Vector EntityBase::FaceGetPointNum() const {
Vector r;
if(type == Type::FACE_NORMAL_PT) {
r = SK.GetEntity(point[0])->PointGetNum();
} else if(type == Type::FACE_XPROD) {
r = numPoint;
} else if(type == Type::FACE_N_ROT_TRANS) {
// The numerical point gets the rotation and translation.
Vector trans = Vector::From(param[0], param[1], param[2]);
Quaternion q = Quaternion::From(param[3], param[4], param[5], param[6]);
r = q.Rotate(numPoint);
r = r.Plus(trans);
} else if(type == Type::FACE_N_TRANS) {
Vector trans = Vector::From(param[0], param[1], param[2]);
r = numPoint.Plus(trans.ScaledBy(timesApplied));
} else if(type == Type::FACE_N_ROT_AA) {
Vector trans = Vector::From(param[0], param[1], param[2]);
Quaternion q = GetAxisAngleQuaternion(3);
r = numPoint.Minus(trans);
r = q.Rotate(r);
r = r.Plus(trans);
} else ssassert(false, "Unexpected entity type");
return r;
}
bool EntityBase::HasEndpoints() const {
return (type == Type::LINE_SEGMENT) ||
(type == Type::CUBIC) ||
(type == Type::ARC_OF_CIRCLE);
}
Vector EntityBase::EndpointStart() const {
if(type == Type::LINE_SEGMENT) {
return SK.GetEntity(point[0])->PointGetNum();
} else if(type == Type::CUBIC) {
return CubicGetStartNum();
} else if(type == Type::ARC_OF_CIRCLE) {
return SK.GetEntity(point[1])->PointGetNum();
} else ssassert(false, "Unexpected entity type");
}
Vector EntityBase::EndpointFinish() const {
if(type == Type::LINE_SEGMENT) {
return SK.GetEntity(point[1])->PointGetNum();
} else if(type == Type::CUBIC) {
return CubicGetFinishNum();
} else if(type == Type::ARC_OF_CIRCLE) {
return SK.GetEntity(point[2])->PointGetNum();
} else ssassert(false, "Unexpected entity type");
}
void EntityBase::RectGetPointsExprs(ExprVector *eb, ExprVector *ec) const {
ssassert(type == Type::TTF_TEXT || type == Type::IMAGE,
"Unexpected entity type");
EntityBase *a = SK.GetEntity(point[0]);
EntityBase *o = SK.GetEntity(point[1]);
// Write equations for each point in the current workplane.
// This reduces the complexity of resulting equations.
ExprVector ea = a->PointGetExprsInWorkplane(workplane);
ExprVector eo = o->PointGetExprsInWorkplane(workplane);
// Take perpendicular vector and scale it by aspect ratio.
ExprVector eu = ea.Minus(eo);
ExprVector ev = ExprVector::From(eu.y, eu.x->Negate(), eu.z).ScaledBy(Expr::From(aspectRatio));
*eb = eo.Plus(ev);
*ec = eo.Plus(eu).Plus(ev);
}
void EntityBase::AddEq(IdList<Equation,hEquation> *l, Expr *expr, int index) const {
Equation eq;
eq.e = expr;
eq.h = h.equation(index);
l->Add(&eq);
}
void EntityBase::GenerateEquations(IdList<Equation,hEquation> *l) const {
switch(type) {
case Type::NORMAL_IN_3D: {
ExprQuaternion q = NormalGetExprs();
AddEq(l, (q.Magnitude())->Minus(Expr::From(1)), 0);
break;
}
case Type::ARC_OF_CIRCLE: {
// If this is a copied entity, with its point already fixed
// with respect to each other, then we don't want to generate
// the distance constraint!
if(SK.GetEntity(point[0])->type != Type::POINT_IN_2D) break;
// If the two endpoints of the arc are constrained coincident
// (to make a complete circle), then our distance constraint
// would be redundant and therefore overconstrain things.
int i;
for(i = 0; i < SK.constraint.n; i++) {
ConstraintBase *c = &(SK.constraint.elem[i]);
if(c->group.v != group.v) continue;
if(c->type != Constraint::Type::POINTS_COINCIDENT) continue;
if((c->ptA.v == point[1].v && c->ptB.v == point[2].v) ||
(c->ptA.v == point[2].v && c->ptB.v == point[1].v))
{
break;
}
}
if(i < SK.constraint.n) break;
Expr *ra = Constraint::Distance(workplane, point[0], point[1]);
Expr *rb = Constraint::Distance(workplane, point[0], point[2]);
AddEq(l, ra->Minus(rb), 0);
break;
}
case Type::IMAGE:
case Type::TTF_TEXT: {
if(SK.GetEntity(point[0])->type != Type::POINT_IN_2D) break;
EntityBase *b = SK.GetEntity(point[2]);
EntityBase *c = SK.GetEntity(point[3]);
ExprVector eb = b->PointGetExprsInWorkplane(workplane);
ExprVector ec = c->PointGetExprsInWorkplane(workplane);
ExprVector ebp, ecp;
RectGetPointsExprs(&ebp, &ecp);
ExprVector beq = eb.Minus(ebp);
AddEq(l, beq.x, 0);
AddEq(l, beq.y, 1);
ExprVector ceq = ec.Minus(ecp);
AddEq(l, ceq.x, 2);
AddEq(l, ceq.y, 3);
break;
}
default: // Most entities do not generate equations.
break;
}
}
+917
View File
@@ -0,0 +1,917 @@
//-----------------------------------------------------------------------------
// The symbolic algebra system used to write our constraint equations;
// routines to build expressions in software or from a user-provided string,
// and to compute the partial derivatives that we'll use when write our
// Jacobian matrix.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
ExprVector ExprVector::From(Expr *x, Expr *y, Expr *z) {
ExprVector r = { x, y, z};
return r;
}
ExprVector ExprVector::From(Vector vn) {
ExprVector ve;
ve.x = Expr::From(vn.x);
ve.y = Expr::From(vn.y);
ve.z = Expr::From(vn.z);
return ve;
}
ExprVector ExprVector::From(hParam x, hParam y, hParam z) {
ExprVector ve;
ve.x = Expr::From(x);
ve.y = Expr::From(y);
ve.z = Expr::From(z);
return ve;
}
ExprVector ExprVector::From(double x, double y, double z) {
ExprVector ve;
ve.x = Expr::From(x);
ve.y = Expr::From(y);
ve.z = Expr::From(z);
return ve;
}
ExprVector ExprVector::Minus(ExprVector b) const {
ExprVector r;
r.x = x->Minus(b.x);
r.y = y->Minus(b.y);
r.z = z->Minus(b.z);
return r;
}
ExprVector ExprVector::Plus(ExprVector b) const {
ExprVector r;
r.x = x->Plus(b.x);
r.y = y->Plus(b.y);
r.z = z->Plus(b.z);
return r;
}
Expr *ExprVector::Dot(ExprVector b) const {
Expr *r;
r = x->Times(b.x);
r = r->Plus(y->Times(b.y));
r = r->Plus(z->Times(b.z));
return r;
}
ExprVector ExprVector::Cross(ExprVector b) const {
ExprVector r;
r.x = (y->Times(b.z))->Minus(z->Times(b.y));
r.y = (z->Times(b.x))->Minus(x->Times(b.z));
r.z = (x->Times(b.y))->Minus(y->Times(b.x));
return r;
}
ExprVector ExprVector::ScaledBy(Expr *s) const {
ExprVector r;
r.x = x->Times(s);
r.y = y->Times(s);
r.z = z->Times(s);
return r;
}
ExprVector ExprVector::WithMagnitude(Expr *s) const {
Expr *m = Magnitude();
return ScaledBy(s->Div(m));
}
Expr *ExprVector::Magnitude() const {
Expr *r;
r = x->Square();
r = r->Plus(y->Square());
r = r->Plus(z->Square());
return r->Sqrt();
}
Vector ExprVector::Eval() const {
Vector r;
r.x = x->Eval();
r.y = y->Eval();
r.z = z->Eval();
return r;
}
ExprQuaternion ExprQuaternion::From(hParam w, hParam vx, hParam vy, hParam vz) {
ExprQuaternion q;
q.w = Expr::From(w);
q.vx = Expr::From(vx);
q.vy = Expr::From(vy);
q.vz = Expr::From(vz);
return q;
}
ExprQuaternion ExprQuaternion::From(Expr *w, Expr *vx, Expr *vy, Expr *vz)
{
ExprQuaternion q;
q.w = w;
q.vx = vx;
q.vy = vy;
q.vz = vz;
return q;
}
ExprQuaternion ExprQuaternion::From(Quaternion qn) {
ExprQuaternion qe;
qe.w = Expr::From(qn.w);
qe.vx = Expr::From(qn.vx);
qe.vy = Expr::From(qn.vy);
qe.vz = Expr::From(qn.vz);
return qe;
}
ExprVector ExprQuaternion::RotationU() const {
ExprVector u;
Expr *two = Expr::From(2);
u.x = w->Square();
u.x = (u.x)->Plus(vx->Square());
u.x = (u.x)->Minus(vy->Square());
u.x = (u.x)->Minus(vz->Square());
u.y = two->Times(w->Times(vz));
u.y = (u.y)->Plus(two->Times(vx->Times(vy)));
u.z = two->Times(vx->Times(vz));
u.z = (u.z)->Minus(two->Times(w->Times(vy)));
return u;
}
ExprVector ExprQuaternion::RotationV() const {
ExprVector v;
Expr *two = Expr::From(2);
v.x = two->Times(vx->Times(vy));
v.x = (v.x)->Minus(two->Times(w->Times(vz)));
v.y = w->Square();
v.y = (v.y)->Minus(vx->Square());
v.y = (v.y)->Plus(vy->Square());
v.y = (v.y)->Minus(vz->Square());
v.z = two->Times(w->Times(vx));
v.z = (v.z)->Plus(two->Times(vy->Times(vz)));
return v;
}
ExprVector ExprQuaternion::RotationN() const {
ExprVector n;
Expr *two = Expr::From(2);
n.x = two->Times( w->Times(vy));
n.x = (n.x)->Plus (two->Times(vx->Times(vz)));
n.y = two->Times(vy->Times(vz));
n.y = (n.y)->Minus(two->Times( w->Times(vx)));
n.z = w->Square();
n.z = (n.z)->Minus(vx->Square());
n.z = (n.z)->Minus(vy->Square());
n.z = (n.z)->Plus (vz->Square());
return n;
}
ExprVector ExprQuaternion::Rotate(ExprVector p) const {
// Express the point in the new basis
return (RotationU().ScaledBy(p.x)).Plus(
RotationV().ScaledBy(p.y)).Plus(
RotationN().ScaledBy(p.z));
}
ExprQuaternion ExprQuaternion::Times(ExprQuaternion b) const {
Expr *sa = w, *sb = b.w;
ExprVector va = { vx, vy, vz };
ExprVector vb = { b.vx, b.vy, b.vz };
ExprQuaternion r;
r.w = (sa->Times(sb))->Minus(va.Dot(vb));
ExprVector vr = vb.ScaledBy(sa).Plus(
va.ScaledBy(sb).Plus(
va.Cross(vb)));
r.vx = vr.x;
r.vy = vr.y;
r.vz = vr.z;
return r;
}
Expr *ExprQuaternion::Magnitude() const {
return ((w ->Square())->Plus(
(vx->Square())->Plus(
(vy->Square())->Plus(
(vz->Square())))))->Sqrt();
}
Expr *Expr::From(hParam p) {
Expr *r = AllocExpr();
r->op = Op::PARAM;
r->parh = p;
return r;
}
Expr *Expr::From(double v) {
// Statically allocate common constants.
// Note: this is only valid because AllocExpr() uses AllocTemporary(),
// and Expr* is never explicitly freed.
if(v == 0.0) {
static Expr zero(0.0);
return &zero;
}
if(v == 1.0) {
static Expr one(1.0);
return &one;
}
if(v == -1.0) {
static Expr mone(-1.0);
return &mone;
}
if(v == 0.5) {
static Expr half(0.5);
return &half;
}
if(v == -0.5) {
static Expr mhalf(-0.5);
return &mhalf;
}
Expr *r = AllocExpr();
r->op = Op::CONSTANT;
r->v = v;
return r;
}
Expr *Expr::AnyOp(Op newOp, Expr *b) {
Expr *r = AllocExpr();
r->op = newOp;
r->a = this;
r->b = b;
return r;
}
int Expr::Children() const {
switch(op) {
case Op::PARAM:
case Op::PARAM_PTR:
case Op::CONSTANT:
case Op::VARIABLE:
return 0;
case Op::PLUS:
case Op::MINUS:
case Op::TIMES:
case Op::DIV:
return 2;
case Op::NEGATE:
case Op::SQRT:
case Op::SQUARE:
case Op::SIN:
case Op::COS:
case Op::ASIN:
case Op::ACOS:
return 1;
}
ssassert(false, "Unexpected operation");
}
int Expr::Nodes() const {
switch(Children()) {
case 0: return 1;
case 1: return 1 + a->Nodes();
case 2: return 1 + a->Nodes() + b->Nodes();
default: ssassert(false, "Unexpected children count");
}
}
Expr *Expr::DeepCopy() const {
Expr *n = AllocExpr();
*n = *this;
int c = n->Children();
if(c > 0) n->a = a->DeepCopy();
if(c > 1) n->b = b->DeepCopy();
return n;
}
Expr *Expr::DeepCopyWithParamsAsPointers(IdList<Param,hParam> *firstTry,
IdList<Param,hParam> *thenTry) const
{
Expr *n = AllocExpr();
if(op == Op::PARAM) {
// A param that is referenced by its hParam gets rewritten to go
// straight in to the parameter table with a pointer, or simply
// into a constant if it's already known.
Param *p = firstTry->FindByIdNoOops(parh);
if(!p) p = thenTry->FindById(parh);
if(p->known) {
n->op = Op::CONSTANT;
n->v = p->val;
} else {
n->op = Op::PARAM_PTR;
n->parp = p;
}
return n;
}
*n = *this;
int c = n->Children();
if(c > 0) n->a = a->DeepCopyWithParamsAsPointers(firstTry, thenTry);
if(c > 1) n->b = b->DeepCopyWithParamsAsPointers(firstTry, thenTry);
return n;
}
double Expr::Eval() const {
switch(op) {
case Op::PARAM: return SK.GetParam(parh)->val;
case Op::PARAM_PTR: return parp->val;
case Op::CONSTANT: return v;
case Op::VARIABLE: ssassert(false, "Not supported yet");
case Op::PLUS: return a->Eval() + b->Eval();
case Op::MINUS: return a->Eval() - b->Eval();
case Op::TIMES: return a->Eval() * b->Eval();
case Op::DIV: return a->Eval() / b->Eval();
case Op::NEGATE: return -(a->Eval());
case Op::SQRT: return sqrt(a->Eval());
case Op::SQUARE: { double r = a->Eval(); return r*r; }
case Op::SIN: return sin(a->Eval());
case Op::COS: return cos(a->Eval());
case Op::ACOS: return acos(a->Eval());
case Op::ASIN: return asin(a->Eval());
}
ssassert(false, "Unexpected operation");
}
Expr *Expr::PartialWrt(hParam p) const {
Expr *da, *db;
switch(op) {
case Op::PARAM_PTR: return From(p.v == parp->h.v ? 1 : 0);
case Op::PARAM: return From(p.v == parh.v ? 1 : 0);
case Op::CONSTANT: return From(0.0);
case Op::VARIABLE: ssassert(false, "Not supported yet");
case Op::PLUS: return (a->PartialWrt(p))->Plus(b->PartialWrt(p));
case Op::MINUS: return (a->PartialWrt(p))->Minus(b->PartialWrt(p));
case Op::TIMES:
da = a->PartialWrt(p);
db = b->PartialWrt(p);
return (a->Times(db))->Plus(b->Times(da));
case Op::DIV:
da = a->PartialWrt(p);
db = b->PartialWrt(p);
return ((da->Times(b))->Minus(a->Times(db)))->Div(b->Square());
case Op::SQRT:
return (From(0.5)->Div(a->Sqrt()))->Times(a->PartialWrt(p));
case Op::SQUARE:
return (From(2.0)->Times(a))->Times(a->PartialWrt(p));
case Op::NEGATE: return (a->PartialWrt(p))->Negate();
case Op::SIN: return (a->Cos())->Times(a->PartialWrt(p));
case Op::COS: return ((a->Sin())->Times(a->PartialWrt(p)))->Negate();
case Op::ASIN:
return (From(1)->Div((From(1)->Minus(a->Square()))->Sqrt()))
->Times(a->PartialWrt(p));
case Op::ACOS:
return (From(-1)->Div((From(1)->Minus(a->Square()))->Sqrt()))
->Times(a->PartialWrt(p));
}
ssassert(false, "Unexpected operation");
}
uint64_t Expr::ParamsUsed() const {
uint64_t r = 0;
if(op == Op::PARAM) r |= ((uint64_t)1 << (parh.v % 61));
if(op == Op::PARAM_PTR) r |= ((uint64_t)1 << (parp->h.v % 61));
int c = Children();
if(c >= 1) r |= a->ParamsUsed();
if(c >= 2) r |= b->ParamsUsed();
return r;
}
bool Expr::DependsOn(hParam p) const {
if(op == Op::PARAM) return (parh.v == p.v);
if(op == Op::PARAM_PTR) return (parp->h.v == p.v);
int c = Children();
if(c == 1) return a->DependsOn(p);
if(c == 2) return a->DependsOn(p) || b->DependsOn(p);
return false;
}
bool Expr::Tol(double a, double b) {
return fabs(a - b) < 0.001;
}
Expr *Expr::FoldConstants() {
Expr *n = AllocExpr();
*n = *this;
int c = Children();
if(c >= 1) n->a = a->FoldConstants();
if(c >= 2) n->b = b->FoldConstants();
switch(op) {
case Op::PARAM_PTR:
case Op::PARAM:
case Op::CONSTANT:
case Op::VARIABLE:
break;
case Op::MINUS:
case Op::TIMES:
case Op::DIV:
case Op::PLUS:
// If both ops are known, then we can evaluate immediately
if(n->a->op == Op::CONSTANT && n->b->op == Op::CONSTANT) {
double nv = n->Eval();
n->op = Op::CONSTANT;
n->v = nv;
break;
}
// x + 0 = 0 + x = x
if(op == Op::PLUS && n->b->op == Op::CONSTANT && Tol(n->b->v, 0)) {
*n = *(n->a); break;
}
if(op == Op::PLUS && n->a->op == Op::CONSTANT && Tol(n->a->v, 0)) {
*n = *(n->b); break;
}
// 1*x = x*1 = x
if(op == Op::TIMES && n->b->op == Op::CONSTANT && Tol(n->b->v, 1)) {
*n = *(n->a); break;
}
if(op == Op::TIMES && n->a->op == Op::CONSTANT && Tol(n->a->v, 1)) {
*n = *(n->b); break;
}
// 0*x = x*0 = 0
if(op == Op::TIMES && n->b->op == Op::CONSTANT && Tol(n->b->v, 0)) {
n->op = Op::CONSTANT; n->v = 0; break;
}
if(op == Op::TIMES && n->a->op == Op::CONSTANT && Tol(n->a->v, 0)) {
n->op = Op::CONSTANT; n->v = 0; break;
}
break;
case Op::SQRT:
case Op::SQUARE:
case Op::NEGATE:
case Op::SIN:
case Op::COS:
case Op::ASIN:
case Op::ACOS:
if(n->a->op == Op::CONSTANT) {
double nv = n->Eval();
n->op = Op::CONSTANT;
n->v = nv;
}
break;
}
return n;
}
void Expr::Substitute(hParam oldh, hParam newh) {
ssassert(op != Op::PARAM_PTR, "Expected an expression that refer to params via handles");
if(op == Op::PARAM && parh.v == oldh.v) {
parh = newh;
}
int c = Children();
if(c >= 1) a->Substitute(oldh, newh);
if(c >= 2) b->Substitute(oldh, newh);
}
//-----------------------------------------------------------------------------
// If the expression references only one parameter that appears in pl, then
// return that parameter. If no param is referenced, then return NO_PARAMS.
// If multiple params are referenced, then return MULTIPLE_PARAMS.
//-----------------------------------------------------------------------------
const hParam Expr::NO_PARAMS = { 0 };
const hParam Expr::MULTIPLE_PARAMS = { 1 };
hParam Expr::ReferencedParams(ParamList *pl) const {
if(op == Op::PARAM) {
if(pl->FindByIdNoOops(parh)) {
return parh;
} else {
return NO_PARAMS;
}
}
ssassert(op != Op::PARAM_PTR, "Expected an expression that refer to params via handles");
int c = Children();
if(c == 0) {
return NO_PARAMS;
} else if(c == 1) {
return a->ReferencedParams(pl);
} else if(c == 2) {
hParam pa, pb;
pa = a->ReferencedParams(pl);
pb = b->ReferencedParams(pl);
if(pa.v == NO_PARAMS.v) {
return pb;
} else if(pb.v == NO_PARAMS.v) {
return pa;
} else if(pa.v == pb.v) {
return pa; // either, doesn't matter
} else {
return MULTIPLE_PARAMS;
}
} else ssassert(false, "Unexpected children count");
}
//-----------------------------------------------------------------------------
// Routines to pretty-print an expression. Mostly for debugging.
//-----------------------------------------------------------------------------
std::string Expr::Print() const {
char c;
switch(op) {
case Op::PARAM: return ssprintf("param(%08x)", parh.v);
case Op::PARAM_PTR: return ssprintf("param(p%08x)", parp->h.v);
case Op::CONSTANT: return ssprintf("%.3f", v);
case Op::VARIABLE: return "(var)";
case Op::PLUS: c = '+'; goto p;
case Op::MINUS: c = '-'; goto p;
case Op::TIMES: c = '*'; goto p;
case Op::DIV: c = '/'; goto p;
p:
return "(" + a->Print() + " " + c + " " + b->Print() + ")";
break;
case Op::NEGATE: return "(- " + a->Print() + ")";
case Op::SQRT: return "(sqrt " + a->Print() + ")";
case Op::SQUARE: return "(square " + a->Print() + ")";
case Op::SIN: return "(sin " + a->Print() + ")";
case Op::COS: return "(cos " + a->Print() + ")";
case Op::ASIN: return "(asin " + a->Print() + ")";
case Op::ACOS: return "(acos " + a->Print() + ")";
}
ssassert(false, "Unexpected operation");
}
//-----------------------------------------------------------------------------
// A parser; convert a string to an expression. Infix notation, with the
// usual shift/reduce approach. I had great hopes for user-entered eq
// constraints, but those don't seem very useful, so right now this is just
// to provide calculator type functionality wherever numbers are entered.
//-----------------------------------------------------------------------------
class ExprParser {
public:
enum class TokenType {
ERROR = 0,
PAREN_LEFT,
PAREN_RIGHT,
BINARY_OP,
UNARY_OP,
OPERAND,
END,
};
class Token {
public:
TokenType type;
Expr *expr;
static Token From(TokenType type = TokenType::ERROR, Expr *expr = NULL);
static Token From(TokenType type, Expr::Op op);
bool IsError() const { return type == TokenType::ERROR; }
};
const char *input;
unsigned inputPos;
std::vector<Token> stack;
char ReadChar();
char PeekChar();
std::string ReadWord();
void SkipSpace();
Token PopOperator(std::string *error);
Token PopOperand(std::string *error);
int Precedence(Token token);
Token LexNumber(std::string *error);
Token Lex(std::string *error);
bool Reduce(std::string *error);
bool Parse(std::string *error, size_t reduceUntil = 0);
static Expr *Parse(const char *input, std::string *error);
};
ExprParser::Token ExprParser::Token::From(TokenType type, Expr *expr) {
Token t;
t.type = type;
t.expr = expr;
return t;
}
ExprParser::Token ExprParser::Token::From(TokenType type, Expr::Op op) {
Token t;
t.type = type;
t.expr = Expr::AllocExpr();
t.expr->op = op;
return t;
}
char ExprParser::ReadChar() {
return input[inputPos++];
}
char ExprParser::PeekChar() {
return input[inputPos];
}
std::string ExprParser::ReadWord() {
std::string s;
while(char c = PeekChar()) {
if(!isalnum(c)) break;
s.push_back(ReadChar());
}
return s;
}
void ExprParser::SkipSpace() {
while(char c = PeekChar()) {
if(!isspace(c)) break;
ReadChar();
}
}
ExprParser::Token ExprParser::LexNumber(std::string *error) {
std::string s;
while(char c = PeekChar()) {
if(!((c >= '0' && c <= '9') || c == 'e' || c == 'E' || c == '.' || c == '_')) break;
if(c == '_') {
ReadChar();
continue;
}
s.push_back(ReadChar());
}
char *endptr;
double d = strtod(s.c_str(), &endptr);
Token t = Token::From();
if(endptr == s.c_str() + s.size()) {
t = Token::From(TokenType::OPERAND, Expr::Op::CONSTANT);
t.expr->v = d;
} else {
*error = "'" + s + "' is not a valid number";
}
return t;
}
ExprParser::Token ExprParser::Lex(std::string *error) {
SkipSpace();
Token t = Token::From();
char c = PeekChar();
if(isupper(c)) {
std::string n = ReadWord();
t = Token::From(TokenType::OPERAND, Expr::Op::VARIABLE);
} else if(isalpha(c)) {
std::string s = ReadWord();
if(s == "sqrt") {
t = Token::From(TokenType::UNARY_OP, Expr::Op::SQRT);
} else if(s == "square") {
t = Token::From(TokenType::UNARY_OP, Expr::Op::SQUARE);
} else if(s == "sin") {
t = Token::From(TokenType::UNARY_OP, Expr::Op::SIN);
} else if(s == "cos") {
t = Token::From(TokenType::UNARY_OP, Expr::Op::COS);
} else if(s == "asin") {
t = Token::From(TokenType::UNARY_OP, Expr::Op::ASIN);
} else if(s == "acos") {
t = Token::From(TokenType::UNARY_OP, Expr::Op::ACOS);
} else if(s == "pi") {
t = Token::From(TokenType::OPERAND, Expr::Op::CONSTANT);
t.expr->v = PI;
} else {
*error = "'" + s + "' is not a valid variable, function or constant";
}
} else if(isdigit(c) || c == '.') {
return LexNumber(error);
} else if(ispunct(c)) {
ReadChar();
if(c == '+') {
t = Token::From(TokenType::BINARY_OP, Expr::Op::PLUS);
} else if(c == '-') {
t = Token::From(TokenType::BINARY_OP, Expr::Op::MINUS);
} else if(c == '*') {
t = Token::From(TokenType::BINARY_OP, Expr::Op::TIMES);
} else if(c == '/') {
t = Token::From(TokenType::BINARY_OP, Expr::Op::DIV);
} else if(c == '(') {
t = Token::From(TokenType::PAREN_LEFT);
} else if(c == ')') {
t = Token::From(TokenType::PAREN_RIGHT);
} else {
*error = "'" + std::string(1, c) + "' is not a valid operator";
}
} else if(c == '\0') {
t = Token::From(TokenType::END);
} else {
*error = "Unexpected character '" + std::string(1, c) + "'";
}
return t;
}
ExprParser::Token ExprParser::PopOperand(std::string *error) {
Token t = Token::From();
if(stack.empty() || stack.back().type != TokenType::OPERAND) {
*error = "Expected an operand";
} else {
t = stack.back();
stack.pop_back();
}
return t;
}
ExprParser::Token ExprParser::PopOperator(std::string *error) {
Token t = Token::From();
if(stack.empty() || (stack.back().type != TokenType::UNARY_OP &&
stack.back().type != TokenType::BINARY_OP)) {
*error = "Expected an operator";
} else {
t = stack.back();
stack.pop_back();
}
return t;
}
int ExprParser::Precedence(Token t) {
ssassert(t.type == TokenType::BINARY_OP ||
t.type == TokenType::UNARY_OP ||
t.type == TokenType::OPERAND,
"Unexpected token type");
if(t.type == TokenType::UNARY_OP) {
return 30;
} else if(t.expr->op == Expr::Op::TIMES ||
t.expr->op == Expr::Op::DIV) {
return 20;
} else if(t.expr->op == Expr::Op::PLUS ||
t.expr->op == Expr::Op::MINUS) {
return 10;
} else if(t.type == TokenType::OPERAND) {
return 0;
} else ssassert(false, "Unexpected operator");
}
bool ExprParser::Reduce(std::string *error) {
Token a = PopOperand(error);
if(a.IsError()) return false;
Token op = PopOperator(error);
if(op.IsError()) return false;
Token r = Token::From(TokenType::OPERAND);
switch(op.type) {
case TokenType::BINARY_OP: {
Token b = PopOperand(error);
if(b.IsError()) return false;
r.expr = b.expr->AnyOp(op.expr->op, a.expr);
break;
}
case TokenType::UNARY_OP: {
Expr *e = a.expr;
switch(op.expr->op) {
case Expr::Op::NEGATE: e = e->Negate(); break;
case Expr::Op::SQRT: e = e->Sqrt(); break;
case Expr::Op::SQUARE: e = e->Times(e); break;
case Expr::Op::SIN: e = e->Times(Expr::From(PI/180))->Sin(); break;
case Expr::Op::COS: e = e->Times(Expr::From(PI/180))->Cos(); break;
case Expr::Op::ASIN: e = e->ASin()->Times(Expr::From(180/PI)); break;
case Expr::Op::ACOS: e = e->ACos()->Times(Expr::From(180/PI)); break;
default: ssassert(false, "Unexpected unary operator");
}
r.expr = e;
break;
}
default: ssassert(false, "Unexpected operator");
}
stack.push_back(r);
return true;
}
bool ExprParser::Parse(std::string *error, size_t reduceUntil) {
while(true) {
Token t = Lex(error);
switch(t.type) {
case TokenType::ERROR:
return false;
case TokenType::END:
case TokenType::PAREN_RIGHT:
while(stack.size() > 1 + reduceUntil) {
if(!Reduce(error)) return false;
}
if(t.type == TokenType::PAREN_RIGHT) {
stack.push_back(t);
}
return true;
case TokenType::PAREN_LEFT: {
// sub-expression
if(!Parse(error, /*reduceUntil=*/stack.size())) return false;
if(stack.empty() || stack.back().type != TokenType::PAREN_RIGHT) {
*error = "Expected ')'";
return false;
}
stack.pop_back();
break;
}
case TokenType::BINARY_OP:
if((stack.size() > reduceUntil && stack.back().type != TokenType::OPERAND) ||
stack.size() == reduceUntil) {
if(t.expr->op == Expr::Op::MINUS) {
t.type = TokenType::UNARY_OP;
t.expr->op = Expr::Op::NEGATE;
stack.push_back(t);
break;
}
}
while(stack.size() > 1 + reduceUntil &&
Precedence(t) <= Precedence(stack[stack.size() - 2])) {
if(!Reduce(error)) return false;
}
stack.push_back(t);
break;
case TokenType::UNARY_OP:
case TokenType::OPERAND:
stack.push_back(t);
break;
}
}
return true;
}
Expr *ExprParser::Parse(const char *input, std::string *error) {
ExprParser parser;
parser.input = input;
parser.inputPos = 0;
if(!parser.Parse(error)) return NULL;
Token r = parser.PopOperand(error);
if(r.IsError()) return NULL;
return r.expr;
}
Expr *Expr::Parse(const char *input, std::string *error) {
return ExprParser::Parse(input, error);
}
Expr *Expr::From(const char *input, bool popUpError) {
std::string error;
Expr *e = ExprParser::Parse(input, &error);
if(!e) {
dbp("Parse/lex error: %s", error.c_str());
if(popUpError) {
Error("Not a valid number or expression: '%s'.\n%s.", input, error.c_str());
}
}
return e;
}
+140
View File
@@ -0,0 +1,140 @@
//-----------------------------------------------------------------------------
// An expression in our symbolic algebra system, used to write, linearize,
// and solve our constraint equations.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#ifndef __EXPR_H
#define __EXPR_H
class Expr {
public:
enum class Op : uint32_t {
// A parameter, by the hParam handle
PARAM = 0,
// A parameter, by a pointer straight in to the param table (faster,
// if we know that the param table won't move around)
PARAM_PTR = 1,
// Operands
CONSTANT = 20,
VARIABLE = 21,
// Binary ops
PLUS = 100,
MINUS = 101,
TIMES = 102,
DIV = 103,
// Unary ops
NEGATE = 104,
SQRT = 105,
SQUARE = 106,
SIN = 107,
COS = 108,
ASIN = 109,
ACOS = 110,
};
Op op;
Expr *a;
union {
double v;
hParam parh;
Param *parp;
Expr *b;
};
Expr() { }
Expr(double val) : op(Op::CONSTANT) { v = val; }
static inline Expr *AllocExpr()
{ return (Expr *)AllocTemporary(sizeof(Expr)); }
static Expr *From(hParam p);
static Expr *From(double v);
Expr *AnyOp(Op op, Expr *b);
inline Expr *Plus (Expr *b_) { return AnyOp(Op::PLUS, b_); }
inline Expr *Minus(Expr *b_) { return AnyOp(Op::MINUS, b_); }
inline Expr *Times(Expr *b_) { return AnyOp(Op::TIMES, b_); }
inline Expr *Div (Expr *b_) { return AnyOp(Op::DIV, b_); }
inline Expr *Negate() { return AnyOp(Op::NEGATE, NULL); }
inline Expr *Sqrt () { return AnyOp(Op::SQRT, NULL); }
inline Expr *Square() { return AnyOp(Op::SQUARE, NULL); }
inline Expr *Sin () { return AnyOp(Op::SIN, NULL); }
inline Expr *Cos () { return AnyOp(Op::COS, NULL); }
inline Expr *ASin () { return AnyOp(Op::ASIN, NULL); }
inline Expr *ACos () { return AnyOp(Op::ACOS, NULL); }
Expr *PartialWrt(hParam p) const;
double Eval() const;
uint64_t ParamsUsed() const;
bool DependsOn(hParam p) const;
static bool Tol(double a, double b);
Expr *FoldConstants();
void Substitute(hParam oldh, hParam newh);
static const hParam NO_PARAMS, MULTIPLE_PARAMS;
hParam ReferencedParams(ParamList *pl) const;
void ParamsToPointers();
std::string Print() const;
// number of child nodes: 0 (e.g. constant), 1 (sqrt), or 2 (+)
int Children() const;
// total number of nodes in the tree
int Nodes() const;
// Make a simple copy
Expr *DeepCopy() const;
// Make a copy, with the parameters (usually referenced by hParam)
// resolved to pointers to the actual value. This speeds things up
// considerably.
Expr *DeepCopyWithParamsAsPointers(IdList<Param,hParam> *firstTry,
IdList<Param,hParam> *thenTry) const;
static Expr *Parse(const char *input, std::string *error);
static Expr *From(const char *in, bool popUpError);
};
class ExprVector {
public:
Expr *x, *y, *z;
static ExprVector From(Expr *x, Expr *y, Expr *z);
static ExprVector From(Vector vn);
static ExprVector From(hParam x, hParam y, hParam z);
static ExprVector From(double x, double y, double z);
ExprVector Plus(ExprVector b) const;
ExprVector Minus(ExprVector b) const;
Expr *Dot(ExprVector b) const;
ExprVector Cross(ExprVector b) const;
ExprVector ScaledBy(Expr *s) const;
ExprVector WithMagnitude(Expr *s) const;
Expr *Magnitude() const;
Vector Eval() const;
};
class ExprQuaternion {
public:
Expr *w, *vx, *vy, *vz;
static ExprQuaternion From(Expr *w, Expr *vx, Expr *vy, Expr *vz);
static ExprQuaternion From(Quaternion qn);
static ExprQuaternion From(hParam w, hParam vx, hParam vy, hParam vz);
ExprVector RotationU() const;
ExprVector RotationV() const;
ExprVector RotationN() const;
ExprVector Rotate(ExprVector p) const;
ExprQuaternion Times(ExprQuaternion b) const;
Expr *Magnitude() const;
};
#endif
@@ -0,0 +1,52 @@
#pragma once
#include "slvs.h"
#include <valarray>
#include <vector>
#include <tuple>
class DLL SolveSpaceSystem
{
public:
SolveSpaceSystem();
Slvs_hParam addParam(Slvs_Param parameter);
Slvs_hEntity addEntity(Slvs_Entity entity);
Slvs_hConstraint addConstr(Slvs_Constraint constr);
enum ResultStatus {
RESULT_OKAY = SLVS_RESULT_OKAY ,
RESULT_INCONSISTENT = SLVS_RESULT_INCONSISTENT ,
RESULT_DIDNT_CONVERGE = SLVS_RESULT_DIDNT_CONVERGE ,
RESULT_TOO_MANY_UNKNOWNS = SLVS_RESULT_TOO_MANY_UNKNOWNS,
};
ResultStatus solve(Slvs_hGroup groupId, bool reportFailedConstraints = true);
double parameterValue(Slvs_hParam paramId);
void setParameterValue(Slvs_hParam paramId, double value);
std::tuple< std::valarray<double>,
std::valarray<double>,
std::valarray<double> >
orientationMx(Slvs_hEntity normalIn3dEntityId);
// Returns point as x, y, z values
std::valarray<double> global3DPos (Slvs_hEntity pointEntityId);
Slvs_Constraint & constraint(Slvs_hConstraint constraintId);
std::vector<Slvs_hConstraint> failedConstraints() const;
private:
Slvs_System m_slvsSystem;
std::vector<Slvs_Param> * m_paramsMemory;
std::vector<Slvs_Entity> * m_entityMemory;
std::vector<Slvs_Constraint> * m_constraintMemory;
std::vector<Slvs_hConstraint>* m_failedConstrMemory;
};
+409
View File
@@ -0,0 +1,409 @@
/*-----------------------------------------------------------------------------
* Data structures and prototypes for slvs.lib, a geometric constraint solver.
*
* See the comments in this file, the accompanying sample code that uses
* this library, and the accompanying documentation (DOC.txt).
*
* Copyright 2009-2013 Jonathan Westhues.
*---------------------------------------------------------------------------*/
#ifndef __SLVS_H
#define __SLVS_H
#ifdef SLVS_LIB_SHARED
#ifdef WIN32
# ifdef EXPORT_DLL
# define DLL __declspec( dllexport )
# else
# define DLL __declspec( dllimport )
# endif
#else
# define DLL
#endif
#else
# define DLL
#endif
#ifdef __cplusplus
extern "C" {
#endif
#ifdef _MSC_VER
typedef unsigned __int32 uint32_t;
#else
#include <stdint.h>
#endif
#include <string.h>
typedef uint32_t Slvs_hParam;
typedef uint32_t Slvs_hEntity;
typedef uint32_t Slvs_hConstraint;
typedef uint32_t Slvs_hGroup;
/* To obtain the 3d (not projected into a workplane) of a constraint or
* an entity, specify this instead of the workplane. */
#define SLVS_FREE_IN_3D 0
typedef struct {
Slvs_hParam h;
Slvs_hGroup group;
double val;
} Slvs_Param;
#define SLVS_E_POINT_IN_3D 50000
#define SLVS_E_POINT_IN_2D 50001
#define SLVS_E_NORMAL_IN_3D 60000
#define SLVS_E_NORMAL_IN_2D 60001
#define SLVS_E_DISTANCE 70000
/* The special point, normal, and distance types used for parametric step
* and repeat, extrude, and assembly are currently not exposed. Please
* contact us if you are interested in using these. */
#define SLVS_E_WORKPLANE 80000
#define SLVS_E_LINE_SEGMENT 80001
#define SLVS_E_CUBIC 80002
#define SLVS_E_CIRCLE 80003
#define SLVS_E_ARC_OF_CIRCLE 80004
typedef struct {
Slvs_hEntity h;
Slvs_hGroup group;
int type;
Slvs_hEntity wrkpl;
Slvs_hEntity point[4];
Slvs_hEntity normal;
Slvs_hEntity distance;
Slvs_hParam param[4];
} Slvs_Entity;
#define SLVS_C_POINTS_COINCIDENT 100000
#define SLVS_C_PT_PT_DISTANCE 100001
#define SLVS_C_PT_PLANE_DISTANCE 100002
#define SLVS_C_PT_LINE_DISTANCE 100003
#define SLVS_C_PT_FACE_DISTANCE 100004
#define SLVS_C_PT_IN_PLANE 100005
#define SLVS_C_PT_ON_LINE 100006
#define SLVS_C_PT_ON_FACE 100007
#define SLVS_C_EQUAL_LENGTH_LINES 100008
#define SLVS_C_LENGTH_RATIO 100009
#define SLVS_C_EQ_LEN_PT_LINE_D 100010
#define SLVS_C_EQ_PT_LN_DISTANCES 100011
#define SLVS_C_EQUAL_ANGLE 100012
#define SLVS_C_EQUAL_LINE_ARC_LEN 100013
#define SLVS_C_SYMMETRIC 100014
#define SLVS_C_SYMMETRIC_HORIZ 100015
#define SLVS_C_SYMMETRIC_VERT 100016
#define SLVS_C_SYMMETRIC_LINE 100017
#define SLVS_C_AT_MIDPOINT 100018
#define SLVS_C_HORIZONTAL 100019
#define SLVS_C_VERTICAL 100020
#define SLVS_C_DIAMETER 100021
#define SLVS_C_PT_ON_CIRCLE 100022
#define SLVS_C_SAME_ORIENTATION 100023
#define SLVS_C_ANGLE 100024
#define SLVS_C_PARALLEL 100025
#define SLVS_C_PERPENDICULAR 100026
#define SLVS_C_ARC_LINE_TANGENT 100027
#define SLVS_C_CUBIC_LINE_TANGENT 100028
#define SLVS_C_EQUAL_RADIUS 100029
#define SLVS_C_PROJ_PT_DISTANCE 100030
#define SLVS_C_WHERE_DRAGGED 100031
#define SLVS_C_CURVE_CURVE_TANGENT 100032
#define SLVS_C_LENGTH_DIFFERENCE 100033
typedef struct {
Slvs_hConstraint h;
Slvs_hGroup group;
int type;
Slvs_hEntity wrkpl;
double valA;
Slvs_hEntity ptA;
Slvs_hEntity ptB;
Slvs_hEntity entityA;
Slvs_hEntity entityB;
Slvs_hEntity entityC;
Slvs_hEntity entityD;
int other;
int other2;
} Slvs_Constraint;
typedef struct {
/*** INPUT VARIABLES
*
* Here, we specify the parameters and their initial values, the entities,
* and the constraints. For example, param[] points to the array of
* parameters, which has length params, so that the last valid element
* is param[params-1].
*
* param[] is actually an in/out variable; if the solver is successful,
* then the new values (that satisfy the constraints) are written to it. */
Slvs_Param *param;
int params;
Slvs_Entity *entity;
int entities;
Slvs_Constraint *constraint;
int constraints;
/* If a parameter corresponds to a point (distance, normal, etc.) being
* dragged, then specify it here. This will cause the solver to favor
* that parameter, and attempt to change it as little as possible even
* if that requires it to change other parameters more.
*
* Unused members of this array should be set to zero. */
Slvs_hParam dragged[4];
/* If the solver fails, then it can determine which constraints are
* causing the problem. But this is a relatively slow process (for
* a system with n constraints, about n times as long as just solving).
* If calculateFaileds is true, then the solver will do so, otherwise
* not. */
int calculateFaileds;
/*** OUTPUT VARIABLES
*
* If the solver fails, then it can report which constraints are causing
* the problem. The caller should allocate the array failed[], and pass
* its size in faileds.
*
* The solver will set faileds equal to the number of problematic
* constraints, and write their Slvs_hConstraints into failed[]. To
* ensure that there is sufficient space for any possible set of
* failing constraints, faileds should be greater than or equal to
* constraints. */
Slvs_hConstraint *failed;
int faileds;
/* The solver indicates the number of unconstrained degrees of freedom. */
int dof;
/* The solver indicates whether the solution succeeded. */
#define SLVS_RESULT_OKAY 0
#define SLVS_RESULT_INCONSISTENT 1
#define SLVS_RESULT_DIDNT_CONVERGE 2
#define SLVS_RESULT_TOO_MANY_UNKNOWNS 3
int result;
} Slvs_System;
DLL void Slvs_Solve(Slvs_System *sys, Slvs_hGroup hg);
/* Our base coordinate system has basis vectors
* (1, 0, 0) (0, 1, 0) (0, 0, 1)
* A unit quaternion defines a rotation to a new coordinate system with
* basis vectors
* U V N
* which these functions compute from the quaternion. */
DLL void Slvs_QuaternionU(double qw, double qx, double qy, double qz,
double *x, double *y, double *z);
DLL void Slvs_QuaternionV(double qw, double qx, double qy, double qz,
double *x, double *y, double *z);
DLL void Slvs_QuaternionN(double qw, double qx, double qy, double qz,
double *x, double *y, double *z);
/* Similarly, compute a unit quaternion in terms of two basis vectors. */
DLL void Slvs_MakeQuaternion(double ux, double uy, double uz,
double vx, double vy, double vz,
double *qw, double *qx, double *qy, double *qz);
/*-------------------------------------
* These are just convenience functions, to save you the trouble of filling
* out the structures by hand. The code is included in the header file to
* let the compiler inline them if possible. */
static inline Slvs_Param Slvs_MakeParam(Slvs_hParam h, Slvs_hGroup group, double val)
{
Slvs_Param r;
r.h = h;
r.group = group;
r.val = val;
return r;
}
static inline Slvs_Entity Slvs_MakePoint2d(Slvs_hEntity h, Slvs_hGroup group,
Slvs_hEntity wrkpl,
Slvs_hParam u, Slvs_hParam v)
{
Slvs_Entity r;
memset(&r, 0, sizeof(r));
r.h = h;
r.group = group;
r.type = SLVS_E_POINT_IN_2D;
r.wrkpl = wrkpl;
r.param[0] = u;
r.param[1] = v;
return r;
}
static inline Slvs_Entity Slvs_MakePoint3d(Slvs_hEntity h, Slvs_hGroup group,
Slvs_hParam x, Slvs_hParam y, Slvs_hParam z)
{
Slvs_Entity r;
memset(&r, 0, sizeof(r));
r.h = h;
r.group = group;
r.type = SLVS_E_POINT_IN_3D;
r.wrkpl = SLVS_FREE_IN_3D;
r.param[0] = x;
r.param[1] = y;
r.param[2] = z;
return r;
}
static inline Slvs_Entity Slvs_MakeNormal3d(Slvs_hEntity h, Slvs_hGroup group,
Slvs_hParam qw, Slvs_hParam qx,
Slvs_hParam qy, Slvs_hParam qz)
{
Slvs_Entity r;
memset(&r, 0, sizeof(r));
r.h = h;
r.group = group;
r.type = SLVS_E_NORMAL_IN_3D;
r.wrkpl = SLVS_FREE_IN_3D;
r.param[0] = qw;
r.param[1] = qx;
r.param[2] = qy;
r.param[3] = qz;
return r;
}
static inline Slvs_Entity Slvs_MakeNormal2d(Slvs_hEntity h, Slvs_hGroup group,
Slvs_hEntity wrkpl)
{
Slvs_Entity r;
memset(&r, 0, sizeof(r));
r.h = h;
r.group = group;
r.type = SLVS_E_NORMAL_IN_2D;
r.wrkpl = wrkpl;
return r;
}
static inline Slvs_Entity Slvs_MakeDistance(Slvs_hEntity h, Slvs_hGroup group,
Slvs_hEntity wrkpl, Slvs_hParam d)
{
Slvs_Entity r;
memset(&r, 0, sizeof(r));
r.h = h;
r.group = group;
r.type = SLVS_E_DISTANCE;
r.wrkpl = wrkpl;
r.param[0] = d;
return r;
}
static inline Slvs_Entity Slvs_MakeLineSegment(Slvs_hEntity h, Slvs_hGroup group,
Slvs_hEntity wrkpl,
Slvs_hEntity ptA, Slvs_hEntity ptB)
{
Slvs_Entity r;
memset(&r, 0, sizeof(r));
r.h = h;
r.group = group;
r.type = SLVS_E_LINE_SEGMENT;
r.wrkpl = wrkpl;
r.point[0] = ptA;
r.point[1] = ptB;
return r;
}
static inline Slvs_Entity Slvs_MakeCubic(Slvs_hEntity h, Slvs_hGroup group,
Slvs_hEntity wrkpl,
Slvs_hEntity pt0, Slvs_hEntity pt1,
Slvs_hEntity pt2, Slvs_hEntity pt3)
{
Slvs_Entity r;
memset(&r, 0, sizeof(r));
r.h = h;
r.group = group;
r.type = SLVS_E_CUBIC;
r.wrkpl = wrkpl;
r.point[0] = pt0;
r.point[1] = pt1;
r.point[2] = pt2;
r.point[3] = pt3;
return r;
}
static inline Slvs_Entity Slvs_MakeArcOfCircle(Slvs_hEntity h, Slvs_hGroup group,
Slvs_hEntity wrkpl,
Slvs_hEntity normal,
Slvs_hEntity center,
Slvs_hEntity start, Slvs_hEntity end)
{
Slvs_Entity r;
memset(&r, 0, sizeof(r));
r.h = h;
r.group = group;
r.type = SLVS_E_ARC_OF_CIRCLE;
r.wrkpl = wrkpl;
r.normal = normal;
r.point[0] = center;
r.point[1] = start;
r.point[2] = end;
return r;
}
static inline Slvs_Entity Slvs_MakeCircle(Slvs_hEntity h, Slvs_hGroup group,
Slvs_hEntity wrkpl,
Slvs_hEntity center,
Slvs_hEntity normal, Slvs_hEntity radius)
{
Slvs_Entity r;
memset(&r, 0, sizeof(r));
r.h = h;
r.group = group;
r.type = SLVS_E_CIRCLE;
r.wrkpl = wrkpl;
r.point[0] = center;
r.normal = normal;
r.distance = radius;
return r;
}
static inline Slvs_Entity Slvs_MakeWorkplane(Slvs_hEntity h, Slvs_hGroup group,
Slvs_hEntity origin, Slvs_hEntity normal)
{
Slvs_Entity r;
memset(&r, 0, sizeof(r));
r.h = h;
r.group = group;
r.type = SLVS_E_WORKPLANE;
r.wrkpl = SLVS_FREE_IN_3D;
r.point[0] = origin;
r.normal = normal;
return r;
}
static inline Slvs_Constraint Slvs_MakeConstraint(Slvs_hConstraint h,
Slvs_hGroup group,
int type,
Slvs_hEntity wrkpl,
double valA,
Slvs_hEntity ptA,
Slvs_hEntity ptB,
Slvs_hEntity entityA,
Slvs_hEntity entityB)
{
Slvs_Constraint r;
memset(&r, 0, sizeof(r));
r.h = h;
r.group = group;
r.type = type;
r.wrkpl = wrkpl;
r.valA = valA;
r.ptA = ptA;
r.ptB = ptB;
r.entityA = entityA;
r.entityB = entityB;
return r;
}
#ifdef __cplusplus
}
#endif
#endif
+272
View File
@@ -0,0 +1,272 @@
//-----------------------------------------------------------------------------
// A library wrapper around SolveSpace, to permit someone to use its constraint
// solver without coupling their program too much to SolveSpace's internals.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
#define EXPORT_DLL
#include <slvs.h>
Sketch SolveSpace::SK = {};
static System SYS;
static int IsInit = 0;
void Group::GenerateEquations(IdList<Equation,hEquation> *) {
// Nothing to do for now.
}
void SolveSpace::CnfFreezeInt(uint32_t, const std::string &)
{
abort();
}
uint32_t SolveSpace::CnfThawInt(uint32_t, const std::string &)
{
abort();
return 0;
}
void SolveSpace::DoMessageBox(const char *, int, int, bool)
{
abort();
}
extern "C" {
void Slvs_QuaternionU(double qw, double qx, double qy, double qz,
double *x, double *y, double *z)
{
Quaternion q = Quaternion::From(qw, qx, qy, qz);
Vector v = q.RotationU();
*x = v.x;
*y = v.y;
*z = v.z;
}
void Slvs_QuaternionV(double qw, double qx, double qy, double qz,
double *x, double *y, double *z)
{
Quaternion q = Quaternion::From(qw, qx, qy, qz);
Vector v = q.RotationV();
*x = v.x;
*y = v.y;
*z = v.z;
}
void Slvs_QuaternionN(double qw, double qx, double qy, double qz,
double *x, double *y, double *z)
{
Quaternion q = Quaternion::From(qw, qx, qy, qz);
Vector v = q.RotationN();
*x = v.x;
*y = v.y;
*z = v.z;
}
void Slvs_MakeQuaternion(double ux, double uy, double uz,
double vx, double vy, double vz,
double *qw, double *qx, double *qy, double *qz)
{
Vector u = Vector::From(ux, uy, uz),
v = Vector::From(vx, vy, vz);
Quaternion q = Quaternion::From(u, v);
*qw = q.w;
*qx = q.vx;
*qy = q.vy;
*qz = q.vz;
}
void Slvs_Solve(Slvs_System *ssys, Slvs_hGroup shg)
{
if(!IsInit) {
InitPlatform(0, NULL);
IsInit = 1;
}
int i;
for(i = 0; i < ssys->params; i++) {
Slvs_Param *sp = &(ssys->param[i]);
Param p = {};
p.h.v = sp->h;
p.val = sp->val;
SK.param.Add(&p);
if(sp->group == shg) {
SYS.param.Add(&p);
}
}
for(i = 0; i < ssys->entities; i++) {
Slvs_Entity *se = &(ssys->entity[i]);
EntityBase e = {};
switch(se->type) {
case SLVS_E_POINT_IN_3D: e.type = Entity::Type::POINT_IN_3D; break;
case SLVS_E_POINT_IN_2D: e.type = Entity::Type::POINT_IN_2D; break;
case SLVS_E_NORMAL_IN_3D: e.type = Entity::Type::NORMAL_IN_3D; break;
case SLVS_E_NORMAL_IN_2D: e.type = Entity::Type::NORMAL_IN_2D; break;
case SLVS_E_DISTANCE: e.type = Entity::Type::DISTANCE; break;
case SLVS_E_WORKPLANE: e.type = Entity::Type::WORKPLANE; break;
case SLVS_E_LINE_SEGMENT: e.type = Entity::Type::LINE_SEGMENT; break;
case SLVS_E_CUBIC: e.type = Entity::Type::CUBIC; break;
case SLVS_E_CIRCLE: e.type = Entity::Type::CIRCLE; break;
case SLVS_E_ARC_OF_CIRCLE: e.type = Entity::Type::ARC_OF_CIRCLE; break;
default: dbp("bad entity type %d", se->type); return;
}
e.h.v = se->h;
e.group.v = se->group;
e.workplane.v = se->wrkpl;
e.point[0].v = se->point[0];
e.point[1].v = se->point[1];
e.point[2].v = se->point[2];
e.point[3].v = se->point[3];
e.normal.v = se->normal;
e.distance.v = se->distance;
e.param[0].v = se->param[0];
e.param[1].v = se->param[1];
e.param[2].v = se->param[2];
e.param[3].v = se->param[3];
SK.entity.Add(&e);
}
IdList<Param, hParam> params = {};
for(i = 0; i < ssys->constraints; i++) {
Slvs_Constraint *sc = &(ssys->constraint[i]);
ConstraintBase c = {};
Constraint::Type t;
switch(sc->type) {
case SLVS_C_POINTS_COINCIDENT: t = Constraint::Type::POINTS_COINCIDENT; break;
case SLVS_C_PT_PT_DISTANCE: t = Constraint::Type::PT_PT_DISTANCE; break;
case SLVS_C_PT_PLANE_DISTANCE: t = Constraint::Type::PT_PLANE_DISTANCE; break;
case SLVS_C_PT_LINE_DISTANCE: t = Constraint::Type::PT_LINE_DISTANCE; break;
case SLVS_C_PT_FACE_DISTANCE: t = Constraint::Type::PT_FACE_DISTANCE; break;
case SLVS_C_PT_IN_PLANE: t = Constraint::Type::PT_IN_PLANE; break;
case SLVS_C_PT_ON_LINE: t = Constraint::Type::PT_ON_LINE; break;
case SLVS_C_PT_ON_FACE: t = Constraint::Type::PT_ON_FACE; break;
case SLVS_C_EQUAL_LENGTH_LINES: t = Constraint::Type::EQUAL_LENGTH_LINES; break;
case SLVS_C_LENGTH_RATIO: t = Constraint::Type::LENGTH_RATIO; break;
case SLVS_C_EQ_LEN_PT_LINE_D: t = Constraint::Type::EQ_LEN_PT_LINE_D; break;
case SLVS_C_EQ_PT_LN_DISTANCES: t = Constraint::Type::EQ_PT_LN_DISTANCES; break;
case SLVS_C_EQUAL_ANGLE: t = Constraint::Type::EQUAL_ANGLE; break;
case SLVS_C_EQUAL_LINE_ARC_LEN: t = Constraint::Type::EQUAL_LINE_ARC_LEN; break;
case SLVS_C_LENGTH_DIFFERENCE: t = Constraint::Type::LENGTH_DIFFERENCE; break;
case SLVS_C_SYMMETRIC: t = Constraint::Type::SYMMETRIC; break;
case SLVS_C_SYMMETRIC_HORIZ: t = Constraint::Type::SYMMETRIC_HORIZ; break;
case SLVS_C_SYMMETRIC_VERT: t = Constraint::Type::SYMMETRIC_VERT; break;
case SLVS_C_SYMMETRIC_LINE: t = Constraint::Type::SYMMETRIC_LINE; break;
case SLVS_C_AT_MIDPOINT: t = Constraint::Type::AT_MIDPOINT; break;
case SLVS_C_HORIZONTAL: t = Constraint::Type::HORIZONTAL; break;
case SLVS_C_VERTICAL: t = Constraint::Type::VERTICAL; break;
case SLVS_C_DIAMETER: t = Constraint::Type::DIAMETER; break;
case SLVS_C_PT_ON_CIRCLE: t = Constraint::Type::PT_ON_CIRCLE; break;
case SLVS_C_SAME_ORIENTATION: t = Constraint::Type::SAME_ORIENTATION; break;
case SLVS_C_ANGLE: t = Constraint::Type::ANGLE; break;
case SLVS_C_PARALLEL: t = Constraint::Type::PARALLEL; break;
case SLVS_C_PERPENDICULAR: t = Constraint::Type::PERPENDICULAR; break;
case SLVS_C_ARC_LINE_TANGENT: t = Constraint::Type::ARC_LINE_TANGENT; break;
case SLVS_C_CUBIC_LINE_TANGENT: t = Constraint::Type::CUBIC_LINE_TANGENT; break;
case SLVS_C_EQUAL_RADIUS: t = Constraint::Type::EQUAL_RADIUS; break;
case SLVS_C_PROJ_PT_DISTANCE: t = Constraint::Type::PROJ_PT_DISTANCE; break;
case SLVS_C_WHERE_DRAGGED: t = Constraint::Type::WHERE_DRAGGED; break;
case SLVS_C_CURVE_CURVE_TANGENT:t = Constraint::Type::CURVE_CURVE_TANGENT; break;
default: dbp("bad constraint type %d", sc->type); return;
}
c.type = t;
c.h.v = sc->h;
c.group.v = sc->group;
c.workplane.v = sc->wrkpl;
c.valA = sc->valA;
c.ptA.v = sc->ptA;
c.ptB.v = sc->ptB;
c.entityA.v = sc->entityA;
c.entityB.v = sc->entityB;
c.entityC.v = sc->entityC;
c.entityD.v = sc->entityD;
c.other = (sc->other) ? true : false;
c.other2 = (sc->other2) ? true : false;
c.Generate(&params);
if(params.n > 0) {
for(Param &p : params) {
p.h = SK.param.AddAndAssignId(&p);
c.valP = p.h;
SYS.param.Add(&p);
}
params.Clear();
c.ModifyToSatisfy();
}
SK.constraint.Add(&c);
}
for(i = 0; i < (int)arraylen(ssys->dragged); i++) {
if(ssys->dragged[i]) {
hParam hp = { ssys->dragged[i] };
SYS.dragged.Add(&hp);
}
}
Group g = {};
g.h.v = shg;
List<hConstraint> bad = {};
// Now we're finally ready to solve!
bool andFindBad = ssys->calculateFaileds ? true : false;
SolveResult how = SYS.Solve(&g, &(ssys->dof), &bad, andFindBad, /*andFindFree=*/false);
switch(how) {
case SolveResult::OKAY:
ssys->result = SLVS_RESULT_OKAY;
break;
case SolveResult::DIDNT_CONVERGE:
ssys->result = SLVS_RESULT_DIDNT_CONVERGE;
break;
case SolveResult::REDUNDANT_DIDNT_CONVERGE:
case SolveResult::REDUNDANT_OKAY:
ssys->result = SLVS_RESULT_INCONSISTENT;
break;
case SolveResult::TOO_MANY_UNKNOWNS:
ssys->result = SLVS_RESULT_TOO_MANY_UNKNOWNS;
break;
}
// Write the new parameter values back to our caller.
for(i = 0; i < ssys->params; i++) {
Slvs_Param *sp = &(ssys->param[i]);
hParam hp = { sp->h };
sp->val = SK.GetParam(hp)->val;
}
if(ssys->failed) {
// Copy over any the list of problematic constraints.
for(i = 0; i < ssys->faileds && i < bad.n; i++) {
ssys->failed[i] = bad.elem[i].v;
}
ssys->faileds = bad.n;
}
bad.Clear();
SYS.param.Clear();
SYS.entity.Clear();
SYS.eq.Clear();
SYS.dragged.Clear();
SK.param.Clear();
SK.entity.Clear();
SK.constraint.Clear();
FreeAllTemporary();
}
} /* extern "C" */
+68
View File
@@ -0,0 +1,68 @@
//-----------------------------------------------------------------------------
// Platform-dependent functionality.
//
// Copyright 2017 whitequark
//-----------------------------------------------------------------------------
#ifndef SOLVESPACE_PLATFORM_H
#define SOLVESPACE_PLATFORM_H
namespace Platform {
// UTF-8 ⟷ UTF-16 conversion, for Windows.
#if defined(WIN32)
std::string Narrow(const wchar_t *s);
std::wstring Widen(const char *s);
std::string Narrow(const std::wstring &s);
std::wstring Widen(const std::string &s);
#endif
// A filesystem path, respecting the conventions of the current platform.
// Transformation functions return an empty path on error.
class Path {
public:
std::string raw;
static Path From(std::string raw);
static Path CurrentDirectory();
void Clear() { raw.clear(); }
bool Equals(const Path &other) const;
bool IsEmpty() const { return raw.empty(); }
bool IsAbsolute() const;
bool HasExtension(std::string ext) const;
std::string FileName() const;
std::string FileStem() const;
std::string Extension() const;
Path WithExtension(std::string ext) const;
Path Parent() const;
Path Join(const std::string &component) const;
Path Join(const Path &other) const;
Path Expand(bool fromCurrentDirectory = false) const;
Path RelativeTo(const Path &base) const;
// Converting to and from a platform-independent representation
// (conventionally, the Unix one).
static Path FromPortable(const std::string &repr);
std::string ToPortable() const;
};
struct PathLess {
bool operator()(const Path &a, const Path &b) const { return a.raw < b.raw; }
};
// File manipulation functions.
FILE *OpenFile(const Platform::Path &filename, const char *mode);
bool ReadFile(const Platform::Path &filename, std::string *data);
bool WriteFile(const Platform::Path &filename, const std::string &data);
void RemoveFile(const Platform::Path &filename);
// Resource loading function.
const void *LoadResource(const std::string &name, size_t *size);
}
#endif
+124
View File
@@ -0,0 +1,124 @@
//-----------------------------------------------------------------------------
// Utility functions used by the Unix port. Notably, our memory allocation;
// we use two separate allocators, one for long-lived stuff and one for
// stuff that gets freed after every regeneration of the model, to save us
// the trouble of freeing the latter explicitly.
//
// Copyright 2008-2013 Jonathan Westhues.
// Copyright 2013 Daniel Richard G. <skunk@iSKUNK.ORG>
//-----------------------------------------------------------------------------
#ifndef LIBRARY
#include <execinfo.h>
#endif
#include "solvespace.h"
namespace SolveSpace {
void dbp(const char *str, ...)
{
va_list f;
static char buf[1024*50];
va_start(f, str);
vsnprintf(buf, sizeof(buf), str, f);
va_end(f);
fputs(buf, stderr);
fputc('\n', stderr);
}
void assert_failure(const char *file, unsigned line, const char *function,
const char *condition, const char *message) {
fprintf(stderr, "File %s, line %u, function %s:\n", file, line, function);
fprintf(stderr, "Assertion '%s' failed: ((%s) == false).\n", message, condition);
#ifndef LIBRARY
static void *ptrs[1024] = {};
size_t nptrs = backtrace(ptrs, sizeof(ptrs) / sizeof(ptrs[0]));
char **syms = backtrace_symbols(ptrs, nptrs);
fprintf(stderr, "Backtrace:\n");
if(syms != NULL) {
for(size_t i = 0; i < nptrs; i++) {
fprintf(stderr, "%2zu: %s\n", i, syms[i]);
}
} else {
for(size_t i = 0; i < nptrs; i++) {
fprintf(stderr, "%2zu: %p\n", i, ptrs[i]);
}
}
#endif
abort();
}
//-----------------------------------------------------------------------------
// A separate heap, on which we allocate expressions. Maybe a bit faster,
// since fragmentation is less of a concern, and it also makes it possible
// to be sloppy with our memory management, and just free everything at once
// at the end.
//-----------------------------------------------------------------------------
typedef struct _AllocTempHeader AllocTempHeader;
typedef struct _AllocTempHeader {
AllocTempHeader *prev;
AllocTempHeader *next;
} AllocTempHeader;
static AllocTempHeader *Head = NULL;
void *AllocTemporary(size_t n)
{
AllocTempHeader *h =
(AllocTempHeader *)malloc(n + sizeof(AllocTempHeader));
h->prev = NULL;
h->next = Head;
if(Head) Head->prev = h;
Head = h;
memset(&h[1], 0, n);
return (void *)&h[1];
}
void FreeTemporary(void *p)
{
AllocTempHeader *h = (AllocTempHeader *)p - 1;
if(h->prev) {
h->prev->next = h->next;
} else {
Head = h->next;
}
if(h->next) h->next->prev = h->prev;
free(h);
}
void FreeAllTemporary(void)
{
AllocTempHeader *h = Head;
while(h) {
AllocTempHeader *f = h;
h = h->next;
free(f);
}
Head = NULL;
}
void *MemAlloc(size_t n) {
void *p = malloc(n);
ssassert(p != NULL, "Cannot allocate memory");
return p;
}
void MemFree(void *p) {
free(p);
}
std::vector<std::string> InitPlatform(int argc, char **argv) {
std::vector<std::string> args;
for(int i = 0; i < argc; i++) {
args.push_back(argv[i]);
}
return args;
}
};
+415
View File
@@ -0,0 +1,415 @@
//-----------------------------------------------------------------------------
// Anything relating to plane polygons and triangles, and (generally, non-
// planar) meshes thereof.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#ifndef __POLYGON_H
#define __POLYGON_H
class SPointList;
class SPolygon;
class SContour;
class SMesh;
class SBsp3;
class SOutlineList;
enum class EarType : uint32_t {
UNKNOWN = 0,
NOT_EAR = 1,
EAR = 2
};
enum class BspClass : uint32_t {
POS = 100,
NEG = 101,
COPLANAR = 200
};
enum class EdgeKind : uint32_t {
NAKED_OR_SELF_INTER = 100,
SELF_INTER = 200,
TURNING = 300,
EMPHASIZED = 400,
SHARP = 500,
};
class SEdge {
public:
int tag;
int auxA, auxB;
Vector a, b;
static SEdge From(Vector a, Vector b);
bool EdgeCrosses(Vector a, Vector b, Vector *pi=NULL, SPointList *spl=NULL) const;
};
class SEdgeList {
public:
List<SEdge> l;
void Clear();
void AddEdge(Vector a, Vector b, int auxA=0, int auxB=0, int tag=0);
bool AssemblePolygon(SPolygon *dest, SEdge *errorAt, bool keepDir=false) const;
bool AssembleContour(Vector first, Vector last, SContour *dest,
SEdge *errorAt, bool keepDir) const;
int AnyEdgeCrossings(Vector a, Vector b,
Vector *pi=NULL, SPointList *spl=NULL) const;
bool ContainsEdgeFrom(const SEdgeList *sel) const;
bool ContainsEdge(const SEdge *se) const;
void CullExtraneousEdges();
void MergeCollinearSegments(Vector a, Vector b);
};
// A kd-tree element needs to go on a side of a node if it's when KDTREE_EPS
// of the boundary. So increasing this number never breaks anything, but may
// result in more duplicated elements. So it's conservative to be sloppy here.
#define KDTREE_EPS (20*LENGTH_EPS)
class SEdgeLl {
public:
SEdge *se;
SEdgeLl *next;
static SEdgeLl *Alloc();
};
class SKdNodeEdges {
public:
int which; // whether c is x, y, or z
double c;
SKdNodeEdges *gt;
SKdNodeEdges *lt;
SEdgeLl *edges;
static SKdNodeEdges *From(SEdgeList *sel);
static SKdNodeEdges *From(SEdgeLl *sell);
static SKdNodeEdges *Alloc();
int AnyEdgeCrossings(Vector a, Vector b, int cnt,
Vector *pi=NULL, SPointList *spl=NULL) const;
};
class SPoint {
public:
int tag;
EarType ear;
Vector p;
Vector auxv;
};
class SPointList {
public:
List<SPoint> l;
void Clear();
bool ContainsPoint(Vector pt) const;
int IndexForPoint(Vector pt) const;
void IncrementTagFor(Vector pt);
void Add(Vector pt);
};
class SContour {
public:
int tag;
int timesEnclosed;
Vector xminPt;
List<SPoint> l;
void AddPoint(Vector p);
void MakeEdgesInto(SEdgeList *el) const;
void Reverse();
Vector ComputeNormal() const;
double SignedAreaProjdToNormal(Vector n) const;
bool IsClockwiseProjdToNormal(Vector n) const;
bool ContainsPointProjdToNormal(Vector n, Vector p) const;
void OffsetInto(SContour *dest, double r) const;
void CopyInto(SContour *dest) const;
void FindPointWithMinX();
Vector AnyEdgeMidpoint() const;
bool IsEar(int bp, double scaledEps) const;
bool BridgeToContour(SContour *sc, SEdgeList *el, List<Vector> *vl);
void ClipEarInto(SMesh *m, int bp, double scaledEps);
void UvTriangulateInto(SMesh *m, SSurface *srf);
};
typedef struct {
uint32_t face;
RgbaColor color;
} STriMeta;
class SPolygon {
public:
List<SContour> l;
Vector normal;
Vector ComputeNormal() const;
void AddEmptyContour();
int WindingNumberForPoint(Vector p) const;
double SignedArea() const;
bool ContainsPoint(Vector p) const;
void MakeEdgesInto(SEdgeList *el) const;
void FixContourDirections();
void Clear();
bool SelfIntersecting(Vector *intersectsAt) const;
bool IsEmpty() const;
Vector AnyPoint() const;
void OffsetInto(SPolygon *dest, double r) const;
void UvTriangulateInto(SMesh *m, SSurface *srf);
void UvGridTriangulateInto(SMesh *m, SSurface *srf);
void TriangulateInto(SMesh *m) const;
void InverseTransformInto(SPolygon *sp, Vector u, Vector v, Vector n) const;
};
class STriangle {
public:
int tag;
STriMeta meta;
union {
struct { Vector a, b, c; };
Vector vertices[3];
};
union {
struct { Vector an, bn, cn; };
Vector normals[3];
};
static STriangle From(STriMeta meta, Vector a, Vector b, Vector c);
Vector Normal() const;
void FlipNormal();
double MinAltitude() const;
int WindingNumberForPoint(Vector p) const;
bool ContainsPoint(Vector p) const;
bool ContainsPointProjd(Vector n, Vector p) const;
STriangle Transform(Vector o, Vector u, Vector v) const;
bool Raytrace(const Vector &rayPoint, const Vector &rayDir,
double *t, Vector *inters) const;
double SignedVolume() const;
};
class SBsp2 {
public:
Vector np; // normal to the plane
Vector no; // outer normal to the edge
double d;
SEdge edge;
SBsp2 *pos;
SBsp2 *neg;
SBsp2 *more;
void InsertTriangleHow(BspClass how, STriangle *tr, SMesh *m, SBsp3 *bsp3);
void InsertTriangle(STriangle *tr, SMesh *m, SBsp3 *bsp3);
Vector IntersectionWith(Vector a, Vector b) const;
void InsertEdge(SEdge *nedge, Vector nnp, Vector out);
static SBsp2 *InsertOrCreateEdge(SBsp2 *where, SEdge *nedge,
Vector nnp, Vector out);
static SBsp2 *Alloc();
};
class SBsp3 {
public:
Vector n;
double d;
STriangle tri;
SBsp3 *pos;
SBsp3 *neg;
SBsp3 *more;
SBsp2 *edges;
static SBsp3 *Alloc();
static SBsp3 *FromMesh(const SMesh *m);
Vector IntersectionWith(Vector a, Vector b) const;
void InsertHow(BspClass how, STriangle *str, SMesh *instead);
void Insert(STriangle *str, SMesh *instead);
static SBsp3 *InsertOrCreate(SBsp3 *where, STriangle *str, SMesh *instead);
void InsertConvexHow(BspClass how, STriMeta meta, Vector *vertex, size_t n,
SMesh *instead);
SBsp3 *InsertConvex(STriMeta meta, Vector *vertex, size_t n, SMesh *instead);
void InsertInPlane(bool pos2, STriangle *tr, SMesh *m);
void GenerateInPaintOrder(SMesh *m) const;
};
class SMesh {
public:
List<STriangle> l;
bool flipNormal;
bool keepCoplanar;
bool atLeastOneDiscarded;
bool isTransparent;
void Clear();
void AddTriangle(const STriangle *st);
void AddTriangle(STriMeta meta, Vector a, Vector b, Vector c);
void AddTriangle(STriMeta meta, Vector n,
Vector a, Vector b, Vector c);
void DoBounding(Vector v, Vector *vmax, Vector *vmin) const;
void GetBounding(Vector *vmax, Vector *vmin) const;
void Simplify(int start);
void AddAgainstBsp(SMesh *srcm, SBsp3 *bsp3);
void MakeFromUnionOf(SMesh *a, SMesh *b);
void MakeFromDifferenceOf(SMesh *a, SMesh *b);
void MakeFromCopyOf(SMesh *a);
void MakeFromTransformationOf(SMesh *a, Vector trans,
Quaternion q, double scale);
void MakeFromAssemblyOf(SMesh *a, SMesh *b);
void MakeEdgesInPlaneInto(SEdgeList *sel, Vector n, double d);
void MakeOutlinesInto(SOutlineList *sol, EdgeKind type);
void PrecomputeTransparency();
void RemoveDegenerateTriangles();
bool IsEmpty() const;
void RemapFaces(Group *g, int remap);
uint32_t FirstIntersectionWith(Point2d mp) const;
Vector GetCenterOfMass() const;
};
// A linked list of triangles
class STriangleLl {
public:
STriangle *tri;
STriangleLl *next;
static STriangleLl *Alloc();
};
class SOutline {
public:
int tag;
Vector a, b, nl, nr;
bool IsVisible(Vector projDir) const;
};
class SOutlineList {
public:
List<SOutline> l;
void Clear();
void AddEdge(Vector a, Vector b, Vector nl, Vector nr, int tag = 0);
void ListTaggedInto(SEdgeList *el, int auxA = 0, int auxB = 0);
void MakeFromCopyOf(SOutlineList *ol);
};
class SKdNode {
public:
struct EdgeOnInfo {
int count;
bool frontFacing;
bool intersectsMesh;
STriangle *tr;
int ai;
int bi;
};
int which; // whether c is x, y, or z
double c;
SKdNode *gt;
SKdNode *lt;
STriangleLl *tris;
static SKdNode *Alloc();
static SKdNode *From(SMesh *m);
static SKdNode *From(STriangleLl *tll);
void AddTriangle(STriangle *tr);
void MakeMeshInto(SMesh *m) const;
void ListTrianglesInto(std::vector<STriangle *> *tl) const;
void ClearTags() const;
void FindEdgeOn(Vector a, Vector b, int cnt, bool coplanarIsInter, EdgeOnInfo *info) const;
void MakeCertainEdgesInto(SEdgeList *sel, EdgeKind how, bool coplanarIsInter,
bool *inter, bool *leaky, int auxA = 0) const;
void MakeOutlinesInto(SOutlineList *sel, EdgeKind tagKind) const;
void OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt) const;
void SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr) const;
void SnapToMesh(SMesh *m);
void SnapToVertex(Vector v, SMesh *extras);
};
class PolylineBuilder {
public:
struct Edge;
struct Vertex {
Vector pos;
std::vector<Edge *> edges;
bool GetNext(uint32_t kind, Vertex **next, Edge **nextEdge);
bool GetNext(uint32_t kind, Vector plane, double d, Vertex **next, Edge **nextEdge);
size_t CountEdgesWithTagAndKind(int tag, uint32_t kind) const;
};
struct VertexPairHash {
size_t operator()(const std::pair<Vertex *, Vertex *> &v) const;
};
struct Edge {
Vertex *a;
Vertex *b;
uint32_t kind;
int tag;
union {
uintptr_t data;
SOutline *outline;
SEdge *edge;
};
Vertex *GetOtherVertex(Vertex *v) const;
bool GetStartAndNext(Vertex **start, Vertex **next, bool loop) const;
};
std::unordered_map<Vector, Vertex *, VectorHash, VectorPred> vertices;
std::unordered_map<std::pair<Vertex *, Vertex *>, Edge *, VertexPairHash> edgeMap;
std::vector<Edge *> edges;
~PolylineBuilder();
void Clear();
Vertex *AddVertex(const Vector &pos);
Edge *AddEdge(const Vector &p0, const Vector &p1, uint32_t kind, uintptr_t data = 0);
void Generate(
std::function<void(Vertex *start, Vertex *next, Edge *edge)> startFunc,
std::function<void(Vertex *next, Edge *edge)> nextFunc,
std::function<void(Edge *)> aloneFunc,
std::function<void()> endFunc = [](){});
void MakeFromEdges(const SEdgeList &sel);
void MakeFromOutlines(const SOutlineList &sol);
void GenerateEdges(SEdgeList *sel);
void GenerateOutlines(SOutlineList *sol);
};
#endif
+366
View File
@@ -0,0 +1,366 @@
//-----------------------------------------------------------------------------
// Backend-agnostic rendering interface, and various backends we use.
//
// Copyright 2016 whitequark
//-----------------------------------------------------------------------------
#ifndef SOLVESPACE_RENDER_H
#define SOLVESPACE_RENDER_H
//-----------------------------------------------------------------------------
// Interfaces and utilities common for all renderers.
//-----------------------------------------------------------------------------
enum class StipplePattern : uint32_t;
// A mapping from 3d sketch coordinates to 2d screen coordinates, using
// an axonometric projection.
class Camera {
public:
size_t width, height;
Vector offset;
Vector projRight;
Vector projUp;
double scale;
double tangent;
bool hasPixels;
bool IsPerspective() const { return tangent != 0.0; }
Point2d ProjectPoint(Vector p) const;
Vector ProjectPoint3(Vector p) const;
Vector ProjectPoint4(Vector p, double *w) const;
Vector UnProjectPoint(Point2d p) const;
Vector UnProjectPoint3(Vector p) const;
Vector VectorFromProjs(Vector rightUpForward) const;
Vector AlignToPixelGrid(Vector v) const;
SBezier ProjectBezier(SBezier b) const;
void LoadIdentity();
void NormalizeProjectionVectors();
};
// A description of scene lighting.
class Lighting {
public:
RgbaColor backgroundColor;
double ambientIntensity;
double lightIntensity[2];
Vector lightDirection[2];
};
class BatchCanvas;
// An interface for populating a drawing area with geometry.
class Canvas {
public:
// Stroke and fill styles are addressed with handles to be able to quickly
// group geometry into indexed draw calls.
class hStroke {
public:
uint32_t v;
};
class hFill {
public:
uint32_t v;
};
// The layer of a geometry describes how it occludes other geometry.
// Within a layer, geometry with higher z-index occludes geometry with lower z-index,
// or geometry drawn earlier if z-indexes match.
enum class Layer {
NORMAL, // Occluded by geometry with lower Z coordinate
OCCLUDED, // Only drawn over geometry with lower Z coordinate
DEPTH_ONLY, // Like NORMAL, but only affects future occlusion, not color
BACK, // Always drawn below all other geometry
FRONT, // Always drawn above all other geometry
LAST = FRONT
};
// The outlines are the collection of all edges that may be drawn.
// Outlines can be classified as emphasized or not; emphasized outlines indicate an abrupt
// change in the surface curvature. These are indicated by the SOutline tag.
// Outlines can also be classified as contour or not; contour outlines indicate the boundary
// of the filled mesh. Whether an outline is a part of contour or not depends on point of view.
enum class DrawOutlinesAs {
EMPHASIZED_AND_CONTOUR = 0, // Both emphasized and contour outlines
EMPHASIZED_WITHOUT_CONTOUR = 1, // Emphasized outlines except those also belonging to contour
CONTOUR_ONLY = 2 // Contour outlines only
};
// Stroke widths, etc, can be scale-invariant (in pixels) or scale-dependent (in millimeters).
enum class Unit {
MM,
PX
};
class Stroke {
public:
hStroke h;
Layer layer;
int zIndex;
RgbaColor color;
double width;
Unit unit;
StipplePattern stipplePattern;
double stippleScale;
void Clear() { *this = {}; }
bool Equals(const Stroke &other) const;
double WidthMm(const Camera &camera) const;
double WidthPx(const Camera &camera) const;
double StippleScaleMm(const Camera &camera) const;
double StippleScalePx(const Camera &camera) const;
};
enum class FillPattern {
SOLID, CHECKERED_A, CHECKERED_B
};
class Fill {
public:
hFill h;
Layer layer;
int zIndex;
RgbaColor color;
FillPattern pattern;
std::shared_ptr<const Pixmap> texture;
void Clear() { *this = {}; }
bool Equals(const Fill &other) const;
};
IdList<Stroke, hStroke> strokes;
IdList<Fill, hFill> fills;
BitmapFont bitmapFont;
Canvas() : strokes(), fills(), bitmapFont() {}
virtual void Clear();
hStroke GetStroke(const Stroke &stroke);
hFill GetFill(const Fill &fill);
BitmapFont *GetBitmapFont();
virtual const Camera &GetCamera() const = 0;
virtual void DrawLine(const Vector &a, const Vector &b, hStroke hcs) = 0;
virtual void DrawEdges(const SEdgeList &el, hStroke hcs) = 0;
virtual bool DrawBeziers(const SBezierList &bl, hStroke hcs) = 0;
virtual void DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs drawAs) = 0;
virtual void DrawVectorText(const std::string &text, double height,
const Vector &o, const Vector &u, const Vector &v,
hStroke hcs) = 0;
virtual void DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d,
hFill hcf) = 0;
virtual void DrawPoint(const Vector &o, hStroke hcs) = 0;
virtual void DrawPolygon(const SPolygon &p, hFill hcf) = 0;
virtual void DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack = {}) = 0;
virtual void DrawFaces(const SMesh &m, const std::vector<uint32_t> &faces, hFill hcf) = 0;
virtual void DrawPixmap(std::shared_ptr<const Pixmap> pm,
const Vector &o, const Vector &u, const Vector &v,
const Point2d &ta, const Point2d &tb, hFill hcf) = 0;
virtual void InvalidatePixmap(std::shared_ptr<const Pixmap> pm) = 0;
virtual std::shared_ptr<BatchCanvas> CreateBatch();
};
// An interface for view-dependent visualization.
class ViewportCanvas : public Canvas {
public:
virtual void SetCamera(const Camera &camera) = 0;
virtual void SetLighting(const Lighting &lighting) = 0;
virtual void NewFrame() = 0;
virtual void FlushFrame() = 0;
virtual std::shared_ptr<Pixmap> ReadFrame() = 0;
virtual void GetIdent(const char **vendor, const char **renderer, const char **version) = 0;
};
// An interface for view-independent visualization.
class BatchCanvas : public Canvas {
public:
const Camera &GetCamera() const override;
virtual void Finalize() = 0;
virtual void Draw() = 0;
};
// A wrapper around Canvas that simplifies drawing UI in screen coordinates.
class UiCanvas {
public:
std::shared_ptr<Canvas> canvas;
bool flip;
void DrawLine(int x1, int y1, int x2, int y2, RgbaColor color, int width = 1,
int zIndex = 0);
void DrawRect(int l, int r, int t, int b, RgbaColor fillColor, RgbaColor outlineColor,
int zIndex = 0);
void DrawPixmap(std::shared_ptr<const Pixmap> pm, int x, int y,
int zIndex = 0);
void DrawBitmapChar(char32_t codepoint, int x, int y, RgbaColor color,
int zIndex = 0);
void DrawBitmapText(const std::string &str, int x, int y, RgbaColor color,
int zIndex = 0);
int Flip(int y) const { return flip ? (int)canvas->GetCamera().height - y : y; }
};
// A canvas that performs picking against drawn geometry.
class ObjectPicker : public Canvas {
public:
Camera camera;
// Configuration.
Point2d point;
double selRadius;
// Picking state.
double minDistance;
int maxZIndex;
uint32_t position;
ObjectPicker() : camera(), point(), selRadius(),
minDistance(), maxZIndex(), position() {}
const Camera &GetCamera() const override { return camera; }
void DrawLine(const Vector &a, const Vector &b, hStroke hcs) override;
void DrawEdges(const SEdgeList &el, hStroke hcs) override;
bool DrawBeziers(const SBezierList &bl, hStroke hcs) override { return false; }
void DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs drawAs) override;
void DrawVectorText(const std::string &text, double height,
const Vector &o, const Vector &u, const Vector &v,
hStroke hcs) override;
void DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d,
hFill hcf) override;
void DrawPoint(const Vector &o, hStroke hcs) override;
void DrawPolygon(const SPolygon &p, hFill hcf) override;
void DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack) override;
void DrawFaces(const SMesh &m, const std::vector<uint32_t> &faces, hFill hcf) override;
void DrawPixmap(std::shared_ptr<const Pixmap> pm,
const Vector &o, const Vector &u, const Vector &v,
const Point2d &ta, const Point2d &tb, hFill hcf) override;
void InvalidatePixmap(std::shared_ptr<const Pixmap> pm) override {}
void DoCompare(double distance, int zIndex, int comparePosition = 0);
void DoQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d,
int zIndex, int comparePosition = 0);
bool Pick(std::function<void()> drawFn);
};
// A canvas that renders onto a 2d surface, performing z-index sorting, occlusion testing, etc,
// on the CPU.
class SurfaceRenderer : public Canvas {
public:
Camera camera;
Lighting lighting;
// Chord tolerance, for converting beziers to pwl.
double chordTolerance;
// Render lists.
handle_map<hStroke, SEdgeList> edges;
handle_map<hStroke, SBezierList> beziers;
SMesh mesh;
// State.
BBox bbox;
SurfaceRenderer() : camera(), lighting(), chordTolerance(), mesh(), bbox() {}
void Clear() override;
// Canvas interface.
const Camera &GetCamera() const override { return camera; }
void DrawLine(const Vector &a, const Vector &b, hStroke hcs) override;
void DrawEdges(const SEdgeList &el, hStroke hcs) override;
bool DrawBeziers(const SBezierList &bl, hStroke hcs) override;
void DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs drawAs) override;
void DrawVectorText(const std::string &text, double height,
const Vector &o, const Vector &u, const Vector &v,
hStroke hcs) override;
void DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d,
hFill hcf) override;
void DrawPoint(const Vector &o, hStroke hcs) override;
void DrawPolygon(const SPolygon &p, hFill hcf) override;
void DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack) override;
void DrawFaces(const SMesh &m, const std::vector<uint32_t> &faces, hFill hcf) override;
void DrawPixmap(std::shared_ptr<const Pixmap> pm,
const Vector &o, const Vector &u, const Vector &v,
const Point2d &ta, const Point2d &tb, hFill hcf) override;
void InvalidatePixmap(std::shared_ptr<const Pixmap> pm) override;
// Geometry manipulation.
void CalculateBBox();
void ConvertBeziersToEdges();
void CullOccludedStrokes();
// Renderer operations.
void OutputInPaintOrder();
virtual bool CanOutputCurves() const = 0;
virtual bool CanOutputTriangles() const = 0;
virtual void OutputStart() = 0;
virtual void OutputBezier(const SBezier &b, hStroke hcs) = 0;
virtual void OutputTriangle(const STriangle &tr) = 0;
virtual void OutputEnd() = 0;
void OutputBezierAsNonrationalCubic(const SBezier &b, hStroke hcs);
};
//-----------------------------------------------------------------------------
// 2d renderers.
//-----------------------------------------------------------------------------
class CairoRenderer : public SurfaceRenderer {
public:
cairo_t *context;
// Renderer configuration.
bool antialias;
// Renderer state.
struct {
hStroke hcs;
} current;
CairoRenderer() : context(), current() {}
void SelectStroke(hStroke hcs);
void MoveTo(Vector p);
void FinishPath();
bool CanOutputCurves() const override { return true; }
bool CanOutputTriangles() const override { return true; }
void OutputStart() override;
void OutputBezier(const SBezier &b, hStroke hcs) override;
void OutputTriangle(const STriangle &tr) override;
void OutputEnd() override;
};
//-----------------------------------------------------------------------------
// 3d renderers.
//-----------------------------------------------------------------------------
// An offscreen renderer based on OpenGL framebuffers.
class GlOffscreen {
public:
unsigned int framebuffer;
unsigned int colorRenderbuffer, depthRenderbuffer;
std::vector<uint8_t> data;
bool Render(int width, int height, std::function<void()> renderFn);
void Clear();
};
std::shared_ptr<ViewportCanvas> CreateRenderer();
#endif
+109
View File
@@ -0,0 +1,109 @@
//-----------------------------------------------------------------------------
// Discovery and loading of our resources (icons, fonts, templates, etc).
//
// Copyright 2016 whitequark
//-----------------------------------------------------------------------------
#ifndef __RESOURCE_H
#define __RESOURCE_H
class Camera;
class Point2d;
class Pixmap;
class Vector;
std::string LoadString(const std::string &name);
std::string LoadStringFromGzip(const std::string &name);
std::shared_ptr<Pixmap> LoadPng(const std::string &name);
class Pixmap {
public:
enum class Format { BGRA, RGBA, BGR, RGB, A };
Format format;
size_t width;
size_t height;
size_t stride;
std::vector<uint8_t> data;
static std::shared_ptr<Pixmap> Create(Format format, size_t width, size_t height);
static std::shared_ptr<Pixmap> FromPng(const uint8_t *data, size_t size, bool flip = false);
static std::shared_ptr<Pixmap> ReadPng(FILE *f, bool flip = false);
static std::shared_ptr<Pixmap> ReadPng(const Platform::Path &filename, bool flip = false);
bool WritePng(FILE *f, bool flip = false);
bool WritePng(const Platform::Path &filename, bool flip = false);
size_t GetBytesPerPixel() const;
RgbaColor GetPixel(size_t x, size_t y) const;
bool Equals(const Pixmap &other) const;
void ConvertTo(Format newFormat);
void SetPixel(size_t x, size_t y, RgbaColor color);
};
class BitmapFont {
public:
struct Glyph {
uint8_t advanceCells;
uint16_t position;
};
std::string unifontData;
std::map<char32_t, Glyph> glyphs;
std::shared_ptr<Pixmap> texture;
bool textureUpdated;
uint16_t nextPosition;
static BitmapFont From(std::string &&unifontData);
static BitmapFont Create();
bool IsEmpty() const { return unifontData.empty(); }
const Glyph &GetGlyph(char32_t codepoint);
void LocateGlyph(char32_t codepoint, double *s0, double *t0, double *s1, double *t1,
size_t *advanceWidth, size_t *boundingHeight);
void AddGlyph(char32_t codepoint, std::shared_ptr<const Pixmap> pixmap);
size_t GetWidth(char32_t codepoint);
size_t GetWidth(const std::string &str);
};
class VectorFont {
public:
struct Contour {
std::vector<Point2d> points;
};
struct Glyph {
std::vector<Contour> contours;
double leftSideBearing;
double boundingWidth;
double advanceWidth;
};
std::string lffData;
std::map<char32_t, Glyph> glyphs;
double rightSideBearing;
double capHeight;
double ascender;
double descender;
static VectorFont From(std::string &&lffData);
static VectorFont *Builtin();
bool IsEmpty() const { return lffData.empty(); }
const Glyph &GetGlyph(char32_t codepoint);
double GetCapHeight(double forCapHeight) const;
double GetHeight(double forCapHeight) const;
double GetWidth(double forCapHeight, const std::string &str);
Vector GetExtents(double forCapHeight, const std::string &str);
void Trace(double forCapHeight, Vector o, Vector u, Vector v, const std::string &str,
std::function<void(Vector, Vector)> traceEdge);
void Trace(double forCapHeight, Vector o, Vector u, Vector v, const std::string &str,
std::function<void(Vector, Vector)> traceEdge, const Camera &camera);
};
#endif
+926
View File
@@ -0,0 +1,926 @@
//-----------------------------------------------------------------------------
// The parametric structure of our sketch, in multiple groups, that generate
// geometric entities and surfaces.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#ifndef __SKETCH_H
#define __SKETCH_H
class hGroup;
class hRequest;
class hEntity;
class hParam;
class hStyle;
class hConstraint;
class hEquation;
class Entity;
class Param;
class Equation;
class Style;
enum class PolyError : uint32_t {
GOOD = 0,
NOT_CLOSED = 1,
NOT_COPLANAR = 2,
SELF_INTERSECTING = 3,
ZERO_LEN_EDGE = 4
};
enum class StipplePattern : uint32_t {
CONTINUOUS = 0,
SHORT_DASH = 1,
DASH = 2,
LONG_DASH = 3,
DASH_DOT = 4,
DASH_DOT_DOT = 5,
DOT = 6,
FREEHAND = 7,
ZIGZAG = 8,
LAST = ZIGZAG
};
const std::vector<double> &StipplePatternDashes(StipplePattern pattern);
double StipplePatternLength(StipplePattern pattern);
enum class Command : uint32_t;
// All of the hWhatever handles are a 32-bit ID, that is used to represent
// some data structure in the sketch.
class hGroup {
public:
// bits 15: 0 -- group index
uint32_t v;
inline hEntity entity(int i) const;
inline hParam param(int i) const;
inline hEquation equation(int i) const;
};
class hRequest {
public:
// bits 15: 0 -- request index
uint32_t v;
inline hEntity entity(int i) const;
inline hParam param(int i) const;
inline bool IsFromReferences() const;
};
class hEntity {
public:
// bits 15: 0 -- entity index
// 31:16 -- request index
uint32_t v;
inline bool isFromRequest() const;
inline hRequest request() const;
inline hGroup group() const;
inline hEquation equation(int i) const;
};
class hParam {
public:
// bits 15: 0 -- param index
// 31:16 -- request index
uint32_t v;
inline hRequest request() const;
};
class hStyle {
public:
uint32_t v;
};
class EntityId {
public:
uint32_t v; // entity ID, starting from 0
};
class EntityMap {
public:
int tag;
EntityId h;
hEntity input;
int copyNumber;
// (input, copyNumber) gets mapped to ((Request)xxx).entity(h.v)
void Clear() {}
};
// A set of requests. Every request must have an associated group.
class Group {
public:
static const hGroup HGROUP_REFERENCES;
int tag;
hGroup h;
enum class CopyAs {
NUMERIC,
N_TRANS,
N_ROT_AA,
N_ROT_TRANS,
};
enum class Type : uint32_t {
DRAWING_3D = 5000,
DRAWING_WORKPLANE = 5001,
EXTRUDE = 5100,
LATHE = 5101,
ROTATE = 5200,
TRANSLATE = 5201,
LINKED = 5300
};
Group::Type type;
int order;
hGroup opA;
hGroup opB;
bool visible;
bool suppress;
bool relaxConstraints;
bool allowRedundant;
bool allDimsReference;
double scale;
bool clean;
bool dofCheckOk;
hEntity activeWorkplane;
double valA;
double valB;
double valC;
RgbaColor color;
struct {
SolveResult how;
int dof;
List<hConstraint> remove;
} solved;
enum class Subtype : uint32_t {
// For drawings in 2d
WORKPLANE_BY_POINT_ORTHO = 6000,
WORKPLANE_BY_LINE_SEGMENTS = 6001,
// For extrudes, translates, and rotates
ONE_SIDED = 7000,
TWO_SIDED = 7001
};
Group::Subtype subtype;
bool skipFirst; // for step and repeat ops
struct {
Quaternion q;
hEntity origin;
hEntity entityB;
hEntity entityC;
bool swapUV;
bool negateU;
bool negateV;
} predef;
SPolygon polyLoops;
SBezierLoopSetSet bezierLoops;
SBezierList bezierOpens;
struct {
PolyError how;
SEdge notClosedAt;
Vector errorPointAt;
} polyError;
bool booleanFailed;
SShell thisShell;
SShell runningShell;
SMesh thisMesh;
SMesh runningMesh;
bool displayDirty;
SMesh displayMesh;
SOutlineList displayOutlines;
enum class CombineAs : uint32_t {
UNION = 0,
DIFFERENCE = 1,
ASSEMBLE = 2
};
CombineAs meshCombine;
bool forceToMesh;
IdList<EntityMap,EntityId> remap;
enum { REMAP_PRIME = 19477 };
int remapCache[REMAP_PRIME];
Platform::Path linkFile;
SMesh impMesh;
SShell impShell;
EntityList impEntity;
std::string name;
void Activate();
std::string DescriptionString();
void Clear();
static void AddParam(ParamList *param, hParam hp, double v);
void Generate(EntityList *entity, ParamList *param);
bool IsSolvedOkay();
void TransformImportedBy(Vector t, Quaternion q);
bool IsForcedToMeshBySource() const;
bool IsForcedToMesh() const;
// When a request generates entities from entities, and the source
// entities may have come from multiple requests, it's necessary to
// remap the entity ID so that it's still unique. We do this with a
// mapping list.
enum {
REMAP_LAST = 1000,
REMAP_TOP = 1001,
REMAP_BOTTOM = 1002,
REMAP_PT_TO_LINE = 1003,
REMAP_LINE_TO_FACE = 1004,
REMAP_LATHE_START = 1006,
REMAP_LATHE_END = 1007,
REMAP_PT_TO_ARC = 1008,
REMAP_PT_TO_NORMAL = 1009,
};
hEntity Remap(hEntity in, int copyNumber);
void MakeExtrusionLines(EntityList *el, hEntity in);
void MakeLatheCircles(IdList<Entity,hEntity> *el, IdList<Param,hParam> *param, hEntity in, Vector pt, Vector axis, int ai);
void MakeExtrusionTopBottomFaces(EntityList *el, hEntity pt);
void CopyEntity(EntityList *el,
Entity *ep, int timesApplied, int remap,
hParam dx, hParam dy, hParam dz,
hParam qw, hParam qvx, hParam qvy, hParam qvz,
CopyAs as);
void AddEq(IdList<Equation,hEquation> *l, Expr *expr, int index);
void GenerateEquations(IdList<Equation,hEquation> *l);
bool IsVisible();
int GetNumConstraints();
Vector ExtrusionGetVector();
void ExtrusionForceVectorTo(const Vector &v);
// Assembling the curves into loops, and into a piecewise linear polygon
// at the same time.
void AssembleLoops(bool *allClosed, bool *allCoplanar, bool *allNonZeroLen);
void GenerateLoops();
// And the mesh stuff
Group *PreviousGroup() const;
Group *RunningMeshGroup() const;
bool IsMeshGroup();
void GenerateShellAndMesh();
template<class T> void GenerateForStepAndRepeat(T *steps, T *outs, Group::CombineAs forWhat);
template<class T> void GenerateForBoolean(T *a, T *b, T *o, Group::CombineAs how);
void GenerateDisplayItems();
enum class DrawMeshAs { DEFAULT, HOVERED, SELECTED };
void DrawMesh(DrawMeshAs how, Canvas *canvas);
void Draw(Canvas *canvas);
void DrawPolyError(Canvas *canvas);
void DrawFilledPaths(Canvas *canvas);
void DrawContourAreaLabels(Canvas *canvas);
SPolygon GetPolygon();
static void MenuGroup(Command id);
};
// A user request for some primitive or derived operation; for example a
// line, or a step and repeat.
class Request {
public:
// Some predefined requests, that are present in every sketch.
static const hRequest HREQUEST_REFERENCE_XY;
static const hRequest HREQUEST_REFERENCE_YZ;
static const hRequest HREQUEST_REFERENCE_ZX;
int tag;
hRequest h;
// Types of requests
enum class Type : uint32_t {
WORKPLANE = 100,
DATUM_POINT = 101,
LINE_SEGMENT = 200,
CUBIC = 300,
CUBIC_PERIODIC = 301,
CIRCLE = 400,
ARC_OF_CIRCLE = 500,
TTF_TEXT = 600,
IMAGE = 700
};
Request::Type type;
int extraPoints;
hEntity workplane; // or Entity::FREE_IN_3D
hGroup group;
hStyle style;
bool construction;
std::string str;
std::string font;
Platform::Path file;
double aspectRatio;
static hParam AddParam(ParamList *param, hParam hp);
void Generate(EntityList *entity, ParamList *param);
std::string DescriptionString() const;
int IndexOfPoint(hEntity he) const;
void Clear() {}
};
#define MAX_POINTS_IN_ENTITY (12)
class EntityBase {
public:
int tag;
hEntity h;
static const hEntity FREE_IN_3D;
static const hEntity NO_ENTITY;
enum class Type : uint32_t {
POINT_IN_3D = 2000,
POINT_IN_2D = 2001,
POINT_N_TRANS = 2010,
POINT_N_ROT_TRANS = 2011,
POINT_N_COPY = 2012,
POINT_N_ROT_AA = 2013,
NORMAL_IN_3D = 3000,
NORMAL_IN_2D = 3001,
NORMAL_N_COPY = 3010,
NORMAL_N_ROT = 3011,
NORMAL_N_ROT_AA = 3012,
DISTANCE = 4000,
DISTANCE_N_COPY = 4001,
FACE_NORMAL_PT = 5000,
FACE_XPROD = 5001,
FACE_N_ROT_TRANS = 5002,
FACE_N_TRANS = 5003,
FACE_N_ROT_AA = 5004,
WORKPLANE = 10000,
LINE_SEGMENT = 11000,
CUBIC = 12000,
CUBIC_PERIODIC = 12001,
CIRCLE = 13000,
ARC_OF_CIRCLE = 14000,
TTF_TEXT = 15000,
IMAGE = 16000
};
Type type;
hGroup group;
hEntity workplane; // or Entity::FREE_IN_3D
// When it comes time to draw an entity, we look here to get the
// defining variables.
hEntity point[MAX_POINTS_IN_ENTITY];
int extraPoints;
hEntity normal;
hEntity distance;
// The only types that have their own params are points, normals,
// and directions.
hParam param[7];
// Transformed points/normals/distances have their numerical base
Vector numPoint;
Quaternion numNormal;
double numDistance;
std::string str;
std::string font;
Platform::Path file;
double aspectRatio;
// For entities that are derived by a transformation, the number of
// times to apply the transformation.
int timesApplied;
Quaternion GetAxisAngleQuaternion(int param0) const;
ExprQuaternion GetAxisAngleQuaternionExprs(int param0) const;
bool IsCircle() const;
Expr *CircleGetRadiusExpr() const;
double CircleGetRadiusNum() const;
void ArcGetAngles(double *thetaa, double *thetab, double *dtheta) const;
bool HasVector() const;
ExprVector VectorGetExprs() const;
ExprVector VectorGetExprsInWorkplane(hEntity wrkpl) const;
Vector VectorGetNum() const;
Vector VectorGetRefPoint() const;
Vector VectorGetStartPoint() const;
// For distances
bool IsDistance() const;
double DistanceGetNum() const;
Expr *DistanceGetExpr() const;
void DistanceForceTo(double v);
bool IsWorkplane() const;
// The plane is points P such that P dot (xn, yn, zn) - d = 0
void WorkplaneGetPlaneExprs(ExprVector *n, Expr **d) const;
ExprVector WorkplaneGetOffsetExprs() const;
Vector WorkplaneGetOffset() const;
EntityBase *Normal() const;
bool IsFace() const;
ExprVector FaceGetNormalExprs() const;
Vector FaceGetNormalNum() const;
ExprVector FaceGetPointExprs() const;
Vector FaceGetPointNum() const;
bool IsPoint() const;
// Applies for any of the point types
Vector PointGetNum() const;
ExprVector PointGetExprs() const;
void PointGetExprsInWorkplane(hEntity wrkpl, Expr **u, Expr **v) const;
ExprVector PointGetExprsInWorkplane(hEntity wrkpl) const;
void PointForceTo(Vector v);
void PointForceParamTo(Vector v);
// These apply only the POINT_N_ROT_TRANS, which has an assoc rotation
Quaternion PointGetQuaternion() const;
void PointForceQuaternionTo(Quaternion q);
bool IsNormal() const;
// Applies for any of the normal types
Quaternion NormalGetNum() const;
ExprQuaternion NormalGetExprs() const;
void NormalForceTo(Quaternion q);
Vector NormalU() const;
Vector NormalV() const;
Vector NormalN() const;
ExprVector NormalExprsU() const;
ExprVector NormalExprsV() const;
ExprVector NormalExprsN() const;
Vector CubicGetStartNum() const;
Vector CubicGetFinishNum() const;
ExprVector CubicGetStartTangentExprs() const;
ExprVector CubicGetFinishTangentExprs() const;
Vector CubicGetStartTangentNum() const;
Vector CubicGetFinishTangentNum() const;
bool HasEndpoints() const;
Vector EndpointStart() const;
Vector EndpointFinish() const;
void RectGetPointsExprs(ExprVector *eap, ExprVector *ebp) const;
void AddEq(IdList<Equation,hEquation> *l, Expr *expr, int index) const;
void GenerateEquations(IdList<Equation,hEquation> *l) const;
void Clear() {}
};
class Entity : public EntityBase {
public:
// Necessary for Entity e = {} to zero-initialize, since
// classes with base classes are not aggregates and
// the default constructor does not initialize members.
//
// Note EntityBase({}); without explicitly value-initializing
// the base class, MSVC2013 will default-initialize it, leaving
// POD members with indeterminate value.
Entity() : EntityBase({}), forceHidden(), actPoint(), actNormal(),
actDistance(), actVisible(), style(), construction(),
beziers(), edges(), edgesChordTol(), screenBBox(), screenBBoxValid() {};
// A linked entity that was hidden in the source file ends up hidden
// here too.
bool forceHidden;
// All points/normals/distances have their numerical value; this is
// a convenience, to simplify the link/assembly code, so that the
// part is entirely described by the entities.
Vector actPoint;
Quaternion actNormal;
double actDistance;
// and the shown state also gets saved here, for later import
bool actVisible;
hStyle style;
bool construction;
SBezierList beziers;
SEdgeList edges;
double edgesChordTol;
BBox screenBBox;
bool screenBBoxValid;
bool IsStylable() const;
bool IsVisible() const;
enum class DrawAs { DEFAULT, OVERLAY, HIDDEN, HOVERED, SELECTED };
void Draw(DrawAs how, Canvas *canvas);
void GetReferencePoints(std::vector<Vector> *refs);
int GetPositionOfPoint(const Camera &camera, Point2d p);
void ComputeInterpolatingSpline(SBezierList *sbl, bool periodic) const;
void GenerateBezierCurves(SBezierList *sbl) const;
void GenerateEdges(SEdgeList *el);
SBezierList *GetOrGenerateBezierCurves();
SEdgeList *GetOrGenerateEdges();
BBox GetOrGenerateScreenBBox(bool *hasBBox);
void CalculateNumerical(bool forExport);
std::string DescriptionString() const;
void Clear() {
beziers.l.Clear();
edges.l.Clear();
}
};
class EntReqTable {
public:
static bool GetRequestInfo(Request::Type req, int extraPoints,
EntityBase::Type *ent, int *pts, bool *hasNormal, bool *hasDistance);
static bool GetEntityInfo(EntityBase::Type ent, int extraPoints,
Request::Type *req, int *pts, bool *hasNormal, bool *hasDistance);
static Request::Type GetRequestForEntity(EntityBase::Type ent);
};
class Param {
public:
int tag;
hParam h;
double val;
bool known;
bool free;
// Used only in the solver
hParam substd;
static const hParam NO_PARAM;
void Clear() {}
};
class hConstraint {
public:
uint32_t v;
inline hEquation equation(int i) const;
inline hParam param(int i) const;
};
class ConstraintBase {
public:
int tag;
hConstraint h;
static const hConstraint NO_CONSTRAINT;
enum class Type : uint32_t {
POINTS_COINCIDENT = 20,
PT_PT_DISTANCE = 30,
PT_PLANE_DISTANCE = 31,
PT_LINE_DISTANCE = 32,
PT_FACE_DISTANCE = 33,
PROJ_PT_DISTANCE = 34,
PT_IN_PLANE = 41,
PT_ON_LINE = 42,
PT_ON_FACE = 43,
EQUAL_LENGTH_LINES = 50,
LENGTH_RATIO = 51,
EQ_LEN_PT_LINE_D = 52,
EQ_PT_LN_DISTANCES = 53,
EQUAL_ANGLE = 54,
EQUAL_LINE_ARC_LEN = 55,
LENGTH_DIFFERENCE = 56,
SYMMETRIC = 60,
SYMMETRIC_HORIZ = 61,
SYMMETRIC_VERT = 62,
SYMMETRIC_LINE = 63,
AT_MIDPOINT = 70,
HORIZONTAL = 80,
VERTICAL = 81,
DIAMETER = 90,
PT_ON_CIRCLE = 100,
SAME_ORIENTATION = 110,
ANGLE = 120,
PARALLEL = 121,
PERPENDICULAR = 122,
ARC_LINE_TANGENT = 123,
CUBIC_LINE_TANGENT = 124,
CURVE_CURVE_TANGENT = 125,
EQUAL_RADIUS = 130,
WHERE_DRAGGED = 200,
COMMENT = 1000
};
Type type;
hGroup group;
hEntity workplane;
// These are the parameters for the constraint.
double valA;
hParam valP;
hEntity ptA;
hEntity ptB;
hEntity entityA;
hEntity entityB;
hEntity entityC;
hEntity entityD;
bool other;
bool other2;
bool reference; // a ref dimension, that generates no eqs
std::string comment; // since comments are represented as constraints
bool HasLabel() const;
void Generate(IdList<Param, hParam> *param);
void GenerateEquations(IdList<Equation,hEquation> *entity,
bool forReference = false) const;
// Some helpers when generating symbolic constraint equations
void ModifyToSatisfy();
void AddEq(IdList<Equation,hEquation> *l, Expr *expr, int index) const;
void AddEq(IdList<Equation,hEquation> *l, const ExprVector &v, int baseIndex = 0) const;
static Expr *DirectionCosine(hEntity wrkpl, ExprVector ae, ExprVector be);
static Expr *Distance(hEntity workplane, hEntity pa, hEntity pb);
static Expr *PointLineDistance(hEntity workplane, hEntity pt, hEntity ln);
static Expr *PointPlaneDistance(ExprVector p, hEntity plane);
static ExprVector VectorsParallel3d(ExprVector a, ExprVector b, hParam p);
static ExprVector PointInThreeSpace(hEntity workplane, Expr *u, Expr *v);
void Clear() {}
};
class Constraint : public ConstraintBase {
public:
// See Entity::Entity().
Constraint() : ConstraintBase({}), disp() {}
// These define how the constraint is drawn on-screen.
struct {
Vector offset;
hStyle style;
} disp;
bool IsVisible() const;
bool IsStylable() const;
hStyle GetStyle() const;
bool HasLabel() const;
std::string Label() const;
enum class DrawAs { DEFAULT, HOVERED, SELECTED };
void Draw(DrawAs how, Canvas *canvas);
Vector GetLabelPos(const Camera &camera);
void GetReferencePoints(const Camera &camera, std::vector<Vector> *refs);
void DoLayout(DrawAs how, Canvas *canvas,
Vector *labelPos, std::vector<Vector> *refs);
void DoLine(Canvas *canvas, Canvas::hStroke hcs, Vector a, Vector b);
void DoStippledLine(Canvas *canvas, Canvas::hStroke hcs, Vector a, Vector b);
bool DoLineExtend(Canvas *canvas, Canvas::hStroke hcs,
Vector p0, Vector p1, Vector pt, double salient);
void DoArcForAngle(Canvas *canvas, Canvas::hStroke hcs,
Vector a0, Vector da, Vector b0, Vector db,
Vector offset, Vector *ref, bool trim);
void DoArrow(Canvas *canvas, Canvas::hStroke hcs,
Vector p, Vector dir, Vector n, double width, double angle, double da);
void DoLineWithArrows(Canvas *canvas, Canvas::hStroke hcs,
Vector ref, Vector a, Vector b, bool onlyOneExt);
int DoLineTrimmedAgainstBox(Canvas *canvas, Canvas::hStroke hcs,
Vector ref, Vector a, Vector b, bool extend,
Vector gr, Vector gu, double swidth, double sheight);
int DoLineTrimmedAgainstBox(Canvas *canvas, Canvas::hStroke hcs,
Vector ref, Vector a, Vector b, bool extend = true);
void DoLabel(Canvas *canvas, Canvas::hStroke hcs,
Vector ref, Vector *labelPos, Vector gr, Vector gu);
void DoProjectedPoint(Canvas *canvas, Canvas::hStroke hcs, Vector *p);
void DoProjectedPoint(Canvas *canvas, Canvas::hStroke hcs, Vector *p, Vector n, Vector o);
void DoEqualLenTicks(Canvas *canvas, Canvas::hStroke hcs,
Vector a, Vector b, Vector gn, Vector *refp);
void DoEqualRadiusTicks(Canvas *canvas, Canvas::hStroke hcs,
hEntity he, Vector *refp);
std::string DescriptionString() const;
static hConstraint AddConstraint(Constraint *c, bool rememberForUndo);
static hConstraint AddConstraint(Constraint *c);
static void MenuConstrain(Command id);
static void DeleteAllConstraintsFor(Constraint::Type type, hEntity entityA, hEntity ptA);
static hConstraint ConstrainCoincident(hEntity ptA, hEntity ptB);
static hConstraint Constrain(Constraint::Type type, hEntity ptA, hEntity ptB, hEntity entityA);
static hConstraint Constrain(Constraint::Type type, hEntity ptA, hEntity ptB,
hEntity entityA, hEntity entityB,
bool other, bool other2);
};
class hEquation {
public:
uint32_t v;
inline bool isFromConstraint() const;
inline hConstraint constraint() const;
};
class Equation {
public:
int tag;
hEquation h;
Expr *e;
void Clear() {}
};
class Style {
public:
int tag;
hStyle h;
enum {
// If an entity has no style, then it will be colored according to
// whether the group that it's in is active or not, whether it's
// construction or not, and so on.
NO_STYLE = 0,
ACTIVE_GRP = 1,
CONSTRUCTION = 2,
INACTIVE_GRP = 3,
DATUM = 4,
SOLID_EDGE = 5,
CONSTRAINT = 6,
SELECTED = 7,
HOVERED = 8,
CONTOUR_FILL = 9,
NORMALS = 10,
ANALYZE = 11,
DRAW_ERROR = 12,
DIM_SOLID = 13,
HIDDEN_EDGE = 14,
OUTLINE = 15,
FIRST_CUSTOM = 0x100
};
std::string name;
enum class UnitsAs : uint32_t {
PIXELS = 0,
MM = 1
};
double width;
UnitsAs widthAs;
double textHeight;
UnitsAs textHeightAs;
enum class TextOrigin : uint32_t {
NONE = 0x00,
LEFT = 0x01,
RIGHT = 0x02,
BOT = 0x04,
TOP = 0x08
};
TextOrigin textOrigin;
double textAngle;
RgbaColor color;
bool filled;
RgbaColor fillColor;
bool visible;
bool exportable;
StipplePattern stippleType;
double stippleScale;
int zIndex;
// The default styles, for entities that don't have a style assigned yet,
// and for datums and such.
typedef struct {
hStyle h;
const char *cnfPrefix;
RgbaColor color;
double width;
int zIndex;
} Default;
static const Default Defaults[];
static std::string CnfColor(const std::string &prefix);
static std::string CnfWidth(const std::string &prefix);
static std::string CnfTextHeight(const std::string &prefix);
static std::string CnfPrefixToName(const std::string &prefix);
static void CreateAllDefaultStyles();
static void CreateDefaultStyle(hStyle h);
static void FillDefaultStyle(Style *s, const Default *d = NULL, bool factory = false);
static void FreezeDefaultStyles();
static void LoadFactoryDefaults();
static void AssignSelectionToStyle(uint32_t v);
static uint32_t CreateCustomStyle(bool rememberForUndo = true);
static RgbaColor RewriteColor(RgbaColor rgb);
static Style *Get(hStyle hs);
static RgbaColor Color(hStyle hs, bool forExport=false);
static RgbaColor Color(int hs, bool forExport=false);
static RgbaColor FillColor(hStyle hs, bool forExport=false);
static double Width(hStyle hs);
static double Width(int hs);
static double WidthMm(int hs);
static double TextHeight(hStyle hs);
static double DefaultTextHeight();
static Canvas::Stroke Stroke(hStyle hs);
static Canvas::Stroke Stroke(int hs);
static bool Exportable(int hs);
static hStyle ForEntity(hEntity he);
static StipplePattern PatternType(hStyle hs);
static double StippleScaleMm(hStyle hs);
std::string DescriptionString() const;
void Clear() {}
};
inline hEntity hGroup::entity(int i) const
{ hEntity r; r.v = 0x80000000 | (v << 16) | (uint32_t)i; return r; }
inline hParam hGroup::param(int i) const
{ hParam r; r.v = 0x80000000 | (v << 16) | (uint32_t)i; return r; }
inline hEquation hGroup::equation(int i) const
{ hEquation r; r.v = (v << 16) | 0x80000000 | (uint32_t)i; return r; }
inline bool hRequest::IsFromReferences() const {
if(v == Request::HREQUEST_REFERENCE_XY.v) return true;
if(v == Request::HREQUEST_REFERENCE_YZ.v) return true;
if(v == Request::HREQUEST_REFERENCE_ZX.v) return true;
return false;
}
inline hEntity hRequest::entity(int i) const
{ hEntity r; r.v = (v << 16) | (uint32_t)i; return r; }
inline hParam hRequest::param(int i) const
{ hParam r; r.v = (v << 16) | (uint32_t)i; return r; }
inline bool hEntity::isFromRequest() const
{ if(v & 0x80000000) return false; else return true; }
inline hRequest hEntity::request() const
{ hRequest r; r.v = (v >> 16); return r; }
inline hGroup hEntity::group() const
{ hGroup r; r.v = (v >> 16) & 0x3fff; return r; }
inline hEquation hEntity::equation(int i) const
{ hEquation r; r.v = v | 0x40000000 | (uint32_t)i; return r; }
inline hRequest hParam::request() const
{ hRequest r; r.v = (v >> 16); return r; }
inline hEquation hConstraint::equation(int i) const
{ hEquation r; r.v = (v << 16) | (uint32_t)i; return r; }
inline hParam hConstraint::param(int i) const
{ hParam r; r.v = v | 0x40000000 | (uint32_t)i; return r; }
inline bool hEquation::isFromConstraint() const
{ if(v & 0xc0000000) return false; else return true; }
inline hConstraint hEquation::constraint() const
{ hConstraint r; r.v = (v >> 16); return r; }
// The format for entities stored on the clipboard.
class ClipboardRequest {
public:
Request::Type type;
int extraPoints;
hStyle style;
std::string str;
std::string font;
Platform::Path file;
bool construction;
Vector point[MAX_POINTS_IN_ENTITY];
double distance;
hEntity oldEnt;
hEntity oldPointEnt[MAX_POINTS_IN_ENTITY];
hRequest newReq;
};
#endif
+935
View File
@@ -0,0 +1,935 @@
//-----------------------------------------------------------------------------
// All declarations not grouped specially elsewhere.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#ifndef __SOLVESPACE_H
#define __SOLVESPACE_H
#include <stdint.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <stddef.h>
#include <stdarg.h>
#include <setjmp.h>
#include <math.h>
#include <setjmp.h>
#include <limits.h>
#include <algorithm>
#include <functional>
#include <memory>
#include <string>
#include <locale>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <map>
#include <set>
#include <chrono>
#include <sstream>
// We declare these in advance instead of simply using FT_Library
// (defined as typedef FT_LibraryRec_* FT_Library) because including
// freetype.h invokes indescribable horrors and we would like to avoid
// doing that every time we include solvespace.h.
#if FULL_LIB_JJS
struct FT_LibraryRec_;
struct FT_FaceRec_;
#endif
typedef struct _cairo cairo_t;
// The few floating-point equality comparisons in SolveSpace have been
// carefully considered, so we disable the -Wfloat-equal warning for them
#ifdef __clang__
# define EXACT(expr) \
(_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wfloat-equal\"") \
(expr) \
_Pragma("clang diagnostic pop"))
#else
# define EXACT(expr) (expr)
#endif
// Debugging functions
#if defined(__GNUC__)
#define ssassert(condition, message) \
do { \
if(__builtin_expect((condition), true) == false) { \
SolveSpace::assert_failure(__FILE__, __LINE__, __func__, #condition, message); \
__builtin_unreachable(); \
} \
} while(0)
#else
#define ssassert(condition, message) \
do { \
if((condition) == false) { \
SolveSpace::assert_failure(__FILE__, __LINE__, __func__, #condition, message); \
abort(); \
} \
} while(0)
#endif
#ifndef isnan
# define isnan(x) (((x) != (x)) || (x > 1e11) || (x < -1e11))
#endif
namespace SolveSpace {
using std::min;
using std::max;
using std::swap;
#if defined(__GNUC__)
__attribute__((noreturn))
#endif
void assert_failure(const char *file, unsigned line, const char *function,
const char *condition, const char *message);
#if defined(__GNUC__)
__attribute__((__format__ (__printf__, 1, 2)))
#endif
std::string ssprintf(const char *fmt, ...);
#if FULL_LIB_JJS
inline int WRAP(int v, int n) {
// Clamp it to the range [0, n)
while(v >= n) v -= n;
while(v < 0) v += n;
return v;
}
inline double WRAP_NOT_0(double v, double n) {
// Clamp it to the range (0, n]
while(v > n) v -= n;
while(v <= 0) v += n;
return v;
}
inline double WRAP_SYMMETRIC(double v, double n) {
// Clamp it to the range (-n/2, n/2]
while(v > n/2) v -= n;
while(v <= -n/2) v += n;
return v;
}
#endif
// Why is this faster than the library function?
inline double ffabs(double v) { return (v > 0) ? v : (-v); }
#if FULL_LIB_JJS
#define CO(v) (v).x, (v).y, (v).z
#endif
#define ANGLE_COS_EPS (1e-6)
#define LENGTH_EPS (1e-6)
#define VERY_POSITIVE (1e10)
#define VERY_NEGATIVE (-1e10)
#if FULL_LIB_JJS
inline double Random(double vmax) {
return (vmax*rand()) / RAND_MAX;
}
#endif
class Expr;
class ExprVector;
class ExprQuaternion;
class RgbaColor;
#if FULL_LIB_JJS
enum class Command : uint32_t;
enum class ContextCommand : uint32_t;
#endif
//================
// From the platform-specific code.
#include "platform/platform.h"
#if FULL_LIB_JJS
const size_t MAX_RECENT = 8;
extern Platform::Path RecentFile[MAX_RECENT];
void RefreshRecentMenus();
enum DialogChoice { DIALOG_YES = 1, DIALOG_NO = -1, DIALOG_CANCEL = 0 };
DialogChoice SaveFileYesNoCancel();
DialogChoice LoadAutosaveYesNo();
DialogChoice LocateImportedFileYesNoCancel(const Platform::Path &filename,
bool canCancel);
#define AUTOSAVE_EXT "slvs~"
enum class Unit : uint32_t {
MM = 0,
INCHES
};
#endif
#if FULL_LIB_JJS
struct FileFilter;
bool GetSaveFile(Platform::Path *filename, const std::string &defExtension,
const FileFilter filters[]);
bool GetOpenFile(Platform::Path *filename, const std::string &defExtension,
const FileFilter filters[]);
std::vector<Platform::Path> GetFontFiles();
void OpenWebsite(const char *url);
void RefreshLocale();
void CheckMenuByCmd(Command id, bool checked);
void RadioMenuByCmd(Command id, bool selected);
void EnableMenuByCmd(Command id, bool enabled);
void ShowGraphicsEditControl(int x, int y, int fontHeight, int minWidthChars,
const std::string &str);
void HideGraphicsEditControl();
bool GraphicsEditControlIsVisible();
void ShowTextEditControl(int x, int y, const std::string &str);
void HideTextEditControl();
bool TextEditControlIsVisible();
void MoveTextScrollbarTo(int pos, int maxPos, int page);
void AddContextMenuItem(const char *legend, ContextCommand id);
void CreateContextSubmenu();
ContextCommand ShowContextMenu();
void ShowTextWindow(bool visible);
void InvalidateText();
void InvalidateGraphics();
void PaintGraphics();
void ToggleFullScreen();
bool FullScreenIsActive();
void GetGraphicsWindowSize(int *w, int *h);
void GetTextWindowSize(int *w, int *h);
double GetScreenDpi();
int64_t GetMilliseconds();
#endif
void dbp(const char *str, ...);
#if FULL_LIB_JJS
#define DBPTRI(tri) \
dbp("tri: (%.3f %.3f %.3f) (%.3f %.3f %.3f) (%.3f %.3f %.3f)", \
CO((tri).a), CO((tri).b), CO((tri).c))
void SetCurrentFilename(const Platform::Path &filename);
void SetMousePointerToHand(bool yes);
#endif
void DoMessageBox(const char *str, int rows, int cols, bool error);
#if FULL_LIB_JJS
void SetTimerFor(int milliseconds);
void SetAutosaveTimerFor(int minutes);
void ScheduleLater();
void ExitNow();
#endif
void CnfFreezeInt(uint32_t val, const std::string &name);
#if FULL_LIB_JJS
void CnfFreezeFloat(float val, const std::string &name);
void CnfFreezeString(const std::string &val, const std::string &name);
std::string CnfThawString(const std::string &val, const std::string &name);
#endif
uint32_t CnfThawInt(uint32_t val, const std::string &name);
#if FULL_LIB_JJS
float CnfThawFloat(float val, const std::string &name);
#endif
std::vector<std::string> InitPlatform(int argc, char **argv);
void *AllocTemporary(size_t n);
void FreeTemporary(void *p);
void FreeAllTemporary();
void *MemAlloc(size_t n);
void MemFree(void *p);
#if FULL_LIB_JJS
void vl(); // debug function to validate heaps
#endif
#include "resource.h"
// End of platform-specific functions
//================
template<class T>
struct CompareHandle {
bool operator()(T lhs, T rhs) const { return lhs.v < rhs.v; }
};
template<class Key, class T>
using handle_map = std::map<Key, T, CompareHandle<Key>>;
class Group;
class SSurface;
#include "dsc.h"
#include "polygon.h"
#include "srf/surface.h"
#include "render/render.h"
class Entity;
class hEntity;
class Param;
class hParam;
typedef IdList<Entity,hEntity> EntityList;
typedef IdList<Param,hParam> ParamList;
enum class SolveResult : uint32_t {
OKAY = 0,
DIDNT_CONVERGE = 10,
REDUNDANT_OKAY = 11,
REDUNDANT_DIDNT_CONVERGE = 12,
TOO_MANY_UNKNOWNS = 20
};
#include "sketch.h"
#if FULL_LIB_JJS
#include "ui.h"
#endif
#include "expr.h"
#if FULL_LIB_JJS
// Utility functions that are provided in the platform-independent code.
class utf8_iterator : std::iterator<std::forward_iterator_tag, char32_t> {
const char *p, *n;
public:
utf8_iterator(const char *p) : p(p), n(NULL) {}
bool operator==(const utf8_iterator &i) const { return p==i.p; }
bool operator!=(const utf8_iterator &i) const { return p!=i.p; }
ptrdiff_t operator- (const utf8_iterator &i) const { return p -i.p; }
utf8_iterator& operator++() { **this; p=n; n=NULL; return *this; }
utf8_iterator operator++(int) { utf8_iterator t(*this); operator++(); return t; }
char32_t operator*();
};
class ReadUTF8 {
const std::string &str;
public:
ReadUTF8(const std::string &str) : str(str) {}
utf8_iterator begin() const { return utf8_iterator(&str[0]); }
utf8_iterator end() const { return utf8_iterator(&str[str.length()]); }
};
#endif
#define arraylen(x) (sizeof((x))/sizeof((x)[0]))
#define PI (3.1415926535897931)
void MakeMatrix(double *mat, double a11, double a12, double a13, double a14,
double a21, double a22, double a23, double a24,
double a31, double a32, double a33, double a34,
double a41, double a42, double a43, double a44);
void MultMatrix(double *mata, double *matb, double *matr);
std::string MakeAcceleratorLabel(int accel);
void Message(const char *str, ...);
void Error(const char *str, ...);
void CnfFreezeBool(bool v, const std::string &name);
void CnfFreezeColor(RgbaColor v, const std::string &name);
bool CnfThawBool(bool v, const std::string &name);
RgbaColor CnfThawColor(RgbaColor v, const std::string &name);
class System {
public:
enum { MAX_UNKNOWNS = 1024 };
EntityList entity;
ParamList param;
IdList<Equation,hEquation> eq;
// A list of parameters that are being dragged; these are the ones that
// we should put as close as possible to their initial positions.
List<hParam> dragged;
enum {
// In general, the tag indicates the subsys that a variable/equation
// has been assigned to; these are exceptions for variables:
VAR_SUBSTITUTED = 10000,
VAR_DOF_TEST = 10001,
// and for equations:
EQ_SUBSTITUTED = 20000
};
// The system Jacobian matrix
struct {
// The corresponding equation for each row
hEquation eq[MAX_UNKNOWNS];
// The corresponding parameter for each column
hParam param[MAX_UNKNOWNS];
// We're solving AX = B
int m, n;
struct {
Expr *sym[MAX_UNKNOWNS][MAX_UNKNOWNS];
double num[MAX_UNKNOWNS][MAX_UNKNOWNS];
} A;
double scale[MAX_UNKNOWNS];
// Some helpers for the least squares solve
double AAt[MAX_UNKNOWNS][MAX_UNKNOWNS];
double Z[MAX_UNKNOWNS];
double X[MAX_UNKNOWNS];
struct {
Expr *sym[MAX_UNKNOWNS];
double num[MAX_UNKNOWNS];
} B;
} mat;
static const double RANK_MAG_TOLERANCE, CONVERGE_TOLERANCE;
int CalculateRank();
bool TestRank();
static bool SolveLinearSystem(double X[], double A[][MAX_UNKNOWNS],
double B[], int N);
bool SolveLeastSquares();
bool WriteJacobian(int tag);
void EvalJacobian();
void WriteEquationsExceptFor(hConstraint hc, Group *g);
void FindWhichToRemoveToFixJacobian(Group *g, List<hConstraint> *bad, bool forceDofCheck);
void SolveBySubstitution();
bool IsDragged(hParam p);
bool NewtonSolve(int tag);
void MarkParamsFree(bool findFree);
int CalculateDof();
SolveResult Solve(Group *g, int *dof, List<hConstraint> *bad,
bool andFindBad, bool andFindFree, bool forceDofCheck = false);
SolveResult SolveRank(Group *g, int *dof, List<hConstraint> *bad,
bool andFindBad, bool andFindFree, bool forceDofCheck = false);
void Clear();
};
#if FULL_LIB_JJS
#include "ttf.h"
class StepFileWriter {
public:
void ExportSurfacesTo(const Platform::Path &filename);
void WriteHeader();
void WriteProductHeader();
int ExportCurve(SBezier *sb);
int ExportCurveLoop(SBezierLoop *loop, bool inner);
void ExportSurface(SSurface *ss, SBezierList *sbl);
void WriteWireframe();
void WriteFooter();
List<int> curves;
List<int> advancedFaces;
FILE *f;
int id;
};
class VectorFileWriter {
protected:
Vector u, v, n, origin;
double cameraTan, scale;
public:
FILE *f;
Platform::Path filename;
Vector ptMin, ptMax;
static double MmToPts(double mm);
static VectorFileWriter *ForFile(const Platform::Path &filename);
void SetModelviewProjection(const Vector &u, const Vector &v, const Vector &n,
const Vector &origin, double cameraTan, double scale);
Vector Transform(Vector &pos) const;
void OutputLinesAndMesh(SBezierLoopSetSet *sblss, SMesh *sm);
void BezierAsPwl(SBezier *sb);
void BezierAsNonrationalCubic(SBezier *sb, int depth=0);
virtual void StartPath(RgbaColor strokeRgb, double lineWidth,
bool filled, RgbaColor fillRgb, hStyle hs) = 0;
virtual void FinishPath(RgbaColor strokeRgb, double lineWidth,
bool filled, RgbaColor fillRgb, hStyle hs) = 0;
virtual void Bezier(SBezier *sb) = 0;
virtual void Triangle(STriangle *tr) = 0;
virtual bool OutputConstraints(IdList<Constraint,hConstraint> *) { return false; }
virtual void StartFile() = 0;
virtual void FinishAndCloseFile() = 0;
virtual bool HasCanvasSize() const = 0;
virtual bool CanOutputMesh() const = 0;
};
class DxfFileWriter : public VectorFileWriter {
public:
struct BezierPath {
std::vector<SBezier *> beziers;
};
std::vector<BezierPath> paths;
IdList<Constraint,hConstraint> *constraint;
static const char *lineTypeName(StipplePattern stippleType);
bool OutputConstraints(IdList<Constraint,hConstraint> *constraint) override;
void StartPath( RgbaColor strokeRgb, double lineWidth,
bool filled, RgbaColor fillRgb, hStyle hs) override;
void FinishPath(RgbaColor strokeRgb, double lineWidth,
bool filled, RgbaColor fillRgb, hStyle hs) override;
void Triangle(STriangle *tr) override;
void Bezier(SBezier *sb) override;
void StartFile() override;
void FinishAndCloseFile() override;
bool HasCanvasSize() const override { return false; }
bool CanOutputMesh() const override { return false; }
bool NeedToOutput(Constraint *c);
};
class EpsFileWriter : public VectorFileWriter {
public:
Vector prevPt;
void MaybeMoveTo(Vector s, Vector f);
void StartPath( RgbaColor strokeRgb, double lineWidth,
bool filled, RgbaColor fillRgb, hStyle hs) override;
void FinishPath(RgbaColor strokeRgb, double lineWidth,
bool filled, RgbaColor fillRgb, hStyle hs) override;
void Triangle(STriangle *tr) override;
void Bezier(SBezier *sb) override;
void StartFile() override;
void FinishAndCloseFile() override;
bool HasCanvasSize() const override { return true; }
bool CanOutputMesh() const override { return true; }
};
class PdfFileWriter : public VectorFileWriter {
public:
uint32_t xref[10];
uint32_t bodyStart;
Vector prevPt;
void MaybeMoveTo(Vector s, Vector f);
void StartPath( RgbaColor strokeRgb, double lineWidth,
bool filled, RgbaColor fillRgb, hStyle hs) override;
void FinishPath(RgbaColor strokeRgb, double lineWidth,
bool filled, RgbaColor fillRgb, hStyle hs) override;
void Triangle(STriangle *tr) override;
void Bezier(SBezier *sb) override;
void StartFile() override;
void FinishAndCloseFile() override;
bool HasCanvasSize() const override { return true; }
bool CanOutputMesh() const override { return true; }
};
class SvgFileWriter : public VectorFileWriter {
public:
Vector prevPt;
void MaybeMoveTo(Vector s, Vector f);
void StartPath( RgbaColor strokeRgb, double lineWidth,
bool filled, RgbaColor fillRgb, hStyle hs) override;
void FinishPath(RgbaColor strokeRgb, double lineWidth,
bool filled, RgbaColor fillRgb, hStyle hs) override;
void Triangle(STriangle *tr) override;
void Bezier(SBezier *sb) override;
void StartFile() override;
void FinishAndCloseFile() override;
bool HasCanvasSize() const override { return true; }
bool CanOutputMesh() const override { return true; }
};
class HpglFileWriter : public VectorFileWriter {
public:
static double MmToHpglUnits(double mm);
void StartPath( RgbaColor strokeRgb, double lineWidth,
bool filled, RgbaColor fillRgb, hStyle hs) override;
void FinishPath(RgbaColor strokeRgb, double lineWidth,
bool filled, RgbaColor fillRgb, hStyle hs) override;
void Triangle(STriangle *tr) override;
void Bezier(SBezier *sb) override;
void StartFile() override;
void FinishAndCloseFile() override;
bool HasCanvasSize() const override { return false; }
bool CanOutputMesh() const override { return false; }
};
class Step2dFileWriter : public VectorFileWriter {
StepFileWriter sfw;
void StartPath( RgbaColor strokeRgb, double lineWidth,
bool filled, RgbaColor fillRgb, hStyle hs) override;
void FinishPath(RgbaColor strokeRgb, double lineWidth,
bool filled, RgbaColor fillRgb, hStyle hs) override;
void Triangle(STriangle *tr) override;
void Bezier(SBezier *sb) override;
void StartFile() override;
void FinishAndCloseFile() override;
bool HasCanvasSize() const override { return false; }
bool CanOutputMesh() const override { return false; }
};
class GCodeFileWriter : public VectorFileWriter {
public:
SEdgeList sel;
void StartPath( RgbaColor strokeRgb, double lineWidth,
bool filled, RgbaColor fillRgb, hStyle hs) override;
void FinishPath(RgbaColor strokeRgb, double lineWidth,
bool filled, RgbaColor fillRgb, hStyle hs) override;
void Triangle(STriangle *tr) override;
void Bezier(SBezier *sb) override;
void StartFile() override;
void FinishAndCloseFile() override;
bool HasCanvasSize() const override { return false; }
bool CanOutputMesh() const override { return false; }
};
#endif
#ifdef LIBRARY
# define ENTITY EntityBase
# define CONSTRAINT ConstraintBase
#else
# define ENTITY Entity
# define CONSTRAINT Constraint
#endif
class Sketch {
public:
// These are user-editable, and define the sketch.
IdList<Group,hGroup> group;
List<hGroup> groupOrder;
IdList<CONSTRAINT,hConstraint> constraint;
IdList<Request,hRequest> request;
IdList<Style,hStyle> style;
// These are generated from the above.
IdList<ENTITY,hEntity> entity;
IdList<Param,hParam> param;
inline CONSTRAINT *GetConstraint(hConstraint h)
{ return constraint.FindById(h); }
inline ENTITY *GetEntity (hEntity h) { return entity. FindById(h); }
inline Param *GetParam (hParam h) { return param. FindById(h); }
inline Request *GetRequest(hRequest h) { return request.FindById(h); }
inline Group *GetGroup (hGroup h) { return group. FindById(h); }
// Styles are handled a bit differently.
void Clear();
BBox CalculateEntityBBox(bool includingInvisible);
Group *GetRunningMeshGroupFor(hGroup h);
};
#undef ENTITY
#undef CONSTRAINT
#if FULL_LIB_JJS
class SolveSpaceUI {
public:
TextWindow *pTW;
TextWindow &TW;
GraphicsWindow GW;
// The state for undo/redo
typedef struct {
IdList<Group,hGroup> group;
List<hGroup> groupOrder;
IdList<Request,hRequest> request;
IdList<Constraint,hConstraint> constraint;
IdList<Param,hParam> param;
IdList<Style,hStyle> style;
hGroup activeGroup;
void Clear() {
group.Clear();
request.Clear();
constraint.Clear();
param.Clear();
style.Clear();
}
} UndoState;
enum { MAX_UNDO = 16 };
typedef struct {
UndoState d[MAX_UNDO];
int cnt;
int write;
} UndoStack;
UndoStack undo;
UndoStack redo;
std::map<Platform::Path, std::shared_ptr<Pixmap>, Platform::PathLess> images;
bool ReloadLinkedImage(const Platform::Path &saveFile, Platform::Path *filename,
bool canCancel);
void UndoEnableMenus();
void UndoRemember();
void UndoUndo();
void UndoRedo();
void PushFromCurrentOnto(UndoStack *uk);
void PopOntoCurrentFrom(UndoStack *uk);
void UndoClearState(UndoState *ut);
void UndoClearStack(UndoStack *uk);
// Little bits of extra configuration state
enum { MODEL_COLORS = 8 };
RgbaColor modelColor[MODEL_COLORS];
Vector lightDir[2];
double lightIntensity[2];
double ambientIntensity;
double chordTol;
double chordTolCalculated;
int maxSegments;
double exportChordTol;
int exportMaxSegments;
double cameraTangent;
float gridSpacing;
float exportScale;
float exportOffset;
bool fixExportColors;
bool drawBackFaces;
bool showContourAreas;
bool checkClosedContour;
bool showToolbar;
Platform::Path screenshotFile;
RgbaColor backgroundColor;
bool exportShadedTriangles;
bool exportPwlCurves;
bool exportCanvasSizeAuto;
bool exportMode;
struct {
float left;
float right;
float bottom;
float top;
} exportMargin;
struct {
float width;
float height;
float dx;
float dy;
} exportCanvas;
struct {
float depth;
int passes;
float feed;
float plungeFeed;
} gCode;
Unit viewUnits;
int afterDecimalMm;
int afterDecimalInch;
int autosaveInterval; // in minutes
std::string MmToString(double v);
double ExprToMm(Expr *e);
double StringToMm(const std::string &s);
const char *UnitName();
double MmPerUnit();
int UnitDigitsAfterDecimal();
void SetUnitDigitsAfterDecimal(int v);
double ChordTolMm();
double ExportChordTolMm();
int GetMaxSegments();
bool usePerspectiveProj;
double CameraTangent();
// Some stuff relating to the tangent arcs created non-parametrically
// as special requests.
double tangentArcRadius;
bool tangentArcManual;
bool tangentArcDeleteOld;
// The platform-dependent code calls this before entering the msg loop
void Init();
bool Load(const Platform::Path &filename);
void Exit();
// File load/save routines, including the additional files that get
// loaded when we have link groups.
FILE *fh;
void AfterNewFile();
static void RemoveFromRecentList(const Platform::Path &filename);
static void AddToRecentList(const Platform::Path &filename);
Platform::Path saveFile;
bool fileLoadError;
bool unsaved;
typedef struct {
char type;
const char *desc;
char fmt;
void *ptr;
} SaveTable;
static const SaveTable SAVED[];
void SaveUsingTable(const Platform::Path &filename, int type);
void LoadUsingTable(const Platform::Path &filename, char *key, char *val);
struct {
Group g;
Request r;
Entity e;
Param p;
Constraint c;
Style s;
} sv;
static void MenuFile(Command id);
bool Autosave();
void RemoveAutosave();
bool GetFilenameAndSave(bool saveAs);
bool OkayToStartNewFile();
hGroup CreateDefaultDrawingGroup();
void UpdateWindowTitle();
void ClearExisting();
void NewFile();
bool SaveToFile(const Platform::Path &filename);
bool LoadAutosaveFor(const Platform::Path &filename);
bool LoadFromFile(const Platform::Path &filename, bool canCancel = false);
void UpgradeLegacyData();
bool LoadEntitiesFromFile(const Platform::Path &filename, EntityList *le,
SMesh *m, SShell *sh);
bool ReloadAllLinked(const Platform::Path &filename, bool canCancel = false);
// And the various export options
void ExportAsPngTo(const Platform::Path &filename);
void ExportMeshTo(const Platform::Path &filename);
void ExportMeshAsStlTo(FILE *f, SMesh *sm);
void ExportMeshAsObjTo(FILE *fObj, FILE *fMtl, SMesh *sm);
void ExportMeshAsThreeJsTo(FILE *f, const Platform::Path &filename,
SMesh *sm, SOutlineList *sol);
void ExportViewOrWireframeTo(const Platform::Path &filename, bool exportWireframe);
void ExportSectionTo(const Platform::Path &filename);
void ExportWireframeCurves(SEdgeList *sel, SBezierList *sbl,
VectorFileWriter *out);
void ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *sm,
Vector u, Vector v,
Vector n, Vector origin,
double cameraTan,
VectorFileWriter *out);
static void MenuAnalyze(Command id);
// Additional display stuff
struct {
SContour path;
hEntity point;
} traced;
SEdgeList nakedEdges;
struct {
bool draw;
Vector ptA;
Vector ptB;
} extraLine;
struct {
bool draw, showOrigin;
Vector pt, u, v;
} justExportedInfo;
struct {
bool draw;
bool dirty;
Vector position;
} centerOfMass;
class Clipboard {
public:
List<ClipboardRequest> r;
List<Constraint> c;
void Clear();
bool ContainsEntity(hEntity old);
hEntity NewEntityFor(hEntity old);
};
Clipboard clipboard;
void MarkGroupDirty(hGroup hg, bool onlyThis = false);
void MarkGroupDirtyByEntity(hEntity he);
// Consistency checking on the sketch: stuff with missing dependencies
// will get deleted automatically.
struct {
int requests;
int groups;
int constraints;
int nonTrivialConstraints;
} deleted;
bool GroupExists(hGroup hg);
bool PruneOrphans();
bool EntityExists(hEntity he);
bool GroupsInOrder(hGroup before, hGroup after);
bool PruneGroups(hGroup hg);
bool PruneRequests(hGroup hg);
bool PruneConstraints(hGroup hg);
static void ShowNakedEdges(bool reportOnlyWhenNotOkay);
enum class Generate : uint32_t {
DIRTY,
ALL,
REGEN,
UNTIL_ACTIVE,
};
void GenerateAll(Generate type = Generate::DIRTY, bool andFindFree = false,
bool genForBBox = false);
void SolveGroup(hGroup hg, bool andFindFree);
void SolveGroupAndReport(hGroup hg, bool andFindFree);
SolveResult TestRankForGroup(hGroup hg);
void WriteEqSystemForGroup(hGroup hg);
void MarkDraggedParams();
void ForceReferences();
void UpdateCenterOfMass();
bool ActiveGroupsOkay();
// The system to be solved.
System *pSys;
System &sys;
// All the TrueType fonts in memory
TtfFontList fonts;
// Everything has been pruned, so we know there's no dangling references
// to entities that don't exist. Before that, we mustn't try to display
// the sketch!
bool allConsistent;
struct {
bool scheduled;
bool showTW;
bool generateAll;
} later;
void ScheduleShowTW();
void ScheduleGenerateAll();
void DoLater();
static void MenuHelp(Command id);
void Clear();
// We allocate TW and sys on the heap to work around an MSVC problem
// where it puts zero-initialized global data in the binary (~30M of zeroes)
// in release builds.
SolveSpaceUI()
: pTW(new TextWindow({})), TW(*pTW),
pSys(new System({})), sys(*pSys) {}
~SolveSpaceUI() {
delete pTW;
delete pSys;
}
};
void ImportDxf(const Platform::Path &file);
void ImportDwg(const Platform::Path &file);
extern SolveSpaceUI SS;
#endif
extern Sketch SK;
}
#ifndef __OBJC__
using namespace SolveSpace;
#endif
#endif
+429
View File
@@ -0,0 +1,429 @@
//-----------------------------------------------------------------------------
// Functions relating to rational polynomial surfaces, which are trimmed by
// curves (either rational polynomial curves, or piecewise linear
// approximations to curves of intersection that can't be represented
// exactly in ratpoly form), and assembled into watertight shells.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#ifndef __SURFACE_H
#define __SURFACE_H
// Utility functions, Bernstein polynomials of order 1-3 and their derivatives.
double Bernstein(int k, int deg, double t);
double BernsteinDerivative(int k, int deg, double t);
class SBezierList;
class SSurface;
class SCurvePt;
// Utility data structure, a two-dimensional BSP to accelerate polygon
// operations.
class SBspUv {
public:
Point2d a, b;
SBspUv *pos;
SBspUv *neg;
SBspUv *more;
enum class Class : uint32_t {
INSIDE = 100,
OUTSIDE = 200,
EDGE_PARALLEL = 300,
EDGE_ANTIPARALLEL = 400,
EDGE_OTHER = 500
};
static SBspUv *Alloc();
static SBspUv *From(SEdgeList *el, SSurface *srf);
void ScalePoints(Point2d *pt, Point2d *a, Point2d *b, SSurface *srf) const;
double ScaledSignedDistanceToLine(Point2d pt, Point2d a, Point2d b,
SSurface *srf) const;
double ScaledDistanceToLine(Point2d pt, Point2d a, Point2d b, bool asSegment,
SSurface *srf) const;
void InsertEdge(Point2d a, Point2d b, SSurface *srf);
static SBspUv *InsertOrCreateEdge(SBspUv *where, Point2d ea, Point2d eb, SSurface *srf);
Class ClassifyPoint(Point2d p, Point2d eb, SSurface *srf) const;
Class ClassifyEdge(Point2d ea, Point2d eb, SSurface *srf) const;
double MinimumDistanceToEdge(Point2d p, SSurface *srf) const;
};
// Now the data structures to represent a shell of trimmed rational polynomial
// surfaces.
class SShell;
class hSSurface {
public:
uint32_t v;
};
class hSCurve {
public:
uint32_t v;
};
// Stuff for rational polynomial curves, of degree one to three. These are
// our inputs, and are also calculated for certain exact surface-surface
// intersections.
class SBezier {
public:
int tag;
int auxA, auxB;
int deg;
Vector ctrl[4];
double weight[4];
uint32_t entity;
Vector PointAt(double t) const;
Vector TangentAt(double t) const;
void ClosestPointTo(Vector p, double *t, bool mustConverge=true) const;
void SplitAt(double t, SBezier *bef, SBezier *aft) const;
bool PointOnThisAndCurve(const SBezier *sbb, Vector *p) const;
Vector Start() const;
Vector Finish() const;
bool Equals(SBezier *b) const;
void MakePwlInto(SEdgeList *sel, double chordTol=0) const;
void MakePwlInto(List<SCurvePt> *l, double chordTol=0) const;
void MakePwlInto(SContour *sc, double chordTol=0) const;
void MakePwlInto(List<Vector> *l, double chordTol=0) const;
void MakePwlWorker(List<Vector> *l, double ta, double tb, double chordTol) const;
void MakePwlInitialWorker(List<Vector> *l, double ta, double tb, double chordTol) const;
void MakeNonrationalCubicInto(SBezierList *bl, double tolerance, int depth = 0) const;
void AllIntersectionsWith(const SBezier *sbb, SPointList *spl) const;
void GetBoundingProjd(Vector u, Vector orig, double *umin, double *umax) const;
void Reverse();
bool IsInPlane(Vector n, double d) const;
bool IsCircle(Vector axis, Vector *center, double *r) const;
bool IsRational() const;
SBezier TransformedBy(Vector t, Quaternion q, double scale) const;
SBezier InPerspective(Vector u, Vector v, Vector n,
Vector origin, double cameraTan) const;
void ScaleSelfBy(double s);
static SBezier From(Vector p0, Vector p1, Vector p2, Vector p3);
static SBezier From(Vector p0, Vector p1, Vector p2);
static SBezier From(Vector p0, Vector p1);
static SBezier From(Vector4 p0, Vector4 p1, Vector4 p2, Vector4 p3);
static SBezier From(Vector4 p0, Vector4 p1, Vector4 p2);
static SBezier From(Vector4 p0, Vector4 p1);
};
class SBezierList {
public:
List<SBezier> l;
void Clear();
void ScaleSelfBy(double s);
void CullIdenticalBeziers();
void AllIntersectionsWith(SBezierList *sblb, SPointList *spl) const;
bool GetPlaneContainingBeziers(Vector *p, Vector *u, Vector *v,
Vector *notCoplanarAt) const;
};
class SBezierLoop {
public:
int tag;
List<SBezier> l;
inline void Clear() { l.Clear(); }
bool IsClosed() const;
void Reverse();
void MakePwlInto(SContour *sc, double chordTol=0) const;
void GetBoundingProjd(Vector u, Vector orig, double *umin, double *umax) const;
static SBezierLoop FromCurves(SBezierList *spcl,
bool *allClosed, SEdge *errorAt);
};
class SBezierLoopSet {
public:
List<SBezierLoop> l;
Vector normal;
Vector point;
double area;
static SBezierLoopSet From(SBezierList *spcl, SPolygon *poly,
double chordTol,
bool *allClosed, SEdge *errorAt,
SBezierList *openContours);
void GetBoundingProjd(Vector u, Vector orig, double *umin, double *umax) const;
double SignedArea();
void MakePwlInto(SPolygon *sp) const;
void Clear();
};
class SBezierLoopSetSet {
public:
List<SBezierLoopSet> l;
void FindOuterFacesFrom(SBezierList *sbl, SPolygon *spxyz, SSurface *srfuv,
double chordTol,
bool *allClosed, SEdge *notClosedAt,
bool *allCoplanar, Vector *notCoplanarAt,
SBezierList *openContours);
void AddOpenPath(SBezier *sb);
void Clear();
};
// Stuff for the surface trim curves: piecewise linear
class SCurvePt {
public:
int tag;
Vector p;
bool vertex;
};
class SCurve {
public:
hSCurve h;
// In a Boolean, C = A op B. The curves in A and B get copied into C, and
// therefore must get new hSCurves assigned. For the curves in A and B,
// we use newH to record their new handle in C.
hSCurve newH;
enum class Source : uint32_t {
A = 100,
B = 200,
INTERSECTION = 300
};
Source source;
bool isExact;
SBezier exact;
List<SCurvePt> pts;
hSSurface surfA;
hSSurface surfB;
static SCurve FromTransformationOf(SCurve *a, Vector t,
Quaternion q, double scale);
SCurve MakeCopySplitAgainst(SShell *agnstA, SShell *agnstB,
SSurface *srfA, SSurface *srfB) const;
void RemoveShortSegments(SSurface *srfA, SSurface *srfB);
SSurface *GetSurfaceA(SShell *a, SShell *b) const;
SSurface *GetSurfaceB(SShell *a, SShell *b) const;
void Clear();
};
// A segment of a curve by which a surface is trimmed: indicates which curve,
// by its handle, and the starting and ending points of our segment of it.
// The vector out points out of the surface; it, the surface outer normal,
// and a tangent to the beginning of the curve are all orthogonal.
class STrimBy {
public:
hSCurve curve;
bool backwards;
// If a trim runs backwards, then start and finish still correspond to
// the actual start and finish, but they appear in reverse order in
// the referenced curve.
Vector start;
Vector finish;
static STrimBy EntireCurve(SShell *shell, hSCurve hsc, bool backwards);
};
// An intersection point between a line and a surface
class SInter {
public:
int tag;
Vector p;
SSurface *srf;
Point2d pinter;
Vector surfNormal; // of the intersecting surface, at pinter
bool onEdge; // pinter is on edge of trim poly
};
// A rational polynomial surface in Bezier form.
class SSurface {
public:
enum class CombineAs : uint32_t {
UNION = 10,
DIFFERENCE = 11,
INTERSECT = 12
};
int tag;
hSSurface h;
// Same as newH for the curves; record what a surface gets renamed to
// when I copy things over.
hSSurface newH;
RgbaColor color;
uint32_t face;
int degm, degn;
Vector ctrl[4][4];
double weight[4][4];
List<STrimBy> trim;
// For testing whether a point (u, v) on the surface lies inside the trim
SBspUv *bsp;
SEdgeList edges;
// For caching our initial (u, v) when doing Newton iterations to project
// a point into our surface.
Point2d cached;
static SSurface FromExtrusionOf(SBezier *spc, Vector t0, Vector t1);
static SSurface FromRevolutionOf(SBezier *sb, Vector pt, Vector axis,
double thetas, double thetaf);
static SSurface FromPlane(Vector pt, Vector u, Vector v);
static SSurface FromTransformationOf(SSurface *a, Vector t, Quaternion q,
double scale,
bool includingTrims);
void ScaleSelfBy(double s);
void EdgeNormalsWithinSurface(Point2d auv, Point2d buv,
Vector *pt, Vector *enin, Vector *enout,
Vector *surfn,
uint32_t auxA,
SShell *shell, SShell *sha, SShell *shb);
void FindChainAvoiding(SEdgeList *src, SEdgeList *dest, SPointList *avoid);
SSurface MakeCopyTrimAgainst(SShell *parent, SShell *a, SShell *b,
SShell *into, SSurface::CombineAs type);
void TrimFromEdgeList(SEdgeList *el, bool asUv);
void IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB,
SShell *into);
void AddExactIntersectionCurve(SBezier *sb, SSurface *srfB,
SShell *agnstA, SShell *agnstB, SShell *into);
typedef struct {
int tag;
Point2d p;
} Inter;
void WeightControlPoints();
void UnWeightControlPoints();
void CopyRowOrCol(bool row, int this_ij, SSurface *src, int src_ij);
void BlendRowOrCol(bool row, int this_ij, SSurface *a, int a_ij,
SSurface *b, int b_ij);
double DepartureFromCoplanar() const;
void SplitInHalf(bool byU, SSurface *sa, SSurface *sb);
void AllPointsIntersecting(Vector a, Vector b,
List<SInter> *l,
bool asSegment, bool trimmed, bool inclTangent);
void AllPointsIntersectingUntrimmed(Vector a, Vector b,
int *cnt, int *level,
List<Inter> *l, bool asSegment,
SSurface *sorig);
void ClosestPointTo(Vector p, Point2d *puv, bool mustConverge=true);
void ClosestPointTo(Vector p, double *u, double *v, bool mustConverge=true);
bool ClosestPointNewton(Vector p, double *u, double *v, bool mustConverge=true) const;
bool PointIntersectingLine(Vector p0, Vector p1, double *u, double *v) const;
Vector ClosestPointOnThisAndSurface(SSurface *srf2, Vector p);
void PointOnSurfaces(SSurface *s1, SSurface *s2, double *u, double *v);
Vector PointAt(double u, double v) const;
Vector PointAt(Point2d puv) const;
void TangentsAt(double u, double v, Vector *tu, Vector *tv) const;
Vector NormalAt(Point2d puv) const;
Vector NormalAt(double u, double v) const;
bool LineEntirelyOutsideBbox(Vector a, Vector b, bool asSegment) const;
void GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) const;
bool CoincidentWithPlane(Vector n, double d) const;
bool CoincidentWith(SSurface *ss, bool sameNormal) const;
bool IsExtrusion(SBezier *of, Vector *along) const;
bool IsCylinder(Vector *axis, Vector *center, double *r,
Vector *start, Vector *finish) const;
void TriangulateInto(SShell *shell, SMesh *sm);
// these are intended as bitmasks, even though there's just one now
enum class MakeAs : uint32_t {
UV = 0x01,
XYZ = 0x00
};
void MakeTrimEdgesInto(SEdgeList *sel, MakeAs flags, SCurve *sc, STrimBy *stb);
void MakeEdgesInto(SShell *shell, SEdgeList *sel, MakeAs flags,
SShell *useCurvesFrom=NULL);
Vector ExactSurfaceTangentAt(Vector p, SSurface *srfA, SSurface *srfB,
Vector dir);
void MakeSectionEdgesInto(SShell *shell, SEdgeList *sel, SBezierList *sbl);
void MakeClassifyingBsp(SShell *shell, SShell *useCurvesFrom);
double ChordToleranceForEdge(Vector a, Vector b) const;
void MakeTriangulationGridInto(List<double> *l, double vs, double vf,
bool swapped) const;
Vector PointAtMaybeSwapped(double u, double v, bool swapped) const;
void Reverse();
void Clear();
};
class SShell {
public:
IdList<SCurve,hSCurve> curve;
IdList<SSurface,hSSurface> surface;
bool booleanFailed;
void MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1,
RgbaColor color);
void MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis,
RgbaColor color, Group *group);
void MakeFromUnionOf(SShell *a, SShell *b);
void MakeFromDifferenceOf(SShell *a, SShell *b);
void MakeFromBoolean(SShell *a, SShell *b, SSurface::CombineAs type);
void CopyCurvesSplitAgainst(bool opA, SShell *agnst, SShell *into);
void CopySurfacesTrimAgainst(SShell *sha, SShell *shb, SShell *into, SSurface::CombineAs type);
void MakeIntersectionCurvesAgainst(SShell *against, SShell *into);
void MakeClassifyingBsps(SShell *useCurvesFrom);
void AllPointsIntersecting(Vector a, Vector b, List<SInter> *il,
bool asSegment, bool trimmed, bool inclTangent);
void MakeCoincidentEdgesInto(SSurface *proto, bool sameNormal,
SEdgeList *el, SShell *useCurvesFrom);
void RewriteSurfaceHandlesForCurves(SShell *a, SShell *b);
void CleanupAfterBoolean();
// Definitions when classifying regions of a surface; it is either inside,
// outside, or coincident (with parallel or antiparallel normal) with a
// shell.
enum class Class : uint32_t {
INSIDE = 100,
OUTSIDE = 200,
COINC_SAME = 300,
COINC_OPP = 400
};
static const double DOTP_TOL;
Class ClassifyRegion(Vector edge_n, Vector inter_surf_n,
Vector edge_surf_n) const;
bool ClassifyEdge(Class *indir, Class *outdir,
Vector ea, Vector eb,
Vector p, Vector edge_n_in,
Vector edge_n_out, Vector surf_n);
void MakeFromCopyOf(SShell *a);
void MakeFromTransformationOf(SShell *a,
Vector trans, Quaternion q, double scale);
void MakeFromAssemblyOf(SShell *a, SShell *b);
void MergeCoincidentSurfaces();
void TriangulateInto(SMesh *sm);
void MakeEdgesInto(SEdgeList *sel);
void MakeSectionEdgesInto(Vector n, double d, SEdgeList *sel, SBezierList *sbl);
bool IsEmpty() const;
void RemapFaces(Group *g, int remap);
void Clear();
};
#endif
+587
View File
@@ -0,0 +1,587 @@
//-----------------------------------------------------------------------------
// Once we've written our constraint equations in the symbolic algebra system,
// these routines linearize them, and solve by a modified Newton's method.
// This also contains the routines to detect non-convergence or inconsistency,
// and report diagnostics to the user.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
// This tolerance is used to determine whether two (linearized) constraints
// are linearly dependent. If this is too small, then we will attempt to
// solve truly inconsistent systems and fail. But if it's too large, then
// we will give up on legitimate systems like a skinny right angle triangle by
// its hypotenuse and long side.
const double System::RANK_MAG_TOLERANCE = 1e-4;
// The solver will converge all unknowns to within this tolerance. This must
// always be much less than LENGTH_EPS, and in practice should be much less.
const double System::CONVERGE_TOLERANCE = (LENGTH_EPS/(1e2));
bool System::WriteJacobian(int tag) {
int a, i, j;
j = 0;
for(a = 0; a < param.n; a++) {
if(j >= MAX_UNKNOWNS) return false;
Param *p = &(param.elem[a]);
if(p->tag != tag) continue;
mat.param[j] = p->h;
j++;
}
mat.n = j;
i = 0;
for(a = 0; a < eq.n; a++) {
if(i >= MAX_UNKNOWNS) return false;
Equation *e = &(eq.elem[a]);
if(e->tag != tag) continue;
mat.eq[i] = e->h;
Expr *f = e->e->DeepCopyWithParamsAsPointers(&param, &(SK.param));
f = f->FoldConstants();
// Hash table (61 bits) to accelerate generation of zero partials.
uint64_t scoreboard = f->ParamsUsed();
for(j = 0; j < mat.n; j++) {
Expr *pd;
if(scoreboard & ((uint64_t)1 << (mat.param[j].v % 61)) &&
f->DependsOn(mat.param[j]))
{
pd = f->PartialWrt(mat.param[j]);
pd = pd->FoldConstants();
pd = pd->DeepCopyWithParamsAsPointers(&param, &(SK.param));
} else {
pd = Expr::From(0.0);
}
mat.A.sym[i][j] = pd;
}
mat.B.sym[i] = f;
i++;
}
mat.m = i;
return true;
}
void System::EvalJacobian() {
int i, j;
for(i = 0; i < mat.m; i++) {
for(j = 0; j < mat.n; j++) {
mat.A.num[i][j] = (mat.A.sym[i][j])->Eval();
}
}
}
bool System::IsDragged(hParam p) {
hParam *pp;
for(pp = dragged.First(); pp; pp = dragged.NextAfter(pp)) {
if(p.v == pp->v) return true;
}
return false;
}
void System::SolveBySubstitution() {
int i;
for(i = 0; i < eq.n; i++) {
Equation *teq = &(eq.elem[i]);
Expr *tex = teq->e;
if(tex->op == Expr::Op::MINUS &&
tex->a->op == Expr::Op::PARAM &&
tex->b->op == Expr::Op::PARAM)
{
hParam a = tex->a->parh;
hParam b = tex->b->parh;
if(!(param.FindByIdNoOops(a) && param.FindByIdNoOops(b))) {
// Don't substitute unless they're both solver params;
// otherwise it's an equation that can be solved immediately,
// or an error to flag later.
continue;
}
if(IsDragged(a)) {
// A is being dragged, so A should stay, and B should go
hParam t = a;
a = b;
b = t;
}
int j;
for(j = 0; j < eq.n; j++) {
Equation *req = &(eq.elem[j]);
(req->e)->Substitute(a, b); // A becomes B, B unchanged
}
for(j = 0; j < param.n; j++) {
Param *rp = &(param.elem[j]);
if(rp->substd.v == a.v) {
rp->substd = b;
}
}
Param *ptr = param.FindById(a);
ptr->tag = VAR_SUBSTITUTED;
ptr->substd = b;
teq->tag = EQ_SUBSTITUTED;
}
}
}
//-----------------------------------------------------------------------------
// Calculate the rank of the Jacobian matrix, by Gram-Schimdt orthogonalization
// in place. A row (~equation) is considered to be all zeros if its magnitude
// is less than the tolerance RANK_MAG_TOLERANCE.
//-----------------------------------------------------------------------------
int System::CalculateRank() {
// Actually work with magnitudes squared, not the magnitudes
double rowMag[MAX_UNKNOWNS] = {};
double tol = RANK_MAG_TOLERANCE*RANK_MAG_TOLERANCE;
int i, iprev, j;
int rank = 0;
for(i = 0; i < mat.m; i++) {
// Subtract off this row's component in the direction of any
// previous rows
for(iprev = 0; iprev < i; iprev++) {
if(rowMag[iprev] <= tol) continue; // ignore zero rows
double dot = 0;
for(j = 0; j < mat.n; j++) {
dot += (mat.A.num[iprev][j]) * (mat.A.num[i][j]);
}
for(j = 0; j < mat.n; j++) {
mat.A.num[i][j] -= (dot/rowMag[iprev])*mat.A.num[iprev][j];
}
}
// Our row is now normal to all previous rows; calculate the
// magnitude of what's left
double mag = 0;
for(j = 0; j < mat.n; j++) {
mag += (mat.A.num[i][j]) * (mat.A.num[i][j]);
}
if(mag > tol) {
rank++;
}
rowMag[i] = mag;
}
return rank;
}
bool System::TestRank() {
EvalJacobian();
return CalculateRank() == mat.m;
}
bool System::SolveLinearSystem(double X[], double A[][MAX_UNKNOWNS],
double B[], int n)
{
// Gaussian elimination, with partial pivoting. It's an error if the
// matrix is singular, because that means two constraints are
// equivalent.
int i, j, ip, jp, imax = 0;
double max, temp;
for(i = 0; i < n; i++) {
// We are trying eliminate the term in column i, for rows i+1 and
// greater. First, find a pivot (between rows i and N-1).
max = 0;
for(ip = i; ip < n; ip++) {
if(ffabs(A[ip][i]) > max) {
imax = ip;
max = ffabs(A[ip][i]);
}
}
// Don't give up on a singular matrix unless it's really bad; the
// assumption code is responsible for identifying that condition,
// so we're not responsible for reporting that error.
if(ffabs(max) < 1e-20) continue;
// Swap row imax with row i
for(jp = 0; jp < n; jp++) {
swap(A[i][jp], A[imax][jp]);
}
swap(B[i], B[imax]);
// For rows i+1 and greater, eliminate the term in column i.
for(ip = i+1; ip < n; ip++) {
temp = A[ip][i]/A[i][i];
for(jp = i; jp < n; jp++) {
A[ip][jp] -= temp*(A[i][jp]);
}
B[ip] -= temp*B[i];
}
}
// We've put the matrix in upper triangular form, so at this point we
// can solve by back-substitution.
for(i = n - 1; i >= 0; i--) {
if(ffabs(A[i][i]) < 1e-20) continue;
temp = B[i];
for(j = n - 1; j > i; j--) {
temp -= X[j]*A[i][j];
}
X[i] = temp / A[i][i];
}
return true;
}
bool System::SolveLeastSquares() {
int r, c, i;
// Scale the columns; this scale weights the parameters for the least
// squares solve, so that we can encourage the solver to make bigger
// changes in some parameters, and smaller in others.
for(c = 0; c < mat.n; c++) {
if(IsDragged(mat.param[c])) {
// It's least squares, so this parameter doesn't need to be all
// that big to get a large effect.
mat.scale[c] = 1/20.0;
} else {
mat.scale[c] = 1;
}
for(r = 0; r < mat.m; r++) {
mat.A.num[r][c] *= mat.scale[c];
}
}
// Write A*A'
for(r = 0; r < mat.m; r++) {
for(c = 0; c < mat.m; c++) { // yes, AAt is square
double sum = 0;
for(i = 0; i < mat.n; i++) {
sum += mat.A.num[r][i]*mat.A.num[c][i];
}
mat.AAt[r][c] = sum;
}
}
if(!SolveLinearSystem(mat.Z, mat.AAt, mat.B.num, mat.m)) return false;
// And multiply that by A' to get our solution.
for(c = 0; c < mat.n; c++) {
double sum = 0;
for(i = 0; i < mat.m; i++) {
sum += mat.A.num[i][c]*mat.Z[i];
}
mat.X[c] = sum * mat.scale[c];
}
return true;
}
bool System::NewtonSolve(int tag) {
int iter = 0;
bool converged = false;
int i;
// Evaluate the functions at our operating point.
for(i = 0; i < mat.m; i++) {
mat.B.num[i] = (mat.B.sym[i])->Eval();
}
do {
// And evaluate the Jacobian at our initial operating point.
EvalJacobian();
if(!SolveLeastSquares()) break;
// Take the Newton step;
// J(x_n) (x_{n+1} - x_n) = 0 - F(x_n)
for(i = 0; i < mat.n; i++) {
Param *p = param.FindById(mat.param[i]);
p->val -= mat.X[i];
if(isnan(p->val)) {
// Very bad, and clearly not convergent
return false;
}
}
// Re-evalute the functions, since the params have just changed.
for(i = 0; i < mat.m; i++) {
mat.B.num[i] = (mat.B.sym[i])->Eval();
}
// Check for convergence
converged = true;
for(i = 0; i < mat.m; i++) {
if(isnan(mat.B.num[i])) {
return false;
}
if(ffabs(mat.B.num[i]) > CONVERGE_TOLERANCE) {
converged = false;
break;
}
}
} while(iter++ < 50 && !converged);
return converged;
}
void System::WriteEquationsExceptFor(hConstraint hc, Group *g) {
int i;
// Generate all the equations from constraints in this group
for(i = 0; i < SK.constraint.n; i++) {
ConstraintBase *c = &(SK.constraint.elem[i]);
if(c->group.v != g->h.v) continue;
if(c->h.v == hc.v) continue;
if(c->HasLabel() && c->type != Constraint::Type::COMMENT &&
g->allDimsReference)
{
// When all dimensions are reference, we adjust them to display
// the correct value, and then don't generate any equations.
c->ModifyToSatisfy();
continue;
}
if(g->relaxConstraints && c->type != Constraint::Type::POINTS_COINCIDENT) {
// When the constraints are relaxed, we keep only the point-
// coincident constraints, and the constraints generated by
// the entities and groups.
continue;
}
c->GenerateEquations(&eq);
}
// And the equations from entities
for(i = 0; i < SK.entity.n; i++) {
EntityBase *e = &(SK.entity.elem[i]);
if(e->group.v != g->h.v) continue;
e->GenerateEquations(&eq);
}
// And from the groups themselves
g->GenerateEquations(&eq);
}
void System::FindWhichToRemoveToFixJacobian(Group *g, List<hConstraint> *bad, bool forceDofCheck) {
int a, i;
for(a = 0; a < 2; a++) {
for(i = 0; i < SK.constraint.n; i++) {
ConstraintBase *c = &(SK.constraint.elem[i]);
if(c->group.v != g->h.v) continue;
if((c->type == Constraint::Type::POINTS_COINCIDENT && a == 0) ||
(c->type != Constraint::Type::POINTS_COINCIDENT && a == 1))
{
// Do the constraints in two passes: first everything but
// the point-coincident constraints, then only those
// constraints (so they appear last in the list).
continue;
}
param.ClearTags();
eq.Clear();
WriteEquationsExceptFor(c->h, g);
eq.ClearTags();
// It's a major speedup to solve the easy ones by substitution here,
// and that doesn't break anything.
if(!forceDofCheck) {
SolveBySubstitution();
}
WriteJacobian(0);
EvalJacobian();
int rank = CalculateRank();
if(rank == mat.m) {
// We fixed it by removing this constraint
bad->Add(&(c->h));
}
}
}
}
SolveResult System::Solve(Group *g, int *dof, List<hConstraint> *bad,
bool andFindBad, bool andFindFree, bool forceDofCheck)
{
WriteEquationsExceptFor(Constraint::NO_CONSTRAINT, g);
int i;
bool rankOk;
/*
dbp("%d equations", eq.n);
for(i = 0; i < eq.n; i++) {
dbp(" %.3f = %s = 0", eq.elem[i].e->Eval(), eq.elem[i].e->Print());
}
dbp("%d parameters", param.n);
for(i = 0; i < param.n; i++) {
dbp(" param %08x at %.3f", param.elem[i].h.v, param.elem[i].val);
} */
// All params and equations are assigned to group zero.
param.ClearTags();
eq.ClearTags();
if(!forceDofCheck) {
SolveBySubstitution();
}
// Before solving the big system, see if we can find any equations that
// are soluble alone. This can be a huge speedup. We don't know whether
// the system is consistent yet, but if it isn't then we'll catch that
// later.
int alone = 1;
for(i = 0; i < eq.n; i++) {
Equation *e = &(eq.elem[i]);
if(e->tag != 0) continue;
hParam hp = e->e->ReferencedParams(&param);
if(hp.v == Expr::NO_PARAMS.v) continue;
if(hp.v == Expr::MULTIPLE_PARAMS.v) continue;
Param *p = param.FindById(hp);
if(p->tag != 0) continue; // let rank test catch inconsistency
e->tag = alone;
p->tag = alone;
WriteJacobian(alone);
if(!NewtonSolve(alone)) {
// We don't do the rank test, so let's arbitrarily return
// the DIDNT_CONVERGE result here.
rankOk = true;
// Failed to converge, bail out early
goto didnt_converge;
}
alone++;
}
// Now write the Jacobian for what's left, and do a rank test; that
// tells us if the system is inconsistently constrained.
if(!WriteJacobian(0)) {
return SolveResult::TOO_MANY_UNKNOWNS;
}
rankOk = TestRank();
// And do the leftovers as one big system
if(!NewtonSolve(0)) {
goto didnt_converge;
}
rankOk = TestRank();
if(!rankOk) {
if(!g->allowRedundant) {
if(andFindBad) FindWhichToRemoveToFixJacobian(g, bad, forceDofCheck);
}
} else {
// This is not the full Jacobian, but any substitutions or single-eq
// solves removed one equation and one unknown, therefore no effect
// on the number of DOF.
if(dof) *dof = CalculateDof();
MarkParamsFree(andFindFree);
}
// System solved correctly, so write the new values back in to the
// main parameter table.
for(i = 0; i < param.n; i++) {
Param *p = &(param.elem[i]);
double val;
if(p->tag == VAR_SUBSTITUTED) {
val = param.FindById(p->substd)->val;
} else {
val = p->val;
}
Param *pp = SK.GetParam(p->h);
pp->val = val;
pp->known = true;
pp->free = p->free;
}
return rankOk ? SolveResult::OKAY : SolveResult::REDUNDANT_OKAY;
didnt_converge:
SK.constraint.ClearTags();
for(i = 0; i < eq.n; i++) {
if(ffabs(mat.B.num[i]) > CONVERGE_TOLERANCE || isnan(mat.B.num[i])) {
// This constraint is unsatisfied.
if(!mat.eq[i].isFromConstraint()) continue;
hConstraint hc = mat.eq[i].constraint();
ConstraintBase *c = SK.constraint.FindByIdNoOops(hc);
if(!c) continue;
// Don't double-show constraints that generated multiple
// unsatisfied equations
if(!c->tag) {
bad->Add(&(c->h));
c->tag = 1;
}
}
}
return rankOk ? SolveResult::DIDNT_CONVERGE : SolveResult::REDUNDANT_DIDNT_CONVERGE;
}
SolveResult System::SolveRank(Group *g, int *dof, List<hConstraint> *bad,
bool andFindBad, bool andFindFree, bool forceDofCheck)
{
WriteEquationsExceptFor(Constraint::NO_CONSTRAINT, g);
// All params and equations are assigned to group zero.
param.ClearTags();
eq.ClearTags();
if(!forceDofCheck) {
SolveBySubstitution();
}
// Now write the Jacobian, and do a rank test; that
// tells us if the system is inconsistently constrained.
if(!WriteJacobian(0)) {
return SolveResult::TOO_MANY_UNKNOWNS;
}
bool rankOk = TestRank();
if(!rankOk) {
if(!g->allowRedundant) {
if(andFindBad) FindWhichToRemoveToFixJacobian(g, bad, forceDofCheck);
}
} else {
// This is not the full Jacobian, but any substitutions or single-eq
// solves removed one equation and one unknown, therefore no effect
// on the number of DOF.
if(dof) *dof = CalculateDof();
MarkParamsFree(andFindFree);
}
return rankOk ? SolveResult::OKAY : SolveResult::REDUNDANT_OKAY;
}
void System::Clear() {
entity.Clear();
param.Clear();
eq.Clear();
dragged.Clear();
}
void System::MarkParamsFree(bool find) {
// If requested, find all the free (unbound) variables. This might be
// more than the number of degrees of freedom. Don't always do this,
// because the display would get annoying and it's slow.
for(int i = 0; i < param.n; i++) {
Param *p = &(param.elem[i]);
p->free = false;
if(find) {
if(p->tag == 0) {
p->tag = VAR_DOF_TEST;
WriteJacobian(0);
EvalJacobian();
int rank = CalculateRank();
if(rank == mat.m) {
p->free = true;
}
p->tag = 0;
}
}
}
}
int System::CalculateDof() {
return mat.n - mat.m;
}
File diff suppressed because it is too large Load Diff
+12 -1
View File
@@ -107,6 +107,13 @@ set(SLIC3R_GUI_SOURCES
GUI/Downloader.hpp
GUI/DownloadProgressDialog.cpp
GUI/DownloadProgressDialog.hpp
GUI/DesignPanel.cpp
GUI/DesignPanel.hpp
GUI/DesignCanvas.cpp
GUI/DesignCanvas.hpp
GUI/DesignSketchTool.cpp
GUI/SketchInlineEditor.cpp
GUI/SketchInlineEditor.hpp
GUI/DragCanvas.cpp
GUI/DragCanvas.hpp
GUI/EditGCodeDialog.cpp
@@ -156,6 +163,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Gizmos/GLGizmoMove.hpp
GUI/Gizmos/GLGizmoPainterBase.cpp
GUI/Gizmos/GLGizmoPainterBase.hpp
GUI/Gizmos/GLGizmoPrimitive.cpp
GUI/Gizmos/GLGizmoPrimitive.hpp
GUI/Gizmos/GLGizmoRotate.cpp
GUI/Gizmos/GLGizmoRotate.hpp
GUI/Gizmos/GLGizmoScale.cpp
@@ -166,6 +175,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Gizmos/GLGizmoSeam.hpp
GUI/Gizmos/GLGizmoSimplify.cpp
GUI/Gizmos/GLGizmoSimplify.hpp
GUI/Gizmos/GLGizmoSketch.cpp
GUI/Gizmos/GLGizmoSketch.hpp
#GUI/Gizmos/GLGizmoSlaSupports.cpp
#GUI/Gizmos/GLGizmoSlaSupports.hpp
GUI/Gizmos/GLGizmosManager.cpp
@@ -746,7 +757,7 @@ if (WIN32)
else ()
add_library(libslic3r_gui STATIC ${SLIC3R_GUI_SOURCES})
endif ()
target_include_directories(libslic3r_gui PRIVATE Utils ${CMAKE_CURRENT_BINARY_DIR})
target_include_directories(libslic3r_gui PRIVATE Utils ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/GUI)
if (WIN32)
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../deps/WebView2/include)
+885
View File
@@ -0,0 +1,885 @@
#include "DesignCanvas.hpp"
#include "SketchInlineEditor.hpp"
#include "GLCanvas3D.hpp"
#include "OpenGLManager.hpp"
#include "3DBed.hpp"
#include "GUI_App.hpp"
#include "Plater.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/TriangleMesh.hpp"
#include "3DScene.hpp"
#include "libslic3r/Config.hpp"
#include <wx/glcanvas.h>
#include <wx/sizer.h>
#include <wx/frame.h>
#include <wx/stattext.h>
#include <wx/toplevel.h>
namespace Slic3r {
namespace GUI {
DesignCanvas::DesignCanvas(wxWindow* parent)
: wxPanel()
{
if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0))
return;
m_canvas_widget = OpenGLManager::create_wxglcanvas(*this);
if (m_canvas_widget == nullptr)
return;
m_canvas = new GLCanvas3D(m_canvas_widget, m_bed);
m_canvas->set_context(wxGetApp().init_glcontext(*m_canvas_widget));
m_canvas->allow_multisample(OpenGLManager::can_multisample());
m_canvas->set_config(wxGetApp().plater()->config());
m_canvas->set_model(&m_model);
// Reuse the editor's shared slicing process: GLCanvas3D::render() (via
// _max_bounding_box) dereferences the process when canvas type == View3D.
// Passing nullptr segfaults; this mirrors View3D/Preview/AssembleView.
m_canvas->set_process(wxGetApp().plater()->get_background_process());
m_canvas->set_type(GLCanvas3D::ECanvasType::CanvasView3D);
m_canvas->enable_picking(false); // viewport face/edge picking is custom (TODO)
m_canvas->enable_moving(false);
m_canvas->enable_gizmos(false);
m_canvas->enable_selection(false); // stock volume selection unused; solid highlight is tree-driven
m_canvas->enable_main_toolbar(false);
m_canvas->enable_select_plate_toolbar(false);
m_canvas->enable_assemble_view_toolbar(false);
m_canvas->enable_separator_toolbar(false);
m_canvas->enable_collapse_toolbar(false);
m_canvas->enable_plate_chrome(false);
m_canvas->enable_labels(false);
m_canvas->set_design_sketch_tool(&m_sketch_tool);
m_sketch_tool.on_commit = [this](const SketchProfile& prof, const SketchPlane& pl) {
if (m_on_sketch_commit) m_on_sketch_commit(prof, pl);
if (m_canvas) m_canvas->set_as_dirty();
if (m_canvas_widget) m_canvas_widget->Refresh();
};
m_sketch_tool.on_commit_entities = [this](const std::vector<SketchEntity>& ents,
const std::vector<SketchEntityConstraintDef>& cons,
const SketchPlane& pl) {
if (m_on_sketch_entities_commit) m_on_sketch_entities_commit(ents, cons, pl);
if (m_canvas) m_canvas->set_as_dirty();
if (m_canvas_widget) m_canvas_widget->Refresh();
};
// Onshape-style in-canvas value editor, floating over the GL canvas. The tool hands
// us a screen pixel (device px) + a commit/cancel pair; we convert to logical client
// px and wrap the callbacks so each one re-solves and re-renders the viewport.
m_inline_editor = std::make_unique<SketchInlineEditor>(m_canvas_widget);
m_sketch_tool.on_inline_edit = [this](wxPoint screen_px, double current,
std::function<void(double)> commit,
std::function<void()> cancel) {
if (!m_inline_editor) { if (cancel) cancel(); return; }
// The tool hands us canvas device px; convert to logical client px, then to
// absolute screen coords for the floating editor frame.
const double s = m_canvas_widget ? m_canvas_widget->GetContentScaleFactor() : 1.0;
const wxPoint client_pt(int(screen_px.x / s), int(screen_px.y / s));
const wxPoint scr = m_canvas_widget ? m_canvas_widget->ClientToScreen(client_pt) : client_pt;
// Freeze the sketch tool while the field is open so a stray click/move on the GL
// canvas can't draw under the floating editor; released on commit or cancel.
m_sketch_tool.set_inline_busy(true);
m_inline_editor->open(scr, current,
[this, commit](double v) {
m_sketch_tool.set_inline_busy(false);
if (commit) commit(v);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
},
[this, cancel]() {
m_sketch_tool.set_inline_busy(false);
if (cancel) cancel();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
});
};
// Let the tool force-close the field (keep-as-drawn) — polyline right-click/double-click
// ends the chain even while a per-segment value field is open.
m_sketch_tool.on_inline_dismiss = [this]() {
if (m_inline_editor) m_inline_editor->cancel();
};
// Bottom-right viewport HUD: a borderless, non-focusable float label showing the active
// tool's current values. Top-level (a child widget is hidden by the GL surface, same as
// the inline editor). Fed every frame by the tool's on_readout; empty text hides it.
{
wxWindow* top = wxGetTopLevelParent(m_canvas_widget);
m_hud = new wxFrame(top, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize,
wxFRAME_NO_TASKBAR | wxBORDER_NONE | wxFRAME_FLOAT_ON_PARENT |
wxSTAY_ON_TOP | wxTRANSPARENT_WINDOW);
m_hud->SetBackgroundColour(wxColour(28, 30, 34));
m_hud_label = new wxStaticText(m_hud, wxID_ANY, wxEmptyString);
m_hud_label->SetForegroundColour(wxColour(0x46, 0xE0, 0xC8)); // teal, reads on dark bed
wxFont f = m_hud_label->GetFont(); f.MakeBold(); m_hud_label->SetFont(f);
auto* hs = new wxBoxSizer(wxHORIZONTAL);
hs->Add(m_hud_label, 0, wxALL, 6);
m_hud->SetSizerAndFit(hs);
m_hud->Hide();
}
m_sketch_tool.on_readout = [this](const std::string& s) { set_readout(s); };
refresh_bed();
m_canvas->bind_event_handlers();
// The Design GL canvas only receives key events (Esc to exit/enter Select, Ctrl+Z undo)
// while it holds keyboard focus. Clicking a side-panel button steals focus, after which
// Esc/Ctrl+Z silently do nothing until the viewport is clicked again. Restore focus
// whenever the pointer enters the viewport (focus-follows-mouse, standard CAD behaviour).
m_canvas_widget->Bind(wxEVT_ENTER_WINDOW, [this](wxMouseEvent& e) {
// …but NOT while an inline value field is open: the field floats over the canvas, so
// the smallest pointer jiggle re-enters the viewport and would yank focus off the
// field (the "no cursor focus on the number, click to focus" bug).
if (m_canvas_widget && !m_sketch_tool.inline_busy()) m_canvas_widget->SetFocus();
e.Skip();
});
auto* sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(m_canvas_widget, 1, wxEXPAND);
SetSizer(sizer);
SetMinSize(wxSize(300, 300));
}
DesignCanvas::~DesignCanvas()
{
if (m_hud) m_hud->Destroy();
delete m_canvas;
delete m_canvas_widget;
}
// Distinct per-body colours (Onshape-style). Body 0 keeps the familiar gold; the rest
// cycle through a small saturated palette so coexisting solids read as separate parts.
static ColorRGBA body_palette(int body_idx)
{
static const ColorRGBA kPalette[] = {
ColorRGBA(0.86f, 0.66f, 0.20f, 1.0f), // gold
ColorRGBA(0.30f, 0.62f, 0.90f, 1.0f), // blue
ColorRGBA(0.45f, 0.78f, 0.42f, 1.0f), // green
ColorRGBA(0.86f, 0.45f, 0.40f, 1.0f), // coral
ColorRGBA(0.70f, 0.52f, 0.86f, 1.0f), // violet
ColorRGBA(0.90f, 0.70f, 0.35f, 1.0f), // amber
};
const int n = int(sizeof(kPalette) / sizeof(kPalette[0]));
return kPalette[((body_idx % n) + n) % n];
}
void DesignCanvas::reload(bool keep_view)
{
m_canvas->reset_volumes();
for (int i = 0; i < (int)m_model.objects.size(); ++i)
m_canvas->load_object(m_model, i);
const ColorRGBA sel_gold(0.40f, 0.82f, 1.0f, 1.0f); // cyan tint = solid selected
const ColorRGBA ghost(0.26f, 0.66f, 1.0f, 0.45f);
const auto& volumes = m_canvas->get_volumes().volumes;
for (auto* v : volumes) {
int obj_idx = v->object_idx();
if (obj_idx == 0) {
// Object 0 holds one volume per body — colour each by its body index so
// multiple coexisting solids are visually distinct (Onshape per-part colour).
const int b = v->volume_idx();
bool hidden = (b >= 0 && b < int(m_body_visible.size())) && !m_body_visible[b];
// Preview-only mode (fillet/chamfer/draft, once a valid target is picked): hide
// every base body so only the result ghost is on screen until Confirm.
if (m_body_hidden) hidden = true;
v->is_active = !hidden; // per-body visibility toggle
if (!hidden) {
// Selection tint wins; otherwise the per-body override (Color tool) or the
// auto palette via body_color().
ColorRGBA c = m_body_selected ? sel_gold : body_color(b);
if (m_body_translucent) c.a(0.30f);
v->set_color(c);
}
} else if (obj_idx == 1) {
// The ghost is normally a faint blue overlay on the visible body. In preview-only
// mode it IS the result (base bodies hidden), so render it opaque so it reads as a
// finished solid rather than a see-through hint.
v->set_color(m_body_hidden ? ColorRGBA(0.40f, 0.82f, 1.0f, 1.0f) : ghost);
}
}
if (!keep_view) {
if (m_first_frame && !m_model.objects.empty()) {
m_canvas->select_view("iso");
m_canvas->zoom_to_volumes();
m_first_frame = false;
}
}
m_canvas->set_as_dirty();
if (m_canvas_widget)
m_canvas_widget->Refresh();
}
void DesignCanvas::set_mesh(const TriangleMesh& mesh)
{
if (m_model.objects.empty()) {
auto* obj = m_model.add_object();
obj->add_volume(mesh);
obj->add_instance();
} else {
ModelObject* obj = m_model.objects.front();
obj->clear_volumes();
obj->add_volume(mesh);
if (obj->instances.empty())
obj->add_instance();
}
reload(!m_first_frame);
}
void DesignCanvas::set_bodies(const std::vector<TriangleMesh>& body_meshes,
const std::vector<bool>& visible)
{
// Object 0 carries one GLVolume per body so reload() can colour each distinctly.
// Falls back to a single-volume object when there's only one body (identical look
// to the old set_mesh path). Picking still uses the combined mesh via set_solid_pick.
m_body_visible = visible; // empty => all visible; reload() reads this per volume
if (body_meshes.empty()) { clear_mesh(); return; }
ModelObject* obj = m_model.objects.empty() ? m_model.add_object()
: m_model.objects.front();
obj->clear_volumes();
for (const TriangleMesh& m : body_meshes)
obj->add_volume(m);
if (obj->instances.empty())
obj->add_instance();
reload(!m_first_frame);
}
void DesignCanvas::clear_mesh()
{
if (!m_model.objects.empty()) {
m_model.delete_object((size_t)0);
reload(true);
}
}
void DesignCanvas::set_preview_mesh(const TriangleMesh& mesh)
{
// Remove existing ghost (object 1) if present
if (m_model.objects.size() > 1)
m_model.delete_object((size_t)1);
auto* obj = m_model.add_object();
obj->add_volume(mesh);
obj->add_instance();
reload(true);
}
void DesignCanvas::clear_preview()
{
if (m_model.objects.size() > 1) {
m_model.delete_object((size_t)1);
reload(true);
}
}
void DesignCanvas::fit_view()
{
if (m_canvas && !m_model.objects.empty()) {
m_canvas->zoom_to_volumes();
m_canvas->set_as_dirty();
if (m_canvas_widget)
m_canvas_widget->Refresh();
}
}
void DesignCanvas::set_view(const std::string& view_name)
{
if (m_canvas) {
m_canvas->select_view(view_name);
m_canvas->zoom_to_volumes();
m_canvas->set_as_dirty();
if (m_canvas_widget)
m_canvas_widget->Refresh();
}
}
void DesignCanvas::begin_sketch(const SketchPlane& plane, DesignSketchTool::Mode mode)
{
m_sketch_tool.begin(plane, mode);
if (m_canvas) m_canvas->set_as_dirty();
if (m_canvas_widget) m_canvas_widget->Refresh();
}
void DesignCanvas::edit_sketch(const std::vector<SketchEntity>& entities,
const std::vector<SketchEntityConstraintDef>& constraints,
const SketchPlane& plane)
{
m_sketch_tool.begin_edit(entities, constraints, plane);
if (m_canvas) m_canvas->set_as_dirty();
if (m_canvas_widget) m_canvas_widget->Refresh();
}
void DesignCanvas::set_sketch_tool(DesignSketchTool::Mode mode)
{
m_sketch_tool.set_tool(mode);
if (m_canvas) m_canvas->set_as_dirty();
if (m_canvas_widget) m_canvas_widget->Refresh();
}
void DesignCanvas::set_sketch_construction(bool c)
{
m_sketch_tool.set_construction(c);
}
void DesignCanvas::set_sketch_polygon_sides(int n)
{
m_sketch_tool.set_polygon_sides(n);
}
void DesignCanvas::set_sketch_polygon_circumscribed(bool c)
{
m_sketch_tool.set_polygon_circumscribed(c);
}
void DesignCanvas::finish_sketch()
{
m_sketch_tool.finish();
if (m_canvas) m_canvas->set_as_dirty();
if (m_canvas_widget) m_canvas_widget->Refresh();
}
// Sync the Design bed to the CURRENT printer bed. Done on every tab activation, not just at
// construction: the panel is built early (before the active printer profile is fully applied),
// so a one-shot read picked up the 200x200 default while the real bed (e.g. 270x270) only
// loaded later — leaving the PartPlate grid spilling past the smaller bed quad.
void DesignCanvas::refresh_bed()
{
const DynamicPrintConfig* config = wxGetApp().plater()->config();
if (!config) return;
const auto* bed_shape_opt = config->opt<ConfigOptionPoints>("printable_area");
if (!bed_shape_opt) return;
double printable_height = 100.0;
const auto* ph_opt = config->opt<ConfigOptionFloat>("printable_height");
if (ph_opt) printable_height = ph_opt->value;
m_bed.set_shape(bed_shape_opt->values, printable_height, "", false);
}
bool DesignCanvas::is_sketching() const { return m_sketch_tool.is_active(); }
void DesignCanvas::cancel_sketch()
{
m_sketch_tool.cancel();
if (m_canvas) m_canvas->set_as_dirty();
if (m_canvas_widget) m_canvas_widget->Refresh();
}
void DesignCanvas::set_on_sketch_commit(std::function<void(const SketchProfile&, const SketchPlane&)> cb)
{
m_on_sketch_commit = std::move(cb);
}
void DesignCanvas::set_on_sketch_entities_commit(
std::function<void(const std::vector<SketchEntity>&,
const std::vector<SketchEntityConstraintDef>&,
const SketchPlane&)> cb)
{
m_on_sketch_entities_commit = std::move(cb);
}
void DesignCanvas::set_on_segment_drawn(std::function<void(double, double)> cb)
{
m_sketch_tool.on_segment_drawn = std::move(cb);
}
void DesignCanvas::set_on_cursor_metrics(std::function<void(double, double, bool)> cb)
{
m_sketch_tool.on_cursor_metrics = std::move(cb);
}
void DesignCanvas::set_on_solve_state(std::function<void(int, bool, bool)> cb)
{
m_sketch_tool.on_solve_state = std::move(cb);
}
void DesignCanvas::apply_segment_length(double len)
{
m_sketch_tool.apply_segment_length(len);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
}
void DesignCanvas::keep_segment_as_drawn()
{
m_sketch_tool.keep_segment_as_drawn();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
}
void DesignCanvas::set_on_sketch_selection_changed(std::function<void(int)> cb)
{
m_sketch_tool.on_selection_changed = std::move(cb);
}
void DesignCanvas::set_on_sketch_face_selected(std::function<void()> cb)
{
m_sketch_tool.on_face_selected = std::move(cb);
}
void DesignCanvas::set_on_display_sketch_selected(std::function<void(int, int)> cb)
{
m_sketch_tool.on_display_sketch_selected = std::move(cb);
}
std::vector<SketchEntity> DesignCanvas::selected_loop_entities() const
{
return m_sketch_tool.selected_loop_entities();
}
std::vector<std::vector<int>> DesignCanvas::region_entity_indices(const std::vector<SketchEntity>& ents) const
{
return m_sketch_tool.region_entity_indices(ents);
}
void DesignCanvas::clear_loop_pick()
{
m_sketch_tool.clear_display_pick();
}
void DesignCanvas::set_solid_pick(const std::vector<CadBody>* bodies, const TriangleMesh* mesh,
const std::vector<int>* tri_face, const std::vector<int>* tri_body,
const std::vector<bool>* visible,
const std::vector<Transform3d>* xform)
{
m_color_bodies = bodies; // stable address (m_doc.bodies); reload() reads colour overrides
m_sketch_tool.set_solid_pick(bodies, mesh, tri_face, tri_body, visible, xform);
}
// Effective display colour for a body: per-body override (Color tool) when set, else the
// auto body-index palette. body_palette() is the file-static helper defined above reload().
ColorRGBA DesignCanvas::body_color(int body) const
{
if (m_color_bodies != nullptr && body >= 0 && body < int(m_color_bodies->size())
&& (*m_color_bodies)[body].has_color)
return (*m_color_bodies)[body].color;
return body_palette(body);
}
void DesignCanvas::begin_move_body(int body, const Vec3d& pivot, const Transform3d& base_xform)
{
m_sketch_tool.set_move_gizmo(body, pivot, base_xform);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } // llvmpipe: force repaint
}
void DesignCanvas::clear_move_gizmo()
{
m_sketch_tool.clear_move_gizmo();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
}
bool DesignCanvas::moving_body() const { return m_sketch_tool.moving_body(); }
void DesignCanvas::set_on_body_move_changed(std::function<void(int, const Transform3d&)> cb)
{
m_sketch_tool.on_body_move_changed = std::move(cb);
}
bool DesignCanvas::begin_fillet_gizmo(const Vec3d& body_centroid, double radius)
{
const bool ok = m_sketch_tool.set_fillet_gizmo(body_centroid, radius);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } // llvmpipe: force repaint
return ok;
}
void DesignCanvas::clear_fillet_gizmo()
{
m_sketch_tool.clear_fillet_gizmo();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
}
bool DesignCanvas::filleting() const { return m_sketch_tool.filleting(); }
void DesignCanvas::set_on_fillet_radius_changed(std::function<void(double)> cb)
{
m_sketch_tool.on_fillet_radius_changed = std::move(cb);
}
void DesignCanvas::begin_hole_gizmo(const SketchPlane& plane, double x, double y,
double diameter, double depth, bool through)
{
m_sketch_tool.set_hole_gizmo(plane, x, y, diameter, depth, through);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } // llvmpipe: force repaint
}
void DesignCanvas::set_hole_face_bounds(bool has, double umin, double umax, double vmin, double vmax)
{
m_sketch_tool.set_hole_face_bounds(has, umin, umax, vmin, vmax);
}
void DesignCanvas::clear_hole_gizmo()
{
m_sketch_tool.clear_hole_gizmo();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
}
bool DesignCanvas::holing() const { return m_sketch_tool.holing(); }
void DesignCanvas::set_on_hole_changed(std::function<void(double, double, double, double)> cb)
{
m_sketch_tool.on_hole_changed = std::move(cb);
}
void DesignCanvas::begin_thread_gizmo(const SketchPlane& plane, double x, double y,
double radius, double height)
{
m_sketch_tool.set_thread_gizmo(plane, x, y, radius, height);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } // llvmpipe: force repaint
}
void DesignCanvas::clear_thread_gizmo()
{
m_sketch_tool.clear_thread_gizmo();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
}
bool DesignCanvas::threading() const { return m_sketch_tool.threading(); }
void DesignCanvas::set_on_thread_changed(std::function<void(double, double, double, double)> cb)
{
m_sketch_tool.on_thread_changed = std::move(cb);
}
void DesignCanvas::begin_shell_gizmo(const Vec3d& face_centroid, const Vec3d& inward_dir,
double thickness)
{
m_sketch_tool.set_shell_gizmo(face_centroid, inward_dir, thickness);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } // llvmpipe: force repaint
}
void DesignCanvas::clear_shell_gizmo()
{
m_sketch_tool.clear_shell_gizmo();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
}
bool DesignCanvas::shelling() const { return m_sketch_tool.shelling(); }
void DesignCanvas::set_on_shell_thickness_changed(std::function<void(double)> cb)
{
m_sketch_tool.on_shell_thickness_changed = std::move(cb);
}
void DesignCanvas::begin_revolve_gizmo(const SketchPlane& plane, const Vec2d& centroid,
int axis_sel, double angle, bool flip)
{
m_sketch_tool.set_revolve_gizmo(plane, centroid, axis_sel, angle, flip);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } // llvmpipe: force repaint
}
void DesignCanvas::clear_revolve_gizmo()
{
m_sketch_tool.clear_revolve_gizmo();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
}
bool DesignCanvas::revolving() const { return m_sketch_tool.revolving(); }
void DesignCanvas::set_on_revolve_angle_changed(std::function<void(double)> cb)
{
m_sketch_tool.on_revolve_angle_changed = std::move(cb);
}
void DesignCanvas::begin_pattern_gizmo(const SketchPlane& plane, const Vec3d& body_centroid,
bool circular, int count, int dir, double spacing, double angle)
{
m_sketch_tool.set_pattern_gizmo(plane, body_centroid, circular, count, dir, spacing, angle);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } // llvmpipe: force repaint
}
void DesignCanvas::clear_pattern_gizmo()
{
m_sketch_tool.clear_pattern_gizmo();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
}
bool DesignCanvas::patterning() const { return m_sketch_tool.patterning(); }
void DesignCanvas::set_on_pattern_changed(std::function<void(double)> cb)
{
m_sketch_tool.on_pattern_changed = std::move(cb);
}
void DesignCanvas::set_on_solid_selection_changed(std::function<void(int, int, int, int)> cb)
{
m_sketch_tool.on_solid_selection_changed = std::move(cb);
}
void DesignCanvas::set_on_place_on_face(std::function<bool()> cb)
{
m_sketch_tool.on_place_on_face = std::move(cb);
}
void DesignCanvas::select_body(int body)
{
m_sketch_tool.select_body(body);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } // redraw the overlay (llvmpipe)
}
void DesignCanvas::set_extrude_gizmo(const SketchPlane& plane, const Vec2d& centroid,
double depth, double depth2, bool two_sided, bool flip)
{
m_sketch_tool.set_extrude_gizmo(plane, centroid, depth, depth2, two_sided, flip);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } // llvmpipe: force repaint
}
void DesignCanvas::clear_extrude_gizmo()
{
m_sketch_tool.clear_extrude_gizmo();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
}
void DesignCanvas::set_on_extrude_depth_changed(std::function<void(double, bool)> cb)
{
m_sketch_tool.on_extrude_depth_changed = std::move(cb);
}
void DesignCanvas::set_on_sketch_exit(std::function<void()> cb)
{
m_sketch_tool.on_exit = std::move(cb);
}
void DesignCanvas::set_on_move_exit(std::function<void()> cb)
{
m_sketch_tool.on_move_exit = std::move(cb);
}
void DesignCanvas::set_on_undo_redo(std::function<void(bool)> cb)
{
m_sketch_tool.on_undo_redo = std::move(cb);
}
void DesignCanvas::set_display_sketches(std::vector<DesignSketchTool::DisplaySketch> ds)
{
m_sketch_tool.set_display_sketches(std::move(ds));
// Direct render: under llvmpipe a scheduled Refresh() often doesn't repaint
// unless some other event (e.g. a modal close) forces it, so programmatic
// overlay changes (hide/show, re-solve) could leave a stale overlay.
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
}
void DesignCanvas::set_datum_planes(std::vector<SketchPlane> planes)
{
m_sketch_tool.set_datum_planes(std::move(planes));
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); } // llvmpipe: force repaint
}
void DesignCanvas::set_readout(const std::string& text)
{
if (!m_hud || !m_hud_label || !m_canvas_widget) return;
if (text == m_hud_last) return; // only touch the WM on a real change
m_hud_last = text;
if (text.empty()) { m_hud->Hide(); return; }
m_hud_label->SetLabel(wxString::FromUTF8(text));
m_hud->Fit();
// Anchor to the canvas's bottom-right corner with a small margin (screen coords).
const wxSize cs = m_canvas_widget->GetClientSize();
const wxSize hs = m_hud->GetSize();
const wxPoint br = m_canvas_widget->ClientToScreen(
wxPoint(cs.GetWidth() - hs.GetWidth() - 12, cs.GetHeight() - hs.GetHeight() - 12));
if (!m_hud->IsShown()) m_hud->Show(); // Show before Move (GTK ignores pre-map Move)
m_hud->Move(br);
m_hud->Raise();
}
void DesignCanvas::set_body_highlight(bool on)
{
if (m_body_selected == on) return;
m_body_selected = on;
reload(true); // recolours the body volume (selected = cyan tint)
}
void DesignCanvas::set_body_translucent(bool on)
{
if (m_body_translucent == on) return;
m_body_translucent = on;
reload(true); // re-applies object-0 alpha so the solid fades for the fillet preview
}
void DesignCanvas::set_body_hidden(bool on)
{
if (m_body_hidden == on) return;
m_body_hidden = on;
reload(true); // hides/show base bodies + flips the ghost opaque/faint for preview-only mode
}
void DesignCanvas::delete_selected_sketch_entities()
{
m_sketch_tool.delete_selected();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
}
void DesignCanvas::clear_sketch_selection()
{
m_sketch_tool.clear_selection();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
}
DesignSketchTool::DimType DesignCanvas::sketch_dimension_kind() const
{
return m_sketch_tool.dimension_kind();
}
double DesignCanvas::sketch_dimension_current() const
{
return m_sketch_tool.dimension_current();
}
void DesignCanvas::apply_sketch_dimension(double v)
{
m_sketch_tool.apply_dimension(v);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
}
void DesignCanvas::open_inline_value(double current, std::function<void(double)> commit,
std::function<void()> cancel)
{
if (!m_inline_editor || !m_canvas_widget) { if (cancel) cancel(); return; }
// Host-driven value entry (committed-feature Constrain path): the trigger is a toolbar
// button. Anchor the field OVER the picked geometry (same as the draw-then-edit tools) when
// the tool can project it; else fall back to the viewport centre, where the sketch is in
// view. GetScreenRect collapses GetClientSize()+ClientToScreen() into one call; if the GL
// canvas reports degenerate geometry (transiently, right after a re-layout), fall back to the
// always-realised top-level window so the editor never lands in the top-left corner.
wxRect r = m_canvas_widget->GetScreenRect();
if (r.GetWidth() <= 1 || r.GetHeight() <= 1) {
if (wxWindow* top = wxGetTopLevelParent(m_canvas_widget))
r = top->GetScreenRect();
}
wxPoint scr(r.GetLeft() + r.GetWidth() / 2, r.GetTop() + r.GetHeight() / 2);
wxPoint anchor;
if (m_sketch_tool.constrain_value_anchor(anchor)) { // device px in the canvas viewport
const double s = m_canvas_widget->GetContentScaleFactor();
scr = m_canvas_widget->ClientToScreen(wxPoint(int(anchor.x / s), int(anchor.y / s)));
}
// Freeze the canvas so focus-follows-mouse can't steal keyboard focus off the field — the
// same fix the draw-then-edit path uses (cursor focus stays on the field, no pre-click).
m_sketch_tool.set_inline_busy(true);
m_inline_editor->open(scr, current,
[this, commit](double v) {
m_sketch_tool.set_inline_busy(false);
if (commit) commit(v);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
},
[this, cancel]() {
m_sketch_tool.set_inline_busy(false);
if (cancel) cancel();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
});
}
void DesignCanvas::set_on_dimension_pick_complete(std::function<void(double)> cb)
{
m_sketch_tool.on_dimension_pick_complete = std::move(cb);
}
DesignSketchTool::DimType DesignCanvas::pending_dimension_type() const
{
return m_sketch_tool.pending_dimension_type();
}
void DesignCanvas::set_sketch_dimension_value(double v)
{
m_sketch_tool.set_dimension_value(v);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
}
void DesignCanvas::cancel_sketch_dimension()
{
m_sketch_tool.cancel_dimension_value();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
}
void DesignCanvas::begin_constrain(const SketchProfile& prof, const SketchPlane& plane)
{
m_sketch_tool.begin_constrain(prof, plane);
// The overlay must appear immediately (no mouse move to trigger a repaint);
// a direct render() is the proven path under llvmpipe.
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
}
void DesignCanvas::begin_imported_transform(
int feat, const std::vector<std::vector<std::vector<Vec2d>>>& base_regions,
const SketchPlane& plane, const Vec2d& offset, double scale_x, double scale_y)
{
m_sketch_tool.begin_imported_transform(feat, base_regions, plane, offset, scale_x, scale_y);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
}
void DesignCanvas::set_on_imported_transform(std::function<void(int, Vec2d, double, double)> cb)
{
m_sketch_tool.on_imported_transform = std::move(cb);
}
void DesignCanvas::end_constrain()
{
// cancel() clears m_active + the picked-segment/entity indices, so the
// constrain overlay (highlighted picks) disappears on the next render.
m_sketch_tool.cancel();
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
}
bool DesignCanvas::is_constraining() const { return m_sketch_tool.is_constraining(); }
bool DesignCanvas::selected_segment(int& a, int& b) const
{
return m_sketch_tool.selected_segment(a, b);
}
void DesignCanvas::update_constrain_profile(const std::vector<Vec2d>& pts)
{
m_sketch_tool.set_profile_points(pts);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
}
void DesignCanvas::begin_constrain_entities(const std::vector<SketchEntity>& ents,
const SketchPlane& plane)
{
m_sketch_tool.begin_constrain_entities(ents, plane);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
}
bool DesignCanvas::is_constraining_entities() const
{
return m_sketch_tool.is_constraining_entities();
}
bool DesignCanvas::selected_constrain_entities(int& e0, int& e1) const
{
return m_sketch_tool.selected_constrain_entities(e0, e1);
}
int DesignCanvas::selected_constrain_axis() const
{
return m_sketch_tool.pick2();
}
bool DesignCanvas::pick0_point(Vec2d& out) const
{
return m_sketch_tool.pick0_point(out);
}
void DesignCanvas::update_constrain_entities(const std::vector<SketchEntity>& ents)
{
m_sketch_tool.set_constrain_entities(ents);
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
}
void DesignCanvas::set_constraint_highlight(std::vector<int> entities)
{
m_sketch_tool.set_constraint_highlight(std::move(entities));
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
}
void DesignCanvas::set_constraint_glyphs(std::vector<SketchEntityConstraintDef> cons)
{
m_sketch_tool.set_constraint_glyphs(std::move(cons));
if (m_canvas) { m_canvas->set_as_dirty(); m_canvas->render(); }
}
}} // namespace Slic3r::GUI
+236
View File
@@ -0,0 +1,236 @@
#ifndef slic3r_DesignCanvas_hpp_
#define slic3r_DesignCanvas_hpp_
#include <wx/panel.h>
#include <functional>
#include <memory>
#include <string>
#include "3DBed.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/SketchEngine.hpp"
#include "DesignSketchTool.hpp"
class wxGLCanvas;
class wxFrame;
class wxStaticText;
namespace Slic3r {
class TriangleMesh;
namespace GUI {
class GLCanvas3D;
class SketchInlineEditor;
class DesignCanvas : public wxPanel
{
public:
explicit DesignCanvas(wxWindow* parent);
~DesignCanvas() override;
void set_mesh(const TriangleMesh& mesh);
// Multi-body display: one GLVolume per body, each coloured distinctly (per-body colour).
// `visible` (optional, indexed by body) hides bodies whose flag is false.
void set_bodies(const std::vector<TriangleMesh>& body_meshes,
const std::vector<bool>& visible = {});
void clear_mesh();
void set_preview_mesh(const TriangleMesh& mesh);
void clear_preview();
void fit_view();
void set_view(const std::string& view_name);
void begin_sketch(const SketchPlane& plane, DesignSketchTool::Mode mode);
// Re-open a committed entity sketch for full in-canvas editing (load geometry +
// constraints, re-detect feature groups). Re-commits via finish_sketch().
void edit_sketch(const std::vector<SketchEntity>& entities,
const std::vector<SketchEntityConstraintDef>& constraints,
const SketchPlane& plane);
void set_sketch_tool(DesignSketchTool::Mode mode);
void set_sketch_construction(bool c);
void set_sketch_polygon_sides(int n);
void set_sketch_polygon_circumscribed(bool c);
void finish_sketch();
bool is_sketching() const;
void refresh_bed(); // re-sync the bed to the current printer (call on tab activation)
void cancel_sketch();
void set_on_sketch_commit(std::function<void(const SketchProfile&, const SketchPlane&)> cb);
void set_on_sketch_entities_commit(
std::function<void(const std::vector<SketchEntity>&,
const std::vector<SketchEntityConstraintDef>&,
const SketchPlane&)> cb);
// Line tool: pending-segment length entry + live readout (Phase 2).
void set_on_segment_drawn(std::function<void(double, double)> cb);
void set_on_cursor_metrics(std::function<void(double, double, bool)> cb);
void set_on_solve_state(std::function<void(int, bool, bool)> cb); // dof, ok, has_constraints
void apply_segment_length(double len); // exact length, then commit & repaint
void keep_segment_as_drawn(); // commit as-drawn & repaint
// Sketch selection (Mode::Select).
void set_on_sketch_selection_changed(std::function<void(int)> cb);
void set_on_sketch_face_selected(std::function<void()> cb); // closed loop clicked
void set_on_display_sketch_selected(std::function<void(int, int)> cb); // committed loop clicked: (feature, region)
std::vector<SketchEntity> selected_loop_entities() const; // entities of the click-selected loop
std::vector<std::vector<int>> region_entity_indices(const std::vector<SketchEntity>& ents) const;
void clear_loop_pick(); // drop the click-selected loop highlight (e.g. after extrude)
// Solid whole/face/edge selection: point the tool at the bodies + concatenated
// tessellation (with per-triangle face & body ids), and a callback fired on each
// whole->face->edge cycle (level, body index, face id, edge id).
void set_solid_pick(const std::vector<CadBody>* bodies, const TriangleMesh* mesh,
const std::vector<int>* tri_face, const std::vector<int>* tri_body,
const std::vector<bool>* visible = nullptr,
const std::vector<Transform3d>* xform = nullptr);
void set_on_solid_selection_changed(std::function<void(int, int, int, int)> cb);
void set_on_place_on_face(std::function<bool()> cb); // F key: Place on Face
void select_body(int body); // Parts-list -> highlight a whole body by index
// Effective display colour of a body: the per-body override (Color tool) when set,
// otherwise the auto body-index palette. Single source of truth shared with reload().
ColorRGBA body_color(int body) const;
// Move-body gizmo (M5): three world-axis drag arrows on a body; drag fires the move
// callback with the body index + accumulated translation (display-only, host applies it).
void begin_move_body(int body, const Vec3d& pivot, const Transform3d& base_xform);
void clear_move_gizmo();
bool moving_body() const;
void set_on_body_move_changed(std::function<void(int, const Transform3d&)> cb);
// Visual Fillet/Chamfer radius gizmo: when a solid edge is picked, anchor a radius arrow on
// it; drag/edit fire the radius callback. Returns false if no edge is currently picked.
bool begin_fillet_gizmo(const Vec3d& body_centroid, double radius);
void clear_fillet_gizmo();
bool filleting() const;
void set_on_fillet_radius_changed(std::function<void(double)> cb);
// Visual Hole gizmo: the panel feeds the hole plane + position + diameter/depth/through while
// its Hole card is open; drag/edit fire the hole callback (x, y, diameter, depth).
void begin_hole_gizmo(const SketchPlane& plane, double x, double y,
double diameter, double depth, bool through);
void set_hole_face_bounds(bool has, double umin, double umax, double vmin, double vmax);
void clear_hole_gizmo();
bool holing() const;
void set_on_hole_changed(std::function<void(double, double, double, double)> cb);
// Visual Thread gizmo: footprint circle + radius/length arrows + draggable centre.
void begin_thread_gizmo(const SketchPlane& plane, double x, double y,
double radius, double height);
void clear_thread_gizmo();
bool threading() const;
void set_on_thread_changed(std::function<void(double, double, double, double)> cb);
// Visual Shell gizmo: inward thickness arrow at the picked open-face centroid.
void begin_shell_gizmo(const Vec3d& face_centroid, const Vec3d& inward_dir, double thickness);
void clear_shell_gizmo();
bool shelling() const;
void set_on_shell_thickness_changed(std::function<void(double)> cb);
// Visual Revolve angle-arc gizmo: the panel feeds the sketch plane + profile centroid + axis
// (0=plane X, 1=plane Y) + angle + flip while its Revolve card is open; drag/edit fire the
// angle callback.
void begin_revolve_gizmo(const SketchPlane& plane, const Vec2d& centroid,
int axis_sel, double angle, bool flip);
void clear_revolve_gizmo();
bool revolving() const;
void set_on_revolve_angle_changed(std::function<void(double)> cb);
// Visual Pattern gizmo: the panel feeds the (world XY) plane + target body centroid + mode +
// count/dir/spacing/angle while its Pattern card is open; drag/edit fire the value callback.
void begin_pattern_gizmo(const SketchPlane& plane, const Vec3d& body_centroid, bool circular,
int count, int dir, double spacing, double angle);
void clear_pattern_gizmo();
bool patterning() const;
void set_on_pattern_changed(std::function<void(double)> cb);
// Visual Extrude depth-arrow gizmo (C5b): the panel feeds the profile plane + centroid +
// live depths/flags while its Extrude card is open; drag/edit fire the depth callback.
void set_extrude_gizmo(const SketchPlane& plane, const Vec2d& centroid,
double depth, double depth2, bool two_sided, bool flip);
void clear_extrude_gizmo();
void set_on_extrude_depth_changed(std::function<void(double, bool)> cb);
void set_on_sketch_exit(std::function<void()> cb); // Esc -> exit the tool
void set_on_undo_redo(std::function<void(bool /*redo*/)> cb); // Ctrl+Z / Ctrl+Shift+Z
// Persistently draw committed sketches (un-consumed ones stay visible).
void set_display_sketches(std::vector<DesignSketchTool::DisplaySketch> ds);
void set_datum_planes(std::vector<SketchPlane> planes); // draw datum/reference planes
void set_body_highlight(bool on); // tint the solid when its feature is tree-selected
void set_body_translucent(bool on); // render the solid see-through (fillet/chamfer preview)
void set_body_hidden(bool on); // preview-only: hide base bodies, show only the result ghost
void set_on_move_exit(std::function<void()> cb); // right-click finished the move-body gizmo
void delete_selected_sketch_entities();
void clear_sketch_selection();
// Dimension tool: act on the current sketch selection.
DesignSketchTool::DimType sketch_dimension_kind() const;
double sketch_dimension_current() const;
void apply_sketch_dimension(double v);
// Open the in-canvas value editor at the cursor for a host-driven value (the
// committed-feature Constrain path uses this instead of a docked numeric card).
void open_inline_value(double current, std::function<void(double)> commit,
std::function<void()> cancel = {});
// Dimension tool (Mode::Dimension): click-to-place quotes. The pick-complete
// callback lets the panel pop the value card; set/cancel apply or keep the value.
void set_on_dimension_pick_complete(std::function<void(double)> cb);
DesignSketchTool::DimType pending_dimension_type() const;
void set_sketch_dimension_value(double v);
void cancel_sketch_dimension();
// Constrain mode: load a committed profile for picking + constraint editing.
void begin_constrain(const SketchProfile& prof, const SketchPlane& plane);
// Leave constrain mode and clear any picked-entity highlight from the overlay.
void end_constrain();
bool is_constraining() const;
bool selected_segment(int& a, int& b) const;
void update_constrain_profile(const std::vector<Vec2d>& pts);
// Entity-aware Constrain (Fase 4.2): pick Line entities of a committed sketch.
void begin_constrain_entities(const std::vector<SketchEntity>& ents, const SketchPlane& plane);
bool is_constraining_entities() const;
// In-canvas bbox transform of imported Text/SVG art (replaces the Move/Scale dialog).
void begin_imported_transform(int feat,
const std::vector<std::vector<std::vector<Vec2d>>>& base_regions,
const SketchPlane& plane, const Vec2d& offset,
double scale_x, double scale_y);
void set_on_imported_transform(std::function<void(int, Vec2d, double, double)> cb);
bool selected_constrain_entities(int& e0, int& e1) const;
int selected_constrain_axis() const; // third pick slot (Symmetric axis), -1 if unset
bool pick0_point(Vec2d& out) const; // plane-coords of the slot-0 pick (trim/extend)
void update_constrain_entities(const std::vector<SketchEntity>& ents);
// Constraint manager (C3.4): highlight the entities referenced by a selected
// constraint (yellow tint in Constrain mode); empty clears the highlight.
void set_constraint_highlight(std::vector<int> entities);
// Constraint glyph badges (C3.4b): the feature's constraints, drawn as iconic
// marks near their entities in Constrain mode; empty clears them.
void set_constraint_glyphs(std::vector<SketchEntityConstraintDef> cons);
private:
void reload(bool keep_view);
wxGLCanvas* m_canvas_widget{nullptr};
GLCanvas3D* m_canvas{nullptr};
Bed3D m_bed;
Model m_model;
bool m_first_frame{true};
bool m_body_selected{false}; // tree selected a body feature → tint the solid
bool m_body_translucent{false};// fillet/chamfer preview → render the body see-through
bool m_body_hidden{false}; // preview-only mode → hide base bodies, ghost = the result
std::vector<bool> m_body_visible; // per-body visibility (empty => all visible)
// Live pointer to the document's bodies (stable address: m_doc.bodies), stashed by
// set_solid_pick so reload()/body_color() can read each body's colour override.
const std::vector<CadBody>* m_color_bodies{nullptr};
DesignSketchTool m_sketch_tool;
std::unique_ptr<SketchInlineEditor> m_inline_editor; // floating in-canvas value editor
// Bottom-right viewport HUD: a borderless float label over the GL canvas showing the
// active tool's current values (fed by the tool's on_readout). Empty text hides it.
wxFrame* m_hud{nullptr};
wxStaticText* m_hud_label{nullptr};
std::string m_hud_last;
void set_readout(const std::string& text);
std::function<void(const SketchProfile&, const SketchPlane&)> m_on_sketch_commit;
std::function<void(const std::vector<SketchEntity>&,
const std::vector<SketchEntityConstraintDef>&,
const SketchPlane&)> m_on_sketch_entities_commit;
};
}} // namespace Slic3r::GUI
#endif // slic3r_DesignCanvas_hpp_
File diff suppressed because it is too large Load Diff
+420
View File
@@ -0,0 +1,420 @@
#ifndef slic3r_DesignPanel_hpp_
#define slic3r_DesignPanel_hpp_
#include <wx/panel.h>
#include <wx/scrolwin.h>
#include <wx/treebase.h> // wxTreeItemId
#include <vector>
#include <memory>
#include <functional>
#include "libslic3r/CadDocument.hpp"
class wxChoice;
class wxCheckBox;
class wxCheckListBox;
class wxSpinCtrl;
class wxSpinCtrlDouble;
class wxTreeCtrl;
class wxImageList;
class wxStaticText;
class wxSizer;
class wxButton;
class wxPanel;
class ScalableButton;
namespace Slic3r { namespace GUI {
class DesignCanvas;
// Design (CAD) tab: a sketch-first, Onshape-style form-driven CAD panel.
// Sketch and Extrude are independent tools: the user creates a Sketch first,
// then selects it and Extrudes to produce a solid.
class DesignPanel : public wxPanel
{
public:
explicit DesignPanel(wxWindow* parent);
void on_tab_shown(); // re-sync bed to the active printer when the Design tab is activated
private:
enum class Tool { None, Sketch, Extrude, Dressup, Hole, Thread, Shell, Revolve, Sweep, Pattern, Plane, Loft, Draft, Boolean, Cut, Insert };
// Onshape-style contextual top toolbar: only the active mode's tool group is
// shown (Feature = sketch/extrude/dress/hole/thread; Sketch = entity tools;
// Constrain = constraints + edit ops). Replaces the old always-visible wall.
enum class UiMode { Feature, Sketch, Constrain };
void set_ui_mode(UiMode m);
// Unified action-bar dispatch: one Confirm / one Cancel for every tool and mode.
void tool_confirm(); // ✓ : commit the active feature / sketch / constrain session
void tool_cancel(); // ✗ / Esc : cancel the active feature / discard / exit
void update_action_bar(); // show the ✓/✗ bar iff a tool or mode is active
void on_shape_changed();
void on_add_sketch();
void on_add_extrude();
void on_add_dressup();
void on_add_hole();
void on_add_thread();
void apply_thread_standard(); // fill pitch/depth/radius from m_thread_std selection
void on_add_revolve();
void on_add_sweep();
void on_add_loft();
void on_add_pattern();
void on_add_plane();
void on_add_shell();
void on_add_draft();
void on_add_boolean();
void on_add_cut(); // commit a plane Cut (split-by-plane)
void populate_body_choices(); // fill m_bool_target / m_bool_tool / m_cut_target from m_doc.bodies
// Import rigid 2D art (Text / SVG) as a new Sketch feature carrying
// imported_regions (no solver entities). on_add_text/on_import_svg gather
// input; add_imported_sketch builds the feature, refreshes tree + display.
void on_add_text();
void on_import_svg();
void on_import_step(); // STEP -> editable B-rep body (keeps the OCCT solid, not a mesh)
bool place_on_face(); // Prepare's Place on Face (F): lay the selected body face on the bed
void add_imported_sketch(const std::vector<std::vector<std::vector<Vec2d>>>& regions,
const wxString& base_name);
// Imported Text/SVG art is placed/sized in-canvas then explicitly committed via a
// small Confirm/Cancel card (Onshape Button->Dialog->Preview->Confirm). The feature
// is added provisionally by add_imported_sketch; Confirm keeps it, Cancel undoes it.
void open_insert_card(const wxString& base_name);
void finalize_insert(); // Confirm: keep the placed art, leave the placement gizmo
void cancel_insert(); // Cancel: undo the provisional insert
// Move / enlarge / stretch (independent X/Y) an imported Text/SVG sketch:
// a modal dialog editing the feature's placement transform in place.
void on_transform_imported(int feat_idx);
void on_commit();
void refresh_tree();
void set_status_ok();
// Feature-tree editing (Onshape-style): act on the selected tree row.
void on_delete_feature();
void on_move_feature(int delta); // -1 = up, +1 = down
void on_toggle_visibility(); // show/hide the selected feature (CadFeature::enabled)
// Constrain mode: enter on the tree-selected sketch, then apply a geometric
// constraint to the in-canvas picked segment and re-solve in the kernel.
void on_begin_constrain(int sel_override = -1);
// Sketch-toolbar Constrain entry: commit the live sketch in place, then enter Constrain
// mode on it (so the constraint palette + Trim/Extend are reachable without leaving the
// sketch flow). Returns true if constrain mode was entered.
bool enter_constrain_inline();
void apply_constraint(SketchConstraintType type);
void apply_entity_constraint(SketchConstraintType type); // Fase 4.2 entity path
enum class EditOp { Mirror, Offset, Fillet, Trim, Extend, Array, Move, Chamfer, Rotate, Scale, PolarArray }; // Fase 4.4/4.5/4.6 sketch edit ops
void apply_edit_op(EditOp op); // mutate selected sketch entities
// Onshape-style docked value entry (replaces wxGetTextFromUser popups for
// Angle/Radius/Diameter constraints + Offset/Fillet edit ops). request_value
// shows the card and stows a continuation run by confirm_value().
void request_value(const wxString& label, double def, double mn, double mx,
std::function<void(double)> cont,
std::function<void()> on_cancel = nullptr);
void confirm_value();
void cancel_value();
void commit_entity_constraint(const SketchEntityConstraintDef& def); // shared solve/refresh tail
void commit_entity_constraints(const std::vector<SketchEntityConstraintDef>& defs); // multi-def (Symmetric)
// Constraint manager (C3.4): a docked list of the constrained sketch's
// entity-constraints with per-row select (highlight the referenced entities in
// the viewport) and delete (drop the constraint + re-solve). Shown in Constrain
// mode only; operates on m_doc.features[m_constrain_feat].entity_constraints.
void rebuild_constraint_list(); // refill m_constraint_rows
void delete_constraint(int idx); // erase + re-solve + refresh
void highlight_constraint_entities(int idx); // push referenced entities to viewport
void refresh_constrain_dof(); // re-solve feature, mirror DoF readout
wxString constraint_label(const SketchEntityConstraintDef& d) const; // human-readable row text
void after_edit_op(); // shared edit-op refresh tail
void on_edit_feature(); // reopen the selected feature's dialog populated
void after_tree_edit(bool ok); // shared post-op refresh of tree/viewport/status
void load_feature_into_dialog(const CadFeature& f);
void reset_edit_state(); // back to add-mode (m_edit_index = -1)
// Onshape loop: Button -> open_tool (show dialog) -> refresh_preview (ghost) ->
// confirm_tool (commit) / cancel_tool (abort).
void open_tool(Tool t);
void close_tool();
void refresh_preview();
void confirm_tool();
void cancel_tool();
// Ctrl+Z / Ctrl+Shift+Z (Ctrl+Y) from the viewport. With a tool/dialog open it
// cancels that (Esc-like); otherwise it undoes/redoes the committed feature history.
void do_undo_redo(bool redo);
// The plane the Hole tool drills on: a picked face (inward, centred) or the dropdown.
SketchPlane hole_plane() const;
// The plane the Thread tool builds on: a picked cylindrical face (axis) or the dropdown.
SketchPlane thread_plane() const;
CadFeature build_candidate(Tool t) const;
int resolve_extrude_sketch() const;
// Plane pickers: fill a choice with XY/XZ/YZ + the document's datum planes, and
// map a choice row back to the actual SketchPlane (rows 0-2 base, 3+ datum).
void populate_plane_choices(wxChoice* c) const;
SketchPlane plane_from_choice(int row) const;
// True when Extrude should build only the click-selected loop (a region of the
// resolved sketch is selected and it carries entities).
bool extrude_uses_loop() const;
void sync_sketch_display(); // push un-consumed committed sketches to the viewport
// Feed the viewport's visual Extrude depth-arrow gizmo (C5b) with the current profile
// plane + centroid + live depths while the Extrude card is open (self-gates on m_active).
void update_extrude_gizmo();
void update_fillet_gizmo(); // edge-anchored radius arrow (Dressup card)
void update_hole_gizmo(); // footprint circle + diameter/depth arrows (Hole card)
void update_thread_gizmo(); // footprint circle + radius/length arrows (Thread card)
void update_shell_gizmo(); // inward thickness arrow on the picked face (Shell card)
void update_revolve_gizmo(); // angle-arc around the axis (Revolve card)
void update_pattern_gizmo(); // linear spacing arrow / circular angle-arc (Pattern card)
CadDocument m_doc;
Tool m_active{Tool::None};
wxSizer* m_box_sketch{nullptr};
wxSizer* m_box_extrude{nullptr};
wxSizer* m_box_dressup{nullptr};
wxSizer* m_box_hole{nullptr};
wxSizer* m_box_thread{nullptr};
wxSizer* m_box_shell{nullptr};
wxSizer* m_box_revolve{nullptr};
wxSizer* m_box_sweep{nullptr};
wxSizer* m_box_pattern{nullptr};
wxSizer* m_box_plane{nullptr};
wxSizer* m_box_loft{nullptr};
wxSizer* m_box_draft{nullptr};
wxSizer* m_box_boolean{nullptr};
wxSizer* m_box_cut{nullptr};
wxSizer* m_box_insert{nullptr}; // Confirm/Cancel card for placing Text/SVG art
int m_insert_feat{-1}; // provisional imported-art feature awaiting Confirm
// Move-body gizmo runs through the unified action bar too: Confirm keeps the placement,
// Cancel reverts to the pose captured when the move started.
int m_move_body{-1};
Transform3d m_move_prev{Transform3d::Identity()};
// Onshape-style dialog-card title rows (icon + bold feature name), retitled
// per tool in open_tool() (edit-mode shows the feature's actual name).
wxStaticText* m_hdr_sketch{nullptr};
// Onshape sketch-entry card (plane/orientation) that opens on "New sketch" and
// persists until Finish (Phase 3).
wxSizer* m_box_sketch_session{nullptr};
wxStaticText* m_hdr_sketch_session{nullptr};
wxStaticText* m_hdr_extrude{nullptr};
wxStaticText* m_hdr_dressup{nullptr};
wxStaticText* m_hdr_hole{nullptr};
wxStaticText* m_hdr_thread{nullptr};
wxStaticText* m_hdr_shell{nullptr};
wxStaticText* m_hdr_revolve{nullptr};
wxStaticText* m_hdr_sweep{nullptr};
wxStaticText* m_hdr_pattern{nullptr};
wxStaticText* m_hdr_plane{nullptr};
wxStaticText* m_hdr_loft{nullptr};
wxStaticText* m_hdr_draft{nullptr};
wxStaticText* m_hdr_boolean{nullptr};
wxStaticText* m_hdr_cut{nullptr};
wxStaticText* m_hdr_insert{nullptr};
wxScrolledWindow* m_form{nullptr};
DesignCanvas* m_viewport{nullptr};
// Top contextual toolbar (parented to the panel, above the form/viewport row).
UiMode m_ui_mode{UiMode::Feature};
wxScrolledWindow* m_toolbar{nullptr}; // horizontally scrollable so the action bar stays reachable on narrow windows
wxSizer* m_tb_feature{nullptr};
wxSizer* m_tb_sketch{nullptr};
wxSizer* m_tb_constrain{nullptr};
// Unified Confirm/Cancel action bar (right end of the ribbon). Shown whenever any
// tool or mode is active; the single confirm/cancel surface for the whole tab.
wxSizer* m_tb_action{nullptr};
// Persistent Undo/Redo group at the left of the ribbon — always visible, independent
// of the mode-gated tool groups. The buttons are greyed per the document history and
// the do_undo_redo gate (see update_undo_redo_buttons).
wxSizer* m_tb_history{nullptr};
ScalableButton* m_btn_undo{nullptr};
ScalableButton* m_btn_redo{nullptr};
void update_undo_redo_buttons(); // enable/disable Undo/Redo from can_undo/can_redo + gate
// All tool buttons, for the active-tool teal highlight (Onshape-style).
std::vector<ScalableButton*> m_tool_btns;
ScalableButton* m_active_tool_btn{nullptr};
void set_active_tool_btn(ScalableButton* b); // nullptr clears the highlight
// Owns the themed DropDown flyouts (and the item vectors they hold by ref).
std::vector<std::shared_ptr<void>> m_flyout_keepalive;
wxCheckBox* m_construction{nullptr}; // sketch-mode construction toggle
wxSpinCtrl* m_sides{nullptr}; // polygon sides
wxCheckBox* m_poly_circ{nullptr}; // polygon circumscribed toggle
wxChoice* m_draw_plane{nullptr};
wxChoice* m_shape{nullptr};
wxChoice* m_plane{nullptr};
wxChoice* m_mode{nullptr};
wxSpinCtrlDouble* m_width{nullptr};
wxSpinCtrlDouble* m_height{nullptr};
wxSpinCtrlDouble* m_radius{nullptr};
wxSpinCtrlDouble* m_distance{nullptr};
wxChoice* m_extrude_end{nullptr}; // Blind/Symmetric/TwoSided/ThroughAll/UpTo*
wxSpinCtrlDouble* m_distance2{nullptr}; // second-side depth (Two-sided)
wxSpinCtrlDouble* m_taper{nullptr}; // draft angle (deg)
wxCheckBox* m_flip{nullptr}; // reverse extrude direction
wxStaticText* m_extrude_sketch_label{nullptr};
int m_extrude_sketch_ref{-1};
// Revolve controls (sweep a sketch profile about an in-plane axis).
wxStaticText* m_revolve_sketch_label{nullptr};
wxSpinCtrlDouble* m_revolve_angle{nullptr};
wxChoice* m_revolve_axis{nullptr}; // 0 = plane X, 1 = plane Y
wxChoice* m_revolve_mode{nullptr}; // New/Add/Cut/Intersect
wxCheckBox* m_revolve_flip{nullptr};
int m_revolve_sketch_ref{-1};
// Sweep controls (sweep a profile sketch along a path sketch).
wxStaticText* m_sweep_profile_label{nullptr};
wxChoice* m_sweep_path{nullptr}; // path Sketch picker (feature index in client data)
wxChoice* m_sweep_mode{nullptr}; // New/Add/Cut/Intersect
int m_sweep_profile_ref{-1};
int m_sweep_path_ref{-1}; // path Sketch feature index (for re-edit pre-select)
// Loft controls (skin a solid through 2+ ordered profile Sketches).
wxCheckListBox* m_loft_list{nullptr}; // every Sketch; check 2+ in list order = profiles
wxCheckBox* m_loft_ruled{nullptr}; // ruled (straight) vs smooth sections
wxChoice* m_loft_mode{nullptr}; // New/Add/Cut/Intersect
std::vector<int> m_loft_sketch_idx; // feature index for each row in m_loft_list
std::vector<int> m_loft_refs; // chosen profile refs (for re-edit pre-check)
// Pattern controls (replicate the target body: linear or circular).
wxChoice* m_pattern_type{nullptr}; // 0 = Linear, 1 = Circular
wxSpinCtrlDouble* m_pattern_count{nullptr}; // total instances incl. seed
wxSpinCtrlDouble* m_pattern_spacing{nullptr}; // linear step (mm)
wxChoice* m_pattern_dir{nullptr}; // linear direction: 0 = plane X, 1 = plane Y
wxSpinCtrlDouble* m_pattern_angle{nullptr}; // circular total angle (deg)
// Boolean controls (combine two existing bodies).
wxChoice* m_bool_op{nullptr}; // 0 = Union, 1 = Subtract, 2 = Intersect
wxChoice* m_bool_target{nullptr}; // body that survives (selection == body index)
wxChoice* m_bool_tool{nullptr}; // body consumed (selection == body index)
wxCheckBox* m_bool_keep{nullptr}; // keep the tool body after the op
wxSpinCtrlDouble* m_bool_tol{nullptr}; // OCCT fuzzy tolerance (mm); robust cut on near-coincident faces
// Plane Cut (split-by-plane): a reference plane + offset splits the target body into
// two separate bodies (both pieces kept).
wxChoice* m_cut_plane{nullptr}; // XY/XZ/YZ + datum planes (cut plane)
wxChoice* m_cut_target{nullptr}; // body to cut (selection == body index)
wxSpinCtrlDouble* m_cut_offset{nullptr}; // offset along the plane normal (mm)
// Datum plane controls (derive a selectable sketch plane: offset + tilt from a base).
wxChoice* m_plane_base{nullptr}; // 0=XY,1=XZ,2=YZ, 3+N = Nth datum plane
wxSpinCtrlDouble* m_plane_offset{nullptr}; // offset along base normal (mm)
wxSpinCtrlDouble* m_plane_tilt{nullptr}; // tilt about a base axis (deg)
wxChoice* m_plane_tilt_axis{nullptr}; // 0 = base X, 1 = base Y
// Plate loop selection (click a committed sketch loop): the Sketch feature + the
// clicked closed-region index, so Extrude builds just that one loop. -1 = none.
int m_sel_sketch_feat{-1};
int m_sel_sketch_region{-1};
// Click-selected solid topology (whole/face/edge cycle): face id for up-to-face / dress-up.
int m_sel_solid_body{-1}; // which body the face/edge selection is on
int m_sel_solid_face{-1};
int m_sel_solid_edge{-1};
// Face-as-profile extrude (Onshape): when Extrude is opened on a picked solid face with
// no sketch source, this carries that global face id so the kernel extrudes the face.
// -1 = ordinary sketch/loop extrude. Set when opening the Extrude card, consumed on add.
int m_extrude_face_src{-1};
wxChoice* m_dressup_type{nullptr};
wxChoice* m_face_group{nullptr};
wxSpinCtrlDouble* m_dressup_size{nullptr};
wxChoice* m_hole_plane{nullptr};
wxSpinCtrlDouble* m_hole_diameter{nullptr};
wxSpinCtrlDouble* m_hole_depth{nullptr};
wxCheckBox* m_hole_through{nullptr};
wxSpinCtrlDouble* m_hole_x{nullptr};
wxSpinCtrlDouble* m_hole_y{nullptr};
// #2: when the Hole tool is opened on a picked solid face, drill on that face centred
// on it (origin = face centroid, normal = inward). m_hole_x/y then read as the offset
// from the face centre. Falls back to the m_hole_plane dropdown when no face is picked.
bool m_hole_on_face{false};
SketchPlane m_hole_face_plane;
int m_hole_face_body{-1};
// #2 Part B: the picked face's (u,v) bounds in m_hole_face_plane, so the hole's construction
// dims read as distance from the face sides (umin/vmin edges) rather than from the centre.
bool m_hole_has_bounds{false};
double m_hole_umin{0}, m_hole_umax{0}, m_hole_vmin{0}, m_hole_vmax{0};
wxChoice* m_thread_plane{nullptr};
wxChoice* m_thread_std{nullptr}; // standard designation (M6, 1/4-20 UNC, ...)
wxSpinCtrlDouble* m_thread_radius{nullptr};
wxSpinCtrlDouble* m_thread_pitch{nullptr};
wxSpinCtrlDouble* m_thread_height{nullptr};
wxSpinCtrlDouble* m_thread_depth{nullptr};
wxCheckBox* m_thread_internal{nullptr};
wxSpinCtrlDouble* m_thread_x{nullptr};
wxSpinCtrlDouble* m_thread_y{nullptr};
// #3: when the Thread tool is opened on a picked cylindrical face (a hole bore or a
// cylinder), thread that surface — plane on its axis, radius/internal derived from it.
bool m_thread_on_face{false};
SketchPlane m_thread_face_plane;
int m_thread_face_body{-1};
wxSpinCtrlDouble* m_shell_thickness{nullptr};
wxStaticText* m_shell_face_label{nullptr}; // shows the picked face to remove
// Draft controls (taper a single picked solid face about the body bottom).
wxSpinCtrlDouble* m_draft_angle{nullptr};
wxStaticText* m_draft_face_label{nullptr}; // shows the picked face to draft
// Onshape-style docked value-entry card (Angle/Radius/Diameter/Offset/Fillet).
wxSizer* m_box_value{nullptr};
wxStaticText* m_value_label{nullptr};
wxTextCtrl* m_value_input{nullptr}; // plain text field: forces en ('.') decimals
double m_value_min{0.0}; // range for confirm-time clamping
double m_value_max{0.0};
std::function<void(double)> m_value_cont; // deferred apply, run on Confirm
std::function<void()> m_value_cancel; // optional action when the card is cancelled
// Feature tree: a wxTreeCtrl with per-feature-type icons. Callers keep using
// integer row indices via tree_selection()/set_tree_selection(); m_tree_items
// maps feature order -> tree node, rebuilt by refresh_tree().
wxTreeCtrl* m_tree{nullptr};
wxImageList* m_tree_images{nullptr};
std::vector<wxTreeItemId> m_tree_items;
// Parts list: tree rows for each body (parallel to m_doc.bodies). Selecting one
// highlights that body and makes it the target for the next op.
std::vector<wxTreeItemId> m_tree_body_items;
// Per-body visibility (parallel to m_doc.bodies; index stable across recompute since
// bodies are appended in feature order). Empty/grown to all-visible by sync_body_visible().
std::vector<bool> m_body_visible;
void sync_body_visible(); // grow/shrink m_body_visible to bodies.size()
// Per-body display translation (Move-body, M5). Parallel to m_doc.bodies; default
// identity. Applied to the display/pick meshes only — the OCCT shape (and face/edge
// global ids) is never touched, so dress-up targeting stays stable across a move.
std::vector<Transform3d> m_body_xform;
std::vector<TriangleMesh> m_disp_body_meshes; // display_body_meshes with m_body_xform applied
TriangleMesh m_disp_pick_mesh; // combined pick mesh with m_body_xform applied
void sync_body_xform(); // grow m_body_xform to bodies.size() (identity)
void rebuild_disp_meshes(); // recompute m_disp_* from m_doc + m_body_xform
void feed_bodies(); // push m_disp_* + visibility/xform to the viewport
void on_move_body(); // start the move gizmo on the selected body
void on_set_body_color(); // Color tool: pick a per-body display colour override
int tree_selection() const; // selected feature row, or wxNOT_FOUND
int tree_body_selection() const; // selected Parts-list body index, or -1
void set_tree_selection(int row);
static int tree_icon_for(CadFeatureType t);
wxStaticText* m_status{nullptr};
wxStaticText* m_dof_status{nullptr}; // DoF / constraint-state readout (P3)
int m_feature_counter{0};
std::vector<wxButton*> m_confirm_btns;
// Edit-in-place state: add-mode is m_edit_index == -1. Single-feature edit
// (Sketch or Extrude independently) uses only m_edit_index as the row to replace.
int m_edit_index{-1};
// Tree row of the sketch currently being constrained (-1 = not constraining).
int m_constrain_feat{-1};
// Constraint-manager card (C3.4): header + a rebuildable list of constraint rows.
wxSizer* m_box_constraints{nullptr};
wxStaticText* m_hdr_constraints{nullptr};
wxSizer* m_constraint_rows{nullptr};
int m_constraint_sel{-1}; // highlighted constraint row, or -1
};
}} // namespace Slic3r::GUI
#endif // slic3r_DesignPanel_hpp_
File diff suppressed because it is too large Load Diff
+980
View File
@@ -0,0 +1,980 @@
#ifndef slic3r_DesignSketchTool_hpp_
#define slic3r_DesignSketchTool_hpp_
#include "libslic3r/Point.hpp"
#include "libslic3r/SketchEngine.hpp"
#include "libslic3r/CadDocument.hpp" // CadBody for per-body solid picking
#include "libslic3r/SketchInference.hpp"
#include "libslic3r/SketchSolver.hpp"
#include "GLModel.hpp"
#include <functional>
#include <vector>
#include <string>
#include <utility>
class wxMouseEvent;
class wxPoint;
namespace Slic3r {
class TriangleMesh; // fwd (libslic3r) — solid-pick mesh, non-owning pointer
namespace GUI {
class GLCanvas3D;
// Onshape-style sketch session. `begin` enters a session on a plane; the active
// drawing tool (Mode) can be switched mid-session via `set_tool` while entities
// accumulate. `finish` commits the whole entity list as one sketch feature;
// `cancel` aborts. Constrain is a separate legacy mode that operates on a
// committed profile's points (entity constraints land in a later chunk).
class DesignSketchTool {
public:
enum class Mode { Select, Dimension, Polyline, Line, CornerRect, CenterRect, ObliqueRect,
RoundedRect, CenterCircle, TwoPointCircle, Point,
ThreePointCircle, ThreePointArc, TangentArc, CenterArc, Slot, ArcSlot, Polygon,
Ellipse, EllipseArc, BSpline,
// In-canvas edit-op TOOLBAR tools (drag-arrow + label, no numeric card):
Fillet, Chamfer, Offset, Mirror,
// Standalone scissors: click a segment to trim/extend it (immediate, no card):
Trim, Extend,
// In-canvas transform TOOLBAR tools (pick targets + drag handle/label, no card):
Move, Rotate, Scale, Array, PolarArray,
// In-canvas bounding-box transform for imported Text/SVG art:
TransformArt,
Constrain };
bool is_edit_op_mode() const { return m_mode == Mode::Fillet || m_mode == Mode::Chamfer ||
m_mode == Mode::Offset || m_mode == Mode::Mirror; }
bool is_transform_mode() const { return m_mode == Mode::Move || m_mode == Mode::Rotate ||
m_mode == Mode::Scale || m_mode == Mode::Array ||
m_mode == Mode::PolarArray; }
// Creation tools that get draw-then-edit: on commit the new entity/feature is selected
// and its primary value editor opens. Line is handled inline (its own length field);
// Polyline/BSpline/Point have no single primary value, so they opt out.
bool is_creation_autoedit_mode() const {
switch (m_mode) {
case Mode::Line:
case Mode::CornerRect: case Mode::CenterRect: case Mode::ObliqueRect:
case Mode::RoundedRect: case Mode::CenterCircle: case Mode::TwoPointCircle:
case Mode::ThreePointCircle: case Mode::ThreePointArc: case Mode::TangentArc:
case Mode::CenterArc: case Mode::Slot: case Mode::ArcSlot: case Mode::Polygon:
case Mode::Ellipse: case Mode::EllipseArc:
return true;
default: return false;
}
}
// The host (DesignCanvas) flags the canvas frozen while an inline value editor is open,
// so a stray click/move can't draw under the floating field. Reuses m_awaiting_length
// (Line's existing freeze flag) as the single "inline editor open" gate.
void set_inline_busy(bool b) { m_awaiting_length = b; }
bool inline_busy() const { return m_awaiting_length; } // true while a value field is open
bool constrain_value_anchor(wxPoint& out) const; // screen anchor over the picked constrain geometry
void begin(const SketchPlane& plane, Mode mode = Mode::Polyline);
// Re-open a committed entity sketch for full in-canvas editing: load its entities +
// driving constraints, re-detect the polygon/rect/slot grouping, and live-solve. The
// caller re-commits via finish() (the panel replaces the feature, see m_edit_index).
void begin_edit(const std::vector<SketchEntity>& entities,
const std::vector<SketchEntityConstraintDef>& constraints,
const SketchPlane& plane);
void set_tool(Mode mode); // switch tool, keep accumulated entities
void set_construction(bool c) { m_construction = c; }
void set_polygon_sides(int n) { m_polygon_sides = (n < 3 ? 3 : n); }
void set_polygon_circumscribed(bool c) { m_polygon_circumscribed = c; }
void finish(); // emit accumulated entities, end session
void cancel();
bool is_active() const { return m_active; }
bool has_entities() const { return !m_entities.empty(); }
bool on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas);
void render(GLCanvas3D& canvas);
// Persistent committed sketches to draw even when no session is active (e.g. an
// un-consumed sketch left visible after its extrude is removed). Each carries its
// own plane. render() draws these as translucent faces + outlines.
struct DisplaySketch { std::vector<SketchEntity> entities; SketchPlane plane; int feature{-1}; };
void set_display_sketches(std::vector<DisplaySketch> ds) { m_display_sketches = std::move(ds); }
bool has_display() const { return m_active || !m_display_sketches.empty()
|| (m_solid_bodies != nullptr && !m_solid_bodies->empty())
|| !m_datum_planes.empty()
|| m_ex_active || m_mv_active || m_fl_active
|| m_hl_active || m_th_active || m_sh_active; }
// Solid topology selection on the committed bodies: clicking a solid cycles
// whole-solid -> face -> edge (Onshape-style) to target fillet/chamfer/extrude. With
// multiple bodies the pick resolves WHICH body was hit (per-triangle body id).
enum class SolidSel { None, Whole, Face, Edge };
// Point the tool at the current bodies + their concatenated tessellation (non-owning;
// pass nullptr to clear). Call after each recompute — selection resets (ids invalidate).
// tri_face = per-triangle face id within its body; tri_body = per-triangle body index.
void set_solid_pick(const std::vector<CadBody>* bodies, const TriangleMesh* mesh,
const std::vector<int>* tri_face, const std::vector<int>* tri_body,
const std::vector<bool>* visible = nullptr,
const std::vector<Transform3d>* xform = nullptr);
void clear_solid_selection();
// Select a whole body by index (from the Parts list) — Whole-level highlight, no face/edge.
// body < 0 or out of range clears the selection.
void select_body(int body);
// Move-body gizmo (M5): translate a whole body with three world-axis drag arrows
// (X red / Y green / Z blue) anchored at the body centroid. Display-only — the host
// keeps a per-body Transform3d and re-feeds the moved display/pick meshes; the OCCT
// shape (and thus face/edge global ids) is never touched. Drag fires on_body_move_changed
// live; a stationary click on an arrow opens the inline offset editor for that axis.
void set_move_gizmo(int body, const Vec3d& pivot, const Transform3d& base_xform);
void clear_move_gizmo();
bool moving_body() const { return m_mv_active; }
int move_body_index() const { return m_mv_body; }
// F key forwarded from the canvas (Prepare's Place on Face): returns true if it acted.
bool request_place_on_face() { return on_place_on_face ? on_place_on_face() : false; }
std::function<bool()> on_place_on_face;
std::function<void(int body, const Transform3d& xform)> on_body_move_changed;
// Fired on each cycle change: (level 0=None/1=Whole/2=Face/3=Edge, body index, face id, edge id).
std::function<void(int level, int body, int face, int edge)> on_solid_selection_changed;
// Click a committed sketch overlay (no live session) -> select that loop: the Sketch
// feature index + the clicked closed-region index within it (-1 = no specific loop).
std::function<void(int feature, int region)> on_display_sketch_selected;
// Entities forming the currently click-selected loop (for a per-loop extrude); empty
// if no loop is selected.
std::vector<SketchEntity> selected_loop_entities() const;
// Per closed loop, the indices into `ents` that form it (for hiding already-extruded
// loops from the committed-sketch overlay).
std::vector<std::vector<int>> region_entity_indices(const std::vector<SketchEntity>& ents) const;
void clear_display_pick() { m_display_pick = -1; m_display_pick_region = -1; }
// Visual Extrude gizmo (C5b). The Extrude tool is a DesignPanel docked card, so the
// sketch tool is NOT active during it; the panel feeds the profile plane + a 2D centroid
// (arrow anchor) + the live depths/flags, and the tool renders an in-canvas world-space
// depth arrow along plane.normal with a draggable handle + editable label. TwoSided draws
// a second arrow along -normal driven by depth2. Drag/edit fire on_extrude_depth_changed
// back to the panel, which writes the spin value + refreshes the ghost preview.
void set_extrude_gizmo(const SketchPlane& plane, const Vec2d& centroid,
double depth, double depth2, bool two_sided, bool flip);
void clear_extrude_gizmo();
// (new_depth, second_side): second_side=false drives the primary depth, true the 2nd side.
std::function<void(double depth, bool second)> on_extrude_depth_changed;
// Visual Fillet/Chamfer gizmo. The Dressup tool is a DesignPanel docked card, so the sketch
// tool is NOT active during it; when a solid EDGE is picked the panel passes the body centroid
// + current radius and the tool anchors a world-space radius arrow at the picked edge midpoint
// (from m_sel_edge_pts), perpendicular to the edge, pointing outward (away from the centroid).
// Dragging the arrow changes the radius live; a stationary click opens the inline editor; both
// fire on_fillet_radius_changed back to the panel, which writes the spin + refreshes the ghost.
// Returns true if it could anchor (needs a picked edge with >=2 sample points).
bool set_fillet_gizmo(const Vec3d& body_centroid, double radius);
void clear_fillet_gizmo();
bool filleting() const { return m_fl_active; }
std::function<void(double radius)> on_fillet_radius_changed;
// Visual Hole gizmo. Like Dressup, the Hole tool is a DesignPanel docked card, so the sketch
// tool is NOT active during it; the panel passes the hole plane + position + diameter + depth +
// through flag, and the tool draws an on-plane footprint circle plus a radial diameter arrow,
// a normal-axis depth arrow (only when !through), and a draggable centre marker. Dragging the
// centre repositions (plane u/v), the diameter arrow resizes, the depth arrow deepens — all
// live; a stationary click on an arrow opens its inline editor. Every change fires
// on_hole_changed back to the panel, which writes the spins + refreshes the ghost.
void set_hole_gizmo(const SketchPlane& plane, double x, double y,
double diameter, double depth, bool through);
// Provide the face (u,v) bounds so the hole's construction dims read from the face sides.
void set_hole_face_bounds(bool has, double umin, double umax, double vmin, double vmax);
void clear_hole_gizmo();
bool holing() const { return m_hl_active; }
std::function<void(double x, double y, double diameter, double depth)> on_hole_changed;
// Visual Thread gizmo. Same docked-card story as Hole: the panel feeds the thread plane +
// axis position + nominal radius + length; the tool draws an on-plane footprint circle plus a
// radial radius arrow and a normal-axis length arrow (always shown — a thread has no "through")
// and a draggable centre. Pitch/depth/internal stay in the card. Drag is live; a stationary
// click on an arrow opens its inline editor; every change fires on_thread_changed.
void set_thread_gizmo(const SketchPlane& plane, double x, double y,
double radius, double height);
void clear_thread_gizmo();
bool threading() const { return m_th_active; }
std::function<void(double x, double y, double radius, double height)> on_thread_changed;
// Visual Shell gizmo. The panel passes the picked open-face centroid + an inward direction
// (-outward normal) + the current wall thickness; the tool anchors a single thickness arrow
// there (mirrors the fillet radius arrow). Dragging sets the thickness live; a stationary
// click opens the inline editor; both fire on_shell_thickness_changed.
void set_shell_gizmo(const Vec3d& face_centroid, const Vec3d& inward_dir, double thickness);
void clear_shell_gizmo();
bool shelling() const { return m_sh_active; }
std::function<void(double thickness)> on_shell_thickness_changed;
// Datum/reference planes (Plane feature) carry no solid; the panel feeds their resolved
// SketchPlanes so they render as translucent rectangles in feature mode (otherwise a
// Plane feature is invisible in the canvas).
void set_datum_planes(std::vector<SketchPlane> planes) { m_datum_planes = std::move(planes); }
// Visual Revolve gizmo. The panel feeds the sketch plane + profile centroid + axis (0=plane X,
// 1=plane Y) + angle + flip while its Revolve card is open; an angle-arc is drawn in the
// revolve plane at the profile radius. Dragging the tip sweeps the angle, a stationary click
// edits it; both fire on_revolve_angle_changed.
void set_revolve_gizmo(const SketchPlane& plane, const Vec2d& centroid,
int axis_sel, double angle, bool flip);
void clear_revolve_gizmo();
bool revolving() const { return m_rv_active; }
std::function<void(double angle)> on_revolve_angle_changed;
// Visual Pattern gizmo. Linear: a 3D arrow along the world axis (plane X/Y per `dir`) of length
// spacing*(count-1) with a tick at each copy; dragging the end sets the spacing. Circular: a
// revolve-style angle-arc about the plane normal through the plane origin sweeping `angle`.
// Both fire on_pattern_changed (spacing for linear, angle for circular).
void set_pattern_gizmo(const SketchPlane& plane, const Vec3d& body_centroid, bool circular,
int count, int dir, double spacing, double angle);
void clear_pattern_gizmo();
bool patterning() const { return m_pt_active; }
std::function<void(double value)> on_pattern_changed;
// Constrain mode: load an already-committed profile for entity picking +
// constraint application (the geometry is solved in the kernel, not here).
void begin_constrain(const SketchProfile& prof, const SketchPlane& plane);
bool is_constraining() const { return m_active && m_mode == Mode::Constrain; }
// Replace the displayed profile (e.g. after the kernel re-solved it).
void set_profile_points(const std::vector<Vec2d>& pts) { m_points = pts; }
// The currently picked segment's endpoint indices into the profile.
bool selected_segment(int& a, int& b) const;
// Entity-aware Constrain (Fase 4.2): load a committed entity sketch and pick
// Line entities (constraints are solved against entity endpoints in the kernel).
void begin_constrain_entities(const std::vector<SketchEntity>& ents, const SketchPlane& plane);
bool is_constraining_entities() const { return m_active && m_mode == Mode::Constrain && m_constrain_entities; }
// In-canvas bounding-box transform of imported Text/SVG art (replaces the Move/Scale
// dialog). `base_regions` are the untransformed region contours; the gizmo shows the
// current bbox with 4 corner scale-handles + a centre move-handle. Dragging fires
// on_imported_transform live with the new offset/scale, which the host writes back to
// the feature. Exiting (Esc/right-click) ends the session.
void begin_imported_transform(int feat,
const std::vector<std::vector<std::vector<Vec2d>>>& base_regions,
const SketchPlane& plane, const Vec2d& offset,
double scale_x, double scale_y);
std::function<void(int feat, Vec2d offset, double scale_x, double scale_y)> on_imported_transform;
// Up to two picked line-entity indices; returns true if at least one is picked.
bool selected_constrain_entities(int& e0, int& e1) const { e0 = m_pick0; e1 = m_pick1; return m_pick0 >= 0; }
// Third pick slot (Symmetric axis): only filled after slots 0 and 1 are set.
int pick2() const { return m_pick2; }
// Plane-coords of the click that filled slot 0 (for pick-point edit ops: trim/extend).
bool pick0_point(Vec2d& out) const { out = m_pick0_pt; return m_pick0 >= 0; }
// Refresh the displayed entities after the kernel re-solved them.
void set_constrain_entities(const std::vector<SketchEntity>& ents) { m_entities = ents; }
// Constraint manager (C3.4): entity indices the panel asks to highlight (the
// entities a selected constraint references); rendered yellow in Constrain mode.
void set_constraint_highlight(std::vector<int> v) { m_constraint_hl = std::move(v); }
// The committed feature's constraints, supplied so Constrain-mode render can draw
// an iconic glyph badge per constraint near its primary entity (C3.4b).
void set_constraint_glyphs(std::vector<SketchEntityConstraintDef> v) { m_constrain_cons = std::move(v); }
// Line tool: after a single segment is placed, the panel pops a length dialog
// (length, angle_deg are the as-drawn values); it then resolves via
// apply_segment_length() (exact length) or keep_segment_as_drawn() (cancel).
std::function<void(double length, double angle_deg)> on_segment_drawn;
void apply_segment_length(double len); // rescale the pending segment, then commit it
void keep_segment_as_drawn(); // commit the pending segment unchanged
// Live readout while drawing a Line/Polyline segment (anchor->cursor metrics).
std::function<void(double length, double angle_deg, bool locked)> on_cursor_metrics;
// DoF feedback (P3): solver state after each live solve. dof>0 = under-constrained,
// dof==0 = fully constrained, ok==false = conflicting/inconsistent constraints.
// has_constraints is false while the sketch carries no driving constraints yet.
std::function<void(int dof, bool ok, bool has_constraints)> on_solve_state;
// Selection (Mode::Select): pick points/lines/arcs/circles of the in-session
// sketch; Shift/Ctrl extends, double-click grabs the whole connected loop.
const std::vector<int>& selection() const { return m_selection; }
void clear_selection();
void delete_selected(); // erase selected entities
std::function<void(int count)> on_selection_changed;
// Dimension tool: infer a driving dimension from the current selection and set
// it exactly. Sizing: 1 line=Length, 1 circle=Diameter, 1 arc=Radius,
// 2 lines=Angle. Positioning (a value of 0 makes them coincident):
// 2 point-likes (point/circle-centre/arc-centre)=Distance, moving the 2nd onto
// the 1st; a point-like + a line=DistanceToLine, moving the point-like's
// reference point onto/away-from the line (e.g. a circle centre onto an axis).
enum class DimType { None, Length, Diameter, Radius, Angle, Distance, DistanceToLine };
DimType dimension_kind() const; // what the selection supports (None if invalid)
double dimension_current() const; // current value, to pre-fill the dialog
void apply_dimension(double v); // set it exactly, then clear the selection
// Onshape-style Dimension tool (Mode::Dimension): with the tool active you click
// directly in the viewport — 2 points -> Distance, a line -> Length, a circle ->
// Diameter, an arc -> Radius, a point then a line -> DistanceToLine. A quote line
// with extension lines, arrowheads and a numeric label is PLACED in the sketch and
// drives the geometry (auto-offset; label editable). on_dimension_pick_complete
// fires when a pick resolves so the panel can pop the value card pre-filled.
std::function<void(double current)> on_dimension_pick_complete;
DimType pending_dimension_type() const; // type of the dim awaiting a value, or None
void set_dimension_value(double v); // apply the typed value to the placed dim
void cancel_dimension_value(); // keep the placed dim at its measured value
// Onshape-style in-canvas value editing: open a floating text editor at the given
// screen pixel, pre-filled with `current`; commit applies the value, cancel keeps
// it. The owner (DesignCanvas) hosts the wxTextCtrl over the GL canvas. This is the
// single numeric-entry path for all sketch dimensions (replaces the modal cards).
std::function<void(wxPoint screen_px, double current,
std::function<void(double)> commit,
std::function<void()> cancel)> on_inline_edit;
// Force-close any open inline field (runs its cancel = keep-as-drawn). Used by the polyline
// terminators (right-click / double-click) to end the chain even mid per-segment edit.
std::function<void()> on_inline_dismiss;
// Bottom-right viewport readout: emitted each frame with the active tool's current
// values (live segment length/angle while drawing a line, or the selected entity's
// characteristic dimensions). Empty string -> hide the HUD. The owner (DesignCanvas)
// shows it as a floating corner label over the GL canvas.
std::function<void(const std::string&)> on_readout;
// Driving dimension constraints accumulated during the session (the Dimension
// tool records a SketchEntityConstraintDef per applied dimension); committed
// alongside the entities on finish() so the kernel keeps enforcing them.
const std::vector<SketchEntityConstraintDef>& constraints() const { return m_constraints; }
// Emitted by finish() with the accumulated entities + driving constraints.
std::function<void(const std::vector<SketchEntity>&,
const std::vector<SketchEntityConstraintDef>&,
const SketchPlane&)> on_commit_entities;
// Legacy single-profile commit (kept for compatibility; unused by entity tools).
std::function<void(const SketchProfile&, const SketchPlane&)> on_commit;
// Emitted when a closed-loop face is clicked in Select mode (Onshape: a region
// becomes a selectable face → extrude). The panel commits the sketch + extrudes.
std::function<void()> on_face_selected;
// Esc pressed while the tool is active: exit/cancel the session (the panel restores
// Feature mode). Layered: an in-progress entity or a non-Select draw tool is dropped
// first; a second Esc exits the session.
std::function<void()> on_exit;
std::function<void()> on_move_exit; // right-click finished the move-body gizmo
void request_exit();
// Ctrl+Z / Ctrl+Shift+Z (Ctrl+Y) while the Design canvas is focused: undo/redo the
// committed feature history. The tool just forwards to the host, which owns the
// CadDocument (the tool has no document of its own). redo == true requests redo.
std::function<void(bool /*redo*/)> on_undo_redo;
void request_undo_redo(bool redo);
private:
bool screen_to_plane(GLCanvas3D& canvas, const wxMouseEvent& evt, Vec2d& out) const;
bool near_first(const Vec2d& p) const;
// Onshape-style angle inference: snap the direction anchor->raw to the nearest
// of {0,30,45,60,90} deg (replicated every 90 deg) when within tolerance, keeping
// the same length. Sets `locked` when a snap was applied. Suppressed by m_snap_off.
Vec2d snap_dir(const Vec2d& anchor, const Vec2d& raw, bool& locked) const;
// Snap a placed point onto the nearest existing entity endpoint within ~8 px so
// chains join across entities (a line + an arc can close into one loop). Shift
// disables it. `snapped` reports whether a vertex was hit.
Vec2d snap_vertex(GLCanvas3D& canvas, const wxMouseEvent& evt, const Vec2d& raw, bool& snapped) const;
// --- P1 inference / auto-constraint engine ---------------------------------
// Plane-units tolerance equivalent to ~`px` screen pixels at the cursor.
double screen_tol(GLCanvas3D& canvas, const wxMouseEvent& evt, const Vec2d& at, double px = 8.0) const;
// Run kernel inference at the cursor, cache the target for the hint renderer.
InferenceSnap infer_at(GLCanvas3D& canvas, const wxMouseEvent& evt, const Vec2d& raw) const;
// True if m_constraints already holds an equivalent Coincident between the two refs.
bool has_coincident(int ea, SketchPointRole ra, int eb, SketchPointRole rb) const;
// Append candidates, live-solve, and roll back the batch if it turns the system
// inconsistent. Returns true when the batch was kept.
bool try_add_constraints(const std::vector<SketchEntityConstraintDef>& cands);
// After entities [base, end) were committed, auto-emit the constraints that make
// the new geometry stick: Coincident between co-located endpoints (so loops close
// on their own) and Horizontal/Vertical on axis-aligned new segments.
void infer_auto_constraints(int base);
// Selection helpers (Mode::Select).
int hit_test(const Vec2d& p, double tol) const; // nearest entity within tol, or -1
std::vector<int> connected_loop(int seed) const; // entities joined by shared endpoints
void apply_angle_between(int ia, int ib, double deg); // rotate line B to set the A^B angle
bool selection_valid() const; // all selection indices in range
void record_dimension_constraint(double v); // append the driving def for the selection
void resolve_live(); // solve accumulated constraints on m_entities now
// Drag-aware re-solve: pins the dragged point at its current coord and lets the
// solver move the rest (Slvs dragged[]). Used live while a point grab is active.
void resolve_live_drag(int dragged_ei, SketchPointRole dragged_role);
// Placed dimension annotation. References entity points/entities (not cached
// coords) so the quote follows the geometry as the kernel solves it. `value`
// drives the constraint stored at index `con` in m_constraints.
struct DimAnnot {
DimType kind{DimType::None};
int ea{-1}; SketchPointRole ra{SketchPointRole::P0};
int eb{-1}; SketchPointRole rb{SketchPointRole::P0};
double value{0.0};
double side{1.0}; // perpendicular offset sign of the quote line
int con{-1}; // slot in m_constraints driving this dimension
Vec2d label_pos{0, 0}; // cached label centre (plane coords), for picking
};
// --- Onshape-style visual editing: handles + parametric feature grouping -----
// A draggable handle on a defining point of an entity (or a derived point of a
// feature group). GUI-only; recomputed from solved geometry every frame (never
// persisted), so handles always track the current solve. Derived roles (radius,
// slot width/centres, rect corners, polygon vertex, ellipse axes) let tools that
// decompose into raw Line/Arc entities still expose their parametric controls.
enum class HandleRole { P0, P1, Center, RadiusHandle,
SlotCenter0, SlotCenter1, SlotWidth,
RectCorner, PolygonVertex, MajorAxis, MinorAxis, BSplineCtrl };
struct Handle {
HandleRole role{HandleRole::P0};
int ei{-1}; // primary entity index
int group{-1}; // index into m_features, or -1 for a raw-entity handle
int ctrl_index{-1}; // BSplineCtrl pole index
Vec2d pos{0, 0}; // current plane coords (recomputed each frame)
bool hovered{false};
};
// A parametric grouping over a contiguous run of entities produced by one gesture.
// Slot/Rect/Polygon/etc. have no SketchEntity type of their own — they decompose
// into raw Line/Arc entities — so the Feature carries the gesture's anchors so
// derived handles + characteristic dimensions can be reconstructed.
enum class FeatureKind { Free, Line, Circle, Arc, CornerRect, CenterRect,
Slot, ArcSlot, Polygon, Ellipse, RoundedRect, BSpline };
struct Feature {
FeatureKind kind{FeatureKind::Free};
int begin{0}, end{0}; // [begin,end) into m_entities
Vec2d c0{0, 0}, c1{0, 0}; // slot centres / rect corners / ellipse centre+major
double param{0.0}; // slot half-width / polygon circumradius / fillet radius
int sides{0}; // polygon side count
};
// Build the live handle set for the current selection / just-drawn feature.
std::vector<Handle> build_handles() const;
// Nearest handle to plane-point p within tol; fills `out`. (Phase A: stub.)
bool hit_test_handle(const Vec2d& p, double tol, Handle& out) const;
// Move a handle to `target`, applying the role-specific geometry edit + re-solve.
void set_handle(const Handle& h, const Vec2d& target);
// On a no-button move, recompute the hovered handle; returns true iff it changed
// (so the caller forces exactly one repaint). No-op for non-Moving events.
bool update_hover(GLCanvas3D& canvas, wxMouseEvent& evt);
// Index of the Feature whose [begin,end) entity span contains ei, or -1.
int feature_of(int ei) const;
// Re-detect parametric Feature groups (polygon / rect / slot) from the raw entity
// list — used when a committed sketch is re-opened, where m_features is empty.
void rebuild_features_from_entities();
// Open/close a Feature record around the entities a single gesture appends.
void begin_feature(FeatureKind kind);
void end_feature(const Vec2d& c0 = Vec2d(0, 0), const Vec2d& c1 = Vec2d(0, 0),
double param = 0.0, int sides = 0);
bool point_at(int ei, SketchPointRole role, Vec2d& out) const; // current coords
void set_point(int ei, SketchPointRole role, const Vec2d& v); // move an entity point
bool hit_test_point(const Vec2d& p, double tol, int& ei, SketchPointRole& role) const;
int hit_test_dimension(const Vec2d& p, double tol) const; // nearest dim label
void edit_dimension(int di); // reopen value card for di
// Representative plane-coords anchor of a dimension (label centre if known, else a
// geometric midpoint/centre) — where the in-canvas value editor is positioned.
Vec2d dim_anchor(const DimAnnot& a) const;
// Open the in-canvas value editor on dimension `di` (falls back to the modal
// pick-complete callback when no inline-edit host is wired).
void open_value_editor(int di);
// In-canvas editor for a line's angle-to-horizontal; commit rotates the segment
// geometrically about P0 (no single-line angle constraint in libslvs).
void open_angle_editor(int ei);
void set_line_angle(int ei, double deg);
// Draw-then-edit (all creation tools): open the inline editor on the freshly-drawn
// selection's PRIMARY characteristic value. Called after render_live_quotes has computed
// the selection's quotes, so it dispatches on the same live-quote state a Select-mode
// click would use.
void open_primary_autoedit();
// Compact "current values" string for the bottom-right HUD (see on_readout).
std::string build_readout() const;
// Open a characteristic live quote as a TENTATIVE driving dimension: the constraint is
// appended only if the user commits a value (Enter); cancel (Esc) adds nothing — so
// drawing never silently over-constrains. (place_dimension is the eager Select-mode twin.)
void open_next_autoedit_dim(); // opens m_autoedit_dims[idx]; commit -> next, Esc -> stop
void arm_polyline_segment_edit();// per-segment Length+Angle edit of the pending chain vertex
// In-canvas editors for a regular polygon's side length and orientation. Both edit
// the whole loop GEOMETRICALLY (polygon has no centre entity): side scales it
// uniformly about its centre, angle rotates it. set_polygon_radius is the shared
// uniform-scale primitive (circumradius).
void open_polygon_side_editor(int fi);
void open_polygon_angle_editor(int fi);
void set_polygon_side(int fi, double side);
void set_polygon_angle(int fi, double deg);
void set_polygon_radius(int fi, double R);
// Arc sweep-angle quote: geometric edit (SLVS angle is line-to-line only). Keeps the
// arc start point + radius fixed and moves the end point to span `deg` degrees.
void open_arc_angle_editor(int ei);
void set_arc_sweep(int ei, double deg);
// Arc handle drag (3 grips): Center rigidly translates; the START point changes the
// radius (keeps both sweep angles); the END point changes the sweep angle (keeps the
// radius). Geometric — no solver (SLVS has no arc radius/angle handle concept here).
void drag_arc_handle(int ei, SketchPointRole role, const Vec2d& target);
// Ellipse axis labels (geometric edit of the semi-axes a/b; phi via the major grip).
void open_ellipse_axis_editor(int ei, bool major);
void set_ellipse_axis(int ei, bool major, double v);
void set_ellipsearc_sweep(int ei, double deg); // draw-then-edit: included sweep of an elliptical arc
void set_rect_angle(int fi, double deg); // draw-then-edit: orientation of an oblique rect
// EllipseArc endpoint drag: Center translates; P0/P1 move the sweep start/end to the
// parametric angle of the cursor on the ellipse frame (radius/shape preserved).
void drag_ellipsearc_handle(int ei, SketchPointRole role, const Vec2d& target);
// Drop orientation constraints (H/V/Parallel/Perp/Angle/LockX/LockY) on entities in
// [begin,end). A ROTATION makes inferred per-edge H/V inconsistent, so re-solving
// against them collapses the shape — drop them first (fixes up DimAnnot.con indices).
void drop_orientation_constraints(int begin, int end);
// Drop every live constraint that references entity `ei` (Trim/Extend slide an endpoint,
// invalidating its constraints) and fix the dimensions' cached constraint indices.
void drop_constraints_referencing(int ei);
// Standalone Trim/Extend scissors on the LIVE sketch: pick the entity nearest `p` (within
// `tol` plane units) and cut it back to / out to its nearest intersection with the others.
// Returns true if an entity was modified.
bool apply_live_trim(const Vec2d& p, double tol, bool extend);
// Pure-computation hover preview for Trim/Extend: mirror apply_live_trim's pick + the
// engine's cut on a COPY (mutating nothing) and return, via `removed_poly`, the polyline
// of the sub-portion a click would REMOVE (Trim) or ADD (Extend). `subject_ei` is the
// picked entity. Returns false if nothing is in range or nothing would change.
bool compute_trim_preview(const Vec2d& p, double tol, bool extend,
int& subject_ei, std::vector<Vec2d>& removed_poly) const;
// Drag a polygon vertex while keeping the loop REGULAR: scale + rotate the whole
// polygon about its centroid so the grabbed vertex follows `target` (adjusts
// circumradius + orientation together).
void drag_polygon_vertex(int fi, int ei, SketchPointRole role, const Vec2d& target);
double measure_dim(const DimAnnot& a) const; // value from geometry
SketchEntityConstraintDef constraint_for(const DimAnnot& a) const; // driving def
int place_dimension(DimAnnot a); // create+drive+notify
std::string dim_text(const DimAnnot& a) const; // rendered label string
void render_dimensions(double unit_per_px); // quote lines + labels
// Draw ONE dimension's quote (extension/dimension lines, arrowheads, label) and
// return its label centre in out_label; false if the annot can't be drawn. Shared
// by render_dimensions (placed driving quotes) and render_live_quotes (live ones).
bool draw_dim_quote(const DimAnnot& a, double th, const ColorRGBA& col, Vec2d& out_label);
// Live, non-driving characteristic quotes for the entity being edited (point/handle
// drag, or a lone selection): the tool's defining dimensions shown Onshape-style so
// editing shows live values; click one (m_live_quotes) to promote it to a driving
// dim. Self-gates; skips a dim already driven on that entity.
void render_live_quotes(double unit_per_px);
// Iconic constraint badges (C3.4b): for each m_constrain_cons entry, append a
// small screen-constant glyph (H, V, ∥, ⊥, =, ○, …) near its primary entity into
// `out`; glyphs touching the same entity stack so they don't overlap.
void build_constraint_glyphs(double unit_per_px, std::vector<std::pair<Vec2d, Vec2d>>& out) const;
void draw_strokes(GLModel& model, const std::vector<std::pair<Vec2d, Vec2d>>& segs,
double hw, const ColorRGBA& color);
void draw_text(GLModel& model, const std::string& s, const Vec2d& center,
double height, const ColorRGBA& color); // GL stroke font
// Entity builders: append to m_entities (honoring the construction flag).
void push_line(const Vec2d& a, const Vec2d& b);
void push_closed_lines(const std::vector<Vec2d>& corners);
void push_open_chain(const std::vector<Vec2d>& pts);
void push_circle(const Vec2d& center, double radius);
void push_point(const Vec2d& p);
// Multi-click tool builders: return the entities for a finished gesture so
// both on_mouse (append) and render (preview) share one geometry path.
std::vector<SketchEntity> make_three_point_circle(const Vec2d& a, const Vec2d& b, const Vec2d& c) const;
std::vector<SketchEntity> make_three_point_arc(const Vec2d& start, const Vec2d& end, const Vec2d& on_arc) const;
std::vector<SketchEntity> make_tangent_arc(const Vec2d& start, const Vec2d& end) const;
// Center-start-end arc: click center, then start (sets radius), then a third
// point whose direction from the center sets the CCW end angle.
std::vector<SketchEntity> make_center_arc(const Vec2d& center, const Vec2d& start, const Vec2d& end_dir) const;
std::vector<SketchEntity> make_slot(const Vec2d& c0, const Vec2d& c1, double half_width) const;
std::vector<SketchEntity> make_arc_slot(const Vec2d& center, const Vec2d& start,
const Vec2d& end_dir, double half_width) const;
std::vector<SketchEntity> make_rounded_rect(const Vec2d& a, const Vec2d& b, const Vec2d& radius_pt) const;
std::vector<SketchEntity> rounded_rect_entities(double xmin, double ymin,
double xmax, double ymax, double r) const;
// Rounded-rect grouped edit: W/H/fillet-R labels rebuild the 8-entity span in place.
void open_rounded_rect_editor(int fi, int which); // 0=Width 1=Height 2=fillet R
void set_rounded_rect(int fi, double w, double h, double r);
// Arc-slot grouped edit: centreline-radius + width labels rebuild the 4-arc span.
void open_arc_slot_editor(int fi, bool radius); // true=centreline R, false=width
void set_arc_slot(int fi, double Rc, double w);
// Grouped derived-handle drag: resize an axis-aligned rect by a corner (opposite corner
// fixed); move a slot end by its cap centre. Both rebuild the feature span geometrically.
void drag_rect_corner(int fi, const Vec2d& cursor);
void drag_slot_handle(int fi, const Vec2d& cursor);
std::vector<SketchEntity> make_polygon(const Vec2d& center, const Vec2d& vertex, int sides) const;
// Ellipse: click center, then major-axis endpoint (sets a + rotation phi),
// then a point whose perpendicular distance to the major axis sets b.
std::vector<SketchEntity> make_ellipse(const Vec2d& center, const Vec2d& major_end,
const Vec2d& minor_pt) const;
// Elliptical arc: same 3 axis clicks, then start and end points whose parametric
// angles on the ellipse bound the CCW sweep.
std::vector<SketchEntity> make_bspline(const std::vector<Vec2d>& ctrl) const;
std::vector<SketchEntity> make_ellipse_arc(const Vec2d& center, const Vec2d& major_end,
const Vec2d& minor_pt, const Vec2d& start_pt,
const Vec2d& end_pt) const;
void append_entities(const std::vector<SketchEntity>& ents);
void draw_entities_preview(const std::vector<SketchEntity>& ents, const ColorRGBA& color);
// --- In-canvas edit-op gizmo (Fillet/Chamfer/Offset/Mirror toolbar tools) --------
// These replace the docked numeric card: pick the entities in-canvas, then a draggable
// arrow with a value label is projected toward the corner/centre (Fillet/Chamfer/Offset),
// or a two-phase pick (axis line, then targets) drives a live mirrored ghost. The
// SketchEngine op is recomputed live so a translucent ghost previews the result; confirm
// applies the geometry and binds constraints into m_constraints (try_add_constraints).
bool op_corner(int a, int b, Vec2d& C, Vec2d& bis, double& theta) const; // line-line vertex + inward bisector
void op_pick(int ei); // route an entity pick to the active op
void recompute_op_ghost(); // rebuild m_op_ghost from m_op_value
void render_op_gizmo(double unit_per_px); // ghost + arrow + value label (caches m_op_label)
bool hit_test_op_arrow(const Vec2d& p, double tol) const;
void drag_op_arrow(const Vec2d& target); // project cursor onto m_op_dir -> value
void open_op_editor(); // inline-edit the value label
void confirm_op(); // apply + bind, then reset for the next gesture
void reset_op(); // clear gizmo state (keeps the tool active)
bool op_ready() const; // required entities picked -> arrow/ghost live
// Sample an entity into a 2D polyline for the overlay renderer.
std::vector<Vec2d> entity_polyline(const SketchEntity& e, bool& closed) const;
// Closed regions formed by the current (non-construction) entities: each a CCW-
// ordered boundary polygon on the plane. A circle is its own region; line/arc
// chains are walked endpoint-to-endpoint into loops. Used to fill faces.
std::vector<std::vector<Vec2d>> closed_regions() const;
std::vector<std::vector<Vec2d>> closed_regions(const std::vector<SketchEntity>& ents) const;
// Same loops, but each carries the indices of the entities that form it — so a single
// loop can be highlighted / extruded on its own (per-region selection on the plate).
struct RegionLoop { std::vector<Vec2d> poly; std::vector<int> ents; };
std::vector<RegionLoop> region_loops(const std::vector<SketchEntity>& ents) const;
// Index of the closed region containing plane-point p (point-in-polygon), or -1.
int region_at(const Vec2d& p) const;
void draw_quad_strip(GLModel& model, const std::vector<Vec2d>& pts, bool closed, const ColorRGBA& color);
// half_size is the square marker half-extent in PLANE units. Callers pass a
// zoom-scaled value (k / zoom) for screen-constant handles; the default keeps
// legacy point markers exactly as before.
void draw_vertices(GLModel& model, const std::vector<Vec2d>& pts, const ColorRGBA& color,
double half_size = 1.3);
void draw_fill(GLModel& model, const std::vector<Vec2d>& poly, const ColorRGBA& color);
bool m_active{false};
SketchPlane m_plane;
std::vector<Vec2d> m_points; // clicks of the in-progress entity / chain
std::vector<SketchEntity> m_entities; // committed entities of this session
bool m_construction{false};
int m_polygon_sides{6};
bool m_polygon_circumscribed{false};
Vec2d m_cursor{0,0};
bool m_has_cursor{false};
bool m_snap_off{false}; // Shift held -> suppress angle snapping
InferenceSnap m_cursor_snap; // last cursor inference target (for hint render)
bool m_cursor_locked{false}; // rubber-band segment is angle-locked
bool m_awaiting_length{false}; // inline value editor open -> freeze canvas
int m_autoedit_seen{-1}; // entity count baseline for draw-then-edit
bool m_autoedit_pending{false};// a new entity just committed -> open editor
// Draw-then-edit step queue: every characteristic dimension of the freshly-drawn shape
// (scalar quote OR geometric editor) becomes one step, opened in sequence over its label.
struct AutoEditStep {
Vec2d label; // anchor (plane coords) — field opens over this
double value; // initial value shown
std::function<void(double)> apply; // commit: set the dimension
std::vector<int> hi; // entities to highlight while THIS field is open
};
std::vector<AutoEditStep> m_autoedit_dims; // queued steps to edit in sequence
int m_autoedit_dim_idx{-1}; // index into m_autoedit_dims (-1 = idle)
std::vector<int> m_selection; // selected entity indices (Mode::Select)
std::vector<std::pair<int, SketchPointRole>> m_point_sel; // selected individual points
int m_last_mouse_x{0}; // last cursor pos (canvas client px), for
int m_last_mouse_y{0}; // anchoring the in-canvas value editor
bool m_dragging_point{false}; // a point grab is in progress (Mode::Select)
int m_drag_ei{-1}; // entity whose point is being dragged
int m_drag_poly_fi{-1}; // >=0 if the grabbed point is a polygon
// vertex: drag scales+rotates the loop
int m_drag_rect_fi{-1}; // >=0 if dragging an axis-aligned rect corner
Vec2d m_drag_rect_anchor{0,0}; // the fixed (opposite) corner
int m_drag_slot_fi{-1}; // >=0 if dragging a slot cap centre
bool m_drag_slot_c1{false}; // true=cap@c1, false=cap@c0
SketchPointRole m_drag_role{SketchPointRole::P0};
std::vector<SketchEntityConstraintDef> m_constraints; // driving dims, committed on finish
// Onshape-style visual editing state.
bool m_show_handles{false}; // draw + interact with handles
bool m_dragging_handle{false};// a handle grab is in progress
Handle m_drag_handle; // the handle being dragged
bool m_has_hover_handle{false};// cursor is near a handle (highlight it)
Handle m_hover_handle; // the hovered handle (recomputed on move)
std::vector<DimAnnot> m_live_quotes; // live non-driving characteristic quotes,
// clickable to promote to driving dims
Vec2d m_live_poly_side_label{0,0}; // polygon side-length quote label
Vec2d m_live_poly_angle_label{0,0}; // polygon orientation quote label
int m_live_poly_fi{-1}; // their Feature (geometric edits)
Vec2d m_live_arc_angle_label{0,0}; // arc sweep-angle quote label
int m_live_arc_ei{-1}; // the arc it belongs to (geometric edit)
Vec2d m_live_ellipse_major_label{0,0}; // ellipse semi-major quote label
Vec2d m_live_ellipse_minor_label{0,0}; // ellipse semi-minor quote label
Vec2d m_live_ellipsearc_sweep_label{0,0}; // elliptical-arc sweep quote label
int m_live_ellipse_ei{-1}; // the ellipse the labels belong to
Vec2d m_live_obrect_angle_label{0,0}; // oblique-rect orientation quote label
int m_live_obrect_fi{-1}; // an OBLIQUE rect Feature (angle editable)
Vec2d m_live_rrect_w_label{0,0}; // rounded-rect width quote label
Vec2d m_live_rrect_h_label{0,0}; // rounded-rect height quote label
Vec2d m_live_rrect_r_label{0,0}; // rounded-rect fillet-radius label
int m_live_rrect_fi{-1}; // the rounded-rect Feature (rebuild edits)
Vec2d m_live_aslot_r_label{0,0}; // arc-slot centreline-radius label
Vec2d m_live_aslot_w_label{0,0}; // arc-slot width label
int m_live_aslot_fi{-1}; // the arc-slot Feature (rebuild edits)
std::vector<Feature> m_features; // parametric groups over m_entities
int m_open_feature{-1}; // index of the Feature being built, or -1
// In-canvas edit-op gizmo state (Fillet/Chamfer/Offset/Mirror). GUI-only, reset by
// set_tool/cancel. Fillet/Chamfer: m_op_a,m_op_b = the two lines; Offset: m_op_a = src;
// Mirror: m_op_a = axis line, m_mirror_targets = entities to mirror.
int m_op_a{-1};
int m_op_b{-1};
double m_op_value{0.0}; // radius / setback / signed offset distance
Vec2d m_op_anchor{0,0}; // arrow base (corner vertex / entity midpoint)
Vec2d m_op_dir{0,0}; // unit arrow direction (inward bisector / outward normal)
Vec2d m_op_label{1e18,1e18}; // cached arrow-label centre, for picking
std::vector<SketchEntity> m_op_ghost; // live result preview (recomputed on value change)
bool m_op_dragging_arrow{false}; // arrowhead drag in progress
std::vector<int> m_mirror_targets; // Mirror: entities to be mirrored (axis = m_op_a)
// In-canvas imported-art transform gizmo (Mode::TransformArt). GUI-only. The art's
// untransformed contours + its bbox in base coords; the live offset/scale; the grabbed
// handle (0..3 = corners, 4 = centre move, -1 = none) and the fixed world anchor (the
// opposite corner during a corner-scale drag).
std::vector<std::vector<std::vector<Vec2d>>> m_xform_base;
int m_xform_feat{-1};
Vec2d m_xform_min{0,0}, m_xform_max{0,0}; // bbox of m_xform_base (untransformed)
Vec2d m_xform_offset{0,0};
double m_xform_sx{1.0}, m_xform_sy{1.0};
int m_xform_handle{-1};
Vec2d m_xform_anchor{0,0};
void xform_world_corners(Vec2d out[4]) const; // 4 bbox corners in plane coords
int hit_test_xform_handle(const Vec2d& p, double tol) const;
void drag_xform_handle(const Vec2d& target);
void render_xform_gizmo();
void emit_xform();
void reset_xform();
// In-canvas transform gizmo state (Mode::Move/Rotate/Scale/Array/PolarArray). GUI-only,
// reset by set_tool/cancel. Pick one or more subject entities (m_tf_targets), then a
// single draggable handle drives the continuous parameter and a live translucent ghost
// previews the result; Array/PolarArray add a second editable label for the copy count.
// Mutating ops (Move/Rotate/Scale) drop the constraint classes the map invalidates;
// additive ops (Array/PolarArray) bind each copy to its source. See confirm_transform().
std::vector<int> m_tf_targets; // picked subject entity indices
Vec2d m_tf_pivot{0,0}; // rotate/scale/polar pivot = set centroid
Vec2d m_tf_delta{0,0}; // Move translation / Array per-step vector
double m_tf_angle{0.0}; // Rotate angle / PolarArray total sweep (rad)
double m_tf_scale{1.0}; // Scale factor
int m_tf_count{3}; // Array/PolarArray copy count (incl. original)
double m_tf_handle_r{1.0}; // ring/handle reference radius (set on pick)
std::vector<SketchEntity> m_tf_ghost; // live result preview
int m_tf_handle{-1}; // 0 = primary drag handle grabbed, -1 = none
bool m_tf_dragging{false};
Vec2d m_tf_label_a{1e18,1e18}; // primary-param label centre (picking)
Vec2d m_tf_label_b{1e18,1e18}; // count label centre (Array/PolarArray)
bool tf_ready() const; // >=1 target picked -> gizmo + ghost live
void tf_pick(int ei); // accumulate a subject, seed defaults once
void compute_tf_pivot(); // centroid + extent of the target set
void recompute_tf_ghost();
Vec2d tf_handle_pos() const; // world position of the drag handle
bool hit_test_tf_handle(const Vec2d& p, double tol) const;
void drag_tf_handle(const Vec2d& target);
void render_tf_gizmo(double unit_per_px);
void open_tf_editor_a(); // inline-edit the continuous parameter
void open_tf_editor_count(); // inline-edit the copy count
void confirm_transform(); // apply geometry + constraint web
void reset_tf();
// DoF feedback state, refreshed by resolve_live() from the libslvs solve result.
int m_dof{-1}; // remaining DoF; 0 = fully constrained, <0 = unknown
bool m_solve_ok{true}; // solver consistent (no conflicting constraints)
std::vector<char> m_entity_conflict; // per-entity flag: touched by a conflicting constraint
std::vector<DimAnnot> m_dimensions; // placed dimension quotes (Mode::Dimension)
int m_dim_e0{-1}; // first picked point's entity (Dimension)
SketchPointRole m_dim_r0{SketchPointRole::P0};
bool m_dim_has0{false}; // a first point is pending
int m_pending_dim{-1}; // dim awaiting a value-card entry
Mode m_mode{Mode::Polyline};
int m_sel_a{-1}; // picked segment endpoints (legacy Constrain mode)
int m_sel_b{-1};
bool m_constrain_entities{false}; // Constrain mode acts on entities
int m_pick0{-1}; // picked line-entity indices (entity Constrain)
int m_pick1{-1};
int m_pick2{-1}; // third slot (Symmetric axis)
Vec2d m_pick0_pt{0,0}; // plane-coords of the slot-0 pick (trim/extend)
std::vector<int> m_constraint_hl; // entities highlighted by the constraint manager
std::vector<SketchEntityConstraintDef> m_constrain_cons; // for glyph badges (C3.4b)
GLModel m_line_model;
GLModel m_vertex_model;
GLModel m_highlight_model;
GLModel m_fill_model; // translucent face fill for closed regions
std::vector<DisplaySketch> m_display_sketches; // committed sketches drawn persistently
int m_display_pick{-1}; // FEATURE index of the click-selected display sketch (-1 none)
// Solid (whole/face/edge) selection on the committed bodies. Pointers are non-owning,
// into CadDocument (bodies + display_mesh + per-triangle face/body ids), refreshed each
// recompute via set_solid_pick. m_sel_edge_pts caches the picked edge's world polyline.
const std::vector<CadBody>* m_solid_bodies{nullptr};
const TriangleMesh* m_solid_mesh{nullptr};
const std::vector<int>* m_solid_tri_face{nullptr};
const std::vector<int>* m_solid_tri_body{nullptr};
const std::vector<bool>* m_solid_visible{nullptr}; // per-body visibility; hidden bodies aren't pickable
const std::vector<Transform3d>* m_solid_xform{nullptr}; // per-body display transform (for edge sampling)
Vec3d body_xform_pt(int body, const Vec3d& p) const; // map an OCCT-shape point through the body xform
bool body_pickable(int b) const; // false when the body is explicitly hidden
SolidSel m_solid_sel{SolidSel::None};
int m_sel_body{-1}; // which body the face/edge selection is on
int m_sel_face{-1};
int m_sel_edge{-1};
std::vector<Vec3d> m_sel_edge_pts;
bool handle_solid_click(GLCanvas3D& canvas, const wxMouseEvent& evt); // cycle + notify
void render_solid_highlight();
void render_datum_planes(); // translucent rectangles for datum/reference planes
std::vector<SketchPlane> m_datum_planes;
GLModel m_solid_face_model;
GLModel m_solid_edge_model;
int m_display_pick_region{-1}; // selected closed-region index within that feature (-1 none)
// Visual Extrude gizmo state (C5b). GUI-only; fed by the panel each refresh_preview.
bool m_ex_active{false};
SketchPlane m_ex_plane; // profile plane (gives normal + to_world anchor)
Vec2d m_ex_centroid{0,0}; // arrow base in plane coords (profile centroid)
double m_ex_depth{0.0}; // primary depth (= m_distance)
double m_ex_depth2{0.0}; // second-side depth (TwoSided, = m_distance2)
bool m_ex_two_sided{false};
bool m_ex_flip{false};
int m_ex_drag{-1}; // 0 = primary arrow, 1 = second arrow, -1 = none
int m_ex_press_x{0}, m_ex_press_y{0}; // press px to tell click-to-edit from drag
void render_extrude_gizmo();
bool hit_test_extrude_arrow(GLCanvas3D& canvas, const wxMouseEvent& evt, int& which) const;
void drag_extrude_arrow(GLCanvas3D& canvas, const wxMouseEvent& evt, int which);
void open_extrude_editor(int which);
GLModel m_ex_arrow_model;
// Move-body gizmo state: 3 world-axis translate arrows + 3 world-axis rotate rings.
// Delta model: offset/rot are deltas about a fixed pivot, composed onto m_mv_base_xform
// (the body's pose when Move opened) so rotation works even on an already-placed body.
bool m_mv_active{false};
int m_mv_body{-1};
Vec3d m_mv_base{Vec3d::Zero()}; // pivot = body's world centroid at Move-open
Vec3d m_mv_offset{Vec3d::Zero()}; // delta translation along world X/Y/Z
Transform3d m_mv_base_xform{Transform3d::Identity()}; // pose when Move opened
Eigen::Matrix3d m_mv_rot{Eigen::Matrix3d::Identity()}; // accumulated delta rotation (world, about pivot)
Eigen::Matrix3d m_mv_rot_start{Eigen::Matrix3d::Identity()}; // rot snapshot at arc-drag start
double m_mv_arc_a0{0.0}; // mouse angle on the ring at drag start
int m_mv_drag{-1}; // 0..2 = X/Y/Z arrow, 3..5 = X/Y/Z ring, -1 none
int m_mv_press_x{0}, m_mv_press_y{0};
Transform3d compose_move_xform() const; // T(offset)*T(pivot)*rot*T(-pivot)*base_xform
void ring_basis(int axis, Vec3d& e, Vec3d& u, Vec3d& v) const; // world axis + in-plane basis
void render_move_gizmo();
bool hit_test_move_arrow(GLCanvas3D& canvas, const wxMouseEvent& evt, int& axis) const;
bool hit_test_move_arc(GLCanvas3D& canvas, const wxMouseEvent& evt, int& axis) const;
void drag_move_arrow(GLCanvas3D& canvas, const wxMouseEvent& evt, int axis);
void drag_move_arc(GLCanvas3D& canvas, const wxMouseEvent& evt, int axis);
bool arc_mouse_angle(GLCanvas3D& canvas, const wxMouseEvent& evt, int axis, double& ang) const;
void open_move_editor(int axis);
GLModel m_mv_arrow_model;
// Fillet/Chamfer radius gizmo state (single world-space arrow at the picked edge midpoint).
bool m_fl_active{false};
Vec3d m_fl_anchor{Vec3d::Zero()}; // edge midpoint (world, already body-transformed)
Vec3d m_fl_dir{Vec3d::UnitZ()}; // unit radius direction (perp to edge, outward)
double m_fl_radius{1.0}; // current radius (= dressup size)
bool m_fl_drag{false};
int m_fl_press_x{0}, m_fl_press_y{0};
double m_fl_grab_proj{0.0}; // axis projection at grab (relative drag reference)
double m_fl_grab_radius{1.0}; // radius at grab (relative drag reference)
void render_fillet_gizmo();
bool hit_test_fillet_arrow(GLCanvas3D& canvas, const wxMouseEvent& evt) const;
double fillet_axis_proj(GLCanvas3D& canvas, const wxMouseEvent& evt) const; // NaN if camera∥axis
void start_fillet_drag(GLCanvas3D& canvas, const wxMouseEvent& evt);
void drag_fillet_arrow(GLCanvas3D& canvas, const wxMouseEvent& evt);
void open_fillet_editor();
GLModel m_fl_arrow_model;
// Hole gizmo state. The hole is a positioned circular cut on m_hl_plane at (m_hl_x, m_hl_y);
// the footprint circle is drawn on the plane, the diameter arrow runs along the plane u-axis,
// the depth arrow along +normal (matching the kernel's make_extrude). Three draggable handles:
// 0 = centre (reposition in plane u/v), 1 = diameter, 2 = depth (only shown when !through).
bool m_hl_active{false};
SketchPlane m_hl_plane;
double m_hl_x{0.0}, m_hl_y{0.0}; // centre on the plane (u/v mm)
double m_hl_diameter{6.0};
double m_hl_depth{10.0};
bool m_hl_through{true};
// #2 Part B: face (u,v) bounds, so the construction dims read as distance from the face SIDES
// (umin/vmin = two adjacent edges) rather than from the centre. Off for a dropdown-plane hole.
bool m_hl_has_bounds{false};
double m_hl_umin{0}, m_hl_umax{0}, m_hl_vmin{0}, m_hl_vmax{0};
int m_hl_drag{-1}; // 0=centre, 1=diameter, 2=depth, 3=X-dim, 4=Y-dim, -1=none
int m_hl_press_x{0}, m_hl_press_y{0};
double m_hl_grab_proj{0.0}; // diameter/depth axis projection at grab (relative)
double m_hl_grab_val{0.0}; // radius (diameter drag) or depth at grab
Vec2d m_hl_grab_uv{0.0, 0.0}; // centre drag: plane-projected grab point
double m_hl_grab_x{0.0}, m_hl_grab_y{0.0}; // centre drag: x/y at grab
void render_hole_gizmo();
int hit_test_hole_handle(GLCanvas3D& canvas, const wxMouseEvent& evt) const; // 0/1/2/-1
double hole_axis_proj(GLCanvas3D& canvas, const wxMouseEvent& evt,
const Vec3d& anchor, const Vec3d& dir) const; // NaN if camera∥axis
void start_hole_drag(GLCanvas3D& canvas, const wxMouseEvent& evt, int which);
void drag_hole_handle(GLCanvas3D& canvas, const wxMouseEvent& evt);
void open_hole_editor(int which);
GLModel m_hl_stroke_model;
// Thread gizmo state (mirrors the hole gizmo; radius arrow uses an R label, length arrow is
// always shown). Handles: 0 = centre (thread_x/y), 1 = radius, 2 = length.
bool m_th_active{false};
SketchPlane m_th_plane;
double m_th_x{0.0}, m_th_y{0.0};
double m_th_radius{5.0};
double m_th_height{10.0};
int m_th_drag{-1}; // 0=centre, 1=radius, 2=length, -1=none
int m_th_press_x{0}, m_th_press_y{0};
double m_th_grab_proj{0.0};
double m_th_grab_val{0.0};
Vec2d m_th_grab_uv{0.0, 0.0};
double m_th_grab_x{0.0}, m_th_grab_y{0.0};
void render_thread_gizmo();
int hit_test_thread_handle(GLCanvas3D& canvas, const wxMouseEvent& evt) const; // 0/1/2/-1
void start_thread_drag(GLCanvas3D& canvas, const wxMouseEvent& evt, int which);
void drag_thread_handle(GLCanvas3D& canvas, const wxMouseEvent& evt);
void open_thread_editor(int which);
GLModel m_th_stroke_model;
// Shell gizmo state (single inward thickness arrow at the picked face centroid).
bool m_sh_active{false};
Vec3d m_sh_anchor{Vec3d::Zero()}; // picked face centroid (world)
Vec3d m_sh_dir{Vec3d::UnitZ()}; // inward unit direction (-outward normal)
double m_sh_thickness{2.0};
bool m_sh_drag{false};
int m_sh_press_x{0}, m_sh_press_y{0};
double m_sh_grab_proj{0.0};
double m_sh_grab_val{2.0};
void render_shell_gizmo();
bool hit_test_shell_arrow(GLCanvas3D& canvas, const wxMouseEvent& evt) const;
void start_shell_drag(GLCanvas3D& canvas, const wxMouseEvent& evt);
void drag_shell_arrow(GLCanvas3D& canvas, const wxMouseEvent& evt);
void open_shell_editor();
GLModel m_sh_stroke_model;
// Revolve gizmo state (arc center = projection of the profile centroid onto the axis).
bool m_rv_active{false};
Vec3d m_rv_center{Vec3d::Zero()}; // arc center on the axis (world)
Vec3d m_rv_axis{Vec3d::UnitX()}; // revolve axis unit dir (world)
Vec3d m_rv_ref{Vec3d::UnitY()}; // angle-0 reference dir (perp to axis, toward profile)
double m_rv_radius{10.0}; // arc radius = profile perpendicular distance (world)
double m_rv_angle{360.0}; // current sweep magnitude (deg, 1..360)
bool m_rv_flip{false}; // sweep sense (matches the kernel's negative-angle flip)
bool m_rv_drag{false};
int m_rv_press_x{0}, m_rv_press_y{0};
void render_revolve_gizmo();
bool hit_test_revolve_handle(GLCanvas3D& canvas, const wxMouseEvent& evt) const;
void drag_revolve_arc(GLCanvas3D& canvas, const wxMouseEvent& evt);
void open_revolve_editor();
GLModel m_rv_stroke_model;
// Pattern gizmo state. Linear arrow along m_pt_dirw from m_pt_base; circular arc like Revolve
// but axis = m_pt_normal through m_pt_origin (the world XY plane by default).
bool m_pt_active{false};
bool m_pt_circular{false};
Vec3d m_pt_base{Vec3d::Zero()}; // target body centroid (world): linear anchor / radius ref
Vec3d m_pt_dirw{Vec3d::UnitX()}; // linear march direction (world)
Vec3d m_pt_origin{Vec3d::Zero()}; // circular rotation axis origin (world)
Vec3d m_pt_normal{Vec3d::UnitZ()}; // circular rotation axis (world)
Vec3d m_pt_cref{Vec3d::UnitX()}; // circular angle-0 reference dir (perp to normal, toward body)
Vec3d m_pt_ccenter{Vec3d::Zero()}; // circular arc center (foot of body centroid on the axis)
double m_pt_radius{10.0}; // circular arc radius (world)
int m_pt_count{3};
double m_pt_spacing{20.0};
double m_pt_angle{360.0};
bool m_pt_drag{false};
int m_pt_press_x{0}, m_pt_press_y{0};
void render_pattern_gizmo();
bool hit_test_pattern_handle(GLCanvas3D& canvas, const wxMouseEvent& evt) const;
void drag_pattern_handle(GLCanvas3D& canvas, const wxMouseEvent& evt);
void open_pattern_editor();
GLModel m_pt_stroke_model;
};
}} // namespace Slic3r::GUI
#endif // slic3r_DesignSketchTool_hpp_
+102 -1
View File
@@ -1,5 +1,6 @@
#include "libslic3r/libslic3r.h"
#include "GLCanvas3D.hpp"
#include "DesignSketchTool.hpp" // SnapOrca Design: interactive 2D sketch tool
#include <igl/unproject.h>
@@ -1833,6 +1834,16 @@ void GLCanvas3D::enable_separator_toolbar(bool enable)
m_separator_toolbar.set_enabled(enable);
}
void GLCanvas3D::enable_collapse_toolbar(bool enable)
{
m_collapse_toolbar_enabled = enable;
}
void GLCanvas3D::enable_plate_chrome(bool enable)
{
m_plate_chrome_enabled = enable;
}
void GLCanvas3D::zoom_to_bed()
{
BoundingBoxf3 box = m_bed.build_volume().bounding_volume();
@@ -2123,6 +2134,11 @@ void GLCanvas3D::render(bool only_init)
if (_is_fxaa_enabled())
_render_fxaa_pass(static_cast<unsigned int>(cnv_size.get_width()), static_cast<unsigned int>(cnv_size.get_height()));
// SnapOrca Design: interactive 2D sketch overlay, drawn over the scene but
// beneath the UI overlays (toolbars, labels).
if (m_design_sketch_tool != nullptr && m_design_sketch_tool->has_display())
m_design_sketch_tool->render(*this);
// draw overlays
_render_overlays();
@@ -3278,6 +3294,56 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
return;
}
// SnapOrca Design: Delete/Backspace removes the selected sketch entities while a
// sketch tool is active and the canvas has focus (dialog text fields are separate
// wx controls, so this never eats their editing keys).
if (m_design_sketch_tool != nullptr && m_design_sketch_tool->is_active()
&& (keyCode == WXK_DELETE || keyCode == WXK_BACK)
&& !m_design_sketch_tool->selection().empty()) {
m_design_sketch_tool->delete_selected();
m_dirty = true;
render();
return;
}
// Esc exits the active sketch tool (Onshape-like, layered: abort in-progress entity ->
// drop to Select -> exit the session back to Feature mode).
if (m_design_sketch_tool != nullptr && m_design_sketch_tool->is_active()
&& keyCode == WXK_ESCAPE) {
m_design_sketch_tool->request_exit();
m_dirty = true;
render();
return;
}
// SnapOrca Design: Ctrl+Z / Ctrl+Shift+Z (and Ctrl+Y) undo/redo the Design feature
// history. Scoped by m_design_sketch_tool — only the Design canvas owns one — so the
// main 3D editor's undo/redo (the CanvasView3D-gated cases further below) is untouched.
// Handled here, before the generic Ctrl block, so it takes precedence and early-returns.
if (m_design_sketch_tool != nullptr && (evt.GetModifiers() & ctrlMask) != 0) {
const bool is_z = (keyCode == 'z' || keyCode == 'Z' || keyCode == WXK_CONTROL_Z);
const bool is_y = (keyCode == 'y' || keyCode == 'Y' || keyCode == WXK_CONTROL_Y);
if (is_z || is_y) {
const bool redo = is_y || ((evt.GetModifiers() & shiftMask) != 0);
m_design_sketch_tool->request_undo_redo(redo);
m_dirty = true;
render();
return;
}
}
// SnapOrca Design: F = Place on Face (Prepare's lay-flat), when the Design viewport is up
// and a body face is selected. The tool forwards to DesignPanel::place_on_face; it returns
// false (no face picked) so F falls through to the default handler below.
if (m_design_sketch_tool != nullptr && m_design_sketch_tool->has_display()
&& (keyCode == 'f' || keyCode == 'F') && (evt.GetModifiers() & ctrlMask) == 0) {
if (m_design_sketch_tool->request_place_on_face()) {
m_dirty = true;
render();
return;
}
}
bool is_in_painting_mode = false;
GLGizmoPainterBase *current_gizmo_painter = dynamic_cast<GLGizmoPainterBase *>(get_gizmos_manager().get_current());
if (current_gizmo_painter != nullptr) {
@@ -3650,6 +3716,18 @@ public:
void GLCanvas3D::on_key(wxKeyEvent& evt)
{
// SnapOrca Design: Delete/Backspace removes selected sketch entities. GTK delivers
// these as KEY_DOWN rather than CHAR, so handle it here too.
if (evt.GetEventType() == wxEVT_KEY_DOWN
&& m_design_sketch_tool != nullptr && m_design_sketch_tool->is_active()
&& (evt.GetKeyCode() == WXK_DELETE || evt.GetKeyCode() == WXK_BACK)
&& !m_design_sketch_tool->selection().empty()) {
m_design_sketch_tool->delete_selected();
m_dirty = true;
render();
return;
}
static GLCanvas3D const * thiz = nullptr;
static TranslationProcessor translationProcessor(nullptr, nullptr);
if (thiz != this) {
@@ -4180,6 +4258,21 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
return;
}
// SnapOrca Design: the interactive sketch tool owns the mouse whenever it has
// something on screen — an active session OR committed sketch overlays that the user
// can click to select. It runs after ImGui (so dialogs still work) but before
// camera/toolbar/gizmo handling; on_mouse returns false for events it doesn't consume
// (drag/orbit/wheel) so the camera keeps working over the display-only plate.
if (m_design_sketch_tool != nullptr && m_design_sketch_tool->has_display()) {
if (evt.LeftDown() && m_canvas != nullptr)
m_canvas->SetFocus(); // grab keyboard focus so Delete/keys reach this canvas
if (m_design_sketch_tool->on_mouse(evt, *this)) {
m_dirty = true;
render(); // force an immediate redraw so the sketch overlay updates live
return;
}
}
#ifdef __WXMSW__
bool on_enter_workaround = false;
if (! evt.Entering() && ! evt.Leaving() && m_mouse.position.x() == -1.0) {
@@ -7853,7 +7946,12 @@ void GLCanvas3D::_render_bed(const Transform3d& view_matrix, const Transform3d&
void GLCanvas3D::_render_platelist(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current, bool only_body, int hover_id, bool render_cali, bool show_grid)
{
wxGetApp().plater()->get_partplate_list().render(view_matrix, projection_matrix, bottom, only_current, only_body, hover_id, render_cali, show_grid);
// SnapOrca Design: transiently suppress plate chrome for opted-out canvases.
auto& plate_list = wxGetApp().plater()->get_partplate_list();
const bool prev_hide_chrome = plate_list.get_hide_chrome();
plate_list.set_hide_chrome(!m_plate_chrome_enabled);
plate_list.render(view_matrix, projection_matrix, bottom, only_current, only_body, hover_id, render_cali, show_grid);
plate_list.set_hide_chrome(prev_hide_chrome);
}
void GLCanvas3D::_render_cast_shadows_on_plate(const Transform3d& view_matrix, const Transform3d& projection_matrix)
@@ -9359,6 +9457,9 @@ void GLCanvas3D::_render_separator_toolbar_left() const
void GLCanvas3D::_render_collapse_toolbar() const
{
if (!m_collapse_toolbar_enabled)
return;
auto& plater = *wxGetApp().plater();
const auto sidebar_docking_dir = plater.get_sidebar_docking_state();
if (sidebar_docking_dir == Sidebar::None) {
+8
View File
@@ -57,6 +57,7 @@ namespace GUI {
class Bed3D;
class PartPlateList;
class DesignSketchTool; // SnapOrca Design: interactive 2D sketch tool
#if ENABLE_RETINA_GL
class RetinaHelper;
@@ -542,6 +543,9 @@ private:
mutable Vec2i32 m_canvas_toolbar_pos = {140, 5};
mutable float m_sc{1};
mutable float m_paint_toolbar_width;
bool m_collapse_toolbar_enabled{true};
bool m_plate_chrome_enabled{true};
DesignSketchTool* m_design_sketch_tool{nullptr};
//BBS: add canvas type for assemble view usage
ECanvasType m_canvas_type;
@@ -879,6 +883,10 @@ public:
void enable_assemble_view_toolbar(bool enable);
void enable_return_toolbar(bool enable);
void enable_separator_toolbar(bool enable);
void enable_collapse_toolbar(bool enable);
void enable_plate_chrome(bool enable);
void set_design_sketch_tool(DesignSketchTool* tool) { m_design_sketch_tool = tool; }
DesignSketchTool* get_design_sketch_tool() const { return m_design_sketch_tool; }
void enable_dynamic_background(bool enable) { m_dynamic_background_enabled = enable; }
void enable_labels(bool enable) { m_labels.enable(enable); }
void enable_slope(bool enable) { m_slope.enable(enable); }
+186
View File
@@ -0,0 +1,186 @@
#include "GLGizmoPrimitive.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/ImGuiWrapper.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/NotificationManager.hpp"
#include "libslic3r/Model.hpp"
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#include <imgui/imgui_internal.h>
namespace Slic3r {
namespace GUI {
GLGizmoPrimitive::GLGizmoPrimitive(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, sprite_id) {}
bool GLGizmoPrimitive::on_init() { return true; }
std::string GLGizmoPrimitive::on_get_name() const { return _u8L("Primitive"); }
bool GLGizmoPrimitive::on_is_activable() const { return true; }
void GLGizmoPrimitive::on_render() {}
void GLGizmoPrimitive::on_set_state()
{ if (m_state == EState::On) { m_params = PrimitiveParams{}; m_preview_dirty = true; } }
bool GLGizmoPrimitive::on_mouse(const wxMouseEvent&) { return false; }
CommonGizmosDataID GLGizmoPrimitive::on_get_requirements() const
{ return CommonGizmosDataID(int(CommonGizmosDataID::SelectionInfo) | int(CommonGizmosDataID::InstancesHider)); }
void GLGizmoPrimitive::on_load(cereal::BinaryInputArchive& ar)
{ ar(m_params); m_preview_dirty = true; }
void GLGizmoPrimitive::on_save(cereal::BinaryOutputArchive& ar) const
{ ar(m_params); }
void GLGizmoPrimitive::apply_preset(const char*, double w, double h, double d)
{
m_params.type = PrimitiveType::Box;
m_params.box_w = w; m_params.box_h = h; m_params.box_d = d;
m_preview_dirty = true;
}
static void gen_mesh_and_add(PrimitiveParams& p, const char* snap_name)
{
TopoDS_Solid solid = GeometryEngine::make_primitive(p);
TopoDS_Shape shape = solid;
if (p.dressup_enabled) {
if (p.dressup_type == DressUpType::Fillet)
shape = GeometryEngine::apply_fillet(shape, p.dressup_radius, p.dressup_faces);
else
shape = GeometryEngine::apply_chamfer(shape, p.dressup_chamfer_dist, p.dressup_faces);
}
TriangleMesh mesh = GeometryEngine::tessellate(shape, p.linear_deflection, p.angular_deflection);
if (mesh.its.indices.empty()) {
wxGetApp().notification_manager()->push_notification(NotificationType::CustomNotification, NotificationManager::NotificationLevel::WarningNotificationLevel, _u8L("Empty mesh generated"));
return;
}
wxGetApp().plater()->take_snapshot(snap_name);
ModelObject* mo = wxGetApp().model().add_object();
std::string name = GeometryEngine::primitive_name(p.type);
if (p.dressup_enabled && p.dressup_type == DressUpType::Fillet) name += " (Fillet)";
else if (p.dressup_enabled) name += " (Chamfer)";
mo->name = name;
mo->add_volume(std::move(mesh))->set_new_unique_id();
mo->ensure_on_bed();
wxGetApp().plater()->update();
}
void GLGizmoPrimitive::apply_primitive() { gen_mesh_and_add(m_params, "Add Primitive"); }
void GLGizmoPrimitive::on_render_input_window(float x, float y, float bottom_limit)
{
y = std::min(y, bottom_limit - ImGui::GetWindowHeight());
const float scale = m_parent.get_scale();
ImGuiWrapper::push_toolbar_style(scale);
GizmoImguiSetNextWIndowPos(x, y, ImGuiCond_Always, 0.0f, 0.0f);
GizmoImguiBegin("Primitive", ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove
| ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse
| ImGuiWindowFlags_NoTitleBar);
if (ImGui::CollapsingHeader("Shape", ImGuiTreeNodeFlags_DefaultOpen)) {
static const char* names[] = {"Box", "Cylinder", "Sphere", "Cone", "Torus"};
int cur = (int)m_params.type;
if (ImGui::Combo("##type", &cur, names, (int)PrimitiveType::COUNT)) {
m_params.type = (PrimitiveType)cur;
m_preview_dirty = true;
}
ImGui::Text("Quick:");
ImGui::SameLine();
if (ImGui::SmallButton("10mm")) apply_preset("10mm cube", 10, 10, 10);
ImGui::SameLine();
if (ImGui::SmallButton("20mm")) apply_preset("20mm cube", 20, 20, 20);
ImGui::SameLine();
if (ImGui::SmallButton("50mm")) apply_preset("50mm cube", 50, 50, 50);
}
ImGui::Separator();
if (ImGui::CollapsingHeader("Dimensions", ImGuiTreeNodeFlags_DefaultOpen)) {
auto dim = [&](const char* label, double& val, double step=0.5, double fast=5.0) {
ImGui::SetNextItemWidth(130);
if (ImGui::InputDouble(label, &val, step, fast, "%.1f mm")) m_preview_dirty = true;
if (val < 0.5) val = 0.5;
};
switch (m_params.type) {
case PrimitiveType::Box:
dim("Width (X)", m_params.box_w);
dim("Depth (Y)", m_params.box_d);
dim("Height (Z)", m_params.box_h);
break;
case PrimitiveType::Cylinder:
dim("Radius", m_params.cyl_radius);
dim("Height", m_params.cyl_height);
break;
case PrimitiveType::Sphere:
dim("Radius", m_params.sph_radius);
break;
case PrimitiveType::Cone:
dim("Bottom R", m_params.cone_r1);
dim("Top R", m_params.cone_r2);
dim("Height", m_params.cone_height);
break;
case PrimitiveType::Torus:
dim("Major R", m_params.torus_r1);
dim("Minor R", m_params.torus_r2, 0.1, 1.0);
break;
default: break;
}
}
ImGui::Separator();
if (ImGui::CollapsingHeader("Fillet / Chamfer")) {
ImGui::Checkbox("Enable", &m_params.dressup_enabled);
if (m_params.dressup_enabled) {
static const char* dn[] = {"Fillet", "Chamfer"};
int du = (int)m_params.dressup_type;
ImGui::SetNextItemWidth(100);
if (ImGui::Combo("##dtype", &du, dn, 2)) { m_params.dressup_type = (DressUpType)du; m_preview_dirty = true; }
static const char* fn[] = {"All edges", "Top edges", "Bottom edges", "Lateral edges"};
int fg = (int)m_params.dressup_faces;
ImGui::SetNextItemWidth(140);
if (ImGui::Combo("Edges", &fg, fn, 4)) { m_params.dressup_faces = (FaceGroup)fg; m_preview_dirty = true; }
if (m_params.dressup_type == DressUpType::Fillet) {
ImGui::SetNextItemWidth(100);
if (ImGui::InputDouble("Radius", &m_params.dressup_radius, 0.1, 1.0, "%.1f mm")) {
if (m_params.dressup_radius < 0.1) m_params.dressup_radius = 0.1;
m_preview_dirty = true;
}
} else {
ImGui::SetNextItemWidth(100);
if (ImGui::InputDouble("Distance", &m_params.dressup_chamfer_dist, 0.1, 1.0, "%.1f mm")) {
if (m_params.dressup_chamfer_dist < 0.1) m_params.dressup_chamfer_dist = 0.1;
m_preview_dirty = true;
}
}
}
}
ImGui::Separator();
if (ImGui::CollapsingHeader("Quality")) {
ImGui::SetNextItemWidth(130);
if (ImGui::InputDouble("Mesh resolution", &m_params.linear_deflection, 0.001, 0.1, "%.3f mm")) {
if (m_params.linear_deflection < 0.001) m_params.linear_deflection = 0.001;
if (m_params.linear_deflection > 1.0) m_params.linear_deflection = 1.0;
m_preview_dirty = true;
}
}
ImGui::Separator();
if (ImGui::Button("Add Shape", {-1, 28}))
apply_primitive();
if (ImGui::Button("Close", {-1, 0}))
m_parent.reset_all_gizmos();
GizmoImguiEnd();
ImGuiWrapper::pop_toolbar_style();
}
} // namespace GUI
} // namespace Slic3r
@@ -0,0 +1,43 @@
#ifndef slic3r_GLGizmoPrimitive_hpp_
#define slic3r_GLGizmoPrimitive_hpp_
#include "GLGizmoBase.hpp"
#include "GLGizmosCommon.hpp"
#include "libslic3r/GeometryEngine.hpp"
namespace Slic3r {
namespace GUI {
class GLGizmoPrimitive : public GLGizmoBase
{
public:
GLGizmoPrimitive(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
~GLGizmoPrimitive() = default;
bool on_mouse(const wxMouseEvent& mouse_event) override;
protected:
bool on_init() override;
std::string on_get_name() const override;
bool on_is_activable() const override;
void on_render() override;
void on_set_state() override;
CommonGizmosDataID on_get_requirements() const override;
void on_render_input_window(float x, float y, float bottom_limit) override;
void on_load(cereal::BinaryInputArchive& ar) override;
void on_save(cereal::BinaryOutputArchive& ar) const override;
private:
void apply_primitive();
void apply_preset(const char* name, double w, double h, double d);
PrimitiveParams m_params;
TriangleMesh m_preview_mesh;
bool m_preview_dirty{true};
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmoPrimitive_hpp_
+459
View File
@@ -0,0 +1,459 @@
#include "GLGizmoSketch.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/ImGuiWrapper.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/NotificationManager.hpp"
#include "libslic3r/Model.hpp"
#include <BRepBuilderAPI_MakeFace.hxx>
#include <BRepPrimAPI_MakeRevol.hxx>
#include <BRepAlgoAPI_Fuse.hxx>
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#include <imgui/imgui_internal.h>
#define L(s) Slic3r::GUI::I18N::translate((s)).c_str()
#define UL(s) Slic3r::GUI::I18N::translate_utf8((s)).c_str()
namespace Slic3r {
namespace GUI {
GLGizmoSketch::GLGizmoSketch(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, sprite_id) {}
bool GLGizmoSketch::on_init() { return true; }
std::string GLGizmoSketch::on_get_name() const { return _u8L("Sketch"); }
bool GLGizmoSketch::on_is_activable() const { return true; }
void GLGizmoSketch::on_render() {}
void GLGizmoSketch::on_set_state() { if (m_state == EState::On) clear_all(); }
bool GLGizmoSketch::on_mouse(const wxMouseEvent&) { return false; }
CommonGizmosDataID GLGizmoSketch::on_get_requirements() const
{ return CommonGizmosDataID(int(CommonGizmosDataID::SelectionInfo)); }
void GLGizmoSketch::on_load(cereal::BinaryInputArchive& ar)
{
ar(m_tool, m_profiles, m_plane, m_sp, m_rect_w, m_rect_h, m_circle_r, m_poly_sides, m_poly_r, m_snap_grid, m_grid_step);
m_active_profile = -1;
}
void GLGizmoSketch::on_save(cereal::BinaryOutputArchive& ar) const
{
ar(m_tool, m_profiles, m_plane, m_sp, m_rect_w, m_rect_h, m_circle_r, m_poly_sides, m_poly_r, m_snap_grid, m_grid_step);
}
SketchProfile& GLGizmoSketch::active_profile()
{
if (m_active_profile < 0 || m_active_profile >= (int)m_profiles.size()) {
m_profiles.emplace_back();
m_active_profile = (int)m_profiles.size() - 1;
}
return m_profiles[m_active_profile];
}
bool GLGizmoSketch::has_closed_profile() const
{
for (auto& p : m_profiles) if (p.closed && p.points.size() >= 3) return true;
return false;
}
void GLGizmoSketch::clear_all()
{
m_profiles.clear();
m_canvas_points.clear();
m_active_profile = -1;
}
void GLGizmoSketch::add_closed_profile()
{
auto& ap = active_profile();
if (ap.points.size() >= 3) {
ap.closed = true;
m_active_profile = -1;
}
}
void GLGizmoSketch::delete_profile(int idx)
{
if (idx >= 0 && idx < (int)m_profiles.size()) {
m_profiles.erase(m_profiles.begin() + idx);
if (m_active_profile >= (int)m_profiles.size()) m_active_profile = -1;
}
}
Vec2d GLGizmoSketch::snap(Vec2d pt) const
{
if (!m_snap_grid) return pt;
double gs = m_grid_step;
return {round(pt.x() / gs) * gs, round(pt.y() / gs) * gs};
}
void GLGizmoSketch::build_preset_profile()
{
auto& ap = active_profile();
ap.clear();
auto add = [&](double x, double y) { ap.points.emplace_back(x, y); };
switch (m_tool) {
case SketchTool::Rectangle:
add(-m_rect_w/2, -m_rect_h/2); add( m_rect_w/2, -m_rect_h/2);
add( m_rect_w/2, m_rect_h/2); add(-m_rect_w/2, m_rect_h/2);
ap.closed = true; m_active_profile = -1; break;
case SketchTool::Circle:
for (int i = 0; i <= m_circle_seg; ++i) {
double a = 2.0*M_PI*i/m_circle_seg;
add(cos(a)*m_circle_r, sin(a)*m_circle_r);
}
ap.closed = true; m_active_profile = -1; break;
case SketchTool::Polygon:
for (int i = 0; i < m_poly_sides; ++i) {
double a = 2.0*M_PI*i/m_poly_sides - M_PI/2;
add(cos(a)*m_poly_r, sin(a)*m_poly_r);
}
ap.closed = true; m_active_profile = -1; break;
default: break;
}
}
void GLGizmoSketch::handle_canvas_click(ImVec2 pos)
{
Vec2d pt = snap({pos.x / m_canvas_scale, -pos.y / m_canvas_scale});
if (m_tool == SketchTool::Line) {
auto& ap = active_profile();
if (ap.points.size() >= 3 && (pt - ap.points.front()).norm() < m_grid_step) {
ap.points.push_back(ap.points.front());
ap.closed = true;
m_active_profile = -1;
return;
}
ap.points.push_back(pt);
}
}
void GLGizmoSketch::draw_canvas()
{
ImDrawList* dl = ImGui::GetWindowDrawList();
ImVec2 pos = ImGui::GetCursorScreenPos();
float w = 280, h = 200;
ImVec2 end(pos.x+w, pos.y+h);
float cx = pos.x+w/2, cy = pos.y+h/2;
auto tc = [&](const ImVec2& p) { return ImVec2(cx+p.x*m_canvas_scale, cy-p.y*m_canvas_scale); };
dl->AddRectFilled(pos, end, IM_COL32(28,28,36,255));
dl->AddRect(pos, end, IM_COL32(55,55,68,255));
float gs = m_grid_step;
for (float g = 0; g < w; g += gs * m_canvas_scale) {
ImU32 gc = (int(g/(gs*m_canvas_scale)) % 5 == 0) ? IM_COL32(60,60,75,100) : IM_COL32(45,45,55,60);
dl->AddLine({pos.x+g,pos.y}, {pos.x+g,end.y}, gc);
}
for (float g = 0; g < h; g += gs * m_canvas_scale) {
ImU32 gc = (int(g/(gs*m_canvas_scale)) % 5 == 0) ? IM_COL32(60,60,75,100) : IM_COL32(45,45,55,60);
dl->AddLine({pos.x,pos.y+g}, {end.x,pos.y+g}, gc);
}
dl->AddLine({cx,pos.y},{cx,end.y}, IM_COL32(70,70,85,180), 1.5f);
dl->AddLine({pos.x,cy},{end.x,cy}, IM_COL32(70,70,85,180), 1.5f);
dl->AddText({end.x-12, cy+2}, IM_COL32(120,120,140,200), "X");
dl->AddText({cx+4, pos.y+2}, IM_COL32(120,120,140,200), "Y");
for (size_t pi = 0; pi < m_profiles.size(); ++pi) {
auto& prof = m_profiles[pi];
if (prof.points.size() < 2) continue;
std::vector<ImVec2> sp;
for (auto& p : prof.points) sp.push_back(tc({(float)p.x(), (float)p.y()}));
if (prof.closed && sp.size() >= 3) {
bool is_outer = (pi == 0);
ImU32 fill = is_outer ? IM_COL32(0,180,90,35) : IM_COL32(180,60,60,35);
ImU32 line = is_outer ? IM_COL32(0,220,100,255) : IM_COL32(220,80,80,255);
dl->AddConvexPolyFilled(sp.data(), (int)sp.size(), fill);
for (size_t i=0; i<sp.size(); ++i)
dl->AddLine(sp[i], sp[(i+1)%sp.size()], line, (pi==0)?2.5f:2.0f);
for (size_t i=0; i<sp.size()-1; ++i)
dl->AddCircleFilled(sp[i], 3.0f, IM_COL32(255,255,255,255));
}
}
auto& ap = active_profile();
if (!ap.closed && ap.points.size() >= 1) {
std::vector<ImVec2> sp;
for (auto& p : ap.points) sp.push_back(tc({(float)p.x(), (float)p.y()}));
for (size_t i=1; i<sp.size(); ++i)
dl->AddLine(sp[i-1], sp[i], IM_COL32(0,200,255,200), 2.0f);
for (auto& s : sp) dl->AddCircleFilled(s, 3.5f, IM_COL32(100,200,255,255));
ImVec2 mouse = ImGui::GetMousePos();
if (mouse.x > pos.x && mouse.x < end.x && mouse.y > pos.y && mouse.y < end.y)
dl->AddLine(sp.back(), mouse, IM_COL32(100,160,220,120), 1.5f);
}
ImGui::InvisibleButton("canvas", ImVec2(w,h));
if (ImGui::IsItemHovered()) {
ImVec2 m = ImGui::GetMousePos();
Vec2d sk({(m.x-cx)/m_canvas_scale, -(m.y-cy)/m_canvas_scale});
if (m_snap_grid) sk = snap(sk);
auto txt = wxString::Format("X:%.1f Y:%.1f", sk.x(), sk.y()).ToStdString();
dl->AddText({pos.x+4, end.y-16}, IM_COL32(160,160,180,200), txt.c_str());
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left))
handle_canvas_click({(m.x-cx)/m_canvas_scale, -(m.y-cy)/m_canvas_scale});
if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
auto& ap2 = active_profile();
if (ap2.points.size() >= 3) {
ap2.points.push_back(ap2.points.front());
ap2.closed = true;
m_active_profile = -1;
}
}
}
}
TopoDS_Shape GLGizmoSketch::build_combined_shape()
{
if (m_profiles.empty() || !m_profiles[0].closed)
throw std::runtime_error("No outer profile");
TopoDS_Wire outer_wire = m_profiles[0].to_occt_wire(m_plane);
BRepBuilderAPI_MakeFace face_maker(outer_wire);
if (!face_maker.IsDone()) throw std::runtime_error("Failed to make outer face");
for (size_t i = 1; i < m_profiles.size(); ++i) {
if (!m_profiles[i].closed) continue;
TopoDS_Wire inner = m_profiles[i].to_occt_wire(m_plane);
face_maker.Add(inner);
}
face_maker.Build();
if (!face_maker.IsDone()) throw std::runtime_error("Failed to build face with holes");
TopoDS_Face face = face_maker.Face();
TopoDS_Shape shape;
if (m_sp.revolve_deg < 360.0 && m_sp.revolve_deg > 0.0) {
gp_Pnt o(m_plane.origin.x(), m_plane.origin.y(), m_plane.origin.z());
gp_Dir xd(m_plane.x_axis.x(), m_plane.x_axis.y(), m_plane.x_axis.z());
gp_Ax1 axis(o, xd);
BRepPrimAPI_MakeRevol rev(face, axis, m_sp.revolve_deg * M_PI / 180.0);
if (!rev.IsDone()) throw std::runtime_error("Revolve failed");
shape = rev.Shape();
} else {
shape = SketchEngine::make_extrude_face(face, m_plane, m_sp.extrude_len, m_sp.extrude_sym);
}
if (m_sp.dressup_enabled) {
if (m_sp.dressup_type == DressUpType::Fillet)
shape = GeometryEngine::apply_fillet(shape, m_sp.dressup_radius, m_sp.dressup_faces);
else
shape = GeometryEngine::apply_chamfer(shape, m_sp.dressup_chamfer_dist, m_sp.dressup_faces);
}
return shape;
}
void GLGizmoSketch::on_render_input_window(float x, float y, float bottom_limit)
{
y = std::min(y, bottom_limit - ImGui::GetWindowHeight());
const float scale = m_parent.get_scale();
ImGuiWrapper::push_toolbar_style(scale);
GizmoImguiSetNextWIndowPos(x, y, ImGuiCond_Always, 0.0f, 0.0f);
GizmoImguiBegin("Sketch", ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove
| ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse
| ImGuiWindowFlags_NoTitleBar);
if (ImGui::CollapsingHeader(UL("Profile"), ImGuiTreeNodeFlags_DefaultOpen)) {
static const char* names[] = {"Line", "Rectangle", "Circle", "Polygon"};
int cur = (int)m_tool;
if (ImGui::Combo("##shape", &cur, names, (int)SketchTool::COUNT)) {
m_tool = (SketchTool)cur;
if (m_tool != SketchTool::Line) build_preset_profile();
}
ImGui::SameLine();
if (m_imgui->button("+##newprofile")) m_active_profile = -1;
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", UL("Start new profile (for holes)"));
if (m_tool == SketchTool::Rectangle) {
ImGui::SetNextItemWidth(80); if (ImGui::InputDouble("W", &m_rect_w,1,10,"%.0f")) build_preset_profile();
ImGui::SameLine();
ImGui::SetNextItemWidth(80); if (ImGui::InputDouble("H", &m_rect_h,1,10,"%.0f")) build_preset_profile();
} else if (m_tool == SketchTool::Circle) {
ImGui::SetNextItemWidth(80); if (ImGui::InputDouble("R", &m_circle_r,1,5,"%.0f")) build_preset_profile();
ImGui::SameLine();
ImGui::SetNextItemWidth(80); if (ImGui::SliderInt("Seg", &m_circle_seg,8,64)) build_preset_profile();
} else if (m_tool == SketchTool::Polygon) {
ImGui::SetNextItemWidth(80); if (ImGui::SliderInt("Sides", &m_poly_sides,3,12)) build_preset_profile();
ImGui::SameLine();
ImGui::SetNextItemWidth(80); if (ImGui::InputDouble("R", &m_poly_r,1,5,"%.0f")) build_preset_profile();
} else {
ImGui::Text("%s", UL("Click on canvas to draw"));
}
ImGui::Checkbox(UL("Snap to grid"), &m_snap_grid);
ImGui::SameLine();
ImGui::SetNextItemWidth(80); ImGui::InputFloat("Step", &m_grid_step, 1, 5, "%.0f mm");
draw_canvas();
if (!m_profiles.empty()) {
ImGui::Text("%s: %zu", UL("Profiles"), m_profiles.size());
for (int i = 0; i < (int)m_profiles.size(); ++i) {
auto& p = m_profiles[i];
ImGui::PushID(i);
bool outer = (i == 0);
ImVec4 col = outer ? ImVec4(0,1,0,1) : ImVec4(1,0.3f,0.3f,1);
const char* label = outer ? "Outer" : "Hole";
ImGui::TextColored(col, "%s %d: %zu pts %s", label, i+1, p.points.size(), p.closed ? "CLOSED" : "");
ImGui::SameLine();
if (ImGui::SmallButton("X")) delete_profile(i);
ImGui::PopID();
}
}
}
ImGui::Separator();
bool is_revolve = false;
bool has_sel = false;
if (ImGui::CollapsingHeader(UL("Operation"), ImGuiTreeNodeFlags_DefaultOpen)) {
static int pi = 0;
if (ImGui::Combo(UL("Plane"), &pi, "XY (Top)\0XZ (Front)\0YZ (Side)\0"))
m_plane = (pi==0) ? SketchPlane::XY() : (pi==1) ? SketchPlane::XZ() : SketchPlane::YZ();
is_revolve = (m_sp.revolve_deg > 0 && m_sp.revolve_deg < 360);
ImGui::SetNextItemWidth(100);
if (ImGui::InputDouble(UL("Revolve deg"), &m_sp.revolve_deg, 15, 90, "%.0f")) {
if (m_sp.revolve_deg > 360) m_sp.revolve_deg = 360;
if (m_sp.revolve_deg < 0) m_sp.revolve_deg = 0;
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", UL("Set to 0 for extrude, >0 for revolve"));
if (!is_revolve) {
ImGui::SetNextItemWidth(100);
ImGui::InputDouble(UL("Length"), &m_sp.extrude_len, 0.5, 5, "%.1f mm");
ImGui::SameLine();
ImGui::Checkbox(UL("Symmetric"), &m_sp.extrude_sym);
}
has_sel = !m_parent.get_selection().is_empty();
if (has_sel) {
if (ImGui::Checkbox(UL("Pocket (cut)"), &m_sp.is_pocket))
if (m_sp.is_pocket) m_sp.dressup_enabled = false;
} else m_sp.is_pocket = false;
}
ImGui::Separator();
if (!m_sp.is_pocket && ImGui::CollapsingHeader(UL("Fillet / Chamfer"))) {
ImGui::Checkbox(UL("Enable"), &m_sp.dressup_enabled);
if (m_sp.dressup_enabled) {
static const char* dn[] = {"Fillet", "Chamfer"};
int du = (int)m_sp.dressup_type;
ImGui::SetNextItemWidth(100);
if (ImGui::Combo("##dtype", &du, dn, 2)) m_sp.dressup_type = (DressUpType)du;
static const char* fn[] = {"All edges", "Top edges", "Bottom edges", "Lateral edges"};
int fg = (int)m_sp.dressup_faces;
ImGui::SetNextItemWidth(140);
ImGui::Combo(UL("Edges"), &fg, fn, 4); m_sp.dressup_faces = (FaceGroup)fg;
ImGui::SetNextItemWidth(100);
if (m_sp.dressup_type == DressUpType::Fillet)
ImGui::InputDouble(UL("Radius"), &m_sp.dressup_radius, 0.1, 1, "%.1f mm");
else
ImGui::InputDouble(UL("Distance"), &m_sp.dressup_chamfer_dist, 0.1, 1, "%.1f mm");
}
}
ImGui::Separator();
bool ok = has_closed_profile();
if (ok) ImGui::TextColored({0,1,0,1}, "%zu %s", m_profiles.size(), UL("closed profile(s)"));
else ImGui::TextColored({0.6f,0.6f,0.6f,1}, "%s", UL("Draw a closed profile to enable"));
auto btn = [&](const char* label, bool enabled) {
if (!enabled) { ImGui::PushItemFlag(ImGuiItemFlags_Disabled,true); ImGui::PushStyleColor(ImGuiCol_Button,{0.25f,0.25f,0.25f,1}); }
bool clicked = ImGui::Button(label, {-1,0});
if (!enabled) { ImGui::PopStyleColor(); ImGui::PopItemFlag(); }
return clicked && enabled;
};
if (m_sp.is_pocket && has_sel) {
if (btn(L("Pocket (Cut)"), ok)) apply_pocket();
} else if (is_revolve) {
if (btn(L("Revolve"), ok)) apply_revolve();
} else {
if (btn(L("Extrude"), ok)) apply_extrude();
}
if (ImGui::Button(L("Clear All"), {-1,0})) clear_all();
if (ImGui::Button(L("Close"), {-1,0})) m_parent.reset_all_gizmos();
GizmoImguiEnd();
ImGuiWrapper::pop_toolbar_style();
}
void GLGizmoSketch::apply_extrude()
{
try {
TopoDS_Shape shape = build_combined_shape();
TriangleMesh mesh = SketchEngine::tessellate(shape, m_sp.linear_deflection);
if (mesh.its.indices.empty()) throw std::runtime_error("Empty result");
wxGetApp().plater()->take_snapshot("Sketch Extrude");
ModelObject* mo = wxGetApp().model().add_object();
mo->name = "Extrusion";
mo->add_volume(std::move(mesh))->set_new_unique_id();
mo->ensure_on_bed();
wxGetApp().plater()->update();
clear_all();
} catch (const std::exception& e) {
wxGetApp().notification_manager()->push_notification(NotificationType::CustomNotification, NotificationManager::NotificationLevel::ErrorNotificationLevel, std::string("Extrude: ")+e.what());
}
}
void GLGizmoSketch::apply_revolve()
{
try {
TopoDS_Shape shape = build_combined_shape();
TriangleMesh mesh = SketchEngine::tessellate(shape, m_sp.linear_deflection);
if (mesh.its.indices.empty()) throw std::runtime_error("Empty result");
wxGetApp().plater()->take_snapshot("Sketch Revolve");
ModelObject* mo = wxGetApp().model().add_object();
mo->name = "Revolve";
mo->add_volume(std::move(mesh))->set_new_unique_id();
mo->ensure_on_bed();
wxGetApp().plater()->update();
clear_all();
} catch (const std::exception& e) {
wxGetApp().notification_manager()->push_notification(NotificationType::CustomNotification, NotificationManager::NotificationLevel::ErrorNotificationLevel, std::string("Revolve: ")+e.what());
}
}
void GLGizmoSketch::apply_pocket()
{
try {
Selection& sel = m_parent.get_selection();
int obj_idx = sel.get_object_idx();
if (obj_idx < 0) throw std::runtime_error("No object selected");
ModelObject* mo = wxGetApp().model().objects[obj_idx];
TopoDS_Wire outer = m_profiles[0].to_occt_wire(m_plane);
BRepBuilderAPI_MakeFace fm(outer);
if (!fm.IsDone()) throw std::runtime_error("Face failed");
for (size_t i = 1; i < m_profiles.size(); ++i)
if (m_profiles[i].closed) fm.Add(m_profiles[i].to_occt_wire(m_plane));
fm.Build();
if (!fm.IsDone()) throw std::runtime_error("Face with holes failed");
TopoDS_Shape tool = SketchEngine::make_extrude_face(fm.Face(), m_plane, m_sp.extrude_len + 5.0, false);
TriangleMesh tool_mesh = SketchEngine::tessellate(tool, m_sp.linear_deflection);
if (tool_mesh.its.indices.empty()) throw std::runtime_error("Tool mesh empty");
wxGetApp().plater()->take_snapshot("Sketch Pocket");
mo->add_volume(std::move(tool_mesh), ModelVolumeType::NEGATIVE_VOLUME)->set_new_unique_id();
mo->ensure_on_bed();
wxGetApp().plater()->update();
clear_all();
wxGetApp().notification_manager()->push_notification(NotificationType::CustomNotification, NotificationManager::NotificationLevel::RegularNotificationLevel, UL("Pocket added (negative volume)"));
} catch (const std::exception& e) {
wxGetApp().notification_manager()->push_notification(NotificationType::CustomNotification, NotificationManager::NotificationLevel::ErrorNotificationLevel, std::string("Pocket: ")+e.what());
}
}
} // namespace GUI
} // namespace Slic3r
+74
View File
@@ -0,0 +1,74 @@
#ifndef slic3r_GLGizmoSketch_hpp_
#define slic3r_GLGizmoSketch_hpp_
#include "GLGizmoBase.hpp"
#include "GLGizmosCommon.hpp"
#include "libslic3r/SketchEngine.hpp"
#include <imgui/imgui.h>
namespace Slic3r {
namespace GUI {
enum class SketchTool { Line, Rectangle, Circle, Polygon, COUNT };
class GLGizmoSketch : public GLGizmoBase
{
public:
GLGizmoSketch(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
bool on_mouse(const wxMouseEvent& mouse_event) override;
protected:
bool on_init() override;
std::string on_get_name() const override;
bool on_is_activable() const override;
void on_render() override;
void on_set_state() override;
CommonGizmosDataID on_get_requirements() const override;
void on_render_input_window(float x, float y, float bottom_limit) override;
void on_load(cereal::BinaryInputArchive& ar) override;
void on_save(cereal::BinaryOutputArchive& ar) const override;
private:
SketchTool m_tool{SketchTool::Line};
std::vector<SketchProfile> m_profiles; // multiple profiles (outer + holes)
SketchPlane m_plane{SketchPlane::XY()};
SketchParams m_sp;
// Shape presets
double m_rect_w{20}, m_rect_h{15};
double m_circle_r{10}; int m_circle_seg{32};
int m_poly_sides{6}; double m_poly_r{10};
// Canvas
std::vector<ImVec2> m_canvas_points;
Vec2d m_canvas_center{0,0};
float m_canvas_scale{5.0f};
bool m_snap_grid{true};
float m_grid_step{5.0f};
// Current profile being drawn
int m_active_profile{-1};
SketchProfile& active_profile();
bool has_closed_profile() const;
void build_preset_profile();
void add_closed_profile();
void delete_profile(int idx);
void clear_all();
TopoDS_Shape build_combined_shape(); // all profiles as face with holes
void apply_extrude();
void apply_revolve();
void apply_pocket();
void draw_canvas();
void handle_canvas_click(ImVec2 pos);
Vec2d snap(Vec2d pt) const;
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmoSketch_hpp_
+10
View File
@@ -27,6 +27,8 @@
#include "slic3r/GUI/Gizmos/GLGizmoSVG.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoAssembly.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoPrimitive.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoSketch.hpp"
#include "libslic3r/format.hpp"
#include "libslic3r/Model.hpp"
@@ -176,6 +178,12 @@ void GLGizmosManager::switch_gizmos_icon_filename()
case (EType::BrimEars):
gizmo->set_icon_filename(m_is_dark ? "toolbar_brimears_dark.svg" : "toolbar_brimears.svg");
break;
case (EType::Primitive):
gizmo->set_icon_filename(m_is_dark ? "toolbar_modifier_cube_dark.svg" : "toolbar_modifier_cube.svg");
break;
case (EType::Sketch):
gizmo->set_icon_filename(m_is_dark ? "toolbar_sketch_dark.svg" : "toolbar_sketch.svg");
break;
}
}
@@ -219,6 +227,8 @@ bool GLGizmosManager::init()
m_gizmos.emplace_back(new GLGizmoAssembly(m_parent, m_is_dark ? "toolbar_assembly_dark.svg" : "toolbar_assembly.svg", EType::Assembly));
m_gizmos.emplace_back(new GLGizmoSimplify(m_parent, "reduce_triangles.svg", EType::Simplify));
m_gizmos.emplace_back(new GLGizmoBrimEars(m_parent, m_is_dark ? "toolbar_brimears_dark.svg" : "toolbar_brimears.svg", EType::BrimEars));
m_gizmos.emplace_back(new GLGizmoPrimitive(m_parent, m_is_dark ? "toolbar_modifier_cube_dark.svg" : "toolbar_modifier_cube.svg", static_cast<unsigned int>(Primitive)));
m_gizmos.emplace_back(new GLGizmoSketch(m_parent, m_is_dark ? "toolbar_sketch_dark.svg" : "toolbar_sketch.svg", static_cast<unsigned int>(Sketch)));
//m_gizmos.emplace_back(new GLGizmoSlaSupports(m_parent, "sla_supports.svg", sprite_id++));
//m_gizmos.emplace_back(new GLGizmoFaceDetector(m_parent, "face recognition.svg", sprite_id++));
//m_gizmos.emplace_back(new GLGizmoHollow(m_parent, "hollow.svg", sprite_id++));
@@ -90,6 +90,8 @@ public:
Assembly,
Simplify,
BrimEars,
Primitive,
Sketch,
//SlaSupports,
// BBS
//FaceRecognition,
+13 -2
View File
@@ -37,6 +37,7 @@
#include "I18N.hpp"
#include "GLCanvas3D.hpp"
#include "Plater.hpp"
#include "DesignPanel.hpp"
#include "WebViewDialog.hpp"
#include "../Utils/Process.hpp"
#include "format.hpp"
@@ -1014,6 +1015,8 @@ void MainFrame::update_layout()
{
case ESettingsLayout::Old:
{
m_design_panel->Reparent(m_tabpanel);
m_tabpanel->InsertPage(tpDesign, m_design_panel, _L("Design"), std::string("tab_design_active"), std::string("tab_design_active"), false);
m_plater->Reparent(m_tabpanel);
m_tabpanel->InsertPage(tp3DEditor, m_plater, _L("Prepare"), std::string("tab_3d_active"), std::string("tab_3d_active"), false);
m_tabpanel->InsertPage(tpPreview, m_plater, _L("Preview"), std::string("tab_preview_active"), std::string("tab_preview_active"), false);
@@ -1270,6 +1273,12 @@ void MainFrame::init_tabpanel() {
}
//else if (panel == m_param_panel)
// m_param_panel->OnActivate();
else if (panel == m_design_panel) {
// Re-sync the Design bed to the active printer: the panel is built before the
// printer profile is fully applied, so its bed must refresh on activation or the
// grid (true bed) spills past the stale default bed quad.
m_design_panel->on_tab_shown();
}
else if (panel == m_monitor) {
//monitor
}
@@ -1316,11 +1325,13 @@ void MainFrame::init_tabpanel() {
}
m_plater = new Plater(this, this);
// Register the plater with the app BEFORE constructing DesignPanel: its
// DesignCanvas reads wxGetApp().plater()->config() at construction time.
wxGetApp().plater_ = m_plater;
m_design_panel = new DesignPanel(this);
m_plater->SetBackgroundColour(*wxWHITE);
m_plater->Hide();
wxGetApp().plater_ = m_plater;
create_preset_tabs();
//BBS add pages
+11 -8
View File
@@ -50,6 +50,7 @@ namespace GUI
class Tab;
class PrintHostQueueDialog;
class Plater;
class DesignPanel;
class MainFrame;
class ParamsDialog;
#ifdef __WXGTK__
@@ -218,14 +219,15 @@ public:
enum TabPosition
{
tpHome = 0,
tp3DEditor = 1,
tpPreview = 2,
tpMonitor = 3,
tpMultiDevice = 4,
tpProject = 5,
tpCalibration = 6,
tpAuxiliary = 7,
toDebugTool = 8,
tpDesign = 1,
tp3DEditor = 2,
tpPreview = 3,
tpMonitor = 4,
tpMultiDevice = 5,
tpProject = 6,
tpCalibration = 7,
tpAuxiliary = 8,
toDebugTool = 9,
};
//BBS: add slice&&print status update logic
@@ -375,6 +377,7 @@ public:
BBLTopbar* m_topbar{ nullptr };
PrintHostQueueDialog* printhost_queue_dlg() { return m_printhost_queue_dlg; }
Plater* m_plater { nullptr };
DesignPanel* m_design_panel { nullptr };
//BBS: GUI refactor
MonitorPanel* m_monitor{ nullptr };
+8 -4
View File
@@ -3384,16 +3384,20 @@ void PartPlate::render(const Transform3d& view_matrix, const Transform3d& projec
if (wxGetApp().show_plate_gridlines() && show_grid)
render_grid(bottom);
if (!bottom && m_selected && !force_background_color) {
const bool hide_chrome = m_partplate_list && m_partplate_list->get_hide_chrome();
if (!hide_chrome && !bottom && m_selected && !force_background_color) {
if (m_partplate_list)
render_logo(bottom, m_partplate_list->render_cali_logo && render_cali);
else
render_logo(bottom);
}
render_icons(bottom, only_body, hover_id);
if (!force_background_color) {
render_only_numbers(bottom);
if (!hide_chrome) {
render_icons(bottom, only_body, hover_id);
if (!force_background_color) {
render_only_numbers(bottom);
}
}
glsafe(::glDisable(GL_DEPTH_TEST));
+7
View File
@@ -614,6 +614,11 @@ class PartPlateList : public ObjectBase
bool render_bedtype_logo = true;
bool render_plate_settings = true;
bool render_cali_logo = true;
// SnapOrca Design: when true, PartPlate::render skips all overlay chrome
// (corner icons, logo watermark, plate numbers) but keeps the bed grid.
// Toggled transiently per-frame by GLCanvas3D::_render_platelist for the
// DesignCanvas; stays false for the main editor.
bool m_hide_chrome = false;
bool m_is_dark = false;
@@ -838,6 +843,8 @@ public:
void render(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current = false, bool only_body = false, int hover_id = -1, bool render_cali = false, bool show_grid = true);
void set_render_option(bool bedtype_texture, bool plate_settings);
void set_render_cali(bool value = true) { render_cali_logo = value; }
void set_hide_chrome(bool value) { m_hide_chrome = value; }
bool get_hide_chrome() const { return m_hide_chrome; }
void register_raycasters_for_picking(GLCanvas3D& canvas)
{
for (auto plate : m_plate_list)
+5
View File
@@ -17477,6 +17477,11 @@ PartPlateList& Plater::get_partplate_list()
return p->partplate_list;
}
BackgroundSlicingProcess* Plater::get_background_process()
{
return &p->background_process;
}
void Plater::apply_background_progress()
{
PartPlate* part_plate = p->partplate_list.get_curr_plate();
+5
View File
@@ -46,6 +46,7 @@ class Model;
class ModelObject;
class ModelInstance;
class Print;
class BackgroundSlicingProcess;
class SLAPrint;
//BBS: add partplatelist and SlicingStatusEvent
class PartPlateList;
@@ -706,6 +707,10 @@ public:
//BBS: partplate list related functions
PartPlateList& get_partplate_list();
// Shared background slicing process (same instance View3D/Preview/AssembleView
// use). Exposed so the Design tab's native GLCanvas3D can be wired exactly like
// the editor canvases (GLCanvas3D::render() dereferences the process).
BackgroundSlicingProcess* get_background_process();
void validate_current_plate(bool& model_fits, bool& validate_error);
//BBS: select the plate by index
int select_plate(int plate_index, bool need_slice = false);
+133
View File
@@ -0,0 +1,133 @@
#include "SketchInlineEditor.hpp"
#include <wx/frame.h>
#include <wx/textctrl.h>
#include <wx/sizer.h>
#include <wx/window.h>
#include <wx/toplevel.h>
#include <wx/gdicmn.h>
#include <algorithm>
#include <cstdio>
namespace Slic3r {
namespace GUI {
namespace {
// Locale-safe value <-> text (wx sets LC_NUMERIC to the user locale, so snprintf may
// emit a comma; parsing accepts either separator). Mirrors DesignPanel's en_*.
wxString en_format(double v, int digits = 2)
{
char fmt[16];
std::snprintf(fmt, sizeof(fmt), "%%.%df", digits);
char buf[64];
std::snprintf(buf, sizeof(buf), fmt, v);
for (char* c = buf; *c; ++c) if (*c == ',') *c = '.';
return wxString::FromUTF8(buf);
}
bool en_parse(const wxString& text, double& out)
{
wxString t(text);
t.Replace(wxT(","), wxT("."));
return t.ToCDouble(&out);
}
} // namespace
SketchInlineEditor::SketchInlineEditor(wxWindow* parent_canvas)
{
wxWindow* top = parent_canvas ? wxGetTopLevelParent(parent_canvas) : nullptr;
// Borderless floating frame: a top-level window so the WM composites it above the
// GL canvas (a child widget would be hidden by the GL surface). Floats on its
// parent and stays on top so it tracks the main window.
// NB: no wxFRAME_FLOAT_ON_PARENT — that maps to a GTK _UTILITY_ window-type hint, which
// many WMs (incl. the xrdp/x11vnc session on :10) refuse to give keyboard focus, so the
// field opened un-focusable and needed a click before typing. Plain stay-on-top frame is
// WM-focusable; we present + SetFocus it explicitly in open().
m_frame = new wxFrame(top, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize,
wxFRAME_NO_TASKBAR | wxBORDER_NONE | wxSTAY_ON_TOP);
m_ctrl = new wxTextCtrl(m_frame, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(82, -1),
wxTE_PROCESS_ENTER | wxTE_RIGHT | wxBORDER_SIMPLE);
auto* sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(m_ctrl, 1, wxEXPAND);
m_frame->SetSizerAndFit(sizer);
m_frame->Hide();
m_ctrl->Bind(wxEVT_TEXT_ENTER, [this](wxCommandEvent&) { do_commit(); });
m_ctrl->Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& e) {
if (e.GetKeyCode() == WXK_ESCAPE) do_cancel();
else e.Skip();
});
}
void SketchInlineEditor::open(const wxPoint& screen_px, double value,
std::function<void(double)> on_commit,
std::function<void()> on_cancel)
{
if (m_frame == nullptr || m_ctrl == nullptr) { if (on_cancel) on_cancel(); return; }
if (m_open) close();
m_commit = std::move(on_commit);
m_cancel = std::move(on_cancel);
m_ctrl->ChangeValue(en_format(value));
m_frame->Fit();
const wxSize sz = m_frame->GetSize();
wxPoint pos(screen_px.x - sz.GetWidth() / 2, screen_px.y - sz.GetHeight() / 2);
// Keep the frame fully on-screen: an anchor that maps off the display makes GTK drop
// the window at a default corner (top-left) instead of the requested point.
const wxRect area = wxGetClientDisplayRect();
pos.x = std::max(area.GetLeft(), std::min(pos.x, area.GetRight() - sz.GetWidth()));
pos.y = std::max(area.GetTop(), std::min(pos.y, area.GetBottom() - sz.GetHeight()));
// Show() BEFORE Move(): GTK ignores a Move() issued before the window is mapped (the
// WM places it at its default, i.e. the top-left corner). Move after Show sticks.
m_frame->Show();
m_frame->Move(pos);
m_frame->Raise(); // gtk_window_present -> activate the top-level so SetFocus routes
m_frame->SetFocus();
m_ctrl->SetFocus();
m_ctrl->SelectAll();
m_open = true;
// Re-assert on the next tick too: the GL canvas can reclaim focus while it finishes
// handling the click/render that opened us, so a single immediate SetFocus may be stolen.
m_ctrl->CallAfter([this] {
if (m_open && m_ctrl) { m_frame->Raise(); m_ctrl->SetFocus(); m_ctrl->SelectAll(); }
});
}
void SketchInlineEditor::do_commit()
{
if (!m_open || m_ctrl == nullptr) return;
double v = 0.0;
if (!en_parse(m_ctrl->GetValue(), v)) { // invalid: keep editing
m_ctrl->SetFocus();
m_ctrl->SelectAll();
return;
}
auto cb = m_commit; // copy-then-close: the callback re-enters (re-solve + render)
close();
if (cb) cb(v);
}
void SketchInlineEditor::cancel()
{
if (m_open) do_cancel();
}
void SketchInlineEditor::do_cancel()
{
if (!m_open) return;
auto cb = m_cancel;
close();
if (cb) cb();
}
void SketchInlineEditor::close()
{
if (m_frame == nullptr || !m_open) return;
m_closing = true;
m_open = false;
m_frame->Hide();
m_commit = nullptr;
m_cancel = nullptr;
m_closing = false;
}
}} // namespace Slic3r::GUI
+48
View File
@@ -0,0 +1,48 @@
#ifndef slic3r_SketchInlineEditor_hpp_
#define slic3r_SketchInlineEditor_hpp_
#include <functional>
class wxWindow;
class wxFrame;
class wxTextCtrl;
class wxPoint;
namespace Slic3r {
namespace GUI {
// Onshape-style in-canvas value editor: a small borderless floating frame holding a
// wxTextCtrl, shown at screen coordinates over the GL canvas. A top-level frame is
// used (not a child widget) because a native child cannot be composited over the
// double-buffered wxGLCanvas under GTK3/llvmpipe — it stays invisible. Enter (or blur)
// commits the parsed number, Esc cancels. This is the single numeric-entry path for
// sketch dimensions, replacing the docked/modal value cards.
class SketchInlineEditor
{
public:
explicit SketchInlineEditor(wxWindow* parent_canvas);
// Show the editor centred on `screen_px` (absolute screen coords), pre-filled with
// `value`. on_commit(parsed) fires on Enter with a valid number; on_cancel() on Esc.
void open(const wxPoint& screen_px, double value,
std::function<void(double)> on_commit,
std::function<void()> on_cancel);
void close();
void cancel(); // if open, run the registered cancel (keep-as-drawn)
bool is_open() const { return m_open; }
private:
void do_commit();
void do_cancel();
wxFrame* m_frame{nullptr};
wxTextCtrl* m_ctrl{nullptr};
std::function<void(double)> m_commit;
std::function<void()> m_cancel;
bool m_open{false};
bool m_closing{false};
};
}} // namespace Slic3r::GUI
#endif // slic3r_SketchInlineEditor_hpp_