Skip to content

Sketcher document model, solver and geometry owned by raygeo - #696

Open
knipknap wants to merge 51 commits into
mainfrom
feat/sketcher-raygeo-solver
Open

knipknap wants to merge 51 commits into
mainfrom
feat/sketcher-raygeo-solver

Conversation

@knipknap

@knipknap knipknap commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Moves the sketcher core onto raygeo in four phases — the document model included:

1. Solver

sketcher/core/solver.py is a façade over raygeo.sketch.Solver (the Rust port of the scipy trust-region-reflective least-squares solver). The façade flattens the registry into PointDefs, solves, and maps positions and constrained flags back. Global solves partition into independent components and solve each island; conflicting-constraint detection maps raygeo indices back to constraint positions.

2. Entity math

Entity geometry/queries delegate to the raygeo shape helpers (line/arc/circle/ellipse/bezier/polygon), replacing hand-rolled Python math with calls into raygeo.geo.shape.*.

3. Constraint drawing

Constraint drawing delegates to the raygeo solver constraint's draw: the same symbol geometry, status colors, selection/conflict underlays and label layout, replayed onto the caller's cairo context. Labels render through the raygeo text stack (status-colored box sized from font metrics, glyphs stroked as text_to_geometry outlines) instead of Pango layouts, keeping raygeo GUI-toolkit-free.

4. Document model

raygeo owns the model and its serialization. The sketcher/core/entities/ and constraints/ façade packages are deleted — rayforge imports raygeo's entity, constraint and registry classes directly:

  • rayforge's Sketch holds the raygeo EntityRegistry; constraints are raygeo value objects over resolved point ids, with reference metadata (entity_ids, shape_id, expressions) carried alongside for the UI and lossless serialization.
  • Serialization round-trips losslessly (including legacy .ryp migration); constraint dicts are stored verbatim so save/reload preserves references, expressions and null keys.
  • rayforge keeps what is app-level: commands/undo, params/expressions, fills/arrays, the flags overlay, and the GTK UI.
  • Entity views are live: coordinate, content/font and flag writes persist through the registry; points read live state so held views survive edits and undos.

Dependency

Pins raygeo==1.78.2 (PyPI wheels + sdist, crates published), pixi.lock re-locked. The document model now owns its Python vocabulary: 1.78.1 added the nominal Constraint/Entity base types and 1.78.2 the registry-owned EntityID (annotated across the whole registry API in the generated stubs). The sketcher imports all three straight from raygeo; sketcher/core/types.py shrinks to the UI-level HitTarget alias alone. The suite below was run against the published wheel, not just the local checkout.

Tests

  • raygeo v1.78.0: sketch 396, geo 1974, ops 1447, mesh 137, svg 160, compressed-array 15 passed.
  • rayforge sketcher suite: 1127 passed, 204 deselected against the published wheel; ruff/flake8/pyflakes clean.
  • The pass-count drop vs the earlier phases is the retired façade packages: their entity/constraint tests moved 1:1 into raygeo's suite.
  • Known pre-existing backlog (unrelated to behavior, out of scope here): pyright reports ~900 type-narrowing findings across the addon (e.g. subscripts on optional get_point results) inherited from the migration's API surface; tests and the other linters are green, tracked as follow-up.

Follow-up

None blocking. The sketcher now runs entirely on raygeo's model; the remaining typings/ stub surface and the pyright backlog are incremental cleanup.

