mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-29 11:51:24 +00:00
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:
co-authored by
Claude Opus 4.8
parent
449a4cf9fc
commit
0f4060c0a9
@@ -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
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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>.
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 ½
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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(¶ms);
|
||||
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" */
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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(¶m, &(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(¶m, &(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(¶m);
|
||||
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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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_
|
||||
@@ -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) {
|
||||
|
||||
@@ -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); }
|
||||
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 };
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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_
|
||||
Reference in New Issue
Block a user