PASTESPEC offers the formats on the clipboard: Objects and Block for
objects copied in the application (the PASTECLIP and PASTEBLOCK paths),
and Text for the system clipboard's text, placed as multi-line text in
the current text style with its lines as paragraphs.
Refs #645
Co-Authored-By: Claude Opus 5.5 <[email protected]>
DVIEW (alias DV) offers TWist and Undo. TWist rolls the active view
or viewport camera to the typed angle; Undo steps back through the
twists made in the session. The new alias reaches existing profiles
through the alias migration table.
Refs #638
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A non-associative hatch's boundary vertex grips now offer Add Vertex and
Remove Vertex next to Stretch, on polyline boundaries and on boundaries
made of line edges alike. Add Vertex inserts the vertex after the picked
one, halfway along the next segment (on the arc for a bulged polyline
segment, splitting its bulge), and lets it be placed like any grip.
Remove Vertex drops it while keeping at least a triangle.
Line-edge boundaries used to show two grips at every corner (one line's
end, the next one's start), so dragging one opened the loop; each corner
is now one grip that moves both ends.
The automation grip_menu_pick action chooses an entry of the open grip
menu, so these edits can be scripted and checked.
Fixes#1058
Co-Authored-By: Claude Opus 5.5 <[email protected]>
PUBLISH now runs what PRINTALL does: pick the layouts, each plotted with
its own page setup, into one multi-page PDF or to the printer.
Refs #1124
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Typing <angle at a point prompt (e.g. <30) now fixes the direction of
the next point from the last one until a point is placed: the cursor
moves along that direction, and a bare number entered next is the
distance along it. The angle is read in the drawing's angle base and
direction and turned into the current UCS. Works from the command line
and from scripts or the automation token input alike.
Fixes#1464
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Host side of HostApi::set_reference_only: plugin-owned display geometry that
renders and snaps normally but must not be edited or picked by the person at
the screen, while the owning plugin keeps full write access through the host.
- Storage: a human-readable marker value in an XDATA record under the
host-owned OCS APPID (registered on first use; foreign values survive
verbatim; stale verbatim DWG EED is dropped on rewrite), so the flag
round-trips DWG and DXF with the entity and cannot collide with plugin
APPID namespaces.
- Grips: refresh_selected_grips yields none for a reference-only entity and
an active grip edit on a newly designated target is cancelled like a
locked-layer one.
- Picking: a reference-only wire flag extends the snap_only skips in every
hit test, so a click passes through to the editable entity underneath, and
box, window, lasso, fence and select-all gathers skip the designation;
fills, solids and instanced meshes are excluded through the shared
candidate handle set.
- Write policy: built-in user paths treat a reference-only entity like a
locked-layer one (Scene::update_entity / update_entities refuse,
erase_entities skips), while the plugin-owned variants
(update_entity_plugin / erase_entities_plugin, mirroring the
remove_entities editor split) and the whole HostSession surface write
through. The MCP entities read exposes a top-level reference_only boolean
and set_properties refuses with the reference_only failure code.
- Undo: the entity-write contract - a change joins a plugin-opened push_undo
scope, a scope-less change is one undo step of its own, refusals and
idempotent re-marks record nothing.
- Known limit: entities nested inside block instances (the block cache
rebuilds instance wires without the flag).
Evidence: marking_flags_and_unmarking_clears,
foreign_values_under_the_host_app_id_survive, other_apps_are_untouched,
reference_only_refusals_record_no_undo_step,
reference_only_designation_composes_undo_like_entity_writes,
reference_only_survives_dxf_and_dwg_round_trips,
user_paths_refuse_and_plugin_paths_write_reference_only_entities,
select_all_visible_skips_reference_only_entities,
clicks_pass_through_reference_only_wires,
box_selections_skip_reference_only_wires,
entities_records_expose_the_reference_only_designation.
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.
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.
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]>
The new pointer op sends moves (canvas pixels, or a world point projected
through the camera the cursor edits with), button presses/releases and
wheel steps in order, and reports the cursor's world point after each, so
grip drags and picks go through the same cursor path as the mouse.
click now projects through that editing camera too (it used the model
camera inside an entered viewport), and the grips read reports each
grip's constraint axis.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- A layout opens for the first time when its page setup was never
initialized (no paper size, no media name) or it was created in this
session; its page setup is initialized then, and with
LAYOUTCREATEVIEWPORT it gets its first viewport. A layout never opened
is saved with an uninitialized page setup and no viewports, so it gets
its viewport when the file is opened again, here or in the reference.
- Select Viewport for view: a viewport is selected when the pick box
(PICKBOX pixels at the sheet's zoom) touches its frame; a pick well
inside or outside selects nothing and the prompt repeats.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Two AutoCAD .acb books ship with the panel and are read the first time it
opens in a session; a loaded book of the same name replaces them. RAL
values are approximate (CC BY-SA 4.0, Wikipedia); sources and licences
are in each file's header and assets/color_books/README.md.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_013rGmDUNr1hUf4zo1X3dak8
Expose creation fields, constraints and examples for the 11 supported types without expanding startup tool schemas. Share the creation contract and constructor with execution to keep accepted inputs consistent.
Validation: MCP, contract/factory and entity creation regression tests.
Renaming a layer or a style was only applied on Enter; clicking anywhere
else dropped the typed name. A click that moves focus off a field whose
text changed now commits it. New and Copy in the style managers open the
generated name for renaming, so a new style can be named right away.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
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]>
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]>
- The Image tab gains an Adjust card: X/Y offset in pixels (-2000..2000)
and X/Y scale (-10..10, tenth steps, a negative scale mirrors, zero keeps
the image's scale) with spinners and Reset, inactive while the image is
stretched; Keep aspect ratio is a switch and makes both scales follow.
- The preview shows the image at its offset and scale, tiled when tiled,
and a stretched image kept to its aspect ratio covers the view.
- Automation: step=<field>,+1|-1 and reset; offset/scale take any numbers.
- Shaded with edges, Shades of Gray and Sketchy have catalog entries in
every language.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The web control channel gains a save operation that serializes the
active drawing in the requested format and returns it as base64 in the
reply, clearing the dirty flag, the counterpart of opening from bytes.
A host page can also define window.openCadStudioSaveFile(name, bytes)
to receive every saved or exported file in place of a browser
download; while it is set no file picker opens.
Fixes#1704Fixes#1705
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- 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]>
A headless plot with plot_style loaded the layout's stored page setup,
which put the layout's CTB back in place and, when that file was not in
the plot styles folder, failed the plot with "not loaded". The
requested table now stays active for every page.
Bump codec ddf0fd2 (viewport on state from DXF group 68, so viewports
written without bit 0x8000 show their contents), kernel b80079a,
graph 45c9649.
Refs #1739
Co-Authored-By: Claude Opus 5.5 <[email protected]>
TRIM already takes objects selected before the command as cutting
edges; EXTEND ignored them and extended to the nearest object in the
path. It now takes them as boundary edges the same way.
Fixes#1732
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The pasted block is defined around the clipboard base, so its origin
is already that point, but the insertion still subtracted the base and
landed the block offset from the pick. The base now only positions the
world-space preview, and it stays f64.
Fixes#1731
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The button runs QNEW since #1635, and QNEW was missing from the
commands the Start screen allows, so it was refused until a drawing
was open.
Fixes#1728
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Selection filter: condition rows name INSERT as a block reference, as
the object-type list does since #1726 (shared qselect_type_label).
- MCP: re-pin the tool schema digest for the view_background action
added in #1722.
- -VIEW: fix an indentation slip and wrap two long lines from #1722.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Document tab: every concrete colour the drawing uses (objects, block
definitions and layers), found in one pass when the panel opens, as a
grid of swatches sortable by recently used, most used, colour and colour
number. The recently-used list is now kept per drawing, so the ribbon's
Recent row shows this drawing's colours.
Color books tab: load AutoCAD .acb colour books (remembered in the
settings on desktop), browse them page by page with the page strip,
search by name and see the RGB equivalent. A book colour is applied as
RGB plus its BOOK$NAME identity on objects and layers instead of
dropping the name.
Strings translated into all 22 locales.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_013rGmDUNr1hUf4zo1X3dak8
Resolve conflicts with the new gradient and graphic-attributes colour
targets: keep main's targets and menu handling, routed through the new
ColorPicker state; adapt dock/view.rs to `color_picker`.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_013rGmDUNr1hUf4zo1X3dak8
- Split ColorEditor::view into mode_row, slider_rows, field_view,
hex_row and hex_field; output is unchanged.
- hex_string reuses config::rgb_to_hex, and parse_hex documents why it
stays separate (it also accepts #RGB).
- ink_is_dark uses Color::into_rgba8 instead of rounding by hand.
- Remove four translation keys the old dialog used and nothing
references any more, from the catalog and all 22 locales.
- Eyedropper: without a main window there is no screenshot to wait for,
so leave the eyedropper; keep the magnifier inside small windows.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_013rGmDUNr1hUf4zo1X3dak8
- Keep the modal keyboard comment with the code it describes and match
shortcut keys without an extra closure.
- Drop unused ColorEditor::mode, EyedropperMsg::Cancel and a duplicate
rgb helper; re-export nearest_aci instead of wrapping it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_013rGmDUNr1hUf4zo1X3dak8
- The gradient mode button is just its gradient, without a frame.
- In the gradient view H, S and B sit on the left and Hex on the right
of one row; the nearest index colour moves to the row below.
- Every colour swatch in Options opens the Select Color panel: the six
theme colours, crosshair, snap marker and command line text join the
three viewport backgrounds. BgTarget becomes UiColor, and one
color_swatch helper replaces the four hand-built swatches.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_013rGmDUNr1hUf4zo1X3dak8
- Current and New are a 50/50 band across the panel with their names
and values inside; the Color text box is gone.
- The gradient mode button is one gradient across the whole button,
gray until that mode is active. Mode buttons share one width.
- Tab and Shift+Tab move between the panel's own fields.
- Number boxes are 54 px wide, the hex box 74 px.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_013rGmDUNr1hUf4zo1X3dak8
- ColorEditor: the HSB / RGB / field editor and hex box, now shared by
the Select Color panel and the Options background swatches. Those
swatches open the panel without tabs or index colours, which removes
the iced_aw color_picker feature and the BgPicker messages.
- Sliders are iced's built-in slider and vertical_slider over a
gradient container; the custom GradientSlider widget is gone.
- Nearest index colour comes from opencadcodec's aci_table::nearest_aci.
- Eyedropper: a pipette at the right of the mode row hides the panel,
captures the window with iced's screenshot task and shows an 11x11
magnifier beside a crosshair; a click picks the pixel, Esc backs out.
Not offered on the web build.
- The hex box applies six-digit values while typing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_013rGmDUNr1hUf4zo1X3dak8
- 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]>
HostSession::update_entity, update_entities_transaction and remove_entity mutate entities and republish the document view, but never rebuild the app-side selection caches the way the built-in edit paths do. A selected entity updated by a plugin kept its cached grip markers at their pre-edit positions until the next real selection event; regen did not help and HostApi::set_selection cannot work around it (it bypasses the grip cache).
Refresh the grip cache for the session tab after a successful update_entity / update_entities_transaction / remove_entity, mirroring the built-in ERASE path and the other editors, so a plugin-driven edit keeps the cached grips on the refreshed geometry. A session targeting a background tab leaves the active tab cache untouched (found by the civil plugin manual QA, 2026-10-12).
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)
The last high-severity web gap plus a small integration bundle.
In-place save: with the File System Access API (Chromium/Edge), Save
now works like desktop. Plain Save on a tab with a retained handle
writes straight through — no picker, no download, no "(1)" copies.
First save or format change opens showSaveFilePicker AT the confirm
gesture (the worker serialization that follows can outlast the
transient-activation window; retained handles need no activation to
write). Picker cancel cancels the save like desktop; picker or write
failure falls back to the legacy download with a note; Firefox/Safari
keep the download flow untouched (feature-detected). Handles are
keyed by tab id in a thread-local registry (Message must be Send —
JsValue cannot ride in messages or App state); closing a tab drops
its handle; Save As re-picks. Dirty clears only on successful
persistence, same race guard as before.
Image paste: Ctrl+V of a screenshot inserts it as an image reference
— the clipboard reader falls back to image/* items when neither the
entity clipboard nor text has anything (precedence preserved), the
bytes enter the image source registry as pasted-<timestamp>.<ext>,
and attach reuses the IMAGE command's model-creation path verbatim.
Browser print: beforeprint snapshots the WebGL canvas into a print-
only <img> (the capture_canvas technique), so the browser's own print
menu produces the drawing instead of a blank page.
Automation capture on web: the capture op no longer calls the
synchronous GPU-map screenshot (dead on wasm); main's frame-parked
capture waits for a presented frame and reads via capture_canvas — the
same workaround the save-thumbnail path uses — delivering the PNG as a
download.
(cherry picked from commit 6e89b36d)
Ported onto main (#1717 review): the web capture rides main's existing
frame-parked capture (one ControlCaptureFrame path, ignoring the timer
tick on wasm) instead of a second parked-capture state; closing a tab
drops its file handle inside main's on_tab_close.
Two reviewers, no blockers, two majors — all fixed.
- The remembered-tab focus after a Restore-all batch fired at the
message boundary, but the LAST restore's tab inserts one async round
later (its OPFS recent-cache write precedes the insertion) and the
insertion's own activation overwrote the selection. The focus flag
now also consumes at the insertion point, so the batch focus holds
in the common all-success case.
- The service worker cached ANY successful in-scope navigation under
the shell key — a direct navigation to a font or JSON would be
served as the app shell for every offline navigation until the next
online visit. Only the scope root caches as the shell now.
Also: '/app/' gets its own web app manifest (the previous commit's
'installable' claim was true only of the marketing site, whose
start_url sits OUTSIDE the service worker's scope — an installed app
opened offline showed an error page). Its icon is the
existing assets/logo.svg, copied next to the page.
(cherry picked from commit 369e2c8b)
Ported onto main (#1717 review): the build-web.sh and README hunks stay
out with the wrapper.
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.
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)
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)