Blocks palette: tabs, options, drag insertion and -INSERT

Rebuild the Blocks palette with Current Drawing / Recent / Favorites /
Libraries tabs, wildcard filter, six views, insertion options, click and
drop insertion and a block context menu. INSERT opens the palette, the
new -INSERT runs on the command line, the ribbon Insert button becomes a
block gallery, and BLOCKMRULIST, BLOCKREDEFINEMODE, BLOCKNAVIGATE,
BLOCKSTATE, EXPLMODE and INSNAME are added.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ramox81andClaude Opus 5.5 committed 2026-10-01 11:17:38 +03:00
1 parent 3dcebf0dc6
commit 0826101c5e
49 files changed
+3401 -573

No files matched your search

+63 -26
View File
@@ -489,25 +489,50 @@ impl OpenCADStudio {
self.active_modal = Some(crate::app::ModalKind::WriteBlock);
}
// INSERT opens the Blocks palette on the drawing's blocks;
// -INSERT runs on the command line.
"INSERT" => {
self.command_line.push_output("*Insert a block from the Blocks palette");
self.open_blocks_palette(Some(crate::ui::window::block_palette::Tab::Current));
}
"-INSERT" => {
use crate::command::CadCommand;
use crate::modules::insert::insert_block::InsertBlockCommand;
let blocks = self.tabs[i].scene.custom_block_names();
if blocks.is_empty() {
self.command_line.push_error(
crate::t!("No user-defined blocks found in this drawing.").as_ref(),
);
} else {
use crate::modules::insert::insert_block::InsertBlockCommand;
let ranked = self.ranked_block_names(&blocks);
let snapshot = self.block_usage_snapshot();
let cmd = InsertBlockCommand::new_with_usage(
ranked,
snapshot,
self.cliprompt_lines.clamp(0, 50) as u8,
);
self.command_line.push_info(&cmd.prompt());
let opts = cmd.options();
self.command_line.set_step_options(opts.clone());
self.tabs[i].active_cmd = Some(Box::new(cmd));
let ranked = self.ranked_block_names(&blocks);
let snapshot = self.block_usage_snapshot();
let cmd = InsertBlockCommand::classic(
ranked,
snapshot,
self.cliprompt_lines.clamp(0, 50) as u8,
self.block_catalog(),
self.insname.clone(),
);
self.command_line.push_info(&cmd.prompt());
self.command_line.set_step_options(cmd.options());
self.tabs[i].active_cmd = Some(Box::new(cmd));
}
// A drawing named at -INSERT's block prompt: brought in as a
// block, then placed.
cmd if cmd.starts_with("_-INSERTFILE ") => {
use crate::command::CadCommand;
use crate::modules::insert::insert_block::InsertBlockCommand;
let path = std::path::PathBuf::from(cmd["_-INSERTFILE ".len()..].trim().trim_matches('"'));
match self.import_file_as_block(path) {
Ok(name) => {
let units = self
.block_catalog()
.units
.remove(&name.to_ascii_uppercase())
.unwrap_or_default();
let preview = self.tabs[i].scene.block_preview_wires(&name);
let cmd = InsertBlockCommand::classic_for_block(name, preview, units);
self.command_line.push_info(&cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(cmd));
}
Err(e) => self.command_line.push_error(e.as_str()),
}
}
@@ -667,15 +692,27 @@ impl OpenCADStudio {
);
}
// BLOCKPALETTE / BLOCKSPALETTE — toggle the docked Insert Block panel.
"BLOCKPALETTE" | "BLOCKSPALETTE" => {
self.show_block_palette ^= true;
if self.show_block_palette {
// Always open expanded so the panel is immediately usable;
// the user can still collapse it via the pin (Auto) button.
self.dock_expanded = Some(crate::ui::dock::PanelId::BlockPalette);
self.refresh_block_palette();
}
// BLOCKSPALETTE opens the Blocks palette; BLOCKSPALETTECLOSE
// closes it. `_BLOCKSPALETTE <tab>` opens it on a tab.
"BLOCKPALETTE" | "BLOCKSPALETTE" => self.open_blocks_palette(None),
"BLOCKSPALETTECLOSE" => {
self.show_block_palette = false;
self.block_palette.placing = None;
}
// A block picked in the ribbon gallery.
cmd if cmd.starts_with("_BLOCKINSERT ") => {
let name = cmd["_BLOCKINSERT ".len()..].trim().to_string();
return Some(self.palette_insert(crate::ui::window::block_palette::Item::Current(name), None));
}
cmd if cmd.starts_with("_BLOCKSPALETTE ") => {
use crate::ui::window::block_palette::Tab;
let tab = match cmd["_BLOCKSPALETTE ".len()..].trim() {
"RECENT" => Tab::Recent,
"FAVORITES" => Tab::Favorites,
"LIBRARIES" => Tab::Libraries,
_ => Tab::Current,
};
self.open_blocks_palette(Some(tab));
}
// ATTMAN / BATTMAN — the Block Attribute Manager. Rather than a
+125
View File
@@ -0,0 +1,125 @@
//! Block system variables. `BLOCKMRULIST`, `BLOCKREDEFINEMODE` and
//! `BLOCKNAVIGATE` are application preferences kept with the settings;
//! `EXPLMODE` lives in the drawing; `INSNAME` is the session's default block
//! name; `BLOCKSTATE` is read-only. Each answers to its bare name (prompting
//! for the value) and to `SETVAR NAME value`, with the reference's messages.
use crate::app::{Message, OpenCADStudio};
use iced::Task;
/// The variables this family answers for, for the command registry and the
/// `SETVAR ?` listing.
pub(super) const BLOCK_SYSVARS: &[&str] = &[
"BLOCKMRULIST",
"BLOCKREDEFINEMODE",
"BLOCKNAVIGATE",
"BLOCKSTATE",
"EXPLMODE",
"INSNAME",
];
/// The `SETVAR ?` line for this family.
pub(super) fn setvar_listing() -> String {
format!("SETVAR (block): {}", BLOCK_SYSVARS.join(" "))
}
/// EXPLMODE of the drawing (default 1).
pub(crate) fn explmode(document: &codec::CadDocument) -> i16 {
crate::io::drawing_variable(document, "EXPLMODE")
.and_then(|v| v.trim().parse().ok())
.unwrap_or(1)
}
impl OpenCADStudio {
pub(super) fn dispatch_blockvars(&mut self, cmd: &str, i: usize) -> Option<Task<Message>> {
let rest = cmd.strip_prefix("SETVAR ").map(str::trim).unwrap_or(cmd);
let mut parts = rest.splitn(2, char::is_whitespace);
let name = parts.next().unwrap_or("").to_ascii_uppercase();
if !BLOCK_SYSVARS.contains(&name.as_str()) {
return None;
}
let value = parts.next().map(str::trim).filter(|v| !v.is_empty());
match name.as_str() {
"BLOCKSTATE" => {
let state = self.tabs[i].refedit_session.is_some() as u8;
self.command_line.push_output(&format!("BLOCKSTATE = {state} (read only)"));
}
"INSNAME" | "BLOCKNAVIGATE" => {
let current = if name == "INSNAME" {
self.insname.clone()
} else {
self.block_navigate.clone()
};
match value {
Some(v) => {
let v = if v == "." { String::new() } else { v.trim_matches('"').to_string() };
if name == "INSNAME" {
self.insname = v;
} else {
self.block_navigate = if v.is_empty() { ".".to_string() } else { v };
self.persist_settings_if_changed();
}
}
None => {
self.command_line.push_output(&format!(
"Enter new value for {name}, or . for none <\"{current}\">:"
));
self.pending_setvar = Some(name);
}
}
}
_ => {
let (current, max) = match name.as_str() {
"BLOCKMRULIST" => (self.block_mru_list as i32, 100),
"BLOCKREDEFINEMODE" => (self.block_redefine_mode as i32, 2),
_ => (explmode(&self.tabs[i].scene.document) as i32, 1),
};
let ask = |app: &mut Self, name: String| {
app.command_line
.push_output(&format!("Enter new value for {name} <{current}>:"));
app.pending_setvar = Some(name);
};
match value {
None => ask(self, name),
Some(v) => match v.parse::<i32>() {
Err(_) => {
self.command_line.push_error("Requires an integer value.");
ask(self, name);
}
Ok(n) if !(0..=max).contains(&n) => {
self.command_line.push_error(&if max == 1 {
"Requires 0 or 1 only.".to_string()
} else {
format!("Requires an integer between 0 and {max}.")
});
ask(self, name);
}
Ok(n) => match name.as_str() {
"BLOCKMRULIST" => {
self.block_mru_list = n as u8;
self.trim_recent_blocks();
self.persist_settings_if_changed();
}
"BLOCKREDEFINEMODE" => {
self.block_redefine_mode = n as u8;
self.persist_settings_if_changed();
}
_ => {
if n != current {
self.push_undo_snapshot(i, "EXPLMODE");
crate::io::set_drawing_variable(
&mut self.tabs[i].scene.document,
"EXPLMODE",
&n.to_string(),
);
self.tabs[i].dirty = true;
}
}
},
},
}
}
}
Some(Task::none())
}
}
+21
View File
@@ -1217,6 +1217,27 @@ impl OpenCADStudio {
self.command_line.push_info(&cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(cmd));
} else {
// EXPLMODE 0 keeps references that are not uniformly and
// positively scaled whole.
let selected: Vec<_> = if super::blockvars::explmode(&self.tabs[i].scene.document) == 0 {
selected
.into_iter()
.filter(|(_, entity)| match entity {
codec::EntityType::Insert(ins) => {
let (x, y, z) = (ins.x_scale(), ins.y_scale(), ins.z_scale());
let uniform = x > 0.0 && (x - y).abs() <= 1e-10 && (x - z).abs() <= 1e-10;
if !uniform {
self.command_line
.push_output("The object is not uniformly and positively scaled.");
}
uniform
}
_ => true,
})
.collect()
} else {
selected
};
let replacements = plan_explode(&selected, &self.tabs[i].scene.document);
let exploded = replacements.len();
if exploded > 0 {
+12
View File
@@ -20,6 +20,7 @@ mod pc_extract;
mod pdf_underlay;
mod pdf_dialogs;
mod xclip;
mod blockvars;
mod plotvars;
mod styleprops;
mod view;
@@ -296,6 +297,9 @@ impl OpenCADStudio {
if let Some(t) = self.dispatch_plotvars(cmd, i) {
return Some(t);
}
if let Some(t) = self.dispatch_blockvars(cmd, i) {
return Some(t);
}
if let Some(t) = self.dispatch_styleprops(cmd, i) {
return Some(t);
}
@@ -473,6 +477,14 @@ inventory::submit!(crate::command::CommandRegistration {
// Block list + block-attribute list (command-line forms).
"BLOCKPALETTE",
"BLOCKSPALETTE",
"BLOCKSPALETTECLOSE",
"-INSERT",
"BLOCKMRULIST",
"BLOCKREDEFINEMODE",
"BLOCKNAVIGATE",
"BLOCKSTATE",
"EXPLMODE",
"INSNAME",
"ATTMAN",
"BATTMAN",
// Drawing-content overview.
+1
View File
@@ -1107,6 +1107,7 @@ impl OpenCADStudio {
let value = it.next().map(|s| s.trim().to_string());
if name.is_empty() || name == "?" {
self.command_line.push_info(&super::plotvars::setvar_listing());
self.command_line.push_info(&super::blockvars::setvar_listing());
self.command_line.push_info(
crate::t!("SETVAR: CETRANSPARENCY LTSCALE CELTSCALE PDMODE PDSIZE TEXTSIZE ORTHOMODE FILLMODE MIRRTEXT FRAME IMAGEFRAME PDFFRAME WIPEOUTFRAME XCLIPFRAME POINTCLOUDCLIPFRAME ZOOMWHEEL ZOOMFACTOR SHORTCUTMENU SHORTCUTMENUDURATION CURSORSIZE PICKBOX CURSORTYPE SNAPANG TEXTFILL CLIPROMPTLINES COMMANDLINEFADETIME ATTREQ ATTDIA DIMASSOC DIMCONTINUEMODE CONSTRAINTSOLVEMODE CONSTRAINTINFER CONSTRAINTBARDISPLAY CONSTRAINTBARMODE CONSTRAINTNAMEFORMAT DYNCONSTRAINTDISPLAY ANGBASE ANGDIR SKETCHINC SKPOLY SKTOLERANCE DONUTID DONUTOD CENTEREXE CENTERLAYER CENTERLTYPE CENTERLTSCALE CENTERLTYPEFILE CENTERCROSSSIZE CENTERCROSSGAP CENTERMARKEXE COLORTHEME SELECTIONAREA SELECTIONAREAOPACITY SELECTIONEFFECT SELECTIONEFFECTCOLOR WINDOWSAREACOLOR CROSSINGAREACOLOR SELECTIONPREVIEW GRIPSIZE GRIPCOLOR GRIPHOT GRIPHOVER GRIPOBJLIMIT | CLAYER CELTYPE TEXTSTYLE (read-only)").as_ref(),
);