Commit Graph
3128 Commits
Author SHA1 Message Date
Felix RiestraandClaude Sonnet 5 8d20d280fe feat(python): block definitions (doc.blocks)
TableOperation gains BlockCreate (copy entities into a definition shifted by the
base point, optionally erasing originals), BlockModify, BlockRename (inserts
follow) and BlockDelete (refused while referenced; removes members, markers and
orphaned draw-order tables). Real-IPC audit with DWG/DXF persistence and undo,
IPC and Python unit tests, docs; the DXF loss of the block description is pinned
by a canary.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-21 16:28:03 +02:00
Felix RiestraandClaude Sonnet 5 aaea15e4ef feat(python): text and dimension styles (doc.text_styles, doc.dim_styles)
Extends TableOperation with text style create/modify, dimension style
create/modify (DimStyle fields as a validated JSON object), rename, delete and
set-current. Reuses OCS's style in-use scan and reference-following rename
(four style_ops methods widened to pub(super)); a renamed text style also
updates dimension styles that name it. Real-IPC audit with DWG/DXF persistence
and undo, IPC and Python unit tests, docs. Four cadcodec findings are pinned by
canaries.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-21 16:18:22 +02:00
Felix RiestraandClaude Sonnet 5 b7b6e2cef5 feat(python): validated, undoable layer table (doc.layers)
Adds the additive v7 TableOperation request and HostApi::table_operation for
layer create/modify/rename/delete/set-current. Refusals are decided before the
undo step is recorded. Python wrapper, real-IPC audit with DWG/DXF persistence,
IPC round-trip test, Python unit test and docs.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-21 16:00:26 +02:00
Felix RiestraandClaude Sonnet 5 6b8056cfeb Merge origin/main into plugin/host-model-api
Conflicts were additive: main's AddLayer/ModifyLayer (LayerConfig, OptHandle)
alongside this branch's v7 system-variable, transaction, selection and solid
operations. Main's variants keep their positions in the IPC enums; the v7
additions follow them. src/io/mod.rs takes main's reworded Shape-rotation note.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-21 15:12:58 +02:00
Hakan SevenandClaude Fable 5.1 fd2062bdec fix(insert): attribute grips follow ATTMODE 2 and annotative scale
Follow-up to the block-attribute grips picked from #1386.

- Movability no longer includes the invisible flag: an attribute ATTMODE 2
  displays is grippable, so the grip the display-aware list shows can be
  dragged. The document-less grips() still skips invisible attributes,
  since it cannot consult ATTMODE.
- The grip sits on the displayed attribute, which an annotative insert
  scales about its insertion point; Scene::apply_grip now unscales the
  dragged target the same way before the attribute stores it, so the grip
  no longer jumps to twice the drag at scale 2.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-20 21:31:54 +03:00
copilot-swe-agent[bot] 2b92fc5b09 Fix missing brace in insert attribute visibility filter
Co-authored-by: dasmatus <[email protected]>
(cherry picked from commit 6cf16d8621)
2026-09-20 21:29:15 +03:00
dasmatus 4559c8a2d8 Refactor attribute filtering logic in insert.rs
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
(cherry picked from commit a8f75e969d)
2026-09-20 21:29:15 +03:00
copilot-swe-agent[bot] bd89507a6c fix insert attribute grip visibility
Co-authored-by: dasmatus <[email protected]>
(cherry picked from commit 68cc414e4a)
2026-09-20 21:29:15 +03:00
Claude 9af327ed0b feat(insert): grip each block attribute
Selecting a block showed one grip, at its insertion point, so there was
no handle to pick an attribute up by: an attribute could only be moved
by moving the whole block.

