Commit Graph
537 Commits
Author SHA1 Message Date
Hakan Seven dc998fe46b refactor(tess): move solid sampling to kernel
Remove local face, edge, spline, sweep, loft, and silhouette
samplers. Meshes and overlays share the kernel tolerance and edge
schedule.
2026-08-10 10:18:53 +03:00
Hakan SevenandClaude Opus 5 afb4fd4733 fix(tess): sample a curve against its own size, not an absolute length
Round things came out with too few sides in drawings measured in metres, and
zooming in did not help.

The chord tolerance was floored at five thousandths of a world unit. That is
a fine sampling for a part measured in millimetres and a coarse one for a
building measured in metres: a ten-centimetre pipe got about ten sides and a
five-centimetre bolt about seven. The floor was also what stopped the frame
asking for better, so closing the camera on one changed nothing.

A chord height is an absolute length, so any fixed value carries an assumption
about the drawing's units. Bounded against the curve's own radius instead,
which carries none. The two ends are really segment counts: a circle gets at
least about fifty sides and never more than about a hundred and sixty,
wherever the camera is and whatever the drawing is measured in. Between them
the frame still decides, so a curve fills in as the camera closes on it.

Straight runs keep the frame's own tolerance — there is no curvature to
sample, and two ends are all a line has.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-10 02:44:52 +03:00
Hakan SevenandClaude Opus 5 8c7ebdc9f1 refactor: drop truck, and evaluate every curve through the kernel
The last four truck crates are gone from the manifest. What they were doing
falls into three parts, and each now has one answer instead of two.

NURBS evaluation. truck was carrying B-spline curves and surfaces for the
SPLINE entity, hatch boundaries, BLEND's endpoint frames, the spline preview
and ACIS spline-surface faces. The kernel now has both, over one de Boor
written across however many coordinates a control point holds — so a plane
curve and a space curve cannot drift apart, and a surface is the same
algorithm applied twice. The rational and the polynomial cases stop being
separate types: weights absent means polynomial.

The entity conversion. Every LINE, ARC, ELLIPSE, SPLINE and polyline was
built into truck topology purely so it could be sampled back into points.
They are sampled through `entities::curve` now, which is where each one's
geometry is already defined once and what EXTRUDE and REVOLVE read — so a
circle drawn on screen and a circle handed to the Model tab come from the
same definition. That retires four of TruckObject's variants and the module
is renamed for what it does.

SWEEP and LOFT. Both only ever produced a mesh, so both are built from point
lists: a band of quads per span, and a lid where a profile closes. Lofting
profiles of different densities resamples them by distance rather than by
index, so a circle lofted to a square no longer twists.

Two things worth noting for anyone reading the old comments. The tolerance
globals that lived in the tessellation module were never about truck at all —
they are the per-frame chord height, and they move to `curve_tol`. And the
rule that a profile handed over as `Lines` silently broke EXTRUDE and REVOLVE
no longer holds: those read the curve directly and never look at this channel.

The two `automation` test failures are unchanged from before this and are not
caused by it.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-10 01:08:50 +03:00
Hakan SevenandClaude Opus 5 1a37562a28 chore: take the stack with its solid layer and ACIS bridge
The kernel gained a B-rep layer — topology, analytic surfaces, surface
intersection, face splitting, point classification, booleans — and
acadifc gained the bridge that lifts an ACIS document into it and lowers
it back, provenance-driven so an untouched body saves byte for byte.

The reachability test is a compile-time one on purpose. A dependency
chain that stops resolving is caught where it breaks rather than the
next time somebody reaches for the far end of it.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-09 11:26:57 +03:00
Hakan SevenandClaude Opus 5 77a9167e34 fix(area): measure the curve, not a polygon through some of its points
A hatch's area was not the area of anything. Its boundary was flattened
into a ring by pushing an arc's *centre* into it, and a spline's control
points — neither of which is on the boundary — and the shoelace formula
was run over the result. Boundary edges now go through the kernel, which
measures each for what it encloses: an arc contributes its sector, a
spline is integrated.

