forked from 1b-agent/opencadstudio-upstream
Merge PR #1178: Apply SETBYLAYER choices and property masks
# Conflicts: # src/app/commands/styleprops.rs
This commit is contained in:
commit
bea874c97f
3 files changed
+222
-18
No files matched your search
@@ -645,46 +645,96 @@ impl OpenCADStudio {
|
||||
// ── SETBYLAYER — clear color/linetype/lineweight overrides ────
|
||||
// Resets the selected entities' direct property overrides back to
|
||||
// ByLayer so they follow their layer again.
|
||||
"SETBYLAYERMODE" => {
|
||||
let command = crate::modules::draw::modify::setbylayer::ModeCommand;
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
}
|
||||
value if value.starts_with("SETBYLAYERMODE ") => {
|
||||
if let Ok(mode) = value.trim_start_matches("SETBYLAYERMODE ").trim().parse::<u8>() {
|
||||
crate::modules::draw::modify::setbylayer::set_mode(mode);
|
||||
} else { self.command_line.push_error("SETBYLAYERMODE requires an integer from 0 to 255."); }
|
||||
}
|
||||
"SETBYLAYER" => {
|
||||
if self.tabs[i].scene.selected.is_empty() {
|
||||
use crate::modules::draw::select::SelectObjectsCommand;
|
||||
let selection = SelectObjectsCommand::new("SETBYLAYER");
|
||||
self.command_line.push_info(&selection.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(selection));
|
||||
return Some(self.finish_dispatch(cmd));
|
||||
let command = crate::modules::draw::modify::setbylayer::SetByLayerCommand::new(
|
||||
self.tabs[i].scene.selected.iter().copied().collect(),
|
||||
);
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
}
|
||||
cmd if cmd.starts_with("SETBYLAYER_APPLY ") => {
|
||||
let flags: Vec<_> = cmd.split_whitespace().skip(1).collect();
|
||||
if flags.len() != 2 || flags.iter().any(|flag| !matches!(*flag, "0" | "1")) {
|
||||
return Some(Task::none());
|
||||
}
|
||||
let handles: Vec<_> = self.tabs[i]
|
||||
let mask = crate::modules::draw::modify::setbylayer::mode();
|
||||
let change_byblock = flags[0] == "1";
|
||||
let include_blocks = flags[1] == "1";
|
||||
let mut handles: Vec<_> = self.tabs[i]
|
||||
.scene
|
||||
.selected_entities()
|
||||
.into_iter()
|
||||
.map(|(h, _)| h)
|
||||
.filter(|handle| !self.tabs[i].scene.is_layer_locked(*handle))
|
||||
.collect();
|
||||
if include_blocks {
|
||||
let mut visited: std::collections::HashSet<_> = handles.iter().copied().collect();
|
||||
let mut index = 0;
|
||||
while index < handles.len() {
|
||||
let children = match self.tabs[i].scene.document.get_entity(handles[index]) {
|
||||
Some(acadrust::EntityType::Insert(insert)) => self.tabs[i].scene.document
|
||||
.block_records.get(&insert.block_name)
|
||||
.map(|block| block.entity_handles.clone()).unwrap_or_default(),
|
||||
_ => Vec::new(),
|
||||
};
|
||||
for child in children {
|
||||
if visited.insert(child) && !self.tabs[i].scene.is_layer_locked(child) {
|
||||
handles.push(child);
|
||||
}
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
if handles.is_empty() {
|
||||
self.command_line
|
||||
.push_error(crate::t!("SETBYLAYER: select entities first.").as_ref());
|
||||
} else {
|
||||
let has_changes = handles.iter().any(|handle| {
|
||||
self.tabs[i].scene.document.get_entity(*handle).is_some_and(|entity| {
|
||||
let mut common = entity.common().clone();
|
||||
crate::modules::draw::modify::setbylayer::apply_mask(
|
||||
&mut common, mask, change_byblock,
|
||||
)
|
||||
})
|
||||
});
|
||||
if !has_changes {
|
||||
self.command_line.push_output(
|
||||
crate::t!("SETBYLAYER: no properties required changes.").as_ref(),
|
||||
);
|
||||
return Some(Task::none());
|
||||
}
|
||||
self.push_undo_snapshot(i, "SETBYLAYER");
|
||||
let mut changed = 0usize;
|
||||
let mut changed = Vec::new();
|
||||
for handle in &handles {
|
||||
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(*handle) {
|
||||
let common = entity.common_mut();
|
||||
common.color = acadrust::types::Color::ByLayer;
|
||||
common.color_name = None;
|
||||
common.color_book_handle = None;
|
||||
common.linetype = "ByLayer".to_string();
|
||||
common.line_weight = acadrust::types::LineWeight::ByLayer;
|
||||
changed += 1;
|
||||
if crate::modules::draw::modify::setbylayer::apply_mask(
|
||||
entity.common_mut(), mask, change_byblock,
|
||||
) {
|
||||
changed.push(*handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
let changes: Vec<_> = handles
|
||||
.into_iter()
|
||||
let changes: Vec<_> = changed
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|handle| (handle, crate::scene::ChangeKind::Modified))
|
||||
.collect();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
self.refresh_properties();
|
||||
self.command_line.push_output(crate::tf!(
|
||||
"SETBYLAYER: reset {changed} entity/entities to ByLayer."
|
||||
"SETBYLAYER: reset {} entity/entities to ByLayer.",
|
||||
changed.len()
|
||||
).as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ pub mod refedit;
|
||||
pub mod reverse;
|
||||
pub mod rotate;
|
||||
pub mod scale;
|
||||
pub mod setbylayer;
|
||||
pub mod spline_ops;
|
||||
pub mod splinedit;
|
||||
pub mod stretch;
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
use acadrust::Handle;
|
||||
use glam::DVec3;
|
||||
use crate::command::{CadCommand, CmdOption, CmdResult};
|
||||
|
||||
static MODE: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(255);
|
||||
pub fn mode() -> u8 { MODE.load(std::sync::atomic::Ordering::Relaxed) }
|
||||
pub fn set_mode(value: u8) { MODE.store(value, std::sync::atomic::Ordering::Relaxed); }
|
||||
|
||||
pub fn apply_mask(common: &mut acadrust::entities::EntityCommon, mask: u8, change_byblock: bool) -> bool {
|
||||
let before = common.clone();
|
||||
if mask & 1 != 0 && (change_byblock || common.color != acadrust::types::Color::ByBlock) {
|
||||
common.color = acadrust::types::Color::ByLayer;
|
||||
common.color_name = None;
|
||||
common.color_book_handle = None;
|
||||
}
|
||||
if mask & 2 != 0 && (change_byblock || !common.linetype.eq_ignore_ascii_case("ByBlock")) {
|
||||
common.linetype = "ByLayer".to_string();
|
||||
common.linetype_handle = None;
|
||||
}
|
||||
if mask & 4 != 0 && (change_byblock || common.line_weight != acadrust::types::LineWeight::ByBlock) {
|
||||
common.line_weight = acadrust::types::LineWeight::ByLayer;
|
||||
}
|
||||
if mask & 8 != 0 && (change_byblock || common.material_flags != 1) {
|
||||
common.material_flags = 0;
|
||||
common.material_handle = None;
|
||||
}
|
||||
if mask & 16 != 0 && (change_byblock || common.plotstyle_flags != 1) {
|
||||
common.plotstyle_flags = 0;
|
||||
common.plotstyle_handle = None;
|
||||
}
|
||||
if mask & 128 != 0 && (change_byblock || common.transparency != acadrust::types::Transparency::BY_BLOCK) {
|
||||
common.transparency = acadrust::types::Transparency::BY_LAYER;
|
||||
}
|
||||
*common != before
|
||||
}
|
||||
|
||||
enum Step { Selection, ByBlock, Blocks }
|
||||
|
||||
pub struct SetByLayerCommand {
|
||||
selected: Vec<Handle>,
|
||||
step: Step,
|
||||
change_byblock: bool,
|
||||
include_blocks: bool,
|
||||
}
|
||||
|
||||
impl SetByLayerCommand {
|
||||
pub fn new(selected: Vec<Handle>) -> Self {
|
||||
let step = if selected.is_empty() { Step::Selection } else { Step::ByBlock };
|
||||
Self { selected, step, change_byblock: mode() & 32 != 0, include_blocks: mode() & 64 != 0 }
|
||||
}
|
||||
|
||||
fn answer(&mut self, yes: bool) -> CmdResult {
|
||||
match self.step {
|
||||
Step::ByBlock => {
|
||||
self.change_byblock = yes;
|
||||
self.step = Step::Blocks;
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
Step::Blocks => {
|
||||
let flags = (mode() & !(32 | 64)) | if self.change_byblock { 32 } else { 0 } | if yes { 64 } else { 0 };
|
||||
set_mode(flags);
|
||||
CmdResult::Relaunch(
|
||||
format!("SETBYLAYER_APPLY {} {}", u8::from(self.change_byblock), u8::from(yes)),
|
||||
self.selected.clone(),
|
||||
) },
|
||||
Step::Selection => CmdResult::NeedPoint,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CadCommand for SetByLayerCommand {
|
||||
fn name(&self) -> &'static str { "SETBYLAYER" }
|
||||
fn prompt(&self) -> String {
|
||||
match self.step {
|
||||
Step::Selection => "Select objects:".into(),
|
||||
Step::ByBlock => format!("Change ByBlock to ByLayer? [Yes/No] <{}>:", if self.change_byblock { "Yes" } else { "No" }),
|
||||
Step::Blocks => format!("Include blocks? [Yes/No] <{}>:", if self.include_blocks { "Yes" } else { "No" }),
|
||||
}
|
||||
}
|
||||
fn is_selection_gathering(&self) -> bool { matches!(self.step, Step::Selection) }
|
||||
fn on_selection_complete(&mut self, handles: Vec<Handle>) -> CmdResult {
|
||||
self.selected = handles;
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
fn options(&self) -> Vec<CmdOption> {
|
||||
if self.is_selection_gathering() { Vec::new() }
|
||||
else { vec![CmdOption::new("Yes", "Y"), CmdOption::new("No", "N")] }
|
||||
}
|
||||
fn wants_text_input(&self) -> bool { !self.is_selection_gathering() }
|
||||
fn on_point(&mut self, _: DVec3) -> CmdResult { CmdResult::NeedPoint }
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
if self.is_selection_gathering() {
|
||||
if self.selected.is_empty() { CmdResult::Cancel }
|
||||
else { self.step = Step::ByBlock; CmdResult::NeedPoint }
|
||||
} else { self.answer(if matches!(self.step, Step::ByBlock) { self.change_byblock } else { self.include_blocks }) }
|
||||
}
|
||||
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
||||
Some(match text.trim().to_uppercase().as_str() {
|
||||
"Y" | "YES" => self.answer(true),
|
||||
"N" | "NO" => self.answer(false),
|
||||
_ => CmdResult::NeedPoint,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ModeCommand;
|
||||
impl CadCommand for ModeCommand {
|
||||
fn name(&self) -> &'static str { "SETBYLAYERMODE" }
|
||||
fn prompt(&self) -> String { format!("Enter new value for SETBYLAYERMODE <{}>:", mode()) }
|
||||
fn wants_text_input(&self) -> bool { true }
|
||||
fn on_point(&mut self, _: DVec3) -> CmdResult { CmdResult::NeedPoint }
|
||||
fn on_enter(&mut self) -> CmdResult { CmdResult::Cancel }
|
||||
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
||||
Some(if let Ok(value) = text.trim().parse::<u8>() {
|
||||
set_mode(value); CmdResult::Cancel
|
||||
} else { CmdResult::NeedPoint })
|
||||
}
|
||||
}
|
||||
inventory::submit!(crate::command::CommandRegistration { names: &["SETBYLAYERMODE"] });
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use acadrust::entities::EntityCommon;
|
||||
use acadrust::types::{Color, Transparency};
|
||||
|
||||
#[test]
|
||||
fn mask_changes_only_requested_properties() {
|
||||
let mut common = EntityCommon::default();
|
||||
common.color = Color::from_index(3);
|
||||
common.linetype = "DASHED".into();
|
||||
common.transparency = Transparency::from_percent(40.0);
|
||||
|
||||
assert!(apply_mask(&mut common, 1 | 128, false));
|
||||
assert_eq!(common.color, Color::ByLayer);
|
||||
assert_eq!(common.linetype, "DASHED");
|
||||
assert_eq!(common.transparency, Transparency::BY_LAYER);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn byblock_values_are_kept_unless_requested() {
|
||||
let mut common = EntityCommon::default();
|
||||
common.color = Color::ByBlock;
|
||||
common.linetype = "ByBlock".into();
|
||||
common.transparency = Transparency::BY_BLOCK;
|
||||
|
||||
assert!(!apply_mask(&mut common, 1 | 2 | 128, false));
|
||||
assert!(apply_mask(&mut common, 1 | 2 | 128, true));
|
||||
assert_eq!(common.color, Color::ByLayer);
|
||||
assert_eq!(common.linetype, "ByLayer");
|
||||
assert_eq!(common.transparency, Transparency::BY_LAYER);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user