Commit Graph
1688 Commits
Author SHA1 Message Date
Hakan SevenandClaude Opus 5.5 29c0d1df9a perf(blocks): keep repeated block copies as one stub wire each
Every further reference to an already expanded block (a translated copy
of the same definition, at the top level or nested) was materialised as
a full copy of every wire, although the GPU already draws those copies
from one shared source with a per-copy offset. Such a copy is now a
single stub wire holding the shared prototype and its offset (stubs nest
inside prototypes, so nested repeats collapse too).

- The block renderer expands stubs into instance references while it
  groups by source, so drawing is unchanged.
- Interaction queries expand only the stubs near the query, so snapping,
  picking, window and crossing selection see each copy's geometry.
- Plot, boundary detection, viewport projection and entity_wires() get
  fully expanded wires; count and reference-edit recolouring expand the
  set while they are active.
- OCS_NO_BLOCK_STUBS turns it off.

A drawing with 480 references to a block of 100 nested 50-line blocks
(2.4M lines) now opens with 11k wires instead of 2.4M: peak memory
5.4 GB -> 0.37 GB, wire assembly 2.4 s -> 36 ms, interaction index
3.7 s -> 23 ms. The viewport, a plotted PDF, snaps and selections are
identical with and without stubs, and on five real drawings the view
renders the same.

Refs #1698, #1542

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-13 14:06:27 +03:00
Karim Jerbi 8d287decd8 fix(explode): use codec block explosion for attributes and arrays
Delegate INSERT and MINSERT explosion to the document-aware codec implementation, removing duplicate attribute and base-point handling. Update workspace, kernel and graph pins together and cover rotated arrays with a nonzero base.
2026-10-13 11:10:11 +01:00
Karim Jerbi 9c08e8a3ed fix(blocks): prevent circular block nesting and cut render cycles
Introduce would_create_cycle in block_service to prevent recursive block definitions with case-insensitive matching across block records. Enforce cycle refusal at INSERT commit in BEDIT and during block redefinition (redefine_block_raw) with a shared error message. Ensure render-time recursion truncates safely with a one-time session warning on cyclic documents.
2026-10-13 11:10:11 +01:00
Karim Jerbi 59bb6faabd fix(blocks): enforce constant attributes in ATTEDIT and restore ATTDEFs on explode
Enforce constant attribute flag in ATTEDIT dialog by presenting constant attributes as disabled and ignoring modifications. When exploding an insert, restore block-definition attribute definitions as transformed ATTDEF entities while dropping instance attribute values.
2026-10-13 11:10:11 +01:00
Hakan SevenandClaude Opus 5.5 df22824fbe perf(scene): keep resident wires once, not again in the memo
The resident tessellation memo held its own copy of every entity's wires
next to the assembled resident set. Runs now stay only in the set: a memo
entry whose wires the set holds just marks them valid (InSet) and reads
them back through a weak link with the set's layout ranges, so the set
remains uniquely owned for in-place patches. A resident build moves new
tessellations straight into the set (Pending until it is bound); hidden
entities copy their run out before the patch blanks it, and an abandoned
patch hands its half-taken set back for the full build that follows.
Builds whose runs are recoloured afterwards (reference edit, count) keep
owned copies as before.

On a drawing with 2.4M nested block lines: wire assembly 3.7s -> 2.3s,
memory after it 4.6 GB -> 3.8 GB.

Refs #1542, #1698

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-13 12:55:00 +03:00
Hakan SevenandClaude Opus 5.5 9cc890c911 fix(grips): drag model grips in the drawing plane, not onto themselves
In a tilted 3D view a grip followed the cursor onto the screen plane
through the orbit target, so a circle or arc jumped to another radius and
a polyline vertex left its plane. A model-space grip now moves in the UCS
XY plane at its own elevation (the screen plane only when seen edge-on).
A solid stays drawn while its grip moves, and its own edges were still
snap candidates: a cone's base radius landed on its own side. The edited
entities are left out of the grip's snap set.