AREA had three measurements between them. The bulged-polyline one
carried its own arc corrections; the planar entities it did not cover
fell through to mass properties. Both are now one call on the entity's
curve, which gets the bulges, the ellipse and the spline right in the
same place. What is left is the genuinely spatial — a 3D polyline, a
spline whose points wander off any plane — measured as rings of points
by Newell's method rather than by projecting them flat.

Regions were doing the same Newell sum by hand. They share it now.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-09 01:44:04 +03:00
Hakan SevenandClaude Opus 5 ece440bcab refactor(curve): draw a fit spline and an offset preview from the curve
Two places were left describing geometry for themselves.

A fit-point SPLINE was drawn from a C² cubic solved in spline.rs while
TRIM and snap cut a separate interpolation from the kernel. Near enough
to look right, far enough apart that a trim landed beside the line it
was aimed at. It now draws the curve those commands work on, and falls
back to the local solve only where the kernel declines — a fit spline
through points in space is not a planar curve, so there is nothing for
it to say.

Closed fit splines were worse than that: the kernel's interpolation is a
clamped solve with no wrap in it, so the curve TRIM used never returned
to its start and the seam cut nothing. Repeating the first fit point
closes it. The seam is C¹ where the rest of the curve is C², which is
the honest limit of a clamped solve — the alternative was a curve with a
gap in it.

OFFSET's preview sampling had drifted the same way TRIM's had: an arc
and a circle read `center.x/.y` as world coordinates when the entity
stores them in its OCS, so an extruded one previewed on the wrong side;
every curved kind was cut at a fixed twenty segments per radian whatever
its size; and an ellipse rebuilt its axis frame from the major axis' X
and Y alone, dropping the Z the entity stores. A hundred lines of that
become one tessellation. A RAY now previews as far as an XLINE does
rather than as a stub the length of its direction vector.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-09 01:08:45 +03:00
Hakan SevenandClaude Opus 5 90abfc21a9 refactor(render): tessellate curved entities from their own curve
The point lists were built three different ways and none of them scaled
with the geometry. A full ellipse was cut into sixteen segments per half
and an elliptical arc into thirty-two, whatever its size, so anything
large read as a polygon; a circle worked out its own segment count from
the chord tolerance; an arc was handed to truck's arc-through-three-
points builder, which cancels at survey coordinates.

All three now sample the entity's curve at the render pass's chord
tolerance, so how round something looks follows how big it is and how
far in the view is zoomed. The ellipse also stops casting its local
coordinates to f32 on the way in — the vertex path splits f64 into a
double-single pair precisely so that precision survives to the shader,
and throwing it away first defeated that.

What does not change is the kind of object each entity hands over.
EXTRUDE, REVOLVE and SWEEP read their profile and path out of Contour or
Curve and have no arm for a bare point list, so an arc or an ellipse
given as Lines would quietly stop being usable as either. The arc keeps
its circle_arc edge for that reason; only the ellipse's control points
change, and they keep the same two-half-edge wire.

The ellipse now decides it is closed from its own endpoints rather than
from its stored parameters, so a file that writes an end a hair short of
a full turn still produces a wire a plane can attach to.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-09 00:55:29 +03:00
Hakan SevenandClaude Opus 5 b36e85e46e refactor(snap): take an entity's snap points from its curve
Each entity built its own, and what they offered had drifted apart from
what the geometry actually has:

- An arc offered no quadrants at all. It now offers the ones its sweep
  covers, and no more.
- A spline offered its control points as endpoints. For a control-point
  spline those are not on the curve, so the cursor landed in empty space
  beside the geometry. It now offers its two real ends and its midpoint,
  with the fit points — which are on the curve — still on offer.
- An elliptical arc had no midpoint and no ends.