Replaces the sketcher's SciPy-based solver with the raygeo sketch
module (barebaric/raygeo#117, #118).  The Solver keeps its public
contract (constructor, solve(), get_conflicting_constraints(),
CONFLICT_ERROR_THRESHOLD) and becomes a thin bridge: it flattens the
registry and constraints into a raygeo problem, runs the Rust
trust-region solve, and writes positions, per-point constrained
flags and conflict indices back to the Python model.

Each constraint provides the mapping through a new polymorphic
to_solver_constraint(registry) method, which resolves entity
references to concrete point ids and hands the already-evaluated
dimensional values to the matching raygeo.sketch.constraints class.
Constraints without a solver counterpart (missing or unsupported
operands) are skipped, and the returned conflict indices are mapped
back to the sketch's constraint list.

The scipy least_squares loop, its BLAS thread limiting and the
numpy/scipy state vector plumbing are gone from the sketcher; the
old _solve_decomposed/_solve_scoped machinery now lives in
raygeo.sketch.Solver, which also exposes status()/nfev() for the
solver debug log.

Two solver-internal partition tests are rewritten against the
public API: the partitioning itself moved into raygeo (covered by
raygeo's own tests), and the remaining observable behavior —
islands solving independently and bridged islands solving as one —
is asserted through solve().  The separated-islands assertion now
checks the satisfied distance rather than a fixed position, since
a single distance constraint alone admits two mirrored solutions
and the solver legitimately picks the nearest one.

Requires raygeo 1.77.0 (release pending; contains raygeo#117 and
#118).  Differential parity against the SciPy solver was verified
on nine representative problems (success flags, positions, DOF
flags and conflict sets) before the swap.
The sketcher entities stop hand-rolling geometry that raygeo
(barebaric/raygeo#119) now provides:

- Arc.tangent_at uses get_arc_tangent_at_point (chirality-signed
  unit tangent); Arc.signed_distance_to and Circle.signed_distance_to
  use get_circle_signed_distance; Arc.to_polygon_vertices and
  preview_polylines sample via get_arc_samples/get_arc_sweep instead
  of manual sweep normalization and trig loops
- Bezier.tangent_at evaluates the derivative via get_bezier_tangent
  (direction-identical; consumers only use the angle), and the
  private _sample_bezier is replaced by get_bezier_samples across
  hit testing, bbox computation, rect intersection and polygon
  assembly
- Ellipse.hit_test, contains_point and preview_polylines delegate
  to is_point_on_ellipse, is_point_inside_ellipse and
  get_ellipse_samples
- Point._compute_constrained_cp delegates to
  compute_constrained_control_offset

The sampling policies (segment counts, tolerance scaling) stay in
the entities; only the geometric kernels moved.  Entity tests that
exercised the removed private sampler are ported onto its public
replacement.
@knipknap knipknap changed the title Solve sketches via the raygeo constraint solver Solve sketches and compute entity geometry via raygeo Nov 11, 2026
The constraint facades' draw methods now delegate to the raygeo
solver constraint's draw, which replays the reference symbol
geometry, colors and underlays onto the caller's cairo context.  The
base class mirrors the registry's points and the line/text-box
entities the symbols consult into a raygeo registry, and forwards
the constraint's status ordinal so hover/conflict/expression colors
keep working.

Labels are rendered through the raygeo text stack (status-colored
background box sized from the font metrics, glyphs stroked as
outlines) instead of Pango layouts; the constraint text rendering
tests now pin the label box geometry against raygeo's text metrics,
and the distance draw test keeps its real-surface pixel check.
raygeo's constraint draw now reads the document through its
DrawContext protocol, so the facades hand the registry over through
a SolverDrawContext adapter instead of mirroring points and entities
into a throwaway raygeo registry on every draw call.  Positions the
symbols draw stay live with the document at all times.
The solver constraints' surface is the raygeo SolverConstraint
protocol; the fifteen drawable constraints pull the typed
_draw_solver_constraint helper in through a SolverDrawConstraint
mixin so the base class keeps its wide to_solver_constraint type
without loosening anything to Any.  The draw context adapter's
parameter names match the protocol keywords.
Replace the sketcher entity/constraint facade packages with raygeo's
document model: the registry re-exports raygeo's EntityRegistry, Sketch
delegates points/entities/constraints to it, and a legacy migration
shim loads old .ryp sketches. Commands, tools, selection and renderer
use the raygeo classes directly. Deletes sketcher/core/entities and
sketcher/core/constraints.
@knipknap knipknap changed the title Solve sketches and compute entity geometry via raygeo Sketcher document model, solver and geometry owned by raygeo Nov 14, 2026
- Renderer junction overlay: _safe_get_point now returns a plain tuple
  as its annotation promises; the live point object crashed the GTK
  snapshot transform.
- Ellipse/circle creation: the temp start point is removed again after
  the commit (as on main) instead of being adopted into the command, so
  no stray standalone dot is left at the press corner. The temp-start
  test now pins the removal.
- Waypoint tools: call sketch.registry.entities() — the bare attribute
  crashed the right-click pie menu.
- Typing: central Entity/Constraint/HitTarget aliases in core.types;
  ~250 Any annotations across the addon replaced with precise types
  (remaining Anys are dict[str, Any] serialization payloads only).
raygeo 1.78.1 gives the document model nominal Python types: every
constraint class extends Constraint, every entity class Entity. The
sketcher drops its local type unions — annotations import the nominal
types straight from raygeo, sketcher/core/types.py shrinks to
EntityID and the UI-only HitTarget alias, and the array guide-frame
annotations reflect the live point views. Verified against the
published 1.78.1 wheel.
entity_ops imports nothing internal at module level, so the local
imports of it (and of raygeo classes, and of constraint_info) in
offset, items, fillet, chamfer, rectangle, text_property,
equal_constraint and entity_flags had no cycle to break — they move to
the module imports. The remaining function-level imports in sketch.py
break documented import cycles and stay.
raygeo 1.78.2 publishes EntityID with the registry API annotated in
it — the model's id space is raygeo-owned, so the sketcher imports the
alias from raygeo instead of defining it locally.
sketcher/core/types.py now contains only the UI-level HitTarget alias.

This branch was successfully deployed

1 active (outdated) deployment
pypi — 3c32831a Deployed Nov 13, 2026 by knipknap via Build and Publish Wheel #2634
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant