Commit Graph
1121 Commits
Author SHA1 Message Date
Hakan SevenandClaude Opus 4.8 415dbe08d4 refactor: move resource/IO root modules under io/
Relocate three root-level modules into io/, where they belong by concern:

- update_check.rs -> io/update_check.rs (its own doc-comment notes it
  mirrors how crate::io wraps blocking calls in async).
- patterns.rs, linetypes.rs -> io/ (standard resource catalogs parsed
  from asset files and populated into the document — IO-shaped work).

Fix the moved files' include_str! paths (one level deeper), add the
mod declarations to io/mod.rs, drop them from lib.rs/main.rs, and
re-point all crate:: references to crate::io::. No behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 23:06:40 +03:00
Hakan SevenandClaude Opus 4.8 bbdbd0850b chore: remove dead extend stub, flatten single-file module dirs
- Delete src/modules/draw/modify/extend.rs (a 1-line placeholder with no
  tool()/logic; `extend::` was referenced nowhere) and its `mod extend;`.
- Flatten single-file module dirs to the modern flat style:
  src/command/mod.rs -> src/command.rs, src/snap/mod.rs -> src/snap.rs.
  Parent `mod command;`/`mod snap;` are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 23:00:26 +03:00
Hakan SevenandClaude Opus 4.8 bddc050b3e refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.

- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
  dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
  with thin delegating arms; the 66 fattest arm bodies move to topic
  methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
  mspace,project,selection,modify,group_layer,preview}. The impl Scene
  body is split across files via inherent-impl-per-file; struct, ctor,
  fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
  The 233-arm dispatch match becomes source-ordered per-family
  dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.

Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
Hakan SevenandClaude Opus 4.8 2baabdf4a0 feat(viewport): reorder model controls, add close-vport button
Reorder the model-tile control chip to grid, grid snap, render mode,
vertical split, horizontal split. Add a close button (shown only with
more than one model tile) that merges the active pane into its
longest-contact neighbour.