A new SnapHint::Endpoint carries an end that cannot go in key_vertices.
Consecutive entries there are taken to be joined by a straight segment
and their midpoint offered, which is right for a line or a polyline and
wrong for anything curved: an arc's two ends would also produce a snap
out on the chord. Splines lose their key_vertices for the same reason —
the chord between two fit points of a curvy spline does not run along
it.

A polyline's midpoints and arc-segment centres stay out of snap_pts
deliberately. The engine already derives a chain's midpoints from its
vertices, and a wire carrying a centre is treated as round elsewhere,
which a polyline with one bulge in it is not.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-09 00:43:44 +03:00
Hakan SevenandClaude Opus 5 57661c5754 feat(entities): give an entity its geometry as a curve
The same shape was being read out of an entity's fields in three places
and the three had drifted. An arc's tessellation cut it at 20 segments
per radian, TRIM's sampling at 16, and its snap points came from a
fourth expression again; a spline was sampled at a fixed 96 points
however long it was; TRIM read an arc's centre as world coordinates
when the entity stores it in its OCS, so an arc carrying the (0, 0, −1)
normal a MIRROR leaves behind acted as a boundary on the wrong side of
the drawing.

entity_curve returns a PlanarCurve: the shape, plus the plane its
coordinates are read in. The plane comes from the entity's OCS — its
extrusion normal through the DXF arbitrary-axis algorithm — not from the
UCS. A UCS is the working plane a user drew in and an entity does not
remember it: draw a circle in a UCS turned 30° about Z and the stored
normal is still +Z, which is why the header keeps the UCS separately.
The algorithm itself stays in transform.rs, where the format is
understood and where #142's degenerate case was fixed once.

Entities that store world coordinates and may not be planar at all —
LINE, SPLINE — are checked rather than assumed. A spline whose points
wander in space returns None instead of being flattened onto XY, which
would move it.

entity_curve_xy folds the plane away for the editing commands, which
work in plan view and need every boundary in one frame before two of
them can be intersected.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-09 00:43:35 +03:00
Hakan SevenandClaude Opus 5 397fb38292 refactor(offset): take polyline offsetting from cadkernel
The offset core moves out: normalising the source, the cavalier_contours
call, the sharp-corner join fixup and the conversion back. What stays is
entity work — reading an LwPolyline in, writing offset LwPolylines out,
and the per-type offsets for lines, arcs, circles, ellipses and splines,
which are analytic and never needed the polyline machinery.

`BulgeArc` moves with it, since the offset preprocessing splits over-half-
turn arcs and cannot work without it. `entities::common` now re-exports it
from the kernel, so the twelve modules already reaching for
`entities::common::BulgeArc` are untouched.

`norm_rad` was a fourth copy of angle normalisation, after the three
removed from trim, fillet and explode. It now aliases the kernel's.

cavalier_contours leaves this crate's manifest: nothing here calls it any
more, and it arrives through the kernel's `offset` feature instead, which
acadifc forwards.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-08 16:01:43 +03:00
Hakan SevenandClaude Opus 5 7307ea3136 fix(dim): measure a two-line angle between the lines that define it
A two-line angular dimension stores two lines, not two rays:
first_point→second_point is one, angle_vertex→definition_point the other,
and the angle is the one they make where they cross. The renderer read
angle_vertex as the centre and the other two points as the rays from it,
which measures something else entirely — a ten degree angle came out as
two hundred and seventy, and a shallow one as very nearly a full circle.

The lines are now intersected for the real vertex, and the arc point
picks which of the four angles at that crossing is meant. Each line
offers its direction and its reverse, and the sweep is tried from either
line: taking only the first→second order leaves out half the angles, and
the half left out is the one wanted whenever the lines are nearly
parallel. Parallel lines cross nowhere, so only the extension lines are
drawn.

