ATTDISP asks "Enter attribute visibility setting [Normal/ON/OFF]" (default:
the current setting) and sets ATTMODE to 1 / 2 / 0. The renderer now reads
ATTMODE: OFF hides every attribute, ON also draws invisible ones, Normal
draws the visible ones as before.
ATTSYNC ("Enter an option [?/Name/Select] <Select>") brings every reference
of a block, in model space, layouts and other block definitions, in line
with the block's attribute definitions: missing attributes are added with
their defaults, ones whose definition is gone are removed, values are kept
for tags still defined (any case), and position, height, style and
visibility come from the definition.
Both live in the new cmd/blocks/attribs.rs.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
LAYMRG moves every object on the source layers (model space, layouts
and block definitions) to the target layer, drops the sources from
viewport freeze lists and colour overrides, and deletes the source
layers. LAYDEL deletes the layers and every object on them the same
way. Typed, both ask "Select object on layer to merge/delete or
[Name]:" until Enter (Undo drops the last one), LAYMRG then asks for
the target layer, and both warn and ask [Yes/No] <No> before changing
anything. Scripts answer Y. The JSON forms laymrg {from, to} and
laydel {names} never prompt.
Layer 0, Defpoints and xref-dependent layers are refused, and so is
merging a layer into itself. LAYDEL refuses the current layer; LAYMRG
merges it and makes the target current. Each run is one undo step.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Midpoint snaps halfway along the length of an elliptical arc or open spline.
Perpendicular and Tangent from a base point find every foot point and
tangent point on ellipses, elliptical arcs and splines (the one nearest the
cursor wins, as for circles): tangents to an ellipse exactly through the unit
circle, the rest by sampling the parameter range and bisecting each sign
change, with fixed iteration bounds. Quadrant on an elliptical arc offers only
the quadrant points the arc passes through.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- A dimension snaps to what it draws: the objects of its block (read from a
file), expanded like block references, or else the lines generated from it
(Endpoint on their ends, Midpoint, Intersection, Nearest); Node on its
definition points.
- TEXT justified other than left-baseline (as read from DXF, group 11)
snaps (Insertion) and grips at its alignment point; attributes too.
- The snap grid starts at SNAPBASE and turns by SNAPANG (drawing variables,
defaults 0,0 and 0 degrees). SETVAR takes `x,y` for point variables.
- Hatches are left out of object snaps unless OSNAPHATCH is 1 (new session
variable, default 0 as in AutoCAD).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Polar points and angle prompts read bare numbers in AUNITS (explicit
d/r/g, deg-min-sec and bearings still work) and measure directions from
ANGBASE in the ANGDIR sense; `d<<a` (degrees) and `d<<<a` (AUNITS)
measure from +X counterclockwise. XLINE Reference turns per ANGDIR;
included angles and sweeps take the units only. ANGBASE is held in
radians like $ANGBASE in DXF; SETVAR reads and reports it in degrees.
JSON params keep their meaning.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- A dimension on a layer with a viewport colour override now takes the
override inside that viewport (dimension and extension lines, arrows,
text), like every other object.
- A LEADER's arrowhead is DIMASZ x DIMSCALE of the leader's dimension
style; the drawing's DIMSCALE only applies when the style's is 0 (as
for dimensions), and both 0 no longer gives a zero-size arrow.
- Attributes are drawn with the properties of their attribute
definition, which AutoCAD copies to the attribute on insertion:
ByLayer = the definition's layer (layer 0 = the block reference's
layer), ByBlock = the block reference's colour and lineweight,
explicit colours and lineweights as set. An attribute on an off or
frozen layer is hidden. Attributes without a definition keep the block
reference's colour and now its lineweight too, instead of 0.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
tools/call checks the arguments against the tool's input schema: an
unknown tool, non-object arguments, a missing required argument or one
of the wrong type is a JSON-RPC -32602 error, as the MCP spec says, so
`command_line {}` no longer presses Enter and `script {}` no longer
succeeds. An empty batch `[]` gets one Invalid Request (-32600).
The `entities` command behind query_entities rejects a malformed window,
non-string type/layer and bad limit/offset instead of ignoring them.
`count` is every match, `returned` the page (`offset` is now in the tool
schema), and a window matches objects whose geometry is inside or
crosses it (the window/crossing selection test), with `crossing: false`
for entirely inside.
list_commands' filter matches command ids, labels and aliases, not any
text in the command's JSON.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Inputs to MOVE, SCALE, ROTATE, ARRAY, OFFSET and the rest are checked for
being finite, but finite inputs can still overflow: moving by 1e308 twice
put a circle at infinity, after which inspect showed null coordinates,
the extents were null and a DXF save wrote it at 0,0.
After a command changes the drawing (JSON `execute`, grip edits, and an
interactive command when it ends), the objects it changed are checked:
every number in their fields and their extents must be finite. If not,
the drawing and selection are put back, no undo step is recorded and the
command fails with "the result is too large: object … would get infinite
or undefined coordinates. Nothing was changed".
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Stretching a grip now drags the grips of the other selected objects that sit
on the same point, so connected lines stay connected, and the Move, Rotate,
Scale and Mirror grip modes transform every selected object about the hot
grip, as in AutoCAD. grip.move/grip.stretch take the other objects in
`handles`; with only `handle` they edit that object alone, as before.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A circle in the aperture replaced the best Nearest candidate without
comparing distances, so a closer line drawn earlier lost to it.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
SAVEAS / QSAVE with a .svg, .png or .pdf path wrote the export and then
marked the drawing saved under that file: the title became the image name,
the unsaved changes were cleared (no "Save changes?" on close) and a later
Save wrote the image again. Exports now only store the file; DXF/DWG saves
are unchanged.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
targets() fell back to the current selection when `handle` wasn't a hex
handle or `handles` wasn't an array, and dropped unreadable entries from
`handles`. A typo such as {"handle": "#101"} made ERASE, MOVE and the other
~70 commands that take objects act on whatever was selected (in a bridged
MCP session: the user's selection in the window). Such values are now an
error naming the bad value; the selection is used only when neither key is
given (or it is null).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Follow-up to #318: a huge array of an empty or tiny block could spin up to
1e8 cells in the snap path without touching the entity budget.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OFFSET's Erase and Layer keywords open their Yes/No and Current/Source
prompts (remembered in OFFSETERASE/OFFSETLAYER and shown in the current
settings line); Multiple keeps offsetting the newest copy until Enter and
Undo takes back the last offset. The JSON form takes `erase` and `layer`.
MOVE and COPY ask for a displacement after the Displacement keyword or
Enter at the base point prompt.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
After the insertion point, the command-line INSERT now asks "Enter X
scale factor, specify opposite corner, or [Corner] <1>", then "Enter Y
scale factor <use X scale factor>", then the rotation, so scripts like
`-INSERT B1 20,20 1 1 0` place the block as written instead of rotating
it by the Y scale and running the rotation as a command. A point (or
Corner) at the X scale prompt gives both factors from the opposite
corner.
The Basepoint, Scale and Rotate options at the insertion point prompt
now work: Scale and Rotate preset the value and skip that prompt later,
Basepoint picks a new base point on the block at its definition
position.
The JSON form takes `scale: [x, y]` and `basePoint` too.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Typed RENAME / -RENAME (and REN) asks for the object type
[Block/Dimstyle/LAyer/LType/Material/multileadeRstyle/Plotstyle/
textStyle/Tablestyle/Ucs/VIew/VPort], the old name and the new name,
then runs the rename command body, so the checks and the single undo
step match the JSON form. An unknown old name or a refused new name
re-prompts. Material, Plotstyle and VPort read both names and report
"not available yet".
The JSON form now renames blocks, as its params doc promised. Every
INSERT follows the new name, in model space, layouts and nested block
definitions, including inserts that carry attributes. INSNAME and
dimension-style arrowheads follow too. Duplicate names (any case),
invalid names and anonymous blocks are refused. Multileader styles,
table styles, named views and UCSs can be renamed as well. A layer
rename no longer records two undo steps.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Running snaps only saw a block reference's insertion point. Now the objects
inside it snap where they are drawn: nested references and MINSERT arrays are
expanded, placed exactly by the reference's transform (non-uniform scale
turns circles and arcs into ellipses), and hidden objects or objects on layers
that are off are left out. Insertion also snaps to visible attributes.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Linetype text elements (FENCELINE1, GAS-style linetypes from DXF files)
were drawn as part of the gap. Each text is now placed where the element
that carries it ends, at every repeat of the pattern: in its text style's
font, at the element's scale times the style's fixed height and the
linetype scale, shifted by its X/Y offsets along and across the line, and
rotated relative to the line direction or absolutely. Runs drawn
continuous (pattern smaller than a pixel) draw no text, and coarse
previews without text skip it. Shape elements need SHX shape files,
which CADCraft doesn't read, so they still draw nothing.
FENCELINE1 carries its circle on the first gap, centred between the two
gaps, so it no longer sits on the start of the long dash.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Save As suggested `.dxf` for every untitled drawing, so saving new work as
DWG meant typing the extension each time. AutoCAD keeps this choice in
Options > Open and Save > "Save as".
- File > Default Save Format > DXF / DWG sets the format Save As suggests
for drawings that have no file yet. Drawings opened from a file keep that
file's format, as before. DXF stays the default.
- The choice is saved across restarts with the other preferences
(`saveFormat`), survives Reset Palettes, and is scriptable as
`ui.saveformat {format}` or the `saveFormat` UiState field.
- Ukrainian labels for the new menu entries.
On macOS the suggested `.dwg` name needs DWG in the Save As filter list
(#198) to survive the dialog.
Claude-Session: https://claude.ai/code/session_019uMCkRAB8DMd8MEmoY9Vyr
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Extents: ignore rays and references to missing or empty blocks, as ZOOM Extents does
Opening real drawings in CADCraft often showed a tiny plan in a huge empty
canvas, because the extents included things that draw nothing:
- the base point of a RAY (Autodesk's own reference says infinite
construction lines and rays do not affect ZOOM Extents; an office plan in
our corpus carries two rays 1.98 million units from the building);
- the insertion point of an INSERT whose block is missing (a purged
definition, a bound xref that never came along) or empty (an unloaded
xref): a floor plan had such references 125 000 and 3.4 million units away.
XLINEs were already ignored; rays now are too, and an insert contributes only
what its block draws (attributes still count). Two regression tests use the
measured coordinates.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Extents: skip invisible block entities, attdefs and blank texts
- Entities in a block definition count for an insert's (and a dimension
block's) extents only if they are not AttDefs, have common.visible set,
and sit on layer "0" or on a layer that is on and thawed. Dynamic blocks
keep hidden constraint geometry far from the visible part.
- Text/MText that is empty, whitespace-only or only "\~" has empty bounds.
- Tests cover each rule separately.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
A wipeout was drawn only as its outline, so objects behind it stayed
visible on the canvas and in PNG, SVG and PDF output. The display list
gets a new primitive kind, Kind::Mask: the wipeout polygon as triangles
that every backend fills with its background colour (paper white for
SVG/PDF and plots, the raster background for PNG, the canvas colour or
the white sheet on screen), in draw order, so it hides what came before
it (DRAWORDER decides). The frame follows WIPEOUTFRAME: 0 none, 1 shown
and plotted (default), 2 shown but not plotted.
The GPU canvas drew all fills before all lines; it now splits the mesh
into draw-order groups at each mask (MeshData::breaks) and draws each
group's fills, lines and quads in turn. Infinite lines stay on top as
before.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A polyline with width was always filled as one continuous band. Now,
with a linetype, each dash is a filled piece of the band (tapers and arc
segments included) and the gaps stay empty; dots are drawn as a line
across the band. As for thin polylines, PLINEGEN runs the pattern along
the whole polyline, otherwise it restarts at every vertex, and a pattern
too small to see (min_dash) fills the whole band.
linetype::ranges lays the pattern out along a length; wide::dashed cuts
the band at those distances.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
display_rgb flipped every near-white colour to black on paper and every
near-black colour to white on the dark canvas, so ACI 51/255 and
true-colour white printed black and dark index colours showed white.
Display-list primitives now carry an aci7 flag set when the colour
resolves to 7 (directly, by layer or by block); only those flip.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
-BLOCK was an alias of BLOCK, so the selected objects were always
converted to a block reference; a script doing -BLOCK then INSERT ended
up with an extra reference. -BLOCK is now its own command: the command
line erases the selection, and its JSON form defaults to keep "delete".
BLOCK (menu Make..., typed BLOCK) and the "block" JSON form still
convert by default.
Both forms ask 'Block "<name>" already exists. Redefine it? [Yes/No]
<N>:' when the name is in use; No asks for the name again.
Session::start resolves a dashed name to a command with that id (the
same change as #280's -ATTEDIT).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The offset copy of a spline was always rebuilt with the open fit-point
interpolation, so a closed spline came out open with a corner at its start
point. Use Spline::from_fit with the source's closed flag, which gives a
periodic spline for closed input.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
wblock {name, base} ignored base and always measured from the block's
own base point. base (in block coordinates) now overrides it, the way
it already worked for selected objects. In both modes the base point
becomes the written drawing's origin, and $INSBASE is 0,0,0.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The linetype library is defined in inches, and LINETYPE loaded it
unchanged into metric drawings: DASHED was 0.5 mm on, 0.25 mm off at
LTSCALE 1, so dashed lines looked solid. CAD programs load ISO-scaled
definitions when MEASUREMENT is 1 (the same patterns with lengths times
25.4). library::standard_linetypes_for(d) does the equivalent with our
own definitions: in a metric drawing every length (dashes, gaps, text and
shape size, offsets) is scaled by 25.4 as it is loaded. LINETYPE and the
CENTER linetype that CENTERMARK/CENTERLINE load use it. Definitions
already in a drawing are not changed.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Objects whose lineweight resolves to Default were always drawn and
plotted at 0.25 mm. They now use LWDEFAULT (hundredths of a mm, 0..211),
falling back to 0.25 mm when it is unset or out of range. All backends
(canvas, PNG, SVG, PDF) read the width from the display list.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Session::execute and interactive input already ran under catch_unwind;
the machine factory, begin (the first step of Session::start), prompt and
preview (called by the UI every frame) did not, so a panic there took
down the app. Session::start now wraps each machine in guard::Guarded,
which runs all of its steps under catch_unwind. A panic in the factory,
begin or input ends the command with an internal error and the session
restores the drawing, as for input. prompt and preview can't end the
command, so they fall back to an "Internal error" prompt and no preview,
and the next input ends the command the same way. AGENTS.md/CLAUDE.md
name the wider guard.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Array commands: ARRAY, and the edit-array option loops for ARRAYRECT/ARRAYPOLAR (#230)
ARRAY (alias AR) selects objects, asks for the array type and continues as
ARRAYRECT, ARRAYPATH or ARRAYPOLAR. ARRAYRECT no longer creates a fixed 4x3
grid right after selection: it previews the array and loops on
ASsociative/Base point/COUnt/Spacing/COLumns/Rows/Levels/eXit. ARRAYPOLAR
asks for the center (or Base point), then loops on Items/Angle between/
Fill angle/ROTate items/eXit. The copies are created on eXit or Enter.
Associative arrays, levels and polar rows report "not available yet" and
return to the loop without consuming the next input.
The array commands move to cmd/array.rs. Arrays stay non-associative
copies, and the JSON forms are unchanged. A full clockwise polar fill
(-360) no longer puts the last item on top of the source.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs/gaps.md: narrow F5 (array option loops, #230)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Arrays: refuse arrays over MAXARRAY instead of hanging or shrinking them (#230)
`arrayrect {"rows":1000000,"cols":1000000}` was clamped to 1000x1000 and
then created a million copies of every selected object, which hangs the
app. ARRAYRECT, ARRAYPOLAR and ARRAYPATH (JSON and interactive, also via
ARRAY) now count items x selected objects and refuse an array over the
new MAXARRAY session variable (default 100000, settable 100..10000000)
with a message naming the limit, instead of clamping the counts and
silently creating fewer copies. The interactive count prompts accept at
most what still fits under MAXARRAY.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>
* 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>
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>
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>
- 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>
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>
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>