hatch_model_from_dxf(), solid_hatch_model(), and wipeout_boundary_2d()
were converting DXF world-space coordinates directly to f32 without
subtracting world_offset. After the world_offset change moved all wire
geometry to local space, hatches/solids/wipeouts rendered at the wrong
position (displaced by the centroid offset, up to ~4M m for Turkish UTM).
All three functions now accept world_offset and apply it to every
boundary vertex before the f64→f32 cast.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Three root causes caused all geometry and UCS arrows to disappear:
1. TruckObject::Lines and TruckObject::Text variants returned world-space
f32 coordinates without applying world_offset, so fit_all() computed
bounds spanning from local space to UTM world space (~4 M m), placing
the camera impossibly far from all visible geometry.
2. Ray/XLine entities with unnormalized direction vectors (common in some
DXF exporters) produced astronomically large far-point coordinates
(up to 1e28) that overwhelmed the camera fit even after the offset fix.
Fixed by normalizing the direction vector before multiplying by
DISPLAY_EXTENT.
3. Origin-stuck corrupted entities (coordinates ≈ 0 in world space become
≈ -world_offset in local space) pulled the fit_all() bounds far from
the actual drawing. Fixed by computing local_extent_max from EXTMIN/EXTMAX
(10× safety margin) and skipping points outside that range in fit_all().
Additional hardening: wire_gpu.rs now skips ±inf segments (not just NaN),
and snap_pts/key_vertices in all TruckObject variants are offset-corrected.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Subtracts the DXF header extents centroid (world_offset) from all coordinates
in f64 BEFORE casting to f32. This eliminates the ~0.5 m ULP error that occurs
at 4,000,000 m UTM coordinates and caused geometry to disappear on zoom-out.
- Scene: adds world_offset field + compute_and_set_world_offset() method
- tessellate.rs: threads world_offset through all f64→f32 conversions
(legacy_geometry, dimension_geometry, solid_wire_fallback, multileader)
- truck_tess.rs: to_local() helper already in place from prior session
- mod.rs: tessellate_entity() and entity_aabb() apply world_offset
- update.rs: compute_and_set_world_offset() called after doc load
- cmd_result.rs / solid3d_cmds.rs: pass correct offset to truck tessellators
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Changed selection overlay pipeline from depth_compare=Greater to
depth_compare=Always so selected wires are redrawn at full brightness
after all other passes, making them visible regardless of what is in
front of them.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Selected wires are now visible through occluding objects. A second
wire_xray pipeline (depth_compare=Greater, depth_write_enabled=false)
renders 25%-alpha ghost copies of selected wires only where they are
behind the depth buffer, leaving fully-visible portions at full glow
brightness unchanged.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Complex linetypes now render embedded text elements (e.g. GAS_LINE's
"GAS" labels): the LtSegment::Text variant is parsed from LIN files,
scaled along with shape/dash segments, and tessellated via
cxf::tessellate_text_ex aligned to the linetype direction.
Full ellipses gain four Quadrant snap hints at ±major/±minor axis
endpoints in addition to the existing Center snap, matching AutoCAD
snap behaviour.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Extension lines for linear and aligned dimensions now start at
DIMEXO × DIMSCALE distance from the definition point (instead of at the
point itself) and extend DIMEXE × DIMSCALE units past the dimension
line, matching AutoCAD rendering. Both values fall back to 0 when the
dimstyle is not found.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
When path_type == Spline, the leader line is now tessellated as a
Catmull-Rom spline through the bend points (8 segments per span)
instead of connecting them with straight segments. Straight/Invisible
path types are unchanged. Key vertices and tangent geoms still use the
raw control points for snap and tangent snapping.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Arrow size for all dimension types (linear, aligned, radius, diameter,
angular) is now read from the associated DimStyle's dimasz field scaled
by dimscale, instead of being hardcoded at 0.12. Falls back to 0.12
when the style isn't found in the document's dim_styles table.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Spline: if flags.closed/periodic and B-spline endpoints don't meet,
add an explicit closing Wire segment to eliminate the visual gap.
- Hatch BoundaryEdge::Spline: evaluate the B-spline at 16 uniform
parameter samples (de Boor via truck BSplineCurve) instead of
plotting raw control points as a polyline; falls back to control-
point polyline for degenerate knot/degree inputs.
- Ellipse arc key_vertices and LWPolyline midpoint grips committed
separately; this commit wraps the spline-related fixes.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Partial ellipse arcs now populate key_vertices with both endpoints so
Endpoint snap works correctly. LWPolyline arc segments (bulge ≠ 0) get
a diamond grip at the arc midpoint; dragging it recomputes the segment
bulge via circumcircle of start/mid/end, giving interactive arc editing.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Polyline3D tessellation now separates spline-fit curve vertices (flag 8)
from frame control points (flag 16): the curve vertices form the rendered
wire while control points are exposed as key_vertices for snap. Polylines
without spline flags are unchanged.
DXF post-load fixup now also converts AttributeEntity and AttributeDefinition
rotation from degrees to radians (group code 50 bug), matching the existing
LinearDimension fix. Attribute text rendered from DXF files now has correct
orientation.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Spline entities now expose fit_points (or control_points when no fit
points are defined) as key_vertices, enabling Endpoint and Midpoint
snap to reach spline construction points.
TextStyle is_backward flag is applied by negating the width_factor,
producing horizontally mirrored text. is_upside_down rotates the text
180°. Both tessellate_text_ex and text_local_bounds now handle negative
width_factor without clamping it to zero.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Block sub-entities with ByBlock color now inherit their INSERT's resolved
color instead of rendering as the default white. ByBlock linetype inherits
the INSERT's linetype pattern, and ByBlock lineweight inherits the INSERT's
effective line weight.
Implemented via render_style_for_block_sub(), called in tessellate_entity()
when expanding INSERT exploded entities.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
INSERT entities now expose their insertion point as a SnapHint::Insertion
pre-baked snap candidate, enabling OSNAP Insertion to locate block
reference origins. The cross marker is also positioned at the actual
insertion point instead of the origin.
Hatch entities now populate key_vertices from Polyline and Line boundary
edges (with correct elevation Z), enabling Endpoint and Midpoint snapping
to hatch boundary geometry.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Comprehensive backlog of acadrust entity fields, behaviors, and subsystems
not yet integrated into H7CAD rendering, snap, grip, and properties systems.
Covers tessellation gaps, OCS→WCS transform, ByBlock style resolution,
snap/grip point gaps, text rendering, and DXF reader unit bugs.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Text/MText values now correctly handle DXF special-character sequences:
- %%d → °, %%p → ±, %%c → ⌀, %%nnn → Unicode scalar (resolved in tessellation)
- %%u / %%o toggle underline and overline strokes for TEXT entities
MText decoration codes (\L/\l, \O/\o, \K/\k) were previously consumed
as argument-bearing codes, silently eating the decorated text. Fixed
strip_mtext_codes to keep them as zero-width markers; tessellate_text_ex
now emits actual line strokes (underline, overline, strikethrough).
measure_mtext_chars and word_wrap skip markers so line widths are correct.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
acadrust's DXF reader does not parse group code 53 (text rotation),
so DimensionBase.text_rotation is always zero for DXF-loaded files.
Fall back to the natural axis-aligned rotation computed from geometry:
Linear dimensions use d.rotation (radians), Aligned dimensions use
atan2 of the second-first vector. Normalize to (-π/2, π/2] so text
never appears upside-down. Explicit non-zero overrides stored by the
DWG reader are preserved.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
append_linear_dimension() applied the same perp offset to both d1 and d2,
but only d2 is guaranteed to be at the definition_point level; d1 must be
projected independently onto the dimension line, otherwise non-collinear
first/second points produce a parallelogram instead of a rectangle.
Fix: compute dim_line_pos = def.dot(perp) and project each endpoint
separately so the dimension line is always parallel to axis.
Additionally, the acadrust DXF reader stores LinearDimension.rotation from
group code 50 (degrees) without converting to radians, while DWG and our
own annotation commands store it in radians. Add a post-load fixup in
load_file() that converts the rotation to radians for DXF files so
tessellation can call cos/sin uniformly.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Standalone hatches and hatches from block inserts were rendered
regardless of entity invisible flag, layer off/frozen state, or
whether the owning INSERT belongs to the current layout block.
- Filter self.hatches by invisible, layer, and belongs_to_visible_block
- Skip INSERT entities that are invisible, on a hidden layer, or outside
the current layout block before exploding their contents
- Skip individual hatches produced by explode that are invisible or on
a hidden layer
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
paper_canvas_wires() now returns Arc<Vec<WireModel>> backed by
paper_canvas_cache (epoch-keyed). Pan/zoom frames hit the cache and
return a pointer copy; only geometry changes rebuild the wire list.
Completes the paper-canvas caching chain (L+M): sheet cache →
projected-per-viewport cache → full canvas Arc cache.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Add two epoch-keyed caches to eliminate per-frame work in paper-space layouts:
paper_sheet_cache — caches wires_for_block(layout_block) as Arc<Vec<WireModel>>.
Previously paper_sheet_wires() re-tessellated all paper-space entities (title
block, annotations, viewport borders) on every mouse-move frame. Now it is an
O(1) Arc clone. entity_wires_arc() reuses the same cache so a geometry-change
frame triggers only one tessellation pass even when both the GPU renderer and
the Iced canvas are active.
paper_projected_cache — caches projected+clipped wires per viewport handle.
viewport_content_wires() now uses model_wires_for_viewport_arc() for the
tessellation step (already O(1) via viewport_wire_cache) and stores the
projection+clip result keyed by (vp_handle, epoch). Navigation frames
(pan/zoom on the paper sheet) are O(N_viewports) Arc/slice copies instead of
O(N_entities × N_points × N_viewports) tessellate+project work.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Add `aabb: [f32; 4]` field to WireModel (world-space 2-D bounding box).
In tessellate_entity() set each wire's AABB from e.as_entity().bounding_box()
via new entity_aabb() helper; Insert sub-entities each get their own AABB.
Preview/interim wires and any entity returning a zero-extent default box get
UNBOUNDED_AABB so they are never pre-rejected.
Replace the chord-sphere heuristic in Snapper::wire_in_range() with a proper
AABB vs snap-circle overlap test. The new check is four scalar comparisons and
correctly handles closed curves (circles, arcs) which the chord-sphere approach
had to special-case.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Option I: add per-viewport Arc wire cache so paper-space viewport rendering
no longer re-tessellates model content every frame.
Option J: hit_test_wires() returns Arc<Vec<WireModel>> — O(1) pointer copy
in model space instead of a full Vec clone on every mouse move during commands.
paper_canvas_hatches/wipeouts() now route through the epoch caches added in F.
Option K: add world_snap_r + wire_in_range() pre-check to Snapper::snap().
All Endpoint/Midpoint/Nearest/Perpendicular/Intersection loops skip wires
whose chord sphere is outside the snap circle using O(1) scalar comparisons,
eliminating O(entities × vertices) matrix multiply cost when zoomed in.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Add Arc<Vec<...>>-backed epoch caches for hatches, wipeouts, images, and
meshes — the same pattern used for wires. build_primitive() now returns
an O(1) Arc refcount bump on navigation frames instead of rebuilding
every collection from scratch.
Also wrap ImageModel.pixels in Arc<Vec<u8>> so cloning an ImageModel is
O(1) regardless of image resolution.
Navigation frames no longer copy pixel data, tessellated hatch boundaries,
or mesh geometry — all per-frame CPU work on unchanged geometry is now
eliminated.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Extract tessellate_one body into a Send-compatible free function
tessellate_entity, then drive it with rayon::into_par_iter() in
wires_for_block(). Spreads file-open and post-edit tessellation across
all CPU cores. Navigation frames are unaffected (Options A/B cache
eliminates per-frame tessellation entirely).
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Replace the O(objects) linear scan inside wires_for_block() with a
sort_cache keyed by geometry_epoch. On a cache miss the index is built
once by scanning all document objects; subsequent calls within the same
epoch do an O(1) HashMap lookup (block_handle → entity→sort map).
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Add two complementary render caches that make mouse navigation essentially
free regardless of file size:
Option A – Wire tessellation cache: entity_wires_arc() caches the
tessellated Vec<WireModel> keyed by geometry_epoch and returns an Arc,
so navigation frames (no preview wires) incur zero Vec clones and zero
tessellation work. Primitive::wires changed to Arc<Vec<WireModel>>.
Option B – GPU buffer cache: Pipeline stores cached_epoch; prepare()
skips all upload_wires/hatches/images/meshes calls when the epoch is
unchanged, writing only the 192-byte camera uniform instead.
geometry_epoch is a process-wide AtomicU64 incremented by every Scene
mutation (add/erase entity, selection, layer visibility, layout switch,
populate_*, grip, transform, copy, clear, preview wires). Using a global
counter rather than a per-scene counter prevents two tabs that happen to
have the same local bump count from sharing a cached_epoch value, which
was causing the first file's geometry to remain on screen after opening a
second file.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Empty LwPolyline and single-control-point Spline fallbacks were calling
builder::line(&v, &v) with the same Rc vertex, triggering the
truck-topology panic "Two same vertices cannot construct an edge."
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
White paper fill was calculated from entity bounding boxes (paper_entity_extents),
so adding a viewport caused the paper to visually shrink to the viewport's bounds.
Now paper_limits() (layout min/max limits) is used directly, and the unused
paper_entity_extents() function is removed.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Make scene::properties::entity_type_name the single source of truth.
entity_type_label in helpers.rs now delegates to it instead of
maintaining a separate match list.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
The Properties panel header was still showing "Entity" for unrecognized
types because helpers.rs had its own incomplete match. Both functions now
enumerate every acadrust EntityType variant explicitly.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Expanded entity_type_name() to cover all EntityType variants from acadrust
instead of falling back to the generic "Entity" label.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
NAVVCUBE: propagate show_viewcube flag through ViewportPane and the wgpu
Primitive so the ViewCube pipeline skips upload/render when hidden.
UCSICON: bare command (no subcommand) now toggles current state instead
of printing usage info.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Integrate NAVVCUBE, NAVBAR, PROPERTIES, FILETAB, LAYOUTTAB, TOOLPALETTES
and SHEETSET commands to toggle UI widgets at runtime. Add 15 new SVG icons
for view module tools previously using glyph fallbacks. Restyle all SVG
icons to use the established two-color scheme: #e0e0e0 for passive geometry,
#4cc9f0 for active/accent elements.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Replace the flat fixed-length axis arrows with a proper 3D tripod:
- Axis lengths are proportional (longest fills UCS_ICON_LEN, shorter
axes stay foreshortened) giving real depth perception as the camera
rotates
- Draw order computed from NDC Z so back axes render behind front ones
- X / Y / Z labels rendered beyond each arrowhead tip
- All axes drawn at full opacity with solid filled arrowheads
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
prepare() was passing viewport.logical_size() (full window) to
viewcube.upload(), while hit_test/hover_id used the shader widget's
bounds. With the properties panel present the widget is narrower than
the full window, so the visual cube was scaled smaller but the click
area stayed full-size — causing the mismatch that grew toward the
bottom-left corner.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Hide OS crosshair and drawn crosshair when cursor is over ViewCube area
- Enhanced hover glow in WGSL shader (blue tint + brightness boost)
- Fixed region_centroids() Y-axis sign for all FRONT/BACK-related regions
- Swapped corner geometry assignments (TPF↔TBK, BTF↔BBK) to match
intended FRONT/BACK semantics (sy=-1 = FRONT, sy=+1 = BACK)
- Corrected all snap_angles: yaw_pitch_to_quat(0,0) places eye at -Y
(FRONT view), so FRONT=0 and BACK=PI — cascaded through edges/corners
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
acadrust initialises every Layout with 12×9 imperial defaults; override
all non-Model layouts to 297×210mm in new_drawing() so the first paper
tab opens at A4 size, consistent with layouts added later.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- paper_entity_extents() computes the bounding box of actual DXF entities
so the white fill always matches the drawn title-block / frame borders,
regardless of whether Layout min/max_limits match entity positions
- Falls back to paper_limits() (→ A4 297×210) when layout has no entities
- Removed paper_boundary_wire: the white fill now acts as the visual paper
edge so the near-white wire (invisible on white) was redundant
- LayoutCreate sets limits to A4 landscape immediately after add_layout()
to override acadrust's imperial default (12×9)
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- paper_limits() falls back to A4 landscape (297×210mm) even when no
Layout object exists in the document
- paper_bg_color default changed to white [1,1,1,1] (paper surface color)
- PaperCanvas::draw() fills canvas with dark desk color first, then draws
the paper area rectangle in paper_bg_color so the sheet is clearly
visible against the desk background
- view.rs container background uses the fixed desk color instead of
paper_bg_color so it no longer conflicts with the paper-surface color
- BACKGROUND RESET for paper space now resets to white instead of dark gray
- Removed unused PaperSheet variant, paper_sheet() constructor,
build_paper_sheet_primitive() and associated match arms (dead code
after 2D canvas replacement)
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>