#1666 sets group 295 on extrusion history records, which makes the kernel
read the embedded profile and path where they stand and ignore the
matrices. The planar fallback profile (a region-derived LWPOLYLINE) was
stored in its plane's local frame with the frame as matrix, so with the
flag it rebuilt at the origin. Re-express it in its own OCS (normal and
elevation) when the record is built.
Pins the kernel and graph at upstream (opencadkernel#84 merged as f051138)
instead of the ramox81/cadkernel [patch].
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A lofted surface's region cross sections are recorded as the curve round
each region, as the reference records them; a region recorded as a
region made the reference refuse to open the drawing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The reference writes none; a draft distance on an extruded surface made
it re-extrude the surface with a taper when the profile changed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The polyline round a region is written about the region's normal in its
object coordinates with an identity transform, as the reference writes
it; a rebuild from the history placed a polyline given in plane
coordinates with a frame at the wrong place once the region moved. Arcs
of one circle making a whole turn are recorded as that circle.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A swept, extruded or revolved surface made from a region records the
region as the curve round it, as the reference does: a lone circle or
whole ellipse as itself, a loop of lines and arcs as a closed polyline in
the region's plane. A region recorded as a region made the reference
refuse to open the drawing (modeling error 58000).
A region read by an associative action carries the step its edge names
belong to (ACAD_STEPID), so the reference finds the edges again and the
surface follows the region.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The surface extrusion, the extrusion along a path, revolve history and
embedded profiles refused a region whenever it had more than one display
wire, which a region of several edges always has. They now refuse only a
region with holes, and a region's loops for a solid extrusion come from
its modeler edges.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A region keeps one display wire per edge, so a region of several edges
gave no polygon and could not be extruded (solid or surface), and a
circle region was extruded from its display polygon. Profiles of a region
with one boundary loop now come from the region's modeler edges.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A region of several edges kept its display wires one per edge, so the
surface extrusion found no polygon and refused it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A swept, extruded, revolved or lofted surface made from a region now
reads the region through one geometry dependency per edge, each naming
its edge by a persistent subentity id recorded in the drawing's
subentity manager, so the surface follows the region. A lofted surface
keeps one continuity and bulge value per cross section.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
SURFACEASSOCIATIVITY now keeps EXTRUDE, REVOLVE and LOFT surfaces linked to
their sources: the profile (and axis object) or cross sections are kept, the
surface is made again when they change, and the drawing's associative
network carries an action with the extruded, revolved or lofted surface
action body, its path parameters and value parameters as the reference
writes them. Swept, extruded, revolved and lofted surfaces can read
splines (NURBS edge curve, fit-point splines through their fit points)
and ellipses (elliptical edge curve). Associative surfaces of a file
opened here follow their sources too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- refresh_associative_sweeps runs on every edit and scanned the xdata of
every entity; it now returns at once when no associative swept surface
exists (its application is not registered) and looks at surfaces only.
- An arc's end angle was raised by TAU in a loop, which never ends on a
huge angle from a damaged file; the sweep is taken with rem_euclid.
- The sweep DELOBJ test expected surface sweeps to delete their sources,
which #1663 changed on purpose: an associative surface keeps them.
- codec c9ffcac (opencadcodec #103) adds an explicit viewport-off bit: the
viewport on state is read with is_on() and set with turn_on()/turn_off()
everywhere, so a viewport the file marks off shows off and can be turned
back on from Properties.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The reference cannot open a drawing whose extrusion history has a taper
and leaves the profile frame to be computed (ErrorStatus 53). Extrusion
and press-pull records now set group 295, as the reference writes them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Swept surfaces made with SURFACEASSOCIATIVITY on keep their profile and
path and are written as a swept-surface action of the drawing's
associative network, reading the profile and path and their scale and
twist expressions. They follow edits of their sources.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A single profile's base point comes from the path it is swept along: a
path leaving the profile's plane from inside the profile sweeps it where
it stands. Kernel 30b3810: pcurves in the edge's parameterisation (valid
spline-faced sweeps), corner crossings fitted to the modeller's
resolution, run faces through stations evenly spaced along the run.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Cargo: drop the fork patches; codec 8e93175, kernel 0665fc9, graph
3b91e67 in every manifest and lock.
- A swept surface's expression link is written through set_entity_xdata,
so OCS_SWEEP_EXPRESSION has its APPID record and the record its handle.
- Applying the whole named-parameter table also rebuilds the swept
surfaces linked to expressions, not only a single-row edit.
- swept_surface_entity keeps its doc comment.
- The native-constraint tests find a scope's network through its own
dictionary, where it is now stored.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A closed 3D polyline profile that does not lie in one plane sweeps a
surface with the reference's refusals. Swept surfaces made with an
Expression scale or twist follow the named parameters, and reference
surfaces linked through their associative action are picked up on open.
Saving keeps the associative actions the constraint graph does not
rebuild and stores each scope network through its own dictionary.
Kernel 23138fc, codec 64fe2a6.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- An open path is swept from its end nearest the pick point (in plan), as
the reference does; the record keeps the original path and the placed
frame marks the start.
- Kernel from ramox81/cadkernel 1df75f5: one face per path run for
twisted, scaled and spline sweeps, placed records swept from the placed
end, end caps without pcurves.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Ctrl+click on a composite selects it with the solid under the cursor as
the one its palette and grips edit: the solid whose own surface passes
through the picked point. With its history shown, a composite draws the
edges of the solids its booleans joined, each where it sits.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With SOLIDHIST on, UNION, SUBTRACT and INTERSECT no longer reduce the
result's history to a frozen body: each solid's history joins the
result's under a boolean step, in the order the result applied them.
The Properties palette lists a composite's solids under Operand; the one
chosen shows its own geometry rows and grips, placed where it sits in
the composite, and editing it replays the whole tree. A composite read
from a file keeps its saved body; the tree is replayed only on edits.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Scale and Twist take an Expression evaluated with the drawing's
parameters; invalid input prints the reference message and asks again.
- The scale and twist prompts use the reference wording and defaults.
- The SWEEP prompts and refusal messages are in the locale catalog with
translations in every locale.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- 3D polyline profiles are recorded as wire bodies, like 3D polyline paths.
- Refused sweeps print the modeling error code and "Unable to sweep N
selected objects."; a scaled sweep rejects closed and cornered spatial
paths with the reference messages and asks for the path again.
- Scale > Reference asks for start and end reference lengths (start / end).
- Bank stays on for later sweeps until a twist angle is entered.
- Refused twist/scale edits in Properties print the modeling error code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Center and Quadrant found nothing on a polyline's bulge segments: the
lightweight and heavy polylines carried no snap points for them, and a
polyline split into several wires kept its points on one piece whose box
the others' never reached. Each arc segment now carries its centre and
quadrants, hovering the segment offers its centre, and a wire's box
covers its snap points.
Fixes#1640
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sweep history records in the reference layout.
Pins resolved to upstream: codec bff95f5 (#93, #94, #95, #96, #97 and
review fixes), kernel 6146d31 (#75 and review fixes), graph 9635ba7.
Fixes:
- Font prompt no longer lists bundled ltypeshp/qcadshp shape files (#1626)
- Wide polylines step at a discontinuous segment width; PEDIT marks the
current vertex; keyword steps refresh the command preview (#1628)
- Radius-0 fillet drops the zero-length segment it left, so the corner
fillets again (#1422)
- FILLET Trim / No trim option (#1558)
- True-colour wires plot in their own colour; colour picker stores the
value it shows (#1621)
- Model tab keeps its page setup; "Save changes to layout" in Model (#1620)
- QNEW default template in Options > Files; .dwt in the open dialog
(#1134, #1113)
- GRIDUNIT / GRIDMODE / PERSPECTIVE variables (#1618, #1524)
- PTYPE (#1433)
- ZOOM nX / nXP (#1465)
- CHSPACE between model and paper space through a viewport (#1428)
- Moving a dimension keeps its automatic text placement
- Shell icon; "&&" button labels (#1554)
- LINETYPE Load from a .lin file (#1588)
- XLINE / RAY hide the crosshair arms (#1437)
- Dynamic-input boxes keep clear of the crosshair and each other (#1546)
- Snap marker colour setting, dark default on light canvases
(#1497, #1229, #1128)
- Dimension style precision as a 0.00 pattern dropdown (#1426)
- Node graph nodes left of the origin no longer clip away (#1592)
- Collapse button in the command history header (#1094)
- Freeze-in-current-viewport toggle in the ribbon layer list (#1466)
- Geometric Center object snap for closed polylines (#1369)
New strings translated in all 21 locales.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
New sweep records store the source and placed profile frames, the
default mitred joint, groups 292-294 and a 3D polyline path as a wire
body, so the reference re-evaluates them. Builds against the sweep
history codec and kernel revisions.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- #1602 changed the COMMANDLINEFADETIME help text and added "Never"
without catalog entries, so both showed in English everywhere. Add
them to the catalog and all 21 locales; drop the "0 skips them" key.
- #1606 named another CAD vendor in a doc comment and two test names.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A point carried a key vertex, so Endpoint (which outranks Node) claimed
it, and an absolute-size glyph's strokes were indexed for Nearest and
Intersection. Points now offer only their Node, in the indexed and the
unindexed snap paths alike.
Fixes#1570
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The PR pinned codec and kernel to its fork through [patch]; codec #77 and
kernel #73 are merged upstream, so the patch sections are replaced by
the upstream pins (codec 8d23408, kernel d22a270, graph 9b5ae39) in this
same commit: the kernel now applies the body transform during the lift,
and an OCS without this PR's change would apply it twice. The new codec
also brings AssocCurveValue, registered with the plugin API's type
registry, and EmbeddedEntity::Body, named as a polyline profile.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Real BT/Tj text objects ride alongside the vector outlines (Tr 3,
zero visual change): runs preserved at layout time, TTF styles embedded
as allsorts subsets with ToUnicode, width-matched Tm, clip-culled.
WireModel literals moved to ..Default::default() tails.
Codec: PRs #71-#74 merged (MPOLYGON layout, multiline ATTDEF/ATTRIB DXF,
DXF MATERIAL/ACSH matrix, restorable SAT/SAB + history roots) and issues
#55 #64-#69 fixed. EntityType now boxes Surface, Extended, Table,
MultiLeader and Helix (2344 B -> 848 B per entity): construction sites
wrap in Box, the entity traits forward through Box, and the python
plugin generator converts with into(). Kernel: PR #70 (cuboid ring
winding).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Point clouds: drawn from their scans, attached, cropped and stylized.
The fork's codec patch is resolved to the upstream pins: the point cloud
records landed in opencadcodec 5ef9376, carried through opencadkernel
68eeda1 and opencadgraph e204966.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Bug:
$ISOLINES (header i16) and Surface.u/v_isolines (file-controlled i16)
were only ever floored at 0, never capped, and were handed to the
tessellation kernel as the per-face isolate count - which draws
0..count isolines per face with no ceiling of its own. SETVAR ISOLINES
32767 (or a DWG carrying that header, or a Surface with u_isolines =
32767) turned every rebuild and every per-cursor preview of
PRESSPULL/REVOLVE/SWEEP/LOFT into a multi-million-vertex computation:
measured 4,259,966 edge vertices for one 12-unit cylinder preview.
Reproduce:
1. SETVAR ISOLINES 32767 (or open a DWG whose $ISOLINES is large, or
edit a Surface whose u_isolines is 32767).
2. Draw a circle and start REVOLVE (or PRESSPULL/SWEEP/LOFT), or shade
any 3D solid.
3. Before: the app allocates without bound - abort on memory failure or
a permanent freeze; previews recompute on every mouse move.
4. After: previews stay interactive; a hostile header renders exactly
like the largest value the Options slider can request.
Fix:
Cap each value at the ceiling this product already agrees on, where the
type changes from i16 to the kernel count: header $ISOLINES <= 64 (the
Options slider's own "Isolines per surface" range), Surface
u/v_isolines <= 200 (the plugin editor's writable range). One helper
(clamp_header_isolines) replaces all 11 copies of .max(0) as usize
across the entity/scene/control/display feed sites,
surface_isoline_counts clamps both axes, and SETVAR ISOLINES stores
x.clamp(0, 64) so the echo, slider and re-saved files agree with what
renders; file-sourced headers keep their raw stored value and are
clamped at consumption. Clamping is density-only: geometry, bounds and
saves are untouched. Three regression tests cover hostile surface
counts, the SETVAR entry, and a file-sourced header measured through
display tessellation (hostile renders like the slider ceiling, negative
renders like zero).
Bug:
The DGN compound-file reader walked FAT chains with a fixed
1,000,000-iteration guard - a step budget, not a byte budget. A
self-referential FAT entry (fat[s] = s) burns the whole budget reading
the same sector: 4.1 GB per call at 4 KiB sectors. Directory, mini
stream and miniFAT chains are alive simultaneously (about 12 GB peak) ->
allocator abort while merely selecting the file in the attach dialog.
Separately, models() inflated every model directory's streams (512 MB
each) with no total, and directory entries can all point at one shared
payload - 1.2 GB retained from a 4.3 MB file.
Reproduce:
1. Craft a ~1.5 KB CFB (sector shift 9) whose directory start sector's
FAT entry points at itself (zeroed FAT sector).
2. ATTACH and pick the file (any listing path parses the CFB).
3. Before: the directory chain grows to 4,000,000 entries from a
1536-byte file, then abort.
4. After: every chain is bounded by the file's own sector count -
instant, memory at most the file size.
5. For the models path: a file with 300 model dirs sharing a 4 MB
payload inflated to 1,258,291,200 bytes before; now it stops at the
file-scaled budget (max(file x 64, 256 MiB)).
Fix:
chain bounds itself by file bytes / sector size: a chain can only read
in-file sectors, and after that many steps it must have repeated one -
the walk is deterministic, so a repeat is a cycle that yields nothing
new. That is the strongest bound the format allows and it never
truncates a legitimate chain (each sector is read at most once). The
mini-stream loop uses the same argument over mini.len() / 64. models()
checks a file-scaled total budget before inflating each dir; later dirs
are dropped (earlier ones still load, name/table discovery degrades
gracefully). Tests: the hostile 1.5 KB file keeps all bytes <= file
size; the 300-model file stays within budget.
Bug:
EMF polyline/polygon records carry a ring count as a raw u32.
pts16/pts32 did Vec::with_capacity(count) on it directly - a crafted
4294967295 pre-allocates about 4 billion slots and the allocator aborts.
The existing total > 400_000 guard only checked the sum of ring counts,
never an individual ring's vector.
Reproduce:
1. Craft an EMF whose POLYPOLYLINE/POLYGON count is 0xFFFFFFFF.
2. Attach/import it.
3. Before: "pre-allocated 4294967295 slots" then memory allocation
failed (multi-GB pre-allocation).
4. After: the vector is sized from the bytes actually present in the
record; the parse consumes what the record holds.
Fix:
pts16/pts32 size their capacity as count.min(available_bytes / point_size)
- the record's own remaining length is the ceiling, so allocation is
O(record). The iteration logic is untouched: it stops when the bytes run
out, so output for valid records is byte-identical (the guard is a pure
allocation bound). Tests: the u32::MAX ring no longer pre-allocates;
valid single- and multi-ring records still parse.
Bug:
arc_cubics turned a raw sweep angle from the DGN file straight into a
piece count - ceil(sweep ...) as usize fed with_capacity, and an extreme
sweep (huge, or +/-inf) produced a count that overflows usize
allocation, causing a capacity overflow panic while attaching the file.
Legal DGN arcs top out near 104 radians.
Reproduce:
1. Craft a DGN with an arc element whose sweep angle is enormous (1e18+,
+/-inf).
2. ATTACH it.
3. Before: "capacity overflow" at raw_vec during model build.
4. After: the arc clamps to +/-4096 quarter-turns (sign preserved, NaN
passes through, +/-inf handled) and renders as many cubics as fit.
Fix:
The clamp happens in the one funnel (arc_cubics) before ceil: sweep is
limited to +/-4096 x 90 degrees, far above the legal 104 rad but orders
below what overflows the count. The sign of the sweep is preserved so
arcs keep their direction, and NaN passes through to the existing
empty-arc handling. Tests cover three sweep magnitudes (including
+/-inf) building without overflow.
Bug:
The PDF underlay renderer sized its pixmap from page size x requested DPI
with no ceiling: a large page at a high raster DPI (display or print)
computes a multi-gigabyte image - testing reached a 17 GB pixmap and a
543-second render attempt before the allocator gave up.
Reproduce:
1. Attach a large-page PDF; set a high raster DPI (print above 150 DPI,
or display with DPI raised).
2. Before: minutes of rendering and a multi-GB allocation, then abort.
3. After: display rasters are clamped so the longest side is at most
3072 (like DWF/DGN, the effective DPI is stored); print rasters over
268,435,456 pixels are refused with an error instead of rendered.
Fix:
Display (at or below 150 DPI): the scale is clamped to
RASTER_SIDE/longest before the pixmap is allocated, and the DPI actually
used is stored for downstream math. Print (above 150 DPI): refused when
width x height exceeds MAX_PRINT_RASTER_PIXELS (256 Mpx) or is
non-finite - print DPI is never silently rescaled because the
sheet-mm-from-exact-DPI conversion must stay exact; the user gets an
error instead of a hang. A regression test covers the hostile page x DPI
case (about 1 second, previously 543 seconds and 17 GB).
Bug:
Loading a DWFx package recursed twice on untrusted input:
1. roxmltree's tokenizer (parse_element <-> parse_content) recurses once
per element nesting level (present in versions 0.20 and 0.21) - a
page or descriptor nested about 100k deep overflows the stack inside
Document::parse, before any application code runs;
2. the XPS walk recursed once per nested Canvas with no limit - about
10k levels overflowed the stack.
Either one aborts the process during ATTACH.
Reproduce:
1. Build a DWFx (zip) whose .fpage / descriptor.xml nests elements
(e.g. Canvas) about 100,000 deep.
2. ATTACH it.
3. Before: STATUS_STACK_OVERFLOW (0xc00000fd) - process death; a test
calling only roxmltree::Document::parse on the same bytes reproduces
it, proving the dependency's tokenizer dies first.
4. After: the file is rejected before parse (a log::warn records "XML
nests deeper than 256 levels ... document rejected"), or - for Canvas
nesting between 65 and 256 - the page renders with the too-deep
subtree skipped while siblings and the rest of the page stay.
Fix:
A linear depth scan (xml_depth_within, skipping comments, CDATA, PIs,
doctype subsets and quoted attribute values so their angle brackets
never count; it may over-reject malformed markup but must never
under-count) gates all three Document::parse sites at 256 levels - real
pages nest single digits - and walk caps Canvas nesting at 64 (continue
skips that subtree, siblings unaffected), matching the compound-file
walker's cap. Rejections are logged so a rejected depth is
distinguishable from a corrupt parse.
Bug:
decode_packed (RLE-packed DIB decoding) guarded the header with a
12-byte check but then read fixed fields at offsets 14-16, 16-20 and
32-36. Any header shorter than 36 bytes that took the non-core path
panicked with an out-of-bounds slice - every input in that range.
Reproduce:
1. Open a BMP whose DIB header takes the non-core path but is shorter
than 36 bytes (a 12-35 byte header claiming packed data).
2. Before: out-of-bounds panic at the fixed-field reads.
3. After: the header is rejected and the image skipped; valid headers
(including a 1x1 DIB) still decode.
Fix:
The non-core branch requires len >= 36 before any of its fixed reads -
every input under 36 bytes panicked before the fix, so no non-panicking
behaviour changes. Valid packed DIBs (tests include a 1x1 image) decode
unchanged.
Bug:
The EMF record reader accepts nSize = 28 (header-only) so legal empty
polylines load, but the sub-arm handling those iTypes also reads a u32
count at offset 28 for records that are not empty - rec.get(28..32) on a
28-byte record is out of bounds, panicking while importing the file.
Reproduce:
1. Craft an EMF with an EMR_POLYLINE (or related iType) record whose
nSize is exactly 28.
2. Attach/import it.
3. Before: out-of-bounds panic reading at offset 28.
4. After: the undersized record is skipped; a full 36-byte record still
renders.
Fix:
The sub-arm that reads at offset 28 now requires rec.len() >= 32 (its
first read), while the shared arm's guard stays at 28 so legal
header-only empty polylines keep loading. Undersized records are skipped
the way other malformed records already are. Tests: iType 7 and 91
records of exactly 28 bytes are skipped, a 36-byte record still renders.
A lone unlocked point cloud selected in a 3D visual style shows a move
gizmo at its insertion point: X/Y/Z arrows and XY/YZ/ZX squares. Dragging
an arrow moves the cloud along that axis, a square moves it in that
plane; the hovered part is highlighted and each drag is one undo step.
Co-Authored-By: Claude Opus 5.5 <[email protected]>