Divider drags now clamp each side to the minimum tile size, so dragging
a boundary to the screen edge no longer collapses a pane — closing is
done with the button instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 21:15:00 +03:00
Hakan Seven 9c03ae2b4d Merge pull request #196 from KevinGriffin-new/feat/background-presets
feat(background): named presets for BACKGROUND (WHITE/BLACK/GRAY/…) (#188)
2026-06-26 20:44:20 +03:00
Hakan Seven f45d85c7ec Merge pull request #195 from KevinGriffin-new/feat/persist-background
feat(background): persist viewport background across sessions (#188)
2026-06-26 20:42:38 +03:00
Hakan Seven b1e4330d8d Merge pull request #194 from KevinGriffin-new/fix/dist-f64-precision
fix(dist): measure in f64 so snapped endpoints stay precise (#193)
2026-06-26 20:39:13 +03:00
Kevin GriffinandClaude Opus 4.8 e22afea0a0 feat(background): named presets for BACKGROUND (WHITE/BLACK/GRAY/…) (#188)
Builds on the background-persistence change: let BACKGROUND take a single
named-colour token in addition to `<r> <g> <b>`, so a common dark/light
background is one word instead of a remembered triplet:

    BACKGROUND WHITE | BLACK | GRAY|GREY | DARKGRAY|DARKGREY | LTGRAY

Colour parsing moves into `parse_background_color`; the persisted-default
and recolor paths are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 10:27:54 -07:00
Kevin GriffinandClaude Opus 4.8 66fc30429c feat(background): persist viewport background across sessions (#188)
The BACKGROUND command set a per-tab colour that was lost on restart and
didn't apply to new drawings, so there was no way to make a non-black
background stick. Persist the chosen model/paper background in UserSettings
(settings.txt) and re-apply it on launch and to every drawing tab created
afterward.

- settings.rs: persist `bg_color` / `paper_bg_color` as 0–255 RGB triplets.
- App gains `default_bg_color` / `default_paper_bg_color`; current_settings /
  apply_settings round-trip them, and apply_settings pushes the restored
  colour onto existing tabs. New/opened/replaced tabs call `apply_bg_default`.
- BACKGROUND updates the persisted default, saved centrally via
  persist_settings_if_changed; RESET clears it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 10:26:53 -07:00
Kevin GriffinandClaude Opus 4.8 21192011be fix(dist): measure in f64 so snapped endpoints stay precise (#193)
DistCommand stored the first point as Vec3 and ran on_point through
`let pt = pt.as_vec3();`, downcasting the f64 picked point to f32 before
computing distance/deltas. At survey-scale coordinates (eastings ~5e5)
f32's 24-bit mantissa gives ~0.03–0.06 unit resolution, so a snapped
endpoint measured slightly off — matching the report in #193.

Store `first` as DVec3 and keep the math in f64; cast to f32 only for
the visual preview wire vertices.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 09:52:36 -07:00
Hakan SevenandClaude Opus 4.8 f40144deac deps: bump acadrust to latest main (7c0c12fe)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 18:06:14 +03:00
Hakan SevenandClaude Opus 4.8 169a8cde3f chore: clear dead-code and unused-import warnings
Remove the unused `crate::par::prelude` glob import in wire_gpu.rs and
the never-called `viewport_at_paper_point` method; fold its doc into the
screen-space twin that is actually used so no intra-doc link dangles.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 18:04:52 +03:00
Hakan Seven 4aa7abfa4e Merge pull request #189 from schoeller/plugin_timeout_protection
Adding timeout protection to host from stuck runner
2026-06-26 17:58:37 +03:00
Hakan SevenandClaude Opus 4.8 5e59421e92 test(plugin): drop Dispatch floor in timeout test; doc stream-close path
The call-timeout test asserted the real 10 s Dispatch floor, adding 10 s+
to every suite run. Add a cfg(test)-only OCS_PLUGIN_TEST_FLOOR_SECS seam
so the test fires at its 1 s base instead; production still enforces the
hard safety floors. Test now finishes in ~1 s.

Also document in mark_dead that the host-side socket is closed indirectly
by killing the child (which unblocks and drops the detached reader
thread's Stream), not by the take() in mark_dead itself.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 17:56:24 +03:00
Hakan SevenandClaude Opus 4.8 da9562466d deps: bump acadrust to LWPOLYLINE BD/3BD parse fix
Picks up acadrust 2a59d3e, which reads/writes LWPOLYLINE thickness and
extrusion as BD / 3BD instead of BT / BE. Without it, polylines carrying
a thickness or extrusion flag parsed with a garbage normal and point
count and were dropped by the corrupt-entity guard on open — e.g. ~1600
valid polylines in the PLN_Sev block of a route drawing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 16:47:31 +03:00
Hakan SevenandClaude Opus 4.8 14ebdd36d1 perf(render): hold model wire set static instead of re-tessellating on camera moves
The model-space wire geometry is camera-independent, yet every pan/zoom
re-tessellated the whole model and re-uploaded it to the GPU. On a
block/text-heavy drawing that is millions of segments, costing a ~100ms
re-tessellation plus a ~150ms GPU re-upload per camera change (~258ms
freeze) while the GPU itself draws the full set comfortably in real time.

Tessellate the full, un-culled model once per geometry epoch, hold it
resident, and return it for every camera and tile. A pan/zoom now only
changes the view uniform; no frustum cull, no zoom LOD, no re-tess or
re-upload on camera moves. Pan/zoom frame time drops to ~7ms (~140fps)
with no stutter.

Removes the now-dead per-tile cull cache, pan-invariant hash, AABB
containment helper, and the reuse-during-motion / settle-regen machinery
that an earlier iteration introduced. Also exempts Face3D from the
sub-pixel stub LOD: a flat face is trivially cheap to tessellate, so it
is drawn full at any zoom and no longer pops a cube stub at the threshold.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 16:06:11 +03:00
Hakan SevenandClaude Opus 4.8 4e8521010b fix(wipeout): keep fill boundary precise at UTM coordinates
`wipeout_boundary_2d` built the fill boundary from the wipeout's
insertion point cast straight to f32, stored in a HatchModel with
world_origin = [0, 0]. At UTM-scale coordinates (~1e6) f32 resolves to
~0.06, so the boundary collapsed and the relative-to-eye fill shader
cancelled `boundary - eye` to noise — the mask rendered as squares.

Return the boundary as small f32 offsets relative to a world_origin
(the insertion point, kept in f64) and set HatchModel.world_origin to
it at both the model-space and paper/viewport fill sites. Same fix as
the wide-polyline and on-create hatch fills.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 11:20:50 +03:00
Sebastian b18be4dacc Adding timeout protection to host from stuck runner 2026-06-26 10:10:56 +02:00
Hakan SevenandClaude Opus 4.8 2672807c13 fix(polyline): keep wide-polyline fills precise at UTM coordinates
The solid fill band of a wide LwPolyline / Polyline2D was built from
absolute WCS vertex coordinates cast straight to f32, then handed to a
HatchModel with world_origin = [0, 0]. At UTM-scale coordinates (~1e6)
f32 resolves to ~0.06, so the half-width offset collapsed and the
relative-to-eye fill shader cancelled `boundary - eye` to noise — the
band rendered as a string of squares. (Exploding the polyline drops the
width, hence no band and a clean line, which is why that "fixed" it.)

Build the band relative to a world_origin (the first vertex) in f64 and
store the small f32 offsets, mirroring how baked hatch fills keep
precision. Same class of bug as the on-create hatch-fill fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 10:55:28 +03:00
Hakan SevenandClaude Opus 4.8 0317dad14e perf(render): hoist per-wire constants to a storage buffer (native)
Every wire segment is one instance, and each carried the wire's color,
line-weight, dash pattern and draw-depth (~44 B) inline — re-fetched per
segment even though they're constant along the wire. On native, move
those into a per-wire `WireConst` storage buffer (group 1) indexed by a
`wire_id` on the instance. The instance drops from 104 B to 60 B (~42 %)
across every entity's wires, and the shared constants are fetched once
per wire (cached) instead of once per segment — less vertex-attribute
bandwidth and less GPU upload on rebuilds.

WebGL2 has no vertex-stage storage buffers, so the wasm build keeps the
original self-contained fat instance and wire.wgsl; native uses the new
slim instance, wire_indexed.wgsl, and a group-1 bind group built per
batch in WireGpu::from_run / from_batch. Visual output is unchanged
(colors are now full f32 rather than 8-bit, if anything more precise).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 10:39:23 +03:00
Hakan SevenandClaude Opus 4.8 0db513e07a fix(hatch): one grip per corner, and live preview while editing
Two follow-ups to dropping the hatch boundary outline from the wire set:

- `closed_outlines` copied segment-list wire points (LwPolyline emits
  each segment as a NaN-separated pair, so every shared corner repeats)
  straight into the hatch boundary, giving two grips at each corner.
  Collapse the points back into a clean ring — skip NaN separators and
  any vertex coincident with the previous one, and drop a trailing
  vertex equal to the first. One grip per corner; point-in-polygon and
  the BOUNDARY / GRADIENT commands get the clean ring too.

- Edit previews (move / copy / array / grip-drag) tessellate the edited
  entity into preview wires, but a hatch now produces none, so a dragged
  hatch didn't update until release. Build a live outline from the
  current HatchModel for the preview only (`apply_grip` keeps it in
  step), so the shape tracks the cursor again. The normal render still
  shows just the fill.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 09:47:29 +03:00
Hakan SevenandClaude Opus 4.8 1831a63236 perf(hatch): drop the boundary outline wire from the render set (#131)
A HATCH was emitting its boundary as a dense outline polyline in the
wire set on top of its fill. On hatch-heavy drawings that outline was
the dominant wire-instance cost — 14.9M of 19.1M segments on the #131
sample — and on integrated GPUs (shared system RAM) the per-segment
instances pushed memory past the OOM threshold on open.

Skip the outline in `tessellate`: hatches render as a fill, are
highlighted by their fill tint when selected (#71), and are still
picked by their fill area via the existing `click_hit_hatch` path, so
nothing else depends on it. Wire segments drop ~78% on the sample.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 09:31:59 +03:00
Hakan SevenandClaude Opus 4.8 21d0db0c25 fix(hatch): show fill on creation, not only after a boundary edit
`add_hatch` let `add_entity` build the render model from the new DXF
entity (via `hatch_model_from_dxf`, AABB-centred `world_origin`) and
insert it — then overwrote it with the command-built model, whose
`world_origin` is [0, 0]. After the world_offset removal the fill is
drawn relative-to-eye from `world_origin`, so a [0, 0] origin with a
full-WCS boundary cancels to garbage in f32 and the fill renders
off-screen — invisible until a later edit rebuilt it from the DXF.

Drop the overwrite so creation keeps the correct seed; it now matches
what an edit or reload produces. Fixes the regression for hatches drawn
away from the origin.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 09:31:59 +03:00
Hakan SevenandClaude Opus 4.8 eaf72254e7 chore(release): v0.6.7
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v0.6.7
2026-06-26 07:33:41 +03:00
Hakan SevenandClaude Opus 4.8 34e750407b fix(render): draw command-preview wires always on top
Preview / interim / grip-drag overlay wires were drawn in the main wire
pass with the depth-tested wire_pipeline (depth_compare=LessEqual), so a
live command preview could be hidden behind occluding geometry — a 3D
solid, or 2D geometry drawn in front of it.

Draw them with the existing wire_xray_pipeline instead (depth_compare=
Always, no depth write), the same always-on-top path selection highlights
already use. Previews are now never occluded, for every command.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 07:29:28 +03:00
Hakan SevenandClaude Opus 4.8 c7ecabffb7 fix(trim): trim circles into arcs, fix arc/circle preview plane (#184)
TRIM ignored circles: the target dispatch handled Line/Arc/Ellipse/
Spline/Ray/XLine/LwPolyline but had no Circle arm, so clicking a circle
fell through to a no-op. Add `trim_circle()` — a full circle needs ≥2
boundary crossings; the arc segment under the click is removed and the
circle becomes a single Arc (center/radius/thickness/normal preserved).
Wire it into both the pick (apply) and hover-preview paths.

Also fix `arc_pts`, which emitted points as [x, y, cy+r·sin] — the XZ
plane — so arc and circle previews drew in the wrong plane (obvious on a
full circle). Match the XY convention used by line_pts and fillet's
arc_pts: [cx+r·cos, cy+r·sin, z]; callers now pass center.z.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 07:25:02 +03:00
Hakan Seven 244b410784 Merge PR #183: bake *D dimension block on save (fixes #178, #181, #182)
fix(dimension): bake a *D block on save so dimensions survive in other CAD
2026-06-26 07:08:16 +03:00
Hakan SevenandClaude Opus 4.8 0efa559bd6 chore(deps): bump acadrust 0.3.4 → 0.4
Tracks acadrust main at 0.4.x: plotstyle/material common-entity
ordering fix plus in-flight DWG reader work. Patch comment updated
to match; drop the git patch once these land in a published 0.4 crate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 07:00:36 +03:00
Kevin GriffinandClaude Opus 4.8 d477c39cbe fix(dimension): bake a *D block on save so dimensions survive in other CAD
A DWG DIMENSION references an anonymous *D<n> block that holds its drawn
geometry; AutoCAD-family readers render that block rather than re-deriving it
from the definition points. OCS re-tessellates dimensions for display and never
materialises the block, so on save a created dimension goes out referencing a
block that doesn't exist and the writer emits a null block handle. Strict
readers (DWG TrueView, QCAD) then drop the dimension or demand a recovery, and
lenient ones (BricsCAD) regenerate it at a different position.

Add bake_dimension_blocks(), called on the cloned document in
io::save_as_version / save_to_bytes, which builds a *D<n> anonymous block from
each block-less dimension's exploded geometry (extension lines + dimension line
+ measurement text) and points its block_name at it. Dimensions that already
own a block (imported, or copied via the *D-cloning copy path) are left
untouched.

Verified end-to-end: before, AutoCAD/Civil 3D 2026 reads 0 dimensions from an
OCS-saved file; after baking it reads all of them.

Fixes #178, #181, #182.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 17:01:10 -07:00
Hakan SevenandClaude Opus 4.8 dee5a05652 fix(text): address PR #180 review
Follow-up cleanups on the merged TTF-fill work:

- Drop the unconditional resolve_text_style log line — it fired for every
  text style and spammed ~7k stderr lines opening a single real drawing.
- Drop the per-glyph tessellation-error eprintln; a malformed font would
  otherwise log once per character. The glyph still falls back silently.
- Tessellate glyph fills with the nonzero winding rule (lyon defaults to
  even-odd, which mis-fills TrueType outlines whose contours overlap).
- Make the sysfont prefix fallback deterministic (sorted) and require ≥3
  chars so a 1–2 letter request can't grab an arbitrary family.
- Remove the unrelated windows-gnu --exclude-all-symbols linker flag that
  slipped into .cargo/config.toml.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 00:27:40 +03:00
Hakan Seven e9cb9af924 Merge PR #180: TTF solid glyph fills, font fallback, Z-order fix 2026-06-26 00:20:59 +03:00
Hakan SevenandClaude Opus 4.8 012ec3febc fix(viewcube): land face clicks upright, never upside-down
A drawing opened with a twisted view made a TOP-face click keep the
twist, so north ended up at the bottom. Add Camera::snap_to_face, which
snaps to a canonical orientation — north (UCS +Y) up for top/bottom,
world (UCS +Z) up for the side elevations — ignoring the current
up-sense. Face clicks use it; edges/corners still preserve their up so
they spin smoothly around the clicked feature.

The Home button routes through the same path, giving a reliable reset
for a twisted view.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 00:11:40 +03:00
Hakan SevenandClaude Opus 4.8 f00c48bebb feat(viewcube): add compass ring, cardinals, and nav controls
Build the ViewCube out into a full navigation widget:

- Compass ring painted in the cube's ground plane (z = base) so it
  reads as a ground compass and foreshortens with the view.
- N/E/S/W cardinals painted flat onto the ring band; clicking one snaps
  to that side elevation. Added S/W glyphs to the bitmap atlas.
- Home button (top-down view), roll arrows (90° roll about the view
  axis), and four nudge triangles (90° tip/spin) as SVG iced controls
  laid out around the cube. New home.svg.
- WCS / named-UCS selector under the cube, switching the active UCS.

The cube hit area now shares one stack with the control buttons, so a
click on a button is caught by the button while a click on the cube
falls through. Face clicks snap to the highlighted region rather than a
cursor position the viewport's move handler can overwrite.

Camera gains home_view / roll_by / nudge_90; floating viewports drive
the same ops through mutate_active_viewport_camera, which re-encodes the
result to the stored view direction + twist.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 23:46:43 +03:00
Hakan SevenandClaude Opus 4.8 6c4b981424 feat(viewcube): paint face names onto the cube surface
Lay each label's glyph quads out in the face's own local plane and
project the corners individually, instead of drawing screen-aligned
billboard text at the projected face centre. The letters now skew with
perspective and sit on the surface as the cube rotates.

Per-face (u, v) basis vectors are chosen so u x v equals the outward
normal, which keeps the text from ever reading mirrored.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 22:40:17 +03:00
Hakan SevenandClaude Opus 4.8 a72b143628 fix(copy): duplicate a dimension's baked block so COPY copies it (#161)
A dimension's drawn geometry lives in a baked anonymous *D block. COPY
cloned the Dimension entity (translating its definition points) but left
its block_name pointing at the source block, whose sub-entities stay at
the original location — so the copy rendered on top of the original and
appeared not to copy at all.

Add `clone_transformed_block`: when copying a dimension that has a baked
block, duplicate that block under a fresh *D name with every sub-entity
transformed by the same offset, and repoint the copy at it. The copy now
lands at the drop point with its baked geometry and text preserved (no
synthesis, so diameter/radius values stay correct). Covered by a portable
regression test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 22:30:45 +03:00
Karim Jerbi 627761b629 fix(text): render TTF glyphs as solid fills, fix font fallback, correct Z-order
Text elements previously rendered as hollow wireframe outlines and sometimes
fell back to the wrong system font due to case-sensitive exact matching. Also,
the viewport background grid drew on top of solid entities.

Case-insensitive and partial-name fallback matching (e.g., mapping "arialn"
to "Arial Narrow") is now added to `sysfont::canonical_family_name`, ensuring
text resolves to the correct installed system font.

TrueType font contours are now triangulated into solid fills (`fill_tris`)
using `lyon_tessellation` in `ttf_glyph.rs`. To prevent the GPU from misclassifying
this 2D text as a 3D mesh (which broke flat shading), text tessellation splits
the output into separate outline and fill `WireModel`s.

The background grid is extracted from `SelectionCanvas` into `GridCanvas` and
moved beneath the 3D viewport to ensure proper layering. Finally, `DepthBiasState`
is enabled on all face/fill pipelines to apply a polygon offset, perfectly
resolving Z-fighting between coplanar wireframe lines and solid faces.
2026-06-25 18:17:01 +01:00
Hakan SevenandClaude Opus 4.8 1a78e6c7ff fix(rotate): apply a typed angle right after the centre (#159)
ROTATE's reference-point step prompts "or skip: type angle directly", but
on_text_input only handled the later Angle step. So picking a centre and
typing the angle did nothing, and the next Enter cancelled the command —
the objects never rotated, which reads as the rotate "erasing" / losing
them.

Accept a typed angle at the reference-point step too, rotating directly
about the centre (the reference point stays optional). Added an automation
regression test that rotates a line 90° with centre + angle only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:58:45 +03:00
Hakan SevenandClaude Opus 4.8 c599c2ba09 fix(pdf): export linetypes and true lineweights (#155)
The internal PDF export set each wire's colour and thickness but never a
dash pattern, so every dashed / centre / dash-dot line printed solid. It
also derived thickness from the on-screen pixel weight, which carries a 2×
screen-legibility boost — so print weights were inconsistent and barely
differentiated.

Emit a PDF dash pattern per wire from its linetype run lengths (mm→pt,
real length = index of the last non-zero element + 1, 1 pt floor so a dot
still marks), resetting to solid where there is no pattern. Convert
lineweight with a factor that undoes both the 96-dpi scaling and the
display boost, so print thickness is the true physical mm.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:13:19 +03:00
Hakan SevenandClaude Opus 4.8 918f6c540f fix(layout): centre the camera on the sheet when first opening a layout
A new drawing's layouts carry paper limits but no sheet viewport; it is
created lazily on the first switch into the layout. That viewport kept
Viewport::new's default view (target 0,0 / height 210), so camera_from_view
aimed at the paper's bottom-left corner, and with no geometry yet the
fit_all fallback was a no-op — the layout opened off-centre.

Give the freshly created sheet viewport a view that frames the page:
view_target at the paper centre and view_height a little taller than the
sheet. The first switch now opens centred on the page, and the view persists.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 18:55:20 +03:00
Hakan SevenandClaude Opus 4.8 244f951c00 fix(viewport): show new paper-space viewport border without a zoom
Creating a viewport in a layout commits through document.add_entity_to_layout
+ Scene::auto_fit_viewport, both of which skip the geometry-cache bump that
Scene::add_entity performs for every other entity. So the new viewport's
border wasn't tessellated until the next zoom/pan happened to invalidate the
cache. Bump the wire tessellation (no-blocks: a viewport touches no block
definitions) right after the viewport is added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 18:55:20 +03:00
Hakan SevenandClaude Opus 4.8 8f8fa40279 fix(scale): restore default scale list when drawing has none (#154)
Driving the annotation / viewport scale picker purely from the drawing's
ACAD_SCALELIST regressed every drawing that carries no Scale objects
(most fresh drawings and minimal DXF/DWG exports): scale_list() returned
empty, so the status-bar scale popup opened with nothing to pick and the
button looked dead.

Fall back to the standard ratio set (1:500 … 1:1 … 10:1) when the drawing
defines no scales of its own; file-defined scales still take precedence
whenever present. Also re-enables the viewport-scale picker in the
properties panel for those drawings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 18:44:02 +03:00
Hakan SevenandClaude Opus 4.8 73497850e2 fix(ribbon): float annotate style dropdowns so they aren't clipped (#153)
The text / dimension / multileader / table style combos rendered their
open list inline inside the ribbon row, so the popup was clipped to the
fixed ribbon-row height and got cut off even with only two entries. The
Draw-tab dropdowns and the layer combo avoid this by rendering as
floating overlays.

Render the style list as an overlay too: the combo now draws only its
button inline, and a new `style_combo_overlay` builds the list as a
floating panel that grows to fit its entries, positioned with the
existing `compute_dropdown_left` (which already accounts for
StyleComboGroup widths) and mirroring `layer_combo_overlay`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 18:35:49 +03:00
Hakan SevenandClaude Opus 4.8 7086a79c79 fix(dimension): render diameter text and shrink point markers (#139)
Two issues in a file whose dimensions carry baked *D### blocks.

Missing diameter text: the baked MText values are "\U+220520" /
"\U+220510" — the \U+2205 (⌀) escape immediately followed by the literal
digits "20"/"10". The MText parser read up to six hex digits for a \U+
escape, so it greedily consumed "220520" = 0x220520, which is past the
maximum code point, char::from_u32 returned None, and the whole text
dropped. A \U+ escape is exactly four hex digits; read four so the symbol
decodes and the trailing digits stay literal. (Angular "90\U+00B0" only
worked because its escape sits at the end of the string.)

Node artifacts: PDMODE=0 points were drawn as a fixed 0.1-world-unit "+",
which in a small drawing turned the 31 dimension def-points on the
Defpoints layer into visible crosses. Size the dot from world-per-pixel
(~1 px, zoom-adaptive via the tessellation cache key) to match the near-
invisible dot other CAD apps draw.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 18:22:35 +03:00
Hakan SevenandClaude Opus 4.8 177ae20be4 fix(dimension): keep DIMCONTINUE chain collinear with the base (#151)
The continued dimension line was positioned with an offset measured from
the base dimension's *first* extension origin, then re-applied to each new
chain point (the previous dim's second origin). When the two extension
origins sat at different perpendicular positions — i.e. the extension
lines had different lengths — that offset placed the new dim line at
`D + (p2 - p1)·perp` instead of the base line's `D`, so the chain drifted
off the base line. Equal-length extension lines have `(p2 - p1)·perp = 0`,
which is why it only misbehaved sometimes.

Store the base dim line's absolute perpendicular coordinate and project
each extension origin onto it, so the whole chain stays collinear
regardless of extension-line lengths. The preview uses the same
projection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 18:04:20 +03:00
Hakan SevenandClaude Opus 4.8 9d5bed2727 fix(dimension): correct aligned/ordinate placement and preview plane (#150)
DIMALIGNED's drag preview never matched where the dimension landed. The
preview offset the dimension line in the XZ plane while the committed
entity's renderer offsets in the XY plane, and the commit path passed the
straight-line p2→cursor *distance* to `set_offset`, which re-applies it
along the perpendicular — wrong distance, always on the +perp side. Both
now store the cursor as the definition point and project it onto the
XY-plane perpendicular, matching the renderer and DIMLINEAR.

The same XZ-vs-XY plane confusion affected other annotate commands:

- DIMORDINATE worked entirely in the XZ plane (dropping the Y coordinate)
  and left `definition_point` at the origin, so the leader kinked through
  (0,0,0). Reworked onto the XY plane with a proper orthogonal elbow and a
  live preview that matches the placed entity.
- DIMTEDIT / DIMJOGLINE cursor markers were built in the XZ plane, so in
  the top-down view they collapsed to a flat line instead of a box /
  zigzag. Rebuilt in the XY plane.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 17:55:39 +03:00
Hakan SevenandClaude Opus 4.8 fcb97b12da fix(linetype): render dotted and dash-dot patterns (#149)
A dot in an LTYPE pattern is a zero-length element. The pattern resolver
encoded it as a tiny `0.01 * scale` world-length dash, which had two bad
effects: it was sub-pixel at normal zoom so the dash LOD saw the pattern's
smallest feature drop below one pixel and collapsed the whole line to
solid, and at larger LTSCALE it left only invisible sub-pixel dots between
big gaps. Either way dotted and dash-dot linetypes were never visible;
only plain dashed (no zero elements) worked.

Keep dots as literal 0.0 so they no longer drag `min_elem` down, and
render each as a fixed ~1.5 px mark in the wire shader (pixel-snapped via
world_per_pixel, mirroring the hatch shader's dot handling). The shader
tells a real dot from trailing array padding by element count — the index
of the last non-zero element plus one — so no producer needs a sentinel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 17:37:24 +03:00
Hakan SevenandClaude Opus 4.8 ce2365e8f9 fix(osnap): snap to arc/circle centre when hovering the curve (#152)
The Center object snap was a pre-baked point gated purely on the
cursor's screen distance to the centre point. A circle's centre sits
inside it and an arc's centre is usually off in empty space well away
from the curve, so hovering the curve — the natural gesture — never
landed within the snap radius and no centre marker appeared.

Add a curve-proximity pass: when Center is enabled and the cursor is
near a circle/arc/ellipse curve, offer that curve's centre, ranked by
the cursor's distance to the curve. It runs after the `try_pt` closure's
borrow ends so it can update the candidate state directly, mirroring the
Tangent pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 17:01:23 +03:00
Hakan SevenandClaude Opus 4.8 6fb5c89a7b fix(hatch): preserve all island loops in complex hatches (#148)
The hatch boundary was flat-truncated to 1024 vertices before upload.
The active renderer is the batched path, which stores boundaries in a
GPU storage buffer with no length limit, so the cap only ever served
the per-hatch uniform path (now wipeout-only, and self-clamping).

On a real drawing a 36-loop, depth-3 nested hatch (1482 verts) was cut
mid-boundary: trailing island loops were dropped and the final partial
loop was left open, flipping the even-odd parity so the fill bled across
the rest of the shape and islands rendered incorrectly.

Drop the flat truncate. Keep every loop; only guard pathological vertex
counts (16384), and when trimming, cut at a NaN sentinel (whole-loop
boundary) so no sub-loop is ever left open.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 16:11:15 +03:00
Hakan SevenandClaude Opus 4.8 e6e8167b36 feat(render): boost model-space lineweight display for legibility (#147)
Lineweights were rendered at their true physical size (mm × 96/25.4 px),
which is ~2 px for a 0.5 mm line — too timid to tell weights apart, and
every weight ≤ 0.25 mm floored to 1 px (no visible difference). Other DWG
editors display model-space lineweights larger than true size for screen
legibility; apply the same 2× boost so weights are pronounced and
distinguishable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 15:39:12 +03:00
Hakan SevenandClaude Opus 4.8 57153254bc build(deps): bump acadrust for the SORTENTSTABLE draw-order fix (#146)
Pull in acadrust 16332e5, which reads/writes the DWG SORTENTSTABLE in the
correct stream layout (sort handles inline in the data section; owner block
+ entity handles in the handle stream). Draw order set in other CAD apps —
e.g. a line sent behind a hatch — is now honoured instead of falling back to
handle order.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 15:04:53 +03:00