Fixes #1033

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-13 12:12:27 +03:00
Hakan Seven 798014c773 Merge pull request #1741 from ramox81/codex/view-background-followups 2026-10-13 11:27:33 +03:00
Hakan Seven 7095802e8e Merge pull request #1743 from ramox81/codex/layout-create-viewport 2026-10-13 11:27:33 +03:00
ramox81andClaude Opus 5.5 bd80d96ce2 Layouts: the first-open viewport is on and drawn at once
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-13 11:19:35 +03:00
Hakan SevenandClaude Opus 5.5 4762987fef docs(scene): keep the unit-factor comment on its own function
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-13 11:11:36 +03:00
ramox81andClaude Opus 5.5 e3a0ff1bad Layouts: a viewport on first opening (LAYOUTCREATEVIEWPORT)
A layout opened for the first time - one whose sheet viewport had to be
made, or one created in this session - gets a viewport centred on the
printable area, 0.8 of its width and height, zoomed to the model's
extents, while LAYOUTCREATEVIEWPORT is on. The profile variable takes
ON/OFF or 1/0 and is kept with the settings.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-13 11:09:03 +03:00
ramox81andClaude Opus 5.5 2a808c4526 Background dialog preview: mirrored images, device pixels; faint inactive values
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-13 11:05:01 +03:00
Hakan SevenandClaude Opus 5.5 5d0390bcdf fix(annotative): start new drawings at the unit-aware annotation scale
A new drawing is metric with metre insertion units, but its annotation
multiplier stayed at Scene::new()'s unitless 1 while opened files and
CANNOSCALE convert paper millimetres into drawing units (0.001 at 1:1).
Annotative text and dimensions were drawn 1000x too large until the
first scale change, then shrank to their real size and looked gone,
also after switching back to 1:1.

One helper now derives the multiplier from the header; new drawings,
file open and every INSUNITS change (UNITS, the units popup, DWGUNITS,
SETVAR) go through it.

Fixes #1674

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-13 10:41:00 +03:00
Hakan SevenandClaude Opus 5.5 d1a2e9db64 fix(render): draw transparent hatches over the lines behind them
Hatches are drawn before the wires and write depth, so a transparent
hatch above lines in draw order still hid them through the depth test.
Transparent hatches now go to their own renderer drawn after the wires:
the lines behind show through the blend and the lines in front still
win the depth test.

Fixes #1720

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-13 10:04:25 +03:00
ramox81andClaude Opus 5.5 064da8829e View backgrounds: image colours as stored, viewport style name, adjust automation
- The background image texture is not an sRGB view: the image is drawn with
  its own colours, as the solid and gradient colours are.
- An entered paper-space viewport shows its named visual style (Realistic,
  ...) in the render-mode picker, and a mode picked there is recorded as the
  viewport's visual style.
- The view_background automation action takes offset=x,y and scale=x,y.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-13 09:44:49 +03:00
Karim Jerbi b5b505ee9d fix(blocks): cross-document entity copies must not share extension dictionaries
add_entity_clone reset handle, owner and mode but kept the source extension dictionary handle, so a copy claimed a dictionary its document does not own. The clone now gets no extension dictionary, like baked and transformed-block children. Failing-first test included.
2026-10-13 07:05:54 +01:00
Hakan Seven 03445a540d Merge pull request #1722 from ramox81/codex/view-background
# Conflicts:
#	locales/ar-SA/opencadstudio.ftl
#	locales/bg-BG/opencadstudio.ftl
#	locales/cs-CZ/opencadstudio.ftl
#	locales/de-DE/opencadstudio.ftl
#	locales/el-GR/opencadstudio.ftl
#	locales/en-US/opencadstudio.ftl
#	locales/es-ES/opencadstudio.ftl
#	locales/fi-FI/opencadstudio.ftl
#	locales/fr-FR/opencadstudio.ftl
#	locales/hi-IN/opencadstudio.ftl
#	locales/hu-HU/opencadstudio.ftl
#	locales/it-IT/opencadstudio.ftl
#	locales/ja-JP/opencadstudio.ftl
#	locales/ko-KR/opencadstudio.ftl
#	locales/lo-LA/opencadstudio.ftl
#	locales/nl-NL/opencadstudio.ftl
#	locales/pl-PL/opencadstudio.ftl
#	locales/pt-BR/opencadstudio.ftl
#	locales/ru-RU/opencadstudio.ftl
#	locales/tr-TR/opencadstudio.ftl
#	locales/zh-CN/opencadstudio.ftl
#	locales/zh-TW/opencadstudio.ftl
2026-10-13 07:44:01 +03:00
Hakan Seven a8af415976 Merge pull request #1736 from turbuczn/feature/selection-filter-list 2026-10-13 07:39:32 +03:00
Hakan Seven 5581bbeedb Merge pull request #1738 from gianlucafiore/fix/block-wipeout-hatch-order 2026-10-13 07:32:50 +03:00
Hakan SevenandClaude Opus 5.5 710a837a9b fix(layout): place a window-plot sheet around its plot window
A layout plotted by window drew its sheet at the paper-space origin, so
the title block sat on the grey background away from the paper. The
sheet now centres on the plot window, or sits at the plot offset from it
when the plot is not centred.

