Commit Graph
174 Commits
Author SHA1 Message Date
aiongg 17952af3fb Regenerate the parity checklist for the batch 2026-10-25 02:33:36 +00:00
aiongg 1b0cefce9c Merge storytold/cadcraft#2049
# Conflicts:
#	docs/gaps.md
2026-10-25 02:23:03 +00:00
aiongg 75c54ce35d Merge storytold/cadcraft#2056 2026-10-25 02:21:58 +00:00
aiongg 313fab8827 Merge storytold/cadcraft#2101 2026-10-25 02:21:58 +00:00
aiongg b901656669 Panels: use craft-ui's shared docking (#2093)
* Pin craft-libs at 420d21a

* Pin craft-libs at 16fb3d4

* Use craft-ui's shared docking workspace and ui.dock commands

Replace the local Workspace, its validation, apply, visibility and the
ui.dock parser with craft_ui::docking::Workspace and DockOperation.
CADCraft keeps its panels, the drawing view's rules, the default layout
and the workspace file. Add tests for loading a v1 workspace file and for
dragging a panel group by its bar.

* Pin craft-libs at 6e7f285

* Docking: reveal panels through craft-ui's set_visible, which reopens groups and places new panels itself

* Docking: palette switches regroup a panel whose recorded anchor is hidden instead of opening it alone
2026-10-24 20:23:39 -04:00
dotCooCoo 8a64e42100 DIMTFILL: dimension text gets a background in the drawing's background color or in DIMTFILLCLR
DIMTFILL and DIMTFILLCLR were not modeled, so a dimension's text never had a
background and lines under it stayed visible. Dimension styles now carry both
variables. DIMTFILL 1 hides what is drawn under the text in the drawing's
background color, and DIMTFILL 2 fills the box in DIMTFILLCLR. The box is the
text box widened by DIMGAP on every side. The arc length symbol over the text
and the DIMINSPECT frame are drawn over the background.

The variables work with SETVAR, DIMOVERRIDE and the style fields. DXF stores
them as DIMSTYLE groups 69 and 70 from R2007 and in the style's CADCRAFT
xdata in older files; the reader skips the record's flags, which also use 70.
For DIMTFILL 2, the dimension's *D block and EXPLODE add a SOLID in
DIMTFILLCLR under the text. A dimension drawn from the *D block it was read
with gets a background around each of the block's texts, before the block's
first text or first segment of the text's marks, except for a text that a
SOLID of the block already covers. The background stays after the drawing is
saved and reopened.
2026-10-24 14:11:01 -07:00
dotCooCoo 6fe715e11c THICKNESS: Format > Thickness, and new 2D objects take the current thickness
New lines, arcs, circles, points and lightweight polylines (for example
PLINE, POLYGON, DONUT and BOUNDARY) now take the THICKNESS system
variable as their 3D thickness. THICKNESS could already be set with
SETVAR and with the Properties palette when nothing is selected, but new
objects were always created with a thickness of 0, apart from RECTANG's
own Thickness option. RECTANG keeps that option, and single-line text
does not take THICKNESS, as in AutoCAD.

The new THICKNESS command is on Format > Thickness. Typed or picked from
the menu, it asks "Enter new value for THICKNESS <0.0000>:"; Enter keeps
the current value, and a negative value is accepted.
docs/parity-checklist.md is regenerated.
2026-10-24 03:24:13 -07:00
dotCooCoo 4aed7a2267 COMPAREEXPORT: export a drawing comparison to a new drawing
COMPAREEXPORT compares the two drawings again and opens the result as a
new untitled drawing, compare_<this> vs <other>.dwg. Model space holds
the other drawing's removed and changed objects as group
COMPARE_COMPARED and the compared tab's objects as group
COMPARE_CURRENT, in the overlay's colors: green for objects only in
this drawing or changed in it, red for the other drawing's, gray for
objects in both.

