value toggle, and redesigned constraint-ribbon icons
Properties panel:
- New "Parameters" section (shown with no selection) listing every named
parameter with inline name/formula editing, add, and delete, backed by
new PropValue::ParamRow/ParamAddRow and command_driver handlers
(resolve_named_parameter_edit, on_prop_param_commit/_delete/_add_new).
A fresh parameter's formula now defaults to "1", not "0" — a 0-length
seed value is a genuine solver singularity for a Distance constraint
driven by that parameter, with no defined direction to grow back out of.
- New "Constraints" section (shown for a single selected entity) listing
every constraint touching it as a clickable PropValue::EntityLink that
selects the linked entities in the viewport, danger-tinted when the
constraint conflicts.
Constraint pills:
- New Options > General toggle, "Show values and parameter names on
constraint markers" (on by default, persisted). Off, every pill shows
just its bare glyph instead of glyph+driven-value/parameter-name — the
glyph alone is enough to see a constraint is present, without the value
text covering nearby geometry on a dense sketch.
Constraint-ribbon icons:
- Replaced all 18 unicode/letter glyph icons (including the plain "F",
"L", "M", "S" letters) with proper SVGs under assets/icons/constrain/,
matching the Modify section's two-color style (#B4B6B9 white / #6DB7ED
blue, consistent stroke width). Tangent reuses the Draw section's
white-arc-plus-blue-dots motif.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
An ocs_gcs Ellipse stores focus1 as an absolute point rather than an
offset from center, so a constraint that pulls only on center (e.g.
Concentric) left focus1's absolute coordinates untouched while center
moved — changing the derived focus1-center vector that major_axis and
minor_axis_ratio are computed from on write-back, so the ellipse visibly
rotated and reshaped even though nothing asked it to.
solve_scope now adds two Difference constraints per registered ellipse
pinning focus1.x-center.x and focus1.y-center.y to their pre-solve
values, so focus1 translates rigidly with center unless something
actually constrains it. Fixed's Ellipse arm no longer re-pins focus1
directly since that's now redundant with these rules.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The DWG writer truncates an XRecord Chunk to u8::MAX bytes, so a sketch's
constraint set or named-parameter table silently lost every entry on the
next DWG open once serialization passed ~255 bytes (found live: a
23-constraint demo round-tripped fine through DXF but came back empty
through DWG). Split the blob across multiple same-code 310 entries on
save and reassemble them on load, entirely on the OCS side.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Arcs are now full constraint participants (center/radius via the existing
Circle-shaped solver path, plus real endpoints kept consistent by arc-rules
CurveValue constraints); adds Diameter/DistanceX/DistanceY/ArcLength/Normal
constraint kinds and initial Ellipse entity support (center-based
constraints only). DWG-native class mappings and gaps (ArcLength, Ellipse)
are grounded against real ObjectARX headers rather than guessed, so files
saved with DWG-native constraints stay honest about what AutoCAD can
actually represent.
Also fixes a case-sensitivity bug where a lowercase named-parameter
reference typed at a dimensional constraint's value prompt was rejected
(the command line uppercases SingleToken input before the parameter-name
lookup), and adds a "Used by" column to the Named Parameters panel.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Root cause (found by instrumenting build_fallback and reading
ttf-parser's own source, not guessed): cosmic-text's fallback search
picks macOS's system font (San Francisco / SFNS.ttf) for any codepoint
no stroke font or explicitly-requested family covers. It's a variable
TrueType font, and ttf-parser's outline_glyph() resolves a variable
glyph's contours through its gvar table's deltas relative to whatever
variation coordinates are currently set on the Face — an unset Face
has none, so outline_glyph silently returns None (not an error) for
such a glyph. The caller then still returned Some(Glyph { strokes:
vec![], .. }): a "resolved" glyph with nothing to render, no error
anywhere.
Two-part fix:
1. pin_default_variations() sets every variation axis to its own
default value before outlining, applied at all TTF outline-
extraction sites (both build_fallback variants, both build_shaped
variants, and the plain by-family glyph() lookup). This alone
wasn't sufficient for San Francisco specifically — even pinned, its
outline still didn't resolve for the glyphs this bug was found on,
apparently because ttf-parser can't retrieve base glyf data gvar
needs for at least some of that font's accented Latin glyphs.
2. So build_fallback() now treats "resolved a glyph ID but couldn't
extract its outline" as a real failure (outline_from_face returns
None, not an empty glyph) and falls through to a direct fontdb scan
for any other installed font that also covers the character and
can actually be outlined, rather than silently giving up.
Full workspace test suite: 813 passed, 0 failed (previously 812
passed, 1 failed — this was the one pre-existing failure carried
through the entire session).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Brings in 54 commits from origin/main: a selection-performance overhaul,
surface thickening, GPU-native wide/tapered stroke rendering, continued
ocs_doc_api evolution, and assorted fixes/polish (block double-click
editing, DWG hatch/database repair, clipboard, collapsible Properties
sections).
Six real conflicts, all where this branch had independently re-applied
a fix upstream later revised or already carried natively:
- src/app/automation.rs, src/app/command_driver.rs: combined both
sides — kept this branch's sketch-constraint/named-parameter loading
on "new"/"open", adopted main's Scene::clear()/material_base_dir and
its newer rebuild_derived_caches() over the older bump_geometry().
Scene::clear() (src/scene/entity.rs) now also resets
sketch_constraints/named_parameters, so every caller gets this for
free instead of each call site repeating it.
- src/io/mod.rs, src/scene/render_graph.rs: adopted main's design
outright (commit 973af87d, authored by Hakan directly) — a large
MINSERT is no longer purged as "corrupt" at load, only its rendering
is bounded, preserving the source data. This branch's own earlier
design (reject at load) predates that revision.
- src/scene/pipeline/mesh_gpu.rs: adopted main's valid_rgba_size()
guard (a real robustness improvement over this branch's version).
- src/snap.rs: replaced wholesale with main's version — this branch's
copy was an explicit verbatim snapshot from an earlier upstream PR
(commit 4c264b8c) with zero constraint-system-specific content, and
main has since evolved it considerably further.
Also removed two now-dead duplicates the merge surfaced without a
conflict marker (both branches had independently fixed the same
things): the OPTIONS/OP command dispatch/autocomplete entries, and
automation.rs's open_finalizes_and_purges_like_the_ui_open_path test
(kept main's version, which covers strictly more: solid_models/
material_base_dir reset on "new").
Full workspace suite passes (812 lib tests + this branch's own 24
constraint/persistence integration tests) except the one pre-existing,
unrelated font-test failure already present on main. Zero clippy
findings in the hand-written merge resolution itself; the ~1,171
warnings clippy reports are pre-existing in the 54 absorbed commits,
not introduced here.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
New ConstraintKind variants — Concentric, CenterPoint, Colinear, Midpoint,
Fixed, PointOnCurve, EqualDistance, Symmetric — each built entirely from
existing ocs_gcs (FreeCAD planegcs) primitives, no new solver math needed.
build_constraint now returns Vec<Rc<dyn Constraint>> instead of
Option<Rc<...>>, generalizing the old Coincident-only "needs a second
system-level constraint" special case to every kind that needs one.
Wired into the ribbon (7 new buttons), a new point+pre-selected-entity
command for CenterPoint/Midpoint/PointOnCurve, a new 4-point command for
EqualDistance, and the existing DWG-native class-name mapping (all 8 reuse
the same generic "plain Geometrical node" path already built for
Coincident/Horizontal/etc.). Deliberately not modeled: Normal and
EqualCurvature, which for this app's Line/Circle-only entity model would
just be Perpendicular and Equal wearing different DWG class names.
Also makes DWG-native constraint persistence opt-in (Options > General,
off by default) rather than unconditional: a scope that doesn't yet have
the native graph is only given one when the setting is on, but a scope
that already has one — from an earlier save, or a file that came from
real AutoCAD — keeps getting it resynced regardless of the setting, so
toggling it off never freezes a stale graph in place.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Implements docs/dwg_constraint_compatibility_design.md's staged plan in
full: materializes the sketch-constraint set as acadrust's real
AssocNetwork/Assoc2dConstraintGroup object graph on save, additive to
the existing OCS_SKETCH_CONSTRAINTS XRecord, so files also carry
constraints AutoCAD/BricsCAD recognize as their own native objects
(per Hakan's request on the upstream project). Covers every
ConstraintKind, including AssocVariable wiring for named-parameter
driving values, and clears+rebuilds on each save.
DWG round-trips the full dependency chain; DXF only keeps the
constraint-group shell since acadrust's own DXF writer deliberately
omits AssocDependency-family objects. Both behaviors are covered by
tests and documented in the design doc's progress section, along with
two other implementation-time findings (skipping the NOD-anchored
top-level network, and the DWG writer's root-node/connections wire
format).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Repurpose CircleInstance center_high.w and params.w for start_width and end_width (preserving exact 128-byte stride)
- Route wide polylines, tapered polylines with bulges, and donuts through split_mixed_polyline into GPU analytical arc instances
- Compute physical annular bands, width tapers, and round end caps in circle.wgsl using world-space distances to prevent 3D perspective foreshortening distortion
- Expand bounding quad margins dynamically with grazing-angle compensation to eliminate clipping
- Relax pick_tris check in wire classification and partition routines so wide entities retain CPU picking while drawing analytically
- Add unit and hardware GPU integration tests, including tilted 3D perspective rendering verification
Named parameters (docs/named_parameters_design.md, stages 3-4):
- ParameterTable (src/scene/named_parameters.rs): a document-wide table of
named formulas, each resolved through a shared expression evaluator with
cross-parameter references, cycle detection, and division-by-zero/parse
error reporting. SketchConstraint::driving_param becomes DrivingValue
(Literal | Named) instead of a bare f64, resolved at solve time by
sketch_solve::build_constraint via Scene::named_parameters.
- Persistence (src/scene/named_parameters_persist.rs): same lazy XRecord
model sketch_persist.rs already established, synced around open/save.
Bumps the sketch-constraint XRecord's own format version (1 -> 2) since
DrivingValue's shape doesn't line up with a version-1 blob's bytes.
- PARAMETERS command and its editor (src/ui/window/named_parameters.rs):
add/remove/edit rows, live validation, duplicate-name detection: Apply
defines the table and re-solves every constraint scope with a
named-parameter-driven constraint (command_driver.rs). Distance/Angle
constraint prompts (modules/draw/constrain/value.rs) now accept either a
literal or an existing parameter's name, mirroring how the constraint's
own value prompt already worked.
- Undo scope-down carried over from this project's own established pattern
(constraint-system stage 9): the geometry an Apply moves is one undo
step, but the parameter table edit itself has no undo entry yet — a
documented follow-up, not a silent gap.
UI scale setting (src/app/settings.rs, app/mod.rs, ui/window/options.rs):
a whole-interface scale percentage (50-200%), wired directly into iced's
own per-window scale_factor hook rather than per-panel font-size
plumbing, so it applies immediately to every panel, dialog, and the
ribbon at once. Persisted alongside the existing lineweight-display-scale
setting.
OPTIONS/OP command dispatch (app/commands/view.rs, app/commands/mod.rs):
OPTIONS was registered for autocomplete but had no dispatch arm — the
Start-tab welcome screen's own button was the only way to reach it.
Found live-testing the UI-scale setting, unrelated to that feature
itself. "OP" is AutoCAD's own short alias.
help/: a new self-contained static user-help manual (manual.md as the
authored source, tools/build_help.py splits it into the committed static
HTML pages plus search index) — not yet wired into the application.
64 new/updated tests across the named-parameters and constraint-prompt
changes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Completes the OSNAP intersection port from 44deee2a: that commit
explicitly deferred ellipse support because this branch's TangentGeom
predates the PlanarEllipse variant added upstream (this branch was cut
from an older main snapshot, ~99 commits behind on unrelated work).
Ports just the variant and its minimal direct dependents, not the
other unrelated evolution in those 99 commits (a separate analytical
GPU fast-path for circle/arc/ellipse rendering, tessellation changes,
etc. — out of scope here).
- wire_model.rs: copied verbatim from main. Restores the
PlanarEllipse variant plus TangentGeom's translated/rotated/scaled/
mirrored methods (this branch was missing all four transform methods
entirely, not just ellipse support in them — a live-drag preview
wire's tangent_geoms weren't being updated at all during a
translate/rotate/scale/mirror preview).
- entities/ellipse.rs: restored building the PlanarEllipse tangent_geom
for a committed ellipse/elliptical-arc entity's RenderEntity — this
is what makes ellipses queryable by OSNAP at all (the resident wire
set is what Intersection/Tangent snap search).
- entities/dimension.rs, scene/cache/block_cache.rs: added back the
three PlanarEllipse match arms needed for exhaustiveness (OCS-to-WCS
remapping, prototype-wire translation, and instance-transform
application) — each a self-contained arm restoring exactly the
ellipse case, not touching the surrounding logic.
- snap.rs: replaced with the exact fix from the upstream PR (HakanSeven12/OpenCADStudio#1104)
branch, whose snap.rs was already verified to differ from main by
nothing but that fix. Upgrades the earlier circle/arc-only port to
the full exact_curve_intersections path: line-vs-circle/arc/ellipse
and ellipse-vs-circle/arc/ellipse, via cadkernel::geom2d::intersect
over a shared embedding plane, including the anti-parallel-normal
reflection fix for both Arc and PlanarEllipse.
7 additional tests now build and pass (line/ellipse intersection
cases, both anti-parallel-normal regressions) plus the wire-transform
test wire_model.rs already carried. Full workspace suite passes except
the one pre-existing, unrelated font-test failure also present on
main.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Resolve curve intersections through cadkernel, retain empty exact results, gather analytic candidates independently of tessellation, and bound material uploads. Preserve large MINSERT data while limiting rendering work; remove obsolete review narratives.
Ports the fixes from fix/osnap-circle-arc-ellipse-intersection (and
this session's audit follow-up) onto this branch. All are independent
correctness/reliability fixes, unrelated to the parametric-constraint-
system work also in progress here.
1. OSNAP Intersection snap used a tessellated approximation instead of
exact geometry (GitHub discussion #1052): a circle/arc always
tessellates at a fixed 48 segments in the resident wire set
regardless of radius or zoom, so every circle/arc intersection snap
was off by construction. Added exact_circle_intersections, a
closed-form, frame-independent circle/arc-vs-circle/arc solver,
wired into both Intersection-snap code paths ahead of the
tessellated sweep. This branch's TangentGeom predates the
PlanarEllipse variant added upstream, so the ellipse/line
generalization from the upstream fix isn't ported here — this
covers the literal reported bug (a circle-circle case) using only
variants this branch already has.
2. Unbounded MINSERT allocation: array_offsets() reserved one Vec
element per array instance with row/column counts parsed as
unchecked u16s — a corrupt 65535x65535 pair requests ~96 GiB.
Fixed with a documented instance budget (20,000), enforced at both
the load-time corrupt-entity filter and a render-graph failsafe.
3. MINSERT spacing was scaled by the block's own scale:
insert_instance_transform composed the offset translation before
the instance transform, so x/y block scale multiplied the row/
column spacing too. Fixed by orienting the offset with rotation +
OCS only, then translating the already-scaled instance transform.
4. Automation "open" skipped the finalization (block-origin
normalization, raster-path resolution, source_path) and corrupt-
entity purge a UI open always runs. Added
io::load_bytes_finalized(path, bytes), used by automation's "open"
handler, which now also reports the purge count.
5. Material maps were uploaded without checking
device.limits().max_texture_dimension_2d, unlike raster-image
rendering (which already tiles to that limit). A mesh material is
sampled by UV, so it can't use the same tiling trick — instead,
upload_rgba_texture now downscales an oversized image before
upload, falling back to the 1x1 material color if malformed.
6. Sketch-constraint persistence/undo, audit-found while reviewing
this branch's own constraint system:
- Clearing every constraint in a scope after a prior non-empty save
left the stale XRecord in place, resurrecting the deleted
constraints on the next load. materialize_sketch_constraints_for_
save now actively removes the XRecord when a scope goes back to
empty.
- ERASE removing an entity took its constraints with it (design
doc's deletion policy), but sketch_constraints lives on Scene,
outside document/document.objects, so nothing captured that
mutation for undo — the erased geometry came back on undo, but
its constraints didn't. refresh_sketch_constraints's deletion
pass now records the touched scope's before-image into the same
UndoRecording the entity removal rides in, threaded through a new
sketch_constraints channel on DeltaSnapshot, so one undo restores
both together. HistorySnapshot::Delta is now boxed (clippy
flagged the resulting size skew across undo/redo stack variants).
31 new/adapted tests across all six fixes. Full workspace suite passes
except the one pre-existing, unrelated font-test failure also present
on main.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Material map images (diffuse/specular/reflection/opacity/bump/
refraction/normal) were decoded at their original file dimensions and
uploaded to wgpu without checking device.limits().max_texture_dimension_2d,
unlike raster-image rendering, which already tiles to that same limit.
An oversized material map produced a wgpu validation error and left
the material unusable; the global error handler prevented a process
abort, but never produced a valid texture either.
A mesh material is sampled by UV across a surface, so it can't use the
same per-quad tiling trick raster images use. Instead,
upload_rgba_texture now checks the decoded image against the device's
texture-size limit and downscales it (aspect-preserving, via
image::imageops::resize) before upload when it's oversized, falling
back to the existing 1x1 material color if the decoded buffer is
malformed (its length doesn't match its reported dimensions).
3 new tests cover the resize math (aspect ratio preserved, both
dimensions respect the limit) and the malformed-input fallback.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two independent MINSERT (array INSERT) bugs found while auditing
render_graph.rs:
1. array_offsets() reserved and filled one Vec element per array
instance, with row_count/column_count parsed as unchecked u16s.
A corrupt or adversarial 65535x65535 pair requests 4,294,836,225
instances (~96 GiB) before any GPU-side chunking limit can help,
and before the render graph even reaches the GPU. Fixed with a
documented instance budget (20,000) enforced at both the load-time
corrupt-entity filter (is_entity_corrupt, matching the existing
polyline/spline vertex-count guards) and a failsafe clamp in
array_offsets itself, for an insert built or edited some other way.
2. insert_instance_transform composed the per-instance row/column
offset translation *before* the INSERT's own transform, so the
block's x/y scale multiplied the spacing along with the block
contents — an INSERT with x-scale 2 and column spacing 10 rendered
20 units between columns instead of 10. The pinned CAD model's own
Insert::array_points() confirms the correct behavior: spacing
rotates with the insert but is never scaled. Fixed by orienting the
offset with rotation + OCS only, then translating the already-
composed (scaled) instance transform by that world-space vector.
7 new tests cover both: pathological/reasonable instance counts at
the load-time filter and the render-graph failsafe, and instance
transforms for scaled and rotated arrays.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>