Merge pull request #1793 from ramox81/codex/block-definition-command-line

Block Definition: command-line forms, block record data and dialog defaults

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-15 00:14:21 +03:00
commit 838e397a6c
23 files changed
+1860 -435

No files matched your search

+39 -10
View File
@@ -1,27 +1,56 @@
use crate::t;
use crate::command::{CadCommand, CmdResult};
use glam::DVec3;
// ── Command implementation ────────────────────────────────────────────────
/// Picks one point and delegates to the inline `BASE <x> <y> <z>` handler,
/// which writes the active space's insertion base point onto the document.
pub struct BaseCommand;
/// BASE (`Enter base point <x,y,z>:`) and INSBASE
/// (`Enter new value for INSBASE <x,y,z>:`): a point sets the active space's
/// insertion base through the inline `BASE <x> <y> <z>` handler; Enter keeps
/// the current one.
pub struct BaseCommand {
current: DVec3,
variable: bool,
}
impl BaseCommand {
pub fn new() -> Self {
Self
pub fn new(current: DVec3) -> Self {
Self {
current,
variable: false,
}
}
/// The INSBASE system variable's prompt.
pub fn insbase(current: DVec3) -> Self {
Self {
current,
variable: true,
}
}
}
fn point_text(p: DVec3) -> String {
format!("{:.4},{:.4},{:.4}", p.x, p.y, p.z)
}
impl CadCommand for BaseCommand {
fn name(&self) -> &'static str {
"BASE"
if self.variable {
"INSBASE"
} else {
"BASE"
}
}
fn prompt(&self) -> String {
t!("BASE Specify base point:").into_owned()
if self.variable {
format!(
"Enter new value for INSBASE <{}>:",
point_text(self.current)
)
} else {
format!("Enter base point <{}>:", point_text(self.current))
}
}
fn on_point(&mut self, pt: DVec3) -> CmdResult {
@@ -29,7 +58,7 @@ impl CadCommand for BaseCommand {
}
fn on_enter(&mut self) -> CmdResult {
CmdResult::Cancel
CmdResult::Measurement(String::new())
}
}
File diff suppressed because it is too large. Load diff
+5 -84
View File
@@ -1,10 +1,8 @@
use crate::t;
use codec::Handle;
use glam::DVec3;
use crate::command::{CadCommand, CmdResult};
use crate::modules::{IconKind, ModuleEvent, ToolDef};
use crate::scene::model::wire_model::WireModel;
pub fn tool() -> ToolDef {
ToolDef {
@@ -15,84 +13,6 @@ pub fn tool() -> ToolDef {
}
}
enum Step {
Name,
Base { name: String },
}
pub struct CreateBlockCommand {
handles: Vec<Handle>,
step: Step,
}
impl CreateBlockCommand {
pub fn new(handles: Vec<Handle>) -> Self {
Self {
handles,
step: Step::Name,
}
}
}
impl CadCommand for CreateBlockCommand {
fn name(&self) -> &'static str {
"BLOCK"
}
fn prompt(&self) -> String {
match &self.step {
Step::Name => t!(
"BLOCK Enter block name [%{count} objects selected]:",
count = self.handles.len()
)
.into_owned(),
Step::Base { name } => t!(
"BLOCK Specify base point for \"%{name}\" [%{count} objects]:",
name = name,
count = self.handles.len()
)
.into_owned(),
}
}
fn on_point(&mut self, pt: DVec3) -> CmdResult {
match &self.step {
Step::Name => CmdResult::NeedPoint,
Step::Base { name } => CmdResult::CreateBlock {
handles: self.handles.clone(),
name: name.clone(),
base: pt,
},
}
}
fn on_enter(&mut self) -> CmdResult {
CmdResult::Cancel
}
fn wants_text_input(&self) -> bool {
matches!(self.step, Step::Name)
}
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
if !matches!(self.step, Step::Name) {
return None;
}
let name = text.trim();
if name.is_empty() {
return None;
}
self.step = Step::Base {
name: name.to_string(),
};
Some(CmdResult::NeedPoint)
}
fn on_preview_wires(&mut self, _pt: DVec3) -> Vec<WireModel> {
vec![]
}
}
/// One-shot point picker for the Block Definition dialog's "Pick point" button.
pub struct BlockPickBasePointCommand;
@@ -102,7 +22,7 @@ impl CadCommand for BlockPickBasePointCommand {
}
fn prompt(&self) -> String {
crate::t!("BLOCK Specify insertion base point:").into_owned()
"Specify insertion base point:".to_string()
}
fn on_point(&mut self, pt: DVec3) -> CmdResult {
@@ -150,9 +70,9 @@ impl CadCommand for BlockOnScreenCommand {
fn prompt(&self) -> String {
if self.need_base {
crate::t!("BLOCK Specify insertion base point:").into_owned()
"Specify insertion base point:".to_string()
} else {
crate::t!("BLOCK Select objects:").into_owned()
"Select objects:".to_string()
}
}
@@ -450,7 +370,8 @@ mod tests {
hyperlink_desc: "".to_string(),
redefine: false,
};
assert!(scene.create_block_with_options(opt_no_handles).is_err());
// No objects define an empty block.
assert!(scene.create_block_with_options(opt_no_handles).is_ok());
}
#[test]
+1
View File
@@ -4,6 +4,7 @@ mod attdef;
pub(crate) mod attedit;
mod attman;
pub mod base_point;
pub(crate) mod block_cmdline;
mod content_browser;
pub(crate) mod create_block;
mod design_center;
+13 -6
View File
@@ -186,8 +186,11 @@ pub fn extract_entities_to_doc_with_base(
if handles.is_empty() {
return Err(t!("No entities selected for WBLOCK.").into_owned());
}
let shift = crate::command::EntityTransform::Translate(-base_point);
copy_with_dependencies(src, handles, &mut out, Some(&shift));
// The objects keep their coordinates; the base point becomes the new
// drawing's insertion base (INSBASE).
copy_with_dependencies(src, handles, &mut out, None);
out.header.model_space_insertion_base =
codec::types::Vector3::new(base_point.x, base_point.y, base_point.z);
if out.entities().count() == 0 {
return Err(t!("None of the selected entities could be exported.").into_owned());
}
@@ -218,12 +221,16 @@ mod tests {
assert_eq!(out.header.insertion_units, 4);
assert_eq!(out.entities().count(), 1);
assert_eq!(
out.header.model_space_insertion_base,
Vector3::new(10.0, 20.0, 0.0)
);
let e = out.entities().next().unwrap();
if let EntityType::Line(l) = e {
assert!((l.start.x - 0.0).abs() < 1e-6);
assert!((l.start.y - 0.0).abs() < 1e-6);
assert!((l.end.x - 20.0).abs() < 1e-6);
assert!((l.end.y - 20.0).abs() < 1e-6);
assert!((l.start.x - 10.0).abs() < 1e-6);
assert!((l.start.y - 20.0).abs() < 1e-6);
assert!((l.end.x - 30.0).abs() < 1e-6);
assert!((l.end.y - 40.0).abs() < 1e-6);
} else {
panic!("Expected Line entity");
}