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

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

Structural integration complete; build verification pending.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
This commit is contained in:
Tommaso Bianchi
2026-06-28 12:40:38 +02:00
co-authored by Claude Opus 4.8
parent 449a4cf9fc
commit 0f4060c0a9
146 changed files with 34845 additions and 17 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ orcaslicer_add_cmake_project(OCCT
#-DBUILD_MODULE_DataExchange=OFF
-DBUILD_MODULE_Draw=OFF
-DBUILD_MODULE_FoundationClasses=OFF
-DBUILD_MODULE_ModelingAlgorithms=OFF
-DBUILD_MODULE_ModelingAlgorithms=ON
-DBUILD_MODULE_ModelingData=OFF
-DBUILD_MODULE_Visualization=OFF
)
+58
View File
@@ -0,0 +1,58 @@
# Design (CAD) tab — upstream portability assessment
**Question:** can the SnapOrca *Design tab* (sketch-first parametric CAD: sketch →
constrain → extrude/revolve/fillet/hole/thread/shell, multi-body, undo) be contributed
to **mainline OrcaSlicer** (2.4 dev) rather than living only in this Snapmaker fork?
**Short answer: yes, technically clean — the feature is self-contained and vendor-neutral.
The single real gatekeeper is whether upstream is willing to take on OpenCASCADE (OCCT) as a
build dependency, i.e. whether OrcaSlicer wants to become a CAD-integrated slicer.**
## Why it is portable
- **Self-contained.** The feature is ~28 kLOC of *new* files (kernel
`CadDocument` / `SketchEngine` / `GeometryEngine` / `Sketch*`; GUI
`DesignPanel` / `DesignCanvas` / `DesignSketchTool` / `GLGizmoSketch`) plus a
vendored, self-contained SolveSpace solver (`src/libslic3r/slvs/`, ~10 kLOC, LGPL).
- **Tiny, guarded injection into shared code (~23 % surface).** The only edits to
pre-existing OrcaSlicer files are: a `m_design_sketch_tool` member + a handful of
null-checked hooks in `GLCanvas3D` (render overlay, mouse routing, Delete/Esc/Ctrl+Z),
a tab member + construction in `MainFrame`, and a few forward declarations. **No
changes** to the slicing pipeline (Print/PrintObject/Layer/GCode), Plater, Tab, Model,
or the printer-profile/config system.
- **Zero Snapmaker coupling.** No "Snapmaker" references in any Design/CAD file; icons
are generic `resources/images/design_*.svg`; the code is pure wxWidgets/OpenGL/OCCT.
It would build and run in any OrcaSlicer fork unchanged.
- **Orthogonal git history.** The feature lives on `feature/cad-primitives` as a clean,
linear series of `Design:` / `C*` / `M*` commits with no interleaved fork-specific
work, so it cherry-picks onto a clean OrcaSlicer base without slicing-logic conflicts.
## The real blocker: OCCT
`src/libslic3r/CMakeLists.txt` links ~30 OCCT toolkits (`TKBRep TKFillet TKOffset
TKBool TKPrim TKTopAlgo TKMath TKernel …`) via `find_package(OpenCASCADE REQUIRED)`.
OCCT is large (hundreds of MB of binaries, +1530 min to a clean deps build) and is a
dependency **mainline OrcaSlicer has never carried**. Accepting it is an architectural
decision about the project's scope, not a code problem.
## Top work items to upstream (≈35 dev-days)
1. **Make OCCT an *optional* dependency.** Add it to `deps/` like the other externals,
gate the whole feature behind a CMake option (`-DENABLE_DESIGN_CAD=OFF` by default),
and document OCCT install per-platform. Builds without OCCT simply omit the tab.
2. **Guard the tab construction** in `MainFrame` (and the `GLCanvas3D` hooks) on that
same flag so a minimal build links and runs with no Design code at all.
3. **Tests + docs + license hygiene.** Add regression coverage for the kernel
(sketch-solve, extrude, fillet, undo), ship the LGPL notice for the vendored
`slvs/`, and add user docs. (libslvs itself is self-contained, no external deps.)
## Verdict
Portability is **high (≈7/10): "needs moderate adaptation," not "deeply entangled."**
The engineering to upstream is modest and mechanical (optional-dependency plumbing +
tests/docs). The decision is strategic: **does OrcaSlicer want OCCT and a CAD tab?** If
yes, this feature is a near-drop-in starting point.
---
*Generated 2026-06-21 from a read-only analysis of the `feature/cad-primitives` branch.
Tracking issue: bd `snaporca-frp`.*
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M4 17 A11 11 0 0 1 20 17"/><circle cx="4" cy="17" r="1.5" fill="#b6b6b6" stroke="none"/><circle cx="12" cy="7" r="1.5" fill="#b6b6b6" stroke="none"/><circle cx="20" cy="17" r="1.5" fill="#b6b6b6" stroke="none"/></svg>

After

Width:  |  Height:  |  Size: 406 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M5 18 A13 13 0 0 1 18 5"/><path d="M5 5 5 18M5 5 18 5" stroke-dasharray="2.5 2.5" opacity="0.5"/><circle cx="5" cy="5" r="2" fill="#b6b6b6" stroke="none"/><circle cx="5" cy="18" r="1.5" fill="#b6b6b6" stroke="none"/><circle cx="18" cy="5" r="1.5" fill="#b6b6b6" stroke="none"/></svg>

After

Width:  |  Height:  |  Size: 472 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="6" height="6"/><rect x="15" y="3" width="6" height="6"/><rect x="3" y="15" width="6" height="6"/><rect x="15" y="15" width="6" height="6"/></svg>

After

Width:  |  Height:  |  Size: 350 B

+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="9" cy="12" r="6"/>
<circle cx="15" cy="12" r="6"/>
</svg>

After

Width:  |  Height:  |  Size: 253 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M3 19 C 6 5, 12 5, 12 12 S 18 19, 21 5"/><circle cx="3" cy="19" r="1.5" fill="#b6b6b6" stroke="none"/><circle cx="12" cy="12" r="1.5" fill="#b6b6b6" stroke="none"/><circle cx="21" cy="5" r="1.5" fill="#b6b6b6" stroke="none"/></svg>

After

Width:  |  Height:  |  Size: 420 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M4 20h16M4 20 16 6"/><path d="M11 20 A7 7 0 0 0 9.1 15.2"/></svg>

After

Width:  |  Height:  |  Size: 254 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2.2" fill="#b6b6b6" stroke="none"/></svg>

After

Width:  |  Height:  |  Size: 279 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="8"/><circle cx="12" cy="12" r="3.5"/><circle cx="12" cy="12" r="1.3" fill="#b6b6b6" stroke="none"/></svg>

After

Width:  |  Height:  |  Size: 312 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="7"/><path d="M6.3 17.7 17.7 6.3"/></svg>

After

Width:  |  Height:  |  Size: 247 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M5 9h14M5 15h14"/></svg>

After

Width:  |  Height:  |  Size: 213 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5.5" r="2"/><path d="M12 7.5V20M8.5 10h7M6 14a6 6 0 0 0 12 0"/></svg>

After

Width:  |  Height:  |  Size: 269 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12h18M3 9v6M21 9v6"/></svg>

After

Width:  |  Height:  |  Size: 219 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12h16M4 9v6M20 9v6"/><circle cx="12" cy="12" r="2" fill="#b6b6b6" stroke="none"/></svg>

After

Width:  |  Height:  |  Size: 279 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M7 20 13 4M13 20 19 4"/></svg>

After

Width:  |  Height:  |  Size: 219 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3v15h15M6 13h5v5"/></svg>

After

Width:  |  Height:  |  Size: 217 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="8"/><path d="M12 12h8M17.5 9.5 20 12 17.5 14.5"/><circle cx="12" cy="12" r="1.5" fill="#b6b6b6" stroke="none"/></svg>

After

Width:  |  Height:  |  Size: 324 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v18" stroke-dasharray="2.5 2.5" opacity="0.5"/><path d="M8 8 4 12 8 16M16 8l4 4-4 4"/></svg>

After

Width:  |  Height:  |  Size: 286 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="14.5" r="5"/><path d="M3 9.5h18"/></svg>

After

Width:  |  Height:  |  Size: 240 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v18M9 3h6M9 21h6"/></svg>

After

Width:  |  Height:  |  Size: 219 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5v8l6 6h8"/></svg>

After

Width:  |  Height:  |  Size: 210 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12l4 4L19 6"/></svg>

After

Width:  |  Height:  |  Size: 212 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="8"/><circle cx="12" cy="12" r="1.5" fill="#b6b6b6" stroke="none"/></svg>

After

Width:  |  Height:  |  Size: 279 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="7.5"/><path d="M6.7 6.7 17.3 17.3" stroke-dasharray="2.5 2.5" opacity="0.5"/><circle cx="6.7" cy="6.7" r="1.5" fill="#b6b6b6" stroke="none"/><circle cx="17.3" cy="17.3" r="1.5" fill="#b6b6b6" stroke="none"/></svg>

After

Width:  |  Height:  |  Size: 420 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="8"/><circle cx="12" cy="4" r="1.5" fill="#b6b6b6" stroke="none"/><circle cx="5.07" cy="16" r="1.5" fill="#b6b6b6" stroke="none"/><circle cx="18.93" cy="16" r="1.5" fill="#b6b6b6" stroke="none"/></svg>

After

Width:  |  Height:  |  Size: 407 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>

After

Width:  |  Height:  |  Size: 270 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12h16" stroke-dasharray="3 3"/><circle cx="4" cy="12" r="1.5" fill="#b6b6b6" stroke="none"/><circle cx="20" cy="12" r="1.5" fill="#b6b6b6" stroke="none"/></svg>

After

Width:  |  Height:  |  Size: 352 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="6" width="16" height="12" rx="1"/><circle cx="12" cy="12" r="1.5" fill="#b6b6b6" stroke="none"/></svg>

After

Width:  |  Height:  |  Size: 297 B

+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M6 8.5l6-3 6 3v7l-6 3-6-3z"/>
<line x1="3" y1="14" x2="21" y2="10"/>
</svg>

After

Width:  |  Height:  |  Size: 268 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13M10 11v6M14 11v6"/></svg>

After

Width:  |  Height:  |  Size: 248 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7v10M20 7v10M4 12h16M7 9l-3 3 3 3M17 9l3 3-3 3"/></svg>

After

Width:  |  Height:  |  Size: 247 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M6 4H18L15 20H9Z"/><path d="M9 20V4" stroke-dasharray="2.5 2.5" opacity="0.5"/><path d="M9 14a4 4 0 0 0 1.3 3"/></svg>

After

Width:  |  Height:  |  Size: 307 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M4 20V10a6 6 0 0 1 6-6h10"/><path d="M4 20h4M20 4v4" stroke-dasharray="2.5 2.5" opacity="0.5"/></svg>

After

Width:  |  Height:  |  Size: 290 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M4 20h4L19 9l-4-4L4 16z"/><path d="M14 6l4 4"/></svg>

After

Width:  |  Height:  |  Size: 242 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="12" rx="9.5" ry="5.5"/><circle cx="12" cy="12" r="1.5" fill="#b6b6b6" stroke="none"/></svg>

After

Width:  |  Height:  |  Size: 292 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M3 13 A9.5 5.5 0 0 1 21 10"/><circle cx="12" cy="12" r="1.5" fill="#b6b6b6" stroke="none"/><circle cx="3" cy="13" r="1.5" fill="#b6b6b6" stroke="none"/><circle cx="21" cy="10" r="1.5" fill="#b6b6b6" stroke="none"/></svg>

After

Width:  |  Height:  |  Size: 409 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12h11"/><path d="M14 12h7m-3-3l3 3-3 3"/><path d="M14 12h7" stroke-dasharray="2.5 2.5" opacity="0.5"/></svg>

After

Width:  |  Height:  |  Size: 300 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="11" width="16" height="9" rx="1"/><path d="M12 8V2M9 5l3-3 3 3"/></svg>

After

Width:  |  Height:  |  Size: 266 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12C5 6 19 6 22 12 19 18 5 18 2 12Z"/><circle cx="12" cy="12" r="3"/></svg>

After

Width:  |  Height:  |  Size: 266 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5v8a6 6 0 0 0 6 6h8"/></svg>

After

Width:  |  Height:  |  Size: 220 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="12" cy="12" r="4"/><circle cx="12" cy="12" r="1.5" fill="#b6b6b6" stroke="none"/></svg>

After

Width:  |  Height:  |  Size: 328 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M5 19 19 5"/><circle cx="5" cy="19" r="1.5" fill="#b6b6b6" stroke="none"/><circle cx="19" cy="5" r="1.5" fill="#b6b6b6" stroke="none"/></svg>

After

Width:  |  Height:  |  Size: 330 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="15.5" width="8.5" height="4.5" rx="0.5"/><ellipse cx="16.5" cy="6" rx="4" ry="1.7"/><path d="M3.2 15.5 12.6 4.8M11.4 20 20.4 7.2"/></svg>

After

Width:  |  Height:  |  Size: 332 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v18" stroke-dasharray="2.5 2.5" opacity="0.5"/><path d="M8 7 4 12 8 17Z"/><path d="M16 7l4 5-4 5Z"/></svg>

After

Width:  |  Height:  |  Size: 300 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="6" height="6" rx="1"/><path d="M12 8V2M9.6 4.4 12 2 14.4 4.4M12 16v6M9.6 19.6 12 22 14.4 19.6M8 12H2M4.4 9.6 2 12 4.4 14.4M16 12h6M19.6 9.6 22 12 19.6 14.4"/></svg>

After

Width:  |  Height:  |  Size: 369 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M6 13l6 6 6-6"/></svg>

After

Width:  |  Height:  |  Size: 219 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19V5M6 11l6-6 6 6"/></svg>

After

Width:  |  Height:  |  Size: 219 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M3 20V10A7 7 0 0 1 10 3H20"/><path d="M7 20V10A3 3 0 0 1 10 7H20"/></svg>

After

Width:  |  Height:  |  Size: 262 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="9" width="6" height="6" rx="1"/><rect x="10.5" y="9" width="6" height="6" rx="1"/><rect x="18" y="9" width="3.6" height="6" rx="1" stroke-dasharray="2.5 2.5" opacity="0.5"/></svg>

After

Width:  |  Height:  |  Size: 374 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M3 8 13 8 21 15 11 15Z"/></svg>

After

Width:  |  Height:  |  Size: 220 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M12 4v3.4M12 16.6v3.4M4 12h3.4M16.6 12h3.4"/><circle cx="12" cy="12" r="2.2" fill="#b6b6b6" stroke="none"/></svg>

After

Width:  |  Height:  |  Size: 302 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="8" stroke-dasharray="2.5 2.5" opacity="0.5"/><circle cx="12" cy="12" r="1.4" fill="#b6b6b6" stroke="none"/><circle cx="12" cy="4" r="1.8" fill="#b6b6b6" stroke="none"/><circle cx="18.9" cy="8" r="1.8" fill="#b6b6b6" stroke="none"/><circle cx="18.9" cy="16" r="1.8" fill="#b6b6b6" stroke="none"/><circle cx="12" cy="20" r="1.8" fill="#b6b6b6" stroke="none"/><circle cx="5.1" cy="16" r="1.8" fill="#b6b6b6" stroke="none"/><circle cx="5.1" cy="8" r="1.8" fill="#b6b6b6" stroke="none"/></svg>

After

Width:  |  Height:  |  Size: 695 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l8 4.5v9L12 21l-8-4.5v-9Z"/><circle cx="12" cy="12" r="1.5" fill="#b6b6b6" stroke="none"/></svg>

After

Width:  |  Height:  |  Size: 290 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M3 18 9 9 14 15 21 5"/><circle cx="3" cy="18" r="1.5" fill="#b6b6b6" stroke="none"/><circle cx="9" cy="9" r="1.5" fill="#b6b6b6" stroke="none"/><circle cx="14" cy="15" r="1.5" fill="#b6b6b6" stroke="none"/><circle cx="21" cy="5" r="1.5" fill="#b6b6b6" stroke="none"/></svg>

After

Width:  |  Height:  |  Size: 462 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="6" width="16" height="12" rx="1"/><circle cx="4" cy="6" r="1.5" fill="#b6b6b6" stroke="none"/></svg>

After

Width:  |  Height:  |  Size: 295 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M4 16 10 6 20 8 14 18Z"/><circle cx="4" cy="16" r="1.5" fill="#b6b6b6" stroke="none"/><circle cx="10" cy="6" r="1.5" fill="#b6b6b6" stroke="none"/></svg>

After

Width:  |  Height:  |  Size: 342 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><rect x="3.5" y="6" width="17" height="12" rx="4.5"/></svg>

After

Width:  |  Height:  |  Size: 239 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M3 16.5 A9 9 0 0 1 21 16.5 L16 16.5 A4 4 0 0 0 8 16.5 Z"/></svg>

After

Width:  |  Height:  |  Size: 253 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-3.5-7.1"/><path d="M21 3 21 8 16 8"/></svg>

After

Width:  |  Height:  |  Size: 249 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="6" height="6" rx="1"/><path d="M8 8 3 3M3 7 3 3 7 3M16 8 21 3M17 3 21 3 21 7M8 16 3 21M3 17 3 21 7 21M16 16 21 21M17 21 21 21 21 17"/></svg>

After

Width:  |  Height:  |  Size: 345 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M5 3 5 17.5 8.8 14.2 11.3 20.3 13.4 19.4 11 13.6 15.9 13.6Z"/></svg>

After

Width:  |  Height:  |  Size: 257 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="16" height="16" rx="1"/><path d="M8 4v12h8V4"/></svg>

After

Width:  |  Height:  |  Size: 258 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M16 3.5a2.1 2.1 0 0 1 3 3L7.5 18l-4 1 1-4Z"/><path d="M14.5 5 19 9.5"/></svg>

After

Width:  |  Height:  |  Size: 266 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M8 7h8a5 5 0 0 1 0 10H8a5 5 0 0 1 0-10Z"/><circle cx="8" cy="12" r="1.5" fill="#b6b6b6" stroke="none"/><circle cx="16" cy="12" r="1.5" fill="#b6b6b6" stroke="none"/></svg>

After

Width:  |  Height:  |  Size: 360 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M5.3 13 A8 8 0 0 1 12.9 17 A2.5 2.5 0 0 0 17.3 14.5 A13 13 0 0 0 4.9 8 A2.5 2.5 0 0 0 5.3 13Z"/></svg>

After

Width:  |  Height:  |  Size: 291 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M3.0 9.0 L16.0 9.0 L16.0 21.0 L3.0 21.0 ZM3.0 9.0 L7.0 5.0 L20.0 5.0 L16.0 9.0M20.0 5.0 L20.0 17.0 L16.0 21.0" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"/><path d="M6.168410256410256 13.423897435897436V13.868410256410256Q5.908923076923077 13.744307692307691 5.678769230769231 13.683384615384615Q5.448615384615385 13.622461538461538 5.23425641025641 13.622461538461538Q4.861948717948718 13.622461538461538 4.66 13.766871794871793Q4.458051282051283 13.91128205128205 4.458051282051283 14.177538461538461Q4.458051282051283 14.400923076923076 4.592307692307693 14.514871794871794Q4.726564102564103 14.628820512820512 5.101128205128205 14.69876923076923L5.3764102564102565 14.755179487179486Q5.886358974358975 14.852205128205128 6.128923076923078 15.09702564102564Q6.37148717948718 15.341846153846152 6.37148717948718 15.75251282051282Q6.37148717948718 16.242153846153844 6.043179487179488 16.49487179487179Q5.7148717948717955 16.747589743589742 5.080820512820513 16.747589743589742Q4.841641025641026 16.747589743589742 4.572000000000001 16.693435897435897Q4.302358974358975 16.639282051282052 4.013538461538462 16.53323076923077V16.063897435897434Q4.291076923076924 16.219589743589744 4.557333333333334 16.2985641025641Q4.823589743589745 16.37753846153846 5.080820512820513 16.37753846153846Q5.471179487179487 16.37753846153846 5.683282051282052 16.224102564102562Q5.895384615384616 16.070666666666664 5.895384615384616 15.786358974358974Q5.895384615384616 15.538153846153845 5.743076923076924 15.398256410256408Q5.590769230769231 15.258358974358973 5.243282051282051 15.188410256410256L4.965743589743591 15.134256410256409Q4.455794871794872 15.032717948717949 4.227897435897436 14.816102564102565Q4.000000000000001 14.599487179487179 4.000000000000001 14.213641025641024Q4.000000000000001 13.766871794871793 4.314769230769231 13.509641025641024Q4.629538461538462 13.252410256410256 5.182358974358975 13.252410256410256Q5.419282051282051 13.252410256410256 5.665230769230769 13.29528205128205Q5.911179487179488 13.338153846153846 6.168410256410256 13.423897435897436ZM6.615179487179487 13.313333333333333H9.46502564102564V13.696923076923076H8.269128205128204V16.682153846153845H7.811076923076923V13.696923076923076H6.615179487179487ZM9.905025641025642 13.313333333333333H12.035076923076925V13.696923076923076H10.360820512820514V14.69425641025641H11.965128205128206V15.077846153846153H10.360820512820514V16.2985641025641H12.075692307692309V16.682153846153845H9.905025641025642ZM13.280615384615384 13.687897435897435V14.953743589743588H13.85374358974359Q14.171897435897435 14.953743589743588 14.345641025641026 14.78902564102564Q14.519384615384615 14.624307692307692 14.519384615384615 14.319692307692307Q14.519384615384615 14.017333333333333 14.345641025641026 13.852615384615383Q14.171897435897435 13.687897435897435 13.85374358974359 13.687897435897435ZM12.824820512820512 13.313333333333333H13.85374358974359Q14.420102564102564 13.313333333333333 14.710051282051282 13.569435897435897Q15.0 13.82553846153846 15.0 14.319692307692307Q15.0 14.818358974358974 14.710051282051282 15.073333333333332Q14.420102564102564 15.32830769230769 13.85374358974359 15.32830769230769H13.280615384615384V16.682153846153845H12.824820512820512Z" fill="#b6b6b6" stroke="none"/></svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M7.081932245922209 8.669636135508153V9.608908406524465Q6.5336260978670015 9.346675031367628 6.047302383939774 9.217942283563362Q5.560978670012547 9.089209535759096 5.108030112923463 9.089209535759096Q4.321329987452948 9.089209535759096 3.894604767879548 9.39435382685069Q3.467879548306148 9.699498117942282 3.467879548306148 10.262107904642408Q3.467879548306148 10.734127979924716 3.751568381430364 10.974905897114176Q4.03525721455458 11.215683814303638 4.8267252195734 11.363488080301128L5.40840652446675 11.482685069008781Q6.48594730238394 11.687703889585945 6.9984943538268505 12.205018820577163Q7.511041405269761 12.72233375156838 7.511041405269761 13.5900878293601Q7.511041405269761 14.624717691342532 6.817314930991217 15.158720200752821Q6.123588456712673 15.69272271016311 4.7838143036386445 15.69272271016311Q4.278419071518194 15.69272271016311 3.708657465495609 15.578293601003763Q3.138895859473024 15.463864491844415 2.528607277289837 15.239774153074025V14.248055207026347Q3.115056461731493 14.577038895859472 3.6776662484316187 14.743914680050187Q4.240276035131744 14.910790464240902 4.7838143036386445 14.910790464240902Q5.608657465495608 14.910790464240902 6.056838143036387 14.586574654956085Q6.505018820577165 14.262358845671265 6.505018820577165 13.66160602258469Q6.505018820577165 13.137139272271014 6.183186951066499 12.841530740276033Q5.861355081555834 12.545922208281052 5.1271016311166875 12.398117942283562L4.540652446675031 12.283688833124215Q3.4631116687578416 12.069134253450438 2.981555834378921 11.611417816813049Q2.5 11.153701380175658 2.5 10.338393977415306Q2.5 9.39435382685069 3.165119196988708 8.850815558343788Q3.830238393977415 8.307277289836886 4.998368883312422 8.307277289836886Q5.498996235884567 8.307277289836886 6.018695106649937 8.397867001254703Q6.538393977415307 8.48845671267252 7.081932245922209 8.669636135508153ZM10.848557089084066 15.554454203262232 8.13086574654956 8.436010037641154H9.136888331242158L11.392095357590966 14.42923462986198L13.652070263488081 8.436010037641154H14.653324968632372L11.940401505646173 15.554454203262232ZM20.546424090338768 14.538895859473023V12.626976160602258H18.97302383939774V11.835508155583437H21.499999999999996V14.891718946047677Q20.942158092848178 15.287452948557087 20.26988707653701 15.4900878293601Q19.597616060225846 15.69272271016311 18.83475533249686 15.69272271016311Q17.16599749058971 15.69272271016311 16.224341279799248 14.717691342534504Q15.282685069008783 13.742659974905896 15.282685069008783 12.002383939774152Q15.282685069008783 10.257340025094102 16.224341279799248 9.282308657465494Q17.16599749058971 8.307277289836886 18.83475533249686 8.307277289836886Q19.53086574654956 8.307277289836886 20.15784190715182 8.478920953575908Q20.784818067754077 8.650564617314929 21.31405269761606 8.984316185696361V10.009410288582181Q20.78005018820577 9.556461731493098 20.179297365119197 9.327603513174402Q19.57854454203262 9.098745294855707 18.915809284818067 9.098745294855707Q17.609410288582183 9.098745294855707 16.953826850690085 9.828230865746548Q16.29824341279799 10.557716436637389 16.29824341279799 12.002383939774152Q16.29824341279799 13.442283563362608 16.953826850690085 14.171769134253449Q17.609410288582183 14.90125470514429 18.915809284818067 14.90125470514429Q19.425972396486824 14.90125470514429 19.826474278544538 14.813048933500625Q20.226976160602256 14.724843161856962 20.546424090338768 14.538895859473023Z" fill="#b6b6b6" stroke="none"/></svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="17" r="2.4"/><path d="M6 17 C 7 9, 15 15, 18 6"/><path d="M15.2 6.6 18 6 17.7 8.9"/></svg>