A zero sweep was also being turned into a full turn, because the wrap
that pushes a negative sweep forwards ran on zero too. Two rays pointing
the same way enclose no angle and now draw none.

Checked against the three angular dimensions in a drawing that carries
its own baked arcs: the vertex and sweep computed from the stored line
endpoints reproduce the baked centre and angle to the decimal — 7.48,
10.61 and 2.29 degrees — from entirely separate inputs.

This is the path taken when a dimension has no baked block: one OCS drew
itself, or one whose block was dropped by an edit. Both paths now agree.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-08 02:59:34 +03:00
Hakan SevenandClaude Opus 5 2ea3bca093 feat(properties): stop offering a height the style has fixed
A text style with a non-zero height fixes the size of everything drawn in
it — the CAD that writes the file skips the height prompt for such a
style, and shows the height without letting it be changed. OCS offered
the field either way, so a drawing could be edited into disagreeing with
its own style.

The height row is now read-only when the style fixes it, for TEXT, MTEXT
and both attribute kinds. `num_prop` already draws a numeric row either
way, so the value still reads the same, just grey.

The panel builders are handed an entity, not the document it came from,
and widening that trait would touch every entity type. The lookup rides
beside the unit context instead, seeded from the same place, since it
answers the same kind of question: something the drawing settles that a
row needs in order to render itself.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-08 02:45:46 +03:00
Hakan SevenandClaude Opus 5 6cbec232bb feat(units): read back every form the drawing writes
The readout learned to write lengths as 5'-6 1/2" and angles as
N 45d0'0" E, but typed input was still a bare parse::<f64>() — a
coordinate could be displayed and not entered. Give the formatters
their inverses.

parse_length takes feet and inches separated by a dash, a space, or
nothing, with the closing quote optional, and whole-plus-fraction in
any of those forms: 5', 60", 5'-9", 5'9-1/2, 9-1/2, 1/2. A leading
minus is a sign; the one inside 5'-9" is a separator, since after feet
there is nothing left to subtract from.

parse_angle reads the mark that says which convention an angle is in —
g for grads, r for radians, d/'/" for degrees-minutes-seconds, compass
letters for a surveyor's bearing — and reads a bare number in whichever
convention the drawing is set to.

ANGBASE and ANGDIR now apply, but only where they mean something. Which
way a thing points is measured from the drawing's zero and runs the way
it says; how wide an arc opens is the same number whatever zero is
counted from. format_direction and parse_direction are the pair for
directions, and the polar readout and polar input both go through them,
so the bearing shown is the bearing accepted. format_angle stays for
sizes, where applying a base angle would corrupt the value.

The command prompts follow the same rule, each classified rather than
swept: OFFSET distance, FILLET radius, CHAMFER distances, ARRAY and
MINSERT spacing, TRACE width, HELIX, and EXTRUDE height read lengths;
the four dimension text-rotation prompts, ARRAY's total angle and
INSERT's rotation read angles. MLINE's scale and INSERT's scale factor
are left alone — a multiplier has no unit to be written in. INSERT read
both through one parse; they are now separate.

The typed variants also take a decimal comma, which a single-value
prompt has no other use for. parse_length itself does not: inside a
coordinate the comma separates the axes.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-07 23:46:49 +03:00
Hakan SevenandClaude Opus 5 523b85f42b fix(units): make the units selector reach what it names
The status-bar units button wrote INSUNITS, which nothing on screen reads.
INSUNITS only tells the drawing how to scale content arriving from
elsewhere, so picking a unit relabelled the drawing and changed nothing
visible — the selector looked broken because the variable it moved was
invisible.

Point the button at LUNITS instead, the setting that decides how every
coordinate and distance is written, and give the three questions the word
"units" runs together a place each:

- The button offers the five length formats, each shown beside a sample of
  itself, so Engineering and Architectural are told apart by sight.
- UNITS / DDUNITS opens a real dialog: length and angle format and
  precision, the direction angle zero points, whether positive angles run
  clockwise, and the insertion unit. Its sample comes from the same
  formatters the drawing uses, so it cannot drift from the readout.
