Commit Graph
541 Commits
Author SHA1 Message Date
Hakan Seven 74a685d0e8 fix(viewport): UTM auto-fit + mesh world_offset
Two world_offset bugs in viewport rendering.

(1) Solid3D / Region / Body meshes were tessellated straight out of
the ACIS SAT data in WCS, while the wire / hatch / face3d pipelines
all run in `(WCS - world_offset)` local space. At UTM scale the
result was triangles drifting millions of units off-camera every
frame. `offset_mesh_lod_set` post-processes the freshly built
`MeshLodSet`, subtracting `world_offset` from every vertex and
recomputing `world_aabb` so the LOD / cull math agrees with the
shared camera space. Applied at the three `tessellate_volume`
call sites — load, `add_entity`, and `populate_meshes_from_document`.

(2) Paper-space content viewports rendered blank on UTM drawings.
The DWG / DXF saved `view_target = (0, 0, 0)` while the model sat
around `world_offset`; the CPU projection in `viewport_content_wires`
already had a "saved view doesn't overlap content cluster →
auto-fit to (world_offset ± local_extent_max)" fallback, but the
GPU path read `vp.view_target` literally and culled every entity
against the empty WCS rect. `camera_for_viewport` now applies the
same overlap test + auto-fit, and `model_wires_for_viewport`
derives its frustum AABB / wpp from the resulting camera so both
the camera and the cull agree on which area to show.

Folding `view_center` directly into `effective_target_wcs` matches
the CPU path's `display_center_x = view_target + view_center`,
removing the prior `+ view_right * view_center.x` shift that would
double-count once auto-fit had overwritten the target.
2026-05-28 20:51:16 +03:00
Hakan Seven 1eb54ee8a3 perf(viewport): per-vp frustum + LOD cull in paper layout
`model_wires_for_viewport` previously passed `view_aabb: None` and
`wpp: None` to `tessellate_entity` — the assumption that paper-space
viewports were too small for culling to matter falls over the moment
the document carries 100k+ entities. Each frame re-tessellated the
full model through every floating viewport.

`wires_for_block_culled` grows two optional parameters
(`frozen_layers`, `anno_scale_override`) and now flips its
`world_offset` / `bg` / `anno` selection on `is_model_block` rather
than `current_layout`, so a paper-layout caller asking for the model
block still gets WCS-subtracted coordinates and the right annotation
scale.

`model_wires_for_viewport`:
- derives the viewport's frustum AABB from its (paper-zoom-invariant)
  camera + entity aspect — 25 % margin to match `view_world_aabb`,
- derives `wpp` from the on-paper pixel height the viewport currently
  occupies, so LOD stubs and zoom-adaptive curve sampling track the
  paper-zoom state,
- delegates everything else to `wires_for_block_culled` so block /
  layer / frozen-layer / curve-tolerance handling stays in one place.

`model_wires_for_viewport_arc` keys on `(geometry_epoch,
round(screen_height_px))` — sub-pixel jitter still hits the cache,
real paper-zoom steps invalidate. Hit-test / CPU-projection callers
pass `screen_height_px = 0.0` to get the no-LOD (full-fidelity) wire
list in their own cache slot.
2026-05-28 20:32:16 +03:00
Hakan Seven 0eaac008dd fix(viewport): per-tile wire cache for Model layout
Tiled Model panes were all reading from the shared `entity_wires_arc`,
whose tessellation runs `view_world_aabb` / `world_per_pixel` against
the live `Scene::camera`. Zooming the active tile re-built that arc
with the active camera's LOD / frustum cull, then every other tile
rendered from the same arc — content in the inactive panes flickered
between full and LOD-stub tessellations as you zoomed.

- `wires_for_block` now delegates to `wires_for_block_culled`, which
  takes `view_aabb` + `wpp` explicitly. The original caller passes
  the live-camera values; tile rendering passes its own.
- New `model_tile_wires_arc` builds a per-tile view AABB + wpp from
  the tile's camera + pixel rect and caches the result. Cache is
  keyed by tile index; the value holds `(geometry_epoch,
  camera_state_hash)`, so a stale hit (camera moved, doc changed)
  still misses and rebuilds.
- `ViewportInstance` gains `tile_idx` so `viewport_data_for` can route
  Model tiles into the per-tile cache without disturbing the paper-
  layout content-viewport path (it still uses `model_wires_for_viewport_arc`).
2026-05-28 20:13:23 +03:00
Hakan Seven 168f248593 fix(overlay): hide system cursor over viewport, drop CAD over divider
Two cursors layered on top of each other (the OS arrow / crosshair
icon plus the CAD crosshair drawn in `SelectionCanvas::draw`) looked
busy and made it harder to see what was pickable. `Interaction::None`
was the first try but `Stack::mouse_interaction` skips that value as
"no opinion" — the correct hide signal is `Interaction::Hidden`,
which iced_winit maps to a hidden cursor.

Over a Model-tile divider the OS now wins (resize arrow), and the CAD
crosshair is suppressed in `draw` via the same `tile_edge_under`
helper that mouse_interaction uses.
2026-05-28 19:52:34 +03:00
Hakan Seven 39eab9a4c4 feat(viewport): draggable Model-tile dividers
Tiled Model layouts now show the inner edges between panes as 2-px
dividers that act as resize grips: cursor on a divider → resize cue,
press + drag moves the edge (with every tile sharing that edge
updating together), release runs a collapse pass that absorbs any
tile that fell below the viewcube-comfort minimum into the neighbour
with the longest shared contact edge — drag a divider all the way
across to remove a pane.

