diff --git a/CHANGELOG.md b/CHANGELOG.md
index 59d6e73..eead0e4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Sketch / 2D Topology
+- **Arc segments:** Each arc is a single sketch edge (not two chained edges). New arcs split existing straight and arc edges at interior crossings; arcs are subdivided when crossed. Face walking uses arc tangents at endpoints. The Arc Segment tool shows a line rubber-band after the first click and an arc preview after the second; optional **Add midpoint nodes** in the Arc tool Options adds a geometric-mid snap node (same setting as line edges). Documented in [usage-sketch.md](docs/usage-sketch.md#arc-segment-creation-tool). **Known open bug:** slot + circle intersection splitting — see [bugs.md](docs/bugs.md).
- For the **Add line edge** and **Add multi-line edge** tools, a new setting **"Add midpoints to new linear edges"** (Settings > Sketch; also exposed in the tool Options pane) controls whether automatic midpoint nodes are created on new straight edges. Default is **off** (no midpoints), so new edges do not get mid snap targets unless the option is enabled. Existing edges with mids are unaffected; JSON load/save supports edges without mids for compatibility.
- **Add line edge** Options: **Place from center** — first click sets the edge midpoint; second click or Tab length uses the **full** edge length (symmetric placement). Documented in the user guide with **?** help links in the Options panel.
diff --git a/agents/drafts/issues/active/gh-165-sketch-slot-circle-intersection-splitting.md b/agents/drafts/issues/active/gh-165-sketch-slot-circle-intersection-splitting.md
new file mode 100644
index 0000000..93f54db
--- /dev/null
+++ b/agents/drafts/issues/active/gh-165-sketch-slot-circle-intersection-splitting.md
@@ -0,0 +1,60 @@
+# Sketch: slot + circle edge splitting at intersections
+
+---
+github_issue: 165
+status: active
+---
+
+**Opened on GitHub:** https://github.com/trailcode/EzyCad/issues/165
+
+---
+
+## Title (GitHub)
+
+Sketch: slot + circle edges split or highlight incorrectly at intersections
+
+## Body (GitHub)
+
+### Summary
+
+When a slot (two horizontal lines + end-cap arcs) is combined with a circle that crosses the horizontals, edge splitting and display can be wrong: one horizontal may split while the other does not, circle arcs may fail to split at line crossings, and some segments show incorrect highlight colors.
+
+### Problem
+
+- Adding end caps can spuriously split slot horizontals near the slot center (before any circle is added).
+- Adding a circle that should cross both horizontals at two points each may not produce symmetric topology.
+- Face update / edge styling can show inconsistent colors (e.g. white vs green segments).
+
+### Likely causes (investigated, not yet fixed)
+
+- `segment_arc_intersections_2d` (`utl_geom.cpp`): `GeomAPI_ExtremaCurveCurve` may return segment points paired with unrelated arc-foot points on the full circle outside the trimmed semicircle.
+- `add_arc_circle_edges` / circle finalize argument order vs `GC_MakeArcOfCircle` node index conventions.
+- Face walker parallel-edge loops (partial guard in `sketch_topo.cpp`).
+
+### Acceptance criteria
+
+- [ ] Slot horizontals remain unsplit after end caps are added (only endpoint nodes at corners).
+- [ ] Circle crossing both horizontals splits each into three segments at the two intersection points.
+- [ ] Circle arcs split at horizontal crossings; edge highlight colors consistent.
+- [ ] `update_faces()` completes without infinite loop on slot + circle geometry.
+- [ ] Regression tests: `SlotHorizontalLinesUnsplitBeforeCircleArcs`, `ArcSplit_CircleCrossesSlotLines`.
+
+### Files / areas
+
+- `src/utl_geom.cpp` — `segment_arc_intersections_2d`
+- `src/sketch_edges.cpp` — `add_arc_circle_edges`
+- `src/sketch_tools.cpp` — `finalize_circle_`, slot finalize
+- `src/sketch_topo.cpp` — face walker
+- `docs/bugs.md` — user-visible note
+- `tests/sketch_tests.cpp`
+
+### Related
+
+- Documented in `docs/bugs.md`
+- Arc topology PR (single edge per arc, intersection splitting) — separate work; does not resolve this bug
+
+### Test plan
+
+- [ ] Reproduce: slot tool + circle tool on same sketch, circle diameter larger than slot height.
+- [ ] Verify edge counts per horizontal line before/after circle.
+- [ ] Run targeted `EzyCad_tests` filters above.
diff --git a/agents/drafts/prs/active/gh-166-sketch-arc-topology-improve.md b/agents/drafts/prs/active/gh-166-sketch-arc-topology-improve.md
new file mode 100644
index 0000000..16c3e50
--- /dev/null
+++ b/agents/drafts/prs/active/gh-166-sketch-arc-topology-improve.md
@@ -0,0 +1,47 @@
+# PR - Sketch arc topology improvements
+
+---
+github_pr: 166
+status: active
+paired_draft: ../issues/active/gh-165-sketch-slot-circle-intersection-splitting.md
+---
+
+**Opened on GitHub:** https://github.com/trailcode/EzyCad/pull/166
+
+Sketch arc topology: single edge per arc, intersection splitting, tangent face walker
+
+## Summary
+
+- Refactor arc sketch edges to **one graph edge per arc** (replacing two-edge internal representation).
+- Split existing straight and arc edges when new arcs/lines cross them; subdivide new arcs at interior intersections.
+- Face walking uses **arc tangents** at endpoints (fixes semicircle / slot cap loops).
+- Arc Segment tool: rubber-band line after first click; **Add midpoint nodes** option adds geometric-mid snap node when enabled.
+- Document arc behavior in `usage-sketch.md`; record open slot+circle bug in `bugs.md`.
+
+## Files Changed
+
+- `src/sketch_edge.cpp`, `src/sketch_edge.h`, `src/sketch_edges.cpp` — single arc edge, intersection split on add
+- `src/sketch_topo.cpp`, `src/sketch_topo.h` — tangent-based face walker, arc/line split helpers, walk guard
+- `src/utl_geom.cpp`, `src/utl_geom.h` — arc-line/arc-arc intersection helpers
+- `src/sketch_tools.cpp`, `src/gui_mode.cpp` — arc preview, midpoint option in arc mode
+- `src/sketch_delta.cpp`, `src/sketch_json.cpp`, `src/sketch_operations.cpp` — arc model migration
+- `tests/sketch_tests.cpp` — arc split, semicircle walker, midpoint option tests
+- `docs/usage-sketch.md`, `docs/bugs.md`, `CHANGELOG.md`
+
+## Related
+
+- Issue: https://github.com/trailcode/EzyCad/issues/165 (slot + circle bug — **not** fixed by this PR)
+- Does **not** fix slot + circle intersection splitting; tracked separately.
+
+## Test Plan
+
+- [ ] Build `EzyCad_tests` (Debug).
+- [ ] Run `Sketch_test.*Arc*`, `Sketch_test.*Semicircle*`, `Sketch_test.UpdateFaces_FaceWithArcs`.
+- [ ] Manual: arc tool three-click flow, preview rubber band, optional midpoint node.
+- [ ] Manual: line crossing arc splits both; slot + circle — confirm known bug still present (see issue).
+- [ ] `sphinx-build -b html -W docs docs/_build`
+
+## Notes
+
+- `Walk_key` step cap in face walker prevents infinite loop on some parallel-edge cases; turn-rule improvement may follow.
+- Circle finalize still uses legacy `add_arc_circle_(points[0], points[2], points[1])` call pattern; may need cleanup with slot+circle fix.
diff --git a/docs/bugs.md b/docs/bugs.md
index b3a4817..da00abf 100644
--- a/docs/bugs.md
+++ b/docs/bugs.md
@@ -37,4 +37,9 @@
* Arc function needs to accept tab for radius.
* Dist tab dialog should state if it is a radius or diameter for arc.*
* Dist tab dialog should state if it is a 1/2 dist or full, for edge tool.
-* **Wasm / OCCT 8.x GLES shading regression:** With OCCT **8.0.0.p1** (`V8_0_0_p1`) and `TKOpenGles`, new solids and extrude previews often render wireframe-only or with partial face shading in the browser. Native desktop (OCCT 8 + `TKOpenGl`) is unaffected. Rebuilding against **7.9.3** (`scripts/build-occt-793-wasm.ps1`) restores correct shading. Workarounds in EzyCad (`SetMaterial` + `Redisplay`, `refresh_shape_shading_`, bisect toggles in `occt_view_wasm_bisect.h`) do not fix the root cause on OCCT 8. Use 7.9.3 for wasm builds and demo publishing until upstream fixes GLES presentation.
+* **Sketch: slot + circle — edges split / colored incorrectly at intersections.** Draw a slot (two horizontals + end-cap arcs), then add a circle that crosses both horizontals. Observed: one horizontal may split while the other does not; circle arcs may not split at line crossings; some edge segments show the wrong highlight color (e.g. white vs green). Caps can spuriously split the horizontals near the slot center before the circle is added. Likely causes investigated (fix attempted and reverted; still open):
+ - `segment_arc_intersections_2d` (`utl_geom.cpp`) uses `GeomAPI_ExtremaCurveCurve` and returns `p_on_seg` while validating arc parameter from `p_on_arc`. Extrema can pair a segment foot with a nearby point on the *full* circle outside the trimmed semicircle, yielding false interior hits (e.g. on the slot centerline when adding the right cap).
+ - `add_arc_circle_edges` (`sketch_edges.cpp`) may use inconsistent node indices for intersection/knot logic vs `GC_MakeArcOfCircle` (vector is `[start, pick, end]` from `Sketch::add_arc_circle_`; `pt_end` / knot end node must be `node_idxs[2]`, not `[1]`). A long-standing knot bug masked wrong argument order in circle finalize (`sketch_tools.cpp`: `add_arc_circle_(points[0], points[2], points[1])` passes bottom/top as `pt_b` where the API expects arc *end*, with pick on the opposite side).
+ - `update_faces()` face walker can loop on parallel shared edges (e.g. slot horizontals); a `Walk_key` step cap was added in `sketch_topo.cpp` as a guard — may hide topology errors rather than fix turn logic.
+ - Relevant tests (if re-added): `SlotHorizontalLinesUnsplitBeforeCircleArcs`, `ArcSplit_CircleCrossesSlotLines` in `tests/sketch_tests.cpp`. Use `slot_c` perpendicular to the slot centerline (e.g. `(15, 5)` for `slot_b = (15, 0)`), not `(0, 5)`, to match the slot tool.
+* Wasm / OCCT 8.x GLES shading regression: With OCCT **8.0.0.p1** (`V8_0_0_p1`) and `TKOpenGles`, new solids and extrude previews often render wireframe-only or with partial face shading in the browser. Native desktop (OCCT 8 + `TKOpenGl`) is unaffected. Rebuilding against **7.9.3** (`scripts/build-occt-793-wasm.ps1`) restores correct shading. Workarounds in EzyCad (`SetMaterial` + `Redisplay`, `refresh_shape_shading_`, bisect toggles in `occt_view_wasm_bisect.h`) do not fix the root cause on OCCT 8. Use 7.9.3 for wasm builds and demo publishing until upstream fixes GLES presentation.
diff --git a/docs/usage-sketch.md b/docs/usage-sketch.md
index b3241af..b4ee513 100644
--- a/docs/usage-sketch.md
+++ b/docs/usage-sketch.md
@@ -47,7 +47,7 @@ While you draw or place points in sketch mode, EzyCad helps you align to existin
| **Snap guides** | **Snap guide mode**: *Traditional* (local markers at guide intersections), *Fullscreen* (view-spanning axis lines), or *Both*. **Snap guide color (node)** is used when both X and Y align to the same vertex; **Snap guide color (axis)** when only one axis aligns. A separate checkbox **All co-axial nodes** (in the sketch Options panel and in Settings) enables *global* mode: when on, full horizontal and vertical guide lines + markers are shown for *all* nodes in the current sketch and all other visible sketches (the complete set of co-axial alignments). When off (default), only the closest node per active axis is annotated (classic closest-relative behavior). |
| **Axis alignment** | Near a snap target, the pick can align to that point's **X** or **Y** on the sketch plane; guides show which axis is active. When **both** axes align to the **same** point, the cursor **locks to that vertex**. |
| **Mid-point snap (Add node)** | A click near a **straight** edge (not at its ends) snaps onto the segment and **splits** it at commit time (see [Add node tool](#add-node-tool)). Separate from vertex lock. |
-| **Automatic splitting on edge intersections** | When you add a new straight (linear) edge using the Line Edge tool or Multi-Line Edge tool, if it crosses or touches the interior of any existing straight edge, the existing edge is automatically split at the intersection point. The new edge is also subdivided into atomic segments where needed. The same splitting occurs when an endpoint of the new edge snaps to the midpoint of an existing edge. This produces correct T-junctions (3 edges), crossings (4 edges), and cleanly divided faces from a single sketch. Arcs have special internal handling and do not trigger the same linear splits. |
+| **Automatic splitting on edge intersections** | When you add a new straight (linear) edge using the Line Edge tool or Multi-Line Edge tool, if it crosses or touches the interior of any existing straight edge, the existing edge is automatically split at the intersection point. The new edge is also subdivided into atomic segments where needed. The same splitting occurs when an endpoint of the new edge snaps to the midpoint of an existing edge. When you add an **arc segment**, existing straight and arc edges are split at interior crossings, and the new arc is subdivided at intersection points on its interior. This produces correct T-junctions, crossings, and cleanly divided faces from a single sketch. |
| **Other visible sketches** | Nodes from **other visible sketches** are projected onto the current sketch plane and act as snap targets (same distance rules). Useful for multi-sketch layouts and tools such as **polar duplicate** that pick sketch points. |
| **Operational axis mode** | While an operational axis is **defined** and **Operational axis** mode is active (mirror/revolve phase), sketch snap and permanent **+** node markers are **suppressed** so edge and face selection stays clear. Normal snapping applies again after **Clear axis** or when you leave the tool. Axis placement (before the axis exists) still uses snap. |
@@ -123,8 +123,8 @@ While the **Line Edge** tool is active, the Options panel (under the **Options**
When checked, each new straight edge created with this tool (and with **Add multi-line edge**) gets an automatic **midpoint node** at the center of the segment. That node is a snap target but does not show a **+** marker and is not listed under **Nodes** in the [Sketch List](usage.md#sketch-list).
- Default is **off** (no midpoint nodes on new edges).
-- The same preference is available globally and persistently in **Settings -> Sketch -> Add midpoints to new linear edges** (saved as `gui.add_mid_pt_edges`). The Options checkbox mirrors that setting for the current session while you use line tools.
-- Only affects **future** edges from the Line and Multi-line tools. Existing edges and midpoints loaded from a project are unchanged.
+- The same preference is available globally and persistently in **Settings -> Sketch -> Add midpoints to new linear edges** (saved as `gui.add_mid_pt_edges`). The Options checkbox mirrors that setting for the current session while you use line tools and the **Arc Segment** tool.
+- Affects **future** edges from the Line, Multi-line, and Arc Segment tools.
- Midpoint snapping on **intersection splits** (when a new edge crosses an existing one) is separate topology behavior; see [Automatic splitting on edge intersections](#sketch-snapping).
(line-edge-option-place-from-center)=
@@ -336,60 +336,72 @@ The circle tool follows this workflow:

-The arc segment tool allows you to create circular arc edges by defining three points that lie on the arc: a start point, a middle point, and an end point.
+The arc segment tool creates a single circular arc edge from three clicks: **start**, a **bulge point** on the arc (between start and end), and **end**.
**Features:**
| | |
| ---: | --- |
-| **Three-point creation** | Click to set the start point, then the middle point, then the end point |
-| **Real-time preview** | See the arc shape while moving the mouse after setting the first two points |
-| **Automatic finalization** | The arc is automatically created and added to your sketch after the third point is clicked |
-| **Circular arc** | Creates a smooth circular arc that passes through all three points |
-| **Snap support** | Automatically snaps to existing nodes and geometry |
-| **Unique points** | All three points must be different (cannot be coincident) |
+| **Three-point creation** | Click start, then a point on the arc that sets bulge/curvature, then end |
+| **Rubber-band preview** | After the first click, a straight preview line follows the cursor; after the second click, an arc preview follows the cursor until the third click |
+| **Automatic finalization** | The arc is created and added to the sketch after the third click |
+| **Circular arc** | One smooth circular arc through the three defining points |
+| **Intersection splitting** | Splits existing straight and arc edges at interior crossings; the new arc is subdivided where other edges cross its interior |
+| **Optional arc midpoint node** | When **Add midpoint nodes** is on in the Options panel, a snap node is placed at the arc's geometric midpoint (see [Arc segment Options](#arc-segment-options)) |
+| **Snap support** | Snaps to existing nodes and geometry |
+| **Unique points** | All three clicks must be different (cannot be coincident) |
**How to use:**
1.  Select the **Arc Segment** tool from the toolbar
-2. Click to set the start point of the arc (first point)
-3. Click to set a point on the arc between start and end (middle point)
-4. Move the mouse to see a preview of the arc
-5. Click to set the end point of the arc (third point)
-6. The arc segment will be automatically created and added to your sketch
+2. Optionally enable **Add midpoint nodes** in the Options panel (see [Arc segment Options](#arc-segment-options))
+3. Click to set the **start** point (first click)
+4. Move the mouse — a straight rubber-band line shows from start to the cursor
+5. Click to set a **bulge point** on the arc (second click — between start and end, not necessarily at the geometric midpoint)
+6. Move the mouse — an arc preview follows the cursor from start through the bulge point toward the end
+7. Click to set the **end** point (third click)
+8. The arc is created and added to your sketch; faces update if the sketch forms closed regions
**Point order:**
| | |
| ---: | --- |
-| **First click** | Start point - where the arc begins |
-| **Second click** | Middle point - a point that lies on the arc between start and end |
-| **Third click** | End point - where the arc ends |
+| **First click** | Start — where the arc begins |
+| **Second click** | Bulge point — any point on the intended arc between start and end (sets which side of the chord the arc bulges) |
+| **Third click** | End — where the arc ends |
**Shortcuts:**
| | |
| ---: | --- |
| Escape | Cancel the current arc creation (clears all points) |
-| **Note** | The arc is automatically finalized after the third point, so no manual finalization is needed |
+| **Note** | The arc is automatically finalized after the third click; no manual finalization is needed |
+
+(arc-segment-options)=
+### Arc segment Options
+
+While the **Arc Segment** tool is active, the Options panel shows the same **Add midpoint nodes** checkbox used by the Line Edge tool (see [Line edge option: Add midpoint nodes](#line-edge-option-add-midpoint-nodes)).
+
+- When **on**, each new arc gets an automatic **midpoint node** at the geometric center of the arc curve (parametric half). That node is a snap target only; it does not show a **+** marker and is not listed under **Nodes** in the [Sketch List](usage.md#sketch-list).
+- When **off** (default), only the three clicked points (start, bulge, end) are stored as nodes.
+- The setting mirrors **Settings -> Sketch -> Add midpoints to new linear edges** (`gui.add_mid_pt_edges`) for the current session.
**Tips:**
-- The three points define a unique circular arc that passes through all of them
-- Use the snap feature to create arcs that connect precisely to existing geometry
-- The arc tool works in any sketch plane
-- Arc segments can be used as part of closed shapes that form faces
-- The middle point helps define the arc's curvature and direction
-- All three points must be unique - clicking the same point twice will be ignored
+- The three clicks define a unique circular arc; the second click controls bulge direction (which side of the chord the arc curves toward).
+- Use snap to connect arc endpoints precisely to existing geometry.
+- Adding an arc across an existing straight edge splits that edge at the crossing; adding a line across an existing arc splits the arc at the crossing.
+- Arc segments combine with straight edges to form closed shapes (slots, circles, custom profiles).
-**Technical details:**
-- The arc is created using the three points to define a circle, then trimming it to the arc segment
-- Internally, the arc is represented as two connected edges for proper topology
-- Arc segments can be combined with straight edges to create complex closed shapes
+**Known limitations:**
+- Combining a **slot** with a **circle** that crosses the slot horizontals can still produce incorrect edge splits or highlight colors in some cases. See [Known bugs — slot + circle](bugs.md) in the bug tracker.
+
+**Technical notes:**
+- Each arc is one graph edge (start and end nodes only); face detection uses arc **tangents** at endpoints when walking closed loops.
+- JSON save/load stores arc mid coordinates when no midpoint node is present.
**Comparison with Circle Tool:**
-- **Circle Tool**: Creates a full circle from center and radius point (2 points)
-- **Arc Segment Tool**: Creates a partial arc from three points on the arc (3 points)
-- Use circles when you need a complete circular shape
-- Use arc segments when you need a curved edge that's part of a larger shape
+- **Circle Tool**: Creates a full circle from center and radius point (2 points, two semicircular arc edges)
+- **Arc Segment Tool**: Creates one partial arc from three points (start, bulge, end)
+- Use circles when you need a complete circular shape; use arc segments for partial curves in larger shapes
## Rectangle and Square Creation Tools
diff --git a/src/gui_mode.cpp b/src/gui_mode.cpp
index 446a2cd..6b5b3a0 100644
--- a/src/gui_mode.cpp
+++ b/src/gui_mode.cpp
@@ -895,6 +895,7 @@ void GUI::options_sketch_add_arc_circle_mode_()
EZY_ASSERT(get_mode() == Mode::Sketch_add_seg_circle_arc);
options_sketch_common_();
+ options_sketch_add_midpoint_nodes_(m_add_mid_pt_line_edges);
}
void GUI::options_sketch_add_square_mode_()
@@ -1047,6 +1048,7 @@ void GUI::sync_sketch_add_mid_pt_edges_if_applicable_()
{
case Mode::Sketch_add_edge:
case Mode::Sketch_add_multi_edges:
+ case Mode::Sketch_add_seg_circle_arc:
case Mode::Sketch_add_square:
case Mode::Sketch_add_rectangle:
case Mode::Sketch_add_rectangle_center_pt:
@@ -1065,6 +1067,7 @@ bool GUI::add_mid_pt_edges_for_mode_(const Mode mode) const
{
case Mode::Sketch_add_edge:
case Mode::Sketch_add_multi_edges:
+ case Mode::Sketch_add_seg_circle_arc:
return m_add_mid_pt_line_edges;
case Mode::Sketch_add_square:
diff --git a/src/sketch.cpp b/src/sketch.cpp
index 985611d..32aa7a9 100644
--- a/src/sketch.cpp
+++ b/src/sketch.cpp
@@ -283,10 +283,15 @@ void Sketch::add_arc_circle_(const gp_Pnt2d& pt_a, const gp_Pnt2d& pt_b, const g
rec.note_curr_node(node_idx_c);
rec.note_curr_node(node_idx_b);
- add_arc_circle_(std::vector{node_idx_a, node_idx_c, node_idx_b});
+ add_arc_circle_(std::vector{node_idx_a, node_idx_c, node_idx_b}, rec);
}
-void Sketch::add_arc_circle_(const std::vector& node_idxs) { m_edges.add_arc_circle_edges(node_idxs); }
+void Sketch::add_arc_circle_(const std::vector& node_idxs) { m_edges.add_arc_circle_edges(node_idxs, nullptr); }
+
+void Sketch::add_arc_circle_(const std::vector& node_idxs, Sketch_op_recorder& rec)
+{
+ m_edges.add_arc_circle_edges(node_idxs, &rec);
+}
void Sketch::on_mode()
{
diff --git a/src/sketch.h b/src/sketch.h
index c059434..48762d9 100644
--- a/src/sketch.h
+++ b/src/sketch.h
@@ -169,6 +169,7 @@ class Sketch
void sketch_json_set_operation_axis_(const gp_Pnt2d& pt_a, const gp_Pnt2d& pt_b);
void add_arc_circle_(const std::vector& node_idxs);
+ void add_arc_circle_(const std::vector& node_idxs, Sketch_op_recorder& rec);
void add_arc_circle_(const gp_Pnt2d& pt_a, const gp_Pnt2d& pt_b, const gp_Pnt2d& pt_c);
void add_arc_circle_(const gp_Pnt2d& pt_a, const gp_Pnt2d& pt_b, const gp_Pnt2d& pt_c, Sketch_op_recorder& rec);
diff --git a/src/sketch_delta.cpp b/src/sketch_delta.cpp
index 1ae4729..3e41335 100644
--- a/src/sketch_delta.cpp
+++ b/src/sketch_delta.cpp
@@ -1,5 +1,7 @@
#include "sketch_delta.h"
+#include
+#include
#include
#include
#include
@@ -9,6 +11,7 @@
#include "occt_view.h"
#include "sketch.h"
#include "sketch_nodes.h"
+#include "sketch_edge.h"
namespace
{
@@ -77,6 +80,7 @@ class Sketch_delta::Impl
static bool prev_linear_equal_(const Prev_edge_rec& x, const Prev_edge_rec& y);
static bool curr_linear_equal_(const Curr_linear_edge_record& x, const Curr_linear_edge_record& y);
static bool arc_equal_(const Arc_edge_record& x, const Arc_edge_record& y);
+ static bool arc_edge_matches_record_(const Sketch& sketch, const Sketch::Edge& e, const Arc_edge_record& rec);
static bool length_dim_equal_(const Length_dim_record& x, const Length_dim_record& y);
static void remove_linear_edges_on_segment_(Sketch& sketch, const gp_Pnt2d& seg_a, const gp_Pnt2d& seg_b);
static void remove_linear_edges_on_node_segment_(Sketch& sketch, const gp_Pnt2d& pt_a, const gp_Pnt2d& pt_b);
@@ -524,6 +528,26 @@ bool Sketch_delta::Impl::arc_equal_(const Arc_edge_record& x, const Arc_edge_rec
x.pt_c.SquareDistance(y.pt_c) <= Precision::SquareConfusion();
}
+bool Sketch_delta::Impl::arc_edge_matches_record_(const Sketch& sketch, const Sketch::Edge& e, const Arc_edge_record& rec)
+{
+ if (!sketch_edge_is_arc(e) || !e.node_idx_b.has_value() || e.shp.IsNull())
+ return false;
+
+ const gp_Pnt2d start = sketch.m_nodes[e.node_idx_a];
+ const gp_Pnt2d end = sketch.m_nodes[*e.node_idx_b];
+ if (!pts_equal_(start, rec.pt_a) || !pts_equal_(end, rec.pt_c))
+ return false;
+
+ Geom_TrimmedCurve_ptr expected =
+ GC_MakeArcOfCircle(to_3d(sketch.m_pln, rec.pt_a), to_3d(sketch.m_pln, rec.pt_b), to_3d(sketch.m_pln, rec.pt_c));
+ if (!expected)
+ return false;
+
+ const gp_Pnt2d expected_mid = arc_curve_midpoint_2d(BRepBuilderAPI_MakeEdge(expected).Edge(), sketch.m_pln);
+ const gp_Pnt2d actual_mid = arc_curve_midpoint_2d(TopoDS::Edge(e.shp->Shape()), sketch.m_pln);
+ return pts_equal_(expected_mid, actual_mid);
+}
+
bool Sketch_delta::Impl::length_dim_equal_(const Length_dim_record& x, const Length_dim_record& y)
{
return pts_equal_(x.pt_lo, y.pt_lo) && pts_equal_(x.pt_hi, y.pt_hi);
@@ -533,7 +557,7 @@ void Sketch_delta::Impl::remove_linear_edges_on_segment_(Sketch& sketch, const g
{
for (auto itr = sketch.m_edges.edges().begin(); itr != sketch.m_edges.edges().end();)
{
- if (itr->circle_arc || !itr->node_idx_b.has_value())
+ if (!sketch_edge_is_linear(*itr))
{
++itr;
continue;
@@ -558,51 +582,22 @@ void Sketch_delta::Impl::remove_linear_edges_on_node_segment_(Sketch& sketch, co
void Sketch_delta::Impl::remove_arc_edge_(Sketch& sketch, const Arc_edge_record& rec)
{
- std::vector shps_to_remove;
-
- for (auto itr = sketch.m_edges.edges().begin(); itr != sketch.m_edges.edges().end(); ++itr)
+ for (auto itr = sketch.m_edges.edges().begin(); itr != sketch.m_edges.edges().end();)
{
- if (!itr->circle_arc || itr->shp.IsNull())
- continue;
-
- const Sketch::Edge* e1 = &*itr;
- const Sketch::Edge* e2 = nullptr;
-
- for (auto itr2 = sketch.m_edges.edges().begin(); itr2 != sketch.m_edges.edges().end(); ++itr2)
- if (itr2 != itr && itr2->shp == e1->shp)
- {
- e2 = &*itr2;
- break;
- }
-
- if (!e2)
- continue;
-
- const Sketch::Edge* first = e1->node_idx_arc.has_value() ? e1 : (e2->node_idx_arc.has_value() ? e2 : nullptr);
- const Sketch::Edge* second = first == e1 ? e2 : e1;
- if (!first || !first->node_idx_arc.has_value() || !second->node_idx_b.has_value())
+ if (!sketch_edge_is_arc(*itr))
+ {
+ ++itr;
continue;
+ }
- const gp_Pnt2d start = sketch.m_nodes[first->node_idx_a];
- const gp_Pnt2d arc = sketch.m_nodes[*first->node_idx_arc];
- const gp_Pnt2d end = sketch.m_nodes[*second->node_idx_b];
-
- if (!arc_equal_({start, arc, end}, rec))
+ if (!arc_edge_matches_record_(sketch, *itr, rec))
+ {
+ ++itr;
continue;
+ }
- const Sketch_AIS_edge_ptr& shp = e1->shp;
- if (std::find(shps_to_remove.begin(), shps_to_remove.end(), shp) == shps_to_remove.end())
- shps_to_remove.push_back(shp);
- }
-
- for (const Sketch_AIS_edge_ptr& shp : shps_to_remove)
- {
- sketch.m_ctx.Remove(shp, false);
- for (auto itr = sketch.m_edges.edges().begin(); itr != sketch.m_edges.edges().end();)
- if (itr->shp == shp)
- itr = sketch.m_edges.edges().erase(itr);
- else
- ++itr;
+ sketch.m_ctx.Remove(itr->shp, false);
+ itr = sketch.m_edges.edges().erase(itr);
}
}
@@ -653,7 +648,7 @@ void Sketch_delta::Impl::restore_prev_linear_edge_(Sketch& sketch, const Prev_ed
sketch.sketch_json_add_linear_edge_(idx_a, idx_b, idx_mid);
if (!rec.name.empty())
for (Sketch::Edge& e : sketch.m_edges.edges())
- if (!e.circle_arc && e.node_idx_b.has_value() && pts_equal_(sketch.m_nodes[e.node_idx_a], rec.pt_a) &&
+ if (sketch_edge_is_linear(e) && pts_equal_(sketch.m_nodes[e.node_idx_a], rec.pt_a) &&
pts_equal_(sketch.m_nodes[*e.node_idx_b], rec.pt_b) &&
((!rec.pt_mid.has_value() && !e.node_idx_mid.has_value()) ||
(rec.pt_mid.has_value() && e.node_idx_mid.has_value() && pts_equal_(sketch.m_nodes[*e.node_idx_mid], *rec.pt_mid))))
@@ -710,10 +705,7 @@ bool on_closed_segment_2d_(const gp_Pnt2d& p, const gp_Pnt2d& a, const gp_Pnt2d&
return point_on_open_segment_2d(p, a, b);
}
-bool is_linear_sketch_edge_(const Sketch::Edge& e)
-{
- return !e.circle_arc && e.node_idx_b.has_value() && !e.node_idx_arc.has_value();
-}
+bool is_linear_sketch_edge_(const Sketch::Edge& e) { return sketch_edge_is_linear(e); }
bool pts_equal_(const gp_Pnt2d& a, const gp_Pnt2d& b)
{
diff --git a/src/sketch_dims.cpp b/src/sketch_dims.cpp
index 7447344..48f43a4 100644
--- a/src/sketch_dims.cpp
+++ b/src/sketch_dims.cpp
@@ -13,6 +13,7 @@
#include "occt_view.h"
#include "sketch.h"
#include "sketch_delta.h"
+#include "sketch_edge.h"
#include "utl_geom.h"
using namespace glm;
@@ -254,7 +255,7 @@ void Sketch_dims::toggle_edge_dim_anno(const ScreenCoords& screen_coords)
}
if (std::list::iterator itr = m_sketch.get_edge_at_(screen_coords); itr != m_sketch.m_edges.edges().end())
- if (!itr->circle_arc && itr->node_idx_b.has_value() && !itr->node_idx_arc.has_value())
+ if (sketch_edge_is_linear(*itr))
{
add_or_toggle_length_dim_between_node_indices_(itr->node_idx_a, *itr->node_idx_b);
clear_pick_state();
diff --git a/src/sketch_edge.cpp b/src/sketch_edge.cpp
index dc29b03..59ad3de 100644
--- a/src/sketch_edge.cpp
+++ b/src/sketch_edge.cpp
@@ -1,6 +1,15 @@
#include "sketch_edge.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
#include "utl.h"
+#include "utl_geom.h"
bool Sketch_edge::reversed(size_t idx_a, size_t idx_b) const
{
@@ -14,4 +23,82 @@ bool Sketch_edge::reversed(size_t idx_a, size_t idx_b) const
return false;
}
-bool sketch_edge_is_linear(const Sketch_edge& e) { return !e.circle_arc && e.node_idx_b.has_value(); }
+bool sketch_edge_is_arc(const Sketch_edge& e)
+{
+ if (!e.node_idx_b.has_value() || e.shp.IsNull())
+ return false;
+
+ const BRepAdaptor_Curve curve(TopoDS::Edge(e.shp->Shape()));
+ return curve.GetType() == GeomAbs_Circle;
+}
+
+bool sketch_edge_is_linear(const Sketch_edge& e) { return e.node_idx_b.has_value() && !sketch_edge_is_arc(e); }
+
+namespace
+{
+gp_Vec2d normalize_or_axis_(gp_Vec2d v)
+{
+ const double mag2 = v.SquareMagnitude();
+ if (mag2 < Precision::Confusion() * Precision::Confusion())
+ return gp_Vec2d(1.0, 0.0);
+
+ v.Normalize();
+ return v;
+}
+
+gp_Vec2d curve_tangent_dir_2d_(const BRepAdaptor_Curve& curve, double u, const gp_Pln& pln, bool forward)
+{
+ gp_Pnt p;
+ gp_Vec tan3;
+ curve.D1(u, p, tan3);
+ if (!forward)
+ tan3.Reverse();
+
+ const gp_Dir& x_dir = pln.XAxis().Direction();
+ const gp_Dir& y_dir = pln.YAxis().Direction();
+ return normalize_or_axis_(gp_Vec2d(tan3.Dot(gp_Vec(x_dir)), tan3.Dot(gp_Vec(y_dir))));
+}
+
+gp_Vec2d arc_outgoing_dir_2d_(const Sketch_edge& e, const gp_Pnt2d& from_pt, const gp_Pnt2d& to_pt, const gp_Pln& pln)
+{
+ EZY_ASSERT(!e.shp.IsNull());
+ const TopoDS_Edge edge = TopoDS::Edge(e.shp->Shape());
+ const BRepAdaptor_Curve curve(edge);
+ const gp_Pnt from_3d = to_3d(pln, from_pt);
+ const gp_Pnt to_pt_3d = to_3d(pln, to_pt);
+
+ const Handle(Geom_Curve) geom = curve.Curve().Curve();
+ const double u_first = curve.FirstParameter();
+ const double u_last = curve.LastParameter();
+
+ GeomAPI_ProjectPointOnCurve proj_from(from_3d, geom, u_first, u_last);
+ GeomAPI_ProjectPointOnCurve proj_to(to_pt_3d, geom, u_first, u_last);
+ EZY_ASSERT(proj_from.NbPoints() > 0 && proj_to.NbPoints() > 0);
+
+ const double u_from = proj_from.LowerDistanceParameter();
+ const double u_to = proj_to.LowerDistanceParameter();
+ const double du = u_to - u_from;
+
+ if (std::abs(du) < Precision::Confusion())
+ return curve_tangent_dir_2d_(curve, u_from, pln, true);
+
+ const double span = u_last - u_first;
+ const double step = std::max(std::abs(du) * 0.01, span * 1e-4);
+ double u_step = u_from + (du > 0.0 ? step : -step);
+ u_step = std::clamp(u_step, u_first, u_last);
+
+ gp_Vec2d ret(from_pt, to_2d(pln, curve.Value(u_step)));
+ if (ret.SquareMagnitude() < Precision::Confusion() * Precision::Confusion())
+ return curve_tangent_dir_2d_(curve, u_from, pln, du > 0.0);
+
+ return normalize_or_axis_(ret);
+}
+} // namespace
+
+gp_Vec2d sketch_edge_outgoing_dir_2d(const Sketch_edge& e, const gp_Pnt2d& from_pt, const gp_Pnt2d& to_pt, const gp_Pln& pln)
+{
+ if (sketch_edge_is_arc(e))
+ return arc_outgoing_dir_2d_(e, from_pt, to_pt, pln);
+
+ return normalize_or_axis_(gp_Vec2d(from_pt, to_pt));
+}
diff --git a/src/sketch_edge.h b/src/sketch_edge.h
index 1587992..2fb2878 100644
--- a/src/sketch_edge.h
+++ b/src/sketch_edge.h
@@ -1,28 +1,33 @@
#pragma once
+#include
+#include
+#include
#include
#include
#include "utl_types.h"
-/// One sketch edge segment (linear or part of a circle arc).
+/// One sketch edge segment (linear segment or circle arc).
struct Sketch_edge
{
size_t node_idx_a;
- std::optional node_idx_b;
- std::optional node_idx_arc; // Only valid for circle arc edges.
- std::optional node_idx_mid; // Midpoint of edge, used for snapping.
+ std::optional node_idx_b; // End node (lines and arcs).
+ std::optional node_idx_arc_pt; // Arc curve midpoint snap node (when add-midpoint option on); not a graph vertex.
+ std::optional node_idx_mid; // Midpoint snap node for lines (when add-midpoint option on).
- // Used to identify the two `Sketch_edge`s defining a circle arc.
- Geom_TrimmedCurve_ptr circle_arc;
- Sketch_AIS_edge_ptr shp; // Current edge annotation.
-
- std::string name;
+ Sketch_AIS_edge_ptr shp; // Edge annotation and OCCT geometry.
+ std::string name;
bool reversed(size_t idx_a, size_t idx_b) const;
};
[[nodiscard]] bool sketch_edge_is_linear(const Sketch_edge& e);
+[[nodiscard]] bool sketch_edge_is_arc(const Sketch_edge& e);
+
+/// Unit direction from \a from_pt toward \a to_pt along \a e (chord for lines, curve tangent for arcs).
+[[nodiscard]] gp_Vec2d sketch_edge_outgoing_dir_2d(const Sketch_edge& e, const gp_Pnt2d& from_pt, const gp_Pnt2d& to_pt,
+ const gp_Pln& pln);
/// Read-only linear edge (node indices only).
struct Sketch_edge_linear
@@ -32,7 +37,7 @@ struct Sketch_edge_linear
std::optional node_mid;
};
-/// Read-only circle-arc edge (start, arc point, end); both arc halves share `shp`.
+/// Read-only circle-arc edge (start, arc point, end).
struct Sketch_edge_arc
{
size_t node_start;
diff --git a/src/sketch_edges.cpp b/src/sketch_edges.cpp
index a45738b..8c09387 100644
--- a/src/sketch_edges.cpp
+++ b/src/sketch_edges.cpp
@@ -1,14 +1,18 @@
#include "sketch_edges.h"
#include
+#include
#include
+#include
#include
+#include
#include
#include
#include "occt_view.h"
#include "sketch.h"
#include "sketch_delta.h"
+#include "sketch_edge.h"
#include "sketch_topo.h"
#include "utl_geom.h"
#include "utl.h"
@@ -62,35 +66,49 @@ void Sketch_edges::add_edge_impl_(const gp_Pnt2d& pt_a, const gp_Pnt2d& pt_b, Sk
std::vector inters;
for (const Sketch_edge& e : m_edges)
{
- if (!is_linear(e))
- continue;
-
- gp_Pnt2d qa = m_sketch.m_nodes[e.node_idx_a];
- gp_Pnt2d qb = m_sketch.m_nodes[e.node_idx_b];
+ if (is_linear(e))
+ {
+ gp_Pnt2d qa = m_sketch.m_nodes[e.node_idx_a];
+ gp_Pnt2d qb = m_sketch.m_nodes[e.node_idx_b];
- if (auto inter = segment_intersection_2d(pt_a, pt_b, qa, qb, Segment_inclusion::Closed))
- add_unique_point(inters, *inter);
+ if (auto inter = segment_intersection_2d(pt_a, pt_b, qa, qb, Segment_inclusion::Closed))
+ add_unique_point(inters, *inter);
+ }
+ else if (sketch_edge_is_arc(e))
+ {
+ for (const gp_Pnt2d& ip :
+ segment_arc_intersections_2d(pt_a, pt_b, TopoDS::Edge(e.shp->Shape()), m_sketch.m_pln, Segment_inclusion::Closed))
+ add_unique_point(inters, ip);
+ }
}
- // Collect only the *interior* hits on existing edges for splitting.
+ // Collect interior hits on existing edges for splitting.
std::vector inters_to_split;
for (const auto& ip : inters)
{
- bool is_old_interior = false;
+ bool split_here = false;
for (const Sketch_edge& e : m_edges)
{
- if (!is_linear(e))
- continue;
-
- gp_Pnt2d qa = m_sketch.m_nodes[e.node_idx_a];
- gp_Pnt2d qb = m_sketch.m_nodes[e.node_idx_b];
- if (point_on_open_segment_2d(ip, qa, qb))
+ if (is_linear(e))
{
- is_old_interior = true;
- break;
+ gp_Pnt2d qa = m_sketch.m_nodes[e.node_idx_a];
+ gp_Pnt2d qb = m_sketch.m_nodes[e.node_idx_b];
+ if (point_on_open_segment_2d(ip, qa, qb))
+ {
+ split_here = true;
+ break;
+ }
+ }
+ else if (sketch_edge_is_arc(e))
+ {
+ if (point_on_open_arc_interior_2d(ip, TopoDS::Edge(e.shp->Shape()), m_sketch.m_pln))
+ {
+ split_here = true;
+ break;
+ }
}
}
- if (is_old_interior)
+ if (split_here)
add_unique_point(inters_to_split, ip);
}
@@ -126,6 +144,7 @@ void Sketch_edges::add_edge_impl_(const gp_Pnt2d& pt_a, const gp_Pnt2d& pt_b, Sk
rec->note_curr_node(nidx);
m_sketch.m_topo.split_linear_edges_at_node_if_interior(nidx, rec);
+ m_sketch.m_topo.split_arcs_at_node_if_interior(nidx, rec);
}
for (size_t i = 0; i + 1 < div_node_idxs.size(); ++i)
@@ -165,19 +184,158 @@ void Sketch_edges::update_end_pt(Sketch_edge& edge, size_t end_pt_idx)
edge.node_idx_mid = std::nullopt;
}
-void Sketch_edges::add_arc_circle_edges(const std::vector& node_idxs)
+void Sketch_edges::add_arc_circle_edges(const std::vector& node_idxs, Sketch_op_recorder* rec)
{
EZY_ASSERT(node_idxs.size() == 3);
+ // node_idxs[2] is the user pick (may be toward the circle center); it defines the arc only.
Geom_TrimmedCurve_ptr arc_of_circle =
GC_MakeArcOfCircle(m_sketch.to_3d_(node_idxs[0]), m_sketch.to_3d_(node_idxs[2]), m_sketch.to_3d_(node_idxs[1]));
+ if (!arc_of_circle)
+ return;
+
+ const TopoDS_Edge new_edge = BRepBuilderAPI_MakeEdge(arc_of_circle).Edge();
+ const gp_Pnt2d pt_start = m_sketch.m_nodes[node_idxs[0]];
+ const gp_Pnt2d pt_end = m_sketch.m_nodes[node_idxs[1]];
+
+ // Intersections with all existing edges (split both olds and the new arc).
+ std::vector inters;
+ for (const Sketch_edge& e : m_edges)
+ {
+ if (is_linear(e))
+ {
+ const gp_Pnt2d qa = m_sketch.m_nodes[e.node_idx_a];
+ const gp_Pnt2d qb = m_sketch.m_nodes[*e.node_idx_b];
+ if (point_on_open_segment_2d(pt_start, qa, qb))
+ add_unique_point(inters, pt_start);
+ if (point_on_open_segment_2d(pt_end, qa, qb))
+ add_unique_point(inters, pt_end);
+
+ for (const gp_Pnt2d& ip :
+ segment_arc_intersections_2d(qa, qb, new_edge, m_sketch.m_pln, Segment_inclusion::Closed))
+ add_unique_point(inters, ip);
+ }
+ else if (sketch_edge_is_arc(e))
+ {
+ for (const gp_Pnt2d& ip :
+ arc_arc_intersections_2d(TopoDS::Edge(e.shp->Shape()), new_edge, m_sketch.m_pln))
+ add_unique_point(inters, ip);
+ }
+ }
+
+ std::vector inters_to_split;
+ for (const gp_Pnt2d& ip : inters)
+ {
+ bool split_here = false;
+ for (const Sketch_edge& e : m_edges)
+ {
+ if (is_linear(e))
+ {
+ const gp_Pnt2d qa = m_sketch.m_nodes[e.node_idx_a];
+ const gp_Pnt2d qb = m_sketch.m_nodes[*e.node_idx_b];
+ if (point_on_open_segment_2d(ip, qa, qb))
+ {
+ split_here = true;
+ break;
+ }
+ }
+ else if (sketch_edge_is_arc(e))
+ {
+ if (point_on_open_arc_interior_2d(ip, TopoDS::Edge(e.shp->Shape()), m_sketch.m_pln))
+ {
+ split_here = true;
+ break;
+ }
+ }
+ }
+ if (split_here)
+ add_unique_point(inters_to_split, ip);
+ }
+
+ for (const gp_Pnt2d& inter_p : inters_to_split)
+ {
+ const size_t nidx = m_sketch.m_nodes.get_node_exact(inter_p);
+ if (rec)
+ rec->note_curr_node(nidx);
+
+ m_sketch.m_topo.split_linear_edges_at_node_if_interior(nidx, rec);
+ m_sketch.m_topo.split_arcs_at_node_if_interior(nidx, rec);
+ }
- Sketch_AIS_edge_ptr shp = new Sketch_AIS_edge(m_sketch, BRepBuilderAPI_MakeEdge(arc_of_circle));
+ std::optional arc_pt_idx;
+ if (Sketch::get_add_mid_pt_edges())
+ arc_pt_idx = m_sketch.m_nodes.add_new_node(arc_curve_midpoint_2d(new_edge, m_sketch.m_pln), true);
+
+ Sketch_AIS_edge_ptr shp = new Sketch_AIS_edge(m_sketch, new_edge);
m_sketch.update_edge_style_(shp);
m_sketch.m_ctx.Display(shp, false);
- // Split into two edges for valid planer graph topology.
- m_edges.push_back({node_idxs[0], node_idxs[2], node_idxs[2], std::nullopt, arc_of_circle, shp});
- m_edges.push_back({node_idxs[2], node_idxs[1], std::nullopt, std::nullopt, arc_of_circle, shp});
+ // One graph edge per arc; endpoints are start and end only.
+ m_edges.push_back({node_idxs[0], node_idxs[1], arc_pt_idx, std::nullopt, shp});
+
+ for (const gp_Pnt2d& ip : inters)
+ {
+ if (!point_on_open_arc_interior_2d(ip, new_edge, m_sketch.m_pln))
+ continue;
+
+ const size_t nidx = m_sketch.m_nodes.get_node_exact(ip);
+ if (rec)
+ rec->note_curr_node(nidx);
+
+ m_sketch.m_topo.split_arcs_at_node_if_interior(nidx, rec);
+ }
+}
+
+void Sketch_edges::split_arc_at_node_(std::list::iterator itr, size_t split_idx, Sketch_op_recorder* rec)
+{
+ EZY_ASSERT(sketch_edge_is_arc(*itr));
+ EZY_ASSERT(itr->node_idx_b.has_value());
+
+ const size_t idx_a = itr->node_idx_a;
+ const size_t idx_b = *itr->node_idx_b;
+ EZY_ASSERT(split_idx != idx_a && split_idx != idx_b);
+
+ const TopoDS_Edge occ_edge = TopoDS::Edge(itr->shp->Shape());
+ if (rec)
+ {
+ const gp_Pnt2d arc_pt = itr->node_idx_arc_pt.has_value() ? m_sketch.m_nodes[*itr->node_idx_arc_pt]
+ : arc_curve_midpoint_2d(occ_edge, m_sketch.m_pln);
+ rec->note_prev_arc_edge(m_sketch.m_nodes[idx_a], arc_pt, m_sketch.m_nodes[idx_b]);
+ }
+
+ const BRepAdaptor_Curve curve(occ_edge);
+ const Handle(Geom_Curve) geom = curve.Curve().Curve();
+ const double u_first = curve.FirstParameter();
+ const double u_last = curve.LastParameter();
+
+ auto param_at = [&](size_t node_idx) -> double
+ {
+ GeomAPI_ProjectPointOnCurve proj(to_3d(m_sketch.m_pln, m_sketch.m_nodes[node_idx]), geom, u_first, u_last);
+ EZY_ASSERT(proj.NbPoints() > 0);
+ return proj.LowerDistanceParameter();
+ };
+
+ const double u_a = param_at(idx_a);
+ const double u_split = param_at(split_idx);
+ const double u_b = param_at(idx_b);
+
+ const gp_Pnt2d bulge1 = to_2d(m_sketch.m_pln, curve.Value((u_a + u_split) * 0.5));
+ const gp_Pnt2d bulge2 = to_2d(m_sketch.m_pln, curve.Value((u_split + u_b) * 0.5));
+
+ m_sketch.m_ctx.Remove(itr->shp, false);
+ m_sketch.m_edges.edges().erase(itr);
+
+ const size_t bulge1_idx = m_sketch.m_nodes.get_node_exact(bulge1);
+ const size_t bulge2_idx = m_sketch.m_nodes.get_node_exact(bulge2);
+
+ add_arc_circle_edges({idx_a, split_idx, bulge1_idx});
+ add_arc_circle_edges({split_idx, idx_b, bulge2_idx});
+
+ if (rec)
+ {
+ rec->note_curr_node(split_idx);
+ rec->note_curr_arc_edge(m_sketch.m_nodes[idx_a], m_sketch.m_nodes[bulge1_idx], m_sketch.m_nodes[split_idx]);
+ rec->note_curr_arc_edge(m_sketch.m_nodes[split_idx], m_sketch.m_nodes[bulge2_idx], m_sketch.m_nodes[idx_b]);
+ }
}
void Sketch_edges::for_each_linear(const Linear_visitor& fn) const
@@ -194,28 +352,14 @@ void Sketch_edges::for_each_linear(const Linear_visitor& fn) const
void Sketch_edges::for_each_arc(const Arc_visitor& fn) const
{
- const Sketch_edge* last_arc_half = nullptr;
-
for (const Sketch_edge& e : m_edges)
{
- if (!e.circle_arc)
+ if (!sketch_edge_is_arc(e))
continue;
EZY_ASSERT(e.node_idx_b.has_value());
-
- if (last_arc_half)
- {
- EZY_ASSERT(last_arc_half->circle_arc.get() == e.circle_arc.get());
- EZY_ASSERT(last_arc_half->node_idx_arc.has_value());
- fn(Sketch_edge_arc{last_arc_half->node_idx_a, *last_arc_half->node_idx_arc, *e.node_idx_b, last_arc_half->shp});
- last_arc_half = nullptr;
- }
- else
- {
- EZY_ASSERT(e.node_idx_arc.has_value());
- EZY_ASSERT(*e.node_idx_b == *e.node_idx_arc);
- last_arc_half = &e;
- }
+ EZY_ASSERT(e.node_idx_arc_pt.has_value());
+ fn(Sketch_edge_arc{e.node_idx_a, *e.node_idx_arc_pt, *e.node_idx_b, e.shp});
}
}
diff --git a/src/sketch_edges.h b/src/sketch_edges.h
index 171afe9..3f25c41 100644
--- a/src/sketch_edges.h
+++ b/src/sketch_edges.h
@@ -23,7 +23,7 @@ class Sketch_edges
void sketch_json_add_linear_edge(size_t idx_a, size_t idx_b, std::optional idx_mid);
void update_end_pt(Sketch_edge& edge, size_t end_pt_idx);
- void add_arc_circle_edges(const std::vector& node_idxs);
+ void add_arc_circle_edges(const std::vector& node_idxs, Sketch_op_recorder* rec = nullptr);
void remove_by_ais(const Sketch_AIS_edge& to_remove);
void remove_displayed();
@@ -52,6 +52,7 @@ class Sketch_edges
void add_edge_raw_(const gp_Pnt2d& pt_a, const gp_Pnt2d& pt_b);
void add_edge_impl_(const gp_Pnt2d& pt_a, const gp_Pnt2d& pt_b, Sketch_op_recorder* rec);
+ void split_arc_at_node_(std::list::iterator itr, size_t split_idx, Sketch_op_recorder* rec);
Sketch& m_sketch;
std::list m_edges;
diff --git a/src/sketch_json.cpp b/src/sketch_json.cpp
index ecc8597..ac9747a 100644
--- a/src/sketch_json.cpp
+++ b/src/sketch_json.cpp
@@ -14,6 +14,7 @@
#include "utl_dbg.h"
#include "utl_geom.h"
#include "sketch.h"
+#include "sketch_edge.h"
#include "sketch_nodes.h"
#include "sketch_underlay.h"
#include "ezy_asset_store.h"
@@ -103,9 +104,11 @@ void Sketch_json::from_json_indexed_(Sketch& ret, const json& j,
for (const auto& edge_json : j["arc_edges"])
{
EZY_ASSERT(edge_json.is_array() && edge_json.size() == 3);
- const std::size_t ia = edge_json[0].get();
- const std::size_t iarc = edge_json[1].get();
- const std::size_t ib = edge_json[2].get();
+ const std::size_t ia = edge_json[0].get();
+ const std::size_t ib = edge_json[2].get();
+ const std::size_t iarc =
+ edge_json[1].is_number_unsigned() ? edge_json[1].get()
+ : ret.get_nodes().get_node_exact(::from_json_pnt2d(edge_json[1]));
// Matches `add_arc_circle_(pt_a, pt_b, pt_c)` -> node_idxs [a, c, b] for (json0, json1, json2) = (start, arc, end).
ret.add_arc_circle_(std::vector{ia, ib, iarc});
}
@@ -190,9 +193,19 @@ nlohmann::json Sketch_json::to_json(const Sketch& sketch, const Ezy_asset_store&
edges_json.push_back(json::array({remap(e.node_a), remap(e.node_b)}));
});
- sketch.m_edges.for_each_arc(
- [&](const Sketch_edge_arc& a)
- { arc_edges_json.push_back(json::array({remap(a.node_start), remap(a.node_arc), remap(a.node_end)})); });
+ for (const Sketch_edge& e : sketch.m_edges.edges())
+ {
+ if (!sketch_edge_is_arc(e) || !e.node_idx_b.has_value() || e.shp.IsNull())
+ continue;
+
+ json arc_mid_json;
+ if (e.node_idx_arc_pt.has_value())
+ arc_mid_json = remap(*e.node_idx_arc_pt);
+ else
+ arc_mid_json = ::to_json(arc_curve_midpoint_2d(TopoDS::Edge(e.shp->Shape()), sketch.m_pln));
+
+ arc_edges_json.push_back(json::array({remap(e.node_idx_a), arc_mid_json, remap(*e.node_idx_b)}));
+ }
json& len_dims_json = j["length_dimensions"] = json::array();
for (const Sketch_dims::Length_dimension& ld : sketch.m_dims.dimensions())
diff --git a/src/sketch_operations.cpp b/src/sketch_operations.cpp
index 83732fb..ddb99dc 100644
--- a/src/sketch_operations.cpp
+++ b/src/sketch_operations.cpp
@@ -2,14 +2,13 @@
#include "sketch.h"
#include
-#include
#include
-#include