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.
Follow-up to 0bfd5dcc:
- A clipped block hatch kept its unclipped plane-local outline, which the
viewport plot projection prefers, so fills bled past the XCLIP boundary
on layouts. Rebuild it from the clipped outline.
- insert_spatial_filter / filter_handle run for every INSERT in each
render walk; resolve the xdictionary through the entity link or the
document index only, and leave the whole-object scan to set_insert_clip.
Fixes#1508
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve the SPATIAL filter through extension-dictionary fallbacks
(missing xdictionary handle, DictionaryWithDefault, case/whitespace
tolerant keys) so clipped INSERTs no longer plot unclipped. The
inverse-block matrix is used as stored; the codec decodes the on-disk
column-major layout (fixed upstream).
- The Frames list shows the drawing's frame state (hide / display and plot /
display but not plot / frames vary), follows commands, undo and document
switches, and lists the mixed state as a disabled item.
- DWFFRAME and DGNFRAME are frame variables of their own, kept in the header,
and FRAME sets them with the others.
- Out-of-range frame values say "Requires an integer between 0 and 2." and
ask again; a set value reports "Regenerating model.".
- IMAGEFRAME is stored in the image variables as bits (2 is written as 3) and
a drawing without them uses the profile value, which setting IMAGEFRAME
updates and the settings file keeps.
- Raster images, PDF underlays and wipeouts are picked on their frame only;
underlay snap geometry is never picked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- CLIP continues as IMAGECLIP on a raster image and as VPCLIP on a layout
viewport, with the reference prompts and stored data.
- Image clip boundaries sit on pixel centres, OFF only clears the display
flag, and an inverted boundary cuts the image.
- VPCLIP polygon (Arc/Length/Close/Undo), clipping object and Delete; the
viewport shrinks to the boundary's exact extents, view kept.
- XCLIP Clipdepth: Remove clears only the plane asked, a front plane behind
the back one is rejected with the reference message.
- Attached and imported images carry the reference display flags.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The codec and kernel repositories and crates are renamed opencadcodec and
opencadkernel. Inside OCS they are depended on under short keys, so code
reads `codec::`, `kernel::` and the new `graph::` (opencadgraph, the node
graph engine). File-format and protocol spellings stay as they were: the
ACIS product id `acadrust`, the ACADRUST_* switches and the plugin
version keys `acadrust_version` / `acadrust_source`.
A button left of MCP on the command line opens a translucent node graph
over the viewport, with a palette of object, input, math, logic, list,
text and geometry nodes added by click or drag. An object node owns
drawing entities and its rows are the Properties panel's own rows for
them: the single-object page is extracted from refresh_properties as
entity_property_sections, values use the control protocol's
property_json, and writes go through set_property_value with a property
target override, so the panel, MCP and the graph share one read/write
path. A list reaching an object row makes one entity per element. Each
graph action is one undo step; the canvas pans and zooms.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- An inverted clip's roundtrip record goes in the filter's extension
dictionary through the codec's own API (ensure_extension_dictionary /
xrecord / extension_dictionary_handle) instead of a scan of every
object, which the renderer ran for each clipped reference.
- Pin cadcodec 1c3be0c (the DXF writer now links a spatial filter's
extension dictionary, so that record is not left unreferenced) and
cadkernel deb529b; plugin crates and the Python plugin lock follow.
- The XCLIP boundary preview stays f64.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Emit transformed analytical curves (circles, arcs, ellipses) in block
expansion as standalone wires rather than merging them into batched line
strips.
- Keep render_instance unset on analytical curves in block prototypes so
they route directly to CircleGpu / EllipseGpu instead of BlockWireGpu.
- Support TangentGeom::Circle in extract_circle_instance and wire partitioning.
- Drop analytical tangents on wires clipped by xclip so clipped geometry
falls back correctly to line segments.
- Add unit test verifying block circles and arcs extract as analytical GPU
instances.
Switching to a Layout re-tessellated the whole model — `memo_miss=186455`,
2034 ms — to arrive at geometry identical to what was already in the memo.
The only thing that differed was colour.
`local_wires_for` already knows this: it stores the raw colour and drops
its background argument with `let _ = bg_color;`, noting that
`Batches::finalize` applies `adapt_to_bg` last, "so the cache no longer
has to rebuild on BACKGROUND / layout-switch". The tessellation memo's
guard had not been told, and still mixed the background in, so every
entry was discarded on a switch.
`finalize` now records what its colour resolution consumed — the raw
colour, the raw glyph colours, and the three flags it branches on — as
`WireModel::bg_adapt`, and `resolve_colors_for_bg` replays it for another
background. The guard drops the background, so a memo entry serves any
layout.
The resolution recomputes from the recorded raw colour rather than from
the resolved one, which makes it idempotent and therefore safe over a set
that mixes memo hits with freshly tessellated misses. Re-adapting the
resolved colour would not be safe: `adapt_to_bg` snaps a near-white colour
to pure black on a light background, and pure black comes back pure
*white* on a dark one, losing the tint. A test pins that asymmetry so the
raw colour is not optimised away later.
The contract is tested against the real `finalize` rather than a copy of
its logic: for every background pair, building under one and resolving for
the other lands exactly where building under the other would have.
`bg_adapt` is `None` for wires built anywhere else, and the resolution
skips them, so nothing outside the block cache changes colour. It is also
`None` when the resolution would be a no-op — a preserved colour with no
canvas fill looks the same under every background — which keeps the common
case at no cost.
Ordering is unchanged: the pass runs inside the builder, before
`append_scene_markers` and `apply_refedit_fade`, both of which have their
own separate background dependency.
`wires-build` gains `colors=` so the pass has a number of its own.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Share source buffers across block placements and render nested content with GPU instance transforms.\n\nChunk silhouettes and compact mesh material data so large drawings stay under device buffer limits and remain responsive.
Route model, paper, viewport and nested block content through one traversal while preserving leaf renderers. Carry decoded material state into mesh shading and harden solid geometry conversion.
Clipped INSERTs cut their content correctly but never showed the
boundary itself. When the header XCLIPFRAME is 1 or 2 (visible), a
closed ring over the world-space clip polygon now rides the insert's
resolved colour and line weight. The frame is deliberately not clipped —
it is the boundary — and carries no snap points.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two fixes for opening drawings with XCLIP'd blocks (crashed / mis-rendered
since SDF text became the default).
- block_cache emit_wire: the fill_tris/fill_tris_low length debug_assert
panicked in debug builds on a legitimate case — a Leader/dimension arrowhead
fill nested in a block carries fill_tris with an empty fill_tris_low (the
WireModel contract's "all-zero low" form, which the code already handles via
get().unwrap_or). Relax the assert to permit the empty case; a *partial* low
half is still caught.
- xclip clip_wires: also clip the SDF glyph quads. Block-internal text was
bleeding past the clip boundary (mixed batch) or vanishing (the tail retain
dropped empty-points wires). Now each glyph quad is kept/dropped by its
centroid vs the boundary (whole-glyph granularity), text_verts-carrying wires
survive the retain, and the pick-box AABB is recomputed from the surviving
glyphs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rearchitect SDF text from a document-wide collector to per-entity
production during tessellation, so each entity owns its glyph quads and
composite/nested text is covered for free (no re-explosion, no per-frame
document walk).
- WireModel gains `text_verts`: glyph quads ride on the wire, so they are
cached by the tess memo, cloned on hit, and transformed by the block
expand loop exactly like `points`.
- Text arm (tessellate.rs) builds the quads when SDF is on and suppresses
the strokes; render gathers text_verts from the viewport wire set
(`gather_text_verts`, cached on wire content id) — the old collector is
removed. Covers model, dimension, table, block-internal and paper-sheet
text uniformly.
- MLEADER text (lays out via layout_mtext, not the Text arm) gets its own
SDF branch driven by the per-run GlyphRun.
- Pick box comes from the rendered glyph quads (true text extent), not
entity_aabb which mis-places the box for MTEXT; composite paths no longer
clobber the empty text wire's tight AABB (dim pick box hugs the text).
- Selection / hover highlight via a text-highlight overlay (upload_text_
highlight, keyed on selection_generation) mirroring the selected-wire
xray overlay; base glyphs stay neutral so a deselect leaves no stale tint.
- Debug: env OCS_TEXT_BOX draws each run's glyph-bounds rectangle.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`clip_hatch_boundary` lifted each loop into the clip polygon's world
space (`+world_origin`) to run the Sutherland–Hodgman clip, but returned
the result in absolute world coordinates. The renderer adds the per-hatch
`world_origin` anchor back in the vertex shader, so clipped hatches with a
large origin (e.g. UTM drawings) were drawn double-shifted and lost f32
precision. Subtract the origin again on the way out so the output stays in
the same precision-preserving anchor space as the input.
Also drop a stale third argument from two xclip tests left over from the
world-offset removal, which had broken `cargo test` compilation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Clean up the remaining `let [ox,oy,oz] = [0,0,0]` bindings and their
zero-valued add/subtract arithmetic across scene/mod.rs (mesh LOD resplit,
model AABB, wide-polyline fills, wipeout boundary, hatch/solid models,
insert/entity AABB), table, multileader and xclip. Coordinates now read as
the plain absolute values they are. No world_offset reference remains.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Now that geometry reaches the GPU/CPU as absolute coordinates via the
double-single relative-to-eye path, world_offset is always [0,0,0]. Strip
the parameter that was threaded through the whole tessellation / block-
expansion / fallback / camera-decode chain and the ExpandCtx field that
carried it — ~340 references across 20 files, all subtracting zero.
- truck_tess: to_local / to_local_low / tessellate_* drop the offset arg;
to_local is now a pure double-single split.
- tessellate: offset_to_ds → points_to_ds (pure DS split); tessellate(),
tessellate_entity(), fallback_geometry(), solid_wire_fallback(),
entity_aabb(), expand_insert(), expand_block_meshes(), the dimension
helpers (vec3_local, dimension_snap_pts, …), text_support, leader,
multileader, image_model, xclip and camera_from_view all lose the param.
- block_cache: ExpandCtx loses its world_offset field.
- Remove the now-dead offset_snap_pts and the unused set_grid_snap.
Behaviour is unchanged (the offset was zero everywhere); the compiler
verifies every call site.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Clipped blocks were broken at UTM and lost their clip when pasted into
another drawing.
- Clip geometry: world_clip_polygon_f64 keeps the boundary in absolute f64;
clip_wires now clips in a frame relative to the boundary's first vertex
(small coords → exact f32 Sutherland–Hodgman) and re-splits the result
into the double-single high/low pair the relative-to-eye renderer needs,
so the clipped block draws correctly and no longer poisons ZOOM Extents.
- ZOOM Extents (fit_all): the outlier reject compared absolute UTM
coordinates against a centre-RELATIVE span (local_extent_max), rejecting
the whole drawing → no-op. Made the reject distance-from-median-centre.
- Cross-drawing paste: clipboard now snapshots each copied entity's whole
xdictionary object graph (ClipboardDeps.ext_objects) and recreates it on
paste with fresh handles + remapped references, re-pointing the pasted
entity's xdictionary at the new root. Wired into PASTECLIP and PASTEORIG.
Fixed handle allocation (allocate_handle, not the non-advancing
next_handle) so the dictionary chain doesn't collapse onto one handle.
- Updated hit_test / plugin_host tests for the eye-relative + DVec3 signatures.
Known gaps: PASTEBLOCK does not yet carry the clip (clip would need to live
inside the new wrapper block definition); hatch-clip path still f32.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
WireModel/LocalWire/BatchEntry key_vertices are now Vec<[f64; 3]>;
tessellation keeps them in f64 (no f32 cast at offset subtraction),
producers (dimension, leader, multileader) build them from the acadrust
f64 source, the block-cache transform keeps f64, and the face3d Face3D
quad path splits them into the double-single (high, low) pair. Consumers
(snap endpoint/midpoint, xclip) read via f32 at the leaf. Still
offset-relative (inert); endpoint/midpoint snap is no longer f32-capped.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
WireModel/LocalWire/TruckEntity snap buffers and the producers/consumers
now carry snap candidates as DVec3 (offset_snap_pts, dimension/multileader
producers, block-cache transform, pick/xclip, snap module). Still
offset-relative, so behaviour is unchanged — this just lets the snap
transport survive the upcoming switch to absolute coordinates without an
extra f32 quantization. The snap module still resolves in f32 (as_vec3 at
entry), matching today's precision.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Move the 30 flat modules under src/scene/ into six folders by role:
convert (entity→geometry), text (fonts/shaping), model (render models),
pick (hit-test/selection/grips/xclip), view (camera/transform/render),
cache (block + property caches). pipeline/ is unchanged.
Full path migration — all `scene::X` references become
`scene::<group>::X`, no compatibility re-exports. render.rs items that
were `pub(super)` (parent was scene) become `pub(in crate::scene)` to
keep their original scene-wide visibility.
No behavior change; build and tests are green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>