'>' button after the Command: label turns the leading-'>' literal-space
behavior into a mode: while on, Space stays in the line instead of
submitting, until toggled off. State persists in settings.json; a
hand-typed leading '>' still works for a single line.
Co-Authored-By: Claude Fable 5 <[email protected]>
Paper layouts gather glyphs for the sheet and each content viewport in
one frame; the single-slot last_sdf_text handed the first source's
glyphs to the rest, hiding every model-space text entity inside
viewports (regression in aea1fd2). Key the reuse by render source, and
invalidate on Tolerance/ATTDEF edits and complex-linetype hosts too.
Co-Authored-By: Claude Fable 5 <[email protected]>
Tint (Chrome/Edge WebGPU) rejects derivative builtins reached after the
family loop's non-uniform early return, invalidating hatch_web/wipeout
shaders on Windows. Take dpdx/dpdy(xz) once at fs_main top and rotate
into the family frame in check_family — same math, uniform flow.
Co-Authored-By: Claude Fable 5 <[email protected]>
Follow-up to the LINE/POINT WCS fix - the same bug class elsewhere:
- 2D SOLID rendered its OCS corners raw in both the wire path and the
shared fill builder (solid_hatch_model); SHAPE placed its marker at
the raw OCS insertion. Both now map through the arbitrary axis.
- Grips: central wrap in view/dispatch.rs shows planar entities' grips
at their world position (OCS→WCS) and converts drags back (WCS→OCS
via the new wcs_point_to_ocs) - per-entity grip code stays in raw
field space, INSERT keeps handling its own OCS.
- Pulls acadrust 81ebfee: OCS-aware bounding boxes, translate,
transform and mirror for the ten planar entity types, so MOVE /
ROTATE / MIRROR and culling work on mirrored entities too.
Co-Authored-By: Claude Fable 5 <[email protected]>
DXF/DWG store LINE endpoints and POINT locations in world coordinates -
the extrusion normal only orients the thickness sweep / glyph. Running
them through the arbitrary-axis OCS mirrored every line and point
carried over from a MIRROR (normal 0,0,-1) onto the wrong side of the
drawing: bolt shafts of a mirrored detail block landed in the adjacent
panel while its nut arcs (correctly OCS-mapped) stayed put.
Co-Authored-By: Claude Fable 5 <[email protected]>
wgpu/naga report pipeline and shader failures through the log facade,
then leave the canvas empty - with no logger installed on wasm the
message was lost, so a broken GPU path looked like "draws nothing" with
a clean console. Install a web logger that mirrors Error-level records
to the browser console and into a fixed DOM overlay with selectable
text, a Copy button and repeat-collapsing; panics land in the same
banner via a chained hook. Windows Chrome/Edge reporters can now paste
the exact ANGLE/D3D11 error instead of a screenshot of a blank canvas.
Co-Authored-By: Claude Fable 5 <[email protected]>
Bumps acadrust to 1b0a362: block headers no longer list handles of
deleted entities, so DWG saves from a session that removed entities
open with full content in TrueView/AutoCAD instead of empty.
Co-Authored-By: Claude Fable 5 <[email protected]>
XATTACH / XRELOAD merge the xref's layer and linetype tables into the
document, but the Layers panel cache only rebuilds via
sync_with_viewports - which nothing triggered until the file was saved
and reopened. Refresh the layer panel (and ribbon mirrors) right after
the merge in both paths.
Co-Authored-By: Claude Fable 5 <[email protected]>
Xref merge now inserts entities in the source block's chain order and
carries SortEntitiesTable objects across (remapped to merged handles).
draw_depth_map values become [depth, half] so exploded block/xref fills
and wide-polyline band wires both compose child depth as
insert_depth + in-block rank x half, recursing into nested inserts with
the same divisor - bands and fills from one block interleave by the
block's internal order instead of stacking at the insert's single depth.
Band batches split per rank via WireModel.depth_override; thin wires
stay merged. The wire arena falls back to a full rebuild when a slab
carries mixed depths so refreshes don't flatten band overrides.
Co-Authored-By: Claude Fable 5 <[email protected]>
A curve-/spline-fit heavy polyline stores both the fitted curve points
(vertex flag 8 / 1) and the spline-frame control points (flag 16),
which only SPLFRAME is meant to show. Chaining every stored vertex
drew spokes from the curve to the control frame — stray points on the
curve and, on wide polylines, a blobbed band (#408). drawn_vertices2d
now drops the control points whenever fit data is present (falling
back to the stored vertices when it is not) and feeds the wire, the
wide-band fills and the tapered band alike, so snapping no longer
lands on frame points either.
Co-Authored-By: Claude Fable 5 <[email protected]>
EXTEND's pick dispatcher covered lines, ellipses, polylines and
splines but never arcs, though TRIM already handled them (#409).
extend_arc gathers every boundary crossing around the arc's full
circle (reusing arc_seg_ts against all boundary kinds) and walks the
clicked end to the nearest one — CCW for the end angle, CW for the
start. The hover preview draws the extended result like the other
types, and the replace-snapshot patches the new Arc's handle.
Co-Authored-By: Claude Fable 5 <[email protected]>
Ribbon tooltips died the day the panels moved into CollapsePanels
(v0.7.6): its overlay() started with `self.open?`, so with no flyout
open — the normal state — it returned None and swallowed every child
overlay, and iced tooltips ARE overlays (#411). Buttons still hovered
and clicked, which is why only the tips were missing. With no flyout
the widget now collects the shown variants' overlays into a Group,
borrows split per panel slot.
Co-Authored-By: Claude Fable 5 <[email protected]>
The web wire pipeline (fat-instance wire.wgsl) had grown to 17 vertex
attributes as the width-band and taper fields were added; WebGL2 and
WebGPU cap vertex attributes at 16, so pipeline creation failed and
the web viewport drew no lines at all — UI and SDF text still worked,
which is why it looked like "the app stopped working" rather than a
crash (#414). Scalars now ride in packed vec4/vec2 attributes (10
locations total); the instance struct's memory layout is unchanged.
Co-Authored-By: Claude Fable 5 <[email protected]>
GRADIENT silently degraded to a plain hatch: add_hatch rebuilt the
render model from the DXF entity but never encoded the gradient on it,
so the committed fill lost its gradient entirely. The entity now
carries the full gradient record (name, angle, colour stops,
solid-fill flag), and the standard shape set became selectable:
Linear / Cylinder / Spherical / Hemispherical / Curved plus an Invert
toggle, offered as prompt buttons on the GRADIENT command and as a
Gradient type dropdown + Invert box in the properties panel. All nine
standard DXF names round-trip (inverted linear persists by swapping
the colour stops), and the shaders shape each kind distinctly —
mirrored cylinder, eased curve, dome-profiled hemisphere — on both the
native and web pipelines without changing the GPU struct sizes.
Co-Authored-By: Claude Fable 5 <[email protected]>
Property edits on a hatch were invisible: fills render from the
prebuilt model cache, which move/copy refreshed but the properties
path never did — so a pattern-scale change (which must also rescale
the stored final pattern lines by the ratio) changed nothing on
screen (#415). invalidate_property_targets now rebuilds the edited
fill's cached model via Scene::refresh_fill_model.
The background row gains an on/off box: off removes the
HATCHBACKGROUNDCOLOR record entirely (the colour could never be
reverted before), on seeds ByLayer. The codec covers the full
colour-method range (ByLayer / ByBlock / RGB / ACI) and the backdrop
resolves ByLayer / ByBlock through the style chain. "More Colors…" on
the background picker now opens the palette window targeting the
background colour — it used to just close the picker.
Co-Authored-By: Claude Fable 5 <[email protected]>
The dynamic-input angle field stripped the typed sign and re-signed
the value from the cursor's side of the X axis, so a negative angle
could not be entered at all — 330 had to stand in for -30 (#417). An
explicit -/+ prefix is now taken literally; unsigned entries keep the
cursor-side behaviour.
Co-Authored-By: Claude Fable 5 <[email protected]>
OFFSET now opens with "Specify offset distance or [Through] <last>":
a typed value locks the distance, bare Enter / Space accepts the last
one (also offered as a prompt button), and T switches to through mode
(#418). After each offset the command loops back to the object pick at
the same distance until Enter / Esc finishes — Space acts as Enter
throughout, where the old flow cancelled. The pick-first path (#422)
keeps working: pre-selected objects jump from the distance step
straight to the side click.
Co-Authored-By: Claude Fable 5 <[email protected]>
Object-snap selection ran a strict priority ladder, so a circle's
Center (rank 4) beat its Quadrants (rank 6) anywhere in the aperture —
and the rim-hover Center candidate measured its distance to the CURVE
(≈ 0 all along the rim), masking quadrants at every rim position
(#420). Discrete point snaps now share one tier decided by cursor
distance, with the classic order kept as the tie-break for coincident
candidates (an Endpoint still beats an Intersection on the same
point), and the rim-hover Center drops to a tier below every discrete
snap but above the continuous ones. The #118 guarantee is unchanged:
continuous snaps (Nearest, Perpendicular, …) still never suppress a
discrete snap inside the aperture.
Co-Authored-By: Claude Fable 5 <[email protected]>
A PLINE point landing back on the first vertex (endpoint snap) now
closes the polyline instead of stacking a duplicate vertex there —
including an arc closing segment, whose bulge is already computed for
the last-to-first span (#421). Line mode requires two real segments
first so a doubled-back line isn't treated as closed. Setting
Closed: Yes in the properties panel on a polyline whose last vertex
already sits on the first drops the duplicate control point for the
same reason.
Co-Authored-By: Claude Fable 5 <[email protected]>
OFFSET now follows the pick-first flow of MOVE / COPY (#422): with
offsettable objects already selected it skips the pick step and goes
straight to the distance / side step, offsetting every selected object
toward the clicked side — each through the cursor point in through
mode, all by the typed magnitude once one is locked. Preview covers
every target and a multi-object result commits as one undo step; with
nothing selected the command gathers a pick exactly as before.
Co-Authored-By: Claude Fable 5 <[email protected]>
REFCLOSE only wrote the temp copies made at REFEDIT entry back into
the block, so anything drawn (or pasted / offset) during the session
stayed behind in model space (#423). The session now captures a
handle watermark at entry; on close, every current-space entity
allocated at or above it joins the working set — Save folds it into
the block through the same inverse transform, Discard removes it.
Covers every creation path with no per-command hooks.
Co-Authored-By: Claude Fable 5 <[email protected]>
Entering the block editor reframes the camera on the block's own
geometry, but nothing remembered where the model view was — Save /
Discard returned to the layout with the camera still parked at the
editor's framing (#425). The session now captures the camera on entry
and both exits restore it exactly.
Co-Authored-By: Claude Fable 5 <[email protected]>
Standard selection keywords, available typed or as prompt buttons in
every gathering command (#426): P / PREVIOUS re-selects the set the
last finishing command worked on (now captured before the working set
is dropped), and L / LAST selects the most recently created object of
the current space (highest handle among its selectable wires). Handled
centrally in the step router, so the command line, the option buttons
and the automation feeder share one implementation; deleted objects
and locked layers are filtered out.
Co-Authored-By: Claude Fable 5 <[email protected]>
The right-click menu was pinned to the screen's bottom-left corner.
Each layout tab now records its screen bounds through a PosReport
wrapper (which learned runtime-built ids for the dynamic tab names),
and the menu anchors next to the clicked tab via the shared
status-bar popup positioning, falling back to the left edge only when
no bounds were recorded (#428).
Co-Authored-By: Claude Fable 5 <[email protected]>
A selected INSERT's Geometry section gains a "Uniform scale" box
(#427): while the three factors are equal it is checked and the panel
shows a single Scale row that writes all three axes; unchecking
expands the familiar Scale X/Y/Z rows without touching the values
(remembered per entity), and re-checking collapses Y/Z onto X as an
undoable CHPROP step.
Co-Authored-By: Claude Fable 5 <[email protected]>
Source half of b672023 (which captured only the removed bundled
thumbnails — the add aborted on the already-staged deletions): the
videos module (playlist scan, oEmbed titles, config-dir thumbnail
cache), the boot fetch on its own thread with the loading state, the
overlay rail + Videos tab on the Start page, and the earlier tabbed-
layout handover.
Co-Authored-By: Claude Fable 5 <[email protected]>
load_config synchronously parsed every recent DWG's embedded preview
on the main thread before the first frame, so the Start page appeared
seconds late on DWG-heavy recents. refresh_recent_thumbs now decodes
the missing previews on a background thread and delivers them via
RecentThumbsLoaded; the page opens immediately and thumbnails pop in
as they resolve. The web build (no threads, no filesystem previews)
skips the fetch.
Co-Authored-By: Claude Fable 5 <[email protected]>
Replace the two baked-in intro video cards (and their bundled JPEG
thumbnails) with a Tutorials rail listing the official playlist. The
list is fetched at boot on a dedicated thread — ordered video ids from
the playlist page, titles via the key-less oEmbed endpoint — and each
thumbnail is downloaded once and cached under the config directory, so
nothing ships in the binary and later launches show the list instantly
(offline included). The rail overlays the welcome column's empty left
side so the centre action buttons keep their position, shows
"Loading videos…" until the first fetch lands, and the side-by-side
layout hands over to the tabbed one (with a Videos tab) before the
centred content could slide under the rail.
Co-Authored-By: Claude Fable 5 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>