- DWGUNITS converts. Model geometry, viewport framing, the header's length
  defaults and each dimension style's scale all move together in one undo
  step; paper space is left alone, since the sheet is still the same sheet.
  Locked layers are converted too — a half-converted drawing would be at
  two scales at once.

The coordinate readout formatted at four decimal places whatever the
drawing asked for; it now goes through the same format_length the
properties panel uses. That made the angle formats reachable in turn, so
degrees-minutes-seconds now carries the d/'/" marks that say which
convention it is, and surveyor's units are written at all.

Typed input is still plain decimal: a length the readout writes as
5'-6 1/2" cannot yet be typed back. ANGBASE and ANGDIR are stored but not
yet applied to displayed directions.

Closes #668

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-07 19:59:09 +03:00
Hakan SevenandClaude Opus 5 ca152de1c0 refactor(text): let acadrust carry the rotation through a transform
The merged fix added the rotation update on this side, matching only
`EntityTransform::Rotate`. That left `Affine` -- which several entity
paths and the UCS world transform emit -- still dropping the angle, and
it sat against the contract this crate already relies on: MIRROR is
delegated precisely because acadrust's entity-aware paths "handle
direction flags, stored-angle conventions and bulges themselves", and
`mirror_text` did exactly that for the same field.

Moved upstream (acadrust 9ecaffc), where one helper carries the angle for
every entity that keeps one. That covers rotation, affine and reflection
in a single place, and closes the same gap for SHAPE and the ATTRIB pair,
which a rotation left facing the old way too -- attributes noticeably so,
since INSERT already turned itself while its attributes did not.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-07 11:48:53 +03:00
gianlucafiore 4dbe9775eb Fix text rotation transforms 2026-08-07 00:16:48 -03:00
Hakan Seven b667eeeb05 refactor(render): unify scene rendering paths
Route model, paper, viewport and nested block content through one traversal while preserving leaf renderers. Carry decoded material state into mesh shading and harden solid geometry conversion.
2026-08-05 21:44:59 +03:00
Hakan Seven 657d023fb7 feat(ucs): integrate coordinate systems
Route coordinate input, geometry creation, transforms, properties,\nviews, and viewport persistence through a shared working plane.\n\nRefs #484
2026-08-04 23:17:54 +03:00
Hakan Seven b1d55c6a7e feat(view): add visual style previews
- preview render modes on hover without committing
- distinguish 2D and 3D wireframe fill and depth behavior

Refs #483
2026-08-04 21:23:25 +03:00
Hakan Seven 539d83de47 feat(grips): improve direct editing
Refit arcs from fixed control points and highlight hovered grips.
Let Lengthen preview from the cursor or accept a typed distance.
2026-08-04 16:27:03 +03:00
Hakan Seven 48332ee807 feat(area): add object and aggregate modes
Mesh-backed measurements need cached surface metrics. Keep shared loop collection available in web builds.
2026-08-03 22:31:20 +03:00
Hakan Seven de2707a23e fix(styles): apply active creation defaults
Resolve style properties before editors and previews open.
Use absolute annotation multipliers for style-driven objects.

Refs #401

Refs #431
2026-08-03 16:43:58 +03:00
Hakan Seven 25515fa33c feat(styles): overhaul style managers
Unify editor layouts, live previews, comparisons, read-only behavior, and localized controls across all named style managers.

Refs #629
2026-08-03 02:01:47 +03:00
Hakan Seven 3e6693b93f feat(locale): localize full interface 2026-08-02 22:09:25 +03:00
Hakan Seven 20c6d419e2 fix(dimensions): render custom arrow blocks
Refs #612
2026-08-02 16:06:37 +03:00
Hakan Seven e03e091b3c feat(annotation): integrate object contexts 2026-08-02 12:00:15 +03:00
Hakan Seven 5909540ebf fix(annotation): apply object context scaling 2026-08-02 03:02:11 +03:00
Hakan Seven 7b50d6a5a3 fix(solid): normalize quadrilateral edges
Accept perimeter and DXF Z point orders while keeping screen fill and interaction on the WCS-aware geometry.

