fix(blocks): prevent circular block nesting and cut render cycles

Introduce would_create_cycle in block_service to prevent recursive block definitions with case-insensitive matching across block records. Enforce cycle refusal at INSERT commit in BEDIT and during block redefinition (redefine_block_raw) with a shared error message. Ensure render-time recursion truncates safely with a one-time session warning on cyclic documents.
This commit is contained in:
Karim Jerbi committed 2026-10-13 11:10:11 +01:00
1 parent 59bb6faabd
commit 9c08e8a3ed
8 files changed
+435 -24

No files matched your search

+44
View File
@@ -1519,3 +1519,47 @@ fn locked_attedit_attribute_flags_cases() {
));
assert_eq!(preset_entity.get_value(), "edited_preset");
}
#[test]
fn locked_bedit_insert_refuses_cycle() {
let mut app = app();
let name_a = "BEDIT_CYC_A";
let name_b = "BEDIT_CYC_B";
define_block(&mut app, name_a);
// B contains an insert of A
let ins_a = Insert::new(name_a.to_string(), Vector3::ZERO);
let i = tab(&app);
app.tabs[i]
.scene
.define_block_raw(name_b, Vector3::ZERO, vec![EntityType::Insert(ins_a)]);
// Now open BEDIT for block A
let insert_a = add_insert(&mut app, name_a);
begin_bedit(&mut app, insert_a);
assert!(bedit_open(&app));
// Try committing an INSERT of B into A inside BEDIT space:
// Inserting B into A would create A -> B -> A cycle
let _ = app.dispatch_command("-INSERT");
let mut feed = |text: &str| {
let _ = app.update(crate::app::Message::CommandInput(text.to_string()));
let _ = app.update(crate::app::Message::CommandSubmit);
};
feed(name_b);
feed("0,0");
feed("1");
feed("1");
feed("0");
// Verify refusal error on command line
let errs = errors(&app);
assert!(
errs.iter()
.any(|e| e.contains("circular dependency detected")),
"expected cycle error in errors: {errs:?}"
);
let _ = app.dispatch_command("BEDIT_DISCARD");
}
+5 -1
View File
@@ -427,7 +427,11 @@ impl OpenCADStudio {
for def in deps.blocks {
let scene = &mut self.tabs[i].scene;
if redefine && def.name.eq_ignore_ascii_case(name) {
scene.redefine_block_raw(&def.name, def.base_point, def.entities);
if let Err(err) = scene.redefine_block_raw(&def.name, def.base_point, def.entities)
{
self.command_line.push_error(&err);
return Err(err);
}
} else if scene.document.block_records.get(&def.name).is_none() {
scene.define_block_raw(&def.name, def.base_point, def.entities);
}
+5 -2
View File
@@ -580,11 +580,14 @@ impl OpenCADStudio {
.define_block_raw(&def.name, def.base_point, def.entities);
}
if redefine {
self.tabs[i].scene.redefine_block_raw(
if let Err(err) = self.tabs[i].scene.redefine_block_raw(
&name,
codec::types::Vector3::new(base.x, base.y, base.z),
entities,
);
) {
self.command_line.push_error(&err);
return Err(err);
}
} else {
self.tabs[i]
.scene