Commit Graph
593 Commits
Author SHA1 Message Date
Valentin KleinandClaude Opus 5.5 c55feb79da SPLINE Close is periodic, options report; POLYGON typed radius has a flat bottom; CIRCLE rejects bad input (fixes #238) (#240)
SPLINE Close now builds a smooth closed (periodic) curve through the fit points
instead of repeating the first point. The geometry gets Spline::from_fit_points_closed:
chord-length parameters including the closing chord, a periodic knot vector, and the
first three control points repeated at the end. The fit data no longer duplicates the
first point. DXF writes group 70 bit 2 (periodic) for it, and a file whose closed spline
needs refitting is refitted closed. SPLINEDIT Close/refit/move and fit-point grips keep
a closed spline periodic.

SPLINE Method asks Fit/CV, and CV continues as SPLINE by control vertices. Knots, Object,
start/end Tangency and toLerance report "not available yet" and stay at the prompt.
Garbage text reports "Point or option keyword required.".

POLYGON with a typed radius puts the bottom edge horizontal for Inscribed and for
Circumscribed. The JSON form uses the same orientation when no angle is given.

CIRCLE reports "Point or option keyword required." for an invalid token at the centre
prompt, and "Invalid point." at the 3P/2P point prompts, then asks again.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:34:24 -04:00
by TruittandClaude Opus 5.5 afe8b93255 Save As: offer DWG, with the drawing's format first (#198)
The Save As dialog listed DXF, SVG, PNG and PDF, though CADCraft writes
DWG. On macOS the dialog accepts only listed extensions, so a name typed as
`part.dwg` was changed to .dxf and the drawing was saved as DXF.

Save As now lists "Drawing (DWG)" too. The existing suggested-type-first
ordering does the rest: a drawing opened from DWG defaults to DWG, while new
drawings and DXF files keep DXF first. The list moves into
`filters_for_save` with a unit test, and the label gets its Ukrainian
translation following "Drawing (DXF)".

Related to #14 (the Open half was fixed in #21).


Claude-Session: https://claude.ai/code/session_019uMCkRAB8DMd8MEmoY9Vyr

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:34:18 -04:00
STE1200andClaude Fable 5.1 be8ae81c40 DXF read: map mirrored OCS (extrusion 0,0,-1) of 2D entities to WCS (#277)
AutoCAD and BricsCAD store a mirrored block reference, and many 2D
entities drawn in a mirrored UCS, with extrusion (0,0,-1). Their points
and angles are then in an OCS whose x axis runs the other way. The reader
only honoured that for CIRCLE, ARC, LWPOLYLINE and SOLID/TRACE, so
mirrored blocks, hatches and texts landed on the wrong side of the
y axis (e.g. profile blocks at x -57618 instead of +57618).

- INSERT: insertion point through the OCS, negative x scale, opposite
  rotation and negative column spacing. Only for extrusions parallel to
  -Z (x/y noise up to 1e-3); a tilted block plane needs a 3D transform
  that Insert cannot hold and is left as read.
- HATCH and R12 2D POLYLINE: boundary x and bulges mirrored like
  LWPOLYLINE; the hatch pattern angle mirrors too.
- TEXT/ATTDEF/ATTRIB and MTEXT: points through the OCS; drawn readable
  (MIRRTEXT = 0) with the mirrored footprint, so the direction turns and
  left/right justification or attachment swap.
- ELLIPSE: centre and major axis are WCS, but the minor axis is
  extrusion x major, so the parameter range is negated.
- DIMENSION: the text midpoint (11) is the one OCS point.

Checked against an independent DXF library on private drawings: every
INSERT transform (plain, mirrored, tilted) matches, as do the mirrored
arcs, ellipses, polylines and hatches. The remaining extents outliers in
those drawings come from references to empty blocks, which still add
their insertion point to the extents.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-10 15:34:12 -04:00
Valentin KleinandClaude Opus 5.5 a15d0e5b8e DIMCONTINUE/DIMBASELINE: one snap index per chain, cap the points (#279) (#281)
With DIMASSOC on, every chained dimension looked up the objects under
its definition points by scanning the whole space, and each point adds
an entity, so a long `points` list took quadratic time (16,000 points:
2.7 s continue, 10 s baseline). The chain now builds one x-sorted
SnapIndex up front. Dimensions are no snap targets, so the index stays
valid while the chain adds them, and each lookup is a binary search. The
index picks the same snap as find_snap: lowest rank first, then the first
object in space order. A call with more than 100,000 points is refused.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:34:07 -04:00
Valentin KleinandClaude Opus 5.5 1d1fe4fc6d BLOCK refuses a definition that references itself; names are case-insensitive (fixes #250) (#252)
Redefining a block with a selection that holds a reference to that block,
directly or through nested blocks, is now refused with "Block "B"
references itself." and changes nothing. Before, the block ended up
containing itself and lost its original geometry. At the command line
the message ends -BLOCK, as for any definition that can't be made.

Redefining with a name that differs only in case (Door, then DOOR)
replaces the existing block instead of adding a second one.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:34:01 -04:00
Valentin KleinandClaude Opus 5.5 1bd1c5cfa2 io: only the first CADCraft xdata string marks the PAPERVIEW viewport (#253)
dxf_ext::is_paper_view (#225) matched any 1000 string in the CADCraft xdata, but later strings
are layer names (VPFROZEN, VPCOLORS). A real viewport with a frozen or recoloured layer named
PAPERVIEW was therefore dropped on read. Only the first string, where the writer puts the
marker, counts now. Regression test in tests/paperview_marker.rs.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:33:56 -04:00
Valentin KleinandClaude Opus 5.5 3ef0edaed9 Layout viewports scale model-space linetypes by PSLTSCALE (fixes #257) (#259)
With PSLTSCALE on (the default), dashes of model-space objects seen through a
viewport keep their paper-space length whatever the viewport scale; before,
a 1:100 viewport drew them 100 times too short, so dashed lines looked solid
in layouts and PDF plots. PSLTSCALE 0 keeps the old behaviour.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:33:50 -04:00
Valentin KleinandClaude Opus 5.5 4c056c54bd Points draw with the point style (PDMODE/PDSIZE) (fixes #261) (#265)
POINT objects were always a one-pixel dot. They now draw the figure PDMODE
picks (nothing, plus, cross, tick, with an optional circle and/or square)
at the size PDSIZE gives: drawing units when positive, a percentage of the
view height when negative, 5 % when 0. The canvas passes its view height;
exports and plots use the plotted window or the extents.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:33:43 -04:00
Valentin KleinandClaude Opus 5.5 575e690cc5 Bound block expansion in drawing and extents: nested, self-referencing or arrayed blocks no longer hang (fixes #255) (#267)
* Bound block expansion in drawing and extents (fixes #255)

MAX_BLOCK_DEPTH bounds only the depth of nested block references, so a
small DXF with a self-referencing block, blocks that each insert the next
several times, or one large MINSERT expanded to billions of copies and
hung the app (drawing, PNG/SVG/PDF export, extents, saving).

- render: a per-display-list budget (MAX_BLOCK_EXPANSION, 2 million
  block copies and entities drawn inside blocks); past it the remaining
  block contents are not drawn.
- doc: entity_bounds computes each block's content bounds once per call
  and reuses them for every further reference.

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

* Move Builder::expand below the drawing primitives

Pure code motion so this branch merges cleanly with fix/272-aci7-only-flips
(#272), which changes the Builder::polyline signature next to it.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:33:37 -04:00
Valentin KleinandClaude Opus 5.5 5eb7fd452a Programmatic SPLINE limits the degree to 1..10 (fixes #268) (#269)
spline {control, degree} took any degree up to the control-point count
minus one; tessellation and extents grow with its square, so a few
thousand control points with a high degree hung the app on every redraw.
The Degree prompt, spline.cv and DXF input already limit it to 1..10.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:33:31 -04:00
Valentin KleinandClaude Opus 5.5 160b0fd644 Draw raster points as a filled path so debug builds don't panic (#271)
tiny-skia's anti-aliased fill_rect hits a debug_assert when a rect under
two pixels wide has both sides mid-pixel, so PNG export panicked in debug
builds depending on where a point landed. Fill the same 1.5 px square
through the path filler instead.

Fixes #270.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:33:25 -04:00
skylar taylor-barrick e09fb50627 UI.9: integrate shared workspace docking and preserve CAD controls 2026-10-10 13:30:09 -06:00
Brandon ThomasandClaude Opus 5.5 17a41184ca Fix two tests after the community PR batch
- entity_props_roundtrip: look for the Standard text style inside the STYLE
  table; #216 now also writes Standard multileader and table styles.
- uk.tsv: translate the Accessibility menu and Show System Cursor (#187).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:02:46 -04:00
Brandon ThomasandClaude Opus 5.5 06f742c787 Clippy (as_chunks) and rustfmt after the community PR batch
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:50:54 -04:00
Valentin KleinandClaude Opus 5.5 27d6bf2548 DWG: keep true colours, transparency, table styles, layouts and viewport ids (fixes #223) (#225)
DWG saves convert our R2000 DXF with acadrust, which wrote an R2000 DWG:
no true colours, no entity transparency, no table styles, and its R2000
reader puts every layout's paper-space entities into the first layout.
The bridge now writes at least AutoCAD 2004 (AC1018) DWG; the DXF itself
stays R2000. acadrust goes to 0.6.3 (explicit transparency encoding).

DWG doesn't store viewport ids: readers number a layout's viewports in
order, and id 1 is the paper-space viewport (the sheet). CADCraft keeps
that one implicit, so its first viewport came back as id 1 and wasn't
drawn. The DXF writer now puts a paper-space viewport (id 1, marked with
CADCraft xdata) ahead of a layout's viewports when it has none, and the
reader drops it again. Viewports also get status 68 and the always-set
bit 32768 in group 90, so other readers don't take them as "off".

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:40:28 -04:00
MarekSpodBiedry 7f84f9c874 Linetype rename keeps layer, object and current-linetype references (#74) (#195) 2026-10-10 14:40:13 -04:00
Valentin KleinandClaude Opus 5.5 3b73884abd Deliver browser saves as downloads; keep unsaved state when they fail (#133) (#193)
On wasm, saving skipped the file write but still marked the drawing saved,
renamed its tab and reported success, so nothing reached the user. Saving
now hands the bytes to a host delivery hook (`file::set_deliver`); the web
app installs a small web-sys download helper (Blob + object URL + temporary
<a download>), reusable for other files such as plots. Without the hook, or
when the download can't be started, save errors and the drawing stays dirty.
A started download counts as saved and keeps the name for later QSAVEs.
The web app's Save/Save As (menu, close prompt) download under the drawing's
name, as there is no file picker in the browser.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:39:58 -04:00
MarekSpodBiedry 3d125493f9 Layer rename re-points paper-space and block-definition entities (#75) (#191) 2026-10-10 14:39:43 -04:00
MarekSpodBiedry e13de2c2f1 Layer delete and purge keep layers used by block definitions (#72) (#189) 2026-10-10 14:39:28 -04:00
Nigel Ihza Firdausy 9441dad29f fix(geom): avoid false line-circle tangencies on long construction lines (#214)
* fix(geom): avoid false line-circle tangencies on long lines

* fix(geom): construct tangent points relative to the projection foot
2026-10-10 14:39:13 -04:00
Nigel Ihza Firdausy c52f4d190e fix(geom): measure bulged polylines and distant polygons accurately (#212)
* fix(geom): compute polyline bulge areas without tessellation error

* fix(geom): preserve polygon area far from the coordinate origin
2026-10-10 14:38:57 -04:00
Nigel Ihza Firdausy f38b3a6447 fix(geom): calculate tight ellipse and arc bounds analytically (#211) 2026-10-10 14:38:42 -04:00
Valentin KleinandClaude Opus 5.5 a560048dd0 Web: File > Open… shows the browser's file picker (fixes #55) (#199)
The web app ran CadApp with Services::default(), so OPEN found no file
picker and returned without doing anything: no picker, no message.

The web app now installs an open picker (apps/cadcraft-web/src/upload.rs,
web-sys only): a hidden <input type="file" accept=".dxf,.dwg"> is
clicked, the chosen file is read in the background (Blob.arrayBuffer)
and handed to the app on its next frame, which runs OPEN with
{data, name} through the new CadApp::open_bytes. Cancelling the picker
does nothing. Files dropped on the page, which eframe hands over unread
on the web and the UI ignored there, take the same route.

Files over 256 MB (CadApp::MAX_OPEN_BYTES; the browser holds the drawing
several times while decoding it) are refused before they are read.
Unreadable or rejected files report on the command line and in the
status bar; nothing panics.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:38:20 -04:00
Valentin KleinandClaude Opus 5.5 238162f332 Option to keep the system cursor over the drawing area for magnifiers (#39) (#187)
The canvas hides the OS cursor (CursorIcon::None, which winit turns into
ShowCursor(FALSE) on Windows) and draws its own crosshair, so Windows
Magnifier's "follow the mouse pointer", screen readers, recorders and
remote desktops lose the pointer there. View > Accessibility > Show System
Cursor (ui.toggle.systemcursor, ui.set {"systemCursor": true}, or
CADCRAFT_SYSTEM_CURSOR=1 at startup) keeps a system crosshair visible at
the centre of the drawn one.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:38:13 -04:00
Valentin KleinandClaude Opus 5.5 d4e1345974 macOS: the window title lines up with the traffic lights (fixes #161) (#202)
AppKit draws the traffic lights centred in its standard 28 pt title-bar
band, and the integrated title row is that band, but the title text sat
1 pt below the centre line. It is now centred on the lights. Neither
winit nor eframe can move the lights without AppKit calls (unsafe), so
the row fits them instead, as in WordCraft (storytold/wordcraft#255).

The geometry lives in chrome.rs. A long drawing name in a narrow window
no longer slides under the lights: the title starts after them, with the
same gap the lights keep from the top of the window.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:38:04 -04:00
Valentin KleinandClaude Opus 5.5 6d7502abf9 UNDO asks for the number of operations; U undoes one (fixes #241) (#242)
UNDO now prompts "Enter the number of operations to undo or
[Auto/Control/BEgin/End/Mark/Back] <1>" and undoes that many; Enter undoes
one. Its other options say they are not available yet (Auto and Control
still take their value), so nothing that follows is run as a command.
The JSON form (`undo {count?}`) is unchanged.

U is its own command, as in AutoCAD: it undoes one operation without
asking and is what Edit > Undo, Cmd+Z and the toolbar button run.

An interactive command outside undo that changed the drawing (UNDO)
records no undo step when it finishes, matching `execute`.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:37:58 -04:00
Valentin KleinandClaude Opus 5.5 ca882b38e2 TEXTEDIT prompt, Undo and Mode; MTEXT takes lines until an empty one (#234) (#239)
TEXTEDIT's prompt no longer reads "or or". Undo reverts the last edit made
in this TEXTEDIT, and Mode switches between Multiple (keep selecting) and
Single (end after one edit). At the command line and in scripts, MTEXT
takes one paragraph per input line until an empty line, instead of ending
after the first line and running the next one as a command. The Text
Editor window sends its contents and then Enter, so it still ends MTEXT.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:37:52 -04:00
Valentin KleinandClaude Opus 5.5 a6b728b0f7 AREA Object/Add area/Subtract area and DIST Multiple points take effect (#233) (#237)
AREA's Object option measures picked objects; Add area / Subtract area keep
a running "Total area" over areas measured by points or by object, and
Enter at the first-corner prompt ends the command. Length continues the
last segment by a typed length in AREA and DIST point sequences. DIST's
Multiple points measures a path and reports the running and total
distance. Arc now says it is not available yet instead of being ignored
(or ending AREA as Total).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:37:46 -04:00
Valentin KleinandClaude Opus 5.5 98a1be7455 ROTATE: Reference option (fixes #235) (#236)
"Specify the reference angle <0>" takes a typed angle or two points;
"Specify the new angle or [Points] <0>" takes a typed angle, a point
measured from the base point, or two points after `P`. The objects turn by
the new angle minus the reference angle. Previously Reference only said
"not available yet" and the values that followed were misread or run as
commands.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:37:40 -04:00
Valentin KleinandClaude Opus 5.5 ba6af44d36 ELLIPSE Rotation/Parameter and BREAK First point take effect (fixes #219) (#222)
ELLIPSE: after Rotation the next value is the rotation around the major
axis (ratio = cos angle, 0 to 89.4 degrees; a picked point gives the
angle from the centre), instead of the other-axis distance. At the
elliptical arc's start/end prompts, Parameter reads the values as
ellipse parameters and Angle switches back to true angles.

BREAK: First point asks for a new first break point; the selection pick
then only selects the object.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:37:34 -04:00
Valentin KleinandClaude Opus 5.5 89799f48d4 Keep raster images through DXF save; report entities a save can't keep (#208) (#217)
Raster images: the reader now follows an IMAGE's group 340 to its IMAGEDEF
and keeps the file path and the definition's name (its ACAD_IMAGE_DICT
key), pixel size and resolution unit. It also keeps the clip boundary (in
pixel coordinates), the clipping state, the display flags and
brightness/contrast/fade. The writer emits the IMAGE entity, plus one
IMAGEDEF per path and name, an IMAGEDEF_REACTOR per image,
RASTERVARIABLES, the ACAD_IMAGE_DICT and ACAD_IMAGE_VARS entries in the
named object dictionary, and the four CLASS records, as the DXF Reference
describes. An image without a clip boundary gets the full-image rectangle.
DWG save keeps the same data, except the resolution unit, which the DWG
bridge drops. The pixels are still not loaded.

Entity types CADCraft doesn't model (MLINE, MULTILEADER, 3DSOLID, REGION,
MESH, ...) are still not written. Writing their raw groups back into the
R2000 file would leave references to objects that aren't kept
(MLINESTYLE, MLEADERSTYLE, ACIS data in later versions), and some of the
types don't exist in R2000 at all. Another CAD program could then reject
the whole file. Instead, qsave, saveas and document.bytes now say on the
command line which ones weren't saved ("Not saved (CADCraft can't write
these object types yet): 1 3DSOLID, 2 MLINE") and list them by type in the
result's `notSaved`. `cadcraft-cli run --save` and `convert` print the
same line.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:37:26 -04:00
Valentin KleinandClaude Opus 5.5 6b43587279 DXF: keep linetype text, multileader styles, page setups, views, UCSs and groups (fixes #209) (#216)
Saving to DXF and reopening lost several table and object-section items.
They are now written and read as the DXF Reference describes:

- LTYPE: embedded text and shape elements (74 type flags, 75 shape number,
  340 STYLE, 46 scale, 50 rotation, 44/45 offset, 9 text). Shapes from a
  shape file get an unnamed shape STYLE record (70 bit 1). DashElement
  gains an `absolute` flag for absolute rotations (74 bit 1). The built-in
  FENCELINE1 names its text style (Standard).
- MLEADERSTYLE objects in ACAD_MLEADERSTYLE with arrow size, text height,
  landing gap, dogleg length and text style, plus $CMLEADERSTYLE, so the
  current multileader style survives reopening.
- LAYOUT plot settings: device (2), plot style table (7), plot area (74),
  custom scale (142/143, 147), scale to fit (70 bit 16 with 75 = 0),
  centred plot and lineweights (70 bits 4 and 128). The layout's saved
  paper-space view goes in CADCraft xdata on the LAYOUT object.
- VIEW and UCS table records, and GROUP objects in ACAD_GROUP (members
  that were written to the file). A named view's layer state is CADCraft
  xdata on the VIEW record.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:37:20 -04:00
Valentin KleinandClaude Opus 5.5 ec80db936e DXF: keep thickness, point angle, hatch origin/background/elevation, xref paths and the built-in font name (#213) (#215)
Saving to DXF and reopening lost several entity, block and style
properties:

- Thickness (group 39) is written for LINE, POINT, CIRCLE, ARC, TEXT,
  SOLID, TRACE, LWPOLYLINE and POLYLINE when non-zero.
- POINT writes its X-axis angle (group 50).
- HATCH: the origin goes into the pattern lines' base points (43/44), so
  other programs see the shift, and travels with the background colour as
  CADCRAFT xdata (1000 HATCH, 1011 origin, 1000 colour name); the DXF
  Reference has no groups for either. It is appended to the gradient
  xdata when there is one. The reader takes the elevation from the z of
  the first group 10.
- Xref blocks get BLOCK 70 bit 4 and their path in group 1, and the
  reader reads the path.
- The built-in "CADCraft Stroke" font is still written as "txt" for other
  readers; its name travels as CADCRAFT xdata (1000 FONT) and is used
  while group 3 is still "txt".
- Polyface (70 bit 64) and polygon mesh (bit 16) POLYLINEs from other
  programs are read as 3D faces instead of a 3D polyline through their
  vertices; face records no longer become vertices at the origin. Bad
  indices and counts are skipped.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:37:14 -04:00
Valentin KleinandClaude Opus 5.5 b4985c5d56 DXF: keep block descriptions and constant attribute definitions (fixes #204) (#207)
The ATTDEF writer put only the invisible bit into group 70, so constant
attribute definitions came back as variable ones: INSERT then created an
attribute for them, and other programs stopped drawing their text. The
constant bit (2) is now written too.

BLOCK's description was never written (BLOCK group 4) nor read, and the
insertion units (BLOCK_RECORD 70) and explodability (280) of blocks were
neither read nor written. All three now round-trip.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:37:07 -04:00
Valentin KleinandClaude Opus 5.5 b265cb1772 DXF: keep saved layer states through save and reopen (fixes #201) (#203)
LAYERSTATE.SAVE stored its states in Drawing::layer_states, but the DXF
writer never wrote them and the reader never read any, so every saved
layer state was gone after saving and reopening (DWG too, as it is
written from the same DXF).

They are now written like the parametric constraints: as JSON in a
CADCRAFT_LAYERSTATES XRECORD of the named object dictionary, read back
with the same payload and size limits. The XRECORD chunking and reading
are shared by both records.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:37:01 -04:00
MarekSpodBiedry e5a91f2eee Keep viewport layer colour overrides through DXF save and reopen (#99) (#197) 2026-10-10 14:36:53 -04:00
MarekSpodBiedry 932ba8cc58 Respect locked layers in Clear, Cut, Stretch, Trim, Extend, Fillet, Explode and SplineEdit (#87) (#196) 2026-10-10 14:36:48 -04:00
MarekSpodBiedry 6957ef8756 Keep the active drawing when closing an earlier inactive one (#76) (#192) 2026-10-10 14:36:37 -04:00
MarekSpodBiedry da4aa413c6 Reject two-line angular constraints over 180 degrees that can never be satisfied (#73) (#190) 2026-10-10 14:36:27 -04:00
MarekSpodBiedry 5a214a0cac Reject layer.set atomically: a refused call no longer applies other changes (#71) (#188) 2026-10-10 14:36:18 -04:00
Nigel Ihza Firdausy 2e3dbe029f fix(geom): preserve three-point circle precision at large coordinates (#210) 2026-10-10 14:36:05 -04:00
Brandon ThomasandClaude Opus 5.5 3ea7de1938 Progress docs: target-app parity, gaps, alpha gate, readiness by audience (#205)
Progress docs per craftrules standards/progress-docs.md: target-app parity, gaps, roadmap with alpha gate, readiness by audience (full / mainstream / essentials) with Opus 5.5 hours, and feature, format, geometry, hardware, UI and localization parity docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:13:04 -04:00
DmytroandClaude Opus 5.5 659d0fa125 i18n: add Ukrainian interface translation (#36)
Rebase onto main: the interface language is saved with main's other
preferences (cadcraft.prefs, beside the theme) instead of its own storage
key, on desktop and web; strings main added or changed in the touched
places (Dynamic Input settings, Properties header and current-property
rows, block picker, theme and command-history menu items, open/save
filters) are wrapped and translated; main's native-menu poll order is kept.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 13:48:44 -04:00
Brandon ThomasandClaude Opus 5.5 733fa94590 docs/parity.md: recompute after the community PR batch (295 commands)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 13:32:38 -04:00
1a4a477f91 Ask to save unsaved changes before closing the window (#34) (#47)
Closing the window (title bar, File > Exit, Cmd+Q) with unsaved drawings now
cancels the close and asks Save / Don't Save / Cancel for each one, through
the same in-app prompt as File > Close All (#172). The app quits once every
drawing has been saved or discarded; Cancel keeps it open. The control
channel's app.quit never asks.

Rebuilt on top of #172 from LloydNicholson's original PR.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 13:23:37 -04:00
3e0adadf94 Dynamic Input: pointer input boxes with Tab, comma and relative entry (#60)
* Dynamic Input: pointer input boxes, Tab separator, relative entry

While a command asks for a point and Dynamic Input is on, the cursor
tooltip shows a pair of value boxes, following AutoCAD's pointer input:

- The first point of a command is X and Y, absolute.
- Second and next points default to polar (distance and angle) measured
  from the last point, so no `@` is needed.
- Typing `,` after a value locks it as X and moves to Y; `<` locks it as
  the distance and moves to the angle. A locked value holds the cursor and
  shows a lock.
- Tab types the separator for the format on show, so `40` Tab `90` is 40
  units at 90 degrees, and with the Cartesian setting `40` Tab `40` is 40
  over and 40 up.
- Enter or Space sends the entry with `@` in front, filling an empty box
  from the cursor. A plain number and Enter is still direct distance entry;
  `@`/`#` entries, keywords and command names go through untouched.

The command-line text is the single source of truth: the boxes are a view
of it, so the command line always shows exactly what will be sent
(`40,`, `40<90`) and Backspace simply edits it.

Tab is taken from egui in the raw input hook (desktop and web) while no
widget has focus. Otherwise egui moves keyboard focus to the next button on
Tab and the command line stops receiving keystrokes.

Two new settings mirror AutoCAD's: DYNPIFORMAT (1 = second points start
Cartesian) and DYNPICOORDS (1 = absolute), through SETVAR and in Drafting
Settings under Dynamic Input. The tooltip uses the theme tokens, matching
the command line.

Scripts, the control channel and MCP never pass through the boxes and keep
the plain command-line rules. The parsing lives in a small pure module
(dyninput.rs) with unit tests.

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

* Dynamic Input tooltip: border colour from theme tokens

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-10 13:21:39 -04:00
kaffeochknivarandClaude Opus 5.5 a43e88e17a UNITS opens a Drawing Units dialog; SETVAR prompts at the command line (#30) (#38)
Typing UNITS ran the JSON form with no parameters, which changed nothing
and printed nothing. Typed or menu-invoked UNITS (no parameters) now opens
a Drawing Units dialog like QSELECT does: length type and precision, angle
type and precision, insertion units and a sample output. OK applies the
values through the `units` command; JSON calls run the command as before.

SETVAR had no prompts either, so `SETVAR LUNITS 2` ran with no name and
the remaining inputs were dropped. Space acting as Enter was not the
cause. SETVAR now asks "Enter variable name or [?]" and then "Enter new
value for NAME <current>"; `?` lists the variables, unknown names end the
command, and a value of the wrong type re-prompts.

sysvars::set now refuses variables that `get` derives from state
(DWGNAME, DBMOD, EXTMIN, ...) and keeps integer header variables integer,
so a typo can no longer store text in LUNITS.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 13:16:16 -04:00
MarekSpodBiedry d648e4d1f1 Show Ctrl/Shift shortcuts on Windows and Linux, symbols only on macOS (#53) (#54) 2026-10-10 13:16:01 -04:00
5c2a18638e DIMANGULAR: accept a circle (#20)
* DIMANGULAR: accept a circle

The first prompt offers "arc, circle, line" but picking a circle was
rejected. Picking a circle now uses its center as the angle vertex and
the picked point as the first endpoint, then asks for the second
endpoint.

* cargo fmt

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-10 13:15:47 -04:00
kaffeochknivarandClaude Opus 5.5 d144d7f371 SETVAR DIM* variables become style overrides for new dimensions (#66) (#129)
`SETVAR DIMSCALE 24` was stored in the header but never reached new
dimensions: they were created with no overrides, and the header DIMSCALE
was only a fallback for styles whose scale is zero.

As in AutoCAD, the header DIM* variables that differ from the current
dimension style are now its overrides, and new dimensions take them on
(`Drawing::dim_overrides`). Existing dimensions keep their size. Making a
style current, or editing the current style, sets the variables back to
the style's values (`Drawing::sync_dim_vars`), so
`dimstyle {"name": "Standard", "DIMSCALE": 24}` still works.
GETVAR/SETVAR read DIM* variables the header doesn't carry from the
current style.

Dimension extents now use the same scale as the geometry (the style with
the dimension's overrides) instead of multiplying by the header DIMSCALE.
A DIMSCALE of zero (AutoCAD: scale to the layout viewport) falls back to
the drawing's DIMSCALE and then 1 instead of 1e-9.

Reading DXF, $DIMDSEP becomes the separator character, as the DIMSTYLE
table already is, so AutoCAD's header variables don't turn into spurious
overrides.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 13:15:33 -04:00