After

Width:  |  Height:  |  Size: 289 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M3 18h8"/><path d="M11 18 A7 7 0 0 1 18 11"/><circle cx="3" cy="18" r="1.5" fill="#b6b6b6" stroke="none"/><circle cx="11" cy="18" r="1.5" fill="#b6b6b6" stroke="none"/></svg>

After

Width:  |  Height:  |  Size: 363 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M6 20 12 4 18 20M8.5 14h7"/></svg>

After

Width:  |  Height:  |  Size: 223 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3h8v13l-4 5-4-5Z"/><path d="M8 7l8-2M8 11l8-2M8 15l8-2"/></svg>

After

Width:  |  Height:  |  Size: 255 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="6.5" r="2.6"/><circle cx="6" cy="17.5" r="2.6"/><path d="M8.3 8 20.5 17M8.3 16 20.5 7"/></svg>

After

Width:  |  Height:  |  Size: 293 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><path d="M1.5,6.5l7.776,3.888a.5.5,0,0,0,.448,0L17.5,6.5,9.724,2.612a.5.5,0,0,0-.448,0Z" style="fill:none;stroke:#fff;stroke-linecap:round;stroke-linejoin:round"/><path d="M1.5,6.5v8l7.776,3.888a.5.5,0,0,0,.448,0L17.5,14.5v-8" style="fill:none;stroke:#fff;stroke-linecap:round;stroke-linejoin:round"/></svg>

After

Width:  |  Height:  |  Size: 390 B

@@ -0,0 +1,4 @@
<svg width="35" height="35" viewBox="0 0 35 35" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.0007 3.15607L5.26748 9.97446C5.0759 10.0857 4.8723 10.2988 4.71546 10.5713C4.55862 10.8438 4.47656 11.127 4.47656 11.349V24.4715C4.47656 24.6929 4.55879 24.9752 4.71606 25.2472C4.87328 25.5192 5.07737 25.7322 5.26956 25.8438L17.1143 32.6992C17.3061 32.8102 17.5904 32.8795 17.902 32.8782C18.2136 32.8768 18.4968 32.8049 18.6868 32.6927L30.2729 25.8523C30.4645 25.739 30.6677 25.524 30.8242 25.2501C30.9806 24.9761 31.0625 24.6919 31.0625 24.4693V11.2435C31.0625 11.0216 30.9805 10.7392 30.8238 10.4679C30.667 10.1966 30.4635 9.98491 30.2715 9.87455L18.582 3.15275C18.3912 3.04273 18.1054 2.97271 17.7915 2.97339C17.4777 2.97408 17.192 3.045 17.0007 3.15607ZM6.3957 11.3642C6.53284 11.1245 6.83831 11.0414 7.07799 11.1785L16.9733 16.8405C17.3553 17.0587 17.6738 17.4171 17.8953 17.7985C18.1169 18.1801 18.2703 18.6338 18.2717 19.0733L18.2893 30.4075C18.2897 30.6837 18.0662 30.9079 17.7901 30.9083C17.5139 30.9087 17.2897 30.6852 17.2893 30.4091L17.2717 19.0767C17.2708 18.8547 17.1879 18.5717 17.0305 18.3007C16.8729 18.0294 16.6691 17.8183 16.4776 17.709L6.58136 12.0465C6.34168 11.9093 6.25855 11.6039 6.3957 11.3642Z" fill="#4db6ac"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.0809 2.28613C18.7002 2.0665 18.2304 1.97243 17.7893 1.9734C17.3481 1.97436 16.8787 2.07053 16.4985 2.29133L4.76515 9.10978C4.38527 9.33044 4.06884 9.69007 3.84874 10.0725C3.6286 10.455 3.47656 10.9093 3.47656 11.349V24.4715C3.47656 24.9118 3.62953 25.3658 3.85033 25.7477C4.07118 26.1297 4.38804 26.4882 4.76735 26.7085L16.6135 33.5647C16.9954 33.7857 17.4651 33.8801 17.9064 33.8782C18.3475 33.8762 18.8161 33.7778 19.1954 33.5537L30.7814 26.7133C31.1591 26.4902 31.4737 26.1292 31.6925 25.746C31.9114 25.3627 32.0625 24.9084 32.0625 24.4693V11.2435C32.0625 10.8037 31.9104 10.3497 31.6896 9.96764C31.4689 9.58563 31.1515 9.22695 30.7698 9.00759L19.0809 2.28613ZM29.6625 11.8015C29.786 12.0485 29.6859 12.3489 29.4389 12.4723L20.3956 16.994C20.1486 17.1175 19.8483 17.0174 19.7248 16.7704C19.6013 16.5234 19.7014 16.2231 19.9484 16.0996L28.9917 11.5779C29.2387 11.4544 29.539 11.5545 29.6625 11.8015Z" fill="#bdbdbd"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

+8
View File
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40">
<!-- Pencil body -->
<path d="M28.5 6.5l5 5L14 31l-5.5 1.5L10 27 28.5 6.5z" style="fill:#009688"/>
<!-- Pencil tip (dark) -->
<path d="M8.5 28.5l1.5 1.5 4.5-1L28.5 15l-3.5-3.5L8.5 28z" style="fill:#2b3436"/>
<!-- Pencil line -->
<line x1="10" y1="26" x2="25" y2="11" stroke="#2b3436" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 406 B

+8
View File
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40">
<!-- Pencil body (dark mode colors) -->
<path d="M28.5 6.5l5 5L14 31l-5.5 1.5L10 27 28.5 6.5z" style="fill:#4db6ac"/>
<!-- Pencil tip -->
<path d="M8.5 28.5l1.5 1.5 4.5-1L28.5 15l-3.5-3.5L8.5 28z" style="fill:#e0e0e0"/>
<!-- Pencil line -->
<line x1="10" y1="26" x2="25" y2="11" stroke="#e0e0e0" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 418 B

