forked from 1b-agent/opencadstudio-upstream
feat(blocks): implement WBLOCK dialog and unify dialog buttons and radio builders
This commit is contained in:
1 parent
dddf60d79e
commit
1a6279e841
33 files changed
+1308
-45
No files matched your search
@@ -460,6 +460,41 @@ impl OpenCADStudio {
|
||||
self.active_modal = Some(crate::app::ModalKind::BlockDefinition);
|
||||
}
|
||||
|
||||
cmd if cmd.starts_with("WBLOCK_POINT_PICKED ") => {
|
||||
let parts: Vec<f64> = cmd["WBLOCK_POINT_PICKED ".len()..]
|
||||
.split_whitespace()
|
||||
.filter_map(|s| s.parse::<f64>().ok())
|
||||
.collect();
|
||||
if parts.len() == 3 {
|
||||
if let Some(state) = self.wblock.as_mut() {
|
||||
state.base_point_x = format!("{:.4}", parts[0]);
|
||||
state.base_point_y = format!("{:.4}", parts[1]);
|
||||
state.base_point_z = format!("{:.4}", parts[2]);
|
||||
}
|
||||
}
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.active_modal = Some(crate::app::ModalKind::WriteBlock);
|
||||
}
|
||||
|
||||
"WBLOCK_POINT_CANCELLED" => {
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.active_modal = Some(crate::app::ModalKind::WriteBlock);
|
||||
}
|
||||
|
||||
"WBLOCK_OBJECTS_GATHERED" => {
|
||||
let handles: Vec<_> = self.tabs[i]
|
||||
.scene
|
||||
.selected_entities()
|
||||
.into_iter()
|
||||
.map(|(h, _)| h)
|
||||
.collect();
|
||||
if let Some(state) = self.wblock.as_mut() {
|
||||
state.selected_handles = handles;
|
||||
}
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.active_modal = Some(crate::app::ModalKind::WriteBlock);
|
||||
}
|
||||
|
||||
"INSERT" => {
|
||||
let blocks = self.tabs[i].scene.custom_block_names();
|
||||
if blocks.is_empty() {
|
||||
@@ -663,9 +698,60 @@ impl OpenCADStudio {
|
||||
"PDFATTACH" => {
|
||||
return Some(Task::done(Message::PdfAttachPick));
|
||||
}
|
||||
cmd if cmd == "WBLOCK" || cmd == "WB" || cmd.starts_with("WBLOCK ") => {
|
||||
let arg = cmd.splitn(2, ' ').nth(1).unwrap_or("").trim();
|
||||
if arg.is_empty() {
|
||||
"XATTACH" => {
|
||||
// Launch the file picker; XAttachPickResult will start the command.
|
||||
return Some(Task::done(Message::XAttachPick));
|
||||
}
|
||||
"WBLOCK" | "WB" => {
|
||||
let handles: Vec<_> = self.tabs[i]
|
||||
.scene
|
||||
.selected_entities()
|
||||
.into_iter()
|
||||
.map(|(h, _)| h)
|
||||
.collect();
|
||||
let existing_blocks = self.tabs[i].scene.custom_block_names();
|
||||
let default_unit = self.tabs[i].scene.document.header.insertion_units;
|
||||
let default_folder = self.tabs[i]
|
||||
.current_path
|
||||
.as_ref()
|
||||
.and_then(|p| p.parent())
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.unwrap_or_default();
|
||||
let default_path = if default_folder.is_empty() {
|
||||
"new_block.dwg".to_string()
|
||||
} else {
|
||||
format!("{}/new_block.dwg", default_folder.replace('\\', "/"))
|
||||
};
|
||||
self.wblock = Some(
|
||||
crate::ui::window::wblock::WblockState::new(
|
||||
existing_blocks,
|
||||
handles,
|
||||
default_unit,
|
||||
default_path,
|
||||
),
|
||||
);
|
||||
self.active_modal = Some(crate::app::ModalKind::WriteBlock);
|
||||
}
|
||||
|
||||
cmd if cmd == "-WBLOCK"
|
||||
|| cmd == "-WB"
|
||||
|| cmd.starts_with("-WBLOCK ")
|
||||
|| cmd.starts_with("-WB ")
|
||||
|| cmd.starts_with("WBLOCK ")
|
||||
|| cmd.starts_with("WB ") =>
|
||||
{
|
||||
let rest = if let Some(r) = cmd.strip_prefix("-WBLOCK") {
|
||||
r.trim()
|
||||
} else if let Some(r) = cmd.strip_prefix("-WB") {
|
||||
r.trim()
|
||||
} else if let Some(r) = cmd.strip_prefix("WBLOCK") {
|
||||
r.trim()
|
||||
} else if let Some(r) = cmd.strip_prefix("WB") {
|
||||
r.trim()
|
||||
} else {
|
||||
""
|
||||
};
|
||||
if rest.is_empty() {
|
||||
// No argument: use selected entities (*) if any, else ask.
|
||||
let sel: Vec<_> = self.tabs[i].scene.selected.iter().copied().collect();
|
||||
if sel.is_empty() {
|
||||
@@ -676,7 +762,7 @@ impl OpenCADStudio {
|
||||
return Some(Task::done(Message::WblockSave("*".to_string())));
|
||||
}
|
||||
} else {
|
||||
return Some(Task::done(Message::WblockSave(arg.to_string())));
|
||||
return Some(Task::done(Message::WblockSave(rest.to_string())));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -495,6 +495,8 @@ pub(super) struct OpenCADStudio {
|
||||
block_definition: Option<crate::ui::window::block_definition::BlockDefinitionState>,
|
||||
/// Working copy of the Attach External Reference dialog; None while closed.
|
||||
xref_attach: Option<crate::ui::window::xref_attach::XrefAttachState>,
|
||||
/// Working copy of the Write Block (WBLOCK) dialog; `None` while it is closed.
|
||||
wblock: Option<crate::ui::window::wblock::WblockState>,
|
||||
/// Working copy of the structured feature-control-frame editor.
|
||||
geometric_tolerance: Option<crate::ui::window::geometric_tolerance::State>,
|
||||
/// PICKDRAG (#226): false (default) = press-drag lassoes; true =
|
||||
@@ -1883,6 +1885,7 @@ pub enum ModalKind {
|
||||
DrawingUnits,
|
||||
BlockDefinition,
|
||||
XrefAttach,
|
||||
WriteBlock,
|
||||
GeometricTolerance,
|
||||
DraftingSettings,
|
||||
AutoConstrainSettings,
|
||||
@@ -2880,6 +2883,24 @@ pub enum Message {
|
||||
BlockDefConfirmRedefine(bool),
|
||||
BlockDefDismissError,
|
||||
BlockDefHelp,
|
||||
/// Write Block (WBLOCK) dialog messages
|
||||
WblockSourceMode(crate::ui::window::wblock::WblockSourceMode),
|
||||
WblockBlockName(String),
|
||||
WblockBlockSelect(String),
|
||||
WblockPickPoint,
|
||||
WblockBaseX(String),
|
||||
WblockBaseY(String),
|
||||
WblockBaseZ(String),
|
||||
WblockSelectObjects,
|
||||
WblockQuickSelect,
|
||||
WblockObjectMode(crate::ui::window::wblock::WblockObjectMode),
|
||||
WblockFilePath(String),
|
||||
WblockBrowsePath,
|
||||
WblockBrowsePathResult(Option<std::path::PathBuf>),
|
||||
WblockUnit(i16),
|
||||
WblockApply,
|
||||
WblockDismissError,
|
||||
WblockHelp,
|
||||
/// One structured feature-control-frame field changed.
|
||||
ToleranceDialogField(crate::ui::window::geometric_tolerance::Field),
|
||||
/// One structured feature-control-frame option changed.
|
||||
@@ -3962,6 +3983,7 @@ impl OpenCADStudio {
|
||||
drawing_units: None,
|
||||
block_definition: None,
|
||||
xref_attach: None,
|
||||
wblock: None,
|
||||
geometric_tolerance: None,
|
||||
pick_drag_rect: false,
|
||||
perf_hud: false,
|
||||
|
||||
@@ -1163,6 +1163,8 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
self.reset_modal_geometry();
|
||||
if self.block_definition.is_some() {
|
||||
self.active_modal = Some(crate::app::ModalKind::BlockDefinition);
|
||||
} else if self.wblock.is_some() {
|
||||
self.active_modal = Some(crate::app::ModalKind::WriteBlock);
|
||||
}
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
@@ -209,6 +209,7 @@ impl OpenCADStudio {
|
||||
Some(GeometricTolerance) => self.geometric_tolerance = None,
|
||||
Some(BlockDefinition) => self.block_definition = None,
|
||||
Some(XrefAttach) => self.xref_attach = None,
|
||||
Some(WriteBlock) => self.wblock = None,
|
||||
Some(Hyperlink) => {
|
||||
self.hyperlink_editor_handles.clear();
|
||||
self.hyperlink_editor_url.clear();
|
||||
@@ -346,6 +347,14 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.active_modal == Some(super::ModalKind::WriteBlock) {
|
||||
if let Some(ref mut state) = self.wblock {
|
||||
if state.error_message.is_some() {
|
||||
state.error_message = None;
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
}
|
||||
return self.update(Message::CloseModal);
|
||||
}
|
||||
if self.active_modal == Some(super::ModalKind::BlockDefinition) {
|
||||
@@ -362,6 +371,13 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.active_modal == Some(super::ModalKind::WriteBlock) {
|
||||
if matches!(msg, Message::CommandFinalize)
|
||||
|| matches!(&msg, Message::ShortcutPressed(key) if key.rsplit('+').next() == Some("ENTER") || key.rsplit('+').next() == Some("RETURN"))
|
||||
{
|
||||
return self.update(Message::WblockApply);
|
||||
}
|
||||
}
|
||||
if is_modal_blocked_key_msg(&msg) {
|
||||
return Task::none();
|
||||
}
|
||||
@@ -4950,6 +4966,294 @@ impl OpenCADStudio {
|
||||
}
|
||||
self.commit_block_definition(false)
|
||||
}
|
||||
Message::WblockSourceMode(mode) => {
|
||||
if let Some(state) = self.wblock.as_mut() {
|
||||
state.source_mode = mode;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::WblockBlockName(name) => {
|
||||
if let Some(state) = self.wblock.as_mut() {
|
||||
state.block_name = name;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::WblockBlockSelect(name) => {
|
||||
if let Some(state) = self.wblock.as_mut() {
|
||||
state.block_name = name.clone();
|
||||
let trimmed = name.trim();
|
||||
if !trimmed.is_empty() {
|
||||
let current_path = std::path::Path::new(&state.file_path);
|
||||
let file_stem = current_path.file_stem().and_then(|s| s.to_str());
|
||||
let is_default_or_block = file_stem == Some("new_block")
|
||||
|| state.existing_blocks.iter().any(|b| Some(b.as_str()) == file_stem);
|
||||
if is_default_or_block {
|
||||
let new_file_name = format!("{}.dwg", trimmed);
|
||||
if let Some(parent) = current_path.parent() {
|
||||
if !parent.as_os_str().is_empty() {
|
||||
state.file_path = parent.join(new_file_name).to_string_lossy().to_string();
|
||||
} else {
|
||||
state.file_path = new_file_name;
|
||||
}
|
||||
} else {
|
||||
state.file_path = new_file_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::WblockPickPoint => {
|
||||
self.active_modal = None;
|
||||
let cmd = crate::modules::insert::wblock::WblockPickBasePointCommand;
|
||||
self.command_line.push_info(&cmd.prompt());
|
||||
self.tabs[self.active_tab].active_cmd = Some(Box::new(cmd));
|
||||
Task::none()
|
||||
}
|
||||
Message::WblockBaseX(val) => {
|
||||
if let Some(state) = self.wblock.as_mut() {
|
||||
state.base_point_x = val;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::WblockBaseY(val) => {
|
||||
if let Some(state) = self.wblock.as_mut() {
|
||||
state.base_point_y = val;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::WblockBaseZ(val) => {
|
||||
if let Some(state) = self.wblock.as_mut() {
|
||||
state.base_point_z = val;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::WblockSelectObjects => {
|
||||
self.active_modal = None;
|
||||
use crate::modules::draw::select::SelectObjectsCommand;
|
||||
let cmd = SelectObjectsCommand::plain("WBLOCK", "WBLOCK_OBJECTS_GATHERED");
|
||||
self.command_line.push_info(&cmd.prompt());
|
||||
self.tabs[self.active_tab].active_cmd = Some(Box::new(cmd));
|
||||
Task::none()
|
||||
}
|
||||
Message::WblockQuickSelect => {
|
||||
self.active_modal = None;
|
||||
self.on_qselect_open()
|
||||
}
|
||||
Message::WblockObjectMode(mode) => {
|
||||
if let Some(state) = self.wblock.as_mut() {
|
||||
state.object_mode = mode;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::WblockFilePath(path) => {
|
||||
if let Some(state) = self.wblock.as_mut() {
|
||||
state.file_path = path;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::WblockBrowsePath => {
|
||||
let default_name = if let Some(state) = self.wblock.as_ref() {
|
||||
let p = std::path::Path::new(&state.file_path);
|
||||
p.file_name()
|
||||
.and_then(|f| f.to_str())
|
||||
.unwrap_or("new_block.dwg")
|
||||
.to_string()
|
||||
} else {
|
||||
"new_block.dwg".to_string()
|
||||
};
|
||||
Task::perform(
|
||||
async move {
|
||||
let path = crate::sys::file_dialog()
|
||||
.set_title(crate::t!("Save Block As").as_ref())
|
||||
.set_file_name(&default_name)
|
||||
.add_filter(crate::t!("DWG Files").as_ref(), &["dwg"])
|
||||
.add_filter(crate::t!("DXF Files").as_ref(), &["dxf"])
|
||||
.save_file()
|
||||
.await
|
||||
.map(|h| crate::sys::handle_path(&h));
|
||||
path
|
||||
},
|
||||
|path| Message::WblockBrowsePathResult(path),
|
||||
)
|
||||
}
|
||||
Message::WblockBrowsePathResult(opt_path) => {
|
||||
if let Some(path) = opt_path {
|
||||
if let Some(state) = self.wblock.as_mut() {
|
||||
state.file_path = path.to_string_lossy().to_string();
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::WblockUnit(unit) => {
|
||||
if let Some(state) = self.wblock.as_mut() {
|
||||
state.unit = unit;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::WblockDismissError => {
|
||||
if let Some(state) = self.wblock.as_mut() {
|
||||
state.error_message = None;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::WblockHelp => {
|
||||
self.command_line.push_info(
|
||||
crate::t!("WBLOCK writes objects, a block, or the entire drawing to a new drawing file.").as_ref(),
|
||||
);
|
||||
Task::none()
|
||||
}
|
||||
Message::WblockApply => {
|
||||
let Some(state) = self.wblock.as_mut() else {
|
||||
return Task::none();
|
||||
};
|
||||
use crate::ui::window::wblock::{WblockObjectMode, WblockSourceMode};
|
||||
match state.source_mode {
|
||||
WblockSourceMode::Block => {
|
||||
let name = state.block_name.trim();
|
||||
if name.is_empty() {
|
||||
state.error_message =
|
||||
Some(crate::t!("Please select or enter a block name.").into_owned());
|
||||
return Task::none();
|
||||
}
|
||||
let i = self.active_tab;
|
||||
if self.tabs[i].scene.document.block_records.get(name).is_none() {
|
||||
state.error_message = Some(
|
||||
crate::tf!("Block \"{}\" does not exist in drawing.", name)
|
||||
.into_owned(),
|
||||
);
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
WblockSourceMode::Objects => {
|
||||
if state.selected_handles.is_empty() {
|
||||
state.error_message = Some(
|
||||
crate::t!("No objects selected. You must select objects to define a block.")
|
||||
.into_owned(),
|
||||
);
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
WblockSourceMode::EntireDrawing => {}
|
||||
}
|
||||
|
||||
let path_str = state.file_path.trim().to_string();
|
||||
if path_str.is_empty() {
|
||||
state.error_message =
|
||||
Some(crate::t!("Please specify a file name and path.").into_owned());
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
let mut path = std::path::PathBuf::from(path_str);
|
||||
if path.extension().is_none() {
|
||||
path.set_extension("dwg");
|
||||
}
|
||||
|
||||
let i = self.active_tab;
|
||||
if let Some(current_path) = self.tabs[i].current_path.as_ref() {
|
||||
if current_path == &path {
|
||||
state.error_message = Some(
|
||||
crate::t!("Cannot write to the current drawing file.").into_owned(),
|
||||
);
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
|
||||
let state = self.wblock.take().unwrap();
|
||||
self.active_modal = None;
|
||||
let document = self.tabs[i].scene.document_for_save();
|
||||
let source_mode = state.source_mode;
|
||||
let block_name = state.block_name.trim().to_string();
|
||||
let handles = state.selected_handles.clone();
|
||||
let base_point = state.parse_base_point();
|
||||
let unit = state.unit;
|
||||
let object_mode = state.object_mode;
|
||||
|
||||
if source_mode == WblockSourceMode::Objects {
|
||||
match object_mode {
|
||||
WblockObjectMode::Retain => {}
|
||||
WblockObjectMode::Delete => {
|
||||
self.push_undo_snapshot(i, "WBLOCK");
|
||||
self.tabs[i].scene.erase_entities(&handles);
|
||||
self.tabs[i].dirty = true;
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.refresh_properties();
|
||||
}
|
||||
WblockObjectMode::Convert => {
|
||||
let block_name_for_conv = path
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("WBLOCK")
|
||||
.to_string();
|
||||
self.push_undo_snapshot(i, "WBLOCK");
|
||||
let ucs = self.tabs[i].ucs_xform();
|
||||
let world_to_block = ucs.to_ucs_transform_at(base_point);
|
||||
let block_to_world = ucs.to_wcs_transform_at(base_point);
|
||||
let options = crate::scene::CreateBlockOptions {
|
||||
name: block_name_for_conv,
|
||||
handles: handles.clone(),
|
||||
base_point,
|
||||
world_to_block,
|
||||
block_to_world,
|
||||
mode: crate::ui::window::block_definition::BlockObjectMode::Convert,
|
||||
annotative: false,
|
||||
match_orientation: false,
|
||||
scale_uniformly: false,
|
||||
allow_exploding: true,
|
||||
unit,
|
||||
description: String::new(),
|
||||
hyperlink_url: String::new(),
|
||||
hyperlink_desc: String::new(),
|
||||
redefine: true,
|
||||
};
|
||||
let _ = self.tabs[i].scene.create_block_with_options(options);
|
||||
self.tabs[i].dirty = true;
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.refresh_properties();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let worker_path = path.clone();
|
||||
let display_name = match source_mode {
|
||||
WblockSourceMode::Block => block_name.clone(),
|
||||
WblockSourceMode::EntireDrawing => "*".to_string(),
|
||||
WblockSourceMode::Objects => "*".to_string(),
|
||||
};
|
||||
let worker_display = display_name.clone();
|
||||
|
||||
file::background_task(
|
||||
move || {
|
||||
let out_doc = match source_mode {
|
||||
WblockSourceMode::Block => {
|
||||
let mut d = crate::modules::insert::wblock::extract_block_to_doc(
|
||||
&document,
|
||||
&block_name,
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
d.header.insertion_units = unit;
|
||||
d
|
||||
}
|
||||
WblockSourceMode::EntireDrawing => {
|
||||
let mut d = document.clone();
|
||||
d.header.insertion_units = unit;
|
||||
d
|
||||
}
|
||||
WblockSourceMode::Objects => {
|
||||
crate::modules::insert::wblock::extract_entities_to_doc_with_base(
|
||||
&document,
|
||||
&handles,
|
||||
base_point,
|
||||
unit,
|
||||
)
|
||||
.map_err(|e| e.to_string())?
|
||||
}
|
||||
};
|
||||
crate::io::save(&out_doc, &worker_path).map_err(|e| e.to_string())
|
||||
},
|
||||
move |result| Message::WblockWriteFinished(worker_display, path, result),
|
||||
)
|
||||
}
|
||||
Message::ToleranceDialogField(field) => {
|
||||
if let Some(state) = self.geometric_tolerance.as_mut() {
|
||||
state.apply_field(field);
|
||||
@@ -5904,6 +6208,8 @@ impl OpenCADStudio {
|
||||
self.reset_modal_geometry();
|
||||
if self.block_definition.is_some() {
|
||||
self.active_modal = Some(super::ModalKind::BlockDefinition);
|
||||
} else if self.wblock.is_some() {
|
||||
self.active_modal = Some(super::ModalKind::WriteBlock);
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
@@ -6110,6 +6416,15 @@ impl OpenCADStudio {
|
||||
.collect();
|
||||
block_def.error_message = None;
|
||||
self.active_modal = Some(super::ModalKind::BlockDefinition);
|
||||
} else if let Some(ref mut wblock) = self.wblock {
|
||||
wblock.selected_handles = self.tabs[i]
|
||||
.scene
|
||||
.selected_entities()
|
||||
.into_iter()
|
||||
.map(|(h, _)| h)
|
||||
.collect();
|
||||
wblock.error_message = None;
|
||||
self.active_modal = Some(super::ModalKind::WriteBlock);
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ impl OpenCADStudio {
|
||||
Some(K::DrawingUnits) => crate::t!("Drawing Units").into_owned(),
|
||||
Some(K::BlockDefinition) => crate::t!("Block Definition").into_owned(),
|
||||
Some(K::XrefAttach) => crate::t!("Attach External Reference").into_owned(),
|
||||
Some(K::WriteBlock) => crate::t!("Write Block").into_owned(),
|
||||
Some(K::GeometricTolerance) => crate::t!("Geometric Tolerance").into_owned(),
|
||||
Some(K::DraftingSettings) => crate::t!("Drafting Settings").into_owned(),
|
||||
Some(K::AutoConstrainSettings) => crate::t!("Constraint Settings").into_owned(),
|
||||
@@ -514,6 +515,12 @@ impl OpenCADStudio {
|
||||
crate::ui::window::xref_attach::view_window(state, flow)
|
||||
})
|
||||
}
|
||||
super::super::ModalKind::WriteBlock => {
|
||||
let state = self.wblock.as_ref()?;
|
||||
sized_flow(ex, 440, 395, |flow| {
|
||||
crate::ui::window::wblock::view_window(state, flow)
|
||||
})
|
||||
}
|
||||
super::super::ModalKind::GeometricTolerance => {
|
||||
let state = self.geometric_tolerance.as_ref()?;
|
||||
sized_flow(ex, 670, 530, |flow| {
|
||||
|
||||
Reference in new issue
Block a user