Reorder the model-tile control chip to grid, grid snap, render mode,
vertical split, horizontal split. Add a close button (shown only with
more than one model tile) that merges the active pane into its
longest-contact neighbour.
Divider drags now clamp each side to the minimum tile size, so dragging
a boundary to the screen edge no longer collapses a pane — closing is
done with the button instead.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Remove the unused `crate::par::prelude` glob import in wire_gpu.rs and
the never-called `viewport_at_paper_point` method; fold its doc into the
screen-space twin that is actually used so no intra-doc link dangles.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The model-space wire geometry is camera-independent, yet every pan/zoom
re-tessellated the whole model and re-uploaded it to the GPU. On a
block/text-heavy drawing that is millions of segments, costing a ~100ms
re-tessellation plus a ~150ms GPU re-upload per camera change (~258ms
freeze) while the GPU itself draws the full set comfortably in real time.
Tessellate the full, un-culled model once per geometry epoch, hold it
resident, and return it for every camera and tile. A pan/zoom now only
changes the view uniform; no frustum cull, no zoom LOD, no re-tess or
re-upload on camera moves. Pan/zoom frame time drops to ~7ms (~140fps)
with no stutter.
Removes the now-dead per-tile cull cache, pan-invariant hash, AABB
containment helper, and the reuse-during-motion / settle-regen machinery
that an earlier iteration introduced. Also exempts Face3D from the
sub-pixel stub LOD: a flat face is trivially cheap to tessellate, so it
is drawn full at any zoom and no longer pops a cube stub at the threshold.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
`wipeout_boundary_2d` built the fill boundary from the wipeout's
insertion point cast straight to f32, stored in a HatchModel with
world_origin = [0, 0]. At UTM-scale coordinates (~1e6) f32 resolves to
~0.06, so the boundary collapsed and the relative-to-eye fill shader
cancelled `boundary - eye` to noise — the mask rendered as squares.
Return the boundary as small f32 offsets relative to a world_origin
(the insertion point, kept in f64) and set HatchModel.world_origin to
it at both the model-space and paper/viewport fill sites. Same fix as
the wide-polyline and on-create hatch fills.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The solid fill band of a wide LwPolyline / Polyline2D was built from
absolute WCS vertex coordinates cast straight to f32, then handed to a
HatchModel with world_origin = [0, 0]. At UTM-scale coordinates (~1e6)
f32 resolves to ~0.06, so the half-width offset collapsed and the
relative-to-eye fill shader cancelled `boundary - eye` to noise — the
band rendered as a string of squares. (Exploding the polyline drops the
width, hence no band and a clean line, which is why that "fixed" it.)
Build the band relative to a world_origin (the first vertex) in f64 and
store the small f32 offsets, mirroring how baked hatch fills keep
precision. Same class of bug as the on-create hatch-fill fix.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Two follow-ups to dropping the hatch boundary outline from the wire set:
- `closed_outlines` copied segment-list wire points (LwPolyline emits
each segment as a NaN-separated pair, so every shared corner repeats)
straight into the hatch boundary, giving two grips at each corner.
Collapse the points back into a clean ring — skip NaN separators and
any vertex coincident with the previous one, and drop a trailing
vertex equal to the first. One grip per corner; point-in-polygon and
the BOUNDARY / GRADIENT commands get the clean ring too.
- Edit previews (move / copy / array / grip-drag) tessellate the edited
entity into preview wires, but a hatch now produces none, so a dragged
hatch didn't update until release. Build a live outline from the
current HatchModel for the preview only (`apply_grip` keeps it in
step), so the shape tracks the cursor again. The normal render still
shows just the fill.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
`add_hatch` let `add_entity` build the render model from the new DXF
entity (via `hatch_model_from_dxf`, AABB-centred `world_origin`) and
insert it — then overwrote it with the command-built model, whose
`world_origin` is [0, 0]. After the world_offset removal the fill is
drawn relative-to-eye from `world_origin`, so a [0, 0] origin with a
full-WCS boundary cancels to garbage in f32 and the fill renders
off-screen — invisible until a later edit rebuilt it from the DXF.
Drop the overwrite so creation keeps the correct seed; it now matches
what an edit or reload produces. Fixes the regression for hatches drawn
away from the origin.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Build the ViewCube out into a full navigation widget:
- Compass ring painted in the cube's ground plane (z = base) so it
reads as a ground compass and foreshortens with the view.
- N/E/S/W cardinals painted flat onto the ring band; clicking one snaps
to that side elevation. Added S/W glyphs to the bitmap atlas.
- Home button (top-down view), roll arrows (90° roll about the view
axis), and four nudge triangles (90° tip/spin) as SVG iced controls
laid out around the cube. New home.svg.
- WCS / named-UCS selector under the cube, switching the active UCS.
The cube hit area now shares one stack with the control buttons, so a
click on a button is caught by the button while a click on the cube
falls through. Face clicks snap to the highlighted region rather than a
cursor position the viewport's move handler can overwrite.
Camera gains home_view / roll_by / nudge_90; floating viewports drive
the same ops through mutate_active_viewport_camera, which re-encodes the
result to the stored view direction + twist.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
A dimension's drawn geometry lives in a baked anonymous *D block. COPY
cloned the Dimension entity (translating its definition points) but left
its block_name pointing at the source block, whose sub-entities stay at
the original location — so the copy rendered on top of the original and
appeared not to copy at all.
Add `clone_transformed_block`: when copying a dimension that has a baked
block, duplicate that block under a fresh *D name with every sub-entity
transformed by the same offset, and repoint the copy at it. The copy now
lands at the drop point with its baked geometry and text preserved (no
synthesis, so diameter/radius values stay correct). Covered by a portable
regression test.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
A new drawing's layouts carry paper limits but no sheet viewport; it is
created lazily on the first switch into the layout. That viewport kept
Viewport::new's default view (target 0,0 / height 210), so camera_from_view
aimed at the paper's bottom-left corner, and with no geometry yet the
fit_all fallback was a no-op — the layout opened off-centre.
Give the freshly created sheet viewport a view that frames the page:
view_target at the paper centre and view_height a little taller than the
sheet. The first switch now opens centred on the page, and the view persists.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Driving the annotation / viewport scale picker purely from the drawing's
ACAD_SCALELIST regressed every drawing that carries no Scale objects
(most fresh drawings and minimal DXF/DWG exports): scale_list() returned
empty, so the status-bar scale popup opened with nothing to pick and the
button looked dead.
Fall back to the standard ratio set (1:500 … 1:1 … 10:1) when the drawing
defines no scales of its own; file-defined scales still take precedence
whenever present. Also re-enables the viewport-scale picker in the
properties panel for those drawings.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The hatch boundary was flat-truncated to 1024 vertices before upload.
The active renderer is the batched path, which stores boundaries in a
GPU storage buffer with no length limit, so the cap only ever served
the per-hatch uniform path (now wipeout-only, and self-clamping).
On a real drawing a 36-loop, depth-3 nested hatch (1482 verts) was cut
mid-boundary: trailing island loops were dropped and the final partial
loop was left open, flipping the even-odd parity so the fill bled across
the rest of the shape and islands rendered incorrectly.
Drop the flat truncate. Keep every loop; only guard pathological vertex
counts (16384), and when trimming, cut at a NaN sentinel (whole-loop
boundary) so no sub-loop is ever left open.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Floating viewports on a survey-coordinate drawing rendered blank: the
model camera was correct, but content viewports whose saved view was
rejected auto-fitted onto empty space.
Two causes, both in model-space extents handling:
- compute_model_space_extents tested vertex finiteness in f64 but stored
f32. A long RAY/XLINE endpoint is a huge-but-finite f64 that overflows
to inf in f32, poisoning the extents to (inf, inf). Also, the wire/mesh
AABB paths checked only x while folding y in, so a vertical ray's
infinite y leaked through. Now every component is validated after the
f32 cast.
- camera_for_viewport framed its overlap test and auto-fit on the raw
extents centre. When a drawing has a second, far cluster (a small-
coordinate legend beside a UTM survey), that centre sits in the empty
gap, so a valid saved view fails the overlap test and the auto-fit aims
at blank space. cluster_extent_from_centers already computed a robust
centre (median of entity centroids) but discarded it; surface it as
Scene.local_center and frame on that instead.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
ViewCube "already there → flip to opposite" tested the main (paper) camera,
which always looks straight down, so inside a floating viewport clicking
"top" landed on "bottom". Route the check through active_gaze_dir() — the
gaze of the camera the cube actually reflects.
Off-screen viewports:
- The active-viewport outline clamped only its origin while keeping full
width/height, shifting the whole border inward. Clip it to the visible
canvas (origin AND size) so it hugs the real viewport edge.
- Double-click activation tested each viewport's full paper rect, so a
click on empty space beside a viewport that runs off-screen matched its
partly-off-canvas rect and switched to it. Activation now keys on the
visible on-screen rect (viewport_at_screen_point), picks the smallest
(topmost) on overlap, and accepts the border-wire pick only when the
click also lands inside that viewport's visible rect — so a far
viewport's edge passing near the cursor can't steal the activation.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Clicking the ViewCube inside a floating viewport set only view_direction,
so the rebuilt camera fell back to WCS-up and the view opened tilted
relative to the UCS-oriented cube the user clicked. Build the full
UCS-aligned orientation (snap_to_direction with the ViewCube UCS) and
decode it to the viewport's stored (view_direction, twist_angle).
twist can't come from -Camera::roll(): camera_from_view rebuilds rotation
with its own yaw convention (atan2(x,-y)) and roll = -twist, which differs
from the camera's internal sync_yaw_pitch. Instead reproduce the decoder's
zero-twist basis and store twist = -signed_angle(up0 -> desired_up).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The hover hit-tests used the bare camera rotation (no UCS) and the main
camera even inside a floating viewport, so the highlighted face diverged
from the cursor whenever a UCS was active or in a paper viewport — while
the snap path was correct. Route hover, click-consume and snap through
active_view_rotation_mat(), and compose the ViewCube UCS in its viewport
branch so all three match ViewportData's drawn cam_rotation exactly.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The function computed a median-centroid offset that is now always
discarded (geometry is absolute + double-single). Rename it to
cluster_extent_from_centers, return only the cluster span used for camera
fit / cull, and drop the dead centroid computation. Rename the companion
WORLD_OFFSET_SANE_EXTENT const to CLUSTER_SANE_EXTENT.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Clean up the remaining `let [ox,oy,oz] = [0,0,0]` bindings and their
zero-valued add/subtract arithmetic across scene/mod.rs (mesh LOD resplit,
model AABB, wide-polyline fills, wipeout boundary, hatch/solid models,
insert/entity AABB), table, multileader and xclip. Coordinates now read as
the plain absolute values they are. No world_offset reference remains.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Now that geometry reaches the GPU/CPU as absolute coordinates via the
double-single relative-to-eye path, world_offset is always [0,0,0]. Strip
the parameter that was threaded through the whole tessellation / block-
expansion / fallback / camera-decode chain and the ExpandCtx field that
carried it — ~340 references across 20 files, all subtracting zero.
- truck_tess: to_local / to_local_low / tessellate_* drop the offset arg;
to_local is now a pure double-single split.
- tessellate: offset_to_ds → points_to_ds (pure DS split); tessellate(),
tessellate_entity(), fallback_geometry(), solid_wire_fallback(),
entity_aabb(), expand_insert(), expand_block_meshes(), the dimension
helpers (vec3_local, dimension_snap_pts, …), text_support, leader,
multileader, image_model, xclip and camera_from_view all lose the param.
- block_cache: ExpandCtx loses its world_offset field.
- Remove the now-dead offset_snap_pts and the unused set_grid_snap.
Behaviour is unchanged (the offset was zero everywhere); the compiler
verifies every call site.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
camera_from_view computed the camera target in f32 (view_target cast to
f32 before folding view_center), quantizing it to ~0.5 m at UTM scale.
Panning/zooming inside a floating viewport nudges view_target by sub-metre
f64 steps, so the camera snapped along the f32 grid and the drawn content
jittered — while the model view (f64 target) stayed smooth.
Compute the target in f64 (axis directions stay f32 — orientation only),
matching the model camera's precision.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Extend UCS support to in-viewport (MSPACE) editing through the existing
single-converter structure: one active_ucs drives every UCS-aware system,
now in the Model tab AND inside a floating viewport.
- Single predicate DocumentTab::editing_model_space() (Model tab or active
viewport) replaces the model-only gate in ucs_wire_affine,
ucs_rotation_angle, ucs_grid_basis, viewcube_ucs_mat, the coordinate
readout and the cursor-plane pick.
- Per-viewport UCS: refresh_active_ucs() sets active_ucs to the pane's UCS
on every transition (the entered viewport's own UCS from its
ucs_origin/x_axis/y_axis/ucs_per_viewport fields, the header model UCS in
the Model tab, none in plain paper); persist_active_ucs() writes changes
back to the viewport entity or the header so they round-trip. Wired into
EnterViewport/ExitViewport, layout switch, load and the UCS command.
- Visuals: grid_views now returns each pane's handle so every content
viewport's grid aligns to its own UCS; the UCS icon draws inside the
active viewport projected through the viewport camera.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Replace the leaky paper-projection approach to MSPACE editing with one
coordinate adapter that routes all interaction through the viewport's own
camera — the exact camera+rect the GPU draws the content with.
- Add Scene::viewport_edit_frame(canvas) -> (camera_for_viewport,
viewport_screen_rect): the unified adapter. Inside a floating viewport
the viewport is the active pane, so snap/pick/grip/preview/DYN run in
model space and land on the pixels the GPU draws. Tracks pan/zoom/twist
exactly (twist handled by camera_from_view); crop is pixel-preserving so
full-rect projection + cursor offset matches.
- Route snap-move, click-pick, grip-drag, grip hit-test, grip render,
hover (HoverDwellTick), selection (single/box/poly/cycle) and command
entity-pick through it via pick_view / cursor_model_point helpers and an
edit_cam-aware "active pane = viewport rect" override, so the existing
tile offset plumbing just works. Fixes wrong-object hover/selection and
stale snap after pan/zoom.
- DYN: project anchors with the viewport camera and shift to canvas space
so the guide/labels sit correctly (also fixes a latent tiled-model bug).
- Separate grid snap from object snap entirely: new Snapper.grid_snap_on
flag, Grid removed from the osnap mode set, evaluated independently of
the osnap master. Object snap never catches grid; grid corners are
picked only when grid snap is on, at lowest priority. Default off.
- grip.rs: project_rte + grips_to_screen_rte / find_hit_grip_rte taking
(view_rot, eye) so grips project through the composed view.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Checkpoint on the way to unified in-viewport editing.
- Drop the _f64 transition suffixes: pick_on_plane / pick_on_target_plane /
paper_to_model are single f64 functions (wrappers removed).
- Paper-space hit-test/snap projects viewport content with points_low, and
projects its aabb / key_vertices / snap_pts into paper coords so click
picking and snap candidates land on the visible sheet geometry.
- camera_for_viewport + paper_to_model + EnterViewport agree on an absolute
drawing-centre view (normalize_active_viewport_view) so UTM viewports
render instead of aiming at the origin.
- model_to_paper inverse; dynamic-input anchors map model→paper before the
paper camera projects them, so the guide no longer tessellates a 512 MB
buffer (clicking the first in-viewport LINE point) — crash fixed.
Known-incomplete (next: unify on the model-space path via one coordinate
adapter): in-viewport snaps stale after pan/zoom (paper_projected_cache not
keyed on the viewport view), grips don't show, command preview misplaced.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Floating viewports rendered blank on UTM drawings. camera_for_viewport
centred both the saved-view overlap test and the auto-fit target on the
ORIGIN — correct only while world_offset re-centred the model there. With
world_offset removed the model sits at absolute (~5.7e6) coordinates, so a
stale (0,0,0) saved view failed the overlap test AND the auto-fit aimed at
empty origin, leaving the viewport empty.
Both now use the absolute drawing centre from model_space_extents, so saved
views that frame the model are kept and the auto-fit falls back onto the
real content.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
entities_centroid summed the f32 points (double-single HIGH half) alone, so
at UTM scale (~5.7e6) it both quantized each term ~0.5 m and lost low bits
across the running total — drifting the paste base and PASTEBLOCK's block
base metres off, which placed the wrapped block at the wrong spot/scale.
It now reconstructs each vertex's absolute f64 from points + points_low and
accumulates in f64. clipboard_centroid is DVec3; the PASTECLIP commit delta
is fully f64; define_block_from_owned_entities takes an f64 base so the
block-local fold (Translate(-base)) stays precise. The interactive ghost
anchors (PasteCommand, InsertBlockCommand) still take the f32 cast — display
only; the committed geometry is f64.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Clipped blocks were broken at UTM and lost their clip when pasted into
another drawing.
- Clip geometry: world_clip_polygon_f64 keeps the boundary in absolute f64;
clip_wires now clips in a frame relative to the boundary's first vertex
(small coords → exact f32 Sutherland–Hodgman) and re-splits the result
into the double-single high/low pair the relative-to-eye renderer needs,
so the clipped block draws correctly and no longer poisons ZOOM Extents.
- ZOOM Extents (fit_all): the outlier reject compared absolute UTM
coordinates against a centre-RELATIVE span (local_extent_max), rejecting
the whole drawing → no-op. Made the reject distance-from-median-centre.
- Cross-drawing paste: clipboard now snapshots each copied entity's whole
xdictionary object graph (ClipboardDeps.ext_objects) and recreates it on
paste with fresh handles + remapped references, re-pointing the pasted
entity's xdictionary at the new root. Wired into PASTECLIP and PASTEORIG.
Fixed handle allocation (allocate_handle, not the non-advancing
next_handle) so the dictionary chain doesn't collapse onto one handle.
- Updated hit_test / plugin_host tests for the eye-relative + DVec3 signatures.
Known gaps: PASTEBLOCK does not yet carry the clip (clip would need to live
inside the new wrapper block definition); hatch-clip path still f32.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Whole command/snap/grip/cursor chain now builds on f64 DVec3 end to end,
so picked points, snaps and grip drags stay precise at UTM-scale
coordinates instead of quantizing to ~0.5 m through f32.
- All ~80 command files migrated: draw (line/circle/arc/ellipse/shapes
in full f64; others via point methods), modify, annotate, insert, view,
inquiry, layers, groups, clipboard, layout.
- Entity apply_grip impls updated for GripApply::{Absolute,Translate}(DVec3).
- Construction sites (EntityTransform/CmdResult/DynAnchor) emit DVec3;
wire-preview boundaries cast to f32 only at the GPU edge.
- Modify/annotate commands keep internal Vec3 geometry where precision is
non-critical, casting at the DVec3 boundary; key draw commands store f64.
Builds clean (no warnings). Visual verification of crosshair/snap/grip at
extreme UTM zoom pending.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Route the remaining absolute-coordinate CPU projection through the
relative-to-eye path so it no longer multiplies by a view_proj matrix
carrying a ~1e7 translation (which cancels catastrophically in f32 after
world_offset removal):
- camera pick_on_plane / pick_on_target_plane delegate to
unproject_on_plane_f64 (rotation-only inverse + f64 eye)
- snapper (world_to_screen), hit_test (click_hit / mesh / hatch), the
scene mesh/solid hit methods, grid + UCS axes, and polar_constrain_near
all take view_rot + eye and project relative-to-eye
- compute_grid_step measures the per-unit scale via the rotation-only
matrix (no eye term needed)
view_proj remains only for conservative CPU frustum-cull / scissor and the
screen-space viewcube / UCS-icon directions. NOTE: SnapResult.world and
the command-point chain are still Vec3 (f32) — a ~0.5 m quantization at
extreme UTM zoom remains; the command layer goes f64 next.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
Delete the world_offset field from Scene and DerivedCaches and the
load-time assignment; every reader now uses a zero origin. Geometry is
absolute and the double-single relative-to-eye pipeline keeps it precise,
so the coarse origin is gone. world_offset_from_centers still derives
local_extent_max (camera/cull span) but returns no offset. The remaining
`world_offset: [f64;3]` function parameters are now always zero — dead
plumbing left for a later mechanical sweep; behaviour is unchanged.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
world_offset_from_centers always returns [0,0,0]: geometry reaches the
GPU as absolute world coordinates and the double-single relative-to-eye
pipeline keeps it precise at UTM scale, so the coarse origin is no longer
needed. Camera target, snap candidates (f64) and the coordinate readout
all operate in absolute coordinates now. The `- world_offset` subtractions
remain as no-ops and are removed in the field-deletion step. local_extent_max
(camera/cull span) is still derived from content.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
WireModel/LocalWire/BatchEntry key_vertices are now Vec<[f64; 3]>;
tessellation keeps them in f64 (no f32 cast at offset subtraction),
producers (dimension, leader, multileader) build them from the acadrust
f64 source, the block-cache transform keeps f64, and the face3d Face3D
quad path splits them into the double-single (high, low) pair. Consumers
(snap endpoint/midpoint, xclip) read via f32 at the leaf. Still
offset-relative (inert); endpoint/midpoint snap is no longer f32-capped.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
WireModel/LocalWire/TruckEntity snap buffers and the producers/consumers
now carry snap candidates as DVec3 (offset_snap_pts, dimension/multileader
producers, block-cache transform, pick/xclip, snap module). Still
offset-relative, so behaviour is unchanged — this just lets the snap
transport survive the upcoming switch to absolute coordinates without an
extra f32 quantization. The snap module still resolves in f32 (as_vec3 at
entry), matching today's precision.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
3D solid / surface meshes were the last single-f32 path. truck's mesh
tessellation now returns a paired verts_low residual (TruckTessResult::Mesh,
polygon_to_result), MeshModel/MeshLodSet carry it, the model-space offset
and block-instance transforms reconstruct the f64 position and re-split it
after subtracting world_offset, MeshVertex gains position_low (location 3),
and mesh.wgsl reconstructs the eye-relative position. Native SAT / ACIS /
OBJ mesh paths default to a zero residual (no regression — they were never
sub-f32 precise).
With this every world-space pipeline (wire, hatch, image, face3d, mesh)
is full double-single relative-to-eye; large-coordinate jitter is gone.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
Table / PolyfaceMesh / greek-text / mleader-background fills go through
the face3d pipeline; their vertices were single f32 and quantized at UTM
scale. WireModel now carries a fill_tris_low residual (filled by the
tessellate Lines path), Face3DVertex gains position_low (location 3), and
face3d.wgsl reconstructs the eye-relative position from the pair. 3DFACE
quads built from key_vertices use a zero residual (no low source).
Co-Authored-By: Claude Opus 4.7 <[email protected]>
Foundation for double-single positions: WireModel now carries an
index-paired f32 residual buffer (`points_low`) next to the existing
`points` high half. Empty means "all-zero residual", so every existing
constructor (interactive draw, previews) is unchanged in behaviour;
tessellation from CAD f64 will fill it in the next steps so vertex
precision survives at UTM-scale coordinates.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
Foundation for relative-to-eye rendering: the pivot the camera orbits
must stay precise at large offset-relative coordinates so the derived eye
(which the RTE path subtracts per vertex) is exact. `Camera::target` is
now DVec3 and `eye_f64()` exposes the full-precision eye; `view_proj`
still casts to f32 at the matrix, so behaviour is unchanged for now.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The ViewCube showed world Front/Top/Right. It now composes the camera
rotation with the active UCS (scene.viewcube_ucs, synced from the tab's
active_ucs), so its faces follow the user's coordinate system in model
space. Render uniform, hover/click hit-test, and the click→snap direction
all go through scene.viewcube_ucs_mat() so they stay in lock-step;
identity outside model space leaves paper-viewport navigation unchanged.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Model-space tiled VPORTs and paper-space floating VIEWPORTs each carried
their own copy of the saved-view→camera math, so a fix to one drifted
from the other (the recurring grid/twist regressions). Route both, plus
the View-table and sheet-viewport paths, through one decoder and fix the
bugs that the duplication hid.
Decoder unification:
- New camera_from_view(direction, target, center, height, twist,
world_offset): the single saved-view→Camera decoder. camera_from_vport
(tiled), camera_for_viewport (floating, + auto-fit), and the View-entry
/ sheet-viewport apply paths all delegate to it. Tiled vs floating now
differ only in field source and the floating auto-fit fallback.
Twist round-trips on layout switch and file save:
- camera_from_view applies roll = -twist; the write path was dropping it,
so model→paper→model (and reload) reset the view to raw orientation.
- New Camera::roll() recovers the camera roll; sync_camera_to_document,
write_camera_view_entry, and vport_from_camera now store -roll as the
view twist on the *Active VPort, the OpenCADStudio_Camera_Model View
entry, the per-tile VPorts, and the paper sheet viewport.
Rotation-aware frustum cull:
- New view_cull_aabb(): the entity-cull rectangle is built from the
camera's right/up basis (so a twisted/rotated view's rotated rectangle
is enclosed) instead of world X/Y axes; returns None for tilted views
to disable the flat-XY cull rather than wrongly hide geometry. Applied
to the model-tile and paper-viewport cull sites.
Paper viewport scale:
- The floating-viewport auto-fit overlap test summed view_target +
view_center raw; under a twist view_center is a DCS offset, so the sum
landed far from the real WCS centre and wrongly tripped the auto-fit,
rendering content at the wrong zoom. It now tests the decoded
(twist-folded, wire-space) target. The CPU projection path derives its
scale from that same camera, locking hit-test/snap to the GPU render.
Grid enumeration:
- ViewportInstance gains grid_on; the two grid-view collectors collapse
into one grid_views() derived from the same active_viewports() list the
renderer uses, so the grid overlay can no longer drift from the views
on screen.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Drawings authored under a rotated UCS save a view twist so they open
square. camera_from_vport ignored VPort.view_twist (roll hardcoded 0),
so such files rendered in raw world orientation and looked tilted.
Feed -view_twist in as the camera roll: the twist rotates world-X onto
screen-right, so the direction that becomes horizontal is its negative.
The roll rides the camera quaternion, surviving render and orbit.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Gate target-specific code so neither build warns about the other's items:
native-only helpers (view, window icon, file dialogs, file association, plugin
host/guard/registry, self-update) are cfg'd to non-wasm; the wasm-only font
fallback (script_of / cjk_lang / request) is cfg'd to wasm. Drop an unused mut,
allow the few genuinely-conditional dead items (mesh_click_hit, GPU layout
helpers, parity stubs), and gate the embedded PlotSettings-unrelated dead
constants. Both targets now build clean (only the unavoidable nom/quick-xml
future-incompat dependency notice remains).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
delete_layout removed the Layout object and its entities but left the paper
space block record, the ACAD_LAYOUT dictionary entry and any standalone
PlotSettings behind. AutoCAD pairs every paper-space block record with a layout,
so the deleted tab could reappear on reopen. Also drop the block record, the
dictionary entry and the page setup so the layout is gone for good.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Grid (and grid-snap) state was shared through the global show_grid flag, so
toggling it in model space also turned it on in paper space, and a floating
viewport's grid painted across the whole paper — and beyond the 3-D view when
zoomed in.
- Store grid/snap per view: a model tile in model space, the active floating
viewport or the layout's sheet viewport in paper space, round-tripped via the
viewport status flags.
- adopt_view_display on layout switch and viewport enter/exit so the live
toggle follows the view you are in.
- Render one grid per view (paper_viewport_grid_views): the sheet over the
paper, each floating viewport clipped to its screen rectangle.
- Clamp the grid clip to the widget so an overflowing viewport rectangle never
paints grid outside the 3-D view.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Builds on the acadrust viewport/PlotSettings round-trip fixes:
- Write the sheet viewport's view position into view_center (DCS) with
view_target at the origin, matching AutoCAD; the old view_target form shifted
the layout.
- PAGESETUP now writes paper size, rotation, origin and units onto the Layout's
embedded PlotSettings (not just a side object), and bumps geometry, so an edit
shows on the sheet immediately and survives a save.
- Draw the printable-area guide (paper inset by plot margins, rotation-aware) as
a dashed rectangle, matching AutoCAD's layout view.
- Plot/PDF export falls back to the Layout's embedded PlotSettings via
effective_plot_settings(), so a loaded file's rotation/origin/scale apply.
Bumps acadrust to pick up the viewport-status and PlotSettings round-trip fixes.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The overall (sheet) viewport was created with its center as (x, 0, z) —
carrying the paper-height midpoint in z with y = 0. AutoCAD/TrueView read a
paper-space viewport center as (x, y), so the sheet view ended up centered at
y = 0, shifting the whole layout half a page down; activating the layout in
AutoCAD could fault on the off-plane viewport. Store the center as (x, y, 0),
matching the convention MVIEW already uses for floating viewports. Verified: a
newly created paper-space viewport no longer faults AutoCAD on layout switch.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
In-place block edit now reads more like a dedicated editor:
- Fade everything except the edited entities toward the background (wires,
block instances and 3D solid meshes) so the block stands out while the
surrounding drawing stays visible for context.
- Don't leave a command active on enter — the user edits the block's geometry
freely (move, grips, draw, erase) and finishes explicitly.
- Show the right-edge vertical toolbar with clickable Save (REFCLOSE_SAVE) and
Discard (REFCLOSE_DISCARD) buttons while a session is active, so the edit
can be ended without typing.
The fade clears on save/discard, restoring full brightness.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The instanced block solid meshes kept the inner solid's handle as their
name, but the highlight tint matches the mesh name against the hovered /
selected handle — which for a block is the parent INSERT. So hovering or
selecting a block whose body is a solid never tinted it. Tag the instanced
meshes with the parent INSERT handle so the orange hover / blue selection
tint applies.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Clicking a block whose body is a solid tried top-level solid meshes first
and block-internal ones only as a fallback, so a stray solid behind the
block always won and the block couldn't be picked. Merge both into one
depth-sorted hit-test (solid_click_hit) keyed by the solid's own handle or
the parent INSERT, so the front-most body wins regardless of kind.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
A DXF SOLID renders as a cached solid-fill hatch, but transform_entities and
copy_entities only rebuilt that cache for true HATCH entities — a SOLID fell
through and kept drawing at its old position (or a copy showed nothing).
Rebuild the solid-fill hatch for EntityType::Solid in both paths, matching
the existing grip/add/open handling.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Box/lasso only tested top-level solid meshes, so a block whose visible body
is a solid (its mesh lives in the per-INSERT instanced cache, and it has no
wires to catch) was never picked up by a window/crossing/lasso. Add
block_mesh_box_hit / block_mesh_poly_hit that test each INSERT's instanced
block solid meshes and resolve a hit to the parent INSERT, and wire them
into the selection paths.
Co-Authored-By: Claude Opus 4.8 <[email protected]>