- Scene grows `model_tile_edges`, `hit_model_tile_edge`,
  `move_model_tile_edge`, `collapse_small_model_tiles`. Edges are
  derived from the existing `model_tiles` rects (no extra storage).
- App carries a `tile_drag` Option; `ViewportLeftPress` starts it
  ahead of tile activation / picking, `ViewportMove` drives the
  edge, `ViewportLeftRelease` collapses + clears.
- `SelectionCanvas` draws the dividers and switches the cursor to
  `Resizing{Horizontally, Vertically}` over them.
2026-05-28 18:42:39 +03:00
Hakan Seven e3c962bdd1 fix(viewport): adapt entity colours to visible bg
`model_wires_for_viewport` was tessellating with `self.bg_color` (the
model bg), so model content rendered inside a paper-layout viewport
adapted to a dark bg even though the transparent shader now reveals
the PaperCanvas sheet behind it — white wires stayed white on a light
sheet. Pass the paper bg in a paper layout.

Wire cache also needed an explicit invalidation when the bg or the
active layout changes; otherwise the cached, already-adapted wires
were returned against the new bg unchanged.

- BACKGROUND command bumps `geometry_epoch` after both branches
- new `Scene::set_current_layout` setter bumps on actual change; layout
  switch + history restore use it
2026-05-28 18:28:18 +03:00
Hakan SevenandClaude Opus 4.7 2b5db8501b fix(viewport): clip MSAA to canvas, crop view_proj instead
The previous off-canvas fix sized each per-viewport MSAA / depth /
resolve texture to the full viewport rect, then UV-cropped the blit.
With deep paper-space zoom that rect can grow past wgpu's 8192-pixel
texture limit and the program panics in `Device::create_texture`
("Dimension Y value 9493 exceeds the limit of 8192").

`viewport_data_for` now clips the screen rect to the canvas before
returning the `ViewportData`, so the per-viewport textures are bounded
by the surface (≤ canvas size, always within the GPU limit). The
camera frustum is still built at the *full* vp aspect (so the world it
projects matches the viewport entity), and a new `crop_view_proj`
post-multiplies the standard view_proj by a clip-space transform that
remaps the visible sub-rect to NDC `[-1, 1]^2` — the off-center ortho
that lets the visible-sized MSAA carry the correct portion of the
view. `viewport_size` is set to the visible size for the wire shader's
screen-space line extrusion; `world_per_pixel` is crop-invariant (the
`vs` factor cancels) so the value `Uniforms::new` computes from the
full bounds remains correct.

Viewports whose screen rect doesn't intersect the canvas at all return
`None` and are filtered out before any inner pipeline is allocated.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 18:12:10 +03:00
Hakan SevenandClaude Opus 4.7 3d24dd5168 fix(viewport): blit UV crop for off-canvas viewports
Refactor the per-viewport blit so a content viewport whose screen rect
extends off the canvas composites the correct slice of its MSAA target
to the visible portion of the surface, instead of being slammed against
the canvas corner by an `(f32 as u32)` saturation on the negative
offset.

- blit.wgsl + Pipeline gain a small uniform buffer (`uv_offset` /
  `uv_scale`). `Primitive::prepare` computes the per-viewport crop from
  `screen_rect`'s on-canvas fraction and writes it via the new
  `upload_blit_uv` helper while it still has the `Queue`.
- `Pipeline::render` now takes `vp_size` (full vp — MSAA / camera size)
  and `surface_dest` (visible intersection — blit destination) and
  reads the crop from the uniform; passes that don't have a non-empty
  surface intersection are skipped.
- `Primitive::render` clips the full vp rect against the surface clip
  in `i32` and skips empty intersections. The earlier "skip GPU for
  inactive off-canvas viewports" workaround in `active_viewports` is
  no longer needed.
- ViewCube draws only when its hosting vp is fully on-canvas (its own
  pipeline writes directly to the surface and would distort under a
  clamped `set_viewport`).

With the GPU path correct for every vp position, drop
`viewport_content_wires` from `paper_canvas_wires` — the inactive
viewport CPU projection was only there to mask the blit bug and was
duplicating the unified shader's output. The hit-test / snap path
(`entity_wires_arc`, `wires_for_block`) still calls
`viewport_content_wires` directly, so picking through inactive
viewports keeps working.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 12:15:00 +03:00
Hakan SevenandClaude Opus 4.7 600717c1ec fix(paper): skip GPU for off-canvas inactive viewports
Zooming the paper view in PSPACE grows each floating viewport's screen
rectangle until its edges run past the canvas. The per-viewport blit
computes its surface offset via `(screen_rect.x * cw) as u32`, which
saturates negative values to 0 — so a viewport hanging off the left
edge was being painted at the canvas top-left instead of its actual
position, producing a drifting ghost copy on top of the CPU projection
that PaperCanvas was already drawing correctly clipped.

`active_viewports` for paper now skips the GPU pass for any inactive
content viewport whose screen rect would extend beyond the canvas. The
CPU projection already handles those, so only one (correct) copy is
visible. The active viewport keeps the GPU path so its per-viewport
render mode (Hidden Line, Gouraud, …) still applies.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 10:38:56 +03:00
Hakan SevenandClaude Opus 4.7 dac835fe33 fix(viewport): use premultiplied blend on surface blit
Geometry passes draw with standard `SrcAlpha / 1-SrcAlpha` into a
transparent MSAA target, so AA-edge fragments sit as `(rgb * a, a)` in
the resolve texture. The previous straight-alpha blit then multiplied
those by `a` a second time, darkening thin lines and curves over the
transparent background. `PREMULTIPLIED_ALPHA_BLENDING` matches what is
actually in the resolve texture and renders edges at full intensity
while still leaving fully-transparent pixels untouched on the surface.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 10:29:05 +03:00
Hakan SevenandClaude Opus 4.7 a058ded74f feat(viewport): persist model tiled layout in VPort table
A multi-pane Model view (split / VPORTS 2H|2V|4) used to collapse back
to a single tile after save→reopen — only the *Active VPort entry was
written, the rest of model_tiles was discarded.

