Files
OneCAD-Draft/tests/block_text_draw_depth.rs
T
Hakan SevenandClaude Opus 5.5 f537aff75c feat: node graph overlay; move to opencadcodec/opencadkernel/opencadgraph
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 <[email protected]>
2026-09-25 07:34:23 +03:00

150 lines
6.1 KiB
Rust

// Regression: text INSIDE a block must reach the GPU at neutral vertex depth
// with its composed child rank carried in `WireModel::depth_override`, so the
// upload resolves it against the insert's scene-graph entry
// (depths[insert] + override * half) — the same composition the wipeout/hatch
// fills get. Baking the raw per-block rank into the vertices instead sinks
// text with a negative rank below its own block's wipes (the "G-3" mark
// labels of BS33-11) and catapults text with a positive rank above later
// draw-order geometry.
//
// The scene mirrors the real-world shape of the bug: a block whose children
// are [wipeout, line, text], inserted into a drawing big enough that the
// insert's child band is as fine-grained as a real one.
use codec::entities::{Insert, Text, Wipeout};
use codec::tables::BlockRecord;
use codec::types::Vector3;
use codec::EntityType;
use OpenCADStudio::scene::cache::block_cache::{expand_insert, BlockCache};
use OpenCADStudio::scene::view::render::InheritStyle;
use OpenCADStudio::scene::Scene;
use OpenCADStudio::scene::WireModel;
const DRAW_ORDER_BIAS: f32 = 0.001;
const DEPTH_QUANTA: f32 = 16_777_216.0; // 2^24 (Depth24Plus)
#[test]
fn block_text_depth_composes_through_the_instance_path() {
let mut scene = Scene::new();
let br_h = codec::Handle::new(scene.document.next_handle());
let mut br = BlockRecord::new("MARK");
br.handle = br_h;
scene.document.block_records.add(br).unwrap();
// Child 0: the wipeout the text is drawn after (the text must win).
let mut wipe = Wipeout::new();
wipe.insertion_point = Vector3::new(-10.0, -10.0, 0.0);
wipe.u_vector = Vector3::new(20.0, 0.0, 0.0);
wipe.v_vector = Vector3::new(0.0, 20.0, 0.0);
let mut wipe_e = EntityType::Wipeout(wipe);
wipe_e.common_mut().owner_handle = br_h;
let wipe_handle = scene.document.add_entity(wipe_e).unwrap();
// Child 1: a spacer so the text sits two sibling steps above the wipe,
// the same gap as the real BS33-11 mark blocks.
let mut spacer = codec::entities::Line::default();
spacer.start = Vector3::new(-10.0, -10.0, 0.0);
spacer.end = Vector3::new(10.0, 10.0, 0.0);
let mut spacer_e = EntityType::Line(spacer);
spacer_e.common_mut().owner_handle = br_h;
let _ = scene.document.add_entity(spacer_e).unwrap();
// Child 2: the text.
let mut txt = Text::new();
txt.value = "G-3".to_string();
txt.height = 2.5;
txt.insertion_point = Vector3::new(0.0, 0.0, 0.0);
let mut txt_e = EntityType::Text(txt);
txt_e.common_mut().owner_handle = br_h;
let text_handle = scene.document.add_entity(txt_e).unwrap();
// A large model space, so the insert's child band compresses to the
// fine granularity real drawings have (~600 top-level entities puts the
// per-sibling spacing at a handful of 24-bit quanta, where an oversized
// wipeout depth bias would swallow it).
for i in 0..600 {
let mut line = codec::entities::Line::default();
line.start = Vector3::new(f64::from(i) * 10.0 - 3000.0, -50.0, 0.0);
line.end = Vector3::new(f64::from(i) * 10.0 - 3000.0, 50.0, 0.0);
let _ = scene.document.add_entity(EntityType::Line(line));
}
let ins = Insert::new("MARK", Vector3::new(0.0, 0.0, 0.0));
let ins_handle = scene.add_entity(EntityType::Insert(ins));
let depths = scene.draw_depth_map();
let [ins_d, ins_half] = depths
.get(&ins_handle.value())
.copied()
.expect("insert must be ranked");
let text_label = depths
.get(&text_handle.value())
.expect("block children must be ranked")[0];
let wipe_label = depths.get(&wipe_handle.value()).expect("wipe ranked")[0];
assert!(
text_label > wipe_label,
"precondition: the drawing orders the text after the wipeout"
);
let cache = BlockCache::build(
&scene.document, 1.0, None, true, [0.0, 0.0, 0.0, 1.0], None, &depths,
);
let wires = expand_insert(
&scene.document, &cache, &Insert::new("MARK", Vector3::new(0.0, 0.0, 0.0)),
ins_handle,
[1.0, 1.0, 1.0, 1.0], 0, 0.0, [0.0; 8], 1.0,
InheritStyle { color: [1.0, 1.0, 1.0, 1.0], pat_len: 0.0, pat: [0.0; 8], lw_px: 1.0 },
0, false, false, 1.0, None, None, false, [0.0, 0.0, 0.0, 1.0], 1.0,
OpenCADStudio::scene::BlockScalePolicy::FromInsert, false,
)
.expect("block defn cached");
let text_wires: Vec<&WireModel> =
wires.iter().filter(|w| !w.text_verts.is_empty()).collect();
assert!(
!text_wires.is_empty(),
"block TEXT must tessellate into glyph quads"
);
// The wire is named after its insert (decimal handle), and the upload
// resolves that name against the depth map: this is the composition the
// GPU finally sees.
for w in &text_wires {
assert!(
w.text_verts.iter().all(|v| v.draw_depth == 0.0),
"glyph vertices must stay at neutral depth; the instance path owns it"
);
assert_eq!(
w.depth_override,
Some(text_label),
"the wire must carry the text's child rank as its depth override"
);
let resolved = w
.name
.parse::<u64>()
.ok()
.and_then(|h| depths.get(&h).copied());
let instance = match (resolved, w.depth_override) {
(Some([d, half]), Some(local)) => d + local * half,
(Some([d, _]), None) => d,
_ => 0.0,
};
assert_eq!(
instance,
ins_d + text_label * ins_half,
"instance depth must compose to depths[insert] + label * half"
);
// The wipeout mask, composed the same way the scene graph does it.
let mask = ins_d + wipe_label * ins_half;
let margin_quanta =
(instance - mask) * DRAW_ORDER_BIAS * DEPTH_QUANTA;
let bias = OpenCADStudio::scene::pipeline::WIPEOUT_DEPTH_BIAS_QUANTA as f32;
assert!(
margin_quanta > bias + 2.0,
"text drawn after its block's wipeout must clear it by more than \
the wipeout depth bias plus headroom, got {margin_quanta} quanta"
);
}
}