Remove personal names, local paths and fork-only process notes from code comments,
docs and the plugin manifest (repository now points at the upstream project), and
drop the handoff section from the plan.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
What it is, how to check it on Linux or Windows without a Mac, the reading order,
the small edits to existing code, what each area proves, the decisions that are the
maintainer's, and what is deliberately not done.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
#1418 added both as direct dependencies although each was already in the
tree: printpdf's to_lopdf_doc returns the lopdf document whose compress
and save_to are plain methods, and the codec's encoding_from_code_page
hands back the Big5 / GBK / Shift_JIS / EUC-KR encodings by their DXF
code-page names. Cargo.toml keeps its dependency list.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
#1415 made a dimensional Distance keep a negative value's sign in the
parameter while the geometry uses its magnitude (and a zero fold the
points together), but left the value-domain test asserting the old
refusal. Assert the new reading, and keep the radius refusal covered.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Runs the plugin API tests, the plugin crate and Python model tests, and the host's
real-IPC plugin tests against a freshly staged plugin and built runner, and fails
if either environment variable target is missing so the tests cannot skip silently.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Symmetric held the connecting direction perpendicular to the axis with
the kernel's angle-based Perpendicular, which locks its turn side at
construction: a point starting far from its mirror image had to swing
the pair through the axis direction, the residual wrapped, and the
solver stalled with the point unmoved. The perpendicularity is now the
projection of the pair onto the axis direction being zero, a polynomial
residual the solver follows from anywhere. Circle centers and ellipse
centers use the same equation. tests/parametric_constraints_solve.rs
symmetric_constraint_mirrors_one_circles_center_across_the_axis_line
passes again.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- .github/workflows/ci.yml runs `cargo test --workspace --locked` on
pushes to main and on pull requests, with the Linux libraries the
release build installs. No formatter check: the tree is not
rustfmt-clean and stays that way.
- flake.nix, flake.lock and the direnv hook are removed. Nothing used
them, and their pinned dependency hashes were already stale after the
codec and kernel bumps.
- Under cfg(test) the "system" language resolves to en-US. The unit
tests compare English text through one process-wide loader, and every
apply_config of a default config asked for the system language, so a
Turkish locale failed 22 tests and parallel tests raced each other
through a language switch (the thickness-row test found its section
under one language and looked it up under another).
- The arc grip test follows #1352: the start, end and midpoint grips
drive the whole arc.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
On Windows the temp directory has backslashes, which a Python string literal reads
as escape sequences (C:\Users is a unicode escape), so the RasterImage and OLE
lifecycle tests could not even create their entity. Windows accepts forward slashes.
Found by the first Windows run of the fork build workflow; Linux passed all 60.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Portable builds (executable plus plugins/opencad.python where the host looks for
it) and the real-IPC plugin tests on ubuntu and windows. Started by pushing a
fork-build-* tag or by hand; publishes nothing. release.yml stages the plugin
only in the macOS bundle.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A saved user preference (on by default) toggled by the SCRIPTCOMMANDS command
gates HostApi::run_command; Cancel stays allowed so a waiting command is never
stranded. The runner refuses the SCRIPTCOMMANDS command itself, so a script
cannot change it. Documents why no helper-thread or stack-growing dependency was
added: native builds link with at least 8 MiB, the nesting depth is fixed, and the
deepest step found needs 2 to 4 MiB in a debug build.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Drop two unneeded mut bindings in tests and mark the generated dict/enum
converters allow(dead_code), since a kind can leave one unused.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Rewrite the host-model design note for the current architecture (entities, tables,
commands, nested validation, known limits), refresh the plan's current-state header,
the ledger and gaps-doc status lines (cadcodec PR #48), the README status and the
superseded ocs.command notes, and drop dangling references to a sibling project that
is not in this repository.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
polyline_close/open/width/reverse/join (a line or arc is turned into a polyline
first), array_path and array_3d, written against observed prompts and checked
for exact geometry in the real-IPC audit. Polylines default their pick point to
the first vertex. The audit runs on a large-stack thread because PEDIT's
line-to-polyline step needs more than a test thread's 2 MiB in a debug build.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The generated dict-to-struct conversions for nested records defaulted every
absent field and ignored unknown keys, so an LwPolyline built from
{'x': .., 'y': ..} vertices became zero-coordinate vertices. They now refuse an
unknown key (naming the keys the record takes) and a manifest list,
required_struct_fields, enforces the fields that define a nested record's
geometry (vertex locations, hatch edge ends and centres, section symbol points).
Regression test over real IPC; every existing lifecycle audit still passes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Each is written against the prompt sequence observed in the exploration harness
and checked for exact geometry in the real-IPC audit. HATCH is left to
create_entity because its command needs an interactive boundary pick.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
HostApi::run_command drives the real OCS command through the same primitives
the automation channel uses, synchronously on the host thread: run a line,
start a command and answer point/text/token/entity/selection/enter prompts, or
cancel (closing anything the command opened). Refused while another command is
active, for a denylist (QUIT, NEW, OPEN, SAVE*, PY_* ...) and off the active tab.
Python: doc.command, doc.start_command and doc.modify (offset, trim, extend,
fillet, move, copy, rotate, scale, mirror, erase). Real-IPC audit checks exact
geometry for every wrapper plus guards and undo; IPC and Python unit tests; docs.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
TableOperation gains simple-linetype create/modify/rename/delete (standard names
taken, rename follows layers and entities, guarded delete) and paper-space
layout create/rename/delete/set-current/set-page (default page setup and sheet
viewport, the application's own layout switch, Model protected). CTAB now reports
the current layout. Real-IPC audit with DWG/DXF persistence and undo, IPC and
Python unit tests, docs.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
BlockEntityAdd adds a validated entity to a block definition (owner set by the
host, same validation as a model-space entity, self-nesting inserts refused),
reusing the scene's block-editor routing. Deleting a block member now also
drops its handle from the block record. Real-IPC audit with an attribute
definition inside a block, DWG/DXF persistence and undo; IPC and Python unit
tests; docs.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
TableOperation gains BlockCreate (copy entities into a definition shifted by the
base point, optionally erasing originals), BlockModify, BlockRename (inserts
follow) and BlockDelete (refused while referenced; removes members, markers and
orphaned draw-order tables). Real-IPC audit with DWG/DXF persistence and undo,
IPC and Python unit tests, docs; the DXF loss of the block description is pinned
by a canary.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Extends TableOperation with text style create/modify, dimension style
create/modify (DimStyle fields as a validated JSON object), rename, delete and
set-current. Reuses OCS's style in-use scan and reference-following rename
(four style_ops methods widened to pub(super)); a renamed text style also
updates dimension styles that name it. Real-IPC audit with DWG/DXF persistence
and undo, IPC and Python unit tests, docs. Four cadcodec findings are pinned by
canaries.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
printpdf 0.9's `PdfSaveOptions::optimize` is a no-op — its `doc.compress()`
call is commented out — so every page's content stream (megabytes of vector
operators for a CAD sheet) was written uncompressed: a 16-sheet drawing set
exported as 280 MB. Serialise through `printpdf::to_lopdf_doc` and run
lopdf's `compress()` before writing; the same export is now 49 MB and
readers see ordinary FlateDecode streams.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two layout defects that together broke every numbered note in a Chinese
drawing:
- Words were split on whitespace only, so a Chinese sentence — which has no
spaces — was one unbreakable word. When it did not fit after "7.<tab>" the
whole sentence dropped to the next line and still ran past the column.
Ideographic text may break between any two characters except before a
closing mark or after an opening one (the basic kinsoku rule); the atom
splitter now emits those break opportunities.
- `\pxi…,l…,r…,t…;` paragraph indents and tab stops are multiples of the
text height, and the first-line indent is relative to the left indent
(a hanging list is `i-1.5,l1.5`). They were applied as raw drawing units,
i.e. a fraction of a unit, so wrapped lines snapped back to the margin
and the number/tab column collapsed. Scale them by the entity height and
resolve the first line from the left indent.
Adds a layout test: a 40-ideograph paragraph in a 30-unit column wraps into
several lines, none wider than the column.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds the additive v7 TableOperation request and HostApi::table_operation for
layer create/modify/rename/delete/set-current. Refusals are decided before the
undo step is recorded. Python wrapper, real-IPC audit with DWG/DXF persistence,
IPC round-trip test, Python unit test and docs.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Only the first line's ends are pinned while the second line is made
parallel, the way the Parallel constraint itself moves it; the distance
is then measured on the solved geometry.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Point & line and 2Lines take a text baseline or an ellipse axis as the
line; a zero value folds the points onto the first one; 2Lines turns the
second line about its start. The Properties Constraint section edits
Constraint Form (Dynamic on the *ADSK_CONSTRAINTS layer, Annotational on
the current layer), Reference (driven constraint in parentheses whose
parameter follows the measurement) and Description (stored with the
parameter and the native variable). Dynamic dimensions keep a 12 px text
and 8 px arrows at any zoom, drop trailing zeros and show formulas as
`fx: len=d1*2`; the hidden constraints layer stays out of the layer lists.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Glyph quads bake the atlas UV of their tile. `grow_height` rescales every
cached entry's V and bumps the atlas generation, and the tessellation memo
folds that generation in — but the caches that hold finished wires do not:
the resident wire set is keyed by geometry epoch, block definitions by
block epoch, and a full build can grow the atlas part-way through (a CJK
sheet set bakes thousands of tiles on first sight). Text tessellated before
the growth then samples the wrong tiles for as long as those caches live,
which on a big drawing is forever: whole tables of scrambled glyphs, and
the block contents stayed scrambled even after a REGEN.
- `resident_wires_for` rebuilds (dropping the block definition cache) until
the atlas generation is stable across the pass.
- `Scene::update` compares the live generation with the one the scene's
caches were built against and bumps geometry once when it moved, so a
later growth (opening a layout, editing text) re-lays-out everything.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`shx.rs` only accepted the classic `AutoCAD-86 shapes 1.x` container, so
every modern Autodesk font (`txt.shx`, `simplex.shx`, `romans.shx`, … are
`unifont 1.0`) and every Asian big font (`chineset.shx`, `hztxt.shx`,
`bigfont.shx` — `bigfont 1.0`) was rejected and text fell back to the LFF
substitutes and TrueType fallback glyphs. Drawings authored with SHX big
fonts (the norm for Chinese / Japanese / Korean sheets) never showed their
real glyphs.
- Parse all three containers. Unifont glyphs are keyed by Unicode; big-font
glyphs by their double-byte code from a sparse hash table of
(code, length, offset) entries. Big fonts reference subshapes with a
two-byte `7, 0, hi, lo`.
- `bigfont_glyph` encodes a character with encoding_rs (Big5 / GBK /
Shift_JIS / EUC-KR, most likely first for the file name) and takes the
first code the font has.
- `Face::WithBig` pairs a style's big font with its primary face: full-width
characters come from the big font, everything else from the primary —
how a STYLE with a "Big Font" behaves in AutoCAD. `resolve_text_style`
resolves `big_font_file` like the primary (next to the drawing, then the
user fonts folder) and rides it along in the font name.
- The missing-fonts dialog lists unresolvable big fonts too.
Unit tests cover a synthetic big font (Big5 lookup, cap-height scaling, no
aliasing of unknown codes), a synthetic unifont, and the pairing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
DCFORM sets the annotational or dynamic form and continues into the
DIMCONSTRAINT option prompt, whose wording and last-used default match the
reference. -PARAMETERS manages parameters from the command line; a
double-clicked constraint dimension opens its value prompt. Dynamic
dimensions rescale on every camera change and their display overrides stay
out of the saved file. A driving dimension between an entity's own ends no
longer has its length retained, so a new value moves the second point along
the dimension axis; parameter edits hold the first constraint point.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
DCALIGNED opens with `[Object/Point & line/2Lines] <Object>`. Point & line
asks `Specify constraint point or [Line] <Line>:` then `Select line:` (or the
line first and `Select constraint point:`), measures perpendicular to the
line from the constraint point to the line end nearest to it, and stores a
directed distance with the line as direction reference. 2Lines asks
`Select first line:` and `Select second line to make parallel:`, adds a
Parallel constraint solved with the first line pinned, then measures on the
solved geometry between the nearest ends. Unsupported line picks report the
reference's message; the same measurement again reports `The constraint
already exists on the selected objects.` and ends the command; an invalid
expression reports both reference lines and asks again.
The offered default and seeded expression carry twelve significant digits;
a typed value moves the second point (or the other object) while the first
point and its measured line stay, negative values keep their sign in the
parameter and use their magnitude, zero folds the points together, and the
dynamic dimension's origins follow every solve. The lock mark sits after the
text, a selected dynamic dimension shows triangle grips at its points and the
text square, and its Properties show the Constraint section (Name and
Expression editable) plus Text rotation.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A plain number shows as typed (d1=100), an expression under the fx: prefix
(fx: d2=d1*2); the text stays horizontal through DIMTIH/DIMTOH overrides
and the lock mark follows the text.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Every dynamic dimension gets a DIMSCALE override for 12 px text, refreshed
per zoom band, at creation and after load. A SCALE/ROTATE of an entity whose
own end points a driving dimension measures now pins its second constraint
point so the dimension moves the first end, as the reference does.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
When a text style's SHX / stroke font lacks a character, the glyph comes
from a system TrueType font through `ttf_glyph::fallback_glyph`. That
substitute was normalised to the Latin cap height (9 units = text height),
which is right for Latin, Greek or Cyrillic stand-ins but wrong for CJK:
an ideograph fills the whole em square, and 1 em is 1.3–1.5 cap heights in
every common CJK font (DFKai-SB has no OS/2 cap height at all, so it fell
to the 0.7 em guess). Every substituted Chinese character therefore came
out ~40 % taller and wider than the big font (`chineset.shx`, `hztxt.shx`,
…) the drawing was authored with, and whole lines of notes ran past their
frames.
A big font draws an ideograph one text height tall and one text height
wide, so size full-width / ideographic fallback glyphs by their em box
instead. Other scripts keep the cap-height normalisation. Real TrueType
styles (`Face::Ttf`, shaped runs) are unchanged.
Adds a unit test that a fallback `中` advances by about one text height and
its ink fits the text height (skipped on machines without a CJK font).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`viewport_content_wires` projects model-space wires into paper coordinates
for plotting: the points and the linetype stations are multiplied by the
viewport scale, but `world_width` (the band width of a wide polyline) was
copied unchanged. The PDF / print exporter strokes that band at
`world_width` in paper millimetres, so a 15-unit-wide bus bar seen through
a 1:100 viewport came out as a ~21 mm black blob instead of 0.15 mm, and
every single-line diagram sheet plotted as solid black shapes.
Scale `world_width` by the viewport scale alongside the points, and keep
the per-point taper widths in step (or drop them when clipping changed the
vertex count, so they cannot misalign). Negative (fixed pixel) and zero
(lineweight) widths are left untouched.
Adds a regression test that plots a 15-unit-wide polyline through a 1:10
viewport and expects a 1.5-unit band in the projected plot wires.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The dynamic dimension of a dimensional constraint is now linked in the
drawing file (AcDbAssocDependency + AcDbAssocDimDependencyBody on the
constraint node), reread on load, hidden by DCHIDE/DYNCONSTRAINTDISPLAY
and never plotted; it carries a lock mark before its text. New
CONSTRAINTNAMEFORMAT and DYNCONSTRAINTDISPLAY variables and the
DIMCONSTRAINT option front end. A transformed entity whose own end points
a driving dimension measures keeps only the first constraint point, so a
scaled line returns to its dimension value.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>