forked from 1b-agent/opencadstudio-upstream
REFEDIT: reference prompts, -REFEDIT, REFSET, nested references, xref write-back
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
b1d4c9bd9b
commit
6784f89993
16 files changed
+1296
-380
No files matched your search
@@ -80,7 +80,7 @@ use crate::command::{CmdResult, SelectionEntity, StepInput};
|
||||
use codec::Handle;
|
||||
use iced::Task;
|
||||
|
||||
mod clipboard;
|
||||
pub(super) mod clipboard;
|
||||
mod constraint;
|
||||
mod dim_edit;
|
||||
mod entity_commit;
|
||||
|
||||
@@ -16,6 +16,9 @@ pub(super) const BLOCK_SYSVARS: &[&str] = &[
|
||||
"BLOCKSTATE",
|
||||
"EXPLMODE",
|
||||
"INSNAME",
|
||||
"REFEDITNAME",
|
||||
"XEDIT",
|
||||
"XFADECTL",
|
||||
];
|
||||
|
||||
/// The `SETVAR ?` line for this family.
|
||||
@@ -45,6 +48,15 @@ impl OpenCADStudio {
|
||||
self.command_line
|
||||
.push_output(&format!("BLOCKSTATE = {state} (read only)"));
|
||||
}
|
||||
"REFEDITNAME" => {
|
||||
let name = self.tabs[i]
|
||||
.refedit_session
|
||||
.as_ref()
|
||||
.map(|s| s.block_name.clone())
|
||||
.unwrap_or_default();
|
||||
self.command_line
|
||||
.push_output(&format!("REFEDITNAME = \"{name}\" (read only)"));
|
||||
}
|
||||
"INSNAME" | "BLOCKNAVIGATE" => {
|
||||
let current = if name == "INSNAME" {
|
||||
self.insname.clone()
|
||||
@@ -77,6 +89,8 @@ impl OpenCADStudio {
|
||||
let (current, max) = match name.as_str() {
|
||||
"BLOCKMRULIST" => (self.block_mru_list as i32, 100),
|
||||
"BLOCKREDEFINEMODE" => (self.block_redefine_mode as i32, 2),
|
||||
"XEDIT" => (self.tabs[i].scene.document.header.xedit as i32, 1),
|
||||
"XFADECTL" => (crate::scene::cache::block_cache::refedit_fade_ctl(), 90),
|
||||
_ => (explmode(&self.tabs[i].scene.document) as i32, 1),
|
||||
};
|
||||
let ask = |app: &mut Self, name: String| {
|
||||
@@ -109,6 +123,23 @@ impl OpenCADStudio {
|
||||
self.block_redefine_mode = n as u8;
|
||||
self.persist_settings_if_changed();
|
||||
}
|
||||
"XEDIT" => {
|
||||
if n != current {
|
||||
self.push_undo_snapshot(i, "XEDIT");
|
||||
self.tabs[i].scene.document.header.xedit = n == 1;
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
}
|
||||
"XFADECTL" => {
|
||||
crate::scene::cache::block_cache::set_refedit_fade_ctl(n);
|
||||
self.persist_settings_if_changed();
|
||||
for tab in &mut self.tabs {
|
||||
if !tab.is_start {
|
||||
tab.scene.recolor_meshes();
|
||||
tab.scene.bump_geometry();
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if n != current {
|
||||
self.push_undo_snapshot(i, "EXPLMODE");
|
||||
|
||||
@@ -457,35 +457,6 @@ impl OpenCADStudio {
|
||||
self.open_attedit_dialog();
|
||||
}
|
||||
|
||||
// ── REFEDIT — in-place block editing ─────────────────────────────
|
||||
"REFEDIT" => {
|
||||
use crate::modules::draw::modify::refedit::RefEditPickCommand;
|
||||
// If a session is already active, tell the user.
|
||||
if self.tabs[i].refedit_session.is_some() {
|
||||
self.command_line.push_error(
|
||||
crate::t!("REFEDIT: a session is already active. Use REFCLOSE first.")
|
||||
.as_ref(),
|
||||
);
|
||||
} else {
|
||||
// Check if a single INSERT is already selected.
|
||||
let selected: Vec<_> =
|
||||
self.tabs[i].scene.selected_entities().into_iter().collect();
|
||||
if selected.len() == 1 {
|
||||
if let Some(codec::EntityType::Insert(_)) = selected.first().map(|(_, e)| e)
|
||||
{
|
||||
let handle = selected[0].0;
|
||||
// Skip pick phase — jump straight to begin.
|
||||
let _ =
|
||||
self.dispatch_command(&format!("REFEDIT_BEGIN:{}", handle.value()));
|
||||
return Some(Task::none());
|
||||
}
|
||||
}
|
||||
let cmd_obj = RefEditPickCommand::new();
|
||||
self.command_line.push_info(&cmd_obj.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(cmd_obj));
|
||||
}
|
||||
}
|
||||
|
||||
"BEDIT" => {
|
||||
use crate::modules::draw::modify::block_edit::BlockEditPickCommand;
|
||||
if self.tabs[i].refedit_session.is_some() {
|
||||
@@ -785,303 +756,6 @@ impl OpenCADStudio {
|
||||
);
|
||||
}
|
||||
|
||||
cmd if cmd.starts_with("REFEDIT_BEGIN:") => {
|
||||
use crate::modules::draw::modify::refedit::{
|
||||
apply_insert_transform, RefEditSession,
|
||||
};
|
||||
use codec::Handle;
|
||||
|
||||
let handle_u64: u64 = cmd["REFEDIT_BEGIN:".len()..].parse().unwrap_or(0);
|
||||
let insert_handle = Handle::new(handle_u64);
|
||||
if self.reject_locked_edit(i, insert_handle) {
|
||||
return Some(Task::none());
|
||||
}
|
||||
|
||||
// Get INSERT entity.
|
||||
let insert = match self.tabs[i].scene.document.get_entity(insert_handle) {
|
||||
Some(codec::EntityType::Insert(ins)) => ins.clone(),
|
||||
_ => {
|
||||
self.command_line.push_error(
|
||||
crate::t!("REFEDIT: selected object is not an INSERT.").as_ref(),
|
||||
);
|
||||
return Some(Task::none());
|
||||
}
|
||||
};
|
||||
|
||||
// Build the INSERT's full placement transform (OCS + rotation +
|
||||
// scale, including non-uniform / mirrored, minus the block base
|
||||
// point) and its inverse, so edits round-trip back to
|
||||
// block-local coordinates on SAVE. The base point is part of
|
||||
// the placement: without it the working copy sits Rot*Scale*base
|
||||
// away from the reference the user picked.
|
||||
let sx = insert.x_scale();
|
||||
let sy = insert.y_scale();
|
||||
let sz = insert.z_scale();
|
||||
let base = crate::scene::render_graph::block_base_point(
|
||||
&self.tabs[i].scene.document,
|
||||
&insert.block_name,
|
||||
);
|
||||
let forward = crate::scene::render_graph::insert_transform_with_base(&insert, base);
|
||||
let inverse = {
|
||||
use codec::types::{Matrix3, Matrix4, Transform};
|
||||
let ocs_t =
|
||||
Matrix4::from_matrix3(Matrix3::arbitrary_axis(insert.normal).transpose());
|
||||
let t_inv = Matrix4::translation(
|
||||
-insert.insert_point.x,
|
||||
-insert.insert_point.y,
|
||||
-insert.insert_point.z,
|
||||
);
|
||||
let r_inv = Matrix4::rotation_z(-insert.rotation);
|
||||
let s_inv = Matrix4::scaling(1.0 / sx, 1.0 / sy, 1.0 / sz);
|
||||
// inverse(T(-base)·OCS·T·R·S) = T(+base)·S⁻¹·R⁻¹·T⁻¹·OCSᵀ
|
||||
Transform::from_matrix(
|
||||
Matrix4::translation(base.x, base.y, base.z)
|
||||
* (s_inv * r_inv * t_inv * ocs_t),
|
||||
)
|
||||
};
|
||||
|
||||
// Find the block record.
|
||||
let br_handle = match self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.block_records
|
||||
.get(&insert.block_name)
|
||||
{
|
||||
Some(br) => br.handle,
|
||||
None => {
|
||||
self.command_line.push_error(
|
||||
crate::tf!("REFEDIT: block \"{}\" not found.", insert.block_name)
|
||||
.as_ref(),
|
||||
);
|
||||
return Some(Task::none());
|
||||
}
|
||||
};
|
||||
|
||||
// Collect block-local entities (skip structural Block/BlockEnd/AttDef).
|
||||
let block_entities: Vec<_> = {
|
||||
let br = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.block_records
|
||||
.get(&insert.block_name)
|
||||
.unwrap();
|
||||
br.entity_handles
|
||||
.iter()
|
||||
.filter_map(|h| self.tabs[i].scene.document.get_entity(*h).cloned())
|
||||
.filter(|e| {
|
||||
!matches!(
|
||||
e,
|
||||
codec::EntityType::Block(_)
|
||||
| codec::EntityType::BlockEnd(_)
|
||||
| codec::EntityType::AttributeDefinition(_)
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
if block_entities.is_empty() {
|
||||
self.command_line
|
||||
.push_error(crate::t!("REFEDIT: block is empty.").as_ref());
|
||||
return Some(Task::none());
|
||||
}
|
||||
|
||||
let session = RefEditSession {
|
||||
block_name: insert.block_name.clone(),
|
||||
br_handle,
|
||||
temp_handles: vec![],
|
||||
forward,
|
||||
inverse,
|
||||
// Everything allocated from here on is part of the session.
|
||||
handle_watermark: self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.next_handle()
|
||||
.max(self.tabs[i].scene.document.header.handle_seed),
|
||||
};
|
||||
|
||||
self.push_undo_snapshot(i, "REFEDIT");
|
||||
self.tabs[i].refedit_session = Some(session.clone());
|
||||
|
||||
// Add block entities to model space with INSERT transform applied.
|
||||
let mut temp_handles = Vec::new();
|
||||
for mut entity in block_entities {
|
||||
apply_insert_transform(&mut entity, &session);
|
||||
entity.common_mut().handle = codec::Handle::NULL;
|
||||
entity.common_mut().owner_handle = codec::Handle::NULL;
|
||||
let h = self.tabs[i].scene.add_entity(entity);
|
||||
temp_handles.push(h);
|
||||
}
|
||||
self.tabs[i].refedit_session.as_mut().unwrap().temp_handles = temp_handles.clone();
|
||||
|
||||
// Fade everything except the entities being edited, so the
|
||||
// surrounding drawing stays visible for context but the block's
|
||||
// geometry stands out. (#136)
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.set_refedit_keep(Some(temp_handles.iter().copied().collect()));
|
||||
|
||||
// Select the temp entities so user can see what they're editing.
|
||||
self.tabs[i].scene.deselect_all();
|
||||
for h in &temp_handles {
|
||||
self.tabs[i].scene.select_entity(*h, false);
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
|
||||
// No active command — the user edits the block's geometry freely
|
||||
// (move, grips, draw, erase…) and runs REFCLOSE when done. (#136)
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.command_line.push_info(crate::tf!(
|
||||
"REFEDIT: Editing block \"{}\". Run REFCLOSE to save, REFCLOSE_DISCARD to cancel.",
|
||||
insert.block_name
|
||||
).as_ref());
|
||||
}
|
||||
|
||||
"REFCLOSE" => {
|
||||
if self.tabs[i].refedit_session.is_some() {
|
||||
use crate::modules::draw::modify::refedit::RefCloseCommand;
|
||||
let cmd_obj = RefCloseCommand::new();
|
||||
self.command_line.push_info(&cmd_obj.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(cmd_obj));
|
||||
} else {
|
||||
self.command_line
|
||||
.push_error(crate::t!("REFCLOSE: no REFEDIT session active.").as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
"REFCLOSE_SAVE" => {
|
||||
use crate::modules::draw::modify::explode::normalize_entity_for_block;
|
||||
use crate::modules::draw::modify::refedit::apply_insert_inverse_transform;
|
||||
|
||||
let session = match self.tabs[i].refedit_session.take() {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
self.command_line
|
||||
.push_error(crate::t!("REFCLOSE: no REFEDIT session active.").as_ref());
|
||||
return Some(Task::none());
|
||||
}
|
||||
};
|
||||
|
||||
self.push_undo_snapshot(i, "REFCLOSE");
|
||||
|
||||
// The working set: the temp copies PLUS everything the user
|
||||
// created in this space during the session (drawn / pasted /
|
||||
// offset entities carry handles at or above the watermark) —
|
||||
// those must fold into the block too, not stay behind in
|
||||
// model space (#423).
|
||||
let working: Vec<codec::Handle> = {
|
||||
let space = self.tabs[i].scene.current_layout_block_handle_pub();
|
||||
let drawn = self.tabs[i].scene.document.entities().filter(|e| {
|
||||
let c = e.common();
|
||||
c.owner_handle == space
|
||||
&& c.handle.value() >= session.handle_watermark
|
||||
&& !session.temp_handles.contains(&c.handle)
|
||||
});
|
||||
session
|
||||
.temp_handles
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(drawn.map(|e| e.common().handle))
|
||||
.collect()
|
||||
};
|
||||
|
||||
// Collect the edited temp entities.
|
||||
let new_entities: Vec<codec::EntityType> = working
|
||||
.iter()
|
||||
.filter_map(|h| self.tabs[i].scene.document.get_entity(*h).cloned())
|
||||
.collect();
|
||||
|
||||
// Remove temp entities from model space.
|
||||
self.tabs[i].scene.erase_entities(&working);
|
||||
|
||||
// Apply inverse INSERT transform → block-local coordinates.
|
||||
let new_entities: Vec<_> = new_entities
|
||||
.into_iter()
|
||||
.map(|mut entity| {
|
||||
apply_insert_inverse_transform(&mut entity, &session);
|
||||
let mut entity = normalize_entity_for_block(entity);
|
||||
entity.common_mut().handle = codec::Handle::NULL;
|
||||
entity.common_mut().owner_handle = session.br_handle;
|
||||
entity
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Remove old block entities from the document.
|
||||
let old_handles: Vec<_> = match self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.block_records
|
||||
.get(&session.block_name)
|
||||
{
|
||||
Some(br) => br.entity_handles.clone(),
|
||||
None => vec![],
|
||||
};
|
||||
for h in &old_handles {
|
||||
self.tabs[i].scene.document.remove_entity(*h);
|
||||
}
|
||||
// Flush the entity_handles list from the block record.
|
||||
if let Some(br) = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.block_records
|
||||
.get_mut(&session.block_name)
|
||||
{
|
||||
br.entity_handles.clear();
|
||||
}
|
||||
|
||||
// Add the new block entities.
|
||||
for entity in new_entities {
|
||||
let _ = self.tabs[i].scene.document.add_entity(entity);
|
||||
}
|
||||
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line.push_output(
|
||||
crate::tf!(
|
||||
"REFCLOSE: Block \"{}\" saved. All references updated.",
|
||||
session.block_name
|
||||
)
|
||||
.as_ref(),
|
||||
);
|
||||
// End the edit fade before rebuilding, so the restored geometry
|
||||
// recolours bright. (#136)
|
||||
self.tabs[i].scene.set_refedit_keep(None);
|
||||
// Rebuild hatch/image/mesh caches since block content changed.
|
||||
self.tabs[i].scene.rebuild_derived_caches();
|
||||
}
|
||||
|
||||
"REFCLOSE_DISCARD" => {
|
||||
let session = match self.tabs[i].refedit_session.take() {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
self.command_line
|
||||
.push_error(crate::t!("REFCLOSE: no REFEDIT session active.").as_ref());
|
||||
return Some(Task::none());
|
||||
}
|
||||
};
|
||||
// Remove the working set without modifying the block — the
|
||||
// temp copies plus anything created during the session (#423).
|
||||
let working: Vec<codec::Handle> = {
|
||||
let space = self.tabs[i].scene.current_layout_block_handle_pub();
|
||||
let drawn = self.tabs[i].scene.document.entities().filter(|e| {
|
||||
let c = e.common();
|
||||
c.owner_handle == space
|
||||
&& c.handle.value() >= session.handle_watermark
|
||||
&& !session.temp_handles.contains(&c.handle)
|
||||
});
|
||||
session
|
||||
.temp_handles
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(drawn.map(|e| e.common().handle))
|
||||
.collect()
|
||||
};
|
||||
self.tabs[i].scene.erase_entities(&working);
|
||||
self.tabs[i].scene.deselect_all();
|
||||
// End the edit fade — restore the drawing to full brightness.
|
||||
self.tabs[i].scene.set_refedit_keep(None);
|
||||
self.command_line
|
||||
.push_output(crate::t!("REFCLOSE: Changes discarded.").as_ref());
|
||||
}
|
||||
|
||||
"ALIGN" => {
|
||||
use crate::modules::draw::modify::align::AlignCommand;
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ mod pc_extract;
|
||||
mod pdf_dialogs;
|
||||
pub(crate) mod pdf_import;
|
||||
mod pdf_underlay;
|
||||
mod refedit;
|
||||
mod plotvars;
|
||||
mod point_cloud;
|
||||
pub(crate) mod sheet_set;
|
||||
@@ -331,6 +332,9 @@ impl OpenCADStudio {
|
||||
if let Some(t) = self.dispatch_dim(cmd, i) {
|
||||
return Some(t);
|
||||
}
|
||||
if let Some(t) = self.dispatch_refedit(cmd, i) {
|
||||
return Some(t);
|
||||
}
|
||||
if let Some(t) = self.dispatch_inquiry(cmd, i) {
|
||||
return Some(t);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,588 @@
|
||||
//! REFEDIT, -REFEDIT, REFSET and REFCLOSE: in-place editing of a block
|
||||
//! reference or an external reference, with the reference's prompts and
|
||||
//! messages. The session itself lives in `DocumentTab::refedit_session`.
|
||||
|
||||
use crate::app::{Message, OpenCADStudio};
|
||||
use crate::modules::draw::modify::refedit::{
|
||||
apply_insert_inverse_transform, apply_insert_transform, level_hits,
|
||||
nesting_chain, RefCloseCommand, RefEditOptionsCommand, RefEditPickCommand, RefEditSession,
|
||||
RefSetCommand, NOT_CHECKED_OUT,
|
||||
};
|
||||
use codec::{EntityType, Handle};
|
||||
use iced::Task;
|
||||
use std::collections::HashSet;
|
||||
|
||||
/// `** Command not allowed, NAME already checked out for editing **`
|
||||
fn already_checked_out(name: &str) -> String {
|
||||
crate::tf!("** Command not allowed, {} already checked out for editing **", name).into_owned()
|
||||
}
|
||||
|
||||
impl OpenCADStudio {
|
||||
pub(super) fn dispatch_refedit(&mut self, cmd: &str, i: usize) -> Option<Task<Message>> {
|
||||
match cmd {
|
||||
"REFEDIT" | "-REFEDIT" => Some(self.start_refedit(i, cmd == "-REFEDIT")),
|
||||
"REFSET" => {
|
||||
if self.tabs[i].refedit_session.is_none() {
|
||||
self.command_line.push_error(crate::t!(NOT_CHECKED_OUT).as_ref());
|
||||
return Some(Task::none());
|
||||
}
|
||||
self.command_line.push_info(
|
||||
crate::t!("Transfer objects between the RefEdit working set and host drawing...")
|
||||
.as_ref(),
|
||||
);
|
||||
let command = RefSetCommand::new();
|
||||
use crate::command::CadCommand;
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
Some(Task::none())
|
||||
}
|
||||
"REFCLOSE" => {
|
||||
if self.tabs[i].refedit_session.is_none() {
|
||||
self.command_line.push_error(crate::t!(NOT_CHECKED_OUT).as_ref());
|
||||
return Some(Task::none());
|
||||
}
|
||||
let command = RefCloseCommand::new();
|
||||
use crate::command::CadCommand;
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
Some(Task::none())
|
||||
}
|
||||
"REFCLOSE_SAVE" => {
|
||||
self.close_refedit(i, true);
|
||||
Some(Task::none())
|
||||
}
|
||||
"REFCLOSE_DISCARD" => {
|
||||
self.close_refedit(i, false);
|
||||
Some(Task::none())
|
||||
}
|
||||
_ if cmd.starts_with("REFEDIT_PICKED:") => {
|
||||
self.refedit_picked(i, &cmd["REFEDIT_PICKED:".len()..]);
|
||||
Some(Task::none())
|
||||
}
|
||||
_ if cmd.starts_with("REFEDIT_BEGIN:") => {
|
||||
self.begin_refedit(i, &cmd["REFEDIT_BEGIN:".len()..]);
|
||||
Some(Task::none())
|
||||
}
|
||||
_ if cmd.starts_with("REFSET_APPLY:") => {
|
||||
self.apply_refset(i, cmd.ends_with("ADD"));
|
||||
Some(Task::none())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Block references and xrefs of the current space.
|
||||
fn refedit_references(&self, i: usize) -> HashSet<Handle> {
|
||||
let space = self.tabs[i].scene.current_layout_block_handle_pub();
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.entities()
|
||||
.filter(|e| matches!(e, EntityType::Insert(_)) && e.common().owner_handle == space)
|
||||
.map(|e| e.common().handle)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn start_refedit(&mut self, i: usize, command_line: bool) -> Task<Message> {
|
||||
if let Some(session) = &self.tabs[i].refedit_session {
|
||||
let message = already_checked_out(&session.block_name);
|
||||
self.command_line.push_error(&message);
|
||||
return Task::none();
|
||||
}
|
||||
let references = self.refedit_references(i);
|
||||
// A single preselected reference is taken as the pick.
|
||||
let selected: Vec<Handle> = self.tabs[i]
|
||||
.scene
|
||||
.selected_entities()
|
||||
.into_iter()
|
||||
.map(|(h, _)| h)
|
||||
.collect();
|
||||
if let [handle] = selected[..] {
|
||||
if references.contains(&handle) {
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.refedit_picked(
|
||||
i,
|
||||
&format!("{}:{}", command_line as u8, handle.value()),
|
||||
);
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
use crate::command::CadCommand;
|
||||
let command = RefEditPickCommand::new(command_line, references);
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// `cli:handle[:x:y:z]` — the reference was picked (at the point).
|
||||
fn refedit_picked(&mut self, i: usize, args: &str) {
|
||||
let parts: Vec<&str> = args.split(':').collect();
|
||||
let command_line = parts.first() == Some(&"1");
|
||||
let Some(top) = parts.get(1).and_then(|v| v.parse::<u64>().ok()).map(Handle::new) else {
|
||||
return;
|
||||
};
|
||||
let point = (parts.len() >= 5)
|
||||
.then(|| {
|
||||
Some(glam::DVec3::new(
|
||||
parts[2].parse().ok()?,
|
||||
parts[3].parse().ok()?,
|
||||
parts[4].parse().ok()?,
|
||||
))
|
||||
})
|
||||
.flatten();
|
||||
self.tabs[i].scene.deselect_all();
|
||||
let chain = nesting_chain(&self.tabs[i].scene.document, top, point);
|
||||
if chain.is_empty() {
|
||||
self.command_line
|
||||
.push_error(crate::t!("Object is not a valid reference").as_ref());
|
||||
return;
|
||||
}
|
||||
if command_line {
|
||||
let hits = chain
|
||||
.iter()
|
||||
.map(|level| level_hits(&self.tabs[i].scene.document, level))
|
||||
.collect();
|
||||
let command = RefEditOptionsCommand::new(top, chain, hits);
|
||||
use crate::command::CadCommand;
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
} else {
|
||||
// The dialog's defaults: the innermost reference, all objects,
|
||||
// attribute definitions not displayed.
|
||||
let path: Vec<String> = chain[1..]
|
||||
.iter()
|
||||
.map(|l| l.insert.value().to_string())
|
||||
.collect();
|
||||
let _ = self.dispatch_command(&format!(
|
||||
"REFEDIT_BEGIN:{}:{}:0:",
|
||||
top.value(),
|
||||
path.join(",")
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// `top[:path[:attributes[:nested]]]` — open the session.
|
||||
fn begin_refedit(&mut self, i: usize, args: &str) {
|
||||
let mut parts = args.split(':');
|
||||
let Some(top) = parts
|
||||
.next()
|
||||
.and_then(|v| v.parse::<u64>().ok())
|
||||
.map(Handle::new)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let handles = |s: Option<&str>| -> Vec<Handle> {
|
||||
s.unwrap_or("")
|
||||
.split(',')
|
||||
.filter_map(|v| v.parse::<u64>().ok())
|
||||
.map(Handle::new)
|
||||
.collect()
|
||||
};
|
||||
let path = handles(parts.next());
|
||||
let attributes = parts.next() == Some("1");
|
||||
let nested = handles(parts.next());
|
||||
if self.tabs[i].refedit_session.is_some() || self.reject_locked_edit(i, top) {
|
||||
return;
|
||||
}
|
||||
let document = &self.tabs[i].scene.document;
|
||||
let mut chain = nesting_chain(document, top, None);
|
||||
if chain.is_empty() {
|
||||
self.command_line
|
||||
.push_error(crate::t!("Object is not a valid reference").as_ref());
|
||||
return;
|
||||
}
|
||||
// Follow the stored path of definition-owned references.
|
||||
for handle in path {
|
||||
let Some(EntityType::Insert(inner)) = document.get_entity(handle) else {
|
||||
break;
|
||||
};
|
||||
let base = crate::scene::render_graph::block_base_point(document, &inner.block_name);
|
||||
let local = crate::scene::render_graph::insert_transform_with_base(inner, base);
|
||||
let outer = chain.last().expect("non-empty chain").forward.clone();
|
||||
chain.push(crate::modules::draw::modify::refedit::ChainLevel {
|
||||
insert: handle,
|
||||
block_name: inner.block_name.clone(),
|
||||
forward: local.then(&outer),
|
||||
});
|
||||
}
|
||||
let level = chain.last().expect("non-empty chain").clone();
|
||||
let Some(br) = document.block_records.get(&level.block_name) else {
|
||||
return;
|
||||
};
|
||||
let br_handle = br.handle;
|
||||
let xref_path = (br.flags.is_xref || br.flags.is_xref_overlay)
|
||||
.then(|| br.xref_path.clone())
|
||||
.filter(|p| !p.is_empty());
|
||||
let extracted: Vec<Handle> = br
|
||||
.entity_handles
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|h| match document.get_entity(*h) {
|
||||
Some(EntityType::Block(_) | EntityType::BlockEnd(_)) | None => false,
|
||||
Some(EntityType::AttributeDefinition(_)) => attributes,
|
||||
Some(_) => nested.is_empty() || nested.contains(h),
|
||||
})
|
||||
.collect();
|
||||
let Some(inverse) =
|
||||
crate::modules::draw::modify::refedit::inverse_placement(&level.forward)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let session = RefEditSession {
|
||||
block_name: level.block_name.clone(),
|
||||
br_handle,
|
||||
temp_handles: Vec::new(),
|
||||
extracted: extracted.clone(),
|
||||
added: HashSet::new(),
|
||||
removed: HashSet::new(),
|
||||
forward: level.forward.clone(),
|
||||
inverse,
|
||||
// Everything allocated from here on is part of the session.
|
||||
handle_watermark: document.next_handle().max(document.header.handle_seed),
|
||||
xref_path,
|
||||
};
|
||||
let copies: Vec<EntityType> = extracted
|
||||
.iter()
|
||||
.filter_map(|h| document.get_entity(*h).cloned())
|
||||
.collect();
|
||||
|
||||
self.push_undo_snapshot(i, "REFEDIT");
|
||||
let mut temp_handles = Vec::new();
|
||||
for mut entity in copies {
|
||||
apply_insert_transform(&mut entity, &session);
|
||||
entity.common_mut().handle = Handle::NULL;
|
||||
entity.common_mut().owner_handle = Handle::NULL;
|
||||
temp_handles.push(self.tabs[i].scene.add_entity(entity));
|
||||
}
|
||||
let mut session = session;
|
||||
session.temp_handles = temp_handles.clone();
|
||||
self.tabs[i].refedit_session = Some(session);
|
||||
self.tabs[i].refedit_closed.clear();
|
||||
// Fade everything outside the working set by XFADECTL. (#136)
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.set_refedit_keep(Some(temp_handles.iter().copied().collect::<rustc_hash::FxHashSet<_>>()));
|
||||
self.tabs[i].scene.refedit_lock = true;
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.tabs[i].dirty = true;
|
||||
// No active command — the definition's objects are edited freely
|
||||
// (move, grips, draw, erase…) until REFCLOSE.
|
||||
self.tabs[i].active_cmd = None;
|
||||
}
|
||||
|
||||
/// REFSET Add / Remove on the current selection.
|
||||
fn apply_refset(&mut self, i: usize, add: bool) {
|
||||
let Some(mut session) = self.tabs[i].refedit_session.take() else {
|
||||
self.command_line.push_error(crate::t!(NOT_CHECKED_OUT).as_ref());
|
||||
return;
|
||||
};
|
||||
let picked: Vec<Handle> = self.tabs[i]
|
||||
.scene
|
||||
.selected_entities()
|
||||
.into_iter()
|
||||
.map(|(h, _)| h)
|
||||
.collect();
|
||||
let mut moved = 0usize;
|
||||
for h in picked {
|
||||
let inside = session.in_working_set(h);
|
||||
if add && !inside {
|
||||
session.removed.remove(&h);
|
||||
if !session.in_working_set(h) {
|
||||
session.added.insert(h);
|
||||
}
|
||||
moved += 1;
|
||||
} else if !add && inside {
|
||||
session.added.remove(&h);
|
||||
if session.in_working_set(h) {
|
||||
session.removed.insert(h);
|
||||
}
|
||||
moved += 1;
|
||||
}
|
||||
}
|
||||
let keep: rustc_hash::FxHashSet<Handle> = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.entities()
|
||||
.map(|e| e.common().handle)
|
||||
.filter(|h| session.in_working_set(*h))
|
||||
.collect();
|
||||
self.tabs[i].refedit_session = Some(session);
|
||||
self.tabs[i].scene.set_refedit_keep(Some(keep));
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.command_line.push_output(&if add {
|
||||
crate::tf!("{} Added to working set", moved).into_owned()
|
||||
} else {
|
||||
crate::tf!("{} Removed from working set", moved).into_owned()
|
||||
});
|
||||
}
|
||||
|
||||
/// REFCLOSE Save (`save`) or Discard.
|
||||
fn close_refedit(&mut self, i: usize, save: bool) {
|
||||
let Some(session) = self.tabs[i].refedit_session.take() else {
|
||||
self.command_line.push_error(crate::t!(NOT_CHECKED_OUT).as_ref());
|
||||
return;
|
||||
};
|
||||
self.push_undo_snapshot(i, "REFCLOSE");
|
||||
self.command_line
|
||||
.push_output(crate::t!("Regenerating model.").as_ref());
|
||||
let space = self.tabs[i].scene.current_layout_block_handle_pub();
|
||||
// The working set: the copies still in it, everything created in this
|
||||
// space during the session (#423) and the host objects added with
|
||||
// REFSET — less what REFSET removed.
|
||||
let created: Vec<Handle> = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.entities()
|
||||
.filter(|e| {
|
||||
let c = e.common();
|
||||
c.owner_handle == space && c.handle.value() >= session.handle_watermark
|
||||
})
|
||||
.map(|e| e.common().handle)
|
||||
.collect();
|
||||
let working: Vec<Handle> = created
|
||||
.iter()
|
||||
.chain(session.added.iter())
|
||||
.copied()
|
||||
.filter(|h| session.in_working_set(*h))
|
||||
.collect::<HashSet<_>>()
|
||||
.into_iter()
|
||||
.collect();
|
||||
if !save {
|
||||
// The copies and new objects go; host objects added with REFSET
|
||||
// stay where they are, and copies moved out with REFSET Remove
|
||||
// stay in the host.
|
||||
let drop: Vec<Handle> = working
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|h| !session.added.contains(h))
|
||||
.collect();
|
||||
self.tabs[i].scene.erase_entities(&drop);
|
||||
self.finish_refedit_close(i, session);
|
||||
return;
|
||||
}
|
||||
|
||||
use crate::modules::draw::modify::explode::normalize_entity_for_block;
|
||||
let originals: HashSet<Handle> = session.temp_handles.iter().copied().collect();
|
||||
let added_count = working.iter().filter(|h| !originals.contains(h)).count();
|
||||
let removed_count = session.extracted.len()
|
||||
- working.iter().filter(|h| originals.contains(h)).count();
|
||||
let edited: Vec<EntityType> = working
|
||||
.iter()
|
||||
.filter_map(|h| self.tabs[i].scene.document.get_entity(*h).cloned())
|
||||
.map(|mut entity| {
|
||||
apply_insert_inverse_transform(&mut entity, &session);
|
||||
let mut entity = normalize_entity_for_block(entity);
|
||||
entity.common_mut().handle = Handle::NULL;
|
||||
entity.common_mut().owner_handle = session.br_handle;
|
||||
entity
|
||||
})
|
||||
.collect();
|
||||
self.tabs[i].scene.erase_entities(&working);
|
||||
// The extracted definition objects are replaced; the rest stay.
|
||||
for h in &session.extracted {
|
||||
self.tabs[i].scene.document.remove_entity(*h);
|
||||
}
|
||||
if let Some(br) = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.block_records
|
||||
.get_mut(&session.block_name)
|
||||
{
|
||||
let extracted: HashSet<Handle> = session.extracted.iter().copied().collect();
|
||||
br.entity_handles.retain(|h| !extracted.contains(h));
|
||||
}
|
||||
for entity in edited.iter().cloned() {
|
||||
let _ = self.tabs[i].scene.document.add_entity(entity);
|
||||
}
|
||||
let instances = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.entities()
|
||||
.filter(|e| {
|
||||
matches!(e, EntityType::Insert(ins)
|
||||
if ins.block_name.eq_ignore_ascii_case(&session.block_name))
|
||||
})
|
||||
.count();
|
||||
let name = session.block_name.clone();
|
||||
match added_count {
|
||||
0 => {}
|
||||
1 => self
|
||||
.command_line
|
||||
.push_output(crate::tf!("1 object added to {}", name).as_ref()),
|
||||
n => self
|
||||
.command_line
|
||||
.push_output(crate::tf!("{} objects added to {}", n, name).as_ref()),
|
||||
}
|
||||
match removed_count {
|
||||
0 => {}
|
||||
1 => self
|
||||
.command_line
|
||||
.push_output(crate::tf!("1 object removed from {}", name).as_ref()),
|
||||
n => self
|
||||
.command_line
|
||||
.push_output(crate::tf!("{} objects removed from {}", n, name).as_ref()),
|
||||
}
|
||||
let reloaded = match &session.xref_path {
|
||||
Some(raw) => match self.write_back_xref(i, &session, raw, edited) {
|
||||
Ok(()) => true,
|
||||
Err(error) => {
|
||||
self.command_line.push_error(&error);
|
||||
false
|
||||
}
|
||||
},
|
||||
None => false,
|
||||
};
|
||||
let updated = match (session.xref_path.is_some(), instances) {
|
||||
(true, 1) => crate::t!("1 xref instance updated").into_owned(),
|
||||
(true, n) => crate::tf!("{} xref instances updated", n).into_owned(),
|
||||
(false, 1) => crate::t!("1 block instance updated").into_owned(),
|
||||
(false, n) => crate::tf!("{} block instances updated", n).into_owned(),
|
||||
};
|
||||
self.command_line.push_output(&updated);
|
||||
self.command_line.push_output(&if reloaded {
|
||||
crate::tf!("{} redefined and reloaded.", name).into_owned()
|
||||
} else {
|
||||
crate::tf!("{} redefined.", name).into_owned()
|
||||
});
|
||||
self.finish_refedit_close(i, session);
|
||||
// Rebuild hatch/image/mesh caches since block content changed.
|
||||
self.tabs[i].scene.rebuild_derived_caches();
|
||||
}
|
||||
|
||||
fn finish_refedit_close(&mut self, i: usize, session: RefEditSession) {
|
||||
self.tabs[i].refedit_closed.push(session);
|
||||
self.tabs[i].scene.refedit_lock = false;
|
||||
self.tabs[i].scene.set_refedit_keep(None);
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
|
||||
/// Write the edited definition into the referenced drawing and reload it.
|
||||
fn write_back_xref(
|
||||
&mut self,
|
||||
i: usize,
|
||||
session: &RefEditSession,
|
||||
raw: &str,
|
||||
edited: Vec<EntityType>,
|
||||
) -> Result<(), String> {
|
||||
let base_dir = self.tabs[i]
|
||||
.current_path
|
||||
.as_ref()
|
||||
.and_then(|p| p.parent().map(std::path::Path::to_path_buf))
|
||||
.unwrap_or_default();
|
||||
let path = crate::io::xref::resolve_path(raw, &base_dir)
|
||||
.ok_or_else(|| format!("\"{raw}\" cannot be found."))?;
|
||||
let mut source = crate::io::load_file(&path)?;
|
||||
let prefix = format!("{}|", session.block_name);
|
||||
let strip = |name: &str| -> String {
|
||||
if name.len() > prefix.len()
|
||||
&& name.is_char_boundary(prefix.len())
|
||||
&& name[..prefix.len()].eq_ignore_ascii_case(&prefix)
|
||||
{
|
||||
name[prefix.len()..].to_string()
|
||||
} else {
|
||||
name.to_string()
|
||||
}
|
||||
};
|
||||
// Dependencies come from the host (where they carry the xref prefix)
|
||||
// and go back under their own names.
|
||||
let mut deps = crate::app::ClipboardDeps::capture(&self.tabs[i].scene.document, &edited);
|
||||
{
|
||||
use codec::TableEntry;
|
||||
for layer in &mut deps.layers {
|
||||
let name = strip(layer.name());
|
||||
layer.name = name;
|
||||
}
|
||||
for linetype in &mut deps.linetypes {
|
||||
let name = strip(linetype.name());
|
||||
linetype.name = name;
|
||||
}
|
||||
for style in &mut deps.text_styles {
|
||||
let name = strip(style.name());
|
||||
style.name = name;
|
||||
}
|
||||
}
|
||||
for block in &mut deps.blocks {
|
||||
block.name = strip(&block.name);
|
||||
}
|
||||
let entities: Vec<EntityType> = edited
|
||||
.into_iter()
|
||||
.map(|mut entity| {
|
||||
let common = entity.common_mut();
|
||||
common.layer = strip(&common.layer);
|
||||
common.linetype = strip(&common.linetype);
|
||||
common.layer_handle = None;
|
||||
common.linetype_handle = None;
|
||||
common.owner_handle = Handle::NULL;
|
||||
match &mut entity {
|
||||
EntityType::Insert(insert) => insert.block_name = strip(&insert.block_name),
|
||||
EntityType::Text(text) => text.style = strip(&text.style),
|
||||
EntityType::MText(text) => text.style = strip(&text.style),
|
||||
_ => {}
|
||||
}
|
||||
entity
|
||||
})
|
||||
.collect();
|
||||
let model: Vec<Handle> = source
|
||||
.model_space_entities()
|
||||
.map(|e| e.common().handle)
|
||||
.collect();
|
||||
for h in model {
|
||||
source.remove_entity(h);
|
||||
}
|
||||
crate::app::command_driver::clipboard::paste_entities_kernel(
|
||||
&mut source,
|
||||
&entities,
|
||||
&deps,
|
||||
None,
|
||||
crate::app::command_driver::clipboard::PasteSpace::Model,
|
||||
);
|
||||
crate::io::save(&source, &path)?;
|
||||
let keys: rustc_hash::FxHashSet<Handle> = [session.br_handle].into_iter().collect();
|
||||
self.tabs[i].xref_stat_cache.0.remove(&session.br_handle.value());
|
||||
let _ = crate::io::xref::resolve_xrefs_for_keys(
|
||||
&mut self.tabs[i].scene.document,
|
||||
&base_dir,
|
||||
&keys,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Undo of REFCLOSE checks the reference out again; undo of REFEDIT ends
|
||||
/// the session (their redo does the reverse).
|
||||
pub(in crate::app) fn refedit_after_history_step(&mut self, i: usize, label: &str, undo: bool) {
|
||||
match (label, undo) {
|
||||
("REFCLOSE", true) | ("REFEDIT", false) => {
|
||||
let reopened = if undo {
|
||||
self.tabs[i].refedit_closed.pop()
|
||||
} else {
|
||||
self.tabs[i].refedit_reopen.pop()
|
||||
};
|
||||
if let Some(session) = reopened {
|
||||
let keep: rustc_hash::FxHashSet<Handle> = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.entities()
|
||||
.map(|e| e.common().handle)
|
||||
.filter(|h| session.in_working_set(*h))
|
||||
.collect();
|
||||
self.tabs[i].refedit_session = Some(session);
|
||||
self.tabs[i].scene.refedit_lock = true;
|
||||
self.tabs[i].scene.set_refedit_keep(Some(keep));
|
||||
}
|
||||
}
|
||||
("REFCLOSE", false) | ("REFEDIT", true) => {
|
||||
if let Some(session) = self.tabs[i].refedit_session.take() {
|
||||
if undo {
|
||||
self.tabs[i].refedit_reopen.push(session);
|
||||
} else {
|
||||
self.tabs[i].refedit_closed.push(session);
|
||||
}
|
||||
self.tabs[i].scene.refedit_lock = false;
|
||||
self.tabs[i].scene.set_refedit_keep(None);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -227,6 +227,10 @@ pub(super) struct DocumentTab {
|
||||
pub(super) paper_bg_color: Option<[f32; 4]>,
|
||||
/// Active REFEDIT session, if any.
|
||||
pub(super) refedit_session: Option<RefEditSession>,
|
||||
/// Sessions closed by REFCLOSE, reopened by undoing it.
|
||||
pub(super) refedit_closed: Vec<RefEditSession>,
|
||||
/// Sessions ended by undoing REFEDIT, reopened by redoing it.
|
||||
pub(super) refedit_reopen: Vec<RefEditSession>,
|
||||
/// COUNT mode, while it is on.
|
||||
pub(crate) count: Option<crate::ui::window::count_palette::CountMode>,
|
||||
/// Open BEDIT block tabs. Definitions are edited live; each tab owns its
|
||||
@@ -662,6 +666,8 @@ impl DocumentTab {
|
||||
bg_color: None,
|
||||
paper_bg_color: None,
|
||||
refedit_session: None,
|
||||
refedit_closed: Vec::new(),
|
||||
refedit_reopen: Vec::new(),
|
||||
count: None,
|
||||
block_edits: Vec::new(),
|
||||
active_block_edit: None,
|
||||
|
||||
@@ -1177,6 +1177,7 @@ impl OpenCADStudio {
|
||||
break;
|
||||
};
|
||||
last_label = snapshot.label().to_string();
|
||||
let step_label = last_label.clone();
|
||||
match snapshot {
|
||||
HistorySnapshot::Delta(mut d) => {
|
||||
// Symmetric: undo applies the before side, then the same
|
||||
@@ -1211,6 +1212,7 @@ impl OpenCADStudio {
|
||||
.push(HistorySnapshot::ObjectVisibility(v));
|
||||
}
|
||||
}
|
||||
self.refedit_after_history_step(i, &step_label, true);
|
||||
}
|
||||
self.finish_history_apply(i, had_full, layer_panel_changed, &changes);
|
||||
self.command_line
|
||||
@@ -1237,6 +1239,7 @@ impl OpenCADStudio {
|
||||
break;
|
||||
};
|
||||
last_label = snapshot.label().to_string();
|
||||
let step_label = last_label.clone();
|
||||
match snapshot {
|
||||
HistorySnapshot::Delta(mut d) => {
|
||||
layer_panel_changed |= d.structure.as_ref().is_some_and(|structure| {
|
||||
@@ -1268,6 +1271,7 @@ impl OpenCADStudio {
|
||||
.push(HistorySnapshot::ObjectVisibility(v));
|
||||
}
|
||||
}
|
||||
self.refedit_after_history_step(i, &step_label, false);
|
||||
}
|
||||
self.finish_history_apply(i, had_full, layer_panel_changed, &changes);
|
||||
self.command_line
|
||||
|
||||
@@ -638,6 +638,9 @@ pub struct UserSettings {
|
||||
/// BLOCKNAVIGATE (default ".").
|
||||
#[serde(default = "default_block_navigate")]
|
||||
pub block_navigate: String,
|
||||
/// XFADECTL: how far objects outside the REFEDIT working set fade (0–90, default 70).
|
||||
#[serde(default = "default_xfadectl")]
|
||||
pub xfadectl: u8,
|
||||
/// COUNTCOLOR (default 3).
|
||||
#[serde(default = "default_count_color")]
|
||||
pub count_color: i16,
|
||||
@@ -708,6 +711,10 @@ fn default_block_view() -> u8 {
|
||||
1
|
||||
}
|
||||
|
||||
fn default_xfadectl() -> u8 {
|
||||
70
|
||||
}
|
||||
|
||||
fn default_block_mru_list() -> u8 {
|
||||
50
|
||||
}
|
||||
@@ -910,6 +917,7 @@ impl Default for UserSettings {
|
||||
block_mru_list: 50,
|
||||
block_redefine_mode: 1,
|
||||
block_navigate: ".".to_string(),
|
||||
xfadectl: 70,
|
||||
count_color: 3,
|
||||
count_error_color: 1,
|
||||
count_service: true,
|
||||
|
||||
@@ -866,6 +866,7 @@ impl OpenCADStudio {
|
||||
block_mru_list: self.block_mru_list,
|
||||
block_redefine_mode: self.block_redefine_mode,
|
||||
block_navigate: self.block_navigate.clone(),
|
||||
xfadectl: crate::scene::cache::block_cache::refedit_fade_ctl() as u8,
|
||||
count_color: self.count_palette.color,
|
||||
count_error_color: self.count_palette.error_color,
|
||||
count_service: self.count_palette.service,
|
||||
@@ -1019,6 +1020,7 @@ impl OpenCADStudio {
|
||||
self.block_mru_list = s.block_mru_list.min(100);
|
||||
self.block_redefine_mode = s.block_redefine_mode.min(2);
|
||||
self.block_navigate = s.block_navigate.clone();
|
||||
crate::scene::cache::block_cache::set_refedit_fade_ctl(i32::from(s.xfadectl.min(90)));
|
||||
self.count_palette.color = s.count_color;
|
||||
self.count_palette.error_color = s.count_error_color;
|
||||
self.count_palette.service = s.count_service;
|
||||
|
||||
Reference in new issue
Block a user