Bump codec 8aa574a (polyline vertices in owned-handle order, which fixes
self-crossing viewport clip boundaries), kernel d79bb30, graph 4810ee0.

Fixes #1678

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-13 06:50:19 +03:00
gianlucafiore fe0edb619f fix: invalidate wipeout render cache on INSERT copy and grip moves 2026-10-12 18:14:13 -03:00
gianlucafiore 93a54d66e3 fix: pass HatchModel references to per-mask GPU builder 2026-10-12 17:42:54 -03:00
gianlucafiore 72e97c6f60 fix: render each transformed wipeout/hatch mask with own boundary instead of sharing source-id GPU batches 2026-10-12 17:39:43 -03:00
gianlucafiore 9e8d07270e fix: avoid translated prototype reuse for wipeout-containing block inserts 2026-10-12 17:30:19 -03:00
turbuczn 09cf99f85c feat: Add advanced selection filter 2026-10-12 22:27:19 +02:00
Hakan SevenandClaude Opus 5.5 390e12169a fix(dwg): keep OCS-saved drawings readable by strict readers
Re-authored constraint-group geometry dependencies carried an empty
persistent subentity id, which crashed strict readers loading the file;
they now name the whole curve with a single-edge id.

Bump codec bc350f2 (declare classes of objects added to pre-R2004
drawings), kernel e692b97, graph 37d38eb.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-12 23:24:22 +03:00
gianlucafiore 8a65f2e8be fix: preserve block-local draw order for ordinary line members alongside wipeouts 2026-10-12 17:16:52 -03:00
Hakan SevenandClaude Opus 5.5 ae56fab62a Cargo: codec 3ec70a2, kernel e710727, graph 338a973
The codec keeps dynamic-block, associative and material records intact on
DWG save (strict readers rejected re-saved drawings at BLOCKSCALEACTION,
associative action bodies and MATERIAL records). A checker material
texture no longer rewrites the native map source, so a map with a
procedural texture counts as content whatever its source says.

Valuable parts of #1671 (with opencadcodec#100): measured on 235 sample
drawings re-saved after an edit, a strict reader now rejects 4 instead
of 7; the remaining 4 are R14/R2000 tables, which the PR codec also
fails.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-12 21:22:13 +03:00
ramox81andClaude Opus 5.5 9ef8f4bedf View backgrounds: reference blend, image adjust, layouts, cleanup, style name
- Gradients: the middle colour sits (horizon - 0.5) viewport widths from the
  centre and blends over height viewport heights each side; two colours
  blend over a viewport height each side.
- Image backgrounds: offset in device pixels (right, down), negative scale
  mirrors, fitted with aspect ratio covers the viewport; the dialog keeps an
  image's offset and scale.
- Layouts: a model-space view restored on a sheet asks for the viewport,
  enters it and gives it the view's camera, background and visual style;
  inside a viewport the view goes there; a paper-space view's background is
  kept on the sheet viewport but not drawn; floating viewports draw their
  background.
- Background objects no view or viewport uses any more are erased; a view
  without a background restored where the viewport already is leaves the
  viewport's background.
- The viewport's named visual style (Realistic, ...) shows by name and the
  render-mode picker records its style on the viewport record.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-12 17:31:01 +03:00
ramox81andClaude Opus 5.5 1cf13971dc Keep a centred image background at its device pixel size
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-12 12:19:03 +03:00
ramox81andClaude Opus 5.5 7a0e08129f Draw a centred image background at its pixel size
An image background not fitted to the screen keeps its own pixel size
(times its scale), centred on the viewport and moved by its offset, as
the reference draws it; tiling repeats it from there.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-12 12:04:52 +03:00
Hakan SevenandClaude Opus 5.5 b94bb18a44 fix: catalog every ported web string in all locales; drop tests of unported STB parsing
- Translate the strings the #1717 port cataloged in English only
  (CSV picker, plot-preview task, extraction cap, shared-copy warning)
  for every locale, and the whole ported web set for lo-LA, which the
  contributor branch predates.
