Iced 0.14 batches all shader prepare() calls before any render() calls.
Multiple ViewportPane widgets sharing the same Pipeline type overwrite
each other's GPU buffers, causing the PaperSheet layer (full-canvas) to
render the last viewport's model content — appearing full-screen.
Fix: paper_canvas_view() now renders a single full-canvas PaperSheet
widget. build_paper_sheet_primitive() is updated to include model-space
content projected through each viewport's view matrix (via
viewport_content_wires()), restoring the original working behaviour.
ViewportPane::Paper mode and viewport_screen_rect() are kept with
#[allow(dead_code)] — a per-viewport wgpu sub-renderer that accumulates
data across frames could revive them in the future.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove Scene: shader::Program<Msg> impl; all GPU rendering now goes
through ViewportPane which supports three modes:
Model — full model-space view (unchanged behaviour)
PaperSheet — paper-space entities only, using the paper camera
Paper — model content through a specific viewport's own camera
- Add Scene helper methods (render.rs):
build_primitive / build_paper_sheet_primitive / build_viewport_primitive
update_viewcube_state / viewcube_mouse_interaction
- Add Scene helpers (mod.rs):
paper_sheet_wires() — paper entities without viewport projection
camera_for_viewport() — derive Camera from Viewport entity data
model_wires_for_viewport() — model wires filtered by per-viewport frozen layers
viewport_screen_rect() — paper-space → pixel rect mapping
- Add paper_canvas_view() in view.rs:
Stack of PaperSheet + one Paper widget per Viewport entity,
positioned with Space offsets from viewport_screen_rect()
- Update VIEWPORT_WIDGET_PLAN.md: mark Steps 1–4 done, document remaining work
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Block-definition geometry (entities stored inside named blocks) was
leaking into the viewport when DXF files omit the owner-handle group
code (330) on block-content entities.
The fix tightens `belongs_to_visible_block`: when `owner_handle` is
null, the current layout block-record's `entity_handles` list is used
as the authoritative allow-list before falling back to the older
"not-in-any-other-block" heuristic. This ensures block definitions
are never rendered directly, only when referenced via an INSERT.
Bumps version to 0.1.4.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Arc rendering now respects the normal vector: when normal.z < 0 the
midpoint is placed on the clockwise side so the curve sweeps the right way
- Removed spurious .to_radians() / .to_degrees() round-trips in arc.rs
(angles are already stored in the unit the trig functions expect)
- apply_grip and apply_transform use consistent angle units
- normalize_insert_entity no longer converts arc angles; the old conversion
was only needed by the now-removed .to_radians() call in to_truck
- Added fix_mirrored_arc: swaps arc start/end angles when the INSERT has a
mirrored scale (x_scale * y_scale < 0) so exploded arcs curve correctly
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- When SAT tessellation yields no mesh (binary SAB or unsupported ACIS
geometry), render the pre-computed edge-wire polylines stored in
Solid3D / Region / Body entities as a visible line fallback.
- Ole2Frame entities now render a bounding rectangle with diagonal X
instead of an invisible stub — marks embedded OLE objects in the
viewport.
Closes ROADMAP 2.13 (partial), 3.2 (Ole2Frame).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Refactors solid3d_tess to share tessellate_sat() across Solid3D,
Region (2D planar ACIS body), and Body (3D ACIS body).
populate_meshes_from_document() and add_entity() now tessellate
all three entity types into GPU MeshModels when ACIS SAT data
is available.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Panel overlay showing all layouts with their active status.
Supports: select, rename, new, delete, reorder (◀▶), set current.
Also adds Scene::swap_layout_order() for tab-order resequencing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BOX, SPHERE, and CYLINDER build truck topology solids and tessellate
them into MeshModels stored alongside a Solid3D placeholder entity so
the GPU mesh pipeline renders them immediately.
EXTRUDE picks any closed 2D profile (Circle, LwPolyline, etc.),
attaches a planar face via try_attach_plane, and translational-sweeps
it (tsweep Face → Solid) along Z by the given height.
REVOLVE picks a profile, two axis points, and an angle (default 360°),
then rotational-sweeps the wire (rsweep) around the axis.
Three new CmdResult variants drive the operations:
CommitSolid3D, ExtrudeEntity, RevolveEntity.
Scene::layer_color() added to retrieve the active layer's RGBA color.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
REFEDIT picks an INSERT, extracts the block entities into model space
with the INSERT transform applied (translate + rotate + uniform scale),
and enters an editing session. Any normal modify command works on the
temporary entities.
REFCLOSE SAVE applies the inverse transform, replaces the block
definition, and rebuilds all derived caches (hatch/image/mesh), so
every INSERT of that block reflects the edits immediately.
REFCLOSE DISCARD removes the temp entities without changing the block.
Non-uniform-scale inserts are rejected with an error message.
RefEditSession is stored on DocumentTab; undo snapshots bracket the
begin and close operations.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
MSAA and depth textures are now sized to the shader widget's clip
bounds instead of the full surface. The resolve writes to a matching
clip-sized intermediate texture; a blit pass (blit.wgsl, viewport
trick) then copies the result to the exact clip region of the surface
target, leaving all other widgets untouched.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All main render pipelines (wire, hatch, image, mesh) now use 4×
multisampling. Drawing passes render to a dedicated MSAA color buffer;
a final empty resolve pass transfers the anti-aliased result to the
iced surface target. The ViewCube renders post-resolve at 1× to avoid
pipeline incompatibility. Depth buffer also upgraded to 4× MSAA.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously wipeout solid fills were part of the hatch pass (pass 1),
drawn before wires (pass 5), so all wires appeared on top of the
wipeout. This fix moves wipeout fills into a dedicated pass 6 that
runs after wires, so the background-colored polygon correctly covers
the lines/text beneath it — matching AutoCAD behaviour.
- Scene.wipeout_models() separated from synced_hatch_models()
- Primitive.wipeout_hatches populated via wipeout_models()
- Pipeline.gpu_wipeouts + upload_wipeouts() + pass 6 after wires
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add VP freeze/thaw toggle columns to the layer panel when in a paper
space layout. Each viewport gets its own column (VP 1, VP 2, …) showing
a freeze icon per layer. Clicking toggles frozen_layers on the Viewport
entity and syncs back to the panel.
- scene: viewport_list() → [(handle, label, frozen_handles)]
- LayerPanel: VpCol + vp_frozen per layer, sync_with_viewports()
- Message::LayerToggleVpFreeze(layer_idx, vp_col_idx)
- Sync VP columns on layout switch, VPLAYER cmd, undo/redo, layer ops
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Scene now carries bg_color / paper_bg_color fields (defaults match
previous hard-coded values). synced_hatch_models() reads these instead
of hard-coded constants, so Wipeout fills correctly use the custom
background when BACKGROUND <r> <g> <b> has been set.
BACKGROUND command syncs both DocumentTab.bg_color and scene.bg_color.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When add_entity receives a RasterImage without a definition_handle,
create an ImageDefinition object in document.objects and link it.
This ensures DWG/DXF files save the IMAGEDEF section correctly so
other CAD programs can resolve image paths on reload.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wipeout entities now render a background-colored solid fill polygon
(via the hatch GPU pipeline) that masks other hatch fills beneath them.
Fills use model space background (0.11 grey) or paper space background
(0.22/0.24/0.28). Polygon and rectangular clip boundaries both supported.
Selected wipeouts use a translucent blue highlight instead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- src/io/plot_style.rs: PlotStyleTable and PlotStyleEntry data structures;
CTB reader (deflate/zlib decompressed text format) with ACI→RGB and
ACI→lineweight lookup; CTB writer (zlib compressed); LW_TABLE constant
- src/io/pdf_export.rs: accept optional PlotStyleTable; apply ACI color
and lineweight overrides during PDF rendering
- src/scene/wire_model.rs: add `aci: u8` field to carry ACI index
- src/scene/render.rs: render_style() now returns ACI alongside RGBA
- src/scene/mod.rs + tessellate.rs: propagate aci through wire building
- app: PlotStyleLoad/Loaded/Clear messages and handlers;
PLOTSTYLE LOAD | CLEAR | STATUS command; active_plot_style state on H7CAD
- flate2 added as dependency
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add `active_ucs: Option<Ucs>` per-tab state to DocumentTab
- Add `ucs_to_wcs`, `wcs_to_ucs`, `ucs_z_axis`, `ucs_rotated_z` helpers
- Add `Camera::pick_on_plane` for ray–plane intersection against any plane
- Mouse picks project onto the active UCS XY plane instead of world XY
- Typed coordinates are converted from UCS space to WCS before dispatch
- UCS command now really activates/deactivates a UCS:
- `UCS W` resets to WCS (clears active_ucs)
- `UCS <name>` activates a named UCS
- `UCS SAVE <name>` saves the current active UCS
- `UCS ORIGIN x,y,z` shifts origin (in current UCS space)
- `UCS X/Y/Z <angle>` rotates the UCS around its own axes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TruckConvertible renders the 4 perimeter edges as wireframe outline.
The scene injects a solid-fill HatchModel for the filled quad using the
existing hatch GPU pipeline (DXF Z-order: p0→p1→p3→p2).
Grippable provides 4 square corner grips; grip moves rebuild the fill model.
PropertyEditable exposes all 4 corners and thickness. Transformable handles
move/rotate/scale/mirror via the standard entity transform path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Polyline, Line, CircularArc, EllipticArc, and Spline boundary edges
all get diamond grips at their control vertices. Dragging a grip updates
the underlying DXF boundary vertex and immediately rebuilds the GPU
hatch model via Scene::apply_grip.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Status bar: shows "N VP" pill (with tooltip) when in a paper layout that has
user viewports (id > 1). New Scene::viewport_count() method.
REVCLOUD: closed LwPolyline with arc bumps (bulge = 0.5) along each edge.
Click polygon corners, Enter to close into a revision cloud shape.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
wires_for_block() now looks up the SortEntitiesTable for the current block.
When found, wires are sorted by sort_handle.value() so DRAWORDER F/B results
are immediately visible in the viewport (not just written to DXF).
Entities not in the sort table default to u64::MAX/2 (middle of draw order).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- viewport_content_wires(): conditional perspective divide using
camera_dist = view_height * lens_length / 24.0 (35mm-film equiv.);
points behind the camera are mapped to NaN for safe clipping
- viewport.rs: expose lens_length, ucs_per_viewport, ucs_origin,
ucs_x_axis, ucs_y_axis as editable properties; handle all new
fields in apply_geom_prop()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Enforce status.locked in pan_active_viewport and zoom_active_viewport:
locked viewports now reject pan/zoom inputs entirely
- Add perspective, render_mode, shade_plot_mode, hide_plot, ucs_icon_visible
fields to the viewport property panel with appropriate pickers/toggles
- Add VPORTS command: lists all user viewports in the current layout
(id, size, position, scale, on/locked state) in the command line
- Add VPLAYER command: per-viewport layer freeze/thaw from the command line
(F <layer> to freeze, T <layer> to thaw, Enter to exit)
- Add CmdResult::VpLayerUpdate variant and its handler in apply_cmd_result
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
line_weight_px was multiplied by the viewport scale factor, making lines
near-invisible at small scales (e.g. 1:100) and overly thick at large
scales (e.g. 5:1). Line weights represent physical pen widths on the
plotted sheet and must be constant regardless of viewport scale.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Selection, snapping and editing are now mode-aware:
PSPACE (paper layout, no active viewport)
- Only paper-space entities (viewport borders, title blocks, etc.) are
hit-testable and selectable. Viewport content wires are invisible to
the interaction layer.
- Drawing commands add entities to the paper-space layout block so
annotations/title blocks land on the sheet, not in model space.
MSPACE (active viewport entered)
- Only model-space entities visible through the active viewport are
hit-testable. Other viewports' content and paper entities are ignored.
- Drawing commands add entities to model space (correct DXF block).
- Snap results from projected viewport-content wires are converted from
paper-space back to model-space via paper_to_model so that placed
coordinates and command previews are consistent.
Entering/exiting MSPACE clears the current selection so no stale
cross-space selection is carried over.
Implementation:
- Scene::hit_test_wires() returns mode-filtered wires for all
hit-tests, snaps, and selections (entity_wires() still returns
everything for rendering and PDF export).
- viewport_content_wires() gains an Option<Handle> filter to render
only the active viewport's content for MSPACE hit-testing.
- Scene::add_entity() routes to the paper-layout block or model-space
block depending on current_layout / active_viewport.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Four bugs prevented viewports from working like AutoCAD model-space windows:
1. Dead-code double-click block: `!is_down2` was always false because
`is_down2 = sel.left_down` is captured while the button is still held
(before the selection block clears it). Changed to `is_down` so the
double-click handler actually fires.
2. Inverted pan direction: pan_active_viewport used `-=` on the delta
returned by screen_delta_to_world, which is the same delta that cam.pan
*adds* to its target — causing reversed drag. Changed to `+=`.
3. Zoom not cursor-centered: zoom_active_viewport now accepts an optional
paper-space cursor position and adjusts view_target so the model point
under the cursor stays fixed (same as cam.zoom_about_point).
4. ESC did not exit MSPACE: CommandEscape now calls ExitViewport when
active_viewport is set and no command is running.
Also added MS/MSPACE and PSPACE command aliases, and MspaceCommand /
PspaceCommand messages so MS auto-enters the first viewport.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CadDocument::new() inserts default Layout1; DXF/DWG readers then add
the file's own Layout1 on top, producing duplicate tabs with the same
name but different handles. layout_names() now deduplicates by name,
preferring the entry with a non-null block_record (the real layout from
the file).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause: the DXF reader never reads group code 340 (block_record
handle) inside LAYOUT objects, so layout.block_record stays Handle::NULL
after opening any DXF file. This caused current_layout_block_handle()
to return NULL, which made belongs_to_visible_block() return true for
every entity — all entities appeared in all layouts simultaneously.
Fix in current_layout_block_handle() — three-tier lookup:
1. Layout.block_record (non-null) — works for DWG files.
2. Conventional DXF name ("*Model_Space", "*Paper_Space", …) derived
from the layout's tab_order — works for standard DXF exporters.
3. Position-based fallback: sort paper-space block records and paper
layouts independently, then match by index — handles exporters that
set tab_order = 0 for all layouts.
Apply the same two-tier lookup to model_space_block_handle().
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Paper limits:
- Change A4 fallback from 12×9 to 297×210 mm in paper_limits()
Context menu:
- Translate layout context menu labels to English (Rename / Delete)
Viewport clipping:
- Replace AABB-only culling with proper Cohen-Sutherland line clipping
in viewport_content_wires(); each projected segment is now clipped at
the exact viewport boundary instead of being included or discarded whole
- Add cs_clip() and clip_polyline_to_rect() free functions in scene/mod.rs
Frozen layers per-viewport UI:
- Inject a "Frozen Layers" EditText property in the Properties panel for
viewport entities (layer names resolved from handles at display time)
- Handle "frozen_layers" in PropGeomCommit: parse comma-separated names,
resolve to layer handles, update vp.frozen_layers in-place
PDF export improvements:
- Add offset_x/offset_y params to export_pdf() for model-space normalization:
coordinates are shifted so the drawing origin maps to the page origin
- Model space export computes extents via model_space_extents() (now public)
and adds a 5 % margin; falls back to A4 landscape for empty drawings
- Skip __paper_boundary__ wire in PDF (white background covers it)
- Map yellow (active viewport border) → black and cyan (vp border) → dark
blue for print-friendly output
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add model_space_extents() to Scene: collects key_vertices from all
model-space entities to compute an AABB.
- Add auto_fit_viewport() to Scene: sets view_target to model centroid
and derives custom_scale / view_height so the content fits the new
viewport with a 10% margin.
- Call auto_fit_viewport() in commit_entity() after a viewport is added
to a paper-space layout, so newly placed viewports immediately show
model-space content at a sensible scale.
- Scale line_weight_px by the viewport scale factor when projecting
model-space wires into paper space, so dimension arrows and annotation
lines appear at the correct visual thickness.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 3D view direction support: model geometry projected using view_right/
view_up vectors derived from Viewport.view_direction — enables Front,
Side, Isometric views inside paper-space viewports
- Proper AABB clipping: replaces centroid heuristic with bounding-box
intersection test against the exact viewport rectangle
- Per-viewport frozen layers: Viewport.frozen_layers Vec<Handle> is
resolved to layer handles and used to skip frozen entities
- view_target used consistently as the model-space pan origin
- Model wires collected per-viewport (inside the loop) so each viewport
can apply its own layer-freeze set
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Viewport ID uniqueness: commit_entity assigns max_existing_id+1
instead of always using id=2
- Lock/On boolean toggles in Properties panel (vp_locked, vp_on)
backed by status.locked / status.is_on fields
- Standard scale Choice picker: 1:500 to 10:1 (also syncs view_height)
- Numeric scale edit now syncs view_height when changed
- Standard view Choice picker: Top/Bottom/Front/Back/Left/Right/4×Iso
updates view_direction vector via apply_geom_prop
- Viewport content not rendered when status.is_on is false
- current_layout_block_handle_pub() added for external access
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add active_viewport: Option<Handle> field to Scene
- Add EnterViewport(Handle) and ExitViewport messages
- Double-click on a user viewport (id > 1) enters MSPACE;
double-click on empty area while in MSPACE exits to PSPACE
- MSPACE middle-drag pans the viewport's model-space view (view_target)
- MSPACE scroll zooms the viewport's view_height / custom_scale
- Drawing commands inside MSPACE receive model-space coordinates
via paper_to_model() coordinate transform
- Active viewport rendered with yellow dashed border (vs. cyan solid)
- Camera screen_delta_to_world helper for MSPACE pan conversion
- LayoutSwitch clears active_viewport
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Kağıt sınırı dikdörtgeni: paper layout'ta Layout.min/max_limits'ten
beyaz çerçeve wire'ı oluşturulur, entity_wires()'a eklenir
- first_viewport_scale(): paper layout'taki ilk kullanıcı viewport'un ölçeğini döner
- paper_limits(): mevcut layout'un kağıt sınırlarını döner, sıfır boyutlu
layout için varsayılan 12×9 kullanılır
- Status bar scale göstergesi: hardcoded "1:1" yerine aktif viewport ölçeği
dinamik olarak "1:50", "2:1" vb. formatında gösterilir
- Overall viewport (ID=1) zaten gri renkte render ediliyor (tessellate_one)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Layout sekme sağ-tıklamasında context menüsü (Yeniden Adlandır / Sil)
- Inline rename: sekme metin-giriş alanına dönüşür, Enter ile commit, ✕ ile iptal
- LayoutDelete: layout ve sahip olduğu tüm entity'leri kaldırır
- LayoutRename: Layout nesnesinin adını yerinde günceller
- LayoutCreate çakışma koruması: mevcut isimler arasında benzersiz ad bulur
- Escape tuşu açık rename/context menüsünü kapatır
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ribbon.rs → ribbon/mod.rs + ribbon/widgets.rs
Extracted all rendering helpers, button styles, colour constants,
layout constants and dropdown position calculators into widgets.rs.
- scene/mod.rs → scene/mod.rs + scene/selection.rs + scene/render.rs
Moved SelectionState into selection.rs; moved CameraState, Primitive,
shader::Program/Primitive impls, render_style and resolve_pattern
into render.rs, reducing mod.rs from 1323 to ~1030 lines.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>