Refs #617
2026-08-01 20:31:28 +03:00
Hakan Seven 1cf2b700ce fix(polyline): place added vertices live
Keep new vertices in grip placement until click and restore the original entity on Escape. Correct reverse direction and segment metadata across polyline variants.\n\nRefs #610
2026-07-31 23:36:07 +03:00
Hakan Seven 9fdd26e1ed build: update acadrust codec revision 2026-07-31 18:05:51 +03:00
Hakan Seven 2c64888048 fix(blocks): preserve UCS transformations
Bake BEDIT frames into block-local geometry while keeping model and viewport UCS isolated. Normalize legacy nonzero block origins during load.
2026-07-30 19:50:33 +03:00
Hakan Seven b8889ccd20 feat(mview): add viewport creation options
Support polygonal and object viewports through native clip-boundary handles, including clipped rendering and selection.

Closes #525
2026-07-27 19:28:51 +03:00
Hakan Seven 4a30928f5a fix(ui): refine empty drawing state
Show drawing defaults when nothing is selected and disable layout controls on Start.

Refs #514
Refs #515
2026-07-27 17:37:17 +03:00
Hakan Seven 759f2316aa fix(hatch): apply pattern property edits
Closes #476
2026-07-27 16:44:31 +03:00
Hakan Seven 6fb68d1c7c feat(scene): integrate DWG object data 2026-07-26 23:38:38 +03:00
Hakan Seven eb23715605 feat(scene): integrate standard DWG data 2026-07-26 22:30:14 +03:00
Hakan Seven 731df4a725 feat(cad): integrate standard codec coverage
Render, edit, query, select, and explode newly decoded standard entity variants. Pin acadifc de8d6b5 and preserve None color semantics.
2026-07-26 19:30:43 +03:00
Hakan Seven 34d0c377e2 fix(3d): complete ACIS solid rendering
- track per-face coverage and retain safe display fallbacks
- tessellate shared NURBS, pcurve trims and periodic surfaces
- pin the matching acadifc decoder revision
2026-07-26 01:35:16 +03:00
Hakan Seven ece5598372 fix: correct viewport stretch and SHX plots
Anchor the opposite viewport corner during grip and STRETCH resizing.
Match plotted stroke-font pen widths to the SDF atlas unless CTB overrides.

Closes #435
2026-07-24 19:26:10 +03:00
Hakan Seven ceddb287f5 fix(renderer): match leader and dash styles
Read canonical ACAD/DSTYLE overrides for leader color and arrow size. Scale block-local linetype patterns through INSERT transforms.

Closes #436
2026-07-24 19:04:57 +03:00
Hakan SevenandClaude Opus 4.8 ce49f4da0a fix(render): DXF leader hookline width + SHAPE name fallback; bump acadrust
Companion to the acadrust DXF-fidelity sweep (bump to 379ce12):
- Legacy LEADER hookline: underline the annotation using the leader's own
  stored text width (DXF code 41) when the MTEXT has no computed extents,
  so the landing spans the text instead of a short stub.
- SHAPE: resolve the glyph by name when the shape number is 0 (DXF SHAPE
  carries only the name), and search shape-file text styles when the entity
  has no direct style reference.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-24 11:30:03 +03:00
