fix(box): preserve construction semantics

This commit is contained in:
Hakan Seven committed 2026-08-28 20:44:14 +03:00
commit fc93efeed7
52 files changed
+6700 -1754

No files matched your search

+346 -102
View File
@@ -856,7 +856,7 @@ impl OpenCADStudio {
.infer_dimension_sources(handle);
self.tabs[i]
.scene
.attach_dimension_association(handle, sources.to_vec());
.attach_dimension_association(handle, sources);
}
}
self.tabs[i].dirty = true;
@@ -1269,7 +1269,7 @@ impl OpenCADStudio {
.infer_dimension_sources(handle);
self.tabs[i]
.scene
.attach_dimension_association(handle, sources.to_vec());
.attach_dimension_association(handle, sources);
}
}
self.tabs[i].dirty = true;
@@ -1287,6 +1287,8 @@ impl OpenCADStudio {
CmdResult::CommitDimension {
mut entity,
association,
preserve_base_style,
continue_command,
} => {
let label = self.history_label_from_active_cmd(i, "DIMENSION");
let association_mode = self.tabs[i]
@@ -1300,6 +1302,17 @@ impl OpenCADStudio {
acadrust::entities::Dimension::Ordinate(_)
)
);
let inherited_dimension = if preserve_base_style {
match &entity {
acadrust::EntityType::Dimension(dimension) => Some((
dimension.base().common.layer.clone(),
dimension.base().style_name.clone(),
)),
_ => None,
}
} else {
None
};
let pending = if association_mode == 0 {
let layer = self.tabs[i].active_layer.clone();
if layer != "0" || entity.as_entity().layer().is_empty() {
@@ -1330,6 +1343,12 @@ impl OpenCADStudio {
&self.tabs[i].scene.document,
&mut entity,
);
if let (Some((layer, style_name)), acadrust::EntityType::Dimension(dimension)) =
(inherited_dimension, &mut entity)
{
dimension.base_mut().common.layer = layer;
dimension.base_mut().style_name = style_name;
}
let pieces = crate::modules::draw::modify::explode::explode_entity(
&entity,
&self.tabs[i].scene.document,
@@ -1345,7 +1364,10 @@ impl OpenCADStudio {
} else {
let delta_safe = self.delta_add_safe(i, &entity);
let pending = self.begin_undo(i, label, 1, delta_safe);
if let Some(handle) = self.commit_entity_handle(entity) {
if let Some(handle) = self.commit_entity_handle_with_dimension_policy(
entity,
preserve_base_style,
) {
if association_mode == 2 {
let mut changes = vec![
(handle, crate::scene::ChangeKind::Modified),
@@ -1357,8 +1379,6 @@ impl OpenCADStudio {
self.tabs[i]
.scene
.infer_dimension_sources(handle)
.into_iter()
.collect()
},
|source| {
if single_source_dimension {
@@ -1393,12 +1413,195 @@ impl OpenCADStudio {
};
self.tabs[i].dirty = true;
self.tabs[i].scene.clear_preview_wire();
self.tabs[i].active_cmd = None;
self.tabs[i].snap_result = None;
self.restore_pre_cmd_tangent();
if let Some(pd) = pending {
self.commit_undo_delta(i, pd);
}
if continue_command {
if let Some(prompt) = self.tabs[i]
.active_cmd
.as_ref()
.map(|cmd| cmd.prompt())
{
self.command_line.push_info(&prompt);
}
} else {
self.tabs[i].active_cmd = None;
self.restore_pre_cmd_tangent();
}
}
CmdResult::CommitDimensionsAndExit(dimensions) => {
let association_mode = self.tabs[i]
.scene
.document
.header
.dimension_associativity;
let mut made = 0;
let pending = if association_mode == 0 {
let mut pieces = Vec::new();
for (mut entity, _) in dimensions {
let layer = self.tabs[i].active_layer.clone();
if layer != "0" || entity.as_entity().layer().is_empty() {
entity.as_entity_mut().set_layer(layer);
}
crate::scene::view::dispatch::apply_color(
&mut entity,
self.ribbon.active_color,
);
crate::scene::view::dispatch::apply_common_prop(
&mut entity,
"linetype",
&self.ribbon.active_linetype.clone(),
);
crate::scene::view::dispatch::apply_line_weight(
&mut entity,
self.ribbon.active_lineweight,
);
let celtscale = self.tabs[i]
.scene
.document
.header
.current_entity_linetype_scale;
if (celtscale - 1.0).abs() > 1e-9 && celtscale.abs() > 1e-9 {
entity.common_mut().linetype_scale = celtscale;
}
crate::scene::creation_style::apply_current_creation_styles(
&self.tabs[i].scene.document,
&mut entity,
);
let exploded = crate::modules::draw::modify::explode::explode_entity(
&entity,
&self.tabs[i].scene.document,
);
if !exploded.is_empty() {
made += 1;
}
pieces.extend(exploded);
}
let delta_safe = pieces
.iter()
.all(|piece| self.delta_add_safe(i, piece));
let pending = self.begin_undo(i, "QDIM", pieces.len(), delta_safe);
for piece in pieces {
self.tabs[i].scene.add_entity(piece);
}
pending
} else {
let delta_safe = dimensions
.iter()
.all(|(entity, _)| self.delta_add_safe(i, entity));
let pending = self.begin_undo(i, "QDIM", dimensions.len(), delta_safe);
for (entity, association) in dimensions {
let Some(handle) = self.commit_entity_handle(entity) else {
continue;
};
made += 1;
if association_mode != 2 {
continue;
}
let sources = match association {
crate::command::DimensionAssociationInput::Infer(source) => {
source.map_or_else(
|| {
self.tabs[i]
.scene
.infer_dimension_sources(handle)
.into_iter()
.map(|source| {
source.map(
crate::command::DimensionAssociationSource::inferred,
)
})
.collect()
},
|source| {
vec![Some(
crate::command::DimensionAssociationSource::inferred(
source,
),
)]
},
)
}
crate::command::DimensionAssociationInput::Explicit(sources) => {
sources
}
};
self.tabs[i]
.scene
.attach_dimension_association_sources(handle, sources.clone());
let mut changes = vec![(handle, crate::scene::ChangeKind::Modified)];
changes.extend(sources.into_iter().flatten().map(|source| {
(source.handle, crate::scene::ChangeKind::Modified)
}));
changes.sort_by_key(|(handle, _)| handle.value());
changes.dedup_by_key(|(handle, _)| handle.value());
self.tabs[i].scene.bump_entities(&changes);
}
pending
};
if made > 0 {
self.tabs[i].dirty = true;
}
self.tabs[i].scene.clear_preview_wire();
self.tabs[i].active_cmd = None;
self.tabs[i].snap_result = None;
self.restore_pre_cmd_tangent();
self.command_line
.push_output(crate::tf!("QDIM {made} dimensions created.").as_ref());
if let Some(pd) = pending {
self.commit_undo_delta(i, pd);
}
}
CmdResult::SetQuickDimensionSnapPriority(priority) => {
self.quick_dimension_snap_priority = priority.min(1);
self.persist_settings_if_changed();
let prompt = self.tabs[i].active_cmd.as_ref().map(|command| command.prompt());
if let Some(prompt) = prompt {
self.command_line.push_info(&prompt);
}
}
CmdResult::AlignMLeaders {
mut handles,
from,
to,
} => {
handles.retain(|handle| {
!self.tabs[i].scene.is_layer_locked(*handle)
&& matches!(
self.tabs[i].scene.document.get_entity(*handle),
Some(acadrust::EntityType::MultiLeader(_))
)
});
if !handles.is_empty() {
self.push_undo_snapshot(i, "MLEADERALIGN");
if apply_mleader_align(&mut self.tabs[i].scene, &handles, from, to) {
self.tabs[i].dirty = true;
self.command_line.push_output(
crate::t!("MLEADERALIGN Leaders aligned.").as_ref(),
);
}
}
self.tabs[i].scene.clear_preview_wire();
self.tabs[i].active_cmd = None;
self.tabs[i].snap_result = None;
self.restore_pre_cmd_tangent();
}
CmdResult::CollectMLeaders { handles, point } => {
let compatible = compatible_mleader_collect_handles(&self.tabs[i].scene, &handles);
if compatible.len() >= 2 {
self.push_undo_snapshot(i, "MLEADERCOLLECT");
if apply_mleader_collect(&mut self.tabs[i].scene, &compatible, point) {
self.tabs[i].dirty = true;
self.command_line.push_output(
crate::t!("MLEADERCOLLECT Leaders collected.").as_ref(),
);
}
}
self.tabs[i].scene.clear_preview_wire();
self.tabs[i].active_cmd = None;
self.tabs[i].snap_result = None;
self.restore_pre_cmd_tangent();
}
CmdResult::CommitSolid {
entity,
@@ -1859,30 +2062,6 @@ impl OpenCADStudio {
self.tabs[i].snap_result = None;
return Task::none();
}
if layer.starts_with("__MLEADERALIGN__") {
if let Some(encoded) = layer.strip_prefix("__MLEADERALIGN__") {
apply_mleader_align(&mut self.tabs[i].scene, encoded);
}
self.push_undo_snapshot(i, "MLEADERALIGN");
self.command_line
.push_output(crate::t!("MLEADERALIGN Leaders aligned.").as_ref());
self.tabs[i].dirty = true;
self.tabs[i].active_cmd = None;
self.tabs[i].snap_result = None;
return Task::none();
}
if layer.starts_with("__MLEADERCOLLECT__") {
if let Some(encoded) = layer.strip_prefix("__MLEADERCOLLECT__") {
apply_mleader_collect(&mut self.tabs[i].scene, encoded);
}
self.push_undo_snapshot(i, "MLEADERCOLLECT");
self.command_line
.push_output(crate::t!("MLEADERCOLLECT Leaders collected.").as_ref());
self.tabs[i].dirty = true;
self.tabs[i].active_cmd = None;
self.tabs[i].snap_result = None;
return Task::none();
}
}
}
@@ -2752,6 +2931,9 @@ impl OpenCADStudio {
.map(|e| apply_pedit(e, &op))
.unwrap_or(false);
if changed {
if let Some(command) = self.tabs[i].active_cmd.as_mut() {
command.on_pedit_applied();
}
self.tabs[i].dirty = true;
// Repaint immediately (wide-band fill included).
self.tabs[i].scene.refresh_fill_model(handle);
@@ -4437,92 +4619,154 @@ fn apply_dimspace(scene: &mut crate::scene::Scene, encoded: &str) {
}
}
// ── MLEADERALIGN helper ───────────────────────────────────────────────────────
fn apply_mleader_align(
scene: &mut crate::scene::Scene,
handles: &[acadrust::Handle],
from: glam::DVec3,
to: glam::DVec3,
) -> bool {
use cadkernel::geom2d::{closest_point, Curve, Vec2, XLine};
/// Parse `h1,h2,...;fx,fz;tx,tz` and align multileader content points along the direction.
fn apply_mleader_align(scene: &mut crate::scene::Scene, encoded: &str) {
// Format: "<h1>,<h2>,...;<fx>,<fz>;<tx>,<tz>"
let parts: Vec<&str> = encoded.splitn(3, ';').collect();
if parts.len() < 3 {
return;
}
let handles: Vec<acadrust::Handle> = parts[0]
.split(',')
.filter_map(|s| s.parse::<u64>().ok().map(acadrust::Handle::from))
.collect();
let from_parts: Vec<f64> = parts[1].split(',').filter_map(|s| s.parse().ok()).collect();
let to_parts: Vec<f64> = parts[2].split(',').filter_map(|s| s.parse().ok()).collect();
if from_parts.len() < 2 || to_parts.len() < 2 || handles.is_empty() {
return;
}
let fx = from_parts[0];
let fz = from_parts[1];
let tx = to_parts[0];
let tz = to_parts[1];
let dx = tx - fx;
let dz = tz - fz;
let len = (dx * dx + dz * dz).sqrt();
if len < 1e-9 {
return;
}
// Project each multileader's content point onto the alignment line, then
// snap it to the line (preserve perpendicular offset from line is discarded;
// align along direction through `from`).
for h in handles {
if let Some(acadrust::EntityType::MultiLeader(ml)) = scene.document.get_entity_mut(h) {
let cp = &mut ml.context.content_base_point;
// Project onto line from_pt + t * dir: keep t component, set perpendicular = 0
let rel_x = cp.x - fx;
let rel_z = cp.z - fz;
let t = (rel_x * (dx / len) + rel_z * (dz / len)) / len;
let t = t.clamp(0.0, 1.0);
cp.x = fx + t * dx;
cp.z = fz + t * dz;
let Some(direction) = Vec2::new(to.x - from.x, to.y - from.y).normalize() else {
return false;
};
let line = Curve::XLine(XLine {
base: [from.x, from.y],
direction: direction.into(),
});
let mut changed = Vec::new();
for &handle in handles {
if let Some(acadrust::EntityType::MultiLeader(ml)) =
scene.document.get_entity_mut(handle)
{
let old = ml.context.content_base_point;
let projected = closest_point(&line, [old.x, old.y]).point;
let new_x = projected[0];
let new_y = projected[1];
let shift_x = new_x - old.x;
let shift_y = new_y - old.y;
if shift_x.abs() <= 1.0e-12 && shift_y.abs() <= 1.0e-12 {
continue;
}
ml.context.content_base_point.x = new_x;
ml.context.content_base_point.y = new_y;
ml.context.text_location.x += shift_x;
ml.context.text_location.y += shift_y;
ml.context.block_content_location.x += shift_x;
ml.context.block_content_location.y += shift_y;
for root in &mut ml.context.leader_roots {
root.connection_point.x += shift_x;
root.connection_point.y += shift_y;
}
changed.push((handle, crate::scene::ChangeKind::Modified));
}
}
if !changed.is_empty() {
scene.bump_entities(&changed);
}
!changed.is_empty()
}
// ── MLEADERCOLLECT helper ─────────────────────────────────────────────────────
/// Parse `h1,h2,...;px,pz` — merge all selected multileaders into the first one at position.
fn apply_mleader_collect(scene: &mut crate::scene::Scene, encoded: &str) {
let parts: Vec<&str> = encoded.splitn(2, ';').collect();
if parts.len() < 2 {
return;
}
let handles: Vec<acadrust::Handle> = parts[0]
.split(',')
.filter_map(|s| s.parse::<u64>().ok().map(acadrust::Handle::from))
.collect();
let pos_parts: Vec<f64> = parts[1].split(',').filter_map(|s| s.parse().ok()).collect();
if handles.len() < 2 || pos_parts.len() < 2 {
return;
}
let px = pos_parts[0];
let pz = pos_parts[1];
// Collect all leader roots from the secondary multileaders.
let mut extra_roots: Vec<acadrust::entities::LeaderRoot> = Vec::new();
for &h in &handles[1..] {
if let Some(acadrust::EntityType::MultiLeader(ml)) = scene.document.get_entity(h) {
extra_roots.extend(ml.context.leader_roots.iter().cloned());
fn compatible_mleader_collect_handles(
scene: &crate::scene::Scene,
handles: &[acadrust::Handle],
) -> Vec<acadrust::Handle> {
let Some((base_block, base_style)) = handles.iter().find_map(|handle| {
if scene.is_layer_locked(*handle) {
return None;
}
let acadrust::EntityType::MultiLeader(leader) =
scene.document.get_entity(*handle)?
else {
return None;
};
(leader.content_type == acadrust::entities::LeaderContentType::Block)
.then_some((leader.block_content_handle, leader.style_handle))
}) else {
return Vec::new();
};
let mut compatible = Vec::new();
for handle in handles.iter().copied() {
if compatible.contains(&handle) {
continue;
}
if !scene.is_layer_locked(handle)
&& matches!(
scene.document.get_entity(handle),
Some(acadrust::EntityType::MultiLeader(leader))
if leader.content_type == acadrust::entities::LeaderContentType::Block
&& leader.block_content_handle == base_block
&& leader.style_handle == base_style
)
{
compatible.push(handle);
}
}
compatible
}
fn apply_mleader_collect(
scene: &mut crate::scene::Scene,
handles: &[acadrust::Handle],
point: glam::DVec3,
) -> bool {
if handles.len() < 2 {
return false;
}
let px = point.x;
let py = point.y;
let Some(acadrust::EntityType::MultiLeader(base)) =
scene.document.get_entity(handles[0])
else {
return false;
};
let base_block = base.block_content_handle;
let base_style = base.style_handle;
let mut extra_roots: Vec<acadrust::entities::LeaderRoot> = Vec::new();
let mut merged_handles = Vec::new();
for &h in &handles[1..] {
if let Some(acadrust::EntityType::MultiLeader(ml)) = scene.document.get_entity(h) {
if ml.content_type == acadrust::entities::LeaderContentType::Block
&& ml.block_content_handle == base_block
&& ml.style_handle == base_style
{
let shift_x = px - ml.context.content_base_point.x;
let shift_y = py - ml.context.content_base_point.y;
extra_roots.extend(ml.context.leader_roots.iter().cloned().map(|mut root| {
root.connection_point.x += shift_x;
root.connection_point.y += shift_y;
root
}));
merged_handles.push(h);
}
}
}
if merged_handles.is_empty() {
return false;
}
// Add collected roots to the first multileader and move its content point.
if let Some(acadrust::EntityType::MultiLeader(ml)) = scene.document.get_entity_mut(handles[0]) {
let shift_x = px - ml.context.content_base_point.x;
let shift_y = py - ml.context.content_base_point.y;
for root in &mut ml.context.leader_roots {
root.connection_point.x += shift_x;
root.connection_point.y += shift_y;
}
ml.context.content_base_point.x = px;
ml.context.content_base_point.z = pz;
ml.context.content_base_point.y = py;
ml.context.block_content_location.x = px;
ml.context.block_content_location.y = py;
ml.context.text_location.x = px;
ml.context.text_location.y = py;
for root in extra_roots {
ml.context.leader_roots.push(root);
}
}
// Erase the secondary multileaders.
scene.erase_entities(&handles[1..]);
scene.erase_entities(&merged_handles);
scene.bump_entities(&[(handles[0], crate::scene::ChangeKind::Modified)]);
true
}
/// MATCHPROP special properties (#281): copy every STYLE-affecting field from
+87 -230
View File
@@ -604,9 +604,31 @@ impl OpenCADStudio {
_ => None,
});
let multiplier = self.tabs[i].scene.creation_annotation_multiplier();
let block_sources = self.tabs[i]
.scene
.document
.block_records
.iter()
.filter(|block| !block.name.is_empty() && !block.name.starts_with('*'))
.map(|block| (block.name.clone(), block.handle))
.collect();
let layers = self.tabs[i]
.scene
.document
.layers
.iter()
.map(|layer| layer.name.clone())
.collect();
let text_styles = self.tabs[i]
.scene
.document
.text_styles
.iter()
.map(|style| (style.name.clone(), style.handle))
.collect();
let new_cmd = style.map_or_else(MLeaderCommand::new, |style| {
MLeaderCommand::with_style(style, multiplier)
});
}).with_drawing_resources(block_sources, layers, text_styles);
self.command_line.push_info(&new_cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
}
@@ -646,138 +668,87 @@ impl OpenCADStudio {
"DIMCONTINUE" => {
use crate::modules::annotate::dim_continue::DimContinueCommand;
let cmd = if let Some((p1, p2, dp, rot, trot)) =
find_last_linear_dim(&self.tabs[i].scene)
{
DimContinueCommand::from_base(p1, p2, dp, rot, trot)
} else {
DimContinueCommand::new()
};
let scene = &self.tabs[i].scene;
let recent = scene
.last_created_dimension
.filter(|handle| scene.entity_belongs_to_active_space(*handle))
.and_then(|handle| scene.document.get_entity(handle))
.cloned();
let cmd = DimContinueCommand::new(
recent,
self.dimension_continue_mode == 1,
);
self.command_line.push_info(&cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(cmd));
}
"DIMBASELINE" => {
use crate::modules::annotate::dim_baseline::DimBaselineCommand;
let cmd = if let Some((p1, p2, dp, rot, trot)) =
find_last_linear_dim(&self.tabs[i].scene)
{
let doc = &self.tabs[i].scene.document;
let dimdli = doc
.dim_styles
.iter()
.find(|s| {
s.name
.eq_ignore_ascii_case(&doc.header.current_dimstyle_name)
})
.map(|s| s.dimdli as f32)
.unwrap_or(1.5);
DimBaselineCommand::from_base(p1, p2, dp, rot, trot, dimdli)
let scene = &self.tabs[i].scene;
let recent = scene
.last_created_dimension
.filter(|handle| scene.entity_belongs_to_active_space(*handle))
.and_then(|handle| scene.document.get_entity(handle))
.cloned();
let dimdli_by_style = scene
.document
.dim_styles
.iter()
.map(|style| (style.name.to_ascii_lowercase(), style.dimdli))
.collect();
let fallback_dimdli = if scene.document.header.measurement == 1 {
3.75
} else {
DimBaselineCommand::new()
0.38
};
let current_style_name = scene.document.header.current_dimstyle_name.clone();
let cmd = DimBaselineCommand::new(
recent,
dimdli_by_style,
current_style_name,
fallback_dimdli,
self.dimension_continue_mode == 1,
);
self.command_line.push_info(&cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(cmd));
}
"QDIM" => {
use crate::modules::annotate::qdim::QdimCommand;
let cmd = QdimCommand::new();
self.command_line.push_info(&cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(cmd));
}
// QDIM second stage: the front-end relaunched with the picked
// dimension-line point and the gathered geometry now selected. Build
// a continuous chain of linear dimensions across the endpoints.
cmd if cmd.starts_with("QDIM_PLACE ") => {
let nums: Vec<f64> = cmd
.split_whitespace()
.skip(1)
.filter_map(|s| s.parse().ok())
.collect();
if nums.len() < 3 {
return Some(Task::none());
}
let plane = if self.tabs[i].editing_model_space() {
self.tabs[i].ucs_xform().working_plane()
} else {
crate::command::WorkingPlane::default()
};
let place = plane.to_local(glam::DVec3::new(nums[0], nums[1], nums[2]));
let handles: Vec<acadrust::Handle> = self.tabs[i]
let document = &self.tabs[i].scene.document;
let dim_spacing = document
.dim_styles
.iter()
.find(|style| {
style
.name
.eq_ignore_ascii_case(&document.header.current_dimstyle_name)
})
.map(|style| style.dimdli)
.unwrap_or_else(|| {
if document.header.measurement == 1 {
3.75
} else {
0.38
}
});
let selection = self.tabs[i]
.scene
.selected_entities()
.iter()
.map(|(h, _)| *h)
.into_iter()
.map(|(handle, entity)| crate::command::SelectionEntity {
handle,
entity: entity.clone(),
surface_area: None,
})
.collect();
let mut pts: Vec<glam::DVec3> = Vec::new();
for h in &handles {
if let Some(e) = self.tabs[i].scene.document.get_entity(*h) {
qdim_collect_points(e, &mut pts);
}
}
for point in &mut pts {
*point = plane.to_local(*point);
}
if pts.len() < 2 {
self.command_line
.push_error(crate::t!("QDIM: no dimensionable endpoints in the selection.").as_ref());
return Some(Task::none());
}
// Choose the dimension axis from the points' spread: a wider X
// span dimensions horizontally (ordered by X), else vertically.
let (minx, maxx) = pts
.iter()
.fold((f64::MAX, f64::MIN), |(a, b), p| (a.min(p.x), b.max(p.x)));
let (miny, maxy) = pts
.iter()
.fold((f64::MAX, f64::MIN), |(a, b), p| (a.min(p.y), b.max(p.y)));
let horizontal = (maxx - minx) >= (maxy - miny);
if horizontal {
pts.sort_by(|a, b| a.x.total_cmp(&b.x));
pts.dedup_by(|a, b| (a.x - b.x).abs() < 1e-4);
} else {
pts.sort_by(|a, b| a.y.total_cmp(&b.y));
pts.dedup_by(|a, b| (a.y - b.y).abs() < 1e-4);
}
if pts.len() < 2 {
self.command_line
.push_error(crate::t!("QDIM: endpoints collapse to a single position.").as_ref());
return Some(Task::none());
}
self.push_undo_snapshot(i, "QDIM");
let v = |p: glam::DVec3| {
acadrust::types::Vector3::new(p.x, p.y, p.z)
};
let mut made = 0usize;
for w in pts.windows(2) {
let (p1, p2) = (w[0], w[1]);
let mut dim = acadrust::entities::DimensionLinear::new(v(p1), v(p2));
dim.rotation = if horizontal {
0.0
} else {
std::f64::consts::FRAC_PI_2
};
// Dim line passes through the picked perpendicular position.
let def = if horizontal {
glam::DVec3::new((p1.x + p2.x) * 0.5, place.y, 0.0)
} else {
glam::DVec3::new(place.x, (p1.y + p2.y) * 0.5, 0.0)
};
dim.definition_point = v(def);
dim.base.definition_point = v(def);
dim.base.actual_measurement = dim.measurement();
let entity = acadrust::EntityType::Dimension(
acadrust::entities::Dimension::Linear(dim),
);
self.commit_entity(plane.place_entity(entity));
made += 1;
}
self.tabs[i].dirty = true;
self.command_line
.push_output(crate::tf!("QDIM {made} dimensions created.").as_ref());
return Some(Task::none());
let cmd = QdimCommand::new(
selection,
dim_spacing,
self.quick_dimension_snap_priority,
);
self.command_line.push_info(&cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(cmd));
}
"DIMEDIT" => {
@@ -1376,120 +1347,6 @@ impl OpenCADStudio {
}
}
/// Find the last placed linear or aligned dimension in the document.
/// Returns `(first_point, second_point, definition_point, rotation_rad)` in world-space.
fn find_last_linear_dim(
scene: &crate::scene::Scene,
) -> Option<(glam::Vec3, glam::Vec3, glam::Vec3, f64, f64)> {
use acadrust::entities::Dimension;
let mut best_handle: u64 = 0;
let mut result: Option<(glam::Vec3, glam::Vec3, glam::Vec3, f64, f64)> = None;
for entity in scene.document.entities() {
if let acadrust::EntityType::Dimension(dim) = entity {
let h = entity.common().handle.value();
if h <= best_handle {
continue;
}
let item = match dim {
Dimension::Linear(d) => {
let p1 = glam::Vec3::new(
d.first_point.x as f32,
d.first_point.y as f32,
d.first_point.z as f32,
);
let p2 = glam::Vec3::new(
d.second_point.x as f32,
d.second_point.y as f32,
d.second_point.z as f32,
);
let dp = glam::Vec3::new(
d.base.definition_point.x as f32,
d.base.definition_point.y as f32,
d.base.definition_point.z as f32,
);
Some((p1, p2, dp, d.rotation, d.base.text_rotation))
}
Dimension::Aligned(d) => {
let p1 = glam::Vec3::new(
d.first_point.x as f32,
d.first_point.y as f32,
d.first_point.z as f32,
);
let p2 = glam::Vec3::new(
d.second_point.x as f32,
d.second_point.y as f32,
d.second_point.z as f32,
);
let dp = glam::Vec3::new(
d.base.definition_point.x as f32,
d.base.definition_point.y as f32,
d.base.definition_point.z as f32,
);
let dx = (d.second_point.x - d.first_point.x) as f32;
let dy = (d.second_point.y - d.first_point.y) as f32;
let rot = dy.atan2(dx) as f64;
Some((p1, p2, dp, rot, d.base.text_rotation))
}
_ => None,
};
if let Some(data) = item {
best_handle = h;
result = Some(data);
}
}
}
result
}
/// Collect candidate dimension endpoints from an entity for QDIM — line ends,
/// polyline vertices, arc endpoints — in world space.
fn qdim_collect_points(e: &acadrust::EntityType, out: &mut Vec<glam::DVec3>) {
use acadrust::EntityType as ET;
let p = |v: &acadrust::types::Vector3| glam::DVec3::new(v.x, v.y, v.z);
let ocs = |point: (f64, f64, f64), normal: acadrust::types::Vector3| {
let world = crate::scene::view::transform::ocs_point_to_wcs(
point,
(normal.x, normal.y, normal.z),
);
glam::DVec3::new(world.0, world.1, world.2)
};
match e {
ET::Line(l) => {
out.push(p(&l.start));
out.push(p(&l.end));
}
ET::LwPolyline(pl) => {
for v in &pl.vertices {
out.push(ocs((v.location.x, v.location.y, pl.elevation), pl.normal));
}
}
ET::Polyline2D(pl) => {
for v in &pl.vertices {
out.push(ocs((v.location.x, v.location.y, pl.elevation), pl.normal));
}
}
ET::Polyline(pl) => {
for v in &pl.vertices {
out.push(p(&v.location));
}
}
ET::Arc(a) => {
for &ang in &[a.start_angle, a.end_angle] {
out.push(ocs(
(
a.center.x + a.radius * ang.cos(),
a.center.y + a.radius * ang.sin(),
a.center.z,
),
a.normal,
));
}
}
_ => {}
}
}
// ── TCASE helpers ──────────────────────────────────────────────────────────
fn sentence_case(s: &str) -> String {
let mut c = s.chars();
+32
View File
@@ -454,6 +454,38 @@ impl OpenCADStudio {
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
}
"3DMESH" => {
use crate::modules::draw::draw::mesh3d::Mesh3dCommand;
let new_cmd = Mesh3dCommand::new();
self.command_line.push_info(&new_cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
}
"3DFACE" => {
use crate::modules::draw::draw::face3d::Face3dCommand;
let new_cmd = Face3dCommand::new();
self.command_line.push_info(&new_cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
}
"EDGE" => {
use crate::modules::draw::draw::face3d::FaceEdgeCommand;
let faces = self.tabs[i]
.scene
.document
.entities()
.filter_map(|entity| match entity {
acadrust::EntityType::Face3D(face) => {
Some((face.common.handle, face.clone()))
}
_ => None,
})
.collect();
let new_cmd = FaceEdgeCommand::new(faces);
self.command_line.push_info(&new_cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
}
// 2D filled solid. Reached via SO / SOLID2D — the bare SOLID verb is
// currently the shaded-display toggle (token collision tracked).
"SOLID" | "SOLID2D" => {
+13
View File
@@ -262,6 +262,7 @@ impl OpenCADStudio {
PeditTarget {
is_poly: true,
convertible: false,
mesh_size: None,
},
)),
acadrust::EntityType::Line(_) | acadrust::EntityType::Arc(_) => {
@@ -270,9 +271,21 @@ impl OpenCADStudio {
PeditTarget {
is_poly: false,
convertible: true,
mesh_size: None,
},
))
}
acadrust::EntityType::PolygonMesh(mesh) => Some((
h,
PeditTarget {
is_poly: true,
convertible: false,
mesh_size: Some((
mesh.m_vertex_count.max(0) as usize,
mesh.n_vertex_count.max(0) as usize,
)),
},
)),
_ => None,
}
})
+24 -1
View File
@@ -931,6 +931,7 @@ impl OpenCADStudio {
| "CENTERCROSSSIZE"
| "CENTERCROSSGAP"
| "CENTERMARKEXE"
| "DIMCONTINUEMODE"
) =>
{
return self.dispatch_styleprops(&format!("SETVAR {cmd}"), i);
@@ -953,7 +954,7 @@ impl OpenCADStudio {
let value = it.next().map(|s| s.trim().to_string());
if name.is_empty() || name == "?" {
self.command_line.push_info(
"SETVAR: LTSCALE CELTSCALE PDMODE PDSIZE TEXTSIZE ORTHOMODE FILLMODE MIRRTEXT FRAME IMAGEFRAME PDFFRAME WIPEOUTFRAME XCLIPFRAME POINTCLOUDCLIPFRAME ZOOMWHEEL ZOOMFACTOR CURSORSIZE PICKBOX CURSORTYPE SNAPANG TEXTFILL CLIPROMPTLINES ATTREQ ATTDIA DIMASSOC ANGBASE ANGDIR SKETCHINC SKPOLY SKTOLERANCE DONUTID DONUTOD CENTEREXE CENTERLAYER CENTERLTYPE CENTERLTSCALE CENTERLTYPEFILE CENTERCROSSSIZE CENTERCROSSGAP CENTERMARKEXE | CLAYER CELTYPE TEXTSTYLE (read-only)",
"SETVAR: LTSCALE CELTSCALE PDMODE PDSIZE TEXTSIZE ORTHOMODE FILLMODE MIRRTEXT FRAME IMAGEFRAME PDFFRAME WIPEOUTFRAME XCLIPFRAME POINTCLOUDCLIPFRAME ZOOMWHEEL ZOOMFACTOR CURSORSIZE PICKBOX CURSORTYPE SNAPANG TEXTFILL CLIPROMPTLINES ATTREQ ATTDIA DIMASSOC DIMCONTINUEMODE ANGBASE ANGDIR SKETCHINC SKPOLY SKTOLERANCE DONUTID DONUTOD CENTEREXE CENTERLAYER CENTERLTYPE CENTERLTSCALE CENTERLTYPEFILE CENTERCROSSSIZE CENTERCROSSGAP CENTERMARKEXE | CLAYER CELTYPE TEXTSTYLE (read-only)",
);
} else {
let frame_kind = crate::scene::frame::kind_for_name(&name);
@@ -1069,6 +1070,28 @@ impl OpenCADStudio {
}
return Some(self.finish_dispatch(cmd));
}
if name == "DIMCONTINUEMODE" {
let current = self.dimension_continue_mode;
if let Some(value) = &value {
match value.parse::<i16>() {
Ok(mode @ 0..=1) => {
self.dimension_continue_mode = mode;
self.persist_settings_if_changed();
self.command_line
.push_output(&format!("DIMCONTINUEMODE = {mode}"));
}
_ => self.command_line.push_error(
"SETVAR: DIMCONTINUEMODE requires 0 or 1.",
),
}
} else {
self.command_line.push_output(crate::tf!(
"Enter new value for {name} <{current}>:"
).as_ref());
self.pending_setvar = Some(name.clone());
}
return Some(self.finish_dispatch(cmd));
}
if matches!(name.as_str(), "DONUTID" | "DONUTOD") {
let current = if name == "DONUTID" {
crate::modules::draw::defaults::get_donut_inner_diameter()
+11
View File
@@ -465,6 +465,10 @@ pub(super) struct OpenCADStudio {
options_tab: crate::ui::window::options::OptionsTab,
/// Controls whether the TEXTEDIT command repeats automatically (0 = Multiple, 1 = Single).
pub texteditmode: bool,
/// QDIM extension-origin priority: 0 = endpoints, 1 = intersections.
pub quick_dimension_snap_priority: u8,
/// Creation-style policy used by continuing and baseline dimensions.
pub dimension_continue_mode: i16,
/// When true (default), saving over an existing file first writes a `.bak`
/// copy of it for recovery (#205). Toggle with the ISAVEBAK command.
pub backup_on_save: bool,
@@ -2398,6 +2402,11 @@ pub enum Message {
PropColorChanged(AcadColor),
/// User selected a lineweight from the Properties pick_list.
PropLwChanged(LineWeight),
/// User selected an object-specific lineweight override.
PropFieldLwChanged {
field: &'static str,
value: LineWeight,
},
/// User selected a linetype from the linetype pick_list.
PropLinetypeChanged(String),
/// User toggled a boolean property (e.g. Invisible).
@@ -3210,6 +3219,8 @@ impl OpenCADStudio {
dyn_input: true,
options_tab: crate::ui::window::options::OptionsTab::General,
texteditmode: false,
quick_dimension_snap_priority: 0,
dimension_continue_mode: 1,
backup_on_save: true,
file_assoc_enabled: true,
savetime_min: 10,
+151 -20
View File
@@ -121,7 +121,14 @@ impl OpenCADStudio {
.and_then(|entity| match entity {
acadrust::EntityType::LwPolyline(polyline) => Some(polyline.vertices.len()),
acadrust::EntityType::Polyline2D(polyline) => Some(polyline.vertices.len()),
acadrust::EntityType::PolygonMesh(mesh) => Some(mesh.vertices.len()),
acadrust::EntityType::Polyline3D(polyline) => Some(
crate::entities::polyline::polyline3d_control_vertex_count(polyline),
),
acadrust::EntityType::Leader(leader) => Some(leader.vertices.len()),
acadrust::EntityType::Face3D(face) => {
Some(if face.is_triangle() { 3 } else { 4 })
}
acadrust::EntityType::Spline(spline) => {
Some(if crate::entities::spline::shows_fit_points(spline) {
spline.fit_points.len()
@@ -860,6 +867,12 @@ impl OpenCADStudio {
.map(|b| b.name.clone()),
)
.collect();
let block_names: Vec<String> = doc
.block_records
.iter()
.map(|block| block.name.clone())
.filter(|name| !name.is_empty() && !name.starts_with('*'))
.collect();
let tstyle_names = text_style_names.clone();
// Currently selected names.
let cur_style = ml
@@ -893,6 +906,15 @@ impl OpenCADStudio {
doc.line_types.iter().find(|l| l.handle == h).map(|l| l.name.clone())
})
.unwrap_or_else(|| "ByBlock".to_string());
let cur_block = ml
.block_content_handle
.and_then(|handle| {
doc.block_records
.iter()
.find(|block| block.handle == handle)
.map(|block| block.name.clone())
})
.unwrap_or_else(|| "(none)".to_string());
let mut set_choice =
|field: &str, selected: String, options: Vec<String>| {
for section in sections.iter_mut() {
@@ -911,6 +933,9 @@ impl OpenCADStudio {
set_choice("text_style_handle", cur_tstyle, tstyle_names);
set_choice("arrowhead_handle", cur_arrow, arrow_names);
set_choice("line_type_handle", cur_ltype, ltype_names);
if !block_names.is_empty() {
set_choice("block_content_handle", cur_block, block_names);
}
}
// Inject viewport-only properties that require doc access.
@@ -1018,6 +1043,26 @@ impl OpenCADStudio {
}
}
// Legacy leaders omit Handle and report annotation association.
if let acadrust::EntityType::Leader(leader) = entity {
if let Some(general) = sections.first_mut() {
general.props.retain(|property| property.field != "handle");
let associative = !leader.annotation_handle.is_null()
&& self.tabs[i]
.scene
.document
.get_entity(leader.annotation_handle)
.is_some();
general.props.push(crate::scene::model::object::Property {
label: t!("Associative").into_owned(),
field: "associative",
value: crate::scene::model::object::PropValue::ReadOnly(
if associative { "Yes" } else { "No" }.to_string(),
),
});
}
}
// Inject DimStyle picker + style-derived groups for Dimensions.
if let acadrust::EntityType::Dimension(d) = entity {
if let Some(general) = sections.first_mut() {
@@ -1212,14 +1257,7 @@ impl OpenCADStudio {
}
}
}
// Lines & Arrows / Text / Fit default to the assigned
// dimension style (the same source the tessellator
// uses); most rows are editable per-object overrides
// stored in ACAD_DSTYLE. Arrow size / block / overall
// scale and dim-line lineweight drive the render; text
// offset / vertical position round-trip to file but
// don't change the leader glyph here (its annotation
// is a separate entity). Dim-line colour is read-only.
// Resolve editable overrides from the assigned dimension style.
if let Some(ds) = find_dim_style(doc, &ld.dimension_style) {
use crate::entities::dim_override as dov;
use crate::scene::model::object::PropValue;
@@ -1265,7 +1303,7 @@ impl OpenCADStudio {
set_row_value(
&mut sections,
"arrow_size",
PropValue::EditText(format!("{asz:.4}")),
PropValue::EditText(asz.to_string()),
);
let lwd = dov::int(xd, dov::DIMLWD).unwrap_or(ds.dimlwd);
@@ -1283,10 +1321,7 @@ impl OpenCADStudio {
},
);
// Dim-line colour: a per-object ACAD_DSTYLE
// override (code 176, an ACI index) wins over the
// style's DIMCLRD. Editable — the picked colour is
// written back as that override so it round-trips.
// A per-object color override wins over the style.
let dim_c = dov::color(xd, dov::DIMCLRD)
.unwrap_or_else(|| acadrust::types::Color::from_index(ds.dimclrd));
set_row_value(
@@ -1299,7 +1334,7 @@ impl OpenCADStudio {
set_row_value(
&mut sections,
"text_offset",
PropValue::EditText(format!("{gap:.4}")),
PropValue::EditText(gap.to_string()),
);
let tad = dov::int(xd, dov::DIMTAD).unwrap_or(ds.dimtad);
@@ -1316,7 +1351,7 @@ impl OpenCADStudio {
set_row_value(
&mut sections,
"dim_scale_overall",
PropValue::EditText(format!("{scl:.4}")),
PropValue::EditText(scl.to_string()),
);
}
}
@@ -1409,6 +1444,21 @@ impl OpenCADStudio {
// MultiLeader: max points + segment-angle constraints
// are MLeaderStyle settings, not stored on the entity.
acadrust::EntityType::MultiLeader(ml) => {
if ml
.text_style_handle
.and_then(|handle| {
doc.text_styles.iter().find(|style| style.handle == handle)
})
.is_some_and(|style| style.has_fixed_height())
{
set_row_value(
&mut sections,
"text_height",
PropValue::ReadOnly(
crate::entities::common::format_length(ml.text_height),
),
);
}
if let Some(sh) = ml.style_handle {
if let Some((mx, a1, a2)) =
doc.objects.iter().find_map(|(h, o)| match o {
@@ -1833,6 +1883,11 @@ impl OpenCADStudio {
doc, entity,
)
}
acadrust::EntityType::Leader(_) => {
crate::scene::annotative::annotation_style_is_annotative(
doc, entity,
)
}
_ => false,
};
// Dimensions/tables/tolerances carry no Annotative row yet — add one
@@ -1870,8 +1925,17 @@ impl OpenCADStudio {
"annotative",
if is_anno { "Yes" } else { "No" }.to_string(),
),
acadrust::EntityType::Leader(_)
if crate::scene::annotative::annotation_style_is_annotative(
doc, entity,
) => set_row(
&mut sections,
"annotative",
"Yes".to_string(),
),
acadrust::EntityType::Text(_)
| acadrust::EntityType::Insert(_)
| acadrust::EntityType::Leader(_)
| acadrust::EntityType::Hatch(_)
| acadrust::EntityType::Dimension(_) => set_row_value(
&mut sections,
@@ -2494,10 +2558,40 @@ impl OpenCADStudio {
/// (or `None` if it could not be added). Lets callers follow up — e.g.
/// open the in-place text editor on a freshly created MultiLeader.
pub(super) fn commit_entity_handle(
&mut self,
entity: acadrust::EntityType,
) -> Option<Handle> {
self.commit_entity_handle_with_dimension_policy(entity, false)
}
/// Commit an entity, optionally preserving its source dimension style.
pub(super) fn commit_entity_handle_with_dimension_policy(
&mut self,
mut entity: acadrust::EntityType,
preserve_dimension_layer_and_style: bool,
) -> Option<Handle> {
let i = self.active_tab;
let tracks_dimension_chain = matches!(
&entity,
acadrust::EntityType::Dimension(
acadrust::entities::Dimension::Linear(_)
| acadrust::entities::Dimension::Aligned(_)
| acadrust::entities::Dimension::Angular2Ln(_)
| acadrust::entities::Dimension::Angular3Pt(_)
| acadrust::entities::Dimension::Ordinate(_)
)
);
let inherited_dimension = if preserve_dimension_layer_and_style {
match &entity {
acadrust::EntityType::Dimension(dimension) => Some((
dimension.base().common.layer.clone(),
dimension.base().style_name.clone(),
)),
_ => None,
}
} else {
None
};
if let acadrust::EntityType::Table(table) = &mut entity {
const PREFIX: &str = "__OPENCAD_LINK_PENDING__";
if let Some(path) = table.name.strip_prefix(PREFIX).map(str::to_string) {
@@ -2553,9 +2647,21 @@ impl OpenCADStudio {
| acadrust::EntityType::Polyline2D(_)
| acadrust::EntityType::Polyline3D(_)
);
let layer = &self.tabs[i].active_layer;
if layer != "0" || entity.as_entity().layer().is_empty() {
entity.as_entity_mut().set_layer(layer.clone());
let explicit_mleader_layer = match &entity {
acadrust::EntityType::MultiLeader(leader) => leader
.common
.layer
.strip_prefix("__MLEADER_LAYER__")
.map(str::to_owned),
_ => None,
};
if let Some(layer) = explicit_mleader_layer {
entity.as_entity_mut().set_layer(layer);
} else {
let layer = &self.tabs[i].active_layer;
if layer != "0" || entity.as_entity().layer().is_empty() {
entity.as_entity_mut().set_layer(layer.clone());
}
}
// INSUNITS: when inserting a block whose BlockRecord.units differ
@@ -2599,6 +2705,12 @@ impl OpenCADStudio {
&self.tabs[i].scene.document,
&mut entity,
);
if let (Some((layer, style_name)), acadrust::EntityType::Dimension(dimension)) =
(inherited_dimension, &mut entity)
{
dimension.base_mut().common.layer = layer;
dimension.base_mut().style_name = style_name;
}
// Smart centre objects carry their own drawing-level creation style.
// Apply it after the generic ribbon style so ordinary LINE entities
@@ -2758,6 +2870,9 @@ impl OpenCADStudio {
self.tabs[i].last_draw_anchor = Some(handle);
}
}
if tracks_dimension_chain {
self.tabs[i].scene.last_created_dimension = new_handle;
}
new_handle
}
}
@@ -2788,8 +2903,13 @@ fn make_sections_read_only(
acadrust::types::Color::Index(index) => index.to_string(),
acadrust::types::Color::Rgb { r, g, b } => format!("{r},{g},{b}"),
},
PropValue::ColorVaries | PropValue::LwVaries => VARIES_LABEL.to_string(),
PropValue::LwChoice(lineweight) => {
PropValue::ColorVaries
| PropValue::LwVaries
| PropValue::FieldLwVaries { .. } => VARIES_LABEL.to_string(),
PropValue::LwChoice(lineweight)
| PropValue::FieldLwChoice {
value: lineweight, ..
} => {
ui::properties::LwItem(*lineweight).to_string()
}
PropValue::BoolToggle { value, .. } => {
@@ -2947,6 +3067,17 @@ fn merge_prop_value(
(PropValue::LwChoice(_), PropValue::LwChoice(_))
| (PropValue::LwVaries, _)
| (_, PropValue::LwVaries) => PropValue::LwVaries,
(
PropValue::FieldLwChoice { field, .. },
PropValue::FieldLwChoice {
field: other_field,
..
},
) if field == other_field => PropValue::FieldLwVaries { field },
(PropValue::FieldLwVaries { field }, _)
| (_, PropValue::FieldLwVaries { field }) => {
PropValue::FieldLwVaries { field }
}
(PropValue::LinetypeChoice(_), PropValue::LinetypeChoice(_)) => {
PropValue::LinetypeChoice(VARIES_LABEL.into())
}
+13
View File
@@ -198,6 +198,13 @@ pub struct UserSettings {
pub osmode: i32,
/// Controls whether the TEXTEDIT command repeats automatically (0 = Multiple, 1 = Single).
pub texteditmode: bool,
/// QDIM extension-origin priority: 0 = endpoints, 1 = intersections.
#[serde(default)]
pub quick_dimension_snap_priority: u8,
/// DIMCONTINUEMODE: 1 inherits the base dimension's layer/style; 0 uses
/// the current creation layer/style. Registry-style, app-level preference.
#[serde(default = "default_dimension_continue_mode")]
pub dimension_continue_mode: i16,
/// TEXTFILL: fill TrueType glyphs (true) or draw them hollow (false).
pub textfill: bool,
/// When true, saving over an existing file first copies it to a sibling
@@ -247,6 +254,10 @@ fn default_clipromptlines() -> i32 {
3
}
fn default_dimension_continue_mode() -> i16 {
1
}
fn deserialize_clipromptlines<'de, D>(de: D) -> Result<i32, D::Error>
where
D: serde::Deserializer<'de>,
@@ -284,6 +295,8 @@ impl Default for UserSettings {
// off (suppress bit 16384).
osmode: 575 | OSMODE_SUPPRESS,
texteditmode: false,
quick_dimension_snap_priority: 0,
dimension_continue_mode: 1,
textfill: true,
backup_on_save: true,
file_assoc_enabled: true,
+50 -2
View File
@@ -2062,6 +2062,7 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
| "text_style_handle"
| "arrowhead_handle"
| "line_type_handle"
| "block_content_handle"
) {
// Resolve a picked name back to the handle the MLEADER
// stores. The style/text-style rows keep their existing
@@ -2107,6 +2108,11 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
.map(|l| l.handle)
}
}
"block_content_handle" => doc
.block_records
.iter()
.find(|block| block.name == value)
.map(|block| block.handle),
_ => None,
};
for &handle in &handles {
@@ -2130,10 +2136,52 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
"text_style_handle" => {
if let Some(h) = resolved {
ml.text_style_handle = Some(h);
ml.context.text_style_handle = Some(h);
ml.property_override_flags.insert(
acadrust::entities::MultiLeaderPropertyOverrideFlags::TEXT_STYLE,
);
}
}
"arrowhead_handle" => {
ml.arrowhead_handle = resolved;
for root in &mut ml.context.leader_roots {
for line in &mut root.lines {
line.arrowhead_handle = resolved;
line.override_flags.insert(
acadrust::entities::LeaderLinePropertyOverrideFlags::ARROWHEAD,
);
}
}
ml.property_override_flags.insert(
acadrust::entities::MultiLeaderPropertyOverrideFlags::ARROWHEAD,
);
}
"line_type_handle" => {
ml.line_type_handle = resolved;
for root in &mut ml.context.leader_roots {
for line in &mut root.lines {
line.line_type_handle = resolved;
line.override_flags.insert(
acadrust::entities::LeaderLinePropertyOverrideFlags::LINE_TYPE,
);
}
}
ml.property_override_flags.insert(
acadrust::entities::MultiLeaderPropertyOverrideFlags::LEADER_LINE_TYPE,
);
}
"block_content_handle" => {
if let Some(block_handle) = resolved {
ml.block_content_handle = Some(block_handle);
ml.context.block_content_handle = Some(block_handle);
ml.context.has_block_contents = true;
ml.context.block_content_location =
ml.context.content_base_point;
ml.property_override_flags.insert(
acadrust::entities::MultiLeaderPropertyOverrideFlags::BLOCK_CONTENT,
);
}
}
"arrowhead_handle" => ml.arrowhead_handle = resolved,
"line_type_handle" => ml.line_type_handle = resolved,
_ => {}
}
}
+4
View File
@@ -382,6 +382,8 @@ impl OpenCADStudio {
self.snapper.snap_enabled,
),
texteditmode: self.texteditmode,
quick_dimension_snap_priority: self.quick_dimension_snap_priority,
dimension_continue_mode: self.dimension_continue_mode,
textfill: crate::scene::text::sdf_atlas::textfill(),
backup_on_save: self.backup_on_save,
file_assoc_enabled: self.file_assoc_enabled,
@@ -440,6 +442,8 @@ impl OpenCADStudio {
self.snapper.enabled = modes.into_iter().collect();
self.snapper.snap_enabled = snap_enabled;
self.texteditmode = s.texteditmode;
self.quick_dimension_snap_priority = s.quick_dimension_snap_priority.min(1);
self.dimension_continue_mode = s.dimension_continue_mode.clamp(0, 1);
crate::scene::text::sdf_atlas::set_textfill(s.textfill);
self.backup_on_save = s.backup_on_save;
self.file_assoc_enabled = s.file_assoc_enabled;
+82 -1
View File
@@ -4657,6 +4657,32 @@ impl OpenCADStudio {
Task::none()
}
Message::PropFieldLwChanged { field, value } => {
let i = self.active_tab;
let handles = self.property_target_handles(i);
self.apply_property_op(i, "CHPROP", &handles, |app, handle| {
if let Some(acadrust::EntityType::MultiLeader(leader)) =
app.tabs[i].scene.document.get_entity_mut(handle)
{
if field == "line_weight" {
leader.line_weight = value;
leader.property_override_flags.insert(
acadrust::entities::MultiLeaderPropertyOverrideFlags::LEADER_LINE_WEIGHT,
);
for root in &mut leader.context.leader_roots {
for line in &mut root.lines {
line.line_weight = value;
line.override_flags.insert(
acadrust::entities::LeaderLinePropertyOverrideFlags::LINE_WEIGHT,
);
}
}
}
}
});
Task::none()
}
Message::PropLinetypeChanged(lt) => {
let i = self.active_tab;
let handles = self.property_target_handles(i);
@@ -4896,11 +4922,18 @@ impl OpenCADStudio {
Message::PropVertexStep(delta) => {
let i = self.active_tab;
let handles = self.property_target_handles(i);
// Vertex navigation applies to a single selected polyline.
// Navigate the active vertex or table cell.
let n = if handles.len() == 1 {
match self.tabs[i].scene.document.get_entity(handles[0]) {
Some(acadrust::EntityType::LwPolyline(p)) => p.vertices.len(),
Some(acadrust::EntityType::Polyline2D(p)) => p.vertices.len(),
Some(acadrust::EntityType::PolygonMesh(p)) => p.vertices.len(),
Some(acadrust::EntityType::Face3D(face)) => {
if face.is_triangle() { 3 } else { 4 }
}
Some(acadrust::EntityType::Polyline3D(p)) => {
crate::entities::polyline::polyline3d_control_vertex_count(p)
}
Some(acadrust::EntityType::Table(table)) => {
table.row_count().saturating_mul(table.column_count())
}
@@ -5114,6 +5147,54 @@ impl OpenCADStudio {
}
return Task::none();
}
if matches!(
field.as_str(),
"line_color" | "text_color" | "block_content_color"
| "background_fill_color"
) {
self.apply_property_op(i, "CHPROP", &handles, |app, handle| {
if let Some(acadrust::EntityType::MultiLeader(leader)) =
app.tabs[i].scene.document.get_entity_mut(handle)
{
match field.as_str() {
"line_color" => {
leader.line_color = color;
leader.property_override_flags.insert(
acadrust::entities::MultiLeaderPropertyOverrideFlags::LINE_COLOR,
);
for root in &mut leader.context.leader_roots {
for line in &mut root.lines {
line.line_color = color;
line.override_flags.insert(
acadrust::entities::LeaderLinePropertyOverrideFlags::LINE_COLOR,
);
}
}
}
"text_color" => {
leader.text_color = color;
leader.context.text_color = color;
leader.property_override_flags.insert(
acadrust::entities::MultiLeaderPropertyOverrideFlags::TEXT_COLOR,
);
}
"block_content_color" => {
leader.block_content_color = color;
leader.context.block_content_color = color;
leader.property_override_flags.insert(
acadrust::entities::MultiLeaderPropertyOverrideFlags::BLOCK_CONTENT_COLOR,
);
}
"background_fill_color" => {
leader.context.background_fill_color = color;
}
_ => {}
}
}
});
self.tabs[i].properties.open_color_field = None;
return Task::none();
}
if matches!(
field.as_str(),
"tbl_cell_content_color" | "tbl_cell_background_color"
+4 -1
View File
@@ -432,7 +432,10 @@ impl OpenCADStudio {
let indexed = match tab.scene.document.get_entity(h) {
Some(acadrust::EntityType::LwPolyline(_))
| Some(acadrust::EntityType::Polyline2D(_))
| Some(acadrust::EntityType::Spline(_)) => true,
| Some(acadrust::EntityType::Polyline3D(_))
| Some(acadrust::EntityType::Spline(_))
| Some(acadrust::EntityType::Face3D(_))
| Some(acadrust::EntityType::PolygonMesh(_)) => true,
_ => false,
};
indexed.then_some(tab.properties.prop_vertex)