Give the INSERT one grip per attribute, taken from the attribute's own
`Grippable` implementation, at ids `1 + index`. A drag on one moves that
attribute alone, through the same `apply_grip` the attribute already has
(which carries `alignment_point` along, so non-left alignments stay put
relative to the text). Grip 0 keeps translating the block and every
attribute with it (#255).

A constant attribute belongs to the block definition and an invisible one
is not drawn, so neither offers a grip; a locked-position attribute
declines on its own.

Fixes HakanSeven12/OpenCADStudio#1259

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01TQzYZsXBwPZ5HwBaGitEUV
(cherry picked from commit 7eb966f6a2)
2026-09-20 21:29:15 +03:00
copilot-swe-agent[bot] 5d4b707214 fix review feedback for OTRACK projection and OtrackHit API
Co-authored-by: dichhead <[email protected]>
(cherry picked from commit 97f94a2bfe)
2026-09-20 21:29:15 +03:00
Claude 033319a4e3 feat(otrack): draw both vectors of an intersection lock
An OTRACK crossing is the meeting of two tracking vectors, but
`OtrackHit` carried only the one a typed distance is measured along, so
the overlay had a single guide to draw. Snapping to the crossing of two
vectors worked and looked like an unexplained point on one line.

Carry the crossed ray out of `otrack_snap` as `OtrackHit::cross`, keep it
on the app beside `otrack_active`, and let the overlay take a list of
guides rather than one. `otrack_active` still drives typed-distance
entry on its own, so distance entry is unchanged.

Fixes HakanSeven12/OpenCADStudio#1313

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01TQzYZsXBwPZ5HwBaGitEUV
(cherry picked from commit 2f6caacece)
2026-09-20 21:29:15 +03:00
copilot-swe-agent[bot] 41101efaa5 fix(trim): skip empty preview polyline segment samples
Co-authored-by: dasmatus <[email protected]>
(cherry picked from commit fb13a7aaa6)
2026-09-20 21:29:15 +03:00
Claude e83c8a28a5 fix(trim): skip polyline segments with no plan-view shape
`sample_entity_xy` walks a polyline's exploded segments and drops the
vertex each segment shares with the previous one. A segment whose curve
plane is upright — any non-horizontal run of a 3D polyline — has no XY
curve, so `entity_curve_xy` declines it and the segment samples to
nothing. With nothing accumulated yet, the seam test compared the two
empty ends as equal and then sliced the empty sample from index 1:

    panicked at src/modules/draw/modify/trim.rs:3982:42:
    range start index 1 out of range for slice of length 0

On the web build that panic unwinds through winit's event-loop runner
and leaves its state borrowed, which is the "RefCell already borrowed"
follow-on in the same report.

Skip an empty sample instead, which leaves the seam handling to the
segments that do have a plan-view shape.

Fixes HakanSeven12/OpenCADStudio#1318

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01TQzYZsXBwPZ5HwBaGitEUV
(cherry picked from commit ac9a6ce91a)
2026-09-20 21:29:15 +03:00
Hakan SevenandClaude Fable 5.1 a1629fd073 fix: closing-segment markers on closed polylines; case-insensitive page-setup names
- resolve_point, set_polyline_vertex and the solver's point_for_marker
  treat the marker one past the last vertex of a closed polyline as
  vertex 0, the way line_segment already did. Equal (#1366), the axis
  pre-alignment (#1365) and the transform pin (#1361) address a picked
  segment's end that way, so on the closing segment they either refused
  the pick or pinned nothing.
- Page-setup dictionary keys compare case-insensitively: page_setup_save
  replaces "site" when "Site" exists instead of adding a second entry
  (PSETUPIN counted it as replaced, #1344), delete and rename match the
  same way, and a rename that only changes case is allowed.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-20 21:12:07 +03:00
Hakan SevenandClaude Fable 5.1 8845095432 fix: DXF attribute rotation converted once; plugin layers refuse unknown linetypes
- The acadrust DXF reader has converted ATTRIB/ATTDEF rotation to radians
  since f226110 (in every codec rev OCS pinned from 2026-08-08 on), while
  fix_dxf_dimension_rotations still applied to_radians a second time on
  load. Only Shape needs the conversion here. Surfaced by #1391's hunk.
- Shape-file text styles are found by their is_shape_file flag instead of
  an empty name (also from #1391).
- #1393's add_layer/modify_layer wrote an unresolved linetype name into
  the LAYER record; a name the drawing (plus the standard set) cannot
  supply is now refused, through one resolve_linetype helper.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-20 20:18:18 +03:00
Hakan Seven 8075caa744 Merge pull request #1393 from yztink/feat/plugin-layer-properties
feat(plugin): add layer creation and modification host API with GUI refresh
2026-09-20 20:14:10 +03:00
Hakan Seven 871b0ab2ec Merge pull request #1392 from buyun14/fix/batch-line-tail
fix(automation): consume and report the tail of a batch command line
2026-09-20 20:14:10 +03:00
Felix RiestraandClaude Sonnet 5 7c959c8395 Register scripted image definitions in ACAD_IMAGE_DICT and reuse per file
A scripted RasterImage now reuses one ImageDefinition per file and registers
it in ACAD_IMAGE_DICT, which owns it, as for an AutoCAD-attached image. The
reactor is deliberately not written because its owner convention cannot be
verified. An ignored spike records that OCS's own IMAGE command creates bare
images that lose their file path on save.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 19:00:16 +02:00
Felix RiestraandClaude Sonnet 5 6e2e073e29 Complete Viewport: layout-tab canvas oracle, unique ids, derived scale
The shared driver now activates the layout that owns a viewport and requires
a drawn frame. The host assigns a unique id of at least 2 and derives
custom_scale as height / view_height on every scripted create or edit, as
MVIEW does, so custom_scale is read-only. A control showed the default id 0
is not invisible, so the id matters for uniqueness.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 18:53:32 +02:00
Felix RiestraandClaude Sonnet 5 5e6a830782 Complete Ole2Frame: embed a picture file from a script
EmbedPicture builds the frame with OCS's own EmbeddedImage and
build_embedded_ole, so the opaque storage is never scripted. PNG, JPEG and
BMP are stored as read and other formats are re-encoded as PNG. The lifecycle
test now creates through Python; a new test covers formats, aspect, origin,
layer and refusals.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 18:12:31 +02:00
Felix RiestraandClaude Sonnet 5 942222c381 Map SectionSymbol, the last unmapped kind
points is canonical; the host derives the point counts and refreshes the
end/tick/label projections with sync_display_fields when it changes, and
validates that style and view-representation references are existing class
objects. The lifecycle test passes every gate in DWG; DXF reopens the symbol
as an unknown entity because cadcodec dispatches SECTIONLINE only inside
blocks (pinned by a canary and already in the cadcodec write-up).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 18:05:38 +02:00
Felix RiestraandClaude Sonnet 5 5a39052397 Add kernel booleans: union, subtract and intersect for Solid3D
SolidOperation::Boolean runs synchronously, surfaces the kernel's refusal
with a plain reason and leaves the operands untouched, consumes them on
success unless keep_operands, and records one undo step. The lifecycle test
uses planar boxes with exact volumes and covers chaining, refusals, both
formats and undo; an ignored test shows a curved refusal reaching the script.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 17:58:38 +02:00
Felix RiestraandClaude Sonnet 5 9cd6653a58 Complete Surface: plane surfaces, extrusions, isolines and moves
Adds SurfaceFromProfile and Extrude kernel operations (doc.solids.surface and
doc.solids.extrude), accepts surfaces in Transform, and maps Surface with
editable isolines. Fixes a real bug found by the test: the surface kind field
overwrote the entity-type kind key in Python dicts, so it is exposed as
surface_kind, with a catalog test guarding against name collisions.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 17:47:22 +02:00
Felix RiestraandClaude Sonnet 5 c731d3a9a4 Complete Region: create from a profile, plus tuple and int friendliness
RegionFromProfile reuses the REGION command's kernel calls; doc.solids.region
exposes it with layer and delete_source options. The lifecycle test builds
regions from real circle and polyline entities and covers refusals, a move,
both formats and undo. create_entity now accepts coordinate tuples for point
properties and every generated float setter accepts Python ints.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 17:37:10 +02:00
Felix RiestraandClaude Sonnet 5 04cd9e462d Add kernel solid operations and complete Solid3D (create and rigid move)
An additive HostApi::solid_operation builds cadkernel primitives and applies
rigid transforms, verifying each result lifts back losslessly and refusing a
payload it cannot lift. Exposed as doc.solids in Python; Solid3D, Body and
Region are mapped read-only with the opaque payload preserved. The lifecycle
test passes through the real runner with analytic volumes and exact extents as
oracles, DWG and DXF reopen with re-edit, and undo restores exactly.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 17:29:06 +02:00
Felix RiestraandClaude Sonnet 5 07fe739571 Investigate the cadkernel path for ACIS kinds; add spikes and findings
OCS already creates, transforms and combines bodies through cadkernel, and
solid_to_sat / kernel_acis_body verify each result lifts back losslessly.
Two ignored spike tests measure volumes and DWG/DXF round trips. The
blocker is an operation channel, not geometry; the note recommends an
additive solid_operation host method and phases the work.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 17:10:51 +02:00
Felix RiestraandClaude Sonnet 5 e583da83db Map Ole2Frame as update-only with the embedded object preserved
Frame corners and the aspect lock are scriptable; the opaque storage and
envelope are excluded and refused on edit. The audit embeds a real PNG
through OCS's own builder and proves it survives DWG and DXF byte-for-byte.
Python creation stays a follow-up.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 16:43:25 +02:00
Felix RiestraandClaude Sonnet 5 f1813b3ebe Draft cadcodec issues for the DXF reader gaps; fix Spline rational flag
Documents six cadcodec reader/writer gaps with source references, canary
tests and suggested fixes (nothing filed). Verifying them showed the Spline
DXF weight loss was an OCS bug: the writer emits weights only for a rational
spline, so the host now sets flags.rational from the weights. Spline passes
every gate.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 16:37:41 +02:00
Felix RiestraandClaude Sonnet 5 eb22de636b Decide Insert and Polyline: Insert creatable by block name, Polyline update-only
Insert can be created by naming an existing ordinary block; the host refuses
unknown blocks, model/paper space and containment cycles, and keeps legacy
inserts of missing blocks editable. The legacy Polyline is update-only with a
hint toward Polyline2D and Polyline3D because both writers convert it on
save. The generator gains update_only_message.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 16:14:02 +02:00
Felix RiestraandClaude Sonnet 5 cae188dd08 Audit AttributeDefinition through both formats; pin the DXF reader gap
Compares every writable ATTDEF field through DWG and DXF. DWG round-trips the
complete state; acadrust's DXF reader handles only eight group codes, so
alignment, width factor, oblique angle, attribute flags, field length,
generation flags, lock and text style reopen as defaults. Pinned by canary
expectations and recorded in the ledger.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 15:48:42 +02:00
Felix RiestraandClaude Sonnet 5 bd43ed7e91 Audit the 17 pre-existing mapped kinds; add shared basic-geometry validation
Runs every first-generation kind through the real-runner lifecycle driver.
The audit found Ellipse unvalidated, zero-height Text/MText and non-finite
Arc angles and Point/Line thickness accepted, and no creation-time checks
for most kinds. validate_basic_entity now covers them and refuses only
problems an edit introduces. Twelve kinds pass every gate; Polyline,
Polyline2D, Polyline3D and Spline carry pinned persistence findings, and
Insert is update-only.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 15:44:26 +02:00
Felix RiestraandClaude Sonnet 5 bfb5e7576b Merge origin/main into plugin/host-model-api
Picks up cadcodec 5b682ed and cadkernel 6f046af and the plotstyle
layer-usage speedup. The adapter's Cargo.lock now resolves acadrust to
5b682ed. All host, API and adapter tests pass and no acadrust canary
assertion tripped.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 15:29:55 +02:00
Felix RiestraandClaude Sonnet 5 12b0823e76 Map Light; record SectionSymbol as blocked
Light placement, intensity, colour, attenuation and shadow fields are
writable with host validation. staged_python_light_lifecycle_over_real_ipc
passes every gate except DXF cast_shadows, pinned by a canary.
SectionSymbol is recorded as blocked: it needs host-created style and view
representation objects and its raw point counts have no documented
derivation.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 15:18:13 +02:00
Felix RiestraandClaude Sonnet 5 21ce4b0e2b Map ViewBorder in the Python host model; record DXF kind blocker
Bounds, scale and rotation are writable with viewport and scale reference
rules. staged_python_view_border_lifecycle_over_real_ipc passes every gate
in DWG; a DXF DRAWINGVIEW reopens as an opaque entity, pinned by a canary.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 15:13:23 +02:00
Felix RiestraandClaude Sonnet 5 9075e53511 Map Viewport in the Python host model
Curated view fields are writable; the owner must be a paper-space block and
frozen layers must exist. staged_python_viewport_lifecycle_over_real_ipc
passes every gate except a paper-space canvas oracle, so the kind is
recorded as integration-tested.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 15:09:23 +02:00
Felix RiestraandClaude Sonnet 5 2e4d77dc09 Map Underlay in the Python host model; record DXF rotation blocker
Scripts place an underlay against an existing UnderlayDefinition; the host
checks that the definition exists and matches the underlay type.
staged_python_underlay_lifecycle_over_real_ipc passes every gate except DXF
persistence: acadrust's reader returns the degree-valued rotation
unconverted and repeated saves compound it. Pinned by canary expectations.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 15:05:11 +02:00
Felix RiestraandClaude Sonnet 5 96e3b54f08 Complete Wipeout host model coverage
Maps the placement, display and clip-boundary fields with rectangular and
polygonal clip validation. staged_python_wipeout_lifecycle_over_real_ipc
passes the full gate including DWG and DXF reopen with re-edit.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 15:00:04 +02:00
Felix RiestraandClaude Sonnet 5 eae74c24dd Restore the MLine flags rejection expectation
An earlier bulk edit for the RasterImage flags message also rewrote MLine's
test expectation; MLine.flags is a manifest override with its own message.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 14:56:34 +02:00
Felix RiestraandClaude Sonnet 5 7f65d6d373 Map RasterImage with host-created image definitions
Scripts name an image file; the host reads its pixel size, creates and links
the ImageDefinition and derives size and the default clip boundary.
staged_python_raster_image_lifecycle_over_real_ipc passes every gate in OCS
with a real PNG. ACAD_IMAGE_DICT and reactor linkage are not created, so the
kind is recorded as integration-tested.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 14:55:29 +02:00
Felix RiestraandClaude Sonnet 5 d4c2520907 Map Helix in the Python host model; record DXF handedness blocker
The host rebuilds the derived spline from the generating parameters on
scripted create/edit. staged_python_helix_lifecycle_over_real_ipc passes
every gate except DXF handedness, which acadrust's reader never applies;
the gap is pinned by a canary via per-format expectations in the shared
mesh lifecycle driver.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 14:48:44 +02:00
Felix RiestraandClaude Sonnet 5 b97d74efa4 Complete PolygonMesh, PolyfaceMesh and Mesh host model coverage
Maps all three mesh kinds with host validation of grid counts, face indices
and edge topology, and skips sub-record common data in the generator.
A shared run_mesh_lifecycle driver passes the full gate for each kind,
including DWG and DXF reopen with re-edit.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 14:42:42 +02:00
Felix RiestraandClaude Sonnet 5 0206c7c30b Complete Table host model coverage
Maps rows, columns, merged ranges, style and placement through the generic
generator (now with tuple and usize support), adds grid/merge/reference
validation, and keeps DXF cell merge dimensions in step with merged_ranges.
staged_python_table_lifecycle_over_real_ipc passes the full gate including
DWG and DXF reopen.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 14:36:12 +02:00
Felix RiestraandClaude Sonnet 5 b3f5388208 Complete MultiLeader host model coverage
Extends the adapter generator (Color, LineWeight, mixed unit/payload enums,
fixed float arrays, bitflags-2 newtypes, keep_types override) and maps
MultiLeader with a merge-style context patch, host validation and
reference checks. staged_python_multileader_lifecycle_over_real_ipc passes
the full gate including DWG and DXF reopen.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 14:27:43 +02:00
Felix RiestraandClaude Sonnet 5 22f9c20fe8 Support Color in the Python adapter generator; map Leader.override_color
Color converts as {'kind': ByLayer|None|ByBlock|Index|Rgb, 'value': ...}.
Leader.override_color is now writable; neither DWG nor DXF stores it, which
is recorded as a fourth persistence blocker with a canary assertion.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 14:18:12 +02:00
Felix RiestraandClaude Sonnet 5 6b4c86fd7a Complete Dimension host model coverage for all nine subtypes
Adds a hand-written flat Dimension model to the adapter (manifest
manual_kinds hook) and a synthetic-property section to the host coverage
policy. The host derives actual_measurement and the base definition point on
scripted create/edit. staged_python_dimension_lifecycle_over_real_ipc passes
the full gate for every subtype including DWG and DXF reopen. Python ints are
now accepted for coordinates and angles.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 14:15:03 +02:00
Felix RiestraandClaude Sonnet 5 a86d5bc727 Complete MLine host model coverage
Maps vertices, flags, justification, normal, scale and style with host
validation and MLineStyle binding. The host recomputes vertex direction,
miter and element parameters on scripted create/edit, so scripts only supply
positions. staged_python_mline_lifecycle_over_real_ipc passes the full gate
including DWG and DXF reopen.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 14:02:40 +02:00
buyun14 899af80c2f fix(automation): consume and report the tail of a batch command line
`run_command_line` feeds the tokens after the command verb to the interactive
command and then terminates it as if Enter were pressed. Two gaps left a
headless caller unable to tell what actually happened.

In-place text editor takes over, remaining tokens were dropped
----------------------------------------------------------------
The `TEXT` content step hands over to the in-place editor, at which point the
command has already ended (`active_cmd` is `None`). The feed loop stopped
there, so the rest of the line — including the text itself — vanished:
`TEXT 0,0 5 0 hello` reported `completed` with `added: 0` and created nothing.
The tail is now typed into that editor and committed, and the commit is done
synchronously so its result is observable: `text_inline_commit()` returning
false leaves the tokens in `unconsumed` instead of reporting a successful line.
Dispatch the commit as a queued message instead and a failed commit would look
exactly like a good one — the text dropped, `added: 0`, `unconsumed: []`.

Only an editor this line opens is taken over, and after a successful commit the
line is closed with an escape: `TEXT` re-arms for the next line
(`open_next_line`), which would otherwise let the *next* batch line be eaten as
the next text position.

Leftover tokens are dropped
---------------------------
Any token no prompt ever asked for is recorded in `CommandLine::unconsumed` and
surfaced by the automation `run` response, so over-typed or misplaced input is
visible instead of silently ignored: `CIRCLE 0,0 5 9` reports
`"unconsumed": ["9"]` while a fully consumed line reports an empty list.

Pending surfaces are reported instead of `completed`
----------------------------------------------------
`run` now carries `blocked_by` — `"command"`, `"text_editor"`,
`"mtext_editor"`, `"modal:<Kind>"`, or `null` — and answers `waiting_input`
when something is still open rather than claiming `completed`.

`blocked_by` is attributed to the line that opened the surface: a condition and
its snapshot are taken before the command runs, so an editor left open by an
earlier line, another tab, or a startup dialog does not turn a finished command
into `waiting_input`. Without that attribution a caller polling for `completed`
would wait forever on someone else's editor. `docs/automation` documented
`waiting_input` as "more input is required", which is exactly what a pending
surface is; the two new fields are documented there as well.

Tests
-----
* `batch_text_line_creates_text_and_consumes_every_token` — the regression case
  creates exactly one `Text` entity and leaves nothing unconsumed.
* `multi_word_text_keeps_the_spaces` — asserts the committed string, so an
  off-by-one in the consumed index or a wrong join cannot pass.
* `run_reports_leftover_tokens_instead_of_dropping_them`.
* `text_without_content_reports_the_open_editor`.
* `a_stray_editor_is_not_hijacked_by_the_next_line` — the next line neither
  types into the stray editor nor inherits its blocker (`blocked_by: null`,
  `status: completed`).
* `a_command_still_waiting_is_named_as_the_blocker`.

Verified over the `--serve` protocol: `TEXT 0,0 5 0 hello world` → `completed`,
`added: 1`, and the entity really holds `hello world`; `TEXT 40,0 5 0` →
`waiting_input` + `blocked_by: "text_editor"`; the following `CIRCLE 0,0 5 9` →
`completed`, `blocked_by: null`, `unconsumed: ["9"]`; `LINE` → `waiting_input`
+ `blocked_by: "command"`.

Full lib suite: 1497 passed, 2 failed — both failures
(`io::plot_style::…named_tables_and_missing_files_get_a_reason`,
`scene::parametric_constraints::…arc_grips_drive_center_start_and_end_but_not_midpoint`)
reproduce on the unmodified tree; run with `LC_ALL=C` since several xref tests
otherwise fail on a non-English locale.

Known remaining gap, unchanged here: `MTEXT` / `HATCH` still answer `completed`
with `added: 0` in this path (they need interactive surfaces the feeder has no
tokens to fill). Reported separately in #1390.
2026-09-20 19:15:55 +08:00
Karim Jerbi 70d7e9a31f perf(plotstyle): extract and speed up layer-usage table rebuild
view_window() rebuilt the 256-bucket ACI layer-usage table inline on every
modal view: unconditional 256-Vec allocation plus sort_by_key (lowercase key
recomputed per comparison). Extract pure build_layer_usage(), build it only
when the CTB path needs it (STB path now allocates nothing), and sort with
sort_by_cached_key while skipping trivial buckets.

Tests:
- build_layer_usage_buckets_index_colors: ACI bucketing + case-insensitive order
- build_layer_usage_ignores_non_index_colors: truecolor layers excluded

Bench (cargo bench ui_plotstyle_layer_usage, 200 layers): ~13us median, PASS.
A/B vs pre-change inline code: ~15.7us -> ~12-13us (~22% faster).
2026-09-20 11:17:05 +01:00
Felix RiestraandClaude Sonnet 5 ec10bdbc48 Map Leader in the Python host model; record acadrust W blockers
Adds Leader validation and annotation/dimension-style reference checks to
ocs_plugin_api, maps 15 writable and 5 read-only fields in the adapter, and
adds staged_python_leader_lifecycle_over_real_ipc covering C/R/E/D/U/I/V/P.
The W gate stays open: acadrust's DXF writer omits the annotation handle and
DXF reads drop annotation_offset; DWG R2010+ does not store text size or the
hookline flag. Each gap is pinned by a canary assertion.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-20 11:12:27 +02:00
Helen Kwok cf004107b2 Keep the task a dispatched command returns
CmdResult::Dispatch and CmdResult::Relaunch end the active command and run the
assembled line through dispatch_command, then discarded the Task it returned
(`let _ =`). A command whose effect is a message carried by that Task was
announced but never applied. VSCURRENT's keyword picker is one: choosing
"Gouraud Shaded" printed "Visual style: Gouraud Shaded." and left the
drawing in wireframe, because SetRenderMode travels in that Task.

Hold the Task and batch it with the focus task that apply_cmd_result_inner
returns. A regression test drives both results through VSCURRENT and checks the
tab's render mode.
2026-09-20 13:17:05 +09:30