Hakan SevenandClaude Opus 4.8 0e61836e91 fix(properties): name AEC / proxy objects instead of "Unknown"
Selecting a proxy object we render from its cached graphics (an AEC
Wall/Door/Window and the like) showed "Unknown" as its type. The entity
now carries its real DXF class name from the CLASSES section (acadrust
bump), so surface it: ui_name_or_class title-cases the class ("AEC_WALL"
→ "Aec Wall") for the properties Type row and the status-bar label, and
still says "Unknown" only when the class truly can't be named.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-23 23:59:27 +03:00
Hakan SevenandClaude Opus 4.8 5e2b67c642 fix(linetype): keep complex linetypes precise at large coordinates
A circle with a complex linetype (embedded shapes/text, e.g. IZOLACE) drew
its dashes and symbols scattered far off the entity at UTM-scale
coordinates. Two defects compounded, both catastrophic-cancellation at a
~1.2M-unit origin:

- The single-entity complex-linetype path fed apply_along the wire's HIGH
  double-single half only (`base.points`), an absolute f32 quantised to
  ~0.1 at 1.2M. Walking the pattern along those points blew the shape /
  dash positions up to ~1e308. Reconstruct the absolute f64 path from
  high+low, re-origin it at the first vertex, walk apply_along in that
  local frame, then lift each result wire back to world with
  shift_wire_to_world (now pub(crate)) — the same local-frame trick the
  MLINE complex-linetype path already uses. Fixes every entity, not just
  circles.

- The circle was tessellated with truck's circle_arc (arc-through-three-
  points), whose fit cancels catastrophically at 1.2M and returned a ~3%
  radius wobble with uneven segments. Tessellate directly as a cos/sin
  polyline (adaptive segment count from the curve tolerance); adding a
  small ±r term to the centre stays precise, and the Lines path RTE-splits
  it. Arc and polyline bulges share the circle_arc root and remain to fix.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-23 17:16:57 +03:00
Hakan SevenandClaude Opus 4.8 2d3f33de10 fix(leader): suppress oversized arrowheads, keep them precise at UTM
Three defects made a leader's arrowhead render as one stray line far
longer than the leader itself, at UTM coordinates:

- The arrowhead was drawn even when it is larger than the first leader
  segment it sits on. A DIMASZ sized for a bigger plot (here 6 units on a
  ~1-4 unit pointer) painted a stroke longer than the whole leader.
  AutoCAD omits the arrowhead when it does not fit the first segment, so
  gate the arrow on `arrow_size <= first_segment_len`.

- Leader geometry was built as absolute f32. At UTM coordinates (Y ~ 4.5M,
  f32 ulp ~ 0.5) the oblique arrow tick's small cross-slope quantised
  away, collapsing the 45-degree tick into a flat horizontal line. Build
  the path and arrow in absolute f64 — the arrow around a local origin so
  append_arrow's f32 math stays precise — then RTE-split into the double
  single (points_low / fill_tris_low) at the end.

- The `_Oblique` / `_Archtick` arrowhead block mapped to ArrowKind::Tick
  with the full DIMASZ, but the tick emitter draws `size` to either side
  (half-length semantics, matching DIMTSZ). Pass half so an oblique
  arrowhead sizes to DIMASZ like every other arrowhead, not double.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-23 16:46:55 +03:00
Hakan SevenandClaude Fable 5 0d4bd274b4 style: clear two build warnings
- image_model.rs: drop unnecessary parentheses around a map's array
  literal return.
- text_support.rs: remove dead row_in_column / col_rows computation and
  its unused `li` binding (the only consumer).

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-23 11:39:23 +03:00
Hakan Seven 1803f10264 Merge PR #442: cut mesh-import wire-set memory ~35% (#358)
entity_wires() returns the memoized Arc instead of a per-call deep copy;
MESH entities over 50k vertices ship no per-vertex snap/key tables
(matching PolyfaceMesh/PolygonMesh); the Face3D/other render split
caches a None marker and uses the base set directly when there is no
Face3D wire; test fix for the depth-map BlockCache::build signature.