Block references, dimensions and multileaders refer to recolored copies
of their blocks, tables and tolerances use recolored copies of their
styles, and inline MTEXT and cell colors, dimension style colors,
multiline element colors and gradients follow the object. The new
drawing takes the layers and styles of the other drawing that this
drawing lacks, and the blocks its exported objects refer to; an
anonymous block whose name is taken is copied under a free name. The
comparison bar gets an Export button.
2026-10-24 01:36:29 -07:00
Nicholas GowerandBrandon Thomas e59195edb8 usage: a read-only cross-reference of layers, blocks, styles and linetypes (#1889)
* usage: a read-only cross-reference of layers, blocks, styles and linetypes

USAGE reports, for every layer, block, text style and linetype, how many
objects use it in model space and the layouts, how many inside block
definitions, whether it is used only inside block definitions, and whether
it is empty or unused. Its unused lists are PURGE's own scan (candidates and
block reachability), shared read-only; PURGE's behaviour is unchanged.

The JSON form takes {type?, limit?, offset?}, returns schema 1 with a paged
items list, per-kind summary and PURGE's unused names in table order.

Fixes #1439

* docs: document the functions touched by #1439

* usage: mark a scan cut at its limit as incomplete (#1439)

USAGE's one pass over model space, the layouts and the block definitions was
capped at 5,000,000 objects with `take`, so a larger drawing silently omitted
later references while the counts, statuses, summary and message looked
complete — an unused layer or block could be reported as used, or vice versa.

The limit is now `MAX_SCAN`, and `Scan::new` takes it as a parameter: it counts
the drawing's objects first and records an `Incomplete { scanned, total }` when
the limit cuts the scan short. The report carries `incomplete: true` and an
`incompleteReason` naming both counts, and the message ends with a note, so a
partial report can never be mistaken for a complete one. A drawing holding
exactly the limit is still complete.

PURGE's scan limit and candidate behaviour are untouched; USAGE only reads
PURGE's unused lists as before.

Tests: unit tests cut a small drawing at a small limit (a 5,000,000-object
drawing is impractical) and check the boundary and the miscount the signal
prevents; the integration test checks a complete drawing says so. Also adds the
missing Ukrainian translation for the command label, which the i18n gate
requires.

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-24 02:04:10 -04:00
Nicholas GowerandBrandon Thomas 5cfe1a4149 appload: load a .lsp file from a path or the file picker (#1903)
* appload: load a .lsp file from a path or the file picker (#1820)

APPLOAD had no user interface: typed or chosen from Tools > Load Application it
started a command-line prompt instead of a file browser, and the JSON form did
not say which commands the file defined.

- The desktop's APPLOAD opens an rfd file browser filtered to .lsp, starting in
  the folder the last routine came from (kept in the preferences as lispDir).
  A cancelled picker is a no-op; the web (no native dialog) keeps the engine's
  name prompt.
- The JSON form `appload {path}` loads to the end, never opens a dialog and now
  returns `commands`, the `c:` commands the file defined; a non-.lsp path is
  refused and unreadable or unparsable files return an ordinary error.
- Tests: a synthetic .lsp in a temp folder loads by path and its command runs;
  the picker path, its cancelled answer and the remembered folder; refused,
  missing and broken files.

* appload: load a picked file interactively; keep path whitespace; web hint

Reviewer follow-ups on #1820:

- A picked file now loads through the interactive APPLOAD command (started
  and answered with the picked path, as a typed name is), so a routine it
  starts that calls getpoint or another get… waits for the user instead of
  receiving nil. JSON calls keep the blocking run.
- A JSON `path` is read as given: only a path of blanks is refused, so a
  relative name with leading or trailing spaces is not trimmed into another
  file.
- The web has no file system, so APPLOAD no longer asks for a file name it
  cannot read: it says to drop a .lsp file on the window, which loads the
  file's contents. The host declares this through Services::no_file_system.

Tests: a picked file whose load asks for a point waits for it; a relative
path in a folder whose name starts with a space loads; on a host without a
file system APPLOAD gives the drop hint instead of prompting.

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-24 01:57:06 -04:00
Nicholas GowerandBrandon Thomas 9f2060d674 analysis: report found/analysed/skipped counts, caps and implausibility (#1890)
* analysis: report found/analyzed/skipped, caps and implausible values (fixes #1440)

* docs: document the functions touched by #1440

Add doc comments to the self-check helpers, the COUNT/SHAPES/MASSPROP entry points and helpers, and the tests and fixtures the analysis self-check change touches, so CodeRabbit's docstring coverage check passes.

* analysis: tolerate rounding past a bound, and skip area checks without extents (#1440)

The self-check's implausible() compared value > bound exactly, so a border
rectangle whose shoelace area equals the extents' width * height could be
flagged by one ulp of rounding. Allow a 1e-9 relative slack past the bound
(the roundings of two ways of measuring the same thing), while a clearly
larger value is still flagged.

MASSPROP and SHAPES fell back to an extents area of 0 when nothing visible
gave the drawing extents, so every region or shape with area got a false
"area is larger than the drawing extents" flag. Pass f64::INFINITY instead:
implausible() already skips non-finite bounds, because no bound applies.

Tests: one ulp past the bound is not flagged while 1e-6 past is; an
extents-filling rectangle is not flagged; invisible-only masses/shapes with
no visible extents are not flagged.

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-24 01:56:51 -04:00
fef32761af DXF/DWG: angular, diametric and radial dimensions get DIMASSOC (#1651) (#1874)
* DXF/DWG: angular, diametric and radial dimensions get DIMASSOC (#1651)

std_assoc_refs had slots only for linear/aligned and ordinate dimensions, so
every other associative kind was saved without a DIMASSOC object: AutoCAD
did not follow angular, diameter or radius dimensions, although CADCraft
itself did through its xdata.

Write the references AutoCAD writes for each kind: both lines of an angular
dimension (4 point references; the 3-point kind its three definition points)
and, for radial and diametric dimensions, the single centre reference to the
circle or arc (associativity flag 2), which is how AutoCAD files store them.
Read the same references back, with the circle's definition points
reconstructed, so files from other writers associate those kinds too.

Curve start references now use osnap 13 as AutoCAD writes them (1 is the
end), which also lets a stored point of 0,0,0 resolve to the right end.

Tests: the issue's commands through the engine, and a DXF/DWG round trip
with CADCraft's xdata stripped so only the standard objects remain.

* docs: document the functions touched by #1651

* DXF read: a line's osnap 1 with a stored point still picks the nearer end (other writers)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 01:56:17 -04:00
73e7486dc9 DXF: proxy objects and entities keep their class through a save (#1812) (#1858)
* dxf: proxy objects and entities keep their class through a save (#1812)

A proxy (ACAD_PROXY_OBJECT, ACAD_PROXY_ENTITY) names its class by number
in group 91: 500 plus the class's place in the CLASSES section (DXF
Reference). Saving rewrites CLASSES (CADCraft's own classes, then the kept
ones), but kept proxies were written with their read number, which then
named another class: an R14 file's visual styles came back as SCALE,
TABLESTYLE or MLEADERSTYLE, and numbers past the last class stayed.

The reader now keeps the file's class order (Foreign::class_order) when
the file has proxies, and the CLASS records its proxies name. The writer
renumbers each kept proxy to its class's place in the written CLASSES and
drops a proxy whose number names no class (with what it owns and its
dictionary entry). The DWG bridge writes through the same path.

Test proxies_keep_their_class_when_classes_are_rewritten (a file with
VISUALSTYLE and WIDGETX first in CLASSES, proxies of both and of a number
past the last class, and a proxy entity; also through a second save).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Nnsfu3QGqjD1aFZbBgUWP

* docs: the proxy class fix is for DXF save (#1812)

From the local CodeRabbit review: DWG save drops proxy entities (acadrust)
and proxy objects through DWG weren't checked, so the progress log says DXF.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Nnsfu3QGqjD1aFZbBgUWP

* dxf: a proxy's class number is found outside 102 groups (#1812)

From the PR review: an application's 102 {NAME ... 102 } group before a
proxy's own groups can hold a group 91, which was read as the class number
and overwritten on save. class_group finds the first 91 outside 102 groups
(before xdata), for reading and renumbering alike (test
proxies_keep_their_class_when_classes_are_rewritten, now with a {MYAPP
group holding a 91 that stays as it was).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Nnsfu3QGqjD1aFZbBgUWP

---------

Co-authored-by: Tyrel Haveman <1434399+tyrel@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-24 01:55:07 -04:00
BooksandBrandon Thomas a94fc0898b Diameter dimensions: the *D block's text keeps the sign as %%c (fixes #1645) (#1696)
The *D block and EXPLODE's MTEXT took the dimension's decoded text, so
%%c arrived as U+2300, which no SHX font has: other programs plotted a
diameter dimension in an SHX style without its sign. They now write
DimGeometry::mtext_contents(): the sign as %%c, which every font draws
(and CADCraft decodes), a % that would run into a code as %%%.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-24 01:53:32 -04:00
WillandBrandon Thomas a0dced6d45 fix(render): honor DIMDSEP in angular dimensions (#1670)
Use the effective dimension style's decimal separator when formatting
angular labels, preserving the public formatter's decimal-point default.

Add rendering and stored DXF/DWG block regression tests, and update the
file-format progress documentation.

Fixes #1646.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-24 01:53:16 -04:00
Jonasandcloudj 941ea341bc fix(ui): toggle QuickCalc with Ctrl/Cmd+8 (#2001)
Co-authored-by: cloudj <jonbahnes@gmail.com>
2026-10-24 01:51:47 -04:00
Antoine Boxho 71882cf5e6 constraints: bound combined expression and parameter depth (#1959) 2026-10-24 01:51:42 -04:00
64c35cadff Distinguish polyline midpoint grips from vertices (#1950) (#1958)
* ui: distinguish polyline midpoint grips

* docs: the midpoint-grip row belongs in the revision history (CodeRabbit)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 01:51:36 -04:00
78c1653f50 loops: implied closed loops from loose LINE and ARC chains (#1906)
* loops: implied closed loops from loose LINE and ARC chains

LOOPS is a read-only inquiry command that chains LINE and ARC endpoints
meeting within a tolerance. Closed loops report their member handles,
perimeter, area and winding (exact with arcs, like SHAPES); open chains
report the endpoint gap, where it is and their length; branch junctions
(three or more ends at one point) are reported but excluded from the
chains rather than resolved.

The tolerance defaults to 1e-9 x the extents diagonal and is an option.
The quadratic endpoint pairing is capped (maxPairs, 1,000,000 by default)
and reports capped/pairTestsNeeded instead of guessing from a partial
pairing. Degenerate lines and arcs are counted under skipped; full-circle
arcs close on their own. The command is read-only.

Fixes #1437

* loops: cap maxPairs at 100,000,000 so a caller can't ask for unbounded work

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 01:51:11 -04:00
fda82da3df RECTANG: add a Center Rectangle construction mode (#1895)
* RECTANG: add a Center Rectangle construction mode (#1127)

The first point of RECTANG was always a corner. A new Center option makes it
the rectangle's centre, so the second point (or Dimensions/Area) builds it
outward evenly: centre 10,10 with a corner at 14,16 gives 6,4 to 14,16.

- interactive: a Center keyword at the first prompt (Chamfer keeps the c
  shortcut; Center matches ce or the full word), which also changes the
  prompt to Specify center point for rectangle;
- JSON: center: true on rectang, keeping p1/p2 and the default corner mode
  unchanged;
- rotation turns about the centre; chamfer, fillet, width, elevation and
  thickness apply as before; the rubber band previews the centred rectangle;
- params documentation updated.

Tests: centre-point geometry for p2, dimensions and area, rotation, fillet,
chamfer, width, thickness, the interactive keyword and preview, and the
unchanged default corner mode.

Fixes #1127

* docs: document the functions touched by #1127

* RECTANG: the Center option is CEnter, so C stays Chamfer's shortcut (as PLINE)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 01:51:06 -04:00
Nicholas Gower 1c7d3f0fae CLI: a --cmd can act on a handle an earlier --cmd returned ({last}) (#1871)
* cli: act on a handle an earlier --cmd returned ({last}) (#1663)

A run's --cmd steps were independent: mview answered {"viewports":["100"]}
but viewport.set could not use that handle, so it failed with "no viewport
given".

The batch runner now remembers the last object handle a --cmd returned (the
strings in its result that name an entity of the active drawing, so counts and
names are not mistaken for handles). A parameter value of exactly {last} in a
later --cmd, at any depth, is replaced with it; every other value is passed
through unchanged. With no handle yet the run fails with a clear error instead
of passing a bad handle to the command.

Fixes #1663

* cli: keep {last} to the drawing and handle fields that returned it

Read handles only from result handle fields, never arbitrary strings: a group named like another object's handle is a name, not a reference. Clear the remembered handle when new, open, document.switch (or a script running one) makes another drawing current, and keep a handle the new drawing's own command returns.
2026-10-24 01:50:40 -04:00
Books dadd1f227f AutoLISP: entmake defines blocks with BLOCK … ENDBLK (fixes #1662) (#1765)
* AutoLISP: entmake defines blocks with BLOCK ... ENDBLK (fixes #1662)

A BLOCK list wasn't an entity, so entmake returned nil and the entities
after it went into model space. Now a BLOCK list begins a definition
(name, flags, base point, description), the entities made meanwhile go
into it, and ENDBLK adds the block (redefining one of the same name, or
naming an anonymous *U block) and returns its name. An open definition,
an xref, an invalid name or a block that inserts itself is refused with
nil, using BLOCK's own name and self-reference checks.

* AutoLISP entmake blocks: review fixes (anonymous names keep a star, no redefinition during REFEDIT, a failed routine drops its open definition)

An anonymous block (flag 1) named without a * got a name like FO1; it now
gets *U. ENDBLK refuses to redefine a block REFEDIT is editing, as BLOCK
does. A routine that fails drops the definition it began, so the next
routine's entities go into the drawing; one that ends normally still
leaves it open.

* AutoLISP entmake blocks: refuse an anonymous block that inserts itself

The self-reference check ran only for named blocks, so an anonymous *U
block holding an INSERT of the name it was about to get (*U1 in an empty
drawing) was stored. The check now runs after the anonymous name is
given, for both kinds.

* AutoLISP entmake blocks: create a definition's layers only when the block is added

Each pending entity created its layer at once, so a definition ENDBLK
refused (a self-reference, REFEDIT) or a failed routine dropped left an
orphan layer. end_block now creates the layers after its checks pass,
just before it adds the block.
2026-10-24 01:49:54 -04:00
47e978b58e DXF/DWG: keep a file's own arrowhead blocks with standard names (_Dot, _DotSmall) (#1694)
* DXF/DWG: keep a file's own arrowhead blocks with standard names

Reading dropped every block with a standard arrowhead name (`_Dot`,
`_DotSmall`, ...) that no INSERT used, since CADCraft regenerates its own on
save. A block only a dimension style names (DIMBLK) was dropped too, so
AutoCAD's `_Dot` (a polyline with width) or a user's redefinition came back
as CADCraft's disc of 24 SOLIDs. Now only blocks whose content is exactly what
CADCraft writes are dropped; any other content is kept and saved as it was,
through DXF and DWG. PURGE keeps arrowhead blocks that dimension styles or
dimension overrides name.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Arrowheads: compare all properties, trim names in PURGE, test every standard arrow

is_generated_arrow now requires the whole Common to match, so only blocks
exactly as CADCraft writes them are regenerated. PURGE trims arrowhead names
from dimension styles and overrides. A new test writes every standard
arrowhead to DXF and DWG and checks none reads back as a user block. Review
of #1694.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: ffishman <20211677+ffishman@users.noreply.github.com>
2026-10-24 01:48:46 -04:00
b517e0749d i18n(pt-BR): native-speaker terminology review (#1665)
* i18n(pt-BR): native-speaker terminology review

Fix mistranslations (Thaw Layer read "Ativar camada", Snap type and the
snap settings label were translated as object snap, Revolve read
"Revolucionar", "Sobre-restrito" spelling) and make the CAD vocabulary
consistent with Brazilian usage: "snap" for every snap mode, "viewport"
and "grip" kept as loanwords, "Multilinha", "Explodir".

Update the Portuguese row in localization-parity.md (it still read 0%)
and the pt-BR note in localization.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcdyVcguHgMao2p9C7rVf2

* i18n(pt-BR): align offset and layer on/off wording with Brazilian usage

Brazilian university AutoCAD handouts (UFSC, UFSCar, UFRGS) keep
Offset as a loanword and describe layers as ligar/desligar.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcdyVcguHgMao2p9C7rVf2

* i18n(pt-BR): fix Bind, keep prompt keywords and SPDX in English

Bind merges an xref into the drawing, so Integrar, not Vincular.
Prompt keywords (Top/Zero/Bottom, Multiple) stay in English because
the command line only accepts the English keywords.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcdyVcguHgMao2p9C7rVf2

---------

Co-authored-by: Rodrigo Gressler <20211677+ffishman@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 01:48:25 -04:00
aiongg dd1f0531ac i18n: missing translations never fail; cargo xtask i18n reports them (#2013) 2026-10-23 15:40:12 -04:00
aiongg 9cf115e808 CI: one test binary per crate, nextest, line tables only (#1989)
* Build: line tables only in dev, no debug info for dependencies

* Tests: one integration-test binary per crate

Each crate's tests/*.rs becomes a module of tests/it/main.rs. Files that
install process-wide state (the file io hooks, user aliases, SAVETIME and
SAVEFILEPATH, user linetype and pattern files, registered fonts) stay their
own test binaries.

* xtask ci: nextest, tests without examples, one wasm check call
2026-10-23 13:34:26 -04:00
Dylan M. Taylorandskylartaylor ca578a8fb7 security: authenticate the local control server with a per-session token (CWE-306) (#1844)
* security: authenticate the local control server with a per-session token (CWE-306)

The optional `--control PORT` server listens on loopback TCP and dispatched any
JSON request without authenticating the peer. Loopback TCP does not distinguish
operating-system users, so another local account could connect and drive the
desktop user's command and file authority (for example reading files through
native Lisp, or create/truncate writes).

Authenticate the peer with an unguessable per-session token:

- New `cadcraft_mcp::control_token` module: `issue(port)` generates a 256-bit
  token and writes it to `cadcraft-control-<port>.token` in the per-user runtime
  directory with owner-only permissions (0600 on Unix; per-user LOCALAPPDATA on
  Windows). `read(port)` reads it; `matches` compares in constant time.
- The control server issues the token at startup and requires every request to
  carry a matching `token` field; requests without one get an `unauthorized`
  reply and the connection is closed before dispatch.
- The MCP bridge (`Remote`) reads the token from the owner-only file — which
  needs the desktop user's own filesystem access — and sends it on every
  request, so the bridge keeps working unchanged for the legitimate user. A
  different local user cannot read the token and cannot authenticate.

Updates `docs/control-protocol.md`. New tests cover token issue/read/compare,
file permissions, and that the server refuses unauthenticated requests while
accepting authenticated ones.

Addresses finding row 48.

* fix: recover control authentication across server restarts

---------

Co-authored-by: skylartaylor <26588250+skylartaylor@users.noreply.github.com>
2026-10-22 17:35:18 -04:00
323f4629eb Add Greek (el) interface localization (#1307)
Co-authored-by: Camthalion77 <2944375+Camthalion77@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 03:02:49 -04:00
8c249aa722 Menu bar switches to the hovered menu once one is open (fixes #1567) (#1571)
With a menu of the in-window bar open, pointing at another title now
closes the open menu and opens that one. With no menu open, pointing
still opens nothing. Clicking the open title, clicking elsewhere, Esc
or choosing an item close the menu as before.

The bar now draws its titles first, then opens the hovered title's menu
if another bar menu is open, then draws the menus. egui 0.36's MenuBar
has no hover switching of its own.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 02:57:40 -04:00
a0aa0863bf Turkish interface (Türkçe) (#1529)
Add a Turkish catalog (tr.tsv, 1,330 rows): the union of the existing
catalogs, four menu headings (Ölçülendirme for the Dimension menu, Ölçü for
the object) and single-form counts, since a Turkish noun stays singular after
a number. No marked UI literal falls back to English. Prompts read "… girin"
for Enter and "… belirtin" for Specify; clickable keywords (Top, Zero, Bottom,
_tan to, nX/nXP) keep the spelling the engine accepts.

Register the language (Preference::Turkish, `tr`, Türkçe) and add locale,
preference, count, menu-heading, live-switch, dialog and font tests. Docs,
README and ATTRIBUTION list it; the translation is AI-assisted, the running
app was reviewed by a native speaker, and line-by-line proofreading is still
to do.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 02:57:28 -04:00
b4509d2368 DWG: leave out orphaned DGN line style components, so DGN-bloated drawings open (fixes #1482) (#1501)
* DWG: leave out orphaned DGN line style components, so DGN-bloated drawings open (fixes #1482)

A DWG that has been through MicroStation can carry hundreds of thousands of
DGN line style components no line style definition uses. In an 82.8 MB site
plan they were 888,483 of 889,053 and made the DXF rendition 1.2 GB, over the
1 GB limit, so the file didn't open. After the failsafe read, components no
LSDEFINITION reaches (through compound and point components) are left out with
their dictionary entries, and a read note says how many.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* contributors: add my real name for the About window credits

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-19 02:57:16 -04:00
41e0860725 ALIASEDIT opens the Command Aliases dialog (#1381) (#1391)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 02:56:52 -04:00
0d23cc3cb3 MOCORO: move, copy, rotate and scale a selection from one base point until eXit (#1388)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 02:56:40 -04:00
397b8e156a MLEDIT Merged Tee: the first multiline's inner elements run on to the second's (#1375)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 02:56:16 -04:00
12567f14f2 BURST: explode block references with their attribute values as text (#1345)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 02:56:04 -04:00
95e86caea5 TXT2MTXT: selected TEXT combined into one MTEXT top-down, or one MTEXT per TEXT, with SEttings (#1343)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 02:55:53 -04:00
7adf8d89cd BREAKLINE: a polyline with a break symbol between two points, with Block, Size and Extension (#1340)
Tools > Express Tools > Break-line Symbol draws one polyline from the first point to the second with a zigzag break symbol at a picked location (the midpoint on Enter), as AutoCAD's Express Tool does. Size scales the symbol (times DIMSCALE), Extension runs the line past both points, and Block takes a block of the drawing whose two points on the Defpoints layer mark where the line meets the symbol. The built-in symbol is our own geometry. One undo step; the options are remembered for the session.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 02:55:41 -04:00
b0ed57f1c7 i18n(sv): translate the 95 strings added since the Swedish catalog (#1258)
Covers the Hatch and Gradient dialog, the Block Editor, REFEDIT, multileader
editing, the Options selection settings, the MTEXT editor and recent files.
The Swedish catalog now has every key of the Ukrainian reference catalog.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-19 02:55:14 -04:00
30db7296c4 선택 객체 조회 필터와 MCP 연동 추가 (#1253)
## 변경 내용
- entities 명령에 selected 불리언 필터를 추가해 현재 공간의 선택 객체만 조회
- 기존 종류·레이어·영역 필터와 결합하고 필터 적용 후 페이지 처리
- MCP query_entities 스키마와 명령·제어 프로토콜 문서에 옵션 공개
- 빈 선택, 중복·오래된 핸들, 다른 공간 객체, 잘못된 입력 및 조회 전후 상태 보존 회귀 테스트 추가

## 검증
- cargo xtask ci 6단계 통과: fmt, clippy, 테스트 2,012개, assets, layers, wasm
- 별도 .NET 클라이언트와 실제 헤드리스 엔진의 localhost 통합 검증 통과
- 실제 데스크톱 UI 조작 및 고객 DWG 왕복 검증은 수행하지 않음

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 02:55:02 -04:00
a76775bf2b Holding Shift overrides ortho while it is held, as in AutoCAD (#1198)
At a point prompt with a base point, holding Shift turns ortho on when
ORTHOMODE is off and off when it is on; releasing it restores the setting.
ORTHOMODE itself is never changed. While the override turns ortho on, polar
tracking is off, as with ortho on.

The UI already passed the Shift state to Session::snap_cursor (for POLARMODE
bit 8's Shift to acquire), so the change is in the engine only: snap_cursor
applies the override to its copy of the settings. Window corners (ZOOM
Window) and prompts without a base point are unaffected.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-19 02:54:51 -04:00
6576cac1a3 JOIN with a single arc or elliptical arc offers cLose: the arc becomes its circle, the elliptical arc its full ellipse (fixes #1175) (#1197)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 02:54:39 -04:00
4125b351cd Add HATCHEDIT boundary add and remove (#1156)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 02:54:28 -04:00
6d15a40f43 Selection cycling: a click over overlapping objects lists them, Shift+Space steps through them (#1149)
* Selection cycling: a click over overlapping objects lists them, Shift+Space steps through them (fixes #1143, fixes #1145)

* canvas: keep viewport_content's doc comment on it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 02:54:16 -04:00
bcd0b3745e fix(render): stabilize dashed highlights of filled objects (#1046)
* fix(render): keep filled highlight dash anchors stable

* outline test: as_chunks for clippy

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 02:53:38 -04:00
ba27f36b58 Inquiry: SHAPES reports closed-shape geometry facts (#1507)
* Inquiry: SHAPES reports closed-shape geometry facts

`shapes` is a read-only command that reports, for every closed shape in the
current space (closed polylines, circles, full ellipses, closed splines),
its perimeter, area, centroid, winding, first self-intersection and
coincident vertices, exact with arcs and bulges. It filters and pages like
`entities` and counts other matches under `skipped` with the reason.

`Polyline::centroid` and `polygon_centroid` give the area centroid.

Fixes #1436.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* shapes: cap the quadratic self-intersection test at 4000 edges

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-19 02:53:10 -04:00
9f7a2422ea feat(engine): support AUDITCTL system variable and .adt log generation (gaps F4) (#1504)
* feat(engine): support AUDITCTL system variable and .adt log generation (gaps F4)

* audit: write the .adt log only beside a saved drawing

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 02:53:04 -04:00
Antoine Boxho 6bd09ea468 docs: customizing and automating CADCraft (for #1561) (#1569)
A short guide for people porting add-ons: what runs today (LISP routines
and startup files, .scr scripts, .pgp alias files, the JSON control
channel, MCP, cadcraft-cli), what doesn't yet (Visual LISP ActiveX, DCL,
reactors, compiled plug-ins), and where each is documented. The README's
automation section links to it.
2026-10-19 02:33:28 -04:00
Marco Arnulfo ef599e58e0 BREAK opens a closed polyline instead of refusing it (fixes #1173) (#1191) 2026-10-19 02:30:41 -04:00
Marco Arnulfo 27fd074f54 AutoLISP entity lists hold angles in radians, as entget does in AutoCAD (fixes #1165) (#1190) 2026-10-19 02:30:36 -04:00
leonardopbatista 7164b6b5b1 feat(options): save a two-click selection window preference (#1032) 2026-10-19 02:28:41 -04:00