A drawing saved by a commercial CAD application (tests/fixtures/plot,
provided by Panos: one 10 m line, three layouts) corrects several
assumptions of the previous commit:
- rotation is relative to the medium's own orientation: an unchanged sheet
keeps the driver's exact medium (its PDF driver lists landscape media of
its own, written with rotation 0; a system printer's portrait A3 plotted
landscape carries 90); only a newly picked sheet is written as the
catalogue's portrait medium
- the stored scale factor (DXF 147) is the ratio × 25.4 on millimetre page
setups and the plain ratio on inch ones; the "use standard scale" bit
stays clear for standard and custom scales and is set for fit-to-paper;
a standard code is kept across a paper-unit conversion ("1:1" on an
inch page setup over a millimetre paper space is 1 in = 25.4 units)
- layout plots take the same scale path as every other area, so a foreign
layout's paper-space unit survives
- "plot with plot styles" / "display plot styles" round-trip as the
dialog's checkboxes, independent of a style table being named
- layout limits place the sheet at minus its displayed margins, the way the
source application does (the printable corner is the paper-space origin);
the margin ↔ rotation mapping shared by the printable-area guide now
turns the medium's top margin into the layout's left one at 90°
The fixture round-trips through the dialog with every stored field
unchanged; picking a new sheet on it writes the canonical form.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
One writer (plot_settings_from_dialog) now serves both the layout's embedded
settings and named page setups, so the two can no longer drift apart:
- media are stored as the portrait sheet with the orientation in the
rotation code (0/90/180/270; upside-down adds a half turn) instead of
swapped width/height with rotation limited to 0/180
- standard scales are written by their code (1:1 … 100:1) with matching
numerator/denominator; other scales as a readable custom ratio in the
layout's own paper unit, converting from the drawing's scale-family unit
(a metre drawing plotting "1:100" stores 10 mm = 1 unit, an inch layout
stores 1 in = 2540 units); reading converts back and keeps a ratio the
scale list has no name for instead of snapping to 1:1
- the layout's paper unit and every flag the dialog does not edit survive a
round trip untouched; layout limits follow the paper-space unit
- new drawings, new layouts, CLEAR and the automation "new" op share one
default page setup (canonical A4 name, rotation 90, none_device, 1:1
layout plot) through Scene::reset_to_new_drawing
Scene gains scale_family_unit_mm, scale_ratio and a scale-name ratio parser
(ratios and architectural names). Round-trip tests cover orientation,
standard/custom/fit scales, unit conversion, named-setup parity, preserved
flags and the default layout.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Display page could only take a typed #RRGGBB, which is precise but
no way to choose a colour by eye. The swatches are now buttons that
open a wheel.
The wheel does not replace the hex field, it sits beside it: typing an
exact value stays the quickest way to reproduce a known colour. On
submit it hands a hex string to the existing ModelSpaceBgChanged and
friends rather than writing the config itself, so the two routes
cannot drift apart on validation, persistence, or the MatchTheme
fallback an empty field triggers.
Also stops the Browser shipping in the default dock layout. It was
added to the right edge, which shifted the block palette to index 1
and broke four dock tests that encode the existing arrangement. Those
tests were right and the change was wrong: the earlier commit claimed
to leave the layout alone. BROWSER now docks the panel on first use,
so nobody's edges rearrange on upgrade for a panel they never asked
for.
nix build now succeeds. The hashes were placeholders before, so it
could not have.
Reading Cargo.toml for the git sources missed two of them: cryoglyph
and dpi come in through iced, which pins its own forks, and they only
appear in Cargo.lock. Six entries, not four. The comment says to
regenerate the list from the lock for that reason.
flake.lock is committed alongside, so a checkout resolves nixpkgs and
rust-overlay the same way next month as today.
Also asserts the snap default instead of leaving it to be eyeballed.
It is only visible in a fresh profile — the app persists settings on
change, so an existing settings.json keeps its own osmode and never
shows what a new install would do.
UCS FACE had no test at all: it was only known to compile. The plane
it builds has two halves and they sit either side of a visibility
boundary, so the coverage splits to match.
tests/ucs_face_plane.rs takes the public route — extrude a disc, then
check the caps come back as distinct planar faces with opposite
outward normals, that an off-centre pick lands on the same face as a
central one, and that the curved wall is never offered as a plane.
The axis construction is pub(crate), so it is unit-tested next to
ucs_from_normal instead: Z is the normal it was handed, the axes stay
orthonormal, a normal along world Z still yields a plane rather than
crossing with itself, the same normal is reproducible, and a zero
vector is refused rather than silently producing a degenerate UCS.
Byte-pair separators in the hex colours, and #[must_use] on the four
constructors, which return a value and do nothing else — dropping one
is always a mistake rather than a choice.
Found by clippy::pedantic. Only this file is touched: the same run
reported ten thousand warnings across the tree, nearly all numeric
cast lints that are load-bearing in geometry code, so fixing them
wholesale is a separate decision rather than a drive-by.
A docked outline of the drawing: the three origin planes, the open
sketch, and every solid labelled with the operation that made it,
recovered through primitive_property_operation. Rows run ordinary
commands, so clicking a plane starts a sketch on it and clicking a
body selects it.
It is a table of contents, not a feature tree. The DWG history records
how each solid was built but nothing re-runs it, so an earlier feature
cannot be edited to rebuild the ones after it. Presenting the list as
editable history would promise exactly that, so the panel states what
it is instead.
Off by default and opened with BROWSER, leaving the existing dock
layout alone for anyone who does not want it.
CREATESKETCH opens a sketch on the current UCS, or on one of the three
origin planes with XY, XZ or YZ. FINISHSKETCH closes it again.
Nothing here is new machinery: the plane is a UCS, the square-on view
is PLAN, the drafting aids are the snapper. What the pair adds is
holding them together and putting them back afterwards, including the
snap master state, so a user who works with snapping off gets that
back rather than silently keeping it.
Squaring the view up is not cosmetic. Drawing on a plane seen nearly
edge-on is the main way geometry lands somewhere the user did not
mean, because a few pixels of cursor travel cover a long way in world
space.
A sketch is a mode rather than a document object, so what it produces
is ordinary model-space geometry and the file stays a DWG other
software can open. Whatever was drawn becomes the Previous selection
set, so EXTRUDE then Previous consumes it without making the user
reselect a profile they just finished.
Draw a profile, extrude it, then sketch on a face of the result: the
operations were all there, but nothing turned a solid's face into the
active drawing plane, so the second half of that loop was unreachable.
UCS FACE takes a surface pick, finds the planar face under it and
builds the plane from the face normal. X comes from the Arbitrary Axis
Algorithm, so the same face always yields the same X axis and a sketch
drawn on it does not spin when the UCS is rebuilt.
UCS OBJECT adopts a planar entity's own stored axes instead of
re-deriving X, so a sketch started from a circle lines up with how
that circle is stored. It reuses the profile extraction EXTRUDE
already runs, which means anything extrudable can also be sketched on.
Bare UCS FACE starts an interactive pick that resolves through the
same inline handler, so the clicked and typed paths cannot diverge.
Fusion Black pairs near-black chrome with a paper-white canvas, so the
drawing is the only bright thing on screen. That breaks the assumption
in theme_canvas_background that the canvas follows the chrome, hence
the explicit lookup: falling through to the palette would paint model
space black and undo the point of the theme.
Both keep a blue accent rather than going fully greyscale. Selection
highlight defaults to the theme primary, and a white primary would be
invisible against a white canvas.
These are Theme::Custom values and so are absent from iced's
Theme::ALL. all_themes() is the list to enumerate from now; the picker
in the options dialog uses it, and parse_theme_name resolves both by
name.
The running snap set was already seeded with endpoint, midpoint,
centre, node, quadrant, intersection and nearest, but the master
suppress bit was set, so none of them did anything until the user
found the status-bar pill. Snapping is what makes a drawing close
rather than nearly close; it should be live from the first click.
Only fresh configs change. A stored settings.json keeps whatever
osmode it already holds, and a legacy file carrying its own $OSMODE
still overrides on open.
The devShell carries the toolchain plus the X11, Wayland, Vulkan,
fontconfig and freetype libraries winit and wgpu dlopen at run time,
and the trunk/wasm-bindgen pair the web build needs. LD_LIBRARY_PATH
is set there too, since a dev binary runs straight out of ./target and
never gets the wrapper the package build applies.
The four git dependencies each need an entry in cargoLock.outputHashes.
They are lib.fakeHash for now; the first `nix build` prints the real
values, and they have to be refreshed whenever a rev in Cargo.toml
moves.
The toolbar Help button was a disabled tip that could never be clicked. It now opens a modal window explaining the manager on the web build: read-only reference list, disabled Attach/Refresh/Change Path (no filesystem access or native file pickers, so no add/reload/repath and no previews), nested read-only rows, and saved-drawing requirement for relative paths.
Includes a Report-an-issue button opening the GitHub issues page and a Close button; wired as ModalKind::XrefHelp through the shared modal frame so dismissal works on native and web.
- The per-character width estimate is 0.66 text heights, measured by
rendering the text of 106 saved dimension pictures with the bundled
fonts; the old 0.60 left every fit and hook estimate ten percent narrow.
With it, the "text plus DIMGAP must clear the arrowheads" rule reproduces
AutoCAD's arrow placement for 1047 of 1078 pictured linear dimensions
whose text sits between the extension lines.
- DIMTMOVE 1 stores where the leader's hook starts, not where it ends: on 39
such radial dimensions the stored point sits DIMASZ plus DIMGAP before the
text. The hook now runs from the stored point one arrow toward the text.
- The diameter prefix is U+2205, matching the %%c expansion and the pictures.
- A diameter whose text the user dragged keeps the DIMTAD lift from #1323;
the four AutoCAD-authored diameters with DIMTAD above in the sample all
store the lifted point directly, so this applies only to dragged text.
Saved dimension pictures across 1311 diameter dimensions carry U+2205 (806) or a
literal %%c (168); U+2300 never appears. The bundled romans and simplex fonts
have U+2205 and lack Latin O-stroke, so one character now serves the %%c
expansion and, in the dimension formatter, the diameter prefix.
The fit and break estimates used two halves at STACK_HALF_SCALE; the stack
is laid out from the lower half's baseline to the upper half's cap, which is
STACK_RAISE + STACK_HALF_SCALE of the run height. The taller estimate flips
arrows outside for a few more stacked texts that AutoCAD drew that way.
A negative DIMLFAC scales a paper-space dimension only when it measures
model space through a viewport. AutoCAD records the factor it used as
ACAD_DIMASSOC_CALC_DIMLFAC; a zero there marks a dimension of paper-space
geometry, which reads its sheet distance unscaled. Regenerating such a
dimension multiplied it by the factor anyway, turning 4.457 into 18.110.
Text placed past an extension line along the dimension's own axis pulls the
dimension line out to its near edge rather than stopping at the two-arrow
stub. A radial leader already within 15 degrees of horizontal runs straight to
the text instead of turning a hook, and a diameter with outside text draws its
single leader for every DIMTMOVE except 2, which keeps only the arrowhead.
Subset parity: 870 of 1076 pictured dimensions now regenerate as drawn.
Comparing the saved picture block of every dimension in a set of production
drawings with the geometry regenerated after a refresh showed half of them
differing. The differences trace to a few layout rules:
- The stored text middle point (group 11) is where the text was drawn, for
automatic and hand-placed text alike; a regenerated dimension keeps it and
only a cleared point is placed from the style.
- Arrows move outside when the text, where it is drawn, would run into an
arrowhead. The text is measured along the dimension line, so horizontal
text on a vertical dimension counts its height, a limits stack its two
lines and `\P` continuation lines their number.
- Text pushed outside starts past the two-arrow stub and a gap; arrow
length is DIMASZ, not the text height.
- Horizontal text outside a non-horizontal dimension gets a leg to its
height and a hook one arrow long that it sits against, both when drawn
and when auto-placed.
- A radius with outside text draws no line to the centre unless DIMTOFL,
a leader to a hook the text sits against, and an arrowhead whose body
points toward the text. A diameter with outside text and no DIMTOFL is a
single leader with one arrowhead, no line across the circle.
- DIMTMOVE 1 stores the hook end; the leader is drawn to it exactly.
- Width estimates measure the widest `\P` line and skip MText format codes.
On a 30-drawing subset with 1076 pictured dimensions the regenerated
drawing now matches the picture for 838 of them, up from 584.
The Snap and Grid tab showed static grid values with dead checkboxes, and every grid line stroked identically.
Grid X/Y spacing and Major-line-every are now editable inputs (validated 2-100 for majors, dirty-tracked, applied with an error message on bad input); Adaptive-grid and Display-beyond-Limits checkboxes are wired to live state. Every Nth line partitions into major_segments and strokes subtly brighter (1.5x alpha, 0.75px) for the reference hierarchy. GRIDMAJOR state persists to UserSettings. Covers partition, invalidation, and parse tests.
The viewport grid ignored the configured spacing and forced square grids: compute_grid_step hardcoded a 1.0 base, a single shared step drove both line families (including the tangent derivative used for gap measurement), and the display always clipped to LIMITS despite the beyond-Limits option.
Grid spacing now scales per-axis from GRIDUNIT via compute_grid_steps (adaptive 5x growth, fixed passthrough), GridParams carries step_x/step_y, snap tolerances follow the visible steps, and the LIMITS clip is skipped when beyond-Limits is set. Also fixes a per-axis overflow fallback that returned the X base for Y. Spacing state (X/Y, adaptive, beyond-Limits) is stored on the app and persisted to UserSettings at defaults; dialog wiring follows separately.