+70
View File
@@ -0,0 +1,70 @@
# Deps-only base image for fast iteration on SnapOrca.
# Identical system+pinned-dependency setup to scripts/Dockerfile, but STOPS after
# `build_linux.sh -dr` (no slicer/AppImage build). Produces an image with the pinned
# deps baked at /OrcaSlicer/deps/build/destdir, so the slicer can be rebuilt
# incrementally via scripts/docker-iter-build.sh without re-running the long deps build.
#
# Build once (rebuild only when deps/ changes, e.g. OCCT module flags):
# docker build -t snaporca-deps -f scripts/Dockerfile.deps .
FROM docker.io/ubuntu:24.04
LABEL maintainer="SnapOrca CAD iteration base"
# Disable interactive package configuration
RUN apt-get update && \
echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
# Add a deb-src
RUN echo deb-src http://archive.ubuntu.com/ubuntu \
$(cat /etc/*release | grep VERSION_CODENAME | cut -d= -f2) main universe>> /etc/apt/sources.list
RUN apt-get update && apt-get install -y \
autoconf \
build-essential \
cmake \
curl \
eglexternalplatform-dev \
extra-cmake-modules \
file \
git \
gstreamer1.0-plugins-bad \
gstreamer1.0-libav \
libcairo2-dev \
libcurl4-openssl-dev \
libdbus-1-dev \
libglew-dev \
libglu1-mesa-dev \
libgstreamer1.0-dev \
libgstreamerd-3-dev \
libgstreamer-plugins-base1.0-dev \
libgstreamer-plugins-good1.0-dev \
libgtk-3-dev \
libsecret-1-dev \
libsoup2.4-dev \
libssl3 \
libssl-dev \
libtool \
libudev-dev \
libwayland-dev \
libwebkit2gtk-4.1-dev \
libxkbcommon-dev \
locales \
locales-all \
m4 \
pkgconf \
sudo \
wayland-protocols \
wget
ENV LC_ALL=en_US.utf8
RUN locale-gen $LC_ALL
ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
COPY ./ OrcaSlicer
WORKDIR OrcaSlicer
# System dependencies
RUN ./build_linux.sh -u
# Pinned dependencies in ./deps (OCCT 7.6 with ModelingAlgorithms enabled, OpenCV,
# OpenVDB, Boost, wxWidgets, ...). This is the long step; it is baked into the image.
RUN ./build_linux.sh -dr
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
# Incremental slicer build against the snaporca-deps base image.
#
# The deps-baked image (built from scripts/Dockerfile.deps) carries the pinned
# dependencies at /OrcaSlicer/deps/build/destdir. This script mounts the LIVE source
# tree and resources over the baked copy so code/CMake edits apply immediately, and
# persists /OrcaSlicer/build in a named volume so ninja recompiles only what changed.
#
# Result: edit -> rebuild in seconds-to-minutes instead of a full Docker rebuild.
#
# Usage (run on the build host, e.g. behemoth, from anywhere):
# scripts/docker-iter-build.sh
# IMAGE=snaporca-deps scripts/docker-iter-build.sh
#
# On success the binary is inside the persistent volume at
# /OrcaSlicer/build/package/bin/snapmaker-orca (copy it out with a follow-up
# `docker run --rm -v snaporca_buildcache:/b alpine cp ...` or via this script's tail).
set -euo pipefail
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
IMAGE="${IMAGE:-snaporca-deps}"
BUILD_VOL="${BUILD_VOL:-snaporca_buildcache}"
echo "REPO=$REPO IMAGE=$IMAGE BUILD_VOL=$BUILD_VOL"
docker run --rm \
-v "$REPO/src":/OrcaSlicer/src \
-v "$REPO/resources":/OrcaSlicer/resources \
-v "$BUILD_VOL":/OrcaSlicer/build \
"$IMAGE" \
bash -lc 'cd /OrcaSlicer && ./build_linux.sh -sr'
echo "=== build finished; checking for binary ==="
docker run --rm -v "$BUILD_VOL":/b "$IMAGE" \
bash -lc 'ls -lh /b/package/bin/snapmaker-orca 2>/dev/null && file /b/package/bin/snapmaker-orca || echo "NO BINARY"'
+23
View File
@@ -20,6 +20,9 @@ endif()
option(BUILD_SHARED_LIBS "Build shared libs" OFF)
# Vendored SolveSpace constraint solver (2D sketch solver backbone).
add_subdirectory(slvs)
set(lisbslic3r_sources
AABBMesh.cpp
AABBMesh.hpp
@@ -275,6 +278,8 @@ set(lisbslic3r_sources
Geometry/VoronoiUtils.cpp
Geometry/VoronoiUtils.hpp
Geometry/VoronoiVisualUtils.hpp
GeometryEngine.cpp
GeometryEngine.hpp
Int128.hpp
KDTreeIndirect.hpp
Layer.cpp
@@ -385,6 +390,20 @@ set(lisbslic3r_sources
SLA/JobController.hpp
SLA/Pad.cpp
SLA/Pad.hpp
SketchEngine.cpp
SketchEngine.hpp
SketchConstraints.cpp
SketchConstraints.hpp
SketchSolver.cpp
SketchSolver.hpp
SketchInference.cpp
SketchInference.hpp
SketchImport.cpp
SketchImport.hpp
CadDocument.cpp
CadDocument.hpp
ThreadStandards.cpp
ThreadStandards.hpp
SLAPrint.cpp
SLAPrint.hpp
SLAPrintSteps.cpp
@@ -546,6 +565,9 @@ find_package(JPEG REQUIRED)
find_package(draco REQUIRED)
set(OCCT_LIBS
TKFillet
TKOffset
TKBool
TKXDESTEP
TKSTEP
TKSTEP209
@@ -591,6 +613,7 @@ target_link_libraries(libslic3r
clipper
Clipper2
draco::draco
libslvs
glu-libtess
JPEG::JPEG
libslic3r_cgal
File diff suppressed because it is too large Load Diff
+355
View File
@@ -0,0 +1,355 @@
#ifndef slic3r_CadDocument_hpp_
#define slic3r_CadDocument_hpp_
#include "TriangleMesh.hpp"
#include "SketchEngine.hpp"
#include "GeometryEngine.hpp" // FaceGroup
#include "Color.hpp" // ColorRGBA (per-body display colour override)
#include <TopoDS_Shape.hxx>
#include <TopoDS_Wire.hxx>
#include <string>
#include <vector>
#include <utility>
namespace Slic3r {
enum class CadFeatureType { Sketch, Extrude, Fillet, Chamfer, Hole, Thread, Shell, Revolve, Sweep, Pattern, Plane, Loft, Draft, Import, Boolean, Cut };
enum class SketchShape { Rectangle, Circle };
enum class BooleanMode { New, Add, Cut, Intersect };
enum class ExtrudeEnd { Blind, Symmetric, TwoSided, ThroughAll, UpToFace, UpToVertex };
struct CadFeature {
CadFeatureType type{CadFeatureType::Sketch};
std::string name;
bool enabled{true};
// Sketch params (centered on the plane origin)
SketchShape shape{SketchShape::Rectangle};
SketchPlane plane{SketchPlane::XY()};
double width{20};
double height{20};
double radius{10};
// Real 2D sketch geometry (Onshape-style). When non-empty this takes
// precedence over the shape/width/height/radius enum path in build_sketch_wire.
SketchProfile profile;
// Onshape-style multi-entity sketch geometry. When non-empty this takes
// precedence over both `profile` and the shape-enum path in build_sketch_wire.
std::vector<SketchEntity> entities;
// 2D geometric constraints on `profile` (point indices). Solved in place.
std::vector<SketchConstraintDef> constraints;
// Onshape-style constraints on `entities` (Fase 4.2). Solved in place against
// entity endpoints. Used when `entities` is non-empty (the legacy `constraints`
// vector applies only to the `profile` path).
std::vector<SketchEntityConstraintDef> entity_constraints;
// Imported rigid 2D art (Text glyphs / SVG vector paths) as filled regions.
// Each region: contour[0] = outer loop, contour[1..] = holes; points in
// plane (u,v) millimetres. Rendered as a sketch overlay and extruded via a
// faces-with-holes path (SketchEngine::make_extrude_regions) — deliberately
// NOT solver entities, so imported art contributes zero DoF and never
// pollutes the constraint solver / DoF readout. When non-empty it takes
// precedence over the entities/profile/shape paths in the Extrude case.
std::vector<std::vector<std::vector<Vec2d>>> imported_regions;
// Imported rigid 3D B-rep solid (STEP). When the feature type is Import this carries
// the OCCT shape verbatim — it is adopted as a base body in route_feature (no parametric
// recipe). Downstream face/edge features (fillet/chamfer/cut/shell/...) act on it like any
// other body. TopoDS_Shape is a cheap handle, so copying it through recompute/checkpoint
// snapshots is cheap. In-session only for now (no BRep serialization yet).
TopoDS_Shape imported_solid;
// Non-destructive placement transform for imported_regions (Text/SVG),
// applied at display + extrude time as
// p -> (p.x*import_scale_x + import_offset.x, p.y*import_scale_y + import_offset.y).
// Lets the art be moved / enlarged / stretched (independent X/Y) repeatedly
// without re-vectorising. Identity = no change.
Vec2d import_offset{0, 0};
double import_scale_x{1.0};
double import_scale_y{1.0};
// Text/SVG dropped ONTO a solid face (centred on it): the extrude then defaults to an
// inward Cut (engraving) targeting `import_face_body`. False = free art on a plane.
bool import_on_face{false};
int import_face_body{-1};
// Extrude params
int sketch_ref{-1}; // index into features[] of the consumed sketch
double distance{10};
bool symmetric{false};
BooleanMode mode{BooleanMode::New};
ExtrudeEnd extrude_end{ExtrudeEnd::Blind};
double distance2{0}; // second-side depth for TwoSided
double taper_deg{0}; // draft angle (C4-part2)
bool flip{false}; // reverse the extrude direction (negate plane normal)
int up_to_face{-1}; // target solid-face id for UpToFace (C4-part2)
int extrude_src_face{-1}; // global face id on the current body to extrude as a profile; -1 = use sketch wire
Vec3d up_to_point{0,0,0}; // target for UpToVertex (C4-part2)
// Multi-body target: which body (index into CadDocument::bodies) this feature acts on.
// -1 = auto (last body). A New extrude appends a fresh body; Add/Cut/Intersect, dress-up,
// hole and face-extrude(non-New) mutate bodies[target]; face-extrude reads its source
// face from bodies[target] too. The source-face owner for face-extrude lives here.
int target_body{-1};
// Dress-up params (Fillet/Chamfer) — applied to the current body in order
double dressup_size{1.0}; // fillet radius or chamfer distance
FaceGroup face_group{FaceGroup::All};
int dressup_edge{-1}; // global edge id for edge-targeted fillet/chamfer; -1 = use face_group
// Hole params (positioned circular cut into the current body)
double hole_diameter{5};
double hole_depth{10};
bool hole_through{true}; // true = symmetric through-cut, ignores hole_depth
double hole_x{0}; // position on the plane (plane u/x axis)
double hole_y{0}; // position on the plane (plane v/y axis)
// Thread params (helical thread about the plane normal at a positioned point)
double thread_radius{5}; // nominal cylinder radius
double thread_pitch{2}; // axial advance per turn
double thread_height{10}; // total axial length
double thread_depth{1}; // radial crest depth of the thread profile
bool thread_internal{false}; // false = external threaded rod (New body);
// true = tapped bore cut into the current body
double thread_x{0}; // axis position on the plane (u/x axis)
double thread_y{0}; // axis position on the plane (v/y axis)
// Shell params (hollow the current body to a wall thickness, removing one open face)
double shell_thickness{2}; // wall thickness (inward offset)
int shell_face{-1}; // global face id to remove (open the shell); -1 = none
// Draft params (taper a single solid face about a neutral plane = body bbox bottom, pull +Z)
int draft_face{-1}; // global face id to draft; -1 = none
double draft_angle{5}; // draft angle in degrees (signed: + leans the face inward)
// Revolve params (sweep a profile about an in-plane axis through the plane origin).
// Reuses sketch_ref / entities (profile), flip (direction), mode (boolean) and
// target_body. revolve_axis: 0 = plane X axis, 1 = plane Y axis.
double revolve_angle{360}; // sweep angle in degrees (1..360)
int revolve_axis{0}; // 0 = plane X, 1 = plane Y
// Sweep: profile carried by sketch_ref / entities (like Extrude); the spine is a
// second Sketch referenced by sweep_path_ref (an open or closed wire). Reuses
// mode (boolean) and target_body.
int sweep_path_ref{-1}; // index into features[] of the path Sketch
// Loft: build a solid through 2+ closed profile Sketches (loft_profile_refs, in
// order, each on its own plane). loft_ruled=false → smooth sections, true → ruled.
// Reuses mode (boolean) and target_body.
std::vector<int> loft_profile_refs; // ordered indices into features[] of profile Sketches
bool loft_ruled{false};
// Pattern: replicate the target body, copies fused into it. pattern_circular=false
// → linear (pattern_count instances spaced pattern_spacing along plane axis
// pattern_dir: 0=X, 1=Y); true → circular (pattern_count instances over
// pattern_angle° total about the plane normal through the plane origin, so a seed
// offset from the origin orbits the axis). Reuses target_body + plane.
bool pattern_circular{false};
int pattern_count{3}; // total instances incl. the seed (>=1)
double pattern_spacing{20}; // linear step (mm)
int pattern_dir{0}; // linear direction: 0 = plane X, 1 = plane Y
double pattern_angle{360}; // circular total angle (degrees)
// Datum/reference plane: a derived SketchPlane the document offers as a selectable
// sketch plane (no solid). plane_base selects the reference (0=XY,1=XZ,2=YZ, or 3+N
// = the Nth earlier datum plane); plane_offset shifts along the base normal;
// plane_angle tilts plane_angle° about the base axis plane_axis (0=base X, 1=base Y).
int plane_base{0};
double plane_offset{20};
double plane_angle_tilt{0}; // degrees (named *_tilt to avoid clash w/ revolve)
int plane_axis{0}; // tilt axis: 0 = base X, 1 = base Y
// Boolean: combine two EXISTING bodies. `mode` reuses BooleanMode (Add = union,
// Cut = subtract tool from target, Intersect = keep overlap; New unused). `target_body`
// is the body that survives (result written back to it); `bool_tool_body` is the other
// operand, consumed (erased) unless `bool_keep_tool`. `bool_tolerance` = OCCT fuzzy value
// (0 = exact). Per-face merge: when both bool_target_face/bool_tool_face are set, the tool
// is first snapped so those two faces are coincident (gap closed within bool_tolerance),
// then the boolean welds them and coplanar faces are unified into one clean face.
int bool_tool_body{-1};
bool bool_keep_tool{false};
double bool_tolerance{0.0};
int bool_target_face{-1}; // global face id on the target body to mate (-1 = none)
int bool_tool_face{-1}; // global face id on the tool body to mate (-1 = none)
// Cut: split one target body with a plane, keeping the upper half, lower half, or both.
// Reuses `plane` for the cut plane and `target_body` for which body is cut.
double cut_offset{0.0}; // offset along the cut-plane normal (mm)
bool cut_flip{false}; // flip the normal => swaps which side is "upper"
bool cut_keep_upper{true}; // keep the +normal half
bool cut_keep_lower{false}; // keep the -normal half (both => split into two bodies)
};
// One independent solid in a multi-body document.
struct CadBody {
TopoDS_Shape shape;
std::string name;
// Per-body display colour override (Color tool). When has_color is false the GUI
// falls back to the auto body-index palette. Carried across recompute() by body index.
bool has_color{false};
ColorRGBA color;
};
// OCCT-only feature tree backing the Design tab. No GUI dependencies (lives in libslic3r).
class CadDocument {
public:
std::vector<CadFeature> features;
// Multi-body result of the last replay. A "New" extrude appends a body; other ops
// mutate a target body. Empty after a failed/empty recompute.
std::vector<CadBody> bodies;
TopoDS_Shape body; // compound of all bodies (1 body => that body) — display/compat
TriangleMesh display_mesh; // tessellation of all bodies, concatenated (picking)
std::vector<TriangleMesh> display_body_meshes; // one mesh per body, in `bodies` order (per-body color)
std::vector<int> display_tri_face; // per-triangle face id WITHIN its source body
std::vector<int> display_tri_body; // per-triangle source body index (into bodies)
std::string error; // last recompute error ("" = ok)
double linear_deflection{0.01};
double angular_deflection{0.5};
int add_sketch(SketchShape shape, const SketchPlane& plane,
double width, double height, double radius,
const std::string& name);
int add_sketch_profile(const SketchProfile& profile, const SketchPlane& plane,
const std::string& name);
// Onshape-style multi-entity sketch: stores the entity list verbatim. When
// non-empty it takes precedence over profile/enum in build_sketch_wire.
int add_sketch_entities(const std::vector<SketchEntity>& entities,
const SketchPlane& plane, const std::string& name,
const std::vector<SketchEntityConstraintDef>& constraints = {});
// Solve features[index]'s sketch constraints, writing solved coordinates back
// into its profile.points. No-op (returns true) if the feature has no
// constraints. Returns false if index is invalid / not a Sketch / solve fails.
bool solve_sketch_feature(int index);
int add_extrude(int sketch_ref, double distance, bool symmetric,
BooleanMode mode, const std::string& name);
// Extrude a single loop given directly as entities (sketch_ref = -1, plane carried).
int add_extrude_entities(const std::vector<SketchEntity>& entities,
const SketchPlane& plane, double distance, bool symmetric,
BooleanMode mode, const std::string& name);
// Extrude an existing solid FACE (global face id on the body) as the profile.
int add_extrude_face(int src_face, double distance, bool symmetric,
BooleanMode mode, const std::string& name);
int add_fillet(double radius, FaceGroup faces, const std::string& name);
int add_fillet(double radius, int edge_id, const std::string& name);
int add_chamfer(double distance, FaceGroup faces, const std::string& name);
int add_chamfer(double distance, int edge_id, const std::string& name);
int add_hole(double diameter, double depth, bool through,
double x, double y, const SketchPlane& plane,
const std::string& name);
int add_thread(double radius, double pitch, double height, double depth,
bool internal, double x, double y, const SketchPlane& plane,
const std::string& name);
int add_revolve(int sketch_ref, double angle, int axis, bool flip,
BooleanMode mode, const std::string& name);
// Self-contained revolve of a single loop given directly as entities (sketch_ref=-1).
int add_revolve_entities(const std::vector<SketchEntity>& entities,
const SketchPlane& plane, double angle, int axis, bool flip,
BooleanMode mode, const std::string& name);
// Sweep the profile Sketch (profile_sketch_ref) along the path Sketch (path_sketch_ref).
int add_pattern(bool circular, int count, double spacing, int dir,
double angle_deg, int target_body, const std::string& name);
int add_sweep(int profile_sketch_ref, int path_sketch_ref, BooleanMode mode,
const std::string& name);
// Loft through the ordered profile Sketches (each a closed wire on its own plane).
int add_loft(const std::vector<int>& profile_refs, bool ruled, BooleanMode mode,
const std::string& name);
int add_shell(double thickness, int face, int target_body, const std::string& name);
int add_draft(double angle, int face, int target_body, const std::string& name);
// Boolean between two existing bodies. op reuses BooleanMode (Add=union, Cut=subtract,
// Intersect=common; New invalid). target survives, tool is consumed unless keep_tool.
// tolerance = OCCT fuzzy value; target_face/tool_face (-1 = none) drive the per-face snap+merge.
int add_boolean(BooleanMode op, int target_body, int tool_body, bool keep_tool,
double tolerance, int target_face, int tool_face, const std::string& name);
// Plane Cut (Onshape split-by-plane): trim target_body by the plane (origin offset along
// its normal by `offset`, normal flipped iff `flip`). keep_upper/keep_lower select the
// +normal / -normal half; both => the body is split into two coexisting bodies.
int add_cut(const SketchPlane& plane, double offset, bool flip,
bool keep_upper, bool keep_lower, int target_body, const std::string& name);
// Datum plane: derived from base (0=XY/1=XZ/2=YZ/3+N=Nth earlier datum), offset
// along its normal, optional tilt about a base axis. Produces no solid.
int add_plane(int base, double offset, double angle_tilt, int axis,
const std::string& name);
// Every datum plane currently in the recipe, in feature order, as (name, plane).
// Used by the GUI to populate plane pickers (after the 3 base planes).
std::vector<std::pair<std::string, SketchPlane>> resolve_datum_planes() const;
void clear();
bool recompute(); // replay features -> body + display_mesh; false on error
// Undo/redo of the feature recipe (Onshape-style Ctrl+Z). The caller marks a
// user-action boundary by calling checkpoint() BEFORE the mutation(s) for that
// action (add/delete/move/replace, or a direct features edit). undo()/redo() then
// restore the snapshot and recompute(). Because everything else (bodies/meshes/
// body) is derived by recompute(), snapshotting `features` alone is a complete,
// exact history; one checkpoint == one Ctrl+Z step.
void checkpoint(); // snapshot `features` for undo + invalidate redo
bool can_undo() const { return !m_undo.empty(); }
bool can_redo() const { return !m_redo.empty(); }
size_t undo_depth() const { return m_undo.size(); }
size_t redo_depth() const { return m_redo.size(); }
bool undo(); // restore the previous feature list + recompute(); false if no history
bool redo(); // re-apply the most recently undone change; false if none
// Feature-tree editing (Onshape-style). All are transactional: they snapshot
// features, mutate, recompute(), and roll back to the snapshot (re-recomputing)
// if the result is invalid — so a failed edit never leaves a broken body.
//
// remove_feature: erase features[index]; deleting a Sketch cascades to the
// Extrude(s) that consume it; surviving sketch_ref indices are remapped.
// move_feature: shift features[index] by delta (-1 up / +1 down), clamped;
// sketch_ref indices of the two swapped slots are remapped.
// replace_feature: overwrite features[index] with `edited` (its name and, for
// an Extrude, its sketch_ref are preserved from the original).
bool remove_feature(int index);
bool move_feature(int index, int delta);
bool replace_feature(int index, const CadFeature& edited);
// replace_sketch_extrude: a box is two linked features (Sketch + Extrude);
// overwrite both slots from one `edited` candidate (sketch params ->
// features[sketch_idx], extrude params -> features[extrude_idx]), keeping
// each slot's name/type and the sketch_ref link. Transactional like above.
bool replace_sketch_extrude(int sketch_idx, int extrude_idx, const CadFeature& edited);
// Apply ONE candidate feature on top of the current committed body and
// tessellate the result into out_mesh, WITHOUT modifying features/body/
// display_mesh. Returns false (with err set) if the candidate is invalid.
// Used by the Design tab to show a translucent ghost before Confirm.
bool preview(const CadFeature& candidate, TriangleMesh& out_mesh, std::string& err) const;
// Same, but also returns the per-body meshes (in `bodies` order; the candidate may append
// one), so the GUI can apply its display-only per-body Move transforms to the ghost and keep
// it overlaid on the moved body instead of floating back at the untransformed origin.
bool preview(const CadFeature& candidate, TriangleMesh& out_mesh,
std::vector<TriangleMesh>& out_body_meshes, std::string& err) const;
private:
TopoDS_Wire build_sketch_wire(const CadFeature& sketch) const;
// Apply a single feature to (result, have_body), throwing std::runtime_error on
// failure. `context` is the body whose faces/edges the feature reads (face-extrude
// source, up-to-face target, dress-up, hole) — it differs from `result` only when the
// feature builds a NEW body from an existing one (face-extrude New). Shared by route.
void apply_feature(TopoDS_Shape& result, bool& have_body,
const TopoDS_Shape& context, const CadFeature& f) const;
// Route one feature into the bodies list: resolve its target body, decide whether it
// starts a new body (empty list, or an Extrude with mode New) vs mutates an existing
// one, then apply_feature. Shared by recompute() (replay all) and preview() (candidate).
void route_feature(std::vector<CadBody>& bodies, const CadFeature& f) const;
// Boolean between two existing bodies: resolve target + tool, optionally snap the tool so
// the picked faces mate, run the OCCT op (with fuzzy tolerance), write the result back to the
// target and erase the consumed tool. Mutates the bodies vector directly (unlike apply_feature,
// which works on a single result shape). Throws std::runtime_error on a failed op.
void apply_boolean(std::vector<CadBody>& bodies, const CadFeature& f) const;
void apply_cut(std::vector<CadBody>& bodies, const CadFeature& f) const;
// Undo/redo stacks of feature-list snapshots. checkpoint() pushes onto m_undo and
// clears m_redo; undo()/redo() shuffle the current state between them. Capped so a
// long session can't grow unbounded.
std::vector<std::vector<CadFeature>> m_undo;
std::vector<std::vector<CadFeature>> m_redo;
static constexpr size_t k_undo_cap = 200;
};
} // namespace Slic3r
#endif // slic3r_CadDocument_hpp_
+442
View File
@@ -0,0 +1,442 @@
#include "GeometryEngine.hpp"
#include <BRepMesh_IncrementalMesh.hxx>
#include <BRep_Tool.hxx>
#include <BRepAdaptor_Surface.hxx>
#include <BRepLProp_SLProps.hxx>
#include <gp_Cylinder.hxx>
#include <BRepFilletAPI_MakeFillet.hxx>
#include <BRepFilletAPI_MakeChamfer.hxx>
#include <stdexcept>
#include <TopExp_Explorer.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Face.hxx>
#include <TopoDS_Edge.hxx>
#include <TopExp.hxx>
#include <TopTools.hxx>
#include <TopTools_IndexedMapOfShape.hxx>
#include <Poly_Triangulation.hxx>
#include <gp_Ax2.hxx>
#include <gp_Dir.hxx>
#include <gp_Pnt.hxx>
#include <BRepGProp.hxx>
#include <GProp_GProps.hxx>
#include <GeomLProp_SLProps.hxx>
#include <BRepAdaptor_Curve.hxx>
#include <GCPnts_TangentialDeflection.hxx>
#include <STEPControl_Reader.hxx>
#include <IFSelect_ReturnStatus.hxx>
#include <Standard_Failure.hxx>
namespace Slic3r {
// ---- STEP import (B-rep, not mesh) ----
std::vector<TopoDS_Shape> GeometryEngine::read_step_solids(const std::string& path, std::string& err)
{
err.clear();
std::vector<TopoDS_Shape> out;
try {
STEPControl_Reader reader;
if (reader.ReadFile(path.c_str()) != IFSelect_RetDone) {
err = "cannot read STEP file";
return out;
}
reader.TransferRoots();
const TopoDS_Shape shape = reader.OneShape();
if (shape.IsNull()) { err = "STEP file has no geometry"; return out; }
// One body per top-level solid; fall back to the whole shape (shells/faces) if none.
for (TopExp_Explorer ex(shape, TopAbs_SOLID); ex.More(); ex.Next())
out.push_back(ex.Current());
if (out.empty())
out.push_back(shape);
} catch (const Standard_Failure& e) {
err = e.GetMessageString() ? e.GetMessageString() : "OCCT failed to read STEP";
out.clear();
}
return out;
}
// ---- Primitive creation ----
TopoDS_Solid GeometryEngine::make_primitive(const PrimitiveParams& params)
{
switch (params.type) {
case PrimitiveType::Box:
return BRepPrimAPI_MakeBox(gp_Pnt(-params.box_w/2, -params.box_d/2, 0),
params.box_w, params.box_d, params.box_h).Solid();
case PrimitiveType::Cylinder:
return BRepPrimAPI_MakeCylinder(gp_Ax2(gp_Pnt(0,0,0), gp_Dir(0,0,1)),
params.cyl_radius, params.cyl_height).Solid();
case PrimitiveType::Sphere:
return BRepPrimAPI_MakeSphere(gp_Pnt(0,0,params.sph_radius), params.sph_radius).Solid();
case PrimitiveType::Cone:
return BRepPrimAPI_MakeCone(gp_Ax2(gp_Pnt(0,0,0), gp_Dir(0,0,1)),
params.cone_r1, params.cone_r2, params.cone_height).Solid();
case PrimitiveType::Torus:
return BRepPrimAPI_MakeTorus(gp_Ax2(gp_Pnt(0,0,params.torus_r2), gp_Dir(0,0,1)),
params.torus_r1, params.torus_r2).Solid();
default:
return BRepPrimAPI_MakeBox(gp_Pnt(-10,-10,0), 20,20,20).Solid();
}
}
// ---- Face classification ----
FaceGroup GeometryEngine::classify_face(const TopoDS_Face& face, const TopoDS_Shape& /*solid*/)
{
try {
BRepAdaptor_Surface surf(face);
if (surf.GetType() == GeomAbs_Plane) {
// Sample normal at center UV
double u = (surf.FirstUParameter() + surf.LastUParameter()) / 2.0;
double v = (surf.FirstVParameter() + surf.LastVParameter()) / 2.0;
gp_Pnt pt; gp_Vec du, dv;
surf.D1(u, v, pt, du, dv);
gp_Dir n = du.Crossed(dv);
if (face.Orientation() == TopAbs_REVERSED) n.Reverse();
if (n.Z() > 0.7) return FaceGroup::Top;
if (n.Z() < -0.7) return FaceGroup::Bottom;
return FaceGroup::Lateral;
}
} catch (...) {}
return FaceGroup::Lateral;
}
// ---- Edge collection ----
std::vector<TopoDS_Edge> GeometryEngine::collect_edges(const TopoDS_Shape& solid, FaceGroup target)
{
std::vector<TopoDS_Edge> result;
if (target == FaceGroup::All) {
for (TopExp_Explorer exp(solid, TopAbs_EDGE); exp.More(); exp.Next())
result.push_back(TopoDS::Edge(exp.Current()));
return result;
}
// Build edge-to-face map once
TopTools_IndexedDataMapOfShapeListOfShape edgeFaceMap;
TopExp::MapShapesAndAncestors(solid, TopAbs_EDGE, TopAbs_FACE, edgeFaceMap);
for (TopExp_Explorer edgeExp(solid, TopAbs_EDGE); edgeExp.More(); edgeExp.Next()) {
const TopoDS_Edge& edge = TopoDS::Edge(edgeExp.Current());
if (!edgeFaceMap.Contains(edge)) continue;
const TopTools_ListOfShape& faces = edgeFaceMap.FindFromKey(edge);
bool include = false;
for (auto it = faces.begin(); it != faces.end(); ++it) {
FaceGroup fg = classify_face(TopoDS::Face(*it), solid);
if (target == FaceGroup::Top && fg == FaceGroup::Top) { include = true; break; }
if (target == FaceGroup::Bottom && fg == FaceGroup::Bottom) { include = true; break; }
if (target == FaceGroup::Lateral && fg == FaceGroup::Lateral) { include = true; break; }
}
if (!include && target == FaceGroup::Top) {
for (auto it = faces.begin(); it != faces.end(); ++it) {
if (classify_face(TopoDS::Face(*it), solid) == FaceGroup::Top) { include = true; break; }
}
}
if (!include && target == FaceGroup::Bottom) {
for (auto it = faces.begin(); it != faces.end(); ++it) {
if (classify_face(TopoDS::Face(*it), solid) == FaceGroup::Bottom) { include = true; break; }
}
}
if (target == FaceGroup::Lateral && !include) {
int lateralCount = 0;
for (auto it = faces.begin(); it != faces.end(); ++it) {
if (classify_face(TopoDS::Face(*it), solid) == FaceGroup::Lateral) ++lateralCount;
}
if (lateralCount >= 2) include = true;
}
if (include) result.push_back(edge);
}
return result;
}
// ---- Fillet/Chamfer ----
TopoDS_Shape GeometryEngine::apply_fillet(const TopoDS_Shape& solid, double radius, FaceGroup faces)
{
if (radius <= 0.001) return solid;
std::vector<TopoDS_Edge> edges = collect_edges(solid, faces);
if (edges.empty()) return solid;
BRepFilletAPI_MakeFillet fillet(solid);
for (const auto& edge : edges)
fillet.Add(radius, edge);
fillet.Build();
// A too-large radius (e.g. >= half the smallest spanned dimension) makes the
// operation degenerate; OCCT leaves IsDone() false. Report it instead of
// silently returning the unfilleted solid (which reads as a false success).
if (!fillet.IsDone()) throw std::runtime_error("fillet radius too large for this geometry");
return fillet.Shape();
}
TopoDS_Shape GeometryEngine::apply_chamfer(const TopoDS_Shape& solid, double distance, FaceGroup faces)
{
if (distance <= 0.001) return solid;
std::vector<TopoDS_Edge> edges = collect_edges(solid, faces);
if (edges.empty()) return solid;
BRepFilletAPI_MakeChamfer chamfer(solid);
for (const auto& edge : edges)
chamfer.Add(distance, edge); // symmetric chamfer
chamfer.Build();
if (!chamfer.IsDone()) throw std::runtime_error("chamfer distance too large for this geometry");
return chamfer.Shape();
}
TopoDS_Shape GeometryEngine::apply_fillet(const TopoDS_Shape& solid, double radius, int edge_id)
{
if (radius <= 0.001) return solid;
TopoDS_Edge edge = edge_by_index(solid, edge_id);
if (edge.IsNull()) throw std::runtime_error("apply_fillet: invalid edge id");
BRepFilletAPI_MakeFillet mk(solid);
mk.Add(radius, edge);
mk.Build();
if (!mk.IsDone()) throw std::runtime_error("apply_fillet: OCCT fillet failed");
return mk.Shape();
}
TopoDS_Shape GeometryEngine::apply_chamfer(const TopoDS_Shape& solid, double distance, int edge_id)
{
if (distance <= 0.001) return solid;
TopoDS_Edge edge = edge_by_index(solid, edge_id);
if (edge.IsNull()) throw std::runtime_error("apply_chamfer: invalid edge id");
BRepFilletAPI_MakeChamfer mk(solid);
mk.Add(distance, edge);
mk.Build();
if (!mk.IsDone()) throw std::runtime_error("apply_chamfer: OCCT chamfer failed");
return mk.Shape();
}
// ---- Tessellation ----
TriangleMesh GeometryEngine::tessellate(const TopoDS_Shape& shape,
double linear_deflection,
double angular_deflection)
{
BRepMesh_IncrementalMesh mesh(shape, linear_deflection, false, angular_deflection, true);
int nbNodes = 0, nbTri = 0;
for (TopExp_Explorer exp(shape, TopAbs_FACE); exp.More(); exp.Next()) {
TopLoc_Location loc;
Handle(Poly_Triangulation) tri = BRep_Tool::Triangulation(TopoDS::Face(exp.Current()), loc);
if (!tri.IsNull()) { nbNodes += tri->NbNodes(); nbTri += tri->NbTriangles(); }
}
if (nbTri == 0 || nbNodes == 0) return TriangleMesh{};
stl_file stl;
stl.stats.type = inmemory;
stl.stats.number_of_facets = (uint32_t)nbTri;
stl.stats.original_num_facets = stl.stats.number_of_facets;
stl_allocate(&stl);
std::vector<Vec3f> pts; pts.reserve(nbNodes);
int ndOff = 0, trOff = 0;
for (TopExp_Explorer exp(shape, TopAbs_FACE); exp.More(); exp.Next()) {
const TopoDS_Shape& F = exp.Current();
TopLoc_Location loc;
Handle(Poly_Triangulation) tri = BRep_Tool::Triangulation(TopoDS::Face(F), loc);
if (tri.IsNull()) continue;
gp_Trsf T = loc.Transformation();
for (int i = 1; i <= tri->NbNodes(); ++i) {
gp_Pnt p = tri->Node(i); p.Transform(T);
pts.emplace_back(Vec3f(p.X(), p.Y(), p.Z()));
}
auto orient = exp.Current().Orientation();
int ids[3];
for (int i = 1; i <= tri->NbTriangles(); ++i) {
Poly_Triangle t = tri->Triangle(i); t.Get(ids[0], ids[1], ids[2]);
if (orient == TopAbs_REVERSED) std::swap(ids[1], ids[2]);
stl_facet f;
f.vertex[0] = pts[ids[0]+ndOff-1].cast<float>();
f.vertex[1] = pts[ids[1]+ndOff-1].cast<float>();
f.vertex[2] = pts[ids[2]+ndOff-1].cast<float>();
f.extra[0]=0; f.extra[1]=0;
stl_normal n; stl_calculate_normal(n,&f); stl_normalize_vector(n);
f.normal=n; stl.facet_start[trOff+i-1]=f;
}
ndOff += tri->NbNodes(); trOff += tri->NbTriangles();
}
TriangleMesh result; result.from_stl(stl); return result;
}
std::string GeometryEngine::primitive_name(PrimitiveType type)
{
switch (type) {
case PrimitiveType::Box: return "Box";
case PrimitiveType::Cylinder: return "Cylinder";
case PrimitiveType::Sphere: return "Sphere";
case PrimitiveType::Cone: return "Cone";
case PrimitiveType::Torus: return "Torus";
default: return "Unknown";
}
}
// ---- Topology accessors ----
int GeometryEngine::face_count(const TopoDS_Shape& shape)
{
int n = 0;
for (TopExp_Explorer e(shape, TopAbs_FACE); e.More(); e.Next())
++n;
return n;
}
TopoDS_Face GeometryEngine::face_by_index(const TopoDS_Shape& shape, int index)
{
if (index < 0) return TopoDS_Face();
int ordinal = 0;
for (TopExp_Explorer e(shape, TopAbs_FACE); e.More(); e.Next()) {
if (ordinal == index)
return TopoDS::Face(e.Current());
++ordinal;
}
return TopoDS_Face();
}
std::vector<TopoDS_Edge> GeometryEngine::edges_of_face(const TopoDS_Face& face)
{
std::vector<TopoDS_Edge> result;
TopTools_IndexedMapOfShape map;
TopExp::MapShapes(face, TopAbs_EDGE, map);
for (int i = 1; i <= map.Extent(); ++i)
result.push_back(TopoDS::Edge(map(i)));
return result;
}
std::vector<Vec3d> GeometryEngine::sample_edge_world(const TopoDS_Edge& edge, double chord_tol)
{
if (BRep_Tool::Degenerated(edge))
return {};
BRepAdaptor_Curve curve(edge);
GCPnts_TangentialDeflection disc(curve, 0.1, chord_tol);
std::vector<Vec3d> pts;
if (disc.NbPoints() >= 2) {
for (int i = 1; i <= disc.NbPoints(); ++i) {
gp_Pnt p = disc.Value(i);
pts.emplace_back(p.X(), p.Y(), p.Z());
}
} else {
gp_Pnt p0 = curve.Value(curve.FirstParameter());
gp_Pnt p1 = curve.Value(curve.LastParameter());
pts.emplace_back(p0.X(), p0.Y(), p0.Z());
pts.emplace_back(p1.X(), p1.Y(), p1.Z());
}
return pts;
}
Vec3d GeometryEngine::face_centroid_world(const TopoDS_Face& face)
{
GProp_GProps props;
BRepGProp::SurfaceProperties(face, props);
gp_Pnt c = props.CentreOfMass();
return Vec3d(c.X(), c.Y(), c.Z());
}
Vec3d GeometryEngine::face_normal_world(const TopoDS_Face& face)
{
BRepAdaptor_Surface surf(face);
const double u = 0.5 * (surf.FirstUParameter() + surf.LastUParameter());
const double v = 0.5 * (surf.FirstVParameter() + surf.LastVParameter());
BRepLProp_SLProps props(surf, u, v, 1, 1e-6);
gp_Dir n(0.0, 0.0, 1.0);
if (props.IsNormalDefined()) n = props.Normal();
if (face.Orientation() == TopAbs_REVERSED) n.Reverse(); // outward (account for face winding)
return Vec3d(n.X(), n.Y(), n.Z());
}
GeometryEngine::CylinderFace GeometryEngine::cylinder_of_face(const TopoDS_Face& face)
{
CylinderFace cf;
if (face.IsNull()) return cf;
BRepAdaptor_Surface surf(face);
if (surf.GetType() != GeomAbs_Cylinder) return cf;
const gp_Cylinder cyl = surf.Cylinder();
const gp_Ax1 ax = cyl.Axis();
const Vec3d axis(ax.Direction().X(), ax.Direction().Y(), ax.Direction().Z());
const Vec3d apt (ax.Location().X(), ax.Location().Y(), ax.Location().Z());
cf.radius = cyl.Radius();
// Axial extent: V is the axial parameter on a cylinder; bound the face's two ends and
// order them so `axis` points base -> top.
const double umid = 0.5 * (surf.FirstUParameter() + surf.LastUParameter());
const gp_Pnt e0 = surf.Value(umid, surf.FirstVParameter());
const gp_Pnt e1 = surf.Value(umid, surf.LastVParameter());
double t0 = (Vec3d(e0.X(), e0.Y(), e0.Z()) - apt).dot(axis);
double t1 = (Vec3d(e1.X(), e1.Y(), e1.Z()) - apt).dot(axis);
if (t1 < t0) std::swap(t0, t1);
cf.base = apt + axis * t0;
cf.axis = axis;
cf.height = t1 - t0;
// Internal (bore) vs external: compare the face's outward normal at its centre to the
// outward radial direction. A bore's normal points toward the axis (dot < 0).
const gp_Pnt sp = surf.Value(umid, 0.5 * (surf.FirstVParameter() + surf.LastVParameter()));
const Vec3d S(sp.X(), sp.Y(), sp.Z());
const Vec3d axpt = cf.base + axis * (S - cf.base).dot(axis);
const Vec3d radial = (S - axpt).normalized();
cf.internal = face_normal_world(face).dot(radial) < 0.0;
cf.ok = true;
return cf;
}
bool GeometryEngine::face_plane_bounds(const TopoDS_Face& face, const Vec3d& origin,
const Vec3d& x_axis, const Vec3d& y_axis,
double& umin, double& umax, double& vmin, double& vmax)
{
umin = vmin = 1e30; umax = vmax = -1e30;
bool any = false;
for (TopExp_Explorer ex(face, TopAbs_VERTEX); ex.More(); ex.Next()) {
const gp_Pnt p = BRep_Tool::Pnt(TopoDS::Vertex(ex.Current()));
const Vec3d P(p.X(), p.Y(), p.Z());
const double u = (P - origin).dot(x_axis);
const double v = (P - origin).dot(y_axis);
umin = std::min(umin, u); umax = std::max(umax, u);
vmin = std::min(vmin, v); vmax = std::max(vmax, v);
any = true;
}
return any;
}
int GeometryEngine::edge_count(const TopoDS_Shape& shape)
{
TopTools_IndexedMapOfShape map;
TopExp::MapShapes(shape, TopAbs_EDGE, map);
return map.Extent();
}
TopoDS_Edge GeometryEngine::edge_by_index(const TopoDS_Shape& shape, int index)
{
TopTools_IndexedMapOfShape map;
TopExp::MapShapes(shape, TopAbs_EDGE, map);
if (index < 0 || index >= map.Extent())
return TopoDS_Edge();
return TopoDS::Edge(map(index + 1));
}
int GeometryEngine::edge_index_of(const TopoDS_Shape& shape, const TopoDS_Edge& edge)
{
TopTools_IndexedMapOfShape map;
TopExp::MapShapes(shape, TopAbs_EDGE, map);
int idx = map.FindIndex(edge);
return (idx > 0) ? (idx - 1) : -1;
}
} // namespace Slic3r
+121
View File
@@ -0,0 +1,121 @@
#ifndef slic3r_GeometryEngine_hpp_
#define slic3r_GeometryEngine_hpp_
#include "TriangleMesh.hpp"
#include <BRepPrimAPI_MakeBox.hxx>
#include <BRepPrimAPI_MakeCylinder.hxx>
#include <BRepPrimAPI_MakeSphere.hxx>
#include <BRepPrimAPI_MakeCone.hxx>
#include <BRepPrimAPI_MakeTorus.hxx>
#include <gp_Ax2.hxx>
#include <TopoDS_Solid.hxx>
#include <TopoDS_Face.hxx>
#include <TopoDS_Edge.hxx>
#include <vector>
#include <string>
namespace Slic3r {
enum class PrimitiveType { Box, Cylinder, Sphere, Cone, Torus, COUNT };
enum class DressUpType { Fillet, Chamfer };
enum class FaceGroup { Top, Bottom, Lateral, All };
struct PrimitiveParams {
PrimitiveType type{PrimitiveType::Box};
double box_w{20}, box_h{20}, box_d{20};
double cyl_radius{10}, cyl_height{20};
double sph_radius{10};
double cone_r1{10}, cone_r2{5}, cone_height{20};
double torus_r1{10}, torus_r2{3};
// Dress-up
bool dressup_enabled{false};
DressUpType dressup_type{DressUpType::Fillet};
FaceGroup dressup_faces{FaceGroup::All};
double dressup_radius{1.0}; // fillet radius
double dressup_chamfer_dist{1.0}; // chamfer distance (symmetric)
// Mesh quality
double linear_deflection{0.01};
double angular_deflection{0.5};
template<class Archive>
void serialize(Archive& ar) {
ar(type, box_w, box_h, box_d, cyl_radius, cyl_height, sph_radius,
cone_r1, cone_r2, cone_height, torus_r1, torus_r2,
dressup_enabled, dressup_type, dressup_faces, dressup_radius, dressup_chamfer_dist,
linear_deflection, angular_deflection);
}
};
class GeometryEngine
{
public:
static TopoDS_Solid make_primitive(const PrimitiveParams& params);
// Read a STEP file into its top-level solids (one TopoDS_Shape per solid; falls back to
// the whole shape if it contains no closed solids). Reuses OCCT's STEPControl_Reader,
// already linked via Format/STEP.cpp — no new dependency. err is set on failure (empty result).
static std::vector<TopoDS_Shape> read_step_solids(const std::string& path, std::string& err);
static TopoDS_Shape apply_fillet(const TopoDS_Shape& solid, double radius,
FaceGroup faces = FaceGroup::All);
static TopoDS_Shape apply_fillet(const TopoDS_Shape& solid, double radius,
int edge_id);
static TopoDS_Shape apply_chamfer(const TopoDS_Shape& solid, double distance,
FaceGroup faces = FaceGroup::All);
static TopoDS_Shape apply_chamfer(const TopoDS_Shape& solid, double distance,
int edge_id);
static TriangleMesh tessellate(const TopoDS_Shape& shape,
double linear_deflection = 0.01,
double angular_deflection = 0.5);
static std::string primitive_name(PrimitiveType type);
// Topology accessors for in-viewport face/edge picking (Design tab). Face index is the
// TopExp_Explorer(shape, TopAbs_FACE) ordinal — identical to SketchEngine::tessellate's
// per-triangle face id, so a picked triangle's id maps back to a face here.
static TopoDS_Face face_by_index(const TopoDS_Shape& shape, int index); // null if out of range
static int face_count(const TopoDS_Shape& shape);
static std::vector<TopoDS_Edge> edges_of_face(const TopoDS_Face& face);
// Centre of mass (world) of a face — used to compute the extrude length for "up to face".
static Vec3d face_centroid_world(const TopoDS_Face& face);
// Outward unit normal of a face at its UV midpoint (orientation-aware) — for the shell gizmo.
static Vec3d face_normal_world(const TopoDS_Face& face);
// Sample an edge into a world-space polyline (>=2 pts) for pick-distance + highlight.
static std::vector<Vec3d> sample_edge_world(const TopoDS_Edge& edge, double chord_tol = 0.05);
// 0-based edge index into TopExp::MapShapes(shape, TopAbs_EDGE, map).
static int edge_count(const TopoDS_Shape& shape);
static TopoDS_Edge edge_by_index(const TopoDS_Shape& shape, int index);
static int edge_index_of(const TopoDS_Shape& shape, const TopoDS_Edge& edge);
// Analysis of a cylindrical face for the Thread tool (a hole bore or a cylinder's lateral
// surface): axis (base at the lower axial end + unit direction), radius, axial extent, and
// whether it is a bore (face normal points toward the axis = internal thread). ok=false if
// the face is not a cylinder.
struct CylinderFace {
bool ok{false};
Vec3d base{0, 0, 0};
Vec3d axis{0, 0, 1};
double radius{0};
double height{0};
bool internal{false};
};
static CylinderFace cylinder_of_face(const TopoDS_Face& face);
// Plane-coordinate (u,v) bounding box of a face's vertices, measured from `origin` along
// `x_axis`/`y_axis`. Lets the Hole tool dimension the hole from the face SIDES (umin/vmin =
// two adjacent edges) instead of from the centre. Returns false if the face has no vertices.
static bool face_plane_bounds(const TopoDS_Face& face, const Vec3d& origin,
const Vec3d& x_axis, const Vec3d& y_axis,
double& umin, double& umax, double& vmin, double& vmax);
private:
static std::vector<TopoDS_Edge> collect_edges(const TopoDS_Shape& solid, FaceGroup faces);
static FaceGroup classify_face(const TopoDS_Face& face, const TopoDS_Shape& solid);
};
} // namespace Slic3r
#endif // slic3r_GeometryEngine_hpp_
+307
View File
@@ -0,0 +1,307 @@
#include "SketchConstraints.hpp"
#include <Eigen/Dense>
#include <cmath>
namespace Slic3r {
int SketchConstraints::add_point(double x, double y)
{
m_vars.push_back(x);
m_vars.push_back(y);
return static_cast<int>(m_vars.size() / 2) - 1;
}
void SketchConstraints::set_point(int id, double x, double y)
{
size_t idx = 2 * id;
m_vars[idx] = x;
m_vars[idx + 1] = y;
}
Vec2d SketchConstraints::get_point(int id) const
{
size_t idx = 2 * id;
return Vec2d(m_vars[idx], m_vars[idx + 1]);
}
int SketchConstraints::point_count() const
{
return static_cast<int>(m_vars.size() / 2);
}
void SketchConstraints::fix_point(int id)
{
size_t idx = 2 * id;
Con c;
c.type = FIX_POINT;
c.a = id;
c.b = c.c = c.d = 0;
c.k0 = m_vars[idx];
c.k1 = m_vars[idx + 1];
m_cons.push_back(c);
}
void SketchConstraints::coincident(int a, int b)
{
Con c;
c.type = COINCIDENT;
c.a = a; c.b = b; c.c = c.d = 0;
c.k0 = c.k1 = 0;
m_cons.push_back(c);
}
void SketchConstraints::horizontal(int a, int b)
{
Con c;
c.type = HORIZONTAL;
c.a = a; c.b = b; c.c = c.d = 0;
c.k0 = c.k1 = 0;
m_cons.push_back(c);
}
void SketchConstraints::vertical(int a, int b)
{
Con c;
c.type = VERTICAL;
c.a = a; c.b = b; c.c = c.d = 0;
c.k0 = c.k1 = 0;
m_cons.push_back(c);
}
void SketchConstraints::distance(int a, int b, double d)
{
Con c;
c.type = DISTANCE;
c.a = a; c.b = b; c.c = c.d = 0;
c.k0 = d; c.k1 = 0;
m_cons.push_back(c);
}
void SketchConstraints::lock_x(int id, double x)
{
Con c;
c.type = LOCK_X;
c.a = id;
c.b = c.c = c.d = 0;
c.k0 = x; c.k1 = 0;
m_cons.push_back(c);
}
void SketchConstraints::lock_y(int id, double y)
{
Con c;
c.type = LOCK_Y;
c.a = id;
c.b = c.c = c.d = 0;
c.k0 = y; c.k1 = 0;
m_cons.push_back(c);
}
void SketchConstraints::equal_length(int a, int b, int c, int d)
{
Con con;
con.type = EQUAL_LENGTH;
con.a = a; con.b = b; con.c = c; con.d = d;
con.k0 = con.k1 = 0;
m_cons.push_back(con);
}
void SketchConstraints::parallel(int a, int b, int c, int d)
{
Con con;
con.type = PARALLEL;
con.a = a; con.b = b; con.c = c; con.d = d;
con.k0 = con.k1 = 0;
m_cons.push_back(con);
}
void SketchConstraints::perpendicular(int a, int b, int c, int d)
{
Con con;
con.type = PERPENDICULAR;
con.a = a; con.b = b; con.c = c; con.d = d;
con.k0 = con.k1 = 0;
m_cons.push_back(con);
}
void SketchConstraints::midpoint(int m, int a, int b)
{
Con con;
con.type = MIDPOINT;
con.a = m; con.b = a; con.c = b; con.d = -1;
con.k0 = con.k1 = 0;
m_cons.push_back(con);
}
void SketchConstraints::symmetric(int a, int b, int c, int d)
{
Con con;
con.type = SYMMETRIC;
con.a = a; con.b = b; con.c = c; con.d = d;
con.k0 = con.k1 = 0;
m_cons.push_back(con);
}
void SketchConstraints::angle(int a, int b, int c, int d, double radians)
{
Con con;
con.type = ANGLE;
con.a = a; con.b = b; con.c = c; con.d = d;
con.k0 = radians; con.k1 = 0;
m_cons.push_back(con);
}
void SketchConstraints::point_line_distance(int p, int a, int b, double dist)
{
Con con;
con.type = PT_LINE_DIST;
con.a = p; con.b = a; con.c = b; con.d = -1;
con.k0 = dist; con.k1 = 0;
m_cons.push_back(con);
}
Eigen::VectorXd SketchConstraints::residuals(const std::vector<double>& v) const
{
auto X = [&](int i) { return v[2 * i]; };
auto Y = [&](int i) { return v[2 * i + 1]; };
std::vector<double> res;
for (const auto& c : m_cons) {
switch (c.type) {
case FIX_POINT:
res.push_back(X(c.a) - c.k0);
res.push_back(Y(c.a) - c.k1);
break;
case COINCIDENT:
res.push_back(X(c.a) - X(c.b));
res.push_back(Y(c.a) - Y(c.b));
break;
case HORIZONTAL:
res.push_back(Y(c.a) - Y(c.b));
break;
case VERTICAL:
res.push_back(X(c.a) - X(c.b));
break;
case DISTANCE:
res.push_back(std::hypot(X(c.a) - X(c.b), Y(c.a) - Y(c.b)) - c.k0);
break;
case LOCK_X:
res.push_back(X(c.a) - c.k0);
break;
case LOCK_Y:
res.push_back(Y(c.a) - c.k0);
break;
case EQUAL_LENGTH:
res.push_back(std::hypot(X(c.a) - X(c.b), Y(c.a) - Y(c.b)) -
std::hypot(X(c.c) - X(c.d), Y(c.c) - Y(c.d)));
break;
case PARALLEL:
res.push_back((X(c.b) - X(c.a)) * (Y(c.d) - Y(c.c)) -
(Y(c.b) - Y(c.a)) * (X(c.d) - X(c.c)));
break;
case PERPENDICULAR:
res.push_back((X(c.b) - X(c.a)) * (X(c.d) - X(c.c)) +
(Y(c.b) - Y(c.a)) * (Y(c.d) - Y(c.c)));
break;
case MIDPOINT:
res.push_back(X(c.a) - 0.5 * (X(c.b) + X(c.c)));
res.push_back(Y(c.a) - 0.5 * (Y(c.b) + Y(c.c)));
break;
case SYMMETRIC: {
const double abx = X(c.b) - X(c.a), aby = Y(c.b) - Y(c.a);
const double cdx = X(c.d) - X(c.c), cdy = Y(c.d) - Y(c.c);
res.push_back(abx * cdx + aby * cdy);
const double mx = 0.5 * (X(c.a) + X(c.b));
const double my = 0.5 * (Y(c.a) + Y(c.b));
res.push_back((mx - X(c.c)) * cdy - (my - Y(c.c)) * cdx);
break;
}
case ANGLE: {
const double ux = X(c.b) - X(c.a), uy = Y(c.b) - Y(c.a);
const double wx = X(c.d) - X(c.c), wy = Y(c.d) - Y(c.c);
const double cross = ux * wy - uy * wx;
const double dot = ux * wx + uy * wy;
res.push_back(std::atan2(cross, dot) - c.k0);
break;
}
case PT_LINE_DIST: {
const double bx = X(c.b), by = Y(c.b);
const double cx = X(c.c), cy = Y(c.c);
const double L = std::hypot(cx - bx, cy - by);
const double num = (X(c.a) - bx) * (cy - by) - (Y(c.a) - by) * (cx - bx);
res.push_back((L > 1e-12 ? std::abs(num) / L : 0.0) - c.k0);
break;
}
}
}
Eigen::VectorXd r(static_cast<Eigen::Index>(res.size()));
for (size_t i = 0; i < res.size(); ++i)
r(static_cast<Eigen::Index>(i)) = res[i];
return r;
}
Eigen::MatrixXd SketchConstraints::jacobian(const std::vector<double>& v) const
{
int m = static_cast<int>(residuals(v).size());
int n = static_cast<int>(v.size());
Eigen::MatrixXd J(m, n);
const double eps = 1e-7;
std::vector<double> vp = v;
std::vector<double> vm = v;
for (int j = 0; j < n; ++j) {
vp[j] = v[j] + eps;
vm[j] = v[j] - eps;
Eigen::VectorXd rp = residuals(vp);
Eigen::VectorXd rm = residuals(vm);
vp[j] = v[j];
vm[j] = v[j];
J.col(j) = (rp - rm) / (2.0 * eps);
}
return J;
}
bool SketchConstraints::solve(int max_iter, double tol)
{
if (m_cons.empty()) return true;
double lambda = 1e-3;
Eigen::VectorXd r = residuals(m_vars);
for (int it = 0; it < max_iter; ++it) {
double rn = r.norm();
if (rn < tol) return true;
Eigen::MatrixXd J = jacobian(m_vars);
Eigen::MatrixXd A = J.transpose() * J;
Eigen::VectorXd g = J.transpose() * r;
bool stepped = false;
for (int t = 0; t < 12; ++t) {
Eigen::MatrixXd Ad = A;
for (int i = 0; i < Ad.rows(); ++i)
Ad(i, i) += lambda * (1.0 + Ad(i, i));
Eigen::VectorXd dx = Ad.ldlt().solve(-g);
std::vector<double> cand = m_vars;
for (size_t i = 0; i < cand.size(); ++i)
cand[i] += dx[static_cast<Eigen::Index>(i)];
Eigen::VectorXd rc = residuals(cand);
if (rc.norm() < rn) {
m_vars = cand;
r = rc;
lambda = std::max(lambda * 0.4, 1e-12);
stepped = true;
break;
}
lambda *= 3.0;
}
if (!stepped) break;
}
return r.norm() < tol * 100;
}
double SketchConstraints::residual_norm() const
{
return residuals(m_vars).norm();
}
} // namespace Slic3r
+68
View File
@@ -0,0 +1,68 @@
#ifndef slic3r_SketchConstraints_hpp_
#define slic3r_SketchConstraints_hpp_
#include "libslic3r/Point.hpp"
#include <vector>
#include <Eigen/Dense>
namespace Slic3r {
class SketchConstraints {
public:
int add_point(double x, double y);
void set_point(int id, double x, double y);
Vec2d get_point(int id) const;
int point_count() const;
void fix_point(int id);
void coincident(int a, int b);
void horizontal(int a, int b);
void vertical(int a, int b);
void distance(int a, int b, double d);
void lock_x(int id, double x);
void lock_y(int id, double y);
void equal_length(int a, int b, int c, int d);
void parallel(int a, int b, int c, int d);
void perpendicular(int a, int b, int c, int d);
void midpoint(int m, int a, int b);
void symmetric(int a, int b, int c, int d);
void angle(int a, int b, int c, int d, double radians);
void point_line_distance(int p, int a, int b, double dist);
bool solve(int max_iter = 200, double tol = 1e-10);
double residual_norm() const;
private:
std::vector<double> m_vars;
enum ConType : int {
FIX_POINT = 0,
COINCIDENT,
HORIZONTAL,
VERTICAL,
DISTANCE,
LOCK_X,
LOCK_Y,
EQUAL_LENGTH,
PARALLEL,
PERPENDICULAR,
MIDPOINT,
SYMMETRIC,
ANGLE,
PT_LINE_DIST
};
struct Con {
int type;
int a, b, c, d;
double k0, k1;
};
std::vector<Con> m_cons;
Eigen::VectorXd residuals(const std::vector<double>& v) const;
Eigen::MatrixXd jacobian(const std::vector<double>& v) const;
};
} // namespace Slic3r
#endif // slic3r_SketchConstraints_hpp_
File diff suppressed because it is too large Load Diff
+257
View File
@@ -0,0 +1,257 @@
#ifndef slic3r_SketchEngine_hpp_
#define slic3r_SketchEngine_hpp_
#include "TriangleMesh.hpp"
#include "libslic3r/Point.hpp"
#include "GeometryEngine.hpp"
#include <gp_Pln.hxx>
#include <gp_Ax3.hxx>
#include <TopoDS_Wire.hxx>
#include <TopoDS_Shape.hxx>
#include <TopoDS_Face.hxx>
#include <vector>
namespace Slic3r {
struct SketchSegment {
enum Type { Line, Arc, Circle, Rectangle, Polygon };
Type type{Line};
Vec2d p0{0,0}, p1{0,0};
Vec2d center{0,0};
double radius{0}, start_angle{0}, end_angle{0};
std::vector<Vec2d> points;
template<class Archive>
void serialize(Archive& ar) { ar(type, p0, p1, center, radius, start_angle, end_angle, points); }
};
struct SketchEntity {
enum class Type { Line, Arc, Circle, Point, Ellipse, EllipseArc, BSpline };
Type type{Type::Line};
Vec2d p0{0,0}; // Line: start; Arc/EllipseArc: start; Circle/Point/Ellipse: center; BSpline: first pole
Vec2d p1{0,0}; // Line: end; Arc/EllipseArc: end; (unused for Circle/Point/Ellipse); BSpline: last pole
Vec2d center{0,0}; // Arc/Circle/Ellipse(Arc) center
double radius{0}; // Circle/Arc radius; Ellipse(Arc): semi-major axis (a)
double start_angle{0}; // Arc sweep start; Ellipse(Arc): parametric start angle (radians)
double end_angle{0}; // Arc sweep end; Ellipse(Arc): parametric end angle
bool construction{false};
double rminor{0}; // Ellipse(Arc): semi-minor axis (b)
double rotation{0}; // Ellipse(Arc): major-axis angle phi (radians, about center)
std::vector<Vec2d> ctrl; // BSpline: control points (poles); p0/p1 mirror first/last pole
template<class Archive>
void serialize(Archive& ar) {
// Append-only: rminor/rotation added for Ellipse(Arc) (P2 Tier-B.1); ctrl for BSpline (B.2).
ar(type, p0, p1, center, radius, start_angle, end_angle, construction, rminor, rotation, ctrl);
}
};
struct SketchPlane {
Vec3d origin{0,0,0};
Vec3d normal{0,0,1};
Vec3d x_axis{1,0,0};
Vec3d y_axis{0,1,0};
gp_Pln to_occt() const;
static SketchPlane from_face(const TopoDS_Face& face);
static SketchPlane XY() { return {}; }
static SketchPlane XZ() { return {{0,0,0}, {0,1,0}, {1,0,0}, {0,0,1}}; }
static SketchPlane YZ() { return {{0,0,0}, {1,0,0}, {0,1,0}, {0,0,1}}; }
Vec2d project(const Vec3d& ray_origin, const Vec3d& ray_dir) const;
Vec3d to_world(const Vec2d& pt) const;
template<class Archive>
void serialize(Archive& ar) { ar(origin, normal, x_axis, y_axis); }
};
struct SketchProfile {
std::vector<Vec2d> points;
bool closed{false};
bool is_closed(double tolerance = 0.5) const;
bool try_close(double tolerance = 0.5);
void clear() { points.clear(); closed = false; }
TopoDS_Wire to_occt_wire(const SketchPlane& plane) const;
template<class Archive>
void serialize(Archive& ar) { ar(points, closed); }
};
enum class SketchConstraintType {
Fix, Coincident, Horizontal, Vertical, Distance,
LockX, LockY, EqualLength, Parallel, Perpendicular,
Concentric,
Tangent, Midpoint, Symmetric, Angle,
Radius, Diameter,
PointOnLine, // a point lies on a line (or at signed perpendicular distance `value`)
PointOnObject // a point lies on an entity edge (line -> PT_ON_LINE, circle -> PT_ON_CIRCLE)
};
// Constraint on a SketchProfile, referencing profile point indices (a,b,c,d).
// `value` carries the target for Distance/LockX/LockY (ignored otherwise).
struct SketchConstraintDef {
SketchConstraintType type{SketchConstraintType::Coincident};
int a{-1}, b{-1}, c{-1}, d{-1};
double value{0.0};
template<class Archive> void serialize(Archive& ar) { ar(type, a, b, c, d, value); }
};
// Which point of an entity a constraint reference names.
// P0 = SketchEntity::p0 (Line start / Point position)
// P1 = SketchEntity::p1 (Line end)
// Center = SketchEntity::center (Arc/Circle center)
enum class SketchPointRole { P0, P1, Center };
// Constraint on coexisting SketchEntity objects (Fase 4.2). Each reference is an
// (entity index, point role) pair. Point-form constraints
// (Fix/Coincident/Horizontal/Vertical/Distance/LockX/LockY) use refs A and B as
// individual points. Segment-form constraints (Parallel/Perpendicular/EqualLength)
// use entity indices `ea`/`eb` as whole line segments (their P0->P1); roles are
// ignored for those. `value` carries the target for Distance/LockX/LockY.
struct SketchEntityConstraintDef {
SketchConstraintType type{SketchConstraintType::Coincident};
int ea{-1}, eb{-1}; // entity indices
SketchPointRole ra{SketchPointRole::P0}; // role within ea
SketchPointRole rb{SketchPointRole::P0}; // role within eb
double value{0.0};
int ec{-1}; // third entity ref (Symmetric axis)
SketchPointRole rc{SketchPointRole::P0}; // role within ec
template<class Archive> void serialize(Archive& ar) { ar(type, ea, eb, ra, rb, value, ec, rc); }
};
// Solve a bare entity list in place against entity-form constraints. Shared by
// CadDocument::solve_sketch_feature (committed features) and the in-session GUI
// sketch tool (live solving as dimensions/constraints are added). Returns true on
// convergence; an empty constraint list is a no-op that returns true.
bool solve_sketch_entities(std::vector<SketchEntity>& entities,
const std::vector<SketchEntityConstraintDef>& constraints);
struct SketchParams {
// Extrude/Revolve
double extrude_len{10}; bool extrude_sym{false}; double extrude_taper{0};
double revolve_deg{360};
bool is_pocket{false}; // cut into selected object instead of new
// Dress-up
bool dressup_enabled{false};
DressUpType dressup_type{DressUpType::Fillet};
FaceGroup dressup_faces{FaceGroup::All};
double dressup_radius{1.0};
double dressup_chamfer_dist{1.0};
// Mesh
double linear_deflection{0.01};
template<class Archive>
void serialize(Archive& ar) {
ar(extrude_len, extrude_sym, extrude_taper, revolve_deg, is_pocket,
dressup_enabled, dressup_type, dressup_faces, dressup_radius, dressup_chamfer_dist,
linear_deflection);
}
};
class SketchEngine
{
public:
static TopoDS_Shape make_extrude(const TopoDS_Wire& wire, const SketchPlane& plane,
double length, bool symmetric = false, double taper_deg = 0.0);
// Asymmetric two-sided prism: extrude the wire's face by `up` along +normal and `down`
// along -normal, fused into one solid. up/down are non-negative magnitudes.
// Tapered (draft) extrude of a planar wire: the top profile is the base wire offset in its
// plane by length*tan(taper_deg), lofted from base to top. Falls back to a straight prism on
// any failure (self-intersecting offset / loft error). taper_deg>0 widens the top.
static TopoDS_Shape make_extrude_taper(const TopoDS_Wire& wire, const SketchPlane& plane,
double length, double taper_deg);
static TopoDS_Shape make_extrude_two_sided(const TopoDS_Wire& wire, const SketchPlane& plane,
double up, double down);
static TopoDS_Shape make_extrude_face(const TopoDS_Face& face, const SketchPlane& plane,
double length, bool symmetric = false, double taper_deg = 0.0);
// Extrude a set of imported rigid regions (Text/SVG). Each region is
// contour[0]=outer loop + contour[1..]=hole loops, in plane (u,v) mm. Builds
// one planar face-with-holes per region, extrudes it, and fuses all region
// solids into a single shape. Empty/degenerate contours are skipped.
static TopoDS_Shape make_extrude_regions(
const std::vector<std::vector<std::vector<Vec2d>>>& regions,
const SketchPlane& plane, double length, bool symmetric = false);
// Revolve a planar profile wire about an axis lying in the sketch plane and
// passing through the plane origin: axis_sel 0 = plane X axis, 1 = plane Y axis.
// A negative angle_deg sweeps the opposite direction (Flip). The profile must
// lie to one side of the axis (Onshape rule); a straddling profile self-intersects.
static TopoDS_Shape make_revolve(const TopoDS_Wire& wire, const SketchPlane& plane,
double angle_deg = 360.0, int axis_sel = 0);
// Sweep a planar profile wire along a path (spine) wire. The profile is turned
// into a face and swept with BRepOffsetAPI_MakePipe, which keeps the profile
// perpendicular to the spine along its length. The path may be open or closed;
// for a clean solid the path's first point should sit on/near the profile plane.
static TopoDS_Shape make_sweep(const TopoDS_Wire& profile, const TopoDS_Wire& path);
// Loft a solid through 2+ closed profile wires (each on its own plane), in the
// given order. ruled=true => straight (ruled) sections; false => smooth (C2).
static TopoDS_Shape make_loft(const std::vector<TopoDS_Wire>& profiles, bool ruled);
static TopoDS_Shape make_pocket(const TopoDS_Wire& wire, const SketchPlane& plane,
const TopoDS_Shape& target, double depth);
static TriangleMesh tessellate(const TopoDS_Shape& shape,
double linear_deflection = 0.01,
double angular_deflection = 0.5);
static TriangleMesh tessellate(const TopoDS_Shape& shape,
std::vector<int>& tri_face,
double linear_deflection = 0.01,
double angular_deflection = 0.5);
static TopoDS_Wire entities_to_wire(const std::vector<SketchEntity>& entities,
const SketchPlane& plane);
static std::vector<SketchEntity> mirror_entities(
const std::vector<SketchEntity>& src, const Vec2d& a, const Vec2d& b);
static std::vector<SketchEntity> offset_entities(
const std::vector<SketchEntity>& src, double d);
// Rigid-transform array. Returns the (count-1) copies for instance i=1..count-1
// (the originals in `src` are NOT included). Each copy i is `src` rigidly
// transformed by: rotate by i*angle_step about `pivot`, then translate by i*step.
// Rectangular/linear array: angle_step = 0, step = spacing*direction (pivot unused).
// Polar array: step = (0,0), angle_step = sweep/count, pivot = centre.
// Orientation-preserving, so arc/ellipse parametric angles shift by i*angle_step.
static std::vector<SketchEntity> array_entities(
const std::vector<SketchEntity>& src, int count,
const Vec2d& step, double angle_step, const Vec2d& pivot);
// General affine transform (move / rotate / scale), applied IN PLACE: returns
// the SAME entities (same count and order), each mapped by
// p -> pivot + scale * R(angle) * (p - pivot) + move
// (radii scale by |scale|; arc/ellipse parametric/rotation angles shift by
// `angle`). Unlike array_entities this mutates the subjects rather than adding
// copies. Move: angle=0, scale=1. Rotate-in-place: move=(0,0), scale=1,
// pivot=centroid. Scale: angle=0.
static std::vector<SketchEntity> transform_entities(
const std::vector<SketchEntity>& src,
const Vec2d& move, double angle, double scale, const Vec2d& pivot);
static bool fillet_lines(const SketchEntity& a, const SketchEntity& b, double r,
SketchEntity& a_out, SketchEntity& b_out, SketchEntity& arc_out);
// Symmetric chamfer between two lines meeting at a corner: trims each line back
// by setback distance `d` from the shared corner and returns the connecting
// straight segment (seg_out) in place of the corner. a_out/b_out are the trimmed
// lines; seg_out goes seg_out.p0 (on a) -> seg_out.p1 (on b). False if the lines
// are parallel or `d` overruns either line.
static bool chamfer_lines(const SketchEntity& a, const SketchEntity& b, double d,
SketchEntity& a_out, SketchEntity& b_out, SketchEntity& seg_out);
static bool trim_entity(SketchEntity& e, const std::vector<SketchEntity>& others,
const Vec2d& pick);
static bool extend_entity(SketchEntity& e, const std::vector<SketchEntity>& others,
const Vec2d& pick);
};
} // namespace Slic3r
#endif // slic3r_SketchEngine_hpp_
+145
View File
@@ -0,0 +1,145 @@
#include "SketchImport.hpp"
#include "Emboss.hpp"
#include "NSVGUtils.hpp"
#include "ExPolygon.hpp"
#include "TextConfiguration.hpp" // FontProp
#include "libslic3r.h" // SCALING_FACTOR
#include "Utils.hpp" // resources_dir
#include <algorithm>
#include <limits>
namespace Slic3r {
// Convert one ExPolygon (outer contour + CW holes) into an ImportRegion,
// mapping each integer Point to plane (u,v) mm via `to_mm`.
template<class ToMm>
static ImportRegion expoly_to_region(const ExPolygon& ex, ToMm to_mm)
{
auto contour_pts = [&](const Polygon& poly) {
std::vector<Vec2d> c;
c.reserve(poly.points.size());
for (const Point& p : poly.points)
c.push_back(to_mm(p));
return c;
};
ImportRegion region;
region.push_back(contour_pts(ex.contour));
for (const Polygon& h : ex.holes)
region.push_back(contour_pts(h));
return region;
}
// Shift all regions so their common bounding-box centre sits on the origin
// (Onshape/typical CAD insert places imported art centred on the sketch).
static void center_regions(ImportRegions& regs)
{
double lo_x = std::numeric_limits<double>::max();
double lo_y = std::numeric_limits<double>::max();
double hi_x = -std::numeric_limits<double>::max();
double hi_y = -std::numeric_limits<double>::max();
bool any = false;
for (const auto& region : regs)
for (const auto& contour : region)
for (const Vec2d& p : contour) {
lo_x = std::min(lo_x, p.x()); hi_x = std::max(hi_x, p.x());
lo_y = std::min(lo_y, p.y()); hi_y = std::max(hi_y, p.y());
any = true;
}
if (!any) return;
const Vec2d c(0.5 * (lo_x + hi_x), 0.5 * (lo_y + hi_y));
for (auto& region : regs)
for (auto& contour : region)
for (Vec2d& p : contour)
p -= c;
}
static std::string default_font_path()
{
return resources_dir() + "/fonts/HarmonyOS_Sans_SC_Regular.ttf";
}
ImportRegions text_to_regions(const std::string& utf8, double size_mm,
const std::string& font_path)
{
if (utf8.empty() || size_mm <= 0.0)
return {};
const std::string path = font_path.empty() ? default_font_path() : font_path;
std::unique_ptr<Emboss::FontFile> ff = Emboss::create_font_file(path.c_str());
if (!ff)
return {};
Emboss::FontFileWithCache fwc(std::move(ff));
if (!fwc.has_value())
return {};
FontProp prop(static_cast<float>(size_mm)); // per_glyph=false
HealedExPolygons healed = Emboss::text2shapes(fwc, utf8.c_str(), prop);
if (healed.expolygons.empty())
return {};
// Shape points are integers scaled by 1/SHAPE_SCALE in font units;
// get_text_shape_scale collapses (size_in_mm / unit_per_em) * SHAPE_SCALE
// into a single mm-per-shape-unit factor. FreeType y is up already.
const double s = Emboss::get_text_shape_scale(prop, *fwc.font_file);
auto to_mm = [s](const Point& p) { return Vec2d(p.x() * s, p.y() * s); };
ImportRegions regs;
regs.reserve(healed.expolygons.size());
for (const ExPolygon& ex : healed.expolygons)
regs.push_back(expoly_to_region(ex, to_mm));
center_regions(regs);
return regs;
}
ImportRegions svg_to_regions(const std::string& svg_path, double scale)
{
if (svg_path.empty() || scale <= 0.0)
return {};
NSVGimage_ptr image = nsvgParseFromFile(svg_path, "mm", 96.0f);
if (!image)
return {};
// A filled shape that also carries a stroke would import the stroke as a
// thick outline band wrapped around the fill (the reported "too large line
// width"). For CAD import the fill silhouette is what's wanted, so drop the
// stroke on any shape that has a fill; stroke-only line art is kept.
for (NSVGshape* s = image->shapes; s != nullptr; s = s->next)
if (s->fill.type != NSVG_PAINT_NONE)
s->stroke.type = NSVG_PAINT_NONE;
// tesselation tolerance is in image (mm) scale; 0.3 mm keeps curves smooth
// without exploding the contour count. is_y_negative (default) flips SVG's
// y-down to the sketch's y-up.
NSVGLineParams param(0.3);
ExPolygonsWithIds ids = create_shape_with_ids(*image, param);
// NSVG points are integers scaled by 1/SCALING_FACTOR (param.scale default):
// mm = point * SCALING_FACTOR, then the user scale factor.
const double s = SCALING_FACTOR * scale;
auto to_mm = [s](const Point& p) { return Vec2d(p.x() * s, p.y() * s); };
ImportRegions regs;
for (const ExPolygonsWithId& w : ids)
for (const ExPolygon& ex : w.expoly)
regs.push_back(expoly_to_region(ex, to_mm));
center_regions(regs);
return regs;
}
ImportRegions transform_regions(const ImportRegions& src, const Vec2d& offset,
double scale_x, double scale_y)
{
ImportRegions out = src;
for (auto& region : out)
for (auto& contour : region)
for (Vec2d& p : contour)
p = Vec2d(p.x() * scale_x + offset.x(), p.y() * scale_y + offset.y());
return out;
}
} // namespace Slic3r
+37
View File
@@ -0,0 +1,37 @@
#ifndef slic3r_SketchImport_hpp_
#define slic3r_SketchImport_hpp_
#include "Point.hpp" // Vec2d
#include <string>
#include <vector>
namespace Slic3r {
// A rigid imported region: contour[0] = outer loop, contour[1..] = holes;
// points in plane (u,v) millimetres. The nested vector type matches
// CadFeature::imported_regions exactly, so results assign directly.
using ImportRegion = std::vector<std::vector<Vec2d>>;
using ImportRegions = std::vector<ImportRegion>;
// Vectorize UTF-8 text into filled regions (mm), centred on the origin.
// `size_mm` is the cap/line height. `font_path` empty -> a bundled default
// font (resources/fonts). Returns an empty vector on any failure.
ImportRegions text_to_regions(const std::string& utf8, double size_mm,
const std::string& font_path = std::string());
// Parse an SVG file's filled paths into regions (mm), centred on the origin.
// `scale` multiplies the authored size (1.0 = as authored). Returns an empty
// vector on any failure.
ImportRegions svg_to_regions(const std::string& svg_path, double scale = 1.0);
// Apply an axis-aligned placement transform to regions:
// p -> ( p.x * scale_x + offset.x, p.y * scale_y + offset.y )
// Used to move / enlarge / stretch imported art non-destructively (the
// feature keeps the centred source regions + this transform).
ImportRegions transform_regions(const ImportRegions& src, const Vec2d& offset,
double scale_x, double scale_y);
} // namespace Slic3r
#endif // slic3r_SketchImport_hpp_
+120
View File
@@ -0,0 +1,120 @@
#include "SketchInference.hpp"
#include <cmath>
namespace Slic3r {
// Candidate target collected during the scan; we keep the closest within each
// priority tier and resolve ties by tier then distance.
namespace {
struct Cand {
InferenceSnap::Kind kind{InferenceSnap::Kind::None};
int entity{-1};
SketchPointRole role{SketchPointRole::P0};
Vec2d point{0, 0};
double dist{0.0};
};
// Lower number = higher priority.
int tier(InferenceSnap::Kind k)
{
switch (k) {
case InferenceSnap::Kind::Endpoint: return 0;
case InferenceSnap::Kind::Center: return 1;
case InferenceSnap::Kind::Origin: return 2;
case InferenceSnap::Kind::Midpoint: return 3;
case InferenceSnap::Kind::OnEdge: return 4;
default: return 9;
}
}
} // namespace
InferenceSnap infer_point_snap(const std::vector<SketchEntity>& entities,
const Vec2d& query, double tol,
bool include_origin)
{
Cand best;
best.kind = InferenceSnap::Kind::None;
best.point = query;
auto offer = [&](InferenceSnap::Kind k, int ent, SketchPointRole r, const Vec2d& q) {
const double d = (q - query).norm();
if (d > tol) return;
const bool better = (best.kind == InferenceSnap::Kind::None) ||
(tier(k) < tier(best.kind)) ||
(tier(k) == tier(best.kind) && d < best.dist);
if (better) { best.kind = k; best.entity = ent; best.role = r; best.point = q; best.dist = d; }
};
for (size_t i = 0; i < entities.size(); ++i) {
const SketchEntity& e = entities[i];
const int ei = int(i);
switch (e.type) {
case SketchEntity::Type::Line: {
offer(InferenceSnap::Kind::Endpoint, ei, SketchPointRole::P0, e.p0);
offer(InferenceSnap::Kind::Endpoint, ei, SketchPointRole::P1, e.p1);
offer(InferenceSnap::Kind::Midpoint, ei, SketchPointRole::P0, 0.5 * (e.p0 + e.p1));
// Projection onto the segment interior (PointOnObject candidate).
const Vec2d d = e.p1 - e.p0;
const double L2 = d.squaredNorm();
if (L2 > 1e-12) {
double t = (query - e.p0).dot(d) / L2;
if (t > 0.02 && t < 0.98)
offer(InferenceSnap::Kind::OnEdge, ei, SketchPointRole::P0, e.p0 + t * d);
}
break;
}
case SketchEntity::Type::Arc:
offer(InferenceSnap::Kind::Endpoint, ei, SketchPointRole::P0, e.p0);
offer(InferenceSnap::Kind::Endpoint, ei, SketchPointRole::P1, e.p1);
offer(InferenceSnap::Kind::Center, ei, SketchPointRole::Center, e.center);
break;
case SketchEntity::Type::Circle: {
offer(InferenceSnap::Kind::Center, ei, SketchPointRole::Center, e.center);
// Nearest point on the circle rim (PointOnObject candidate).
const Vec2d v = query - e.center;
const double n = v.norm();
if (n > 1e-9 && e.radius > 1e-9)
offer(InferenceSnap::Kind::OnEdge, ei, SketchPointRole::Center,
e.center + v * (e.radius / n));
break;
}
case SketchEntity::Type::Point:
offer(InferenceSnap::Kind::Endpoint, ei, SketchPointRole::P0, e.p0);
break;
case SketchEntity::Type::EllipseArc:
offer(InferenceSnap::Kind::Endpoint, ei, SketchPointRole::P0, e.p0);
offer(InferenceSnap::Kind::Endpoint, ei, SketchPointRole::P1, e.p1);
offer(InferenceSnap::Kind::Center, ei, SketchPointRole::Center, e.center);
break;
case SketchEntity::Type::Ellipse:
offer(InferenceSnap::Kind::Center, ei, SketchPointRole::Center, e.center);
break;
case SketchEntity::Type::BSpline:
// Endpoints (first/last pole) snap for loop closure.
offer(InferenceSnap::Kind::Endpoint, ei, SketchPointRole::P0, e.p0);
offer(InferenceSnap::Kind::Endpoint, ei, SketchPointRole::P1, e.p1);
break;
}
}
if (include_origin)
offer(InferenceSnap::Kind::Origin, -1, SketchPointRole::P0, Vec2d(0, 0));
InferenceSnap r;
r.kind = best.kind; r.entity = best.entity; r.role = best.role; r.point = best.point;
return r;
}
std::optional<SketchConstraintType>
infer_axis_constraint(const Vec2d& anchor, const Vec2d& tip, double ang_tol_rad)
{
const Vec2d d = tip - anchor;
if (d.squaredNorm() < 1e-12) return std::nullopt;
const double ang = std::atan2(std::abs(d.y()), std::abs(d.x())); // 0=horizontal, pi/2=vertical
if (ang <= ang_tol_rad) return SketchConstraintType::Horizontal;
if (ang >= M_PI / 2.0 - ang_tol_rad) return SketchConstraintType::Vertical;
return std::nullopt;
}
} // namespace Slic3r
+44
View File
@@ -0,0 +1,44 @@
#ifndef slic3r_SketchInference_hpp_
#define slic3r_SketchInference_hpp_
#include "SketchEngine.hpp"
#include <vector>
#include <optional>
#include <cmath>
namespace Slic3r {
// Result of snapping a free cursor point onto the most relevant inference target
// among the committed sketch entities and the sketch origin. This is the backbone
// that lets geometry self-constrain as it is drawn: the GUI records the returned
// target at click time and, once the entity it belongs to exists, emits the
// matching constraint (Coincident onto an endpoint/centre, Fix onto the origin,
// PointOnObject onto an edge) so the relation survives a re-solve.
struct InferenceSnap {
enum class Kind { None, Endpoint, Center, Midpoint, OnEdge, Origin };
Kind kind{Kind::None};
int entity{-1}; // hit entity index (-1 = origin/none)
SketchPointRole role{SketchPointRole::P0}; // which point of `entity` (Endpoint/Center)
Vec2d point{0, 0}; // snapped coordinate (== query when None)
bool snapped() const { return kind != Kind::None; }
};
// Snap `query` onto the best inference target within `tol` plane units. Priority,
// highest first: Endpoint, Center, Origin, Midpoint, OnEdge. Construction entities
// participate (you constrain to them too). Returns {None, query} when nothing is in
// range. Pure — no GUI / GL dependencies, so it is unit-testable in libslic3r.
InferenceSnap infer_point_snap(const std::vector<SketchEntity>& entities,
const Vec2d& query, double tol,
bool include_origin = true);
// Relational inference for an in-progress segment anchor->tip. If its direction is
// within `ang_tol_rad` of an axis, returns Horizontal or Vertical (the constraint to
// auto-emit on the committed segment); std::nullopt otherwise. Degenerate (near-zero
// length) segments return nullopt.
std::optional<SketchConstraintType>
infer_axis_constraint(const Vec2d& anchor, const Vec2d& tip, double ang_tol_rad = 3.0 * M_PI / 180.0);
} // namespace Slic3r
#endif // slic3r_SketchInference_hpp_
+361
View File
@@ -0,0 +1,361 @@
#include "SketchSolver.hpp"
#include <slvs.h>
#include <cmath>
#include <cstring>
#include <unordered_map>
namespace Slic3r {
using CT = SketchConstraintType;
using Role = SketchPointRole;
namespace {
constexpr Slvs_hGroup G_FIXED = 1; // workplane / reference: held constant
constexpr Slvs_hGroup G_SK = 2; // sketch geometry: the group we solve
// Per-entity slvs handles. p0/p1/center are point2d entity handles; prim is the
// line/arc/circle entity; rparam is the circle radius param.
struct Slots {
Slvs_hEntity prim{0}, p0{0}, p1{0}, center{0};
Slvs_hParam rparam{0};
std::vector<Slvs_hEntity> pts; // BSpline control points (point2d handles)
};
struct Build {
std::vector<Slvs_Param> params;
std::vector<Slvs_Entity> ents;
std::vector<Slvs_Constraint> cons;
Slvs_hParam ph{0};
Slvs_hEntity eh{0};
Slvs_hConstraint ch{0};
Slvs_hEntity wp{0}, normal{0};
Slvs_hParam P(Slvs_hGroup g, double v) { params.push_back(Slvs_MakeParam(++ph, g, v)); return ph; }
Slvs_hEntity E(Slvs_Entity e) { ents.push_back(e); return e.h; }
Slvs_hEntity pt2d(Slvs_hGroup g, double u, double v)
{ return E(Slvs_MakePoint2d(++eh, g, wp, P(g, u), P(g, v))); }
// Generic constraint (entityC unused by Slvs_MakeConstraint — set it manually below).
void C(int type, double val, Slvs_hEntity ptA, Slvs_hEntity ptB,
Slvs_hEntity eA, Slvs_hEntity eB, Slvs_hEntity eC = 0, int other = 0)
{
Slvs_Constraint c = Slvs_MakeConstraint(++ch, G_SK, type, wp, val, ptA, ptB, eA, eB);
c.entityC = eC;
c.other = other;
cons.push_back(c);
}
};
inline int role_idx(Role r) { return int(r); }
} // namespace
static SketchSolveResult solve_impl(std::vector<SketchEntity>& entities,
const std::vector<SketchEntityConstraintDef>& constraints,
int dragged_ei, Role dragged_role)
{
SketchSolveResult out;
if (constraints.empty()) { out.ok = true; out.dof = -1; return out; }
Build b;
// ---- Fixed 2D XY workplane (origin at 0,0,0; identity normal) -------------------
Slvs_hEntity origin = b.E(Slvs_MakePoint3d(++b.eh, G_FIXED,
b.P(G_FIXED, 0.0), b.P(G_FIXED, 0.0), b.P(G_FIXED, 0.0)));
double qw, qx, qy, qz;
Slvs_MakeQuaternion(1, 0, 0, 0, 1, 0, &qw, &qx, &qy, &qz);
b.normal = b.E(Slvs_MakeNormal3d(++b.eh, G_FIXED,
b.P(G_FIXED, qw), b.P(G_FIXED, qx), b.P(G_FIXED, qy), b.P(G_FIXED, qz)));
b.wp = b.E(Slvs_MakeWorkplane(++b.eh, G_FIXED, origin, b.normal));
// ---- Entities -------------------------------------------------------------------
std::vector<Slots> slot(entities.size());
for (size_t i = 0; i < entities.size(); ++i) {
const SketchEntity& e = entities[i];
Slots s;
switch (e.type) {
case SketchEntity::Type::Line:
s.p0 = b.pt2d(G_SK, e.p0.x(), e.p0.y());
s.p1 = b.pt2d(G_SK, e.p1.x(), e.p1.y());
s.prim = b.E(Slvs_MakeLineSegment(++b.eh, G_SK, b.wp, s.p0, s.p1));
break;
case SketchEntity::Type::Point:
s.p0 = b.pt2d(G_SK, e.p0.x(), e.p0.y());
break;
case SketchEntity::Type::Circle: {
s.center = b.pt2d(G_SK, e.center.x(), e.center.y());
s.p0 = s.center; // p0 mirrors centre for circles
s.rparam = b.P(G_SK, e.radius > 1e-9 ? e.radius : 1.0);
Slvs_hEntity dist = b.E(Slvs_MakeDistance(++b.eh, G_SK, b.wp, s.rparam));
s.prim = b.E(Slvs_MakeCircle(++b.eh, G_SK, b.wp, s.center, b.normal, dist));
break;
}
case SketchEntity::Type::Arc:
s.center = b.pt2d(G_SK, e.center.x(), e.center.y());
s.p0 = b.pt2d(G_SK, e.p0.x(), e.p0.y()); // start
s.p1 = b.pt2d(G_SK, e.p1.x(), e.p1.y()); // end
s.prim = b.E(Slvs_MakeArcOfCircle(++b.eh, G_SK, b.wp, b.normal, s.center, s.p0, s.p1));
break;
// libslvs has no conic entity (scope note): register the ellipse's defining
// points only (center + arc endpoints) so center/endpoint constraints solve;
// the a/b/phi shape params pass through unsolved.
case SketchEntity::Type::Ellipse:
s.center = b.pt2d(G_SK, e.center.x(), e.center.y());
s.p0 = s.center; // p0 mirrors centre (circle convention)
break;
case SketchEntity::Type::EllipseArc:
s.center = b.pt2d(G_SK, e.center.x(), e.center.y());
s.p0 = b.pt2d(G_SK, e.p0.x(), e.p0.y()); // start
s.p1 = b.pt2d(G_SK, e.p1.x(), e.p1.y()); // end
break;
// No native slvs curve for an arbitrary-degree spline: register the control
// poles as point2d so endpoints (and any pole-targeted constraint) solve. The
// OCCT curve is rebuilt from the solved poles. p0/p1 mirror first/last pole so
// Coincident at the spline ends closes loops just like a Line.
case SketchEntity::Type::BSpline:
s.pts.reserve(e.ctrl.size());
for (const Vec2d& cp : e.ctrl)
s.pts.push_back(b.pt2d(G_SK, cp.x(), cp.y()));
if (!s.pts.empty()) { s.p0 = s.pts.front(); s.p1 = s.pts.back(); }
break;
}
slot[i] = s;
}
auto valid = [&](int ei) { return ei >= 0 && ei < int(entities.size()); };
auto ptOf = [&](int ei, Role r) -> Slvs_hEntity {
if (!valid(ei)) return 0;
const Slots& s = slot[ei];
switch (r) {
case Role::P0: return s.p0;
case Role::P1: return s.p1;
case Role::Center: return s.center ? s.center : s.p0;
}
return 0;
};
auto primOf = [&](int ei) -> Slvs_hEntity { return valid(ei) ? slot[ei].prim : 0; };
auto coordOf = [&](int ei, Role r) -> Vec2d {
if (!valid(ei)) return Vec2d(0, 0);
const SketchEntity& e = entities[ei];
switch (r) { case Role::P0: return e.p0; case Role::P1: return e.p1; case Role::Center: return e.center; }
return e.p0;
};
// A fixed reference point at (x,y) — used to pin coordinates (Fix / LockX / LockY).
auto fixedRef = [&](double x, double y) -> Slvs_hEntity { return b.pt2d(G_FIXED, x, y); };
// ---- Constraints ----------------------------------------------------------------
for (const auto& c : constraints) {
// Robustness: never feed libslvs a null handle. A constraint that references an
// entity which produced no solver primitive (Point/Ellipse/EllipseArc/BSpline get
// no `prim`) or no point for the requested role would make Slvs FindById abort the
// whole process. Skip such a constraint instead of crashing.
bool ref_ok = true;
switch (c.type) {
case CT::Coincident: case CT::Horizontal: case CT::Vertical: case CT::Distance:
ref_ok = ptOf(c.ea, c.ra) && ptOf(c.eb, c.rb); break;
case CT::Concentric:
ref_ok = ptOf(c.ea, Role::Center) && ptOf(c.eb, Role::Center); break;
case CT::Fix: case CT::LockX: case CT::LockY:
ref_ok = ptOf(c.ea, c.ra) != 0; break;
case CT::EqualLength: case CT::Parallel: case CT::Perpendicular:
case CT::Angle: case CT::Tangent:
ref_ok = primOf(c.ea) && primOf(c.eb); break;
case CT::Radius: case CT::Diameter:
ref_ok = primOf(c.ea) != 0; break;
case CT::Midpoint:
ref_ok = ptOf(c.ea, c.ra) && primOf(c.eb); break;
case CT::Symmetric:
ref_ok = ptOf(c.ea, c.ra) && ptOf(c.eb, c.rb) && primOf(c.ec); break;
case CT::PointOnLine: case CT::PointOnObject:
ref_ok = ptOf(c.ea, c.ra) && primOf(c.eb); break;
}
if (!ref_ok) continue;
switch (c.type) {
case CT::Coincident:
b.C(SLVS_C_POINTS_COINCIDENT, 0, ptOf(c.ea, c.ra), ptOf(c.eb, c.rb), 0, 0);
break;
case CT::Concentric:
b.C(SLVS_C_POINTS_COINCIDENT, 0, ptOf(c.ea, Role::Center), ptOf(c.eb, Role::Center), 0, 0);
break;
case CT::Horizontal:
b.C(SLVS_C_HORIZONTAL, 0, ptOf(c.ea, c.ra), ptOf(c.eb, c.rb), 0, 0);
break;
case CT::Vertical:
b.C(SLVS_C_VERTICAL, 0, ptOf(c.ea, c.ra), ptOf(c.eb, c.rb), 0, 0);
break;
case CT::Distance:
b.C(SLVS_C_PT_PT_DISTANCE, c.value, ptOf(c.ea, c.ra), ptOf(c.eb, c.rb), 0, 0);
break;
case CT::Fix: {
const Vec2d p = coordOf(c.ea, c.ra);
b.C(SLVS_C_POINTS_COINCIDENT, 0, ptOf(c.ea, c.ra), fixedRef(p.x(), p.y()), 0, 0);
break;
}
case CT::LockX: {
const Vec2d p = coordOf(c.ea, c.ra);
b.C(SLVS_C_VERTICAL, 0, ptOf(c.ea, c.ra), fixedRef(c.value, p.y()), 0, 0);
break;
}
case CT::LockY: {
const Vec2d p = coordOf(c.ea, c.ra);
b.C(SLVS_C_HORIZONTAL, 0, ptOf(c.ea, c.ra), fixedRef(p.x(), c.value), 0, 0);
break;
}
case CT::EqualLength:
b.C(SLVS_C_EQUAL_LENGTH_LINES, 0, 0, 0, primOf(c.ea), primOf(c.eb));
break;
case CT::Parallel:
b.C(SLVS_C_PARALLEL, 0, 0, 0, primOf(c.ea), primOf(c.eb));
break;
case CT::Perpendicular:
b.C(SLVS_C_PERPENDICULAR, 0, 0, 0, primOf(c.ea), primOf(c.eb));
break;
case CT::Midpoint:
b.C(SLVS_C_AT_MIDPOINT, 0, ptOf(c.ea, c.ra), 0, primOf(c.eb), 0);
break;
case CT::Symmetric:
// ptA, ptB symmetric about the axis line (ec).
b.C(SLVS_C_SYMMETRIC_LINE, 0, ptOf(c.ea, c.ra), ptOf(c.eb, c.rb), primOf(c.ec), 0);
break;
case CT::Angle:
// model stores radians; slvs angle is in degrees.
b.C(SLVS_C_ANGLE, c.value * 180.0 / M_PI, 0, 0, primOf(c.ea), primOf(c.eb));
break;
case CT::Radius:
b.C(SLVS_C_DIAMETER, 2.0 * c.value, 0, 0, primOf(c.ea), 0);
break;
case CT::Diameter:
b.C(SLVS_C_DIAMETER, c.value, 0, 0, primOf(c.ea), 0);
break;
case CT::Tangent: {
const bool aCurve = valid(c.ea) && entities[c.ea].type != SketchEntity::Type::Line;
const bool bCurve = valid(c.eb) && entities[c.eb].type != SketchEntity::Type::Line;
if (aCurve && bCurve)
b.C(SLVS_C_CURVE_CURVE_TANGENT, 0, 0, 0, primOf(c.ea), primOf(c.eb));
else {
const Slvs_hEntity arc = aCurve ? primOf(c.ea) : primOf(c.eb);
const Slvs_hEntity line = aCurve ? primOf(c.eb) : primOf(c.ea);
b.C(SLVS_C_ARC_LINE_TANGENT, 0, 0, 0, arc, line);
}
break;
}
case CT::PointOnLine:
if (std::abs(c.value) < 1e-9)
b.C(SLVS_C_PT_ON_LINE, 0, ptOf(c.ea, c.ra), 0, primOf(c.eb), 0);
else
b.C(SLVS_C_PT_LINE_DISTANCE, std::abs(c.value), ptOf(c.ea, c.ra), 0, primOf(c.eb), 0);
break;
case CT::PointOnObject:
// Point (ea,ra) lies on entity edge eb: a circle rim -> PT_ON_CIRCLE,
// otherwise the segment line -> PT_ON_LINE.
if (valid(c.eb) && entities[c.eb].type == SketchEntity::Type::Circle)
b.C(SLVS_C_PT_ON_CIRCLE, 0, ptOf(c.ea, c.ra), 0, primOf(c.eb), 0);
else
b.C(SLVS_C_PT_ON_LINE, 0, ptOf(c.ea, c.ra), 0, primOf(c.eb), 0);
break;
}
}
// ---- Solve ----------------------------------------------------------------------
Slvs_System sys;
std::memset(&sys, 0, sizeof(sys));
sys.param = b.params.data(); sys.params = int(b.params.size());
sys.entity = b.ents.data(); sys.entities = int(b.ents.size());
sys.constraint = b.cons.data(); sys.constraints = int(b.cons.size());
std::vector<Slvs_hConstraint> failed(b.cons.size() + 1, 0);
sys.failed = failed.data();
sys.faileds = int(failed.size());
sys.calculateFaileds = 1;
// Drag pin: feed the dragged point's two params into sys.dragged[] so the solver
// favours keeping that point at the cursor and re-solves the rest around it.
if (dragged_ei >= 0) {
const Slvs_hEntity h = ptOf(dragged_ei, dragged_role);
for (const Slvs_Entity& en : b.ents)
if (en.h == h) { sys.dragged[0] = en.param[0]; sys.dragged[1] = en.param[1]; break; }
}
Slvs_Solve(&sys, G_SK);
out.result = sys.result;
out.dof = sys.dof;
out.ok = (sys.result == SLVS_RESULT_OKAY);
// Map solved param handles -> values, then read points back.
std::unordered_map<Slvs_hParam, double> pv;
pv.reserve(sys.params * 2);
for (int i = 0; i < sys.params; ++i) pv[sys.param[i].h] = sys.param[i].val;
std::unordered_map<Slvs_hEntity, const Slvs_Entity*> byH;
byH.reserve(sys.entities * 2);
for (int i = 0; i < sys.entities; ++i) byH[sys.entity[i].h] = &sys.entity[i];
auto coord = [&](Slvs_hEntity h) -> Vec2d {
auto it = byH.find(h);
if (it == byH.end()) return Vec2d(0, 0);
return Vec2d(pv[it->second->param[0]], pv[it->second->param[1]]);
};
// Map failed constraint handles back to indices into `constraints`.
if (!out.ok && sys.faileds > 0) {
std::unordered_map<Slvs_hConstraint, int> chToIdx;
// constraint handles were assigned in order starting after the fixed group; the
// i-th sketch constraint in b.cons has handle = its position. Rebuild by scanning.
for (size_t k = 0; k < b.cons.size(); ++k) chToIdx[b.cons[k].h] = int(k);
for (int i = 0; i < sys.faileds; ++i) {
auto it = chToIdx.find(failed[i]);
if (it != chToIdx.end() && it->second < int(constraints.size()))
out.bad.push_back(it->second);
}
}
// ---- Read solved geometry back --------------------------------------------------
for (size_t i = 0; i < entities.size(); ++i) {
SketchEntity& e = entities[i];
const Slots& s = slot[i];
if (s.p0) e.p0 = coord(s.p0);
if (s.p1) e.p1 = coord(s.p1);
if (s.center) e.center = coord(s.center);
if (e.type == SketchEntity::Type::BSpline) {
for (size_t k = 0; k < s.pts.size() && k < e.ctrl.size(); ++k)
e.ctrl[k] = coord(s.pts[k]);
if (!e.ctrl.empty()) { e.p0 = e.ctrl.front(); e.p1 = e.ctrl.back(); }
} else if (e.type == SketchEntity::Type::Circle) {
if (s.rparam) { auto it = pv.find(s.rparam); if (it != pv.end()) e.radius = it->second; }
e.p0 = e.center;
} else if (e.type == SketchEntity::Type::Arc && s.center) {
// Reflow arc angles from solved centre + endpoints, preserving sweep sign.
const double old_sweep = e.end_angle - e.start_angle;
const double ns = std::atan2(e.p0.y() - e.center.y(), e.p0.x() - e.center.x());
const double ne = std::atan2(e.p1.y() - e.center.y(), e.p1.x() - e.center.x());
double sweep = ne - ns;
const double TWO_PI = 2.0 * M_PI;
while (sweep <= -TWO_PI) sweep += TWO_PI;
while (sweep >= TWO_PI) sweep -= TWO_PI;
if (old_sweep >= 0.0 && sweep < 0.0) sweep += TWO_PI;
if (old_sweep < 0.0 && sweep > 0.0) sweep -= TWO_PI;
e.start_angle = ns;
e.end_angle = ns + sweep;
e.radius = 0.5 * ((e.p0 - e.center).norm() + (e.p1 - e.center).norm());
}
}
return out;
}
SketchSolveResult sketch_solve(std::vector<SketchEntity>& entities,
const std::vector<SketchEntityConstraintDef>& constraints)
{
return solve_impl(entities, constraints, -1, Role::P0);
}
SketchSolveResult sketch_solve_drag(std::vector<SketchEntity>& entities,
const std::vector<SketchEntityConstraintDef>& constraints,
int dragged_ei, SketchPointRole dragged_role)
{
return solve_impl(entities, constraints, dragged_ei, dragged_role);
}
} // namespace Slic3r
+37
View File
@@ -0,0 +1,37 @@
#ifndef slic3r_SketchSolver_hpp_
#define slic3r_SketchSolver_hpp_
// Bridge from SnapOrca's SketchEntity / SketchEntityConstraintDef model onto the
// vendored SolveSpace constraint solver (src/libslic3r/slvs, libslvs). Replaces the
// hand-rolled SketchConstraints: full constraint set, real DoF counting, and
// over-constrained (bad-constraint) detection. Solves on a fixed 2D XY workplane.
#include "SketchEngine.hpp"
#include <vector>
namespace Slic3r {
struct SketchSolveResult {
bool ok{false}; // solver converged & consistent
int dof{-1}; // remaining degrees of freedom (>0 under-constrained)
int result{0}; // raw SLVS_RESULT_* code
std::vector<int> bad; // indices (into `constraints`) of conflicting constraints
};
// Solve `constraints` over `entities` in place (writes solved coordinates back into the
// entities; arc angles are reflowed preserving sweep direction). No-op success when
// `constraints` is empty.
SketchSolveResult sketch_solve(std::vector<SketchEntity>& entities,
const std::vector<SketchEntityConstraintDef>& constraints);
// Drag-aware solve: pins the (dragged_ei, dragged_role) point's parameters via the
// solver's `dragged[]` priority list so the solver keeps that point where the cursor
// placed it (caller must have moved it first) and moves the OTHER free geometry to
// re-satisfy the constraints. dragged_ei < 0 behaves identically to sketch_solve.
SketchSolveResult sketch_solve_drag(std::vector<SketchEntity>& entities,
const std::vector<SketchEntityConstraintDef>& constraints,
int dragged_ei, SketchPointRole dragged_role);
} // namespace Slic3r
#endif
+95
View File
@@ -0,0 +1,95 @@
#include "ThreadStandards.hpp"
namespace Slic3r {
// Imperial helpers: convert nominal inch diameter / threads-per-inch to mm.
static constexpr double IN = 25.4;
static inline double tpi_pitch(double tpi) { return IN / tpi; }
const std::vector<ThreadSpec>& thread_standards()
{
using S = ThreadSpec::Series;
static const std::vector<ThreadSpec> table = {
// --- ISO metric, coarse pitch (ISO 261 preferred series) ---
{"M1", 1.0, 0.25, S::MetricCoarse},
{"M1.2", 1.2, 0.25, S::MetricCoarse},
{"M1.6", 1.6, 0.35, S::MetricCoarse},
{"M2", 2.0, 0.40, S::MetricCoarse},
{"M2.5", 2.5, 0.45, S::MetricCoarse},
{"M3", 3.0, 0.50, S::MetricCoarse},
{"M4", 4.0, 0.70, S::MetricCoarse},
{"M5", 5.0, 0.80, S::MetricCoarse},
{"M6", 6.0, 1.00, S::MetricCoarse},
{"M8", 8.0, 1.25, S::MetricCoarse},
{"M10", 10.0, 1.50, S::MetricCoarse},
{"M12", 12.0, 1.75, S::MetricCoarse},
{"M14", 14.0, 2.00, S::MetricCoarse},
{"M16", 16.0, 2.00, S::MetricCoarse},
{"M20", 20.0, 2.50, S::MetricCoarse},
{"M24", 24.0, 3.00, S::MetricCoarse},
{"M30", 30.0, 3.50, S::MetricCoarse},
{"M36", 36.0, 4.00, S::MetricCoarse},
{"M42", 42.0, 4.50, S::MetricCoarse},
{"M48", 48.0, 5.00, S::MetricCoarse},
{"M56", 56.0, 5.50, S::MetricCoarse},
{"M64", 64.0, 6.00, S::MetricCoarse},
// --- ISO metric, common fine pitches (ISO 261 fine series) ---
{"M8x1", 8.0, 1.00, S::MetricFine},
{"M10x1.25", 10.0, 1.25, S::MetricFine},
{"M10x1", 10.0, 1.00, S::MetricFine},
{"M12x1.5", 12.0, 1.50, S::MetricFine},
{"M12x1.25", 12.0, 1.25, S::MetricFine},
{"M16x1.5", 16.0, 1.50, S::MetricFine},
{"M20x1.5", 20.0, 1.50, S::MetricFine},
{"M24x2", 24.0, 2.00, S::MetricFine},
// --- Unified National Coarse (UTS / ASME B1.1) ---
{"#1-64 UNC", 0.073 * IN, tpi_pitch(64), S::UNC},
{"#2-56 UNC", 0.086 * IN, tpi_pitch(56), S::UNC},
{"#3-48 UNC", 0.099 * IN, tpi_pitch(48), S::UNC},
{"#4-40 UNC", 0.112 * IN, tpi_pitch(40), S::UNC},
{"#5-40 UNC", 0.125 * IN, tpi_pitch(40), S::UNC},
{"#6-32 UNC", 0.138 * IN, tpi_pitch(32), S::UNC},
{"#8-32 UNC", 0.164 * IN, tpi_pitch(32), S::UNC},
{"#10-24 UNC", 0.190 * IN, tpi_pitch(24), S::UNC},
{"#12-24 UNC", 0.216 * IN, tpi_pitch(24), S::UNC},
{"1/4-20 UNC", 0.250 * IN, tpi_pitch(20), S::UNC},
{"5/16-18 UNC", 0.3125 * IN, tpi_pitch(18), S::UNC},
{"3/8-16 UNC", 0.375 * IN, tpi_pitch(16), S::UNC},
{"7/16-14 UNC", 0.4375 * IN, tpi_pitch(14), S::UNC},
{"1/2-13 UNC", 0.500 * IN, tpi_pitch(13), S::UNC},
{"9/16-12 UNC", 0.5625 * IN, tpi_pitch(12), S::UNC},
{"5/8-11 UNC", 0.625 * IN, tpi_pitch(11), S::UNC},
{"3/4-10 UNC", 0.750 * IN, tpi_pitch(10), S::UNC},
{"7/8-9 UNC", 0.875 * IN, tpi_pitch(9), S::UNC},
{"1-8 UNC", 1.000 * IN, tpi_pitch(8), S::UNC},
// --- Unified National Fine (UTS / ASME B1.1) ---
{"#2-64 UNF", 0.086 * IN, tpi_pitch(64), S::UNF},
{"#4-48 UNF", 0.112 * IN, tpi_pitch(48), S::UNF},
{"#6-40 UNF", 0.138 * IN, tpi_pitch(40), S::UNF},
{"#8-36 UNF", 0.164 * IN, tpi_pitch(36), S::UNF},
{"#10-32 UNF", 0.190 * IN, tpi_pitch(32), S::UNF},
{"1/4-28 UNF", 0.250 * IN, tpi_pitch(28), S::UNF},
{"5/16-24 UNF", 0.3125 * IN, tpi_pitch(24), S::UNF},
{"3/8-24 UNF", 0.375 * IN, tpi_pitch(24), S::UNF},
{"7/16-20 UNF", 0.4375 * IN, tpi_pitch(20), S::UNF},
{"1/2-20 UNF", 0.500 * IN, tpi_pitch(20), S::UNF},
{"9/16-18 UNF", 0.5625 * IN, tpi_pitch(18), S::UNF},
{"5/8-18 UNF", 0.625 * IN, tpi_pitch(18), S::UNF},
{"3/4-16 UNF", 0.750 * IN, tpi_pitch(16), S::UNF},
{"1-12 UNF", 1.000 * IN, tpi_pitch(12), S::UNF},
};
return table;
}
const ThreadSpec* find_thread_standard(const std::string& name)
{
for (const ThreadSpec& s : thread_standards())
if (s.name == name)
return &s;
return nullptr;
}
} // namespace Slic3r
+39
View File
@@ -0,0 +1,39 @@
#ifndef slic3r_ThreadStandards_hpp_
#define slic3r_ThreadStandards_hpp_
#include <string>
#include <vector>
namespace Slic3r {
// Canonical mechanical thread specifications (ISO metric + Unified imperial).
// All dimensions are stored in millimetres so the CAD kernel can consume them
// directly. The profile is the common 60deg V shared by ISO 261/965 and ASME
// B1.1 (UTS), so the cut/ridge depth used by the Design-tab Thread tool is the
// basic external thread height h = 0.6134 * pitch, and the internal (tapped)
// minor diameter is D1 = D - 1.0825 * pitch (= D - 2*5H/8).
struct ThreadSpec {
enum class Series { MetricCoarse, MetricFine, UNC, UNF };
std::string name; // designation, e.g. "M6", "1/4-20 UNC"
double major_diameter_mm; // nominal (crest) diameter
double pitch_mm; // axial advance per turn
Series series;
// 60deg basic external thread height (radial crest-to-root engagement).
double thread_depth_mm() const { return 0.6134 * pitch_mm; }
// Internal/tapped minor (tap-drill) diameter for the same nominal thread.
double minor_diameter_mm() const { return major_diameter_mm - 1.0825 * pitch_mm; }
bool imperial() const { return series == Series::UNC || series == Series::UNF; }
};
// Full ordered table (metric coarse, metric fine, UNC, UNF) for GUI listing.
const std::vector<ThreadSpec>& thread_standards();
// Exact case-sensitive designation lookup; nullptr if not a known standard.
const ThreadSpec* find_thread_standard(const std::string& name);
} // namespace Slic3r
#endif
+30
View File
@@ -0,0 +1,30 @@
# Vendored SolveSpace geometric constraint solver (libslvs), isolated solver core.
# Source: github.com/JacobStoren/SolveSpaceLib (extraction of solvespace.com libslvs).
# GPLv3 — compatible with this AGPL fork. Built as a self-contained STATIC lib; only
# include/slvs.h (+ SolveSpaceSystem.h C++ wrapper) is exposed to libslic3r.
# No external deps: it ships its own dense linear solver (no Eigen).
add_library(libslvs STATIC
constrainteq.cpp
entity.cpp
expr.cpp
system.cpp
util.cpp
platform/unixutil.cpp
lib.cpp
SolveSpaceSystem.cpp)
target_compile_definitions(libslvs PRIVATE -DLIBRARY)
# Public API headers (include/slvs.h, include/SolveSpaceSystem.h) propagate to consumers;
# the internal solvespace headers stay PRIVATE so they never leak into libslic3r TUs.
target_include_directories(libslvs
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
# libslic3r is linked into shared targets → PIC required. Silence warnings for this
# vendored code (it predates the project's -Wall/-Werror=return-type cleanliness).
set_target_properties(libslvs PROPERTIES POSITION_INDEPENDENT_CODE ON)
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
target_compile_options(libslvs PRIVATE -w -fno-strict-aliasing)
endif()
+675
View File
@@ -0,0 +1,675 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+177
View File
@@ -0,0 +1,177 @@
#define EXPORT_DLL
#include "SolveSpaceSystem.h"
#include <assert.h>
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
SolveSpaceSystem::SolveSpaceSystem()
: m_paramsMemory (new std::vector<Slvs_Param> ())
, m_entityMemory (new std::vector<Slvs_Entity> ())
, m_constraintMemory (new std::vector<Slvs_Constraint> ())
, m_failedConstrMemory(new std::vector<Slvs_hConstraint>())
{
m_paramsMemory ->reserve(100);
m_entityMemory ->reserve(100);
m_constraintMemory->reserve(100);
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
Slvs_hParam SolveSpaceSystem::addParam(Slvs_Param parameter)
{
parameter.h = static_cast<Slvs_hParam>(m_paramsMemory->size()+1);
m_paramsMemory->push_back(parameter);
m_slvsSystem.param = m_paramsMemory->data();
m_slvsSystem.params = static_cast<int>(m_paramsMemory->size());
return parameter.h;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
Slvs_hEntity SolveSpaceSystem::addEntity(Slvs_Entity entity)
{
entity.h = static_cast<Slvs_hEntity>(m_entityMemory->size()+1);
m_entityMemory->push_back(entity);
m_slvsSystem.entity = m_entityMemory->data();
m_slvsSystem.entities = static_cast<int>(m_entityMemory->size());
return entity.h;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
Slvs_hConstraint SolveSpaceSystem::addConstr(Slvs_Constraint constr)
{
constr.h = static_cast<Slvs_hConstraint>(m_constraintMemory->size()+1);
m_constraintMemory->push_back(constr);
m_slvsSystem.constraint = m_constraintMemory->data();
m_slvsSystem.constraints = static_cast<int>(m_constraintMemory->size());
return constr.h;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
SolveSpaceSystem::ResultStatus SolveSpaceSystem::solve(Slvs_hGroup groupId, bool reportFailedConstraints /*= true*/)
{
m_failedConstrMemory->resize(m_constraintMemory->size());
m_slvsSystem.failed = m_failedConstrMemory->data();
m_slvsSystem.faileds = static_cast<int>(m_failedConstrMemory->size());
m_slvsSystem.calculateFaileds = reportFailedConstraints;
Slvs_Solve(&m_slvsSystem, groupId);
m_failedConstrMemory->resize(m_slvsSystem.faileds);
return static_cast<ResultStatus>(m_slvsSystem.result);
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double SolveSpaceSystem::parameterValue(Slvs_hParam paramId)
{
return (*m_paramsMemory)[paramId-1].val;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void SolveSpaceSystem::setParameterValue(Slvs_hParam paramId, double value)
{
(*m_paramsMemory)[paramId-1].val = value;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::tuple< std::valarray<double>,
std::valarray<double>,
std::valarray<double> > SolveSpaceSystem::orientationMx(Slvs_hEntity normalIn3dEntityId)
{
Slvs_Entity e_CS = (*m_entityMemory)[normalIn3dEntityId -1];
if ( e_CS.type == SLVS_E_NORMAL_IN_3D )
{
std::valarray<double> quat ={ 0.0, 0.0, 0.0, 0.0 };
quat[0] = parameterValue(e_CS.param[0]);
quat[1] = parameterValue(e_CS.param[1]);
quat[2] = parameterValue(e_CS.param[2]);
quat[3] = parameterValue(e_CS.param[3]);
std::valarray<double> Ex ={ 0.0,0.0,0.0 };
std::valarray<double> Ey ={ 0.0,0.0,0.0 };
std::valarray<double> Ez ={ 0.0,0.0,0.0 };
Slvs_QuaternionU(quat[0], quat[1], quat[2], quat[3],
&Ex[0], &Ex[1], &Ex[2]);
Slvs_QuaternionV(quat[0], quat[1], quat[2], quat[3],
&Ey[0], &Ey[1], &Ey[2]);
Slvs_QuaternionN(quat[0], quat[1], quat[2], quat[3],
&Ez[0], &Ez[1], &Ez[2]);
return std::make_tuple(Ex, Ey, Ez);
}
assert(false);
return std::make_tuple(std::valarray<double>(), std::valarray<double>(), std::valarray<double>());
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::valarray<double> SolveSpaceSystem::global3DPos(Slvs_hEntity pointEntityId)
{
std::valarray<double> point ={ 0.0,0.0,0.0 };
Slvs_Entity pointEntity = (*m_entityMemory)[pointEntityId -1];
if ( pointEntity.type == SLVS_E_POINT_IN_2D )
{
std::valarray<double> locPoint ={ 0.0,0.0,0.0 };
locPoint[0] = parameterValue(pointEntity.param[0]);
locPoint[1] = parameterValue(pointEntity.param[1]);
Slvs_Entity e_Plane = (*m_entityMemory)[pointEntity.wrkpl - 1];
std::valarray<double> origin = global3DPos(e_Plane.point[0]);
auto mx = orientationMx(e_Plane.normal);
point = origin + std::get<0>(mx)*locPoint[0] + std::get<1>(mx)*locPoint[1];
}
else if ( pointEntity.type == SLVS_E_POINT_IN_3D )
{
point[0] = parameterValue(pointEntity.param[0]);
point[1] = parameterValue(pointEntity.param[1]);
point[2] = parameterValue(pointEntity.param[2]);
}
return point;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
Slvs_Constraint& SolveSpaceSystem::constraint(Slvs_hConstraint constraintId)
{
return (*m_constraintMemory)[constraintId-1];
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<Slvs_hConstraint> SolveSpaceSystem::failedConstraints() const
{
return (*m_failedConstrMemory);
}
+799
View File
@@ -0,0 +1,799 @@
//-----------------------------------------------------------------------------
// Given a constraint, generate one or more equations in our symbolic algebra
// system to represent that constraint; also various geometric helper
// functions for that.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
const hConstraint ConstraintBase::NO_CONSTRAINT = { 0 };
bool ConstraintBase::HasLabel() const {
switch(type) {
case Type::PT_LINE_DISTANCE:
case Type::PT_PLANE_DISTANCE:
case Type::PT_FACE_DISTANCE:
case Type::PT_PT_DISTANCE:
case Type::PROJ_PT_DISTANCE:
case Type::DIAMETER:
case Type::LENGTH_RATIO:
case Type::LENGTH_DIFFERENCE:
case Type::ANGLE:
case Type::COMMENT:
return true;
default:
return false;
}
}
ExprVector ConstraintBase::VectorsParallel3d(ExprVector a, ExprVector b, hParam p) {
return a.Minus(b.ScaledBy(Expr::From(p)));
}
Expr *ConstraintBase::PointLineDistance(hEntity wrkpl, hEntity hpt, hEntity hln)
{
EntityBase *ln = SK.GetEntity(hln);
EntityBase *a = SK.GetEntity(ln->point[0]);
EntityBase *b = SK.GetEntity(ln->point[1]);
EntityBase *p = SK.GetEntity(hpt);
if(wrkpl.v == EntityBase::FREE_IN_3D.v) {
ExprVector ep = p->PointGetExprs();
ExprVector ea = a->PointGetExprs();
ExprVector eb = b->PointGetExprs();
ExprVector eab = ea.Minus(eb);
Expr *m = eab.Magnitude();
return ((eab.Cross(ea.Minus(ep))).Magnitude())->Div(m);
} else {
Expr *ua, *va, *ub, *vb;
a->PointGetExprsInWorkplane(wrkpl, &ua, &va);
b->PointGetExprsInWorkplane(wrkpl, &ub, &vb);
Expr *du = ua->Minus(ub);
Expr *dv = va->Minus(vb);
Expr *u, *v;
p->PointGetExprsInWorkplane(wrkpl, &u, &v);
Expr *m = ((du->Square())->Plus(dv->Square()))->Sqrt();
Expr *proj = (dv->Times(ua->Minus(u)))->Minus(
(du->Times(va->Minus(v))));
return proj->Div(m);
}
}
Expr *ConstraintBase::PointPlaneDistance(ExprVector p, hEntity hpl) {
ExprVector n;
Expr *d;
SK.GetEntity(hpl)->WorkplaneGetPlaneExprs(&n, &d);
return (p.Dot(n))->Minus(d);
}
Expr *ConstraintBase::Distance(hEntity wrkpl, hEntity hpa, hEntity hpb) {
EntityBase *pa = SK.GetEntity(hpa);
EntityBase *pb = SK.GetEntity(hpb);
ssassert(pa->IsPoint() && pb->IsPoint(),
"Expected two points to measure projected distance between");
if(wrkpl.v == EntityBase::FREE_IN_3D.v) {
// This is true distance
ExprVector ea, eb, eab;
ea = pa->PointGetExprs();
eb = pb->PointGetExprs();
eab = ea.Minus(eb);
return eab.Magnitude();
} else {
// This is projected distance, in the given workplane.
Expr *au, *av, *bu, *bv;
pa->PointGetExprsInWorkplane(wrkpl, &au, &av);
pb->PointGetExprsInWorkplane(wrkpl, &bu, &bv);
Expr *du = au->Minus(bu);
Expr *dv = av->Minus(bv);
return ((du->Square())->Plus(dv->Square()))->Sqrt();
}
}
//-----------------------------------------------------------------------------
// Return the cosine of the angle between two vectors. If a workplane is
// specified, then it's the cosine of their projections into that workplane.
//-----------------------------------------------------------------------------
Expr *ConstraintBase::DirectionCosine(hEntity wrkpl,
ExprVector ae, ExprVector be)
{
if(wrkpl.v == EntityBase::FREE_IN_3D.v) {
Expr *mags = (ae.Magnitude())->Times(be.Magnitude());
return (ae.Dot(be))->Div(mags);
} else {
EntityBase *w = SK.GetEntity(wrkpl);
ExprVector u = w->Normal()->NormalExprsU();
ExprVector v = w->Normal()->NormalExprsV();
Expr *ua = u.Dot(ae);
Expr *va = v.Dot(ae);
Expr *ub = u.Dot(be);
Expr *vb = v.Dot(be);
Expr *maga = (ua->Square()->Plus(va->Square()))->Sqrt();
Expr *magb = (ub->Square()->Plus(vb->Square()))->Sqrt();
Expr *dot = (ua->Times(ub))->Plus(va->Times(vb));
return dot->Div(maga->Times(magb));
}
}
ExprVector ConstraintBase::PointInThreeSpace(hEntity workplane,
Expr *u, Expr *v)
{
EntityBase *w = SK.GetEntity(workplane);
ExprVector ub = w->Normal()->NormalExprsU();
ExprVector vb = w->Normal()->NormalExprsV();
ExprVector ob = w->WorkplaneGetOffsetExprs();
return (ub.ScaledBy(u)).Plus(vb.ScaledBy(v)).Plus(ob);
}
void ConstraintBase::ModifyToSatisfy() {
if(type == Type::ANGLE) {
Vector a = SK.GetEntity(entityA)->VectorGetNum();
Vector b = SK.GetEntity(entityB)->VectorGetNum();
if(other) a = a.ScaledBy(-1);
if(workplane.v != EntityBase::FREE_IN_3D.v) {
a = a.ProjectVectorInto(workplane);
b = b.ProjectVectorInto(workplane);
}
double c = (a.Dot(b))/(a.Magnitude() * b.Magnitude());
valA = acos(c)*180/PI;
} else if(type == Type::PT_ON_LINE) {
EntityBase *eln = SK.GetEntity(entityA);
EntityBase *ea = SK.GetEntity(eln->point[0]);
EntityBase *eb = SK.GetEntity(eln->point[1]);
EntityBase *ep = SK.GetEntity(ptA);
ExprVector exp = ep->PointGetExprsInWorkplane(workplane);
ExprVector exa = ea->PointGetExprsInWorkplane(workplane);
ExprVector exb = eb->PointGetExprsInWorkplane(workplane);
ExprVector exba = exb.Minus(exa);
SK.GetParam(valP)->val = exba.Dot(exp.Minus(exa))->Eval() / exba.Dot(exba)->Eval();
} else {
// We'll fix these ones up by looking at their symbolic equation;
// that means no extra work.
IdList<Equation,hEquation> l = {};
// Generate the equations even if this is a reference dimension
GenerateEquations(&l, /*forReference=*/true);
ssassert(l.n == 1, "Expected constraint to generate a single equation");
// These equations are written in the form f(...) - d = 0, where
// d is the value of the valA.
valA += (l.elem[0].e)->Eval();
l.Clear();
}
}
void ConstraintBase::AddEq(IdList<Equation,hEquation> *l, Expr *expr, int index) const
{
Equation eq;
eq.e = expr;
eq.h = h.equation(index);
l->Add(&eq);
}
void ConstraintBase::AddEq(IdList<Equation,hEquation> *l, const ExprVector &v,
int baseIndex) const {
AddEq(l, v.x, baseIndex);
AddEq(l, v.y, baseIndex + 1);
if(workplane.v == EntityBase::FREE_IN_3D.v) {
AddEq(l, v.z, baseIndex + 2);
}
}
void ConstraintBase::Generate(IdList<Param,hParam> *l) {
switch(type) {
case Type::PARALLEL:
case Type::CUBIC_LINE_TANGENT:
// Add new parameter only when we operate in 3d space
if(workplane.v != EntityBase::FREE_IN_3D.v) break;
// fallthrough
case Type::SAME_ORIENTATION:
case Type::PT_ON_LINE: {
Param p = {};
valP = h.param(0);
p.h = valP;
l->Add(&p);
break;
}
default:
break;
}
}
void ConstraintBase::GenerateEquations(IdList<Equation,hEquation> *l,
bool forReference) const {
if(reference && !forReference) return;
Expr *exA = Expr::From(valA);
switch(type) {
case Type::PT_PT_DISTANCE:
AddEq(l, Distance(workplane, ptA, ptB)->Minus(exA), 0);
return;
case Type::PROJ_PT_DISTANCE: {
ExprVector pA = SK.GetEntity(ptA)->PointGetExprs(),
pB = SK.GetEntity(ptB)->PointGetExprs(),
dp = pB.Minus(pA);
ExprVector pp = SK.GetEntity(entityA)->VectorGetExprs();
pp = pp.WithMagnitude(Expr::From(1.0));
AddEq(l, (dp.Dot(pp))->Minus(exA), 0);
return;
}
case Type::PT_LINE_DISTANCE:
AddEq(l,
PointLineDistance(workplane, ptA, entityA)->Minus(exA), 0);
return;
case Type::PT_PLANE_DISTANCE: {
ExprVector pt = SK.GetEntity(ptA)->PointGetExprs();
AddEq(l, (PointPlaneDistance(pt, entityA))->Minus(exA), 0);
return;
}
case Type::PT_FACE_DISTANCE: {
ExprVector pt = SK.GetEntity(ptA)->PointGetExprs();
EntityBase *f = SK.GetEntity(entityA);
ExprVector p0 = f->FaceGetPointExprs();
ExprVector n = f->FaceGetNormalExprs();
AddEq(l, (pt.Minus(p0)).Dot(n)->Minus(exA), 0);
return;
}
case Type::EQUAL_LENGTH_LINES: {
EntityBase *a = SK.GetEntity(entityA);
EntityBase *b = SK.GetEntity(entityB);
AddEq(l, Distance(workplane, a->point[0], a->point[1])->Minus(
Distance(workplane, b->point[0], b->point[1])), 0);
return;
}
// These work on distance squared, since the pt-line distances are
// signed, and we want the absolute value.
case Type::EQ_LEN_PT_LINE_D: {
EntityBase *forLen = SK.GetEntity(entityA);
Expr *d1 = Distance(workplane, forLen->point[0], forLen->point[1]);
Expr *d2 = PointLineDistance(workplane, ptA, entityB);
AddEq(l, (d1->Square())->Minus(d2->Square()), 0);
return;
}
case Type::EQ_PT_LN_DISTANCES: {
Expr *d1 = PointLineDistance(workplane, ptA, entityA);
Expr *d2 = PointLineDistance(workplane, ptB, entityB);
AddEq(l, (d1->Square())->Minus(d2->Square()), 0);
return;
}
case Type::LENGTH_RATIO: {
EntityBase *a = SK.GetEntity(entityA);
EntityBase *b = SK.GetEntity(entityB);
Expr *la = Distance(workplane, a->point[0], a->point[1]);
Expr *lb = Distance(workplane, b->point[0], b->point[1]);
AddEq(l, (la->Div(lb))->Minus(exA), 0);
return;
}
case Type::LENGTH_DIFFERENCE: {
EntityBase *a = SK.GetEntity(entityA);
EntityBase *b = SK.GetEntity(entityB);
Expr *la = Distance(workplane, a->point[0], a->point[1]);
Expr *lb = Distance(workplane, b->point[0], b->point[1]);
AddEq(l, (la->Minus(lb))->Minus(exA), 0);
return;
}
case Type::DIAMETER: {
EntityBase *circle = SK.GetEntity(entityA);
Expr *r = circle->CircleGetRadiusExpr();
AddEq(l, (r->Times(Expr::From(2)))->Minus(exA), 0);
return;
}
case Type::EQUAL_RADIUS: {
EntityBase *c1 = SK.GetEntity(entityA);
EntityBase *c2 = SK.GetEntity(entityB);
AddEq(l, (c1->CircleGetRadiusExpr())->Minus(
c2->CircleGetRadiusExpr()), 0);
return;
}
case Type::EQUAL_LINE_ARC_LEN: {
EntityBase *line = SK.GetEntity(entityA),
*arc = SK.GetEntity(entityB);
// Get the line length
ExprVector l0 = SK.GetEntity(line->point[0])->PointGetExprs(),
l1 = SK.GetEntity(line->point[1])->PointGetExprs();
Expr *ll = (l1.Minus(l0)).Magnitude();
// And get the arc radius, and the cosine of its angle
EntityBase *ao = SK.GetEntity(arc->point[0]),
*as = SK.GetEntity(arc->point[1]),
*af = SK.GetEntity(arc->point[2]);
ExprVector aos = (as->PointGetExprs()).Minus(ao->PointGetExprs()),
aof = (af->PointGetExprs()).Minus(ao->PointGetExprs());
Expr *r = aof.Magnitude();
ExprVector n = arc->Normal()->NormalExprsN();
ExprVector u = aos.WithMagnitude(Expr::From(1.0));
ExprVector v = n.Cross(u);
// so in our new csys, we start at (1, 0, 0)
Expr *costheta = aof.Dot(u)->Div(r);
Expr *sintheta = aof.Dot(v)->Div(r);
double thetas, thetaf, dtheta;
arc->ArcGetAngles(&thetas, &thetaf, &dtheta);
Expr *theta;
if(dtheta < 3*PI/4) {
theta = costheta->ACos();
} else if(dtheta < 5*PI/4) {
// As the angle crosses pi, cos theta is not invertible;
// so use the sine to stop blowing up
theta = Expr::From(PI)->Minus(sintheta->ASin());
} else {
theta = (Expr::From(2*PI))->Minus(costheta->ACos());
}
// And write the equation; r*theta = L
AddEq(l, (r->Times(theta))->Minus(ll), 0);
return;
}
case Type::POINTS_COINCIDENT: {
EntityBase *a = SK.GetEntity(ptA);
EntityBase *b = SK.GetEntity(ptB);
if(workplane.v == EntityBase::FREE_IN_3D.v) {
ExprVector pa = a->PointGetExprs();
ExprVector pb = b->PointGetExprs();
AddEq(l, pa.x->Minus(pb.x), 0);
AddEq(l, pa.y->Minus(pb.y), 1);
AddEq(l, pa.z->Minus(pb.z), 2);
} else {
Expr *au, *av;
Expr *bu, *bv;
a->PointGetExprsInWorkplane(workplane, &au, &av);
b->PointGetExprsInWorkplane(workplane, &bu, &bv);
AddEq(l, au->Minus(bu), 0);
AddEq(l, av->Minus(bv), 1);
}
return;
}
case Type::PT_IN_PLANE:
// This one works the same, whether projected or not.
AddEq(l, PointPlaneDistance(
SK.GetEntity(ptA)->PointGetExprs(), entityA), 0);
return;
case Type::PT_ON_FACE: {
// a plane, n dot (p - p0) = 0
ExprVector p = SK.GetEntity(ptA)->PointGetExprs();
EntityBase *f = SK.GetEntity(entityA);
ExprVector p0 = f->FaceGetPointExprs();
ExprVector n = f->FaceGetNormalExprs();
AddEq(l, (p.Minus(p0)).Dot(n), 0);
return;
}
case Type::PT_ON_LINE: {
EntityBase *ln = SK.GetEntity(entityA);
EntityBase *a = SK.GetEntity(ln->point[0]);
EntityBase *b = SK.GetEntity(ln->point[1]);
EntityBase *p = SK.GetEntity(ptA);
ExprVector ep = p->PointGetExprsInWorkplane(workplane);
ExprVector ea = a->PointGetExprsInWorkplane(workplane);
ExprVector eb = b->PointGetExprsInWorkplane(workplane);
ExprVector ptOnLine = ea.Plus(eb.Minus(ea).ScaledBy(Expr::From(valP)));
ExprVector eq = ptOnLine.Minus(ep);
AddEq(l, eq);
return;
}
case Type::PT_ON_CIRCLE: {
// This actually constrains the point to lie on the cylinder.
EntityBase *circle = SK.GetEntity(entityA);
ExprVector center = SK.GetEntity(circle->point[0])->PointGetExprs();
ExprVector pt = SK.GetEntity(ptA)->PointGetExprs();
EntityBase *normal = SK.GetEntity(circle->normal);
ExprVector u = normal->NormalExprsU(),
v = normal->NormalExprsV();
Expr *du = (center.Minus(pt)).Dot(u),
*dv = (center.Minus(pt)).Dot(v);
Expr *r = circle->CircleGetRadiusExpr();
AddEq(l, du->Square()->Plus(dv->Square())->Sqrt()->Minus(r), 0);
return;
}
case Type::AT_MIDPOINT:
if(workplane.v == EntityBase::FREE_IN_3D.v) {
EntityBase *ln = SK.GetEntity(entityA);
ExprVector a = SK.GetEntity(ln->point[0])->PointGetExprs();
ExprVector b = SK.GetEntity(ln->point[1])->PointGetExprs();
ExprVector m = (a.Plus(b)).ScaledBy(Expr::From(0.5));
if(ptA.v) {
ExprVector p = SK.GetEntity(ptA)->PointGetExprs();
AddEq(l, (m.x)->Minus(p.x), 0);
AddEq(l, (m.y)->Minus(p.y), 1);
AddEq(l, (m.z)->Minus(p.z), 2);
} else {
AddEq(l, PointPlaneDistance(m, entityB), 0);
}
} else {
EntityBase *ln = SK.GetEntity(entityA);
EntityBase *a = SK.GetEntity(ln->point[0]);
EntityBase *b = SK.GetEntity(ln->point[1]);
Expr *au, *av, *bu, *bv;
a->PointGetExprsInWorkplane(workplane, &au, &av);
b->PointGetExprsInWorkplane(workplane, &bu, &bv);
Expr *mu = Expr::From(0.5)->Times(au->Plus(bu));
Expr *mv = Expr::From(0.5)->Times(av->Plus(bv));
if(ptA.v) {
EntityBase *p = SK.GetEntity(ptA);
Expr *pu, *pv;
p->PointGetExprsInWorkplane(workplane, &pu, &pv);
AddEq(l, pu->Minus(mu), 0);
AddEq(l, pv->Minus(mv), 1);
} else {
ExprVector m = PointInThreeSpace(workplane, mu, mv);
AddEq(l, PointPlaneDistance(m, entityB), 0);
}
}
return;
case Type::SYMMETRIC:
if(workplane.v == EntityBase::FREE_IN_3D.v) {
EntityBase *plane = SK.GetEntity(entityA);
EntityBase *ea = SK.GetEntity(ptA);
EntityBase *eb = SK.GetEntity(ptB);
ExprVector a = ea->PointGetExprs();
ExprVector b = eb->PointGetExprs();
// The midpoint of the line connecting the symmetric points
// lies on the plane of the symmetry.
ExprVector m = (a.Plus(b)).ScaledBy(Expr::From(0.5));
AddEq(l, PointPlaneDistance(m, plane->h), 0);
// And projected into the plane of symmetry, the points are
// coincident.
Expr *au, *av, *bu, *bv;
ea->PointGetExprsInWorkplane(plane->h, &au, &av);
eb->PointGetExprsInWorkplane(plane->h, &bu, &bv);
AddEq(l, au->Minus(bu), 1);
AddEq(l, av->Minus(bv), 2);
} else {
EntityBase *plane = SK.GetEntity(entityA);
EntityBase *a = SK.GetEntity(ptA);
EntityBase *b = SK.GetEntity(ptB);
Expr *au, *av, *bu, *bv;
a->PointGetExprsInWorkplane(workplane, &au, &av);
b->PointGetExprsInWorkplane(workplane, &bu, &bv);
Expr *mu = Expr::From(0.5)->Times(au->Plus(bu));
Expr *mv = Expr::From(0.5)->Times(av->Plus(bv));
ExprVector m = PointInThreeSpace(workplane, mu, mv);
AddEq(l, PointPlaneDistance(m, plane->h), 0);
// Construct a vector within the workplane that is normal
// to the symmetry pane's normal (i.e., that lies in the
// plane of symmetry). The line connecting the points is
// perpendicular to that constructed vector.
EntityBase *w = SK.GetEntity(workplane);
ExprVector u = w->Normal()->NormalExprsU();
ExprVector v = w->Normal()->NormalExprsV();
ExprVector pa = a->PointGetExprs();
ExprVector pb = b->PointGetExprs();
ExprVector n;
Expr *d;
plane->WorkplaneGetPlaneExprs(&n, &d);
AddEq(l, (n.Cross(u.Cross(v))).Dot(pa.Minus(pb)), 1);
}
return;
case Type::SYMMETRIC_HORIZ:
case Type::SYMMETRIC_VERT: {
ssassert(workplane.v != Entity::FREE_IN_3D.v,
"Unexpected horizontal/vertical symmetric constraint in 3d");
EntityBase *a = SK.GetEntity(ptA);
EntityBase *b = SK.GetEntity(ptB);
Expr *au, *av, *bu, *bv;
a->PointGetExprsInWorkplane(workplane, &au, &av);
b->PointGetExprsInWorkplane(workplane, &bu, &bv);
if(type == Type::SYMMETRIC_HORIZ) {
AddEq(l, av->Minus(bv), 0);
AddEq(l, au->Plus(bu), 1);
} else {
AddEq(l, au->Minus(bu), 0);
AddEq(l, av->Plus(bv), 1);
}
return;
}
case Type::SYMMETRIC_LINE: {
EntityBase *pa = SK.GetEntity(ptA);
EntityBase *pb = SK.GetEntity(ptB);
Expr *pau, *pav, *pbu, *pbv;
pa->PointGetExprsInWorkplane(workplane, &pau, &pav);
pb->PointGetExprsInWorkplane(workplane, &pbu, &pbv);
EntityBase *ln = SK.GetEntity(entityA);
EntityBase *la = SK.GetEntity(ln->point[0]);
EntityBase *lb = SK.GetEntity(ln->point[1]);
Expr *lau, *lav, *lbu, *lbv;
la->PointGetExprsInWorkplane(workplane, &lau, &lav);
lb->PointGetExprsInWorkplane(workplane, &lbu, &lbv);
Expr *dpu = pbu->Minus(pau), *dpv = pbv->Minus(pav);
Expr *dlu = lbu->Minus(lau), *dlv = lbv->Minus(lav);
// The line through the points is perpendicular to the line
// of symmetry.
AddEq(l, (dlu->Times(dpu))->Plus(dlv->Times(dpv)), 0);
// And the signed distances of the points to the line are
// equal in magnitude and opposite in sign, so sum to zero
Expr *dista = (dlv->Times(lau->Minus(pau)))->Minus(
(dlu->Times(lav->Minus(pav))));
Expr *distb = (dlv->Times(lau->Minus(pbu)))->Minus(
(dlu->Times(lav->Minus(pbv))));
AddEq(l, dista->Plus(distb), 1);
return;
}
case Type::HORIZONTAL:
case Type::VERTICAL: {
ssassert(workplane.v != Entity::FREE_IN_3D.v,
"Unexpected horizontal/vertical constraint in 3d");
hEntity ha, hb;
if(entityA.v) {
EntityBase *e = SK.GetEntity(entityA);
ha = e->point[0];
hb = e->point[1];
} else {
ha = ptA;
hb = ptB;
}
EntityBase *a = SK.GetEntity(ha);
EntityBase *b = SK.GetEntity(hb);
Expr *au, *av, *bu, *bv;
a->PointGetExprsInWorkplane(workplane, &au, &av);
b->PointGetExprsInWorkplane(workplane, &bu, &bv);
AddEq(l, (type == Type::HORIZONTAL) ? av->Minus(bv) : au->Minus(bu), 0);
return;
}
case Type::SAME_ORIENTATION: {
EntityBase *a = SK.GetEntity(entityA);
EntityBase *b = SK.GetEntity(entityB);
ExprVector au = a->NormalExprsU(),
an = a->NormalExprsN();
ExprVector bu = b->NormalExprsU(),
bv = b->NormalExprsV(),
bn = b->NormalExprsN();
ExprVector eq = VectorsParallel3d(an, bn, valP);
AddEq(l, eq.x, 0);
AddEq(l, eq.y, 1);
AddEq(l, eq.z, 2);
Expr *d1 = au.Dot(bv);
Expr *d2 = au.Dot(bu);
// Allow either orientation for the coordinate system, depending
// on how it was drawn.
if(fabs(d1->Eval()) < fabs(d2->Eval())) {
AddEq(l, d1, 3);
} else {
AddEq(l, d2, 3);
}
return;
}
case Type::PERPENDICULAR:
case Type::ANGLE: {
EntityBase *a = SK.GetEntity(entityA);
EntityBase *b = SK.GetEntity(entityB);
ExprVector ae = a->VectorGetExprs();
ExprVector be = b->VectorGetExprs();
if(other) ae = ae.ScaledBy(Expr::From(-1));
Expr *c = DirectionCosine(workplane, ae, be);
if(type == Type::ANGLE) {
// The direction cosine is equal to the cosine of the
// specified angle
Expr *rads = exA->Times(Expr::From(PI/180)),
*rc = rads->Cos();
double arc = fabs(rc->Eval());
// avoid false detection of inconsistent systems by gaining
// up as the difference in dot products gets small at small
// angles; doubles still have plenty of precision, only
// problem is that rank test
Expr *mult = Expr::From(arc > 0.99 ? 0.01/(1.00001 - arc) : 1);
AddEq(l, (c->Minus(rc))->Times(mult), 0);
} else {
// The dot product (and therefore the direction cosine)
// is equal to zero, perpendicular.
AddEq(l, c, 0);
}
return;
}
case Type::EQUAL_ANGLE: {
EntityBase *a = SK.GetEntity(entityA);
EntityBase *b = SK.GetEntity(entityB);
EntityBase *c = SK.GetEntity(entityC);
EntityBase *d = SK.GetEntity(entityD);
ExprVector ae = a->VectorGetExprs();
ExprVector be = b->VectorGetExprs();
ExprVector ce = c->VectorGetExprs();
ExprVector de = d->VectorGetExprs();
if(other) ae = ae.ScaledBy(Expr::From(-1));
Expr *cab = DirectionCosine(workplane, ae, be);
Expr *ccd = DirectionCosine(workplane, ce, de);
AddEq(l, cab->Minus(ccd), 0);
return;
}
case Type::ARC_LINE_TANGENT: {
EntityBase *arc = SK.GetEntity(entityA);
EntityBase *line = SK.GetEntity(entityB);
ExprVector ac = SK.GetEntity(arc->point[0])->PointGetExprs();
ExprVector ap =
SK.GetEntity(arc->point[other ? 2 : 1])->PointGetExprs();
ExprVector ld = line->VectorGetExprs();
// The line is perpendicular to the radius
AddEq(l, ld.Dot(ac.Minus(ap)), 0);
return;
}
case Type::CUBIC_LINE_TANGENT: {
EntityBase *cubic = SK.GetEntity(entityA);
EntityBase *line = SK.GetEntity(entityB);
ExprVector a;
if(other) {
a = cubic->CubicGetFinishTangentExprs();
} else {
a = cubic->CubicGetStartTangentExprs();
}
ExprVector b = line->VectorGetExprs();
if(workplane.v == EntityBase::FREE_IN_3D.v) {
ExprVector eq = VectorsParallel3d(a, b, valP);
AddEq(l, eq);
} else {
EntityBase *w = SK.GetEntity(workplane);
ExprVector wn = w->Normal()->NormalExprsN();
AddEq(l, (a.Cross(b)).Dot(wn), 0);
}
return;
}
case Type::CURVE_CURVE_TANGENT: {
bool parallel = true;
int i;
ExprVector dir[2];
for(i = 0; i < 2; i++) {
EntityBase *e = SK.GetEntity((i == 0) ? entityA : entityB);
bool oth = (i == 0) ? other : other2;
if(e->type == Entity::Type::ARC_OF_CIRCLE) {
ExprVector center, endpoint;
center = SK.GetEntity(e->point[0])->PointGetExprs();
endpoint =
SK.GetEntity(e->point[oth ? 2 : 1])->PointGetExprs();
dir[i] = endpoint.Minus(center);
// We're using the vector from the center of the arc to
// an endpoint; so that's normal to the tangent, not
// parallel.
parallel = !parallel;
} else if(e->type == Entity::Type::CUBIC) { // BRANCH_ALWAYS_TAKEN
if(oth) {
dir[i] = e->CubicGetFinishTangentExprs();
} else {
dir[i] = e->CubicGetStartTangentExprs();
}
} else {
ssassert(false, "Unexpected entity types for CURVE_CURVE_TANGENT");
}
}
if(parallel) {
EntityBase *w = SK.GetEntity(workplane);
ExprVector wn = w->Normal()->NormalExprsN();
AddEq(l, ((dir[0]).Cross(dir[1])).Dot(wn), 0);
} else {
AddEq(l, (dir[0]).Dot(dir[1]), 0);
}
return;
}
case Type::PARALLEL: {
EntityBase *ea = SK.GetEntity(entityA), *eb = SK.GetEntity(entityB);
ExprVector a = ea->VectorGetExprsInWorkplane(workplane);
ExprVector b = eb->VectorGetExprsInWorkplane(workplane);
if(workplane.v == EntityBase::FREE_IN_3D.v) {
ExprVector eq = VectorsParallel3d(a, b, valP);
AddEq(l, eq);
} else {
// We use expressions written in workplane csys, so we can assume the workplane
// normal is (0, 0, 1). We can write the equation as:
// Expr *eq = a.Cross(b).Dot(ExprVector::From(0.0, 0.0, 1.0));
// but this will just result in elimination of x and y terms after dot product.
// We can only use the z expression:
// Expr *eq = a.Cross(b).z;
// but it's more efficient to write it in the terms of pseudo-scalar product:
Expr *eq = (a.x->Times(b.y))->Minus(a.y->Times(b.x));
AddEq(l, eq, 0);
}
return;
}
case Type::WHERE_DRAGGED: {
EntityBase *ep = SK.GetEntity(ptA);
if(workplane.v == EntityBase::FREE_IN_3D.v) {
ExprVector ev = ep->PointGetExprs();
Vector v = ep->PointGetNum();
AddEq(l, ev.x->Minus(Expr::From(v.x)), 0);
AddEq(l, ev.y->Minus(Expr::From(v.y)), 1);
AddEq(l, ev.z->Minus(Expr::From(v.z)), 2);
} else {
Expr *u, *v;
ep->PointGetExprsInWorkplane(workplane, &u, &v);
AddEq(l, u->Minus(Expr::From(u->Eval())), 0);
AddEq(l, v->Minus(Expr::From(v->Eval())), 1);
}
return;
}
case Type::COMMENT:
return;
}
ssassert(false, "Unexpected constraint ID");
}

Some files were not shown because too many files have changed in this diff Show More