Reviewed: correctness sound — wire AABB comes from entity_aabb (not
key_vertices) so large meshes stay selectable; snap consumers are
empty-guarded; the Arc/strong_count reasoning for try_resident_patch
holds. One consistent pre-existing quirk noted (auto_fit_viewport
centroid excludes tableless meshes, as Poly*Mesh already do).
2026-07-23 10:25:27 +03:00
Hakan SevenandClaude Fable 5 faa135a7a1 fix(mleader): read the attachment anchor from the annotation context
The horizontal attachment point was read from the entity-level field,
which only exists from R2010 on; older records (R2007 and earlier) left
it at the default and centre/right-attached leaders shifted. The
annotation context carries the same value in every version, so anchor
from there for both the render and the width-grip geometry.

acadrust bump also brings: the R2007 MULTILEADER tail gating (leaders no
longer render upside-down), the R2004-R2006 inline SAB payload read, the
v1-SAT wireframe point_present decode (3D solids/regions land in place
on R2000/R2004), and the XCLIPFRAME default of 2 so clip-frame borders
draw on files whose header omits the variable.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-23 08:15:51 +03:00
Hakan SevenandClaude Fable 5 bfcc71678a fix(mline): drive geometry from the stored vertex parameters
Element lines were re-derived from the style's flat offsets along the
vertex miters, but the file already stores the real numbers: each
vertex's per-element parameter list starts with the offset measured
ALONG THE MITER — corner lengthening (1/cos of the half-angle),
justification and scale baked in — followed by draw/gap toggle
distances along the segment, which is how crossing multilines cut
each other. Re-deriving pinched the channel at every corner (the ends
looked flared) and drew full uncut lines through crossings, stacking
intersecting multilines on top of each other.

mline_lines now consumes the stored parameters directly (runs chain
across vertices when continuous; an odd toggle count leaves the last
run open to the segment end) and computes end caps from the actual
per-vertex offsets. Files without parameters keep the style-offset
fallback.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-23 07:00:35 +03:00
Hakan SevenandClaude Fable 5 bcd1fc15ea feat(shx): real SHX glyphs for text fonts and linetype shapes
The SHX reader now serves all three consumers instead of just SHAPE
entities:

- Complex-linetype shape elements stopped being skipped: a document
  linetype's Shape{number} resolves its style's shape file (shared path
  fallbacks) and draws the real glyph; `.lin` catalog references keep
  the file token and look the shape up by name. The converted-LFF
  substitute set remains the fallback for anything unresolved.

- Face::Shx renders resolvable on-disk .SHX text FONTS with their real
  stroke glyphs: character code → shape lookup, cap height normalised
  to the 9-unit space every stroke-font consumer expects, advance from
  the interpreter's final pen position, line spacing from the shape #0
  above/below header, TTF fallback per missing glyph.
  resolve_text_style resolves a style's .shx next to the drawing and
  passes the absolute path through; unresolvable names keep their LFF
  substitutes. Shapes-only files (no #0 header) are refused as fonts.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-23 06:44:48 +03:00
Hakan SevenandClaude Fable 5 1de4344280 feat(shape): render SHAPE entities from their compiled SHX shape file
SHAPE entities only drew a diamond placeholder — .SHX files were never
parsed. A new reader (scene/text/shx.rs) decodes the classic
`AutoCAD-86 shapes 1.0/1.1` container and interprets the full shape
bytecode — pen up/down, scale divide/multiply, position push/pop,
subshapes, signed XY runs, octant/fractional/bulge arcs, the 16
direction vectors and the vertical-only skip — into unit-space
polylines, cached per file and per shape number.

The entity resolves its STYLE by handle (name fallback), the style's
shape file with the shared path fallbacks (as stored → relative to the
drawing → basename next to it; traded files carry foreign absolute
paths), then places the glyph with size × relative X scale, oblique
shear and rotation through the OCS mapping. load_file now records
doc.source_path so render-time resolution knows the drawing's folder.
A missing or unreadable shape file keeps the diamond placeholder.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-23 03:53:41 +03:00