- hu-HU carried twelve keys twice after the port's merge; keep main's.
- The STB container/refusal tests exercised parsing this port did not
  bring over; the preview-clamp test now only pins the upper bound, since
  main's raster path already clamps preview pages further.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-12 12:02:06 +03:00
Hakan SevenandClaude Opus 5.5 ce6bd2c73e fix: bound the image decode transient and keep native picks on disk
Follow-up to the #1717 port:

- The downsample tier holds the decoded RGBA, the resize's vertical-pass
  intermediate (width x new height in RGBA f32) and the output at once;
  that sum now has to fit the decode budget, or the image is refused up
  front instead of overshooting it several times over. The native
  budget drops from 4 GiB to 2 GiB.
- A native IMAGE pick decodes from the picked file instead of copying
  its bytes into the session registry, so an edited file reloads and the
  bytes are not held for the session. The web keeps registering them.
- Reword a plot-preview comment.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-12 12:02:06 +03:00
Afonsop 1c0963b68d fix: close the adversarial findings on the save/paste wave
Ported from da950460 (#1717).

In-place save:
- A stale retained handle could receive bytes serialized for a
  DIFFERENT format (Save As .dwg->.dxf whose re-pick failed wrote DXF
  bytes through the .dwg handle) or silently save in place while the
  user was told a copy was downloading. The serialized format now
  travels to completion (tab-keyed pending map), the write requires a
  matching handle extension AND a live tab, and a failed re-pick
  drops the old handle before the download fallback.
- Discarding an unsaved tab leaked its handle, and a serialization
  still in flight could then overwrite the real file the user just
  declined to save. Discard drops the handle; a tab-gone completion
  downloads instead of writing.
- Overlapping saves had no guard: a wasm in-flight slot refuses a
  second save with the same localized message, cleared on every exit
  path.

Paste: pasted_image_name built its timestamp from Date::new(0) — a
CONSTANT name, so every paste overwrote every earlier paste's image.
Real clock, milliseconds, and a collision-suffixed unique name now.

Capture: a web capture in a hidden tab, or one with nothing redrawing,
could hold the automation slot forever. It fails fast when the page is
hidden and gives up after 5 s, on main's frame-parked capture.

The XREF manager reports registry-loaded xrefs with their size on the
web instead of NotFound.

Left out: the always-warm print snapshot (a frames subscription that
emits a message on every frame keeps the app redrawing at the display
rate), so the browser-menu print wiring from the previous commit goes
too — its beforeprint readback printed blank pages on WebGL2. The
merge-residue hunks (re-added XREF bind tests, snap endpoint map and
3D-snap gate) belong to code main already has in its own form.

(cherry picked from commit da950460)
2026-10-12 12:02:05 +03:00
Afonsop 6af8d8ba8c feat: restore-all session recovery, ?file= deep links, offline PWA
Session recovery: more than one pending snapshot adds a 'Restore all'
button to the Recover Drawing modal — every snapshot goes through the
exact same guarded path as a single restore (deleted only after its
own open succeeds). The open-tab list and active tab persist to
localStorage on web (open/save/close) and the remembered tab is
re-selected once a restore batch settles; the modal notes how many
drawings the session had. Nothing is auto-opened.

Deep links: ?file=<url|name> opens a drawing at boot. http(s) URLs
must end in .dwg/.dxf and are fetched with the same streaming,
64 MB-capped, 8 s-deadline reader the remote images use, then go
through the guarded open queue; cross-origin opens announce the
origin first. Bare names resolve from the browser's recent storage,
with a hint when absent. Everything else (file:, javascript:, drive
letters, non-drawing tails) is refused with a console note.

Offline PWA: the app was installable (manifest) but downloaded its
55 MB bundle on every visit. A cache-first service worker now serves
the whole bundle from Cache Storage after one online visit —
navigations network-first so updates land, hashed assets
stale-while-revalidate, non-GET/cross-origin passthrough, old caches
cleaned on activate, and the waiting-worker update flow documented.
The Pages workflow stamps the cache version from the bundle hash and
fails if sw.js is missing or unstamped.

(cherry picked from commit 5f9b345e)

Ported onto main (#1717 review): the cache-version stamp moved from the
skipped build-web.sh wrapper into the Pages workflow; the tab-close hook
lands in main's on_tab_close, which also cancels pinned control requests.
2026-10-12 12:02:05 +03:00
Afonsop 2d2444fcfa fix: close the review findings on the issue wave
Ported from d719fb77 (#1717); the hatch draw-order, PDF and
recovery-settle hunks stay out (main owns those paths).

- La16 was still billed at 4 B/px: the no-conversion special case
  keyed on native_bpp == 4, which La16 also is — but only Rgba8 is a
  real move in into_rgba8, so La16's decode peak is 8. The case now
  keys on the color type, and a table test pins every layout's peak
  (plus GIF's screen+frame 8) so the accounting can't silently drift.
- json_values_match: an integer beyond f64 precision no longer matches
  a float it merely rounds onto; a mixed pair matches only when the
  float is whole and converts back to exactly that integer (also the
  fuzz finding of 9262d63f).
- ocs-control.js resolves `ready` with the API object instead of
  window.ocsControl, which is still undefined when the bridge already
  exists as the script runs (built before the first settle, avoiding
  the temporal-dead-zone throw of a later const); DATALINK on web says
  the typed path is superseded by the picker instead of silently
  discarding it.

(cherry picked from commit d719fb77)
2026-10-12 12:02:05 +03:00
Afonsop db8db14006 feat: web automation wrapper (#1349), DATALINK table on web, System theme; honest decode accounting
Ported from 2c38de53 (#1717). Main already carries the hatch PDF draw
order fix (#1345), the numeric set_properties compare (#1348) and the
web open from bytes, so those parts stay out; what remains:

#1349 — window.ocsControl.ready/request promise wrapper with an
ocs-ready event (web/ocs-control.js, loaded by the web page), and the
startup donation prompt is suppressed once a control request is seen
(opt-in pre-boot via localStorage["opencadstudio.automation"] = "1").
docs/automation/web.md documents both. The control protocol stays at 1.

DATALINK CSV-to-table works on web (picker + bytes through the session
registry — was std::fs, silently dead); COLORSCHEME accepts System
(prefers-color-scheme on web, AppsUseLightTheme on Windows).

Decode accounting, made honest per the fix-of-fix review: into_rgba8
allocates the RGBA destination while the native buffer is alive for
every non-Rgba8 layout (L8 5, La8 6, Rgb8 7, L16 6, La16 8 B/px
peaks — the formula now says so), GIF bills the screen+frame
coexistence at 8 B/px, and TIFF drops the 3x multipliers both ways —
tiff 0.11 splits the given budget between frame and scratch (a total
ceiling), so the old double-billing refused honest 16-bit GeoTIFFs
~2.25x early while admitting hostile chunk transients at 3x.

(cherry picked from commit 2c38de53)
2026-10-12 12:02:05 +03:00
Afonsop f015c3b5fa fix: native-bpp decode accounting, GIF frame bombs, streaming remote bodies
A second adversarial round (with empirical reproduction against the
vendored image 0.25) found the budget contract still had holes:

- The probe billed 4 bytes/px, but 16-bit formats allocate their
  native layout during decode (8/px for RGBA16 PNG; TIFF stacks ~3x
  codec scratch on top). In-budget 16-bit images were falsely refused
  with self-contradictory notes, and the decode transient could exceed
  the budget the tiers claimed to enforce. The probe now reads the
  color type alongside the dimensions and bills the real peak (native
  layout + RGBA where they coexist, plus the TIFF codec factor), so
  refusals are honest and per-platform-correct, and the TIFF allowance
  follows the same accounting.
- GIF: the probe reads the LOGICAL SCREEN, but a crafted frame may
  declare itself arbitrarily larger (empirically: a 10x10 probed
  screen with an 8192x8192 frame — 320 MB and 29 s from a
  probe-claimed-400-byte image). The GIF frame buffer is now capped
  to the probed size plus slack; frame bombs refuse like any other
  oversize.
- Remote bodies on web streamed incrementally with the 64 MB cap
  enforced DURING the read (array_buffer() materialized a hostile
  chunked reply in full before any check could fire), aborting and
  cancelling the reader the moment the cap is crossed.
- Degenerate raster entities (zero/non-finite size or vectors) no
  longer produce NaN placement math that aborted whole PDF exports —
  they resolve to the broken-reference placeholder.
- Same-name resolution now has a deterministic winner (newest
  registration, via insertion order) instead of HashMap iteration
  order; URL query strings no longer break by-name matching; the
  image pick matches its refusal note by the quoted file name
  (substring collisions like a.tif/2011.tif attributed errors to the
  wrong file); a dropped remote outcome at the queue cap triggers one
  unconditional catch-up re-resolution instead of waiting for another
  event; the unknown-size refusal says so instead of printing 0x0;
  and the build script fails cleanly when there are no fonts to
  verify.

(cherry picked from commit c6db94e8)

Ported onto main (#1717 review): the build-web.sh hunk stays out with
the wrapper; the new messages are cataloged (image refusal under the
file group it is looked up in, plus the extraction cap, shared-copy
warning and plot-preview task title that were missing), and the web
SAVEALL failure reuses the existing SAVEALL message.
2026-10-12 12:02:05 +03:00
Afonsop a10d9c6f89 fix: trust the probe for over-budget images; TIFF allowance
Review of the image wave (with empirical reproduction against the
vendored image 0.25) found the budget contract did not hold across
formats: the decoder's Limits reserve checks NATIVE bytes-per-pixel,
so a grayscale/RGB JPEG/PNG/BMP over the RGBA budget decoded anyway
and into_rgba8() then ballooned to up to 4x it — the tab death the
budgets exist to prevent. The probed dimensions already prove an
OverBudget image cannot fit, so it is now refused without decoding,
removing all dependence on per-decoder limit fidelity.

Conversely, image's TIFF codec stacks ~3x scratch buffers on top of
the reserve, so an in-budget TIFF (the flagship GIS scenario) was
refused with a self-contradictory note ('400 MB, over the 512 MB
limit'); the decoder allowance for TIFF is now 3x while tier math
stays on the real budget.

Also: the refusal message saturates instead of overflowing on crafted
headers, the image pick only matches the refusal note naming ITS file
(peek, not drain — other references' notes survive for their own
drains), and the Georgian Supplement range ends at U+2D2F (U+2D30.. is
Tifinagh, which ships no font).

(cherry picked from commit 58d3789bcf)

Ported onto main (#1717 review): the build-web.sh wrapper and its
pages/README/Trunk wiring stay out — it works around a local Windows
trunk dist lock; the Linux Pages build is unaffected.
2026-10-12 12:02:05 +03:00
Afonsop 93e97bdf18 feat: bounded huge-image decoding, webp/gif, remote rasters, Georgian/Armenian fonts
Giant rasters (500 MB aerial/GIS TIFFs) decoded whole: multiple GB of
RGBA on a wasm heap is tab death, and even desktop silently failed
past the image crate's 512 MiB default allocation cap. Decoding now
probes the header for dimensions first and runs in tiers — full decode
under a per-target budget (512 MB wasm / 4 GB native, enforced through
the decoder's allocation limits so oversize fails fast instead of
ballooning), Lanczos3 downsample to a display cap (4096² wasm /
8192² native) when the decode fits but the buffer is oversized, and a
clear localized refusal naming the dimensions and the limit when it
cannot. The probed source dimensions stay the entity's sizing truth,
so a downsampled buffer never shrinks the on-paper footprint; PDF
export self-corrects (effective DPI derives from placement × stored
pixels). Refusals surface once per image through the pick, open, and
drop/re-resolve paths.

Decode coverage gains webp and gif (pure Rust in image 0.25 — no C,
no lockfile churn), with the attach picker and drop filters matching.

Remote http(s) raster references now fetch on web: CORS-classified
failures name CORS, HTTP errors name the status, bodies over 64 MB and
8 s deadlines are bounded, and fetched bytes join the session source
registry (so ARCHIVE can package them and re-resolution reads cache).

Georgian and Armenian Noto subsets ship (generation blocks included),
so those scripts render instead of skipping; scripts still without
subsets remain silently skipped, unchanged.

(cherry picked from commit ca0d7b96)

Ported onto main (#1717 review): Armenian and Georgian take the script
indices after Lao, which main added meanwhile.
2026-10-12 12:02:05 +03:00
Afonsop 61c32c7f74 web: XREFs, data links & extraction, ARCHIVE transmittals, worker-side saves
The last four big web-vs-desktop gaps.

XREFs: XATTACH reads the picked drawing's bytes and registers them
name-keyed (the image/PDF-underlay pattern), pre-parses on the web
worker, and the commit merge produces the same xref block flow as
native. Opened drawings resolve xrefs from the registry (trailing-name
match) before cache build; unresolved references produce ONE warning
listing the names and the fix — drop the referenced files on the page
(drops and picker opens now register their bytes) and reopen or
XRELOAD. XRELOAD works off the registry on unsaved drawings too.

Data Link & Extraction: spreadsheet links read picked xlsx/csv bytes
(calamine from slices / CSV from string) through a session registry,
so preview, refresh, and table-insert work on web; extraction takes
external drawings via a multi-file picker parsed on the worker, and
outputs CSV/TXT/.dxex via download_bytes. The disabled 'Add folder'
button explains why.

ARCHIVE/ETRANSMIT: assembles the drawing plus registered images, PDF
underlays, and session plot tables into a ZIP delivered as
{stem}_transmit.zip; skipped dependencies are reported by name.

Save/autosave off the UI thread: the parse worker gains a serialize
action (protocol v5) — the document is prepared once on the main
thread (clone, styles, dimension bake), then the DWG/DXF writer pass,
the dominant cost, runs on the worker; any worker failure falls back
to inline serialization with the identical shared implementation.
The save path also clears the dirty flag only when the tab didn't
change mid-write, and the runtime-fields sidecar now carries
graphic_data, fixing a pre-existing loss of raw DWG round-trip bytes
on web-opened documents.

(cherry picked from commit 2e100634)

Ported onto main (#1717 review): the new ocs_save crate builds on the
pinned codec; the hand-written ZIP writer is replaced by the zip crate
already in the tree; ARCHIVE packs registered xref bytes too and
serializes document_for_save() like a web Save; the web save prepares
document_for_save() (prepare_document_for_save now takes the copy by
value, so no second clone); the wasm resolve_path consults the xref
registry, so every reference lookup — not just resolve — finds dropped
files; XRELOAD keeps main's undo, unload and stale-baseline handling;
native data extraction streams the source drawings one at a time.
2026-10-12 12:02:04 +03:00
Afonsop 9cd898a17a web: raster images, WBLOCK/SAVEALL/SCRIPT/CTB/LAYTRANS/INSERT, filled TTF text
A fresh web-vs-desktop audit found the remaining gaps; this closes the
top ten.

Raster images: IMAGE attach read the picked file via image::open on a
name-only path (dead on wasm), and raster references inside opened
drawings decoded the same way. Picked bytes are now registered in a
name-keyed store (the PDF-underlay pattern), decoding runs from memory,
and dropped .png/.jpg/.bmp/.tif files register by name so unresolved
references in opened drawings re-resolve and render (and export to PDF).

Command gaps fixed with the bytes-at-pick / download_bytes pattern:
- WBLOCK now downloads the extracted block instead of failing on fs.
- SAVEALL saves every dirty tab instead of printing a no-op message.
- SCRIPT picks and reads the .scr file through the browser picker.
- CENTERLTYPEFILE picks the .lin/.dnl file and populates from text.
- Custom .ctb tables picked on web now parse, register for the
  session, appear in the plot dialog list, and apply (load_named
  previously returned builtin-only on wasm).
- LAYTRANS 'Load standards file' parses the picked drawing on the
  existing web worker.
- INSERT 'from file' in the block palette parses picked bytes and
  feeds the shared import path.

Text fidelity: TTF-named styles rendered as thin stroke glyphs on web
because the font database was empty. Fetched Noto faces are now
registered into it with a family alias table (Arial, Times New Roman,
Cambria, ... -> Noto Sans/Serif), so those styles render filled, shape,
export to PDF as vector fills, and appear in the style picker.
ViewCube labels request semibold again on web.

docs/native-vs-web.md: the hatch and font rows were stale (both work on
web now), the GPU row claimed WebGPU is used where available (the
backend is pinned to OpenGL), and the parallelism row missed the parse
worker — all corrected.

(cherry picked from commit c643a10e)

Ported onto main (#1717 review): conflicts resolved toward main — the
font-file lookup, PDF searchable text, purge service, block palette and
plot-style copy stay as they are on main; the web pieces (font registry
and aliases, session plot styles, SCRIPT/SAVEALL/CENTERLTYPEFILE picks,
block-from-file on the web) are layered on top.
2026-10-12 12:02:04 +03:00
Afonsop 112060e370 web: harden plot preview after adversarial review
Three reviewers (security, correctness, performance) converged on:

- Download PDF died on the second click: Closure::once_into_js throws
  when invoked twice. The handler is now a plain FnMut kept for the
  overlay's lifetime.
- Rasterisation was bounded by page count, not pixels: 12 A0 sheets at
  150 DPI peaked near 1.8 GB of RGBA plus ~1.7 GB of decoded images,
  and a drawing declaring absurd paper sizes made the rasteriser
  saturate its u16 dimensions into a ~17 GB allocation that aborts the
  tab. Preview now computes a uniform DPI under a 64-Mpx total budget
  (floor 24 DPI) from the page tree before allocating, with a per-page
  4096-px edge clamp.
- Overlays stacked when previewing twice and their blob URLs were only
  revoked via Close. The overlay is now single-instance (id-tagged);
  a replacement or Close revokes every URL through a shared registry.
- emit_image resampled before probing the XObject cache, so a title
  block placed on 12 layouts ran 12 Lanczos passes to embed once.
  The cache is now probed with the target dimensions first.
- Preview downloads are named {stem}_preview.pdf instead of a constant
  plot_preview.pdf, and the page label says 'first N of M' when the
  preview is capped while the PDF keeps every page.

(cherry picked from commit 2a05479d69)
2026-10-12 12:02:04 +03:00
Afonsop 4d1f9d5960 web: render plot preview in-page instead of a new tab
A plot preview should show the plotted result on screen instead of
producing a file. The web build now does: the plotted PDF bytes
are rasterised with the hayro pipeline already used for PDF underlays
(150 DPI, first 12 pages) and shown in an in-page overlay with
Download-PDF and Close actions.

The previous strategy opened a new tab via window.open from the async
plot task; browsers withhold popups without a live user gesture, and
embedded hosts ship no PDF viewer plugin at all — so the fallback
download fired and preview behaved like an export. Plain PNG <img>
elements need neither permission.

(cherry picked from commit 8722d84910)
2026-10-12 12:02:04 +03:00
ramox81andClaude Opus 5.5 46daf03bc9 Draw gradient backgrounds as the reference does
The rotation is stored in degrees and turns the bottom-to-top direction
counter-clockwise, measured in pixels; a two-colour gradient blends from
the bottom colour to the top one across the whole viewport.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-12 11:37:53 +03:00
ramox81andClaude Opus 5.5 8055fa710d Restore a view's visual style; no background in 2D wireframe
Restoring a named view gives the model viewport the view's visual style
(the closest render mode), and a 2D wireframe viewport shows no
background, as in the reference.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-12 11:24:07 +03:00
ramox81andClaude Opus 5.5 c8167c54ee -VIEW sEttings Background dialog and view backgrounds
Color, Gradient and Image at the background prompt open a Background
dialog (None / Solid / Gradient / Image, preview, solid colour, two- or
three-colour gradient with rotation, image file with position and
scale). Apply links the view to a new background object in
ACAD_BACKGROUND; restoring a view gives the model viewport a copy of
it (or none), and saving a view copies the viewport's background.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-12 10:50:00 +03:00
Karim Jerbi 2b50b6c858 fix(blocks): one block definition service for delete, rename and purge on every path
new src/scene/block_service.rs (references, delete, rename, purge candidates, name validation, one error type and message). The command-line RENAME, palette Remove, plugin API, control/MCP block_delete and PURGE all use it. Layout, xref, dynamic-block, anonymous and open-in-editor records are protected; PURGE removes an anonymous block only when nothing references it. PROTOCOL CHANGE: control/MCP block_delete refuses a referenced block unless force=true (erases referencing INSERTs in one undo step). New REST route DELETE /api/v1/blocks/{name}?force=true. MCP tool digest re-pinned. Undo labels are Delete block X, Rename block X to Y and Purge.
2026-10-12 07:25:11 +01:00
Karim Jerbi 5dfa41c032 fix(blocks): one base point convention for creation, redefine, insert, explode, XCLIP and REFEDIT
world = insert_point + Rot * Scale * (P - base_point). Attribute placement, EXPLODE, XCLIP storage, REFEDIT's working copy, the UCS reframe of attributes and the dynamic-block visibility grip ignored the base point and were offset by Rot*Scale*base. They now share insert_transform_with_base. Creation stores a ZERO base and redefine resets it; a no-op redefine moves nothing; the Block Definition dialog prefills the base from the definition. XCLIP filters stored by older builds keep their old displacement.
2026-10-12 04:35:19 +01:00
Karim Jerbi a456367b26 fix(save): baked dimension blocks and block clones must not share extension dictionaries
bake_dimension_blocks copied the dimension's whole EntityCommon, including its extension dictionary handle, into every baked child, so many entities claimed one dictionary and file validation reported an error per entity. Baked children now get no extension dictionary. The same defect is fixed in define_transformed_block and the cross-document paste clone. Each has a test.
2026-10-12 04:35:19 +01:00