forked from 1b-agent/opencadstudio-upstream
feat(blocks): implement Block Definition dialog (BLOCK/BMAKE) with on-screen sequencing
Implement the complete Block Definition dialog window launched via BLOCK
and BMAKE commands, featuring standard CAD layout, workflows, and entity
creation pipelines.
- Dialog UI & Layout:
- 2-column compact CAD grid with synchronized vertical heights (580 × 390px).
- Editable combobox with existing block names, invalid character validation,
and inline redefinition badge.
- Multi-line text editor widget for block descriptions (replacing single-line input).
- Quick Select (QSELECT) button integration restoring dialog state upon completion.
- Confirmation guard when attempting to redefine an existing block definition.
- Centered radio buttons, clean coordinate inputs, and accessible keyboard shortcuts.
- Base Point & On-Screen Workflow:
- BlockOnScreenCommand strictly enforces prompt sequencing: Base point is
prompted before object selection.
- Dynamically recalculates world_to_block and block_to_world affine transforms
when base point is specified on-screen, preventing stale (0,0,0) normalization.
- Supports Retain, Convert to block (INSERT placement), and Delete modes.
- Explode Enforcement:
- Guarded EXPLODE command to prevent decomposing blocks flagged as non-explodable.
- Dynamic Themed Icons:
- Added pick_point.svg, select_objects.svg, quick_select.svg, and warning_triangle.svg.
- Routed all icons through the semantic theming engine so line work (#B4B6B9)
and accents (#6DB7ED) adapt automatically to the active theme.
- Tests:
- Added unit tests for retain/convert/delete modes, redefinition, explodable flag,
on-screen base point transform recalculation, and command sequencing.
This commit is contained in:
1 parent
889a19c362
commit
652f4d6bbc
19 files changed
+2316
-318
No files matched your search
@@ -1,6 +1,6 @@
|
||||
use crate::t;
|
||||
use acadrust::Handle;
|
||||
use glam::DVec3;
|
||||
use crate::t;
|
||||
|
||||
use crate::command::{CadCommand, CmdResult};
|
||||
use crate::modules::{IconKind, ModuleEvent, ToolDef};
|
||||
@@ -92,3 +92,560 @@ impl CadCommand for CreateBlockCommand {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
/// One-shot point picker for the Block Definition dialog's "Pick point" button.
|
||||
pub struct BlockPickBasePointCommand;
|
||||
|
||||
impl CadCommand for BlockPickBasePointCommand {
|
||||
fn name(&self) -> &'static str {
|
||||
"BLOCK"
|
||||
}
|
||||
|
||||
fn prompt(&self) -> String {
|
||||
crate::t!("BLOCK Specify insertion base point:").into_owned()
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
CmdResult::Dispatch(format!("BLOCK_POINT_PICKED {} {} {}", pt.x, pt.y, pt.z))
|
||||
}
|
||||
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
CmdResult::Dispatch("BLOCK_POINT_CANCELLED".to_string())
|
||||
}
|
||||
|
||||
fn on_escape(&mut self) -> CmdResult {
|
||||
CmdResult::Dispatch("BLOCK_POINT_CANCELLED".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Command handling the on-screen prompting sequence when "Specify On-screen" is checked in the dialog.
|
||||
/// Enforces standard command ordering: Base point is ALWAYS prompted before object selection.
|
||||
pub struct BlockOnScreenCommand {
|
||||
options: crate::scene::CreateBlockOptions,
|
||||
ucs: crate::app::helpers::UcsXform,
|
||||
need_base: bool,
|
||||
need_objects: bool,
|
||||
}
|
||||
|
||||
impl BlockOnScreenCommand {
|
||||
pub fn new(
|
||||
options: crate::scene::CreateBlockOptions,
|
||||
ucs: crate::app::helpers::UcsXform,
|
||||
need_base: bool,
|
||||
need_objects: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
options,
|
||||
ucs,
|
||||
need_base,
|
||||
need_objects,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CadCommand for BlockOnScreenCommand {
|
||||
fn name(&self) -> &'static str {
|
||||
"BLOCK"
|
||||
}
|
||||
|
||||
fn prompt(&self) -> String {
|
||||
if self.need_base {
|
||||
crate::t!("BLOCK Specify insertion base point:").into_owned()
|
||||
} else {
|
||||
crate::t!("BLOCK Select objects:").into_owned()
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
if self.need_base {
|
||||
self.options.base_point = pt;
|
||||
self.options.world_to_block = self.ucs.to_ucs_transform_at(pt);
|
||||
self.options.block_to_world = self.ucs.to_wcs_transform_at(pt);
|
||||
self.need_base = false;
|
||||
if self.need_objects {
|
||||
CmdResult::NeedPoint
|
||||
} else {
|
||||
CmdResult::CreateBlockWithOptions {
|
||||
options: Box::new(self.options.clone()),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
}
|
||||
|
||||
fn is_selection_gathering(&self) -> bool {
|
||||
!self.need_base && self.need_objects
|
||||
}
|
||||
|
||||
fn on_selection_complete(&mut self, handles: Vec<Handle>) -> CmdResult {
|
||||
self.options.handles = handles;
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
if self.need_base {
|
||||
return CmdResult::Cancel;
|
||||
}
|
||||
if self.options.handles.is_empty() {
|
||||
return CmdResult::Cancel;
|
||||
}
|
||||
CmdResult::CreateBlockWithOptions {
|
||||
options: Box::new(self.options.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_escape(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::scene::{CreateBlockOptions, Scene};
|
||||
use crate::ui::window::block_definition::BlockObjectMode;
|
||||
use acadrust::entities::Line;
|
||||
use acadrust::types::{Transform, Vector3};
|
||||
|
||||
fn make_test_scene() -> (Scene, Handle) {
|
||||
let mut scene = Scene::new();
|
||||
let mut line = Line::new();
|
||||
line.start = Vector3::new(0.0, 0.0, 0.0);
|
||||
line.end = Vector3::new(10.0, 10.0, 0.0);
|
||||
let h = scene.add_entity(acadrust::EntityType::Line(line));
|
||||
(scene, h)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_block_retain_mode() {
|
||||
let (mut scene, line_h) = make_test_scene();
|
||||
let options = CreateBlockOptions {
|
||||
name: "BLK_RETAIN".to_string(),
|
||||
handles: vec![line_h],
|
||||
base_point: DVec3::ZERO,
|
||||
world_to_block: Transform::identity(),
|
||||
block_to_world: Transform::identity(),
|
||||
mode: BlockObjectMode::Retain,
|
||||
annotative: false,
|
||||
match_orientation: false,
|
||||
scale_uniformly: false,
|
||||
allow_exploding: true,
|
||||
unit: 0,
|
||||
description: "Retain test".to_string(),
|
||||
hyperlink_url: "".to_string(),
|
||||
hyperlink_desc: "".to_string(),
|
||||
redefine: false,
|
||||
};
|
||||
|
||||
let result = scene.create_block_with_options(options);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), Handle::NULL); // No insert created in Retain mode
|
||||
|
||||
// Original line must still exist in document
|
||||
assert!(scene.document.get_entity(line_h).is_some());
|
||||
// Block definition must exist
|
||||
assert!(scene.document.block_records.get("BLK_RETAIN").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_block_convert_mode() {
|
||||
let (mut scene, line_h) = make_test_scene();
|
||||
let options = CreateBlockOptions {
|
||||
name: "BLK_CONVERT".to_string(),
|
||||
handles: vec![line_h],
|
||||
base_point: DVec3::ZERO,
|
||||
world_to_block: Transform::identity(),
|
||||
block_to_world: Transform::identity(),
|
||||
mode: BlockObjectMode::Convert,
|
||||
annotative: false,
|
||||
match_orientation: false,
|
||||
scale_uniformly: true,
|
||||
allow_exploding: true,
|
||||
unit: 4, // mm
|
||||
description: "Convert test".to_string(),
|
||||
hyperlink_url: "".to_string(),
|
||||
hyperlink_desc: "".to_string(),
|
||||
redefine: false,
|
||||
};
|
||||
|
||||
let result = scene.create_block_with_options(options);
|
||||
assert!(result.is_ok());
|
||||
let insert_h = result.unwrap();
|
||||
assert_ne!(insert_h, Handle::NULL);
|
||||
|
||||
// Original line was erased from model space
|
||||
assert!(scene.document.get_entity(line_h).is_none());
|
||||
// Insert entity exists in drawing
|
||||
let insert_entity = scene.document.get_entity(insert_h);
|
||||
assert!(insert_entity.is_some());
|
||||
if let Some(acadrust::EntityType::Insert(ins)) = insert_entity {
|
||||
assert_eq!(ins.block_name, "BLK_CONVERT");
|
||||
} else {
|
||||
panic!("Expected Insert entity");
|
||||
}
|
||||
|
||||
// Check BlockRecord properties
|
||||
let br = scene.document.block_records.get("BLK_CONVERT").unwrap();
|
||||
assert_eq!(br.units, 4);
|
||||
assert!(br.scale_uniformly);
|
||||
assert!(br.explodable);
|
||||
assert_eq!(br.description, "Convert test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_block_delete_mode() {
|
||||
let (mut scene, line_h) = make_test_scene();
|
||||
let options = CreateBlockOptions {
|
||||
name: "BLK_DELETE".to_string(),
|
||||
handles: vec![line_h],
|
||||
base_point: DVec3::ZERO,
|
||||
world_to_block: Transform::identity(),
|
||||
block_to_world: Transform::identity(),
|
||||
mode: BlockObjectMode::Delete,
|
||||
annotative: false,
|
||||
match_orientation: false,
|
||||
scale_uniformly: false,
|
||||
allow_exploding: true,
|
||||
unit: 0,
|
||||
description: "".to_string(),
|
||||
hyperlink_url: "".to_string(),
|
||||
hyperlink_desc: "".to_string(),
|
||||
redefine: false,
|
||||
};
|
||||
|
||||
let result = scene.create_block_with_options(options);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), Handle::NULL);
|
||||
|
||||
// Original line erased
|
||||
assert!(scene.document.get_entity(line_h).is_none());
|
||||
// Block definition exists
|
||||
assert!(scene.document.block_records.get("BLK_DELETE").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_block_redefinition() {
|
||||
let (mut scene, line1_h) = make_test_scene();
|
||||
let options1 = CreateBlockOptions {
|
||||
name: "REDEF_BLOCK".to_string(),
|
||||
handles: vec![line1_h],
|
||||
base_point: DVec3::ZERO,
|
||||
world_to_block: Transform::identity(),
|
||||
block_to_world: Transform::identity(),
|
||||
mode: BlockObjectMode::Retain,
|
||||
annotative: false,
|
||||
match_orientation: false,
|
||||
scale_uniformly: false,
|
||||
allow_exploding: true,
|
||||
unit: 0,
|
||||
description: "Initial".to_string(),
|
||||
hyperlink_url: "".to_string(),
|
||||
hyperlink_desc: "".to_string(),
|
||||
redefine: false,
|
||||
};
|
||||
|
||||
assert!(scene.create_block_with_options(options1).is_ok());
|
||||
|
||||
// Attempting to create duplicate without redefine flag fails
|
||||
let line2_h = {
|
||||
let mut line2 = Line::new();
|
||||
line2.start = Vector3::new(5.0, 5.0, 0.0);
|
||||
line2.end = Vector3::new(15.0, 15.0, 0.0);
|
||||
scene.add_entity(acadrust::EntityType::Line(line2))
|
||||
};
|
||||
|
||||
let options2 = CreateBlockOptions {
|
||||
name: "REDEF_BLOCK".to_string(),
|
||||
handles: vec![line2_h],
|
||||
base_point: DVec3::ZERO,
|
||||
world_to_block: Transform::identity(),
|
||||
block_to_world: Transform::identity(),
|
||||
mode: BlockObjectMode::Retain,
|
||||
annotative: false,
|
||||
match_orientation: false,
|
||||
scale_uniformly: false,
|
||||
allow_exploding: true,
|
||||
unit: 6, // meters
|
||||
description: "Redefined".to_string(),
|
||||
hyperlink_url: "".to_string(),
|
||||
hyperlink_desc: "".to_string(),
|
||||
redefine: false,
|
||||
};
|
||||
|
||||
let err_res = scene.create_block_with_options(options2.clone());
|
||||
assert!(err_res.is_err());
|
||||
assert!(err_res.unwrap_err().contains("already exists"));
|
||||
|
||||
// Redefine with redefine: true succeeds and updates fields
|
||||
let mut options3 = options2;
|
||||
options3.redefine = true;
|
||||
let redef_res = scene.create_block_with_options(options3);
|
||||
assert!(redef_res.is_ok());
|
||||
|
||||
let br = scene.document.block_records.get("REDEF_BLOCK").unwrap();
|
||||
assert_eq!(br.description, "Redefined");
|
||||
assert_eq!(br.units, 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_block_validation() {
|
||||
let (mut scene, line_h) = make_test_scene();
|
||||
|
||||
// Empty name
|
||||
let opt_empty_name = CreateBlockOptions {
|
||||
name: " ".to_string(),
|
||||
handles: vec![line_h],
|
||||
base_point: DVec3::ZERO,
|
||||
world_to_block: Transform::identity(),
|
||||
block_to_world: Transform::identity(),
|
||||
mode: BlockObjectMode::Retain,
|
||||
annotative: false,
|
||||
match_orientation: false,
|
||||
scale_uniformly: false,
|
||||
allow_exploding: true,
|
||||
unit: 0,
|
||||
description: "".to_string(),
|
||||
hyperlink_url: "".to_string(),
|
||||
hyperlink_desc: "".to_string(),
|
||||
redefine: false,
|
||||
};
|
||||
assert!(scene.create_block_with_options(opt_empty_name).is_err());
|
||||
|
||||
// Anonymous '*' prefix blocked
|
||||
let opt_star = CreateBlockOptions {
|
||||
name: "*ANON".to_string(),
|
||||
handles: vec![line_h],
|
||||
base_point: DVec3::ZERO,
|
||||
world_to_block: Transform::identity(),
|
||||
block_to_world: Transform::identity(),
|
||||
mode: BlockObjectMode::Retain,
|
||||
annotative: false,
|
||||
match_orientation: false,
|
||||
scale_uniformly: false,
|
||||
allow_exploding: true,
|
||||
unit: 0,
|
||||
description: "".to_string(),
|
||||
hyperlink_url: "".to_string(),
|
||||
hyperlink_desc: "".to_string(),
|
||||
redefine: false,
|
||||
};
|
||||
assert!(scene.create_block_with_options(opt_star).is_err());
|
||||
|
||||
// Empty handles
|
||||
let opt_no_handles = CreateBlockOptions {
|
||||
name: "EMPTY_HANDLES".to_string(),
|
||||
handles: vec![],
|
||||
base_point: DVec3::ZERO,
|
||||
world_to_block: Transform::identity(),
|
||||
block_to_world: Transform::identity(),
|
||||
mode: BlockObjectMode::Retain,
|
||||
annotative: false,
|
||||
match_orientation: false,
|
||||
scale_uniformly: false,
|
||||
allow_exploding: true,
|
||||
unit: 0,
|
||||
description: "".to_string(),
|
||||
hyperlink_url: "".to_string(),
|
||||
hyperlink_desc: "".to_string(),
|
||||
redefine: false,
|
||||
};
|
||||
assert!(scene.create_block_with_options(opt_no_handles).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_block_on_screen_command_ordering() {
|
||||
let dummy_options = CreateBlockOptions {
|
||||
name: "ON_SCREEN_TEST".to_string(),
|
||||
handles: vec![],
|
||||
base_point: DVec3::ZERO,
|
||||
world_to_block: Transform::identity(),
|
||||
block_to_world: Transform::identity(),
|
||||
mode: BlockObjectMode::Convert,
|
||||
annotative: false,
|
||||
match_orientation: false,
|
||||
scale_uniformly: false,
|
||||
allow_exploding: true,
|
||||
unit: 0,
|
||||
description: "".to_string(),
|
||||
hyperlink_url: "".to_string(),
|
||||
hyperlink_desc: "".to_string(),
|
||||
redefine: false,
|
||||
};
|
||||
|
||||
// When both base point and objects are to be specified on-screen:
|
||||
let ucs = crate::app::helpers::UcsXform::identity();
|
||||
let mut cmd = BlockOnScreenCommand::new(dummy_options, ucs, true, true);
|
||||
|
||||
// 1. Initial step must be insertion base point
|
||||
assert!(cmd.prompt().contains("Specify insertion base point"));
|
||||
|
||||
// 2. Pick base point -> prompt switches to object selection
|
||||
let picked_pt = DVec3::new(10.0, 20.0, 0.0);
|
||||
let res1 = cmd.on_point(picked_pt);
|
||||
assert!(matches!(res1, CmdResult::NeedPoint));
|
||||
assert!(cmd.prompt().contains("Select objects"));
|
||||
assert!(cmd.is_selection_gathering());
|
||||
|
||||
// 3. User attempts enter before selecting objects -> cancelled
|
||||
assert!(matches!(cmd.on_enter(), CmdResult::Cancel));
|
||||
|
||||
// 4. Complete selection with handles
|
||||
let fake_handle = Handle::new(42);
|
||||
cmd.on_selection_complete(vec![fake_handle]);
|
||||
|
||||
// 5. User presses enter to finish selection
|
||||
let finish = cmd.on_enter();
|
||||
match finish {
|
||||
CmdResult::CreateBlockWithOptions { options } => {
|
||||
assert_eq!(options.name, "ON_SCREEN_TEST");
|
||||
assert_eq!(options.base_point, picked_pt);
|
||||
assert_eq!(options.handles, vec![fake_handle]);
|
||||
assert_eq!(options.world_to_block, ucs.to_ucs_transform_at(picked_pt));
|
||||
assert_eq!(options.block_to_world, ucs.to_wcs_transform_at(picked_pt));
|
||||
}
|
||||
_ => panic!("Expected CreateBlockWithOptions"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_block_on_screen_base_point_sets_correct_transforms() {
|
||||
let (mut scene, line_h) = make_test_scene();
|
||||
let initial_options = CreateBlockOptions {
|
||||
name: "ON_SCREEN_BASE_TEST".to_string(),
|
||||
handles: vec![line_h],
|
||||
base_point: DVec3::ZERO,
|
||||
world_to_block: Transform::identity(),
|
||||
block_to_world: Transform::identity(),
|
||||
mode: BlockObjectMode::Convert,
|
||||
annotative: false,
|
||||
match_orientation: false,
|
||||
scale_uniformly: false,
|
||||
allow_exploding: true,
|
||||
unit: 0,
|
||||
description: "".to_string(),
|
||||
hyperlink_url: "".to_string(),
|
||||
hyperlink_desc: "".to_string(),
|
||||
redefine: false,
|
||||
};
|
||||
|
||||
// User picked objects upfront in dialog, but checked "Specify base point on screen"
|
||||
let ucs = crate::app::helpers::UcsXform::identity();
|
||||
let mut cmd = BlockOnScreenCommand::new(initial_options, ucs, true, false);
|
||||
|
||||
// Pick base point at (10, 10, 0) (end of the line in make_test_scene)
|
||||
let pick_pt = DVec3::new(10.0, 10.0, 0.0);
|
||||
let res = cmd.on_point(pick_pt);
|
||||
|
||||
let created_options = match res {
|
||||
CmdResult::CreateBlockWithOptions { options } => *options,
|
||||
_ => {
|
||||
panic!("Expected CreateBlockWithOptions immediately when objects already selected")
|
||||
}
|
||||
};
|
||||
|
||||
assert_eq!(created_options.base_point, pick_pt);
|
||||
assert_eq!(
|
||||
created_options.world_to_block,
|
||||
ucs.to_ucs_transform_at(pick_pt)
|
||||
);
|
||||
assert_eq!(
|
||||
created_options.block_to_world,
|
||||
ucs.to_wcs_transform_at(pick_pt)
|
||||
);
|
||||
|
||||
// Create the block with these options and verify insert position and definition entities
|
||||
let insert_h = scene.create_block_with_options(created_options).unwrap();
|
||||
assert_ne!(insert_h, Handle::NULL);
|
||||
|
||||
// In Convert mode, the insert entity must be placed at the picked base point
|
||||
let insert_entity = scene.document.get_entity(insert_h).unwrap();
|
||||
if let acadrust::EntityType::Insert(ins) = insert_entity {
|
||||
assert_eq!(ins.block_name, "ON_SCREEN_BASE_TEST");
|
||||
assert!((ins.insert_point.x - 10.0).abs() < 1e-6);
|
||||
assert!((ins.insert_point.y - 10.0).abs() < 1e-6);
|
||||
assert!((ins.insert_point.z - 0.0).abs() < 1e-6);
|
||||
} else {
|
||||
panic!("Expected Insert entity");
|
||||
}
|
||||
|
||||
// Inside the block table record, the line entity must have been transformed relative to (10, 10, 0)
|
||||
// Original line was (0,0,0) to (10,10,0), so inside the block it must be (-10,-10,0) to (0,0,0)
|
||||
let br = scene
|
||||
.document
|
||||
.block_records
|
||||
.get("ON_SCREEN_BASE_TEST")
|
||||
.unwrap();
|
||||
let block_line = scene
|
||||
.document
|
||||
.entities()
|
||||
.find(|e| {
|
||||
e.common().owner_handle == br.handle && matches!(e, acadrust::EntityType::Line(_))
|
||||
})
|
||||
.unwrap();
|
||||
if let acadrust::EntityType::Line(l) = block_line {
|
||||
assert!((l.start.x - (-10.0)).abs() < 1e-6);
|
||||
assert!((l.start.y - (-10.0)).abs() < 1e-6);
|
||||
assert!((l.end.x - 0.0).abs() < 1e-6);
|
||||
assert!((l.end.y - 0.0).abs() < 1e-6);
|
||||
} else {
|
||||
panic!("Expected Line entity inside block");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_explodable_flag_prevents_explode() {
|
||||
let (mut scene, line_h) = make_test_scene();
|
||||
let options = CreateBlockOptions {
|
||||
name: "BLK_NO_EXPLODE".to_string(),
|
||||
handles: vec![line_h],
|
||||
base_point: DVec3::ZERO,
|
||||
world_to_block: Transform::identity(),
|
||||
block_to_world: Transform::identity(),
|
||||
mode: BlockObjectMode::Convert,
|
||||
annotative: false,
|
||||
match_orientation: false,
|
||||
scale_uniformly: false,
|
||||
allow_exploding: false,
|
||||
unit: 0,
|
||||
description: "".to_string(),
|
||||
hyperlink_url: "".to_string(),
|
||||
hyperlink_desc: "".to_string(),
|
||||
redefine: false,
|
||||
};
|
||||
let insert_h = scene.create_block_with_options(options).unwrap();
|
||||
let insert = scene.document.get_entity(insert_h).unwrap();
|
||||
let pieces = crate::modules::draw::modify::explode::explode_entity(insert, &scene.document);
|
||||
assert!(pieces.is_empty(), "Unexplodable block must not explode");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_explodable_flag_allows_explode() {
|
||||
let (mut scene, line_h) = make_test_scene();
|
||||
let options = CreateBlockOptions {
|
||||
name: "BLK_CAN_EXPLODE".to_string(),
|
||||
handles: vec![line_h],
|
||||
base_point: DVec3::ZERO,
|
||||
world_to_block: Transform::identity(),
|
||||
block_to_world: Transform::identity(),
|
||||
mode: BlockObjectMode::Convert,
|
||||
annotative: false,
|
||||
match_orientation: false,
|
||||
scale_uniformly: false,
|
||||
allow_exploding: true,
|
||||
unit: 0,
|
||||
description: "".to_string(),
|
||||
hyperlink_url: "".to_string(),
|
||||
hyperlink_desc: "".to_string(),
|
||||
redefine: false,
|
||||
};
|
||||
let insert_h = scene.create_block_with_options(options).unwrap();
|
||||
let insert = scene.document.get_entity(insert_h).unwrap();
|
||||
let pieces = crate::modules::draw::modify::explode::explode_entity(insert, &scene.document);
|
||||
assert_eq!(
|
||||
pieces.len(),
|
||||
1,
|
||||
"Explodable block should explode into 1 line"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user