Each ModelTile now round-trips through a dedicated `*OCS_Tile_<i>` entry
in the VPort table:
- save_model_tiles_to_vports stashes the live camera into the active
  tile, removes any previous tile entries, and writes one VPort per
  tile (rect via lower_left/upper_right, camera via
  view_target/direction/height); the active tile is mirrored to
  `*Active` so apps that only read that entry still get a sensible
  view.
- restore_model_tiles_from_vports rebuilds the tile list on file open
  and selects the active tile by matching the entry whose view_target
  equals `*Active`'s, falling back to index 0.

Single-tile configurations and files from other apps skip the tile
entries entirely — restore returns false and the existing *Active /
View-table path runs, so behaviour for non-tiled files is unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 10:16:09 +03:00
Hakan SevenandClaude Opus 4.7 8d64426087 refactor(viewport): unify paper into one shader, transparent clear
Collapse the paper-space rendering path into the same shader widget the
model layout uses. The PaperCanvas widget draws the paper sheet, paper
entities and viewport borders underneath; the unified shader paints all
content viewports on top, scissored to each viewport's rect.

- Drop PaperViewportPane / PaperViewportPipeline newtype /
  PaperViewportPrimitive, ViewportPaneMode::Paper, ViewportPane::paper,
  build_primitive and build_viewport_primitive. Scene exposes only
  build_viewports.
- view.rs: the paper branch stacks PaperCanvas + the unified shader
  instead of the old paper_canvas_view function. The active paper
  viewport's blue border, render-mode picker and ViewCube hit-area are
  layered in the same viewport_stack as the model layout.
- Transparent shader clear (alpha=0) + alpha-blended blit pipeline so
  the underlying widget (container bg in model, PaperCanvas sheet in
  paper) shows through everywhere the shader has not painted geometry —
  including inside content viewport rectangles.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 10:03:01 +03:00
Hakan SevenandClaude Opus 4.7 417a708275 feat(viewport): tile-relative input + overlays in unified model
Route every per-frame interaction through the active model tile's screen
rectangle so a tiled VPORTS layout behaves like independent panes while
still sharing one shader widget:

- pick / draw / snap / box+lasso: cursor mapped to tile-local space; the
  hover-activates-tile rule on ViewportMove keeps the active tile under
  the cursor, so the picked camera matches what the user sees.
- pan: switched to Camera::pan_screen(dx, dy, viewport_height) so the
  world-per-pixel rate matches the active tile's height instead of the
  full canvas.
- grid: clipped via Frame::with_clip to the active tile rect; world→screen
  adds the tile origin so the lines align with the panel.
- UCS icon: origin offset by the tile rect so the gizmo sits in the
  active tile's bottom-left corner.
- render-mode picker (+ split buttons) and ViewCube hit-area: positioned
  via active_model_tile_bounds with leading Spaces, mirroring the
  floating-viewport pattern.
- ViewCube hover: driven by the CursorMoved/ViewportMove handlers into a
  new Scene::viewcube_hover cell, so the highlight survives the cube
  hit-area overlay masking events from shader::Program::update.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 09:45:19 +03:00
Hakan SevenandClaude Opus 4.7 6fe25e4b5f feat(viewport): interactive VPORTS model tiled presets
Model space'de VPORTS artık iki aşamalı: bare VPORTS bir yapılandırma
istiyor (SIngle/2H/2V/4), sonraki komut-satırı girdisi düzeni kuruyor
(boş giriş = SINGLE). set_model_tile_layout normalized rect listesiyle
tiled düzeni değiştiriyor:
- SINGLE: tek tam-ekran
- 2H: üst/alt
- 2V: sol/sağ
- 4: 2x2

awaiting_vports app flag'i prompt sonrası girdiyi yakalıyor. Paper-space
VPORTS davranışı değişmedi.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 22:52:55 +03:00
Hakan SevenandClaude Opus 4.7 41c75dbb9b feat(viewport): click activates tile, swaps in its camera
Tiled Model layout'ta aktif olmayan bir tile'a tıklamak onu aktive edip
kamerasını canlı scene.camera'ya yüklüyor (set_active_model_tile_at).
Böylece her panel bağımsız bir bakış tutuyor — bir panelde orbit/zoom
yaparken diğerleri sabit kalıyor. Aktif tile'a tıklama normal
selection/draw akışına devam ediyor.

Pick/pan/viewcube'un tam tile-relative koordinat yönlendirmesi sıradaki
adımda.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 22:38:45 +03:00
Hakan SevenandClaude Opus 4.7 c45146bbda feat(viewport): model-space tiled split (▤ / ▥ buttons)
Model layout artık tiled viewport destekliyor. Scene.model_tiles
normalized ekran rect'i + kamera tutan tile listesi; active_viewports
model layout'ta her tile için bir ViewportInstance üretiyor (aktif tile
canlı scene.camera'yı, inaktif tile'lar saklı snapshot'ı kullanıyor).

Render-mode picker'ın yanına iki buton eklendi: ▤ aktif tile'ı yatay
(üst/alt), ▥ dikey (sol/sağ) ikiye böler. split_active_model_tile
bölmeyi yapıyor; her iki yarı aktif kameranın kopyasını alıyor.

set_active_model_tile_at (cursor → aktif tile, kamera swap) ve
reset_model_tiles eklendi ama henüz bağlanmadı — aktif tile cursor
seçimi + pick yönlendirme sonraki adımda.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 22:33:36 +03:00
Hakan SevenandClaude Opus 4.7 c83726c037 feat(viewport): model space renders through active_viewports (step 4-5a/8)
build_viewports, active_viewports'un ViewportInstance listesinden
viewport başına ViewportData üretiyor (model: tüm model wire'ları;
paper viewport: layer-freeze'li alt küme). Model space artık
ViewportPane::model -> build_viewports üzerinden birleşik çoklu-viewport
yolundan render ediliyor — şimdilik tek tam-ekran viewport, davranış
birebir aynı.

Paper space hâlâ eski yolda (PaperCanvas + PaperViewportPane); sonraki
adımda birleşik yola taşınacak.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 19:55:49 +03:00
Hakan SevenandClaude Opus 4.7 b073421b92 refactor(viewport): Primitive carries Vec<ViewportData> + MultiPipeline (step 2-3/8)
Birleşik viewport mimarisinin yapısal temeli. Primitive artık tek kamera/
geometri yerine viewport başına bir ViewportData listesi taşıyor; yeni
MultiPipeline her viewport için bir iç Pipeline instance tutuyor.
prepare/render viewport listesi üzerinde dönüp her iç-pipeline'ı kendi
(normalized) ekran rect'ine çiziyor.

Mevcut Pipeline kodu (upload/LOD/render/blit) HİÇ değişmedi — sadece
viewport başına bir kez çalışıyor. build_primitive ve
build_viewport_primitive tek-elemanlı liste (FULL_VIEWPORT_RECT)
ürettiği için render davranışı tek-viewport ile birebir aynı; çoklu
viewport sonraki adımda (view.rs tek widget + active_viewports) aktive
edilecek.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 19:47:42 +03:00
Hakan SevenandClaude Opus 4.7 fbc127b8d9 feat(viewport): ViewportInstance + Scene::active_viewports (step 1/6)
Birleşik viewport mimarisinin ilk parçası: tek render geçişinde
çizilecek viewport'ları tanımlayan ViewportInstance (handle, ekran
rect, kamera, render mode, aktif) ve bunları üreten active_viewports.
Model layout tek tam-ekran instance, paper layout her content viewport
entity'si için bir instance döndürüyor. Henüz render'a bağlı değil
(additive); mevcut davranış değişmedi.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 19:09:13 +03:00
Hakan SevenandClaude Opus 4.7 eeb7df5fec fix(paper): make active-viewport picker & ViewCube clickable
Picker ve ViewCube paper_canvas içinde gömülü olduğundan tümüyle
viewport mouse_area'nın altında kalıyor ve tıklamaları o yakalıyordu.
İkisi de ana view stack'ine, mouse_area'nın üstüne taşındı; aktif
viewport'un ekran dikdörtgeninin köşelerine konumlandırılıyor.

- model picker artık yalnızca model layout'ta gösteriliyor (paper'da
  viewport kendi picker'ını alıyor)
- ViewCube hit alanı aktif viewport rect'inin sağ üst köşesinde
- CursorMoved ve ViewportClick, ViewCube hit-test'ini full canvas yerine
  aktif viewport rect'ine göre eşliyor; böylece paper'da gizmo ile
  tıklama hizalı
- picker/viewcube dışı tıklamalar alttaki mouse_area'ya geçtiği için
  viewport içi seçim/çizim etkilenmiyor

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 18:17:10 +03:00
Hakan SevenandClaude Opus 4.7 a41a05baa1 feat(paper): render-mode picker + ViewCube on active viewport
Paper-space'de bir viewport'a çift tıklanıp aktif hale gelince, model
space'deki gibi sol üst köşede render-mode dropdown'ı ve sağ üst köşede
ViewCube gösteriliyor.

- render_mode_picker helper'a çıkarıldı; model space ve aktif paper
  viewport ortak kullanıyor (kod tekrarı yok)
- SetRenderMode artık viewport-aware: aktif viewport varsa o viewport
  entity'sinin render_mode'unu, yoksa model-layout tab stilini günceller
  (Scene::active_viewport_render_mode getter/setter)
- build_viewport_primitive'e show_viewcube parametresi; aktif viewport
  primitive'i ViewCube gizmo'sunu çiziyor
- PaperViewportPane shader::Program artık Message'a özelleşti ve sol
  tık ViewCube hit-test'ini bounds-relative yapıp ViewCubeSnap üretiyor

Bilinen sorun: picker/viewcube doğru konumda görünüyor ancak tıklamalar
üstteki viewport_mouse mouse_area katmanına gidiyor; katman sırası bir
sonraki adımda düzeltilecek.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 18:05:09 +03:00
Hakan SevenandClaude Opus 4.7 fda0f2dbb9 refactor(ui): remove the viewport navigation bar
Sağ kenardaki dikey orbit/pan/zoom/zoom-extents araç çubuğu tümüyle
kaldırıldı: viewport render'ı, overlay::nav_toolbar fonksiyonu,
show_navbar state + ToggleNavbar mesajı/handler'ı, NAVBAR komutu, View
şeridindeki "Navigation Bar" aracı ve navbar modülü/ikonu. Aynı
gezinme işlemleri komutlar (3DORBIT, PAN, ZOOM*) ve ViewCube üzerinden
erişilebilir durumda.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 17:49:23 +03:00
Hakan SevenandClaude Opus 4.7 6483566c5d fix(dyn-input): capture numpad 8/2 as digits, not history nav
NumLock açıkken Numpad8 / Numpad2 logical key olarak ArrowUp / ArrowDown
geliyor ama text "8" / "2" taşıyor. Klavye aboneliğinde ok/geçmiş
arm'ları bu tuşları yakaladığı için dinamik girişte 8 ve 2 yazılamıyordu.
Printable glyph üreten her tuş artık navigation arm'larından önce, text
alanı üzerinden yakalanıyor; whitespace/control üreten tuşlar (Space,
Enter, Tab) named handler'lara düşmeye devam ediyor. Gerçek ok tuşları
(text üretmez) geçmiş gezinmesini korur.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 17:36:06 +03:00
Hakan SevenandClaude Opus 4.7 ded1b7b158 chore(release): bump to 0.4.2
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
v0.4.2
2026-05-27 14:19:05 +03:00
Hakan SevenandClaude Opus 4.7 4abde6001a feat(dyn-input): TAB-editable distance/angle/coordinate fields
Dinamik giriş artık cursor yanında düzenlenebilir kutular gösteriyor.
TAB ile kutular arası geçilir, bir kutuya yazınca o kutu kilitlenir
(değer sabitlenir), diğerleri cursor'ı takip etmeye devam eder, Enter
ile çözülen nokta komuta point-pick olarak iletilir.

- DocumentTab.dyn_fields / dyn_active state; DynComponent + DynFieldEntry
- sync_dyn_fields komutun dyn_field() + base noktasına göre kutu kümesini
  türetir: nokta+base → [d, açı], nokta+base yok → [X, Y], radius → [d],
  açı → [açı]
- klavye yönlendirme: rakam/./-/+ aktif kutuya, TAB sonraki kutuya,
  Enter try_dyn_commit, Backspace kilidi açar
- dyn_capturing iken command-line text alanı on_input'suz bırakılıyor
  ki focus'u ve tuşları çalmasın; girişler global key aboneliğinden
  geliyor (numpad dahil text alanı üzerinden yakalanıyor)
- çoklu-kutu overlay render: aktif kutu vurgulu, kilitli kutu farklı
  kenar rengi

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 14:08:57 +03:00
Hakan SevenandClaude Opus 4.7 1fc73ba8b0 fix(dyn-input): correct Y coordinate, label by prompt type — #27
DYN overlay artık ekran-yukarı için world Y'yi (w.y) gösteriyor; eskiden
w.z kullanıldığı için Z-up sahnede top/3D görünümde Y hep 0 görünüyordu
(#27).

Ayrıca overlay etiketi aktif komutun o anki sorusuna göre biçimleniyor:
yeni DynField (Point / Distance / Angle) trait metodu eklendi. CIRCLE
yarıçap adımında 'd=...', ROTATE açı adımında '<...°', diğer nokta
adımlarında koordinat / polar (d + açı) gösteriliyor.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 13:41:54 +03:00
Hakan SevenandClaude Opus 4.7 874226bd43 feat(input): relative coordinate entry (@/#) and DYN default — #26
Koordinat girişi artık göreli/mutlak ayrımını destekliyor:
- `@x,y[,z]` → son girilen noktaya göreli (UCS eksenlerinde döndürülür)
- `#x,y[,z]` → her zaman mutlak (DYN durumundan bağımsız)
- ön ek yok → DYN açıksa göreli, kapalıysa mutlak
İlk noktada referans olmadığından göreli giriş mutlağa düşer. Çözülen
WCS noktası last_point olarak saklanıyor, böylece ardışık göreli
girişler zincirleniyor.

parse_coord artık (Vec3, CoordKind) döndürüyor; relative offset için
ucs_rotate_vec eklendi (origin translation olmadan UCS rotasyonu).
UCS ORIGIN komutu prefix'i yok sayıp mutlak kalıyor.

Ayrıca ViewCube snap refactor'undan kalan kullanılmayan kod temizlendi:
angle_close, active_viewport_yaw_pitch, CubeRegion::snap_angles.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 11:40:28 +03:00
Hakan SevenandClaude Opus 4.7 451239c4f9 fix(viewcube): snap to direction with nearest-canonical up axis
ViewCube tıklaması artık sabit (yaw, pitch) tablosu yerine bir yön
vektörüne (eye direction) snap ediyor. Up vektörü şöyle seçiliyor:

  1. Mevcut up alın.
  2. ±X / ±Y / ±Z arasından (yeni gaze ile (anti-)paralel olmayanlar)
     mevcut up'a en yüksek dot-product veren cardinal axis'i seç.
  3. Bu axis'i yeni gaze yönüne dik plane'e projekte et ve up olarak
     kullan.

Sonuç:
- Küçük tilt'ler ilgili canonical axis'e snap olarak temizleniyor.
- TOP yukarıda iken TOP-LEFT edge tıklayınca TOP yukarıda kalıyor;
  cube edge'e dönüyor ama kullanıcının "yukarı" hissi korunuyor.
- Upside-down orbit edilmişse -Z up olarak seçilir, snap baş aşağı
  yapılandırmayı bozmuyor.

CubeRegion::snap_angles ve Camera::snap_to_angles yerini
snap_direction / snap_to_direction aldı.
snap_active_viewport_to_direction viewport'un view_direction'ını
günceller, twist_angle'a dokunmaz.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 08:09:38 +03:00
Hakan SevenandClaude Opus 4.7 26bcd89358 feat(lod): preserve 3D position for sub-pixel entity stubs
Yakındayken tessellate edilen 3D entity'ler (Face3D, Solid3D, Mesh,
PolyfaceMesh, PolygonMesh, Body, Region) <5 px eşiğine düştüğünde,
eski tek-çizgili stub'ın z=0 düzlemine indirilmesi yüzünden farklı
bir konumda görünüyordu. Yeni lod_stub_wire_3d entity'nin 3D AABB
kübünü (8 köşe, 12 kenar) çiziyor:
- XY'de entity_aabb local frame'i (world_offset shift edilmiş)
- Z aralığı bbox.min/max.z, world_offset[2] subtract edilmiş

2D entity stub'ı (lod_stub_wire) text fallback'leri ve diğer 2D
geometriler için z=0'da kalıyor.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 07:17:42 +03:00
Hakan SevenandClaude Opus 4.7 3a64f98755 fix(render): hide PolyfaceMesh outline edges in shaded modes
PolyfaceMesh / PolygonMesh için outline edges WireModel.points üzerinden
gpu_wires'a giriyor; bu nedenle FlatShaded / GouraudShaded modlarında
shaded yüzeyin üstünde görünmeye devam ediyordu. WireGpu artık kaynak
WireModel'da fill_tris doluysa 'is_3d_mesh_edge' bayrağını set ediyor;
wire pass'i show_3d_edges=false iken bu instance'ları atlıyor.
*WithEdges, Wireframe ve HiddenLine modları show_3d_edges=true ile
geliyor — mesh kenarları orada görünür kalır.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 06:36:59 +03:00
Hakan SevenandClaude Opus 4.7 132efe6574 feat(render): Flat/Gouraud shading, WithEdges overlay, paper-viewport mode
Faz 3 — FlatShaded vs GouraudShaded
- Uniforms.flat_shade (f32, 0/1) + mesh shader fragment derivative
  bazlı per-triangle face normal hesabı; GouraudShaded ise interpolate
  edilen vertex normalini kullanıyor.

Faz 4 — *WithEdges varyantları
- Pipeline::render.show_3d_edges parametresi:
  - FlatShaded / GouraudShaded → 3DFACE edge pass'i atlanıyor
  - *WithEdges varyantları → shaded fill'in üstüne mesh_wireframe
    pipeline ile triangle-edge overlay (LessEqual depth test ile)
  - face3d_edges pass'i show_3d_edges ile gate'lendi

Faz 5 — Paper-space viewport entity round-trip
- build_viewport_primitive / build_active_viewport_primitive artık
  render_mode parametresi almıyor; viewport entity'sinin kendi
  render_mode alanından okuyor. PaperViewportPane'dan render_mode
  field'i kaldırıldı. Model layout'taki tab.render_mode hala
  pick_list ile yönetiliyor.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 05:57:19 +03:00
Hakan SevenandClaude Opus 4.7 56834dfba6 feat(render): HiddenLine mode via depth-only prepass
3D yüzeyleri (Solid3D mesh, 3DFACE quad, PolyfaceMesh/PolygonMesh face
tris) HiddenLine modunda renk yazmadan z-buffer'a yazıyor; ardından
kenarlar wireframe pipeline ile çizilince LessEqual depth testi
arkadaki segmentleri eliyor. 2D fill'ler (text greek, MultiLeader
arka planı) ayrı bir vertex buffer'a ayrıldı ve her modda renkli
çizilmeye devam ediyor.

- Face3DGpu: vertex_buffer artık 3D / 2D olarak ikiye ayrıldı
- mesh_depth_pipeline + face3d_depth_pipeline (write_mask=empty)
- RenderModeFlags.hidden_line + Primitive.hidden_line plumbing

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 05:40:39 +03:00
Hakan SevenandClaude Opus 4.7 8dbdebeedc feat(render): wireframe modes hide mesh & 3D fills
Wireframe 2D/3D modlarında Solid3D meshlerini edge-only çizmek için
LineList topolojili bir mesh wireframe pipeline ve her üçgenin
(a,b),(b,c),(c,a) kenarlarına genişletilmiş ayrı bir index buffer
eklendi. PolyfaceMesh / PolygonMesh face fill'leri (fill_tris + dolu
points) wireframe'de gizleniyor; text greek / MultiLeader gibi 2D
fill'ler (points boş) etkilenmiyor.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 05:28:20 +03:00
Hakan SevenandClaude Opus 4.7 2145ab8a66 feat(render): phase 0 — render-mode pick_list + 3-flag pipeline plumbing
Replaces the binary Wireframe/Solid toggle with a 7-mode dropdown
that maps onto acadrust's `ViewportRenderMode` enum: Wireframe 2D /
Wireframe 3D / Hidden Line / Flat Shaded / Gouraud Shaded / Flat
Shaded + Edges / Gouraud Shaded + Edges. Per-tab state lives on
`DocumentTab::render_mode`; the old `SetWireframe` message forwards
to `SetRenderMode` so the ribbon + WIREFRAME / SOLID command line
keep working.

`scene::render::render_mode_flags()` translates the mode into three
pipeline-facing booleans (`face3d_fill`, `mesh_fill`,
`show_3d_edges`); `mesh_fill` / `show_3d_edges` are stubs that the
upcoming phases will consume. `Primitive` carries the derived
`view_wireframe` plus the two new fields through to `prepare()`.

`Pipeline::upload_face3d` takes a `wireframe_only` flag that drops
3DFACE quad fills (built from `key_vertices`) while keeping the
`all_wires.fill_tris` channel — that channel carries text-LOD greek
rectangles and PolyfaceMesh face triangles which must stay visible
in wireframe mode. 3DFACE edges stay on regardless of mode.

Solid3D mesh wireframe rendering, Hidden Line z-prepass, and Flat /
Gouraud shader uniforms come in phases 1–4.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 00:08:37 +03:00
Hakan Seven 5ea996d5fe Replace screenshot in README.md
Updated image in README with a new screenshot.
2026-05-26 23:06:08 +03:00
Hakan SevenandClaude Opus 4.7 b2d9660221 chore(release): bump to 0.4.1
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
v0.4.1
2026-05-26 22:48:30 +03:00
Hakan SevenandClaude Opus 4.7 05ea31aa2a feat(commandline): autocomplete suggestions + inventory-based registry
Type-as-you-go suggestion popup above the input that filters known
command names by substring (so `LEADER` surfaces both LEADER and
MLEADER). Up / Down navigates the list, Enter dispatches the
highlighted entry, clicking a row dispatches directly. Esc on a
non-empty input clears the buffer instead of escalating to
command-cancel.

The name corpus comes from `inventory::submit!` blocks placed next
to each `CadCommand` impl rather than a hand-maintained static list.
Adding a new interactive command means dropping one `inventory::submit!`
line in its own module file — the autocomplete picks it up at the
next compile. 98 submissions seeded across 60 module files;
`command::all_registered_command_names()` flattens them at runtime.

One-shot dispatch arms in `app/commands.rs` (NEW, OPEN, SAVE, UNDO,
…) don't have a `CadCommand` impl and are therefore absent from
autocomplete by design.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 22:45:54 +03:00
Hakan SevenandClaude Opus 4.7 4124cb2689 feat(commandline): full-history dropdown next to the input
Add a ▸/▾ toggle button on the right edge of the input row. Tapping
it pops up the full backlog of everything the command line has pushed
since startup — useful once the on-overlay rows have faded out.

The dropdown is a fixed-height scrollable panel rendered above the
input pill. `anchor_bottom` keeps the newest entry in view on open
and `opaque` blocks wheel events from bubbling through to the
viewport shader behind it (without it, scrolling the history zoomed
the drawing). Submitting a command closes the dropdown so the new
prompt is immediately visible.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 22:13:15 +03:00
Hakan SevenandClaude Opus 4.7 5a8c79deb5 feat(commandline): fade history entries off the overlay after 3 s
The overlay used to keep the last four history lines on screen
indefinitely, leaving stale command output cluttering the viewport.
Stamp each `HistoryEntry` with its `Instant` of insertion and filter
the rows shown on the overlay to entries younger than 3 s.

`CommandLine::has_visible_history()` gates a per-frame tick
subscription so the view re-renders and the entry disappears
on time. The subscription auto-stops once no entry is fresh enough,
keeping the idle CPU draw at zero (#18).

Full history stays in the backing `Vec` — the upcoming dropdown
button will surface it on demand.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 19:28:09 +03:00
Hakan SevenandClaude Opus 4.7 862e5aed31 feat(commandline): float as bottom-centre overlay over the viewport
The command-line panel used to consume a full-width row at the bottom
of the main column, taking vertical space away from the drawing area
even when no command was active. Move it onto the viewport stack as a
fixed-width 720 px pill aligned to the bottom-centre, with rounded
corners so it reads as a floating element. The status bar still owns
the very bottom of the window.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 19:23:19 +03:00
Hakan SevenandClaude Opus 4.7 979ca28632 fix(commandline): empty Space/Enter repeats the last command
Two follow-ups to the always-on keyboard work:

1. **Repeat last command on empty Submit.** Pressing Space (or Enter)
   after a command ended did nothing — the trailing-space submit path
   reached `CommandSubmit`, found no active command and no buffered
   text, and bailed. `CommandFinalize` already had the repeat-last
   shortcut via `tab.last_cmd`; mirror it inside `CommandSubmit` so
   the space-as-submit path picks it up too.

2. **Drop the per-event focus_cmd_input calls** added in the previous
   commit for `ViewportLeftPress`, `ViewportClick`, `TabNew`,
   `TabSwitch`, `RibbonSelectTab` and `RibbonToolClick`. The global
   keyboard subscription routes unhandled character / backspace
   events into the buffer regardless of focus, so the only places
   that still need an explicit refocus are `cmd_result.rs` (command
   lifecycle), `boot()` (initial caret), and the per-char append
   handlers (caret feedback while typing).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 19:10:01 +03:00
Hakan SevenandClaude Opus 4.7 39150d617f feat(commandline): always-on keyboard capture + space submits like enter
Two behaviour changes that bring the command line in line with
established CAD workflow:

1. **Always-on keyboard capture.** The input was blurred whenever no
   command was running, so typed letters went nowhere until the user
   clicked into the field. Replace the blur with a re-focus on every
   command lifecycle transition (cmd_result.rs) and on viewport / tab
   / ribbon clicks (update.rs), focus the input at startup, and add
   a global key subscription that catches any unhandled character key
   or backspace and routes it into the buffer via two new messages
   (CommandAppendChar / CommandBackspace) — so the command line keeps
   accumulating keystrokes even if focus parks somewhere not
   focusable. Removes the unused blur_cmd_input helper.

2. **Space submits.** Typing a space at the end of the input now
   submits the buffer the same way Enter does. Only exception: an
   active command that's currently collecting free-form prose
   (TEXT / MTEXT / DDEDIT body, ATTDEF prompt / default value,
   INSERT attribute value, ATTEDIT cell). Those advertise the new
   `CadCommand::wants_text_with_spaces()` flag so the Space character
   reaches the buffer as a literal.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 18:59:51 +03:00
Hakan SevenandClaude Opus 4.7 e55b280dda fix(paper): handle NaN sentinels when building hatch fill path
`HatchModel.boundary` stores multi-region hatches as a single Vec
with NaN-NaN sentinels separating each disconnected sub-path
(documented on the field). The paper-canvas hatch fill ignored the
sentinels and forwarded every vertex straight to the iced canvas
path builder. When a layout contained a multi-region hatch, lyon
received a polygon containing NaN coordinates and panicked with
`Tessellate path.: Internal(IncorrectActiveEdgeOrder(4))` from
inside `iced_wgpu::geometry::Frame::fill`, taking the whole app down
the first time Layout 1 painted.

Walk the boundary instead, opening a fresh sub-path on each sentinel
and closing whichever was active. Each region is now filled as its
own closed polygon. Reproduces with #22.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 18:22:52 +03:00
Hakan SevenandClaude Opus 4.7 a3e9b05130 fix(hatch): stop drawing ghost line between boundary polylines
The polyline arm of the hatch wire outline guarded its NaN separator
with `if count > 0 && pts.len() != start_idx`. `start_idx` was assigned
to `pts.len()` two lines earlier with nothing in between to grow `pts`,
so the inequality was structurally always false — the separator was
never emitted.

When a hatch carried more than one boundary path (multi-region hatch),
the renderer drew a straight segment from the last vertex of path A to
the first vertex of path B because the two polylines lived in `pts`
back-to-back with no NaN break. The result was a visible stray line
between hatch regions.

Switch the polyline arm to the same `if !pts.is_empty()` guard the
Line / CircularArc / EllipticArc arms use, and capture `start_idx`
*after* the separator so the closed-loop close-back logic still points
at this polyline's first real vertex.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 18:16:40 +03:00
Hakan Seven 89a01a8654 Revert "chore(github): add issue templates (bug / feature / question)"
This reverts commit 2e69d0a425.
2026-05-26 17:39:05 +03:00
Hakan SevenandClaude Opus 4.7 2e69d0a425 chore(github): add issue templates (bug / feature / question)
YAML issue forms with required fields so new reports arrive with the
context needed to act on them (version, OS, reproduction steps for
bugs; problem-before-solution for features). Auto-applies the matching
label.

blank_issues_enabled: false forces use of one of the three templates.
config.yml also surfaces the Patreon link as a contact option.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 17:37:03 +03:00
Hakan SevenandClaude Opus 4.7 2e8d7ba878 refactor(hatch): collapse repeated reflect_xy_point calls
The Hatch boundary-mirror transform called
`crate::scene::transform::reflect_xy_point(&mut x.x, &mut x.y, p1, p2)`
nine times with rustfmt expanding each long-path call into a six-line
block. Total: ~55 lines for nine reflects.

Add a local `use crate::scene::transform::reflect_xy_point as reflect;`
at the top of the closure so each reflect collapses to a single line.

-19 lines, no behavior change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 17:24:18 +03:00
Hakan SevenandClaude Opus 4.7 e66905e9b1 refactor(entities): Solid3D/Region/Body accessor trio in entities/solid3d.rs
The same three-arm `match entity { Solid3D | Region | Body }` block
appeared at five callsites — twice in tessellate.rs (point_of_reference,
fallback wires) and three times in scene/mod.rs (mesh tess dispatch,
apply_grip pre/post position).

Add three accessors in entities/solid3d.rs:

  point_of_reference(e) → Option<&Vector3>
  fallback_wires(e)     → Option<&[Wire]>
  tessellate_volume(e, color, facet_res) → Option<MeshLodSet>

Callsites shrink from 5–8 lines to one call. The apply_grip path
collapses two parallel pre/post matches plus the entity-list `matches!`
guard.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 17:22:02 +03:00
Hakan SevenandClaude Opus 4.7 5da32ff202 refactor(entities): collocate ui_name + dxf_name into entities/names.rs
Two near-identical match-on-EntityType tables lived far apart:
scene/properties.rs::entity_type_name returned mixed-case UI labels;
app/commands.rs::entity_type_name returned uppercase DXF record names.
Both enumerated ~30 variants; adding a new entity meant remembering to
touch two files in two crates' worth of source layout.

Move both into a single entities/names.rs as `ui_name()` and
`dxf_name()`. Adjacent so a new variant gets both labels added at once;
function names make the intended audience explicit at the callsite.

scene/properties.rs and app/commands.rs each lose their local match
plus one re-export, gaining a `crate::entities::names::*` call.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 17:18:47 +03:00
Hakan SevenandClaude Opus 4.7 9b5073752d refactor(entities): collapse trait wrapper boilerplate via macro
Every entity file ended with the same three-trait wrapper block:

    impl Grippable for T {
        fn grips(&self) -> Vec<GripDef> { grips(self) }
        fn apply_grip(&mut self, ...) { apply_grip(self, ...) }
    }
    impl PropertyEditable for T { ... }
    impl Transformable for T { ... }

— 23 lines of pure delegation around five same-named free functions.

Replace with `crate::impl_entity_basics!(T);` (a macro in
entities/traits.rs that expands to the three trait impls).

Applied where the file already follows the free-fn delegation
convention (Arc, Circle, Ellipse, Insert, Leader, Line, LwPolyline,
MultiLeader, Ole2Frame, Point, Spline, Viewport). Files that inlined
the trait method bodies directly (Ray, XLine, Shape, Solid, Tolerance,
MLine, Mesh, Solid3D/Region/Body, Table, RasterImage, Wipeout,
Underlay, Hatch, Polyline, Polyline2D, Polyline3D, Dimension) left
alone — they already skip the indirection. Text/MText/Attribute use
the with-text-style flavour and will move once that variant is needed
beyond the existing two manual impls.

A second macro `impl_entity_basics_with_text_styles!` is included for
the text-like flavour but no entity uses it yet.

Net: -204 lines.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 17:16:06 +03:00