The Esc path cleared box_anchor but left box_anchor_world behind, so
the next middle-button pan re-projected the world anchor back into
box_anchor (the #234 camera-tracking path) and the cancelled marquee
sprang back to life. Clear the world anchor too, matching the other
cancellation sites.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
HatchModel lost the field when viewport clipping moved to the GPU
stencil path; the literal in this test no longer compiled.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hatch boundary paths in real files do not store their edges as a
sequential walk: associative hatches list them in boundary-source
order with arbitrary direction, so the next edge may attach to either
end of the chain built so far — or to the far side of the loop.
Concatenating them verbatim drew self-crossing "bowtie" outlines that
flipped the even-odd fill over the wrong region (block-internal
pattern hatches, 26-edge associative loops). chain_path_edges keeps
the loop open at both ends and greedily attaches the unused edge whose
endpoint lies nearest either end, reversing or prepending as needed;
correctly-ordered files match at distance zero and reproduce exactly.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Images, OLE frames, wipeouts, underlays, paper-space viewports and
drawing-view borders were selectable only on their outline. Each now
carries an interior pick surface (pick_tris over its rectangle, or the
triangulated clip polygon for a clipped underlay), ranked below edge
and fill hits so content drawn inside still wins the click. The
layout's own sheet ("overall") viewport is excluded — identified by
the Layout object's viewport link, since DWG files carry viewport
id = 0 and some center the sheet viewport off-origin — or it would
swallow every click over the real viewports beneath it. Drawing-view
borders draw nothing normally; they emit an invisible pick-only wire
whose rect outline appears while selected.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wire segments expanded only perpendicular to their axis, leaving wedge
gaps on the outside of corners and along tessellated arcs once a band
is wider than a pixel. Extend each quad longitudinally by the end
half-width and round the overhang off in the fragment stage: adjoining
segments now meet in overlapping round joints and line ends get round
caps, with the dash arc-length extrapolated across the overhang so
patterns stay continuous through a joint.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every rfd dialog result funnels through sys::handle_path, which now
records the picked file's folder (persisted to the config dir), and all
pickers are built via sys::file_dialog(), seeded with that folder.
Save-As keeps the current tab's own directory as the first choice and
falls back to the remembered one.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
OLE2FRAME entities whose compound file carries a metafile presentation
(Excel/Word previews, scanned WMF pages) drew only their border frame —
the old decoder scanned the raw blob for a contiguous BMP, which the
CFB sector layout almost never provides. acadrust now extracts the
picture bytes tagged by kind; the new scene/model/gdi player rasterizes
EMF and WMF record streams on the CPU (pens/brushes, polygons, paths,
DIB blits, clipping, transforms) with text drawn through the existing
system-font glyph cache, and image_model feeds the result into the
raster pipeline. Rasters (BMP/PNG/JPEG/GIF, CF_DIB) decode as before
via the image crate.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Section marks and drawing-view borders are now real acadrust entity
types instead of Unknown-with-extras, so the renderer dispatches on the
variant directly, the arrow-direction resolver reads the active viewport
off the ViewBorder entity, and selection / properties show "Section
Symbol" and "View Border" instead of "Unknown". View borders are
non-plotting and tessellate to nothing; both count toward the lossy
Save-As warning since they round-trip via their raw records.
Bumps acadrust for the entity promotion.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The section mark's appearance now comes entirely from file data instead
of invented conventions:
- Arrow direction: resolved through the drawing-view graph — the
symbol's parent AcDbViewRep, the section view (owner of the section
definition), each view border's active viewport — by projecting the
section camera's sight onto the parent camera's basis, using the same
yaw/pitch/twist convention the viewport renderer does. Only when the
chain is unavailable (DXF, older files) does a geometric fallback
apply.
- Broken cutting line: driven by the style's plane-line / end-segment
flags rather than hard-coded.
- Arrowhead: resolved through the shared dimension/leader arrow table
from the style's arrow block handle; a null handle gives the standard
ClosedFilled default.
- Label gap: the style's identifier offset.
Bumps acadrust for the style and view-graph decode.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Draw decoded section symbols (AcDbSectionSymbol) as their "A-A" cut mark:
the cut line through both endpoints extended past each end by its signed
tick, a short perpendicular barb at each tip, and the identifier glyph.
The mark now appears on the layout alongside the drawing views instead
of being silently dropped.
Bumps acadrust to pick up the section-symbol decode.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A floating viewport with a real saved view — a non-zero view target, as a model
documentation drawing view or a detail/section viewport carries — now uses that
view directly instead of running the median-cluster overlap test. That test
measured against the densest sub-cluster (a big symbol library beside the small
view geometry) and wrongly auto-fit these viewports onto the library. A fully
uninitialised view (target AND centre both zero) frames its saved origin and
stays empty rather than auto-fitting the whole model in; the auto-fit rescue is
kept only for a stale target=(0,0,0) paired with a non-zero pre-georeference
centre.
Also widen the restored-view ortho depth half-range (view_height ×4 → ×64): a
thin projection such as a section/side view has content far deeper than its
visible height (its cut plane sits ~half an object-depth from the view target),
so the tighter range clipped the view outline behind the far plane. Orthographic
depth precision is linear, so the wider range costs no z-fighting.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A content viewport that froze layers still drew those layers' hatches,
2-D solids, images, OLE frames and 3-D solids — only its wires honoured
`Viewport.frozen_layers`. A viewport meant to be empty (all content
layers frozen) leaked the whole model instead.
viewport_data_for fed content viewports the full model hatch/wipeout/
image/mesh sets with no per-viewport frozen filter. Thread the viewport's
frozen-layer set through the builders (synced_hatch_models,
exploded_insert_hatch_models, wipeout_models, image_models, mesh_models,
instanced_block_meshes) via Scene::layer_frozen_in — the same
layer-name→handle→membership test the wire path uses — and expose
*_for_viewport accessors that cache the filtered set per order-independent
frozen-set signature (viewports sharing a frozen set share the build; an
empty set returns the shared unfiltered arc, so model tiles and
non-freezing viewports are byte-identical).
Each viewport renders in its own pipeline slot with the hatch upload
gated on geometry_epoch, so a per-viewport filtered set uploads once per
epoch rather than every frame.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
An INSERT attribute left with an empty value drew a "[]" placeholder: the
SingleLine tessellation fell back to `[value]` whenever every line was
empty, but that branch only fires once the value is already empty, so it
always produced bare brackets (and never carried the tag it was meant to
preview). On a heavily scaled block instance the unscaled brackets ballooned
across the sheet.
Render nothing for an empty value instead, matching AutoCAD, where a blank
attribute shows no text. An attribute definition previews its tag as a
non-empty value, so it never reached this branch and is unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The previous limits/paper-size ratio heuristic only recognised millimetre
(1.0) and inch (1/25.4) paper spaces. Issue #341's layout uses an
arbitrary architectural scale (~11.84 mm per paper-space unit), which the
snap-to-inch-or-mm logic missed, so the sheet was drawn 11.8x too large
and the drawing shrank into the corner.
Derive the mm->paper-space factor from the plot scale instead, matching
how the DXF format defines the paper-space/paper relationship:
page_unit_mm = 25.4 if plot_paper_units==inch else 1.0
mm_per_unit = (scale_numerator / scale_denominator) * page_unit_mm
factor = 1 / mm_per_unit (fallback 1.0 when scale is degenerate)
The scale and page unit are a matched pair, so this is correct even when
the page unit alone looks wrong (an inch page at 1:25.4 is a millimetre
paper space). Uses scale_numerator/denominator (codes 142/143), not the
scale_factor field (code 147), whose value is unreliable for Scale-To-Fit.
Verified against budweiser2018 (inch + mm layouts) and the #341 AEC Bldg
Plan Sample (11.84 mm/unit): every sheet now matches its viewport bbox.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Some layouts lay out paper space in inches (viewports and drawing limits
in inch units), but paper_width/height and plot margins are always
millimetres. paper_limits() drew the sheet at the mm size, 25.4x too
large, so the drawing shrank into the corner and the printable-area
dashes ran 25.4x too long.
Infer the mm->paper-space unit factor per layout from the limits/paper
ratio (plot_paper_units records the plot output unit and can disagree,
so it is not used). Scale the sheet, the printable-area margins and its
dash pattern by it. PDF export recovers physical mm by dividing back,
leaving millimetre layouts byte-identical.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A wide polyline with per-vertex varying widths rendered at its widest edge
(uniform-width ones were already exact). Draw the true taper instead.
`WireModel.taper_widths` carries a per-point full band width. The wire
instance (native slim + web fat) gains per-endpoint `world_hw_a/b`, and both
wire shaders interpolate `mix(hw_a, hw_b, which_end)` so each segment's band
narrows/widens linearly — depth-tested, and the linetype dash still applies.
Alignment (widths must match the tessellated points) is solved by building
both together: a new `TruckObject::TaperedLines(points, widths)`, filled by
`common::tapered_band_points` (straight = endpoints, arc = 16 samples with the
width lerped along it). to_truck routes a varying-width polyline here —
LwPolyline before the PLINEGEN split, Polyline2D at its return — while a
uniform-width one keeps the constant-band Contour path unchanged. `world_width`
stays the widest edge as the PDF-stroke + zoom-floor fallback.
Blocks fall back to the constant-widest band (`LocalWire` carries no
taper_widths).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extends the wide-polyline shader band to the two cases the first pass left on
the hatch pipeline.
Polyline2D:
- A flat wide Polyline2D now draws its band from the centre-line wire
(`world_width`), same as LwPolyline (`polyline_band_width` handles both).
- A thickened wide Polyline2D extrudes a solid 3-D tube. `thick_wide_band`'s
body moves to a shared `common::thick_band_tube(origin, fills, thickness,
normal, to_wcs)`; LwPolyline delegates to it and Polyline2D calls it from
its thickness path. Tube edges read black for both kinds.
- The model-space wide-band hatch loop is deleted (both kinds now wire/tube).
Blocks:
- Block-internal wide polylines draw the shader band too. `LocalWire` carries
`world_width`; `emit_wire` scales it by the insert transform (average of the
X/Y axis image lengths) so the band tracks a scaled insert, and the scaled
width joins the batch `style_key`. `push_block_wide_fills` (the block hatch
band) is removed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The shader-band change stopped emitting a hatch fill for wide polylines, so
PDF export — which drew the band from that HatchModel — lost it and plotted
only the thin centre-line.
Stroke the centre-line wire at the band width instead. In `build_pdf`, a wire
carrying `world_width` sets the pen to `world_width * MM_TO_PT` with no
`/ scale`: a lineweight is absolute on paper (hence the divide), but a band is
drawing-unit geometry and must scale with the plot. The CTM's scale then
renders the band at `world_width * scale` on the sheet, matching the geometry,
and the linetype dash pattern strokes it dashed. Overrides any CTB pen weight
(the width is geometry, not a lineweight).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A wide polyline's flat band was drawn by the hatch pipeline: a solid fill
with only draw-order depth. It ignored the entity linetype (the width
"crushed" the dash pattern) and showed through 3-D geometry in a tilted view.
Draw the band from the polyline's own centre-line wire instead. A new
`WireModel.world_width` (drawing units) makes the wire vertex shader expand
the quad by `world_half_width / world_per_pixel` (clamped to a hairline), so
the band is sized in world units and tracks zoom. Because it is now a wire:
- the GPU dash shader dashes the band by arc-length — the linetype applies;
- it is depth-tested at the polyline elevation — no draw-order see-through;
- no CPU band triangles or per-dash geometry are generated.
`world_half_width` rides in `WireConst` (native, replacing a pad) and the web
`WireInstance` (loc 14); both wire shaders branch on it. The tessellator sets
`world_width` from the LwPolyline width on the Contour / SegmentedLines arms,
and the model-space fill loop no longer emits a hatch band for any LwPolyline.
Pick still uses the band polygon (`pick_tris`).
Blocks keep the hatch band: `LocalWire` carries no `world_width` (finalize
zeroes it), so a scaled insert still scales its band correctly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Entering a thickness (DXF 39 extrusion) on a wide polyline now works end
to end:
- The properties Thickness field is a General-group common prop, so it is
routed to apply_common_prop (set_entity_thickness) instead of the
geometry setter, which ignored it — the value was silently dropped and
reverted to 0.
- A wide polyline extrudes its whole band into a 3-D tube (outer + inner
walls with top and bottom caps), not just its centreline.
- The tube is real 3-D geometry, not a flat draw-order hatch: it renders
solid in every view mode with true depth, so nothing shows through the
walls. The flat 2-D band fill is skipped for a thickened polyline.
- A fill whose triangles span depth keeps its real depth (no draw-order
bias), so the extrusion's own edge wires stay in front of the surface
and remain visible instead of only the silhouette.
- The corner and cap edge lines read black, framing the coloured tube.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rectangular paper viewports now go through the same GPU stencil clip as
non-rectangular ones: viewport_clip_boundary_ndc returns the four rect
corners (a full-target mask) instead of nothing, so the render path has
no rect/non-rect special case — every content viewport carries a clip
boundary and draws with stencil reference 0xFF.
With a single clip path, the old per-wire / -image / -wipeout scissor
mechanism is fully dead and removed: compute_*_scissors, the
*_pixel_scissors buffers, the project_scissor helper, the per-pass
set_scissor_rect blocks, the vp_scissor field threaded through
WireModel / ImageModel / HatchModel and their GPU mirrors (WireGpu /
ImageGpu / WipeoutGpu / HatchWebGpu), from_run's scissor parameter, the
scissor grouping in build_wire_buffers, and the is_arena_eligible gate.
Net -245 lines across 54 files.
Rectangular viewports still clip exactly via their own render rectangle;
the stencil mask is a redundant full-target no-op there, kept only so
the clip code path stays uniform. XCLIP keeps its CPU wire clip.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Paper content viewports with a circle / spline / ellipse clip boundary
drew their model content to the full bounding rectangle instead of the
shape. Each paper content viewport is its own render into its own screen
rectangle, so clipping is done per viewport: the boundary polygon is
projected into that viewport's render-target NDC (paper shape mapped
through the same visible-sub-rect crop as the content, so it stays fixed
to the viewport frame) and stamped into the stencil buffer with an
even-odd Invert, which marks the interior of any shape, convex or not,
without triangulation. Every content pass then draws only where the
stencil is set, so hatch, wire, mesh, 3DFACE, text, image, wipeout and
selection are all clipped uniformly.
Because Invert turns the cleared 0x00 into 0xFF, clipped content draws
with stencil reference 0xFF (unclipped content keeps reference 0). The
shared depth buffer moves to Depth24PlusStencil8 across every pipeline.
Rectangular viewports keep clipping via their own render rectangle and
XCLIP keeps its existing CPU wire clip; neither needs the stencil path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Re-evaluate AcDbField-driven MTEXT — date/login/document-property fields, table
formulas and object properties — against the current system instead of showing
the frozen last-save cache. The evaluation engine lives in `acadrust::fields`;
OCS supplies only the environment (system clock, OS login, env vars) through a
FieldContext implementation, and hooks the resolved text into MTEXT rendering,
falling back to the cached value for anything it can't evaluate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lights, camera views and the geographic-location marker had no on-screen
representation. Add display glyphs for all three:
- Lights render through a new EntityType::Light: a cone/flashlight for spot
lights, a wire sphere for point lights, a sun for distant lights, sized in
screen space (fixed-world fallback).
- Camera views (perspective VIEW records) and the GEODATA "daisy" marker are
synthesized into the model-space resident wire set from the document, since
they are not entities.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The DISPSILH silhouette for a cone anchored its `CurvedGen::Cone.base` at
the face's h_min (the base rim) but stored `radius` as the surface's h=0
root radius. The silhouette builder treats `radius` as the radius AT
`base`, so its base line landed at the wrong radius and its top line
(`radius + span·tan_a`) overshot the apex — a visibly wrong outline in
wireframe, and wrong lines drawn over the (correct) shaded fill. Store the
radius at the base instead (`radius + h_min·tan_a`). Cylinders (tan_a=0)
and root-based cones (h_min=0) are unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The `fill_is_3d` classifier tagged only PolyfaceMesh / PolygonMesh as real
3-D surfaces (fill routed to the 3-D buffer, drawn shaded-only). The
modern subdivision Mesh was missing, so its face fill fell into the 2-D
overlay buffer that draws in every view mode — showing the shaded faces in
wireframe. Add `EntityType::Mesh` to the classifier.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The paragraph-start caret slot used the raw entity height, so a blank
line's caret stood entity-tall — many times the visible text when `\H…x;`
runs shrink it (5× on budweiser2018.dwg handle 50019). Size it to the
line's own text height instead (an empty line inherits the previous line's
height, the same rule `per_line_h` already uses for spacing).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The editor preview mis-sized imported MTEXT badly (tiny, huge, or
collapsed). Two root causes:
- The entity's base height often isn't the visible text height: relative
`\H…x;` codes compound, so an entity of height 1 whose body is `\H0.2x`
renders at 0.2. Normalize the preview scale by the DOMINANT rendered run
height (the mode of glyph-box heights, skipping zero-width caret slots),
not `height_value()`, so the body lands at a fixed readable size and
headings keep their proportions. EM_PX = 15.
- The bug that made it "completely wrong" was in the serializer, fixed in
acadrust 389b31a (\H heights decayed on the parse→serialize→parse the
editor does via folded_value); bumped here.
Also render multi-column MTEXT with its columns side by side (as the
drawing does) and let the preview scroll horizontally to reach them.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Complete the editor/layout support for two MTEXT inline codes that were
parsed but not rendered:
- `\f…|i1;` set the font italic flag but layout only fed oblique from
`\Q`, so imported italic showed upright. Map font.italic to the same
15° slant the editor's Italic button uses (explicit `\Q` still wins),
and make that button recognise + clear the flag so imported italic can
be toggled off.
- `\~` (non-breaking space) was a normal Space atom, so lines could wrap
at it. Fold it into the word — it still renders as a space but no longer
breaks.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Route the MTEXT editor through the shared in-canvas modal frame with an
Apply-at-top toolbar (style-manager style): Apply commits and keeps the
editor open (creates on first apply, updates in place after), the ✕
cancels. Give every modal a bottom-right corner resize grip driven by a
shared grow-only `modal_resize` delta that resets with `modal_offset` on
open/close, so each dialog opens at its natural size and can be dragged
larger — style managers included.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
An empty `\P` paragraph carries no glyph to size it, so the per-line
advance fell back to the full entity height. In a small-text block (a
`\H0.2x` column) that blew a single blank line up to several times the
height of the text around it, shoving everything below it far down the
page. Size a glyph-less line by the last non-empty line's height instead.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Picks up the ezdxf-audited MTEXT parser fixes: raw LF as a paragraph
break, control chars rendered as spaces, and the corrected `\c`
true-color byte order (so `\c` colors no longer come out red/blue
swapped).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two defects in tabbed fields inside a multi-column MText:
- Stop positions are relative to the paragraph's own left edge, but the
tab logic compared them against the column-absolute `cursor_x`. In a
second column `cursor_x` already carries the column offset, so no stop
ever matched and the field shot off to the default 4-em grid, way past
the page. Measure against the column's `box_left` instead.
- A field after a center/right/decimal tab aligns on the stop, reaching
only part of its width to the right, but word-wrap flushed it left and
wrapped it off the line by its full width — dropping a wide decimal
number onto a fresh, un-tabbed line. Don't wrap the field that follows
an aligning tab.
Picks up acadrust b16137f (decimal stop position parse).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A `\t` reaching a center, right, or decimal (`D`) tab stop now aligns the
field that follows it — the run of words up to the next space/tab — instead
of just advancing the pen:
- decimal: the field's first `.` lands on the stop (a column of numbers
aligns on the decimal point; a field with no `.` aligns its end),
- right: the field's end lands on the stop,
- center: its middle.
The stop wins even when the field is wider than the room before it, so the
dot/edge stays put and the field overruns to the left, keeping the column
aligned. Left stops and the default 4-em grid are unchanged. Picks up
acadrust 162f194, which carries the tab-stop kinds.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`qj`/`qd` were stubbed to left. Now the layout fills the line to the
content width:
- Distribute (`qd`, and headings that reset with `q*` → justify) shares
the slack across every glyph gap — word gaps widen and letters spread
via tracking (LFF stroke fonts; TTF with zero letter-spacing spread
their word gaps only). Every line, including the paragraph's last.
- Justify spreads only the word gaps and leaves the last line ragged.
Bumps acadrust to 504281a, which parses `\pqd` as Distributed and
`\pq*` as Justified so these alignments reach the layout at all.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Several MTEXT layout fixes, all in the run-aware layout engine:
- Honour paragraph spacing (`\p…b#/a#`) and per-paragraph line spacing
(`\psm#/pse#`), which the parser already produced but layout ignored.
They are sticky per-field, so a `\pxb0.25;` set once applies to every
later paragraph — stacked paragraphs now read apart instead of running
together.
- Underline / overline / strike now continue across the blanks between
words: a decorated Space atom emits its own rule, matching the exact
advance lff gives the space so it meets its neighbours' rules.
- Anchor the first line by its own cap height rather than the entity
height, so a small heading (`\H0.2x;First column`) no longer opens a
full blank line above it; the column's first paragraph still opens its
own `\p…b#;` space-before as the top margin, so the heading sits where
the entity data places it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A gradient hatch was drawn linear from the entity colour to its second
stop, so a red->yellow spherical fill came out as a pale linear wash. Two
fixes:
- Colour: start from the gradient's first stop, not the entity colour.
hatch_model_from_dxf already baked stop-0 in; stop synced_hatch_models
from clobbering it with the entity's resolved colour for gradient fills.
- Shape: spherical / hemispherical / curved gradient names now render as a
radial fill (new shader mode 3, centred on the boundary with radius to the
farthest vertex) instead of linear. Native and web shaders both updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extends the proxy-graphics decoder past the door/wall's absolute polylines
so an ACAD_TABLE preview renders in full:
- type 29 transform: a 4x4 local->world matrix now applies to every
following primitive (identity for the door/wall, which are absolute).
- type 9 shell: vertices + a face list -> each face as a closed boundary
(the table's cell rectangles).
- type 32: a normal-tagged polyline (grid lines).
- type 38 text: position + direction (its length is the glyph height) +
UTF-16 content and font, drawn as glyph strokes (simplex.shx and kin are
single-stroke fonts, so the outline is the character).
True-colour (22) and lineweight (23) traits now reach real geometry too.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The preview stream sets a current colour (record type 14, an ACI) before
its geometry; track it and tag each decoded primitive. The Unknown-entity
path groups primitives by colour and draws one wire per colour, resolving a
real index (1..=255) to its RGB and letting ByLayer/ByBlock inherit the
entity colour. So a custom entity's preview now renders in its own colours
(e.g. an ACA door's blue swing) instead of a single flat colour.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A custom entity without its object enabler (an AutoCAD Architecture door,
wall, ...) arrives as Unknown but ships a cached proxy-graphics preview —
the vector fallback AutoCAD itself draws. The old decoder read only a lone
type-6 polyline; generalise it to the real record stream
([total_size, record_count, {record_size, record_type, data}...]) and decode
the geometry records: polyline (6), polygon (7, closed) and circular arc
(4/5, flattened to a strip). Trait records (colour/lineweight/...) are
skipped for now, so the preview draws in the entity's own colour.
The Unknown-entity path now draws every decoded primitive in one wire, so
these objects appear (and become selectable) instead of vanishing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A raster image reference now resolves through one memoised path shared by
the loader and the unresolved-reference placeholder:
- http/https URLs are fetched natively (rustls, 8s timeout, 32MB cap); a
local file is read as before. The web build never fetches. The cache is
cleared when a new document opens.
- Online, the fetched image displays; offline or on a broken/missing
reference, the frame + saved path text still shows. One resolver keeps the
two in agreement without fetching twice.
The raster is also clipped to its boundary: the image draws as the
triangulated clip polygon (ear-clipped) with per-vertex UVs instead of a
full quad, so pixels appear only inside the clip. Clip-boundary Y is raster
space (top-down), so it is flipped to the image's Y-up space in both the
outline and the clip triangulation so they align.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
An IMAGE whose file can't be opened (a URL, or a missing/renamed file) now
draws its clip/frame outline plus the saved path as centred text in the
image colour — AutoCAD's broken-reference placeholder — instead of an empty
frame. OCS never fetches remote paths. Bumps acadrust so the entity's path
is populated from its ImageDefinition.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Picks up the ATTDEF flags/geometry fix so a CONSTANT block attribute
renders instead of being skipped as a template.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A CONSTANT ATTDEF has no per-insert ATTRIB — its value belongs to the block
itself — so block expansion must draw it instead of skipping every ATTDEF.
Non-constant templates are still skipped (the insert supplies their ATTRIB).
Also bumps acadrust to decode the embedded MTEXT of R2018 multiline
attributes, so a value like "A\PB" renders both lines.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Picks up the ATTRIB flags/generation/field-length/lock fix so an
attribute tagged invisible is no longer drawn (ATTMODE 1).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The mode-4 tick was drawn as a full vertical line centred on the point;
AutoCAD draws it rising from the point upward by PDSIZE/2. The '+' and
'×' arms were half size — AutoCAD extends them to the full PDSIZE so the
cross pokes out past an enclosing circle/square (which sit at PDSIZE/2).
Ground-truthed every PDMODE combination against ezdxf's point renderer.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pulls the acadrust fix that decides the R2013 has_ds bit for MULTILEADER by
peeking the class_version, so a MULTILEADER whose writer emits the bit
(budweiser handle 9821) decodes its leader roots / arrowhead / content instead
of an empty context. The leader now renders.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MTEXT background fill flag 0x02 means "use the drawing-window colour" — a mask
that erases geometry behind the text — while 0x01 means "paint background_color".
The fill-colour pick tested 0x01 first, so text flagged 0x03 (both bits, e.g. a
dimension whose DIMTFILL is the drawing background) painted the stored
background_color (a stray grey box) instead of masking. AutoCAD shows no colour
there.
Test 0x02 first: when set, fill with the drawing-window colour (invisible on a
matching canvas) regardless of 0x01; a plain 0x01 fill still paints its colour.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>