feat(dimension): complete linear workflow
This commit is contained in:
commit
959197bb3c
22 changed files with 2576 additions and 258 deletions
|
|
@ -801,7 +801,21 @@ impl OpenCADStudio {
|
|||
// the structure snapshot fallback.
|
||||
let delta_safe = self.delta_add_safe(i, &entity);
|
||||
let pending = self.begin_undo(i, label, 1, delta_safe);
|
||||
self.commit_entity(entity);
|
||||
let is_linear_dimension = matches!(
|
||||
entity,
|
||||
acadrust::EntityType::Dimension(acadrust::entities::Dimension::Linear(_))
|
||||
);
|
||||
let committed = self.commit_entity_handle(entity);
|
||||
if is_linear_dimension {
|
||||
if let Some(handle) = committed {
|
||||
let sources = self.tabs[i]
|
||||
.scene
|
||||
.infer_linear_dimension_sources(handle);
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.attach_linear_dimension_association(handle, sources);
|
||||
}
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
let prompt = self.tabs[i].active_cmd.as_ref().map(|c| c.prompt());
|
||||
if let Some(p) = prompt {
|
||||
|
|
@ -1191,7 +1205,21 @@ impl OpenCADStudio {
|
|||
let label = self.history_label_from_active_cmd(i, "ENTITY");
|
||||
let delta_safe = self.delta_add_safe(i, &entity);
|
||||
let pending = self.begin_undo(i, label, 1, delta_safe);
|
||||
self.commit_entity(entity);
|
||||
let is_linear_dimension = matches!(
|
||||
entity,
|
||||
acadrust::EntityType::Dimension(acadrust::entities::Dimension::Linear(_))
|
||||
);
|
||||
let committed = self.commit_entity_handle(entity);
|
||||
if is_linear_dimension {
|
||||
if let Some(handle) = committed {
|
||||
let sources = self.tabs[i]
|
||||
.scene
|
||||
.infer_linear_dimension_sources(handle);
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.attach_linear_dimension_association(handle, sources);
|
||||
}
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.tabs[i].scene.clear_preview_wire();
|
||||
self.tabs[i].active_cmd = None;
|
||||
|
|
@ -1201,6 +1229,27 @@ impl OpenCADStudio {
|
|||
self.commit_undo_delta(i, pd);
|
||||
}
|
||||
}
|
||||
CmdResult::CommitAssociativeDimension { entity, source } => {
|
||||
let label = self.history_label_from_active_cmd(i, "DIMLINEAR");
|
||||
let pending = self.begin_undo(i, label, 1, false);
|
||||
if let Some(handle) = self.commit_entity_handle(entity) {
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.attach_linear_dimension_association(handle, [Some(source), Some(source)]);
|
||||
self.tabs[i].scene.bump_entities(&[
|
||||
(handle, crate::scene::ChangeKind::Modified),
|
||||
(source, crate::scene::ChangeKind::Modified),
|
||||
]);
|
||||
}
|
||||
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(pending) = pending {
|
||||
self.commit_undo_delta(i, pending);
|
||||
}
|
||||
}
|
||||
CmdResult::CommitSolid {
|
||||
entity,
|
||||
solid,
|
||||
|
|
|
|||
|
|
@ -1218,6 +1218,8 @@ pub enum ColorPickTarget {
|
|||
Properties,
|
||||
/// Selected entities' background colour (hatch / MTEXT background row).
|
||||
PropertiesBg,
|
||||
/// A named per-entity Properties colour field.
|
||||
PropertiesField(String),
|
||||
/// Current creation colour (ribbon).
|
||||
Ribbon,
|
||||
/// A layer's colour, by panel row index.
|
||||
|
|
|
|||
|
|
@ -1006,6 +1006,21 @@ impl OpenCADStudio {
|
|||
|
||||
// Inject DimStyle picker + style-derived groups for Dimensions.
|
||||
if let acadrust::EntityType::Dimension(d) = entity {
|
||||
if let Some(general) = sections.first_mut() {
|
||||
general.props.retain(|property| property.field != "handle");
|
||||
let associative =
|
||||
crate::scene::dimension_assoc::dimension_is_associative(
|
||||
&self.tabs[i].scene.document,
|
||||
d.base().common.handle,
|
||||
);
|
||||
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(),
|
||||
),
|
||||
});
|
||||
}
|
||||
let dim_style_names: Vec<String> = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
|
|
@ -1046,12 +1061,14 @@ impl OpenCADStudio {
|
|||
&& s.name.eq_ignore_ascii_case("Standard"))
|
||||
})
|
||||
{
|
||||
sections.extend(crate::entities::dimension::style_sections(style));
|
||||
sections.extend(crate::entities::dimension::style_sections(
|
||||
style,
|
||||
d,
|
||||
&self.tabs[i].scene.document,
|
||||
));
|
||||
|
||||
// The style groups are a read-only mirror, but the
|
||||
// dim-line colour is an editable per-object override:
|
||||
// prefer the ACAD_DSTYLE code-176 (ACI) override, else
|
||||
// the style's DIMCLRD.
|
||||
// Prefer entity-level dimension-variable overrides;
|
||||
// fall back to the assigned style values.
|
||||
use crate::entities::dim_override as dov;
|
||||
use crate::scene::model::object::PropValue;
|
||||
let dim_c = dov::color(&d.base().common.extended_data, dov::DIMCLRD)
|
||||
|
|
@ -1061,6 +1078,20 @@ impl OpenCADStudio {
|
|||
"dim_line_color",
|
||||
PropValue::ColorChoice(dim_c),
|
||||
);
|
||||
for (field, code, inherited) in [
|
||||
("dim_ext_line_color", dov::DIMCLRE, style.dimclre),
|
||||
("dim_text_color", dov::DIMCLRT, style.dimclrt),
|
||||
] {
|
||||
let color = dov::color(&d.base().common.extended_data, code)
|
||||
.unwrap_or_else(|| {
|
||||
acadrust::types::Color::from_index(inherited)
|
||||
});
|
||||
set_row_value(
|
||||
&mut sections,
|
||||
field,
|
||||
PropValue::ColorChoice(color),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1323,10 +1354,8 @@ impl OpenCADStudio {
|
|||
}
|
||||
|
||||
// Annotative Yes/No + a conditional "Annotative scale" row.
|
||||
// Read-only: annotative state comes from the entity's style
|
||||
// (or its own flag) and the assigned annotation-scale name(s)
|
||||
// are walked from the entity's extension dictionary — both
|
||||
// need the document, so they are resolved here.
|
||||
// Annotative state and assigned scale names need the
|
||||
// document, so they are resolved here.
|
||||
{
|
||||
// Which entities show an Annotative row, the field it uses,
|
||||
// and — for those that don't already carry the row
|
||||
|
|
@ -1368,7 +1397,7 @@ impl OpenCADStudio {
|
|||
// (MTEXT via its native flag, single-line TEXT via the
|
||||
// context alone) get an editable toggle: turning it on
|
||||
// synthesizes a real per-scale representation. The
|
||||
// remaining types are style-driven and stay read-only.
|
||||
// remaining style-only types stay read-only.
|
||||
if anno_field == "annotative" {
|
||||
match entity {
|
||||
acadrust::EntityType::MText(t) => set_row_value(
|
||||
|
|
@ -1381,7 +1410,8 @@ impl OpenCADStudio {
|
|||
),
|
||||
acadrust::EntityType::Text(_)
|
||||
| acadrust::EntityType::Insert(_)
|
||||
| acadrust::EntityType::Hatch(_) => set_row_value(
|
||||
| acadrust::EntityType::Hatch(_)
|
||||
| acadrust::EntityType::Dimension(_) => set_row_value(
|
||||
&mut sections,
|
||||
"annotative",
|
||||
crate::scene::model::object::PropValue::BoolToggle {
|
||||
|
|
@ -1397,16 +1427,26 @@ impl OpenCADStudio {
|
|||
}
|
||||
}
|
||||
if is_anno {
|
||||
// The applied annotation scale follows the current
|
||||
// annotation scale (CANNOSCALE / the status-bar
|
||||
// scale pill), not a per-object stored value.
|
||||
let memberships = crate::scene::annotative::object_scale_memberships(
|
||||
doc,
|
||||
entity.common().handle,
|
||||
);
|
||||
let assigned_scales = if memberships.is_empty() {
|
||||
doc.header.current_annotation_scale.clone()
|
||||
} else {
|
||||
memberships
|
||||
.into_iter()
|
||||
.map(|(name, _)| name)
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
};
|
||||
insert_row_after(
|
||||
&mut sections,
|
||||
anno_field,
|
||||
crate::entities::common::ro_prop(
|
||||
t!("Annotative scale").as_ref(),
|
||||
"annotative_scale",
|
||||
doc.header.current_annotation_scale.clone(),
|
||||
assigned_scales,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -2411,18 +2451,7 @@ fn set_row_value(
|
|||
/// The lineweight dropdown options (named defaults + the standard millimetre
|
||||
/// steps), matching the labels `dim_lineweight_label` produces.
|
||||
pub(crate) fn lineweight_options() -> Vec<String> {
|
||||
let mut v = vec![
|
||||
"ByLayer".to_string(),
|
||||
"ByBlock".to_string(),
|
||||
"Default".to_string(),
|
||||
];
|
||||
for lw in [
|
||||
0, 5, 9, 13, 15, 18, 20, 25, 30, 35, 40, 50, 53, 60, 70, 80, 90, 100, 106, 120, 140, 158,
|
||||
200, 211,
|
||||
] {
|
||||
v.push(format!("{:.2} mm", lw as f64 / 100.0));
|
||||
}
|
||||
v
|
||||
crate::entities::common::lineweight_options()
|
||||
}
|
||||
|
||||
/// Inverse of `dim_lineweight_label`: a lineweight label → DIMLWD enum value.
|
||||
|
|
@ -2548,13 +2577,7 @@ fn leader_arrow_label(
|
|||
|
||||
/// DIMLWD lineweight enum → label.
|
||||
fn dim_lineweight_label(dimlwd: i16) -> String {
|
||||
match dimlwd {
|
||||
-1 => "ByLayer".to_string(),
|
||||
-2 => "ByBlock".to_string(),
|
||||
-3 => "Default".to_string(),
|
||||
v if v >= 0 => format!("{:.2} mm", v as f64 / 100.0),
|
||||
_ => "Default".to_string(),
|
||||
}
|
||||
crate::entities::common::lineweight_label(dimlwd)
|
||||
}
|
||||
|
||||
/// Human-readable INSUNITS name (DXF group 70 unit codes).
|
||||
|
|
|
|||
|
|
@ -1140,6 +1140,7 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
if matches!(
|
||||
item.action,
|
||||
GripMenuAction::Stretch
|
||||
| GripMenuAction::MoveWithDimLine
|
||||
| GripMenuAction::MoveWithLeader
|
||||
| GripMenuAction::MoveIndependent
|
||||
) {
|
||||
|
|
@ -1839,7 +1840,24 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
}
|
||||
self.push_undo_snapshot(i, "CHPROP");
|
||||
|
||||
if field == "vscale_std" {
|
||||
if field.starts_with("dim_") {
|
||||
for &handle in &handles {
|
||||
if self.tabs[i].scene.is_layer_locked(handle) {
|
||||
continue;
|
||||
}
|
||||
if matches!(
|
||||
self.tabs[i].scene.document.get_entity(handle),
|
||||
Some(acadrust::EntityType::Dimension(_))
|
||||
) {
|
||||
crate::entities::dim_override::set_property(
|
||||
&mut self.tabs[i].scene.document,
|
||||
handle,
|
||||
field,
|
||||
&value,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if field == "vscale_std" {
|
||||
for &handle in &handles {
|
||||
if matches!(
|
||||
self.tabs[i].scene.document.get_entity(handle),
|
||||
|
|
@ -2410,6 +2428,19 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
continue;
|
||||
}
|
||||
match field {
|
||||
_ if field.starts_with("dim_") => {
|
||||
if matches!(
|
||||
self.tabs[i].scene.document.get_entity(handle),
|
||||
Some(acadrust::EntityType::Dimension(_))
|
||||
) {
|
||||
crate::entities::dim_override::set_property(
|
||||
&mut self.tabs[i].scene.document,
|
||||
handle,
|
||||
field,
|
||||
&val,
|
||||
);
|
||||
}
|
||||
}
|
||||
"hyperlink" => {
|
||||
// Stored in the standard PE_URL XDATA
|
||||
// record; an empty value clears it.
|
||||
|
|
|
|||
|
|
@ -4834,8 +4834,25 @@ impl OpenCADStudio {
|
|||
// line with the rest of the dim-colour stack (index-only through
|
||||
// the file layer). Guarded to leaders / dimensions so a mixed
|
||||
// selection can't stamp the override onto other entities.
|
||||
if field == "dim_line_color" {
|
||||
if matches!(
|
||||
field.as_str(),
|
||||
"dim_line_color"
|
||||
| "dim_ext_line_color"
|
||||
| "dim_text_color"
|
||||
| "dim_text_fill_color"
|
||||
) {
|
||||
let fill_mode = (field == "dim_text_fill_color").then(|| match color {
|
||||
acadrust::types::Color::None => 0,
|
||||
acadrust::types::Color::ByBlock => 1,
|
||||
_ => 2,
|
||||
});
|
||||
let aci = color.approximate_index();
|
||||
let code = match field.as_str() {
|
||||
"dim_ext_line_color" => crate::entities::dim_override::DIMCLRE,
|
||||
"dim_text_color" => crate::entities::dim_override::DIMCLRT,
|
||||
"dim_text_fill_color" => crate::entities::dim_override::DIMTFILLCLR,
|
||||
_ => crate::entities::dim_override::DIMCLRD,
|
||||
};
|
||||
let targets: Vec<acadrust::Handle> = handles
|
||||
.iter()
|
||||
.copied()
|
||||
|
|
@ -4850,10 +4867,23 @@ impl OpenCADStudio {
|
|||
if !targets.is_empty() {
|
||||
self.push_undo_snapshot(i, "CHPROP");
|
||||
for &handle in &targets {
|
||||
if field == "dim_text_fill_color" {
|
||||
crate::entities::dim_override::set(
|
||||
&mut self.tabs[i].scene.document,
|
||||
handle,
|
||||
crate::entities::dim_override::DIMTFILL,
|
||||
Some(acadrust::xdata::XDataValue::Integer16(
|
||||
fill_mode.unwrap_or(2),
|
||||
)),
|
||||
);
|
||||
if fill_mode != Some(2) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
crate::entities::dim_override::set(
|
||||
&mut self.tabs[i].scene.document,
|
||||
handle,
|
||||
crate::entities::dim_override::DIMCLRD,
|
||||
code,
|
||||
Some(acadrust::xdata::XDataValue::Integer16(aci)),
|
||||
);
|
||||
}
|
||||
|
|
@ -7527,6 +7557,7 @@ impl OpenCADStudio {
|
|||
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].properties.color_picker_open = false;
|
||||
self.tabs[i].properties.open_color_field = None;
|
||||
self.tabs[i].layers.color_picker_row = None;
|
||||
|
||||
Task::none()
|
||||
|
|
|
|||
|
|
@ -1146,6 +1146,9 @@ pub(super) fn on_text_style_dialog_open(&mut self) -> Task<Message> {
|
|||
Some(crate::app::ColorPickTarget::PropertiesBg) => {
|
||||
Some(Message::PropBgColorChanged(color))
|
||||
}
|
||||
Some(crate::app::ColorPickTarget::PropertiesField(field)) => {
|
||||
Some(Message::PropColorFieldChanged { field, color })
|
||||
}
|
||||
Some(crate::app::ColorPickTarget::MText) => {
|
||||
Some(Message::MTextColorChanged(color))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -474,6 +474,7 @@ fn mtext_editor_content<'a>(
|
|||
crate::ui::color_select::ColorExtras {
|
||||
by_layer: true,
|
||||
by_block: false,
|
||||
..Default::default()
|
||||
},
|
||||
Message::MTextColorChanged,
|
||||
Message::MTextColorPickerToggle,
|
||||
|
|
|
|||
|
|
@ -1212,6 +1212,11 @@ pub enum CmdResult {
|
|||
CommitEntitiesAndExit(Vec<EntityType>),
|
||||
/// Commit an acadrust entity to the document and end the command.
|
||||
CommitAndExit(EntityType),
|
||||
/// Commit an object-selected linear dimension and retain its source link.
|
||||
CommitAssociativeDimension {
|
||||
entity: EntityType,
|
||||
source: Handle,
|
||||
},
|
||||
/// Commit a Model-tab 3D solid: the acadrust entity (for selection /
|
||||
/// persistence) plus its B-rep (cached for boolean ops + shaded
|
||||
/// rendering). Ends the command.
|
||||
|
|
|
|||
|
|
@ -575,6 +575,33 @@ pub fn edit_scalar_prop(label: &str, field: &'static str, value: f64) -> Propert
|
|||
}
|
||||
}
|
||||
|
||||
/// Standard lineweight choices shared by entity-specific Properties rows.
|
||||
pub fn lineweight_options() -> Vec<String> {
|
||||
let mut options = vec![
|
||||
"ByLayer".to_string(),
|
||||
"ByBlock".to_string(),
|
||||
"Default".to_string(),
|
||||
];
|
||||
for value in [
|
||||
0, 5, 9, 13, 15, 18, 20, 25, 30, 35, 40, 50, 53, 60, 70, 80, 90, 100, 106, 120,
|
||||
140, 158, 200, 211,
|
||||
] {
|
||||
options.push(format!("{:.2} mm", value as f64 / 100.0));
|
||||
}
|
||||
options
|
||||
}
|
||||
|
||||
/// DIMLWD/DIMLWE lineweight value displayed by a Properties dropdown.
|
||||
pub fn lineweight_label(value: i16) -> String {
|
||||
match value {
|
||||
-1 => "ByLayer".to_string(),
|
||||
-2 => "ByBlock".to_string(),
|
||||
-3 => "Default".to_string(),
|
||||
value if value >= 0 => format!("{:.2} mm", value as f64 / 100.0),
|
||||
_ => "Default".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ro_prop(label: &str, field: &'static str, value: impl Into<String>) -> Property {
|
||||
Property {
|
||||
label: label.into(),
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
use acadrust::types::Color;
|
||||
use acadrust::xdata::{ExtendedData, XDataValue};
|
||||
use acadrust::{CadDocument, Handle};
|
||||
use acadrust::{CadDocument, EntityType, Handle};
|
||||
|
||||
// DXF group codes of the dimension variables surfaced on the leader panel.
|
||||
pub const DIMSCALE: i16 = 40; // overall scale (real)
|
||||
|
|
@ -77,7 +77,12 @@ pub const DIMATFIT: i16 = 289;
|
|||
pub const DIMFXLON: i16 = 290;
|
||||
pub const DIMTFILL: i16 = 69;
|
||||
pub const DIMTFILLCLR: i16 = 70;
|
||||
pub const DIMTXTDIRECTION: i16 = 295;
|
||||
pub const DIMTXTDIRECTION: i16 = 294;
|
||||
pub const DIMALTMZF: i16 = 295;
|
||||
pub const DIMALTMZS: i16 = 296;
|
||||
pub const DIMMZF: i16 = 297;
|
||||
pub const DIMMZS: i16 = 298;
|
||||
pub const DIMTALN: i16 = 392;
|
||||
pub const DIMTXSTY: i16 = 340;
|
||||
pub const DIMBLK: i16 = 342;
|
||||
pub const DIMBLK1: i16 = 343;
|
||||
|
|
@ -229,3 +234,479 @@ pub fn set(doc: &mut CadDocument, handle: Handle, code: i16, value: Option<XData
|
|||
}
|
||||
write_pairs(doc, handle, kept);
|
||||
}
|
||||
|
||||
pub fn property_real_code(field: &str) -> Option<i16> {
|
||||
Some(match field {
|
||||
"dim_arrow_size" => DIMASZ,
|
||||
"dim_line_ext" => DIMDLE,
|
||||
"dim_ext_line_ext" => DIMEXE,
|
||||
"dim_ext_line_offset" => DIMEXO,
|
||||
"dim_ext_line_fixed_length" => DIMFXL,
|
||||
"dim_text_height" => DIMTXT,
|
||||
"dim_text_offset" => DIMGAP,
|
||||
"dim_scale_overall" => DIMSCALE,
|
||||
"dim_roundoff" => DIMRND,
|
||||
"dim_scale_linear" => DIMLFAC,
|
||||
"dim_alt_scale_factor" => DIMALTF,
|
||||
"dim_alt_sub_units_scale" => DIMALTMZF,
|
||||
"dim_alt_roundoff" => DIMALTRND,
|
||||
"dim_sub_units_scale" => DIMMZF,
|
||||
"dim_tolerance_limit_lower" => DIMTM,
|
||||
"dim_tolerance_limit_upper" => DIMTP,
|
||||
"dim_tolerance_text_height" => DIMTFAC,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn property_int_code(field: &str) -> Option<i16> {
|
||||
Some(match field {
|
||||
"dim_line_lineweight" => DIMLWD,
|
||||
"dim_ext_line_lineweight" => DIMLWE,
|
||||
"dim_ext_line_fixed" => DIMFXLON,
|
||||
"dim_text_pos_vert" => DIMTAD,
|
||||
"dim_text_pos_hor" => DIMJUST,
|
||||
"dim_text_outside_align" => DIMTOH,
|
||||
"dim_text_inside_align" => DIMTIH,
|
||||
"dim_fit" => DIMATFIT,
|
||||
"dim_text_inside" => DIMTIX,
|
||||
"dim_text_movement" => DIMTMOVE,
|
||||
"dim_line_forced" => DIMTOFL,
|
||||
"dim_line_inside" => DIMSOXD,
|
||||
"dim_units" => DIMLUNIT,
|
||||
"dim_precision" => DIMDEC,
|
||||
"dim_decimal_separator" => DIMDSEP,
|
||||
"dim_fractional_type" => DIMFRAC,
|
||||
"dim_text_view_direction" => DIMTXTDIRECTION,
|
||||
"dim_alt_enabled" => DIMALT,
|
||||
"dim_alt_format" => DIMALTU,
|
||||
"dim_alt_precision" => DIMALTD,
|
||||
"dim_alt_tolerance_precision" => DIMALTTD,
|
||||
"dim_tolerance_precision" => DIMTDEC,
|
||||
"dim_tolerance_pos_vert" => DIMTOLJ,
|
||||
"dim_tolerance_alignment" => DIMTALN,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn property_string_code(field: &str) -> Option<i16> {
|
||||
Some(match field {
|
||||
"dim_sub_units_suffix" => DIMMZS,
|
||||
"dim_alt_sub_units_suffix" => DIMALTMZS,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
fn yes(value: &str) -> Option<i16> {
|
||||
match value.trim().to_ascii_lowercase().as_str() {
|
||||
"yes" | "on" | "true" | "1" => Some(1),
|
||||
"no" | "off" | "false" | "0" => Some(0),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn inherited_int(doc: &CadDocument, handle: Handle, code: i16) -> i16 {
|
||||
let Some(EntityType::Dimension(dimension)) = doc.get_entity(handle) else {
|
||||
return 0;
|
||||
};
|
||||
if let Some(value) = int(&dimension.base().common.extended_data, code) {
|
||||
return value;
|
||||
}
|
||||
let Some(style) = doc.dim_styles.iter().find(|style| {
|
||||
style.name.eq_ignore_ascii_case(&dimension.base().style_name)
|
||||
|| (dimension.base().style_name.trim().is_empty()
|
||||
&& style.name.eq_ignore_ascii_case("Standard"))
|
||||
}) else {
|
||||
return 0;
|
||||
};
|
||||
match code {
|
||||
DIMSD1 => style.dimsd1 as i16,
|
||||
DIMSD2 => style.dimsd2 as i16,
|
||||
DIMSE1 => style.dimse1 as i16,
|
||||
DIMSE2 => style.dimse2 as i16,
|
||||
DIMZIN => style.dimzin,
|
||||
DIMALTZ => style.dimaltz,
|
||||
DIMTZIN => style.dimtzin,
|
||||
DIMALTTZ => style.dimalttz,
|
||||
DIMTOL => style.dimtol as i16,
|
||||
DIMLIM => style.dimlim as i16,
|
||||
DIMALT => style.dimalt as i16,
|
||||
DIMTFILL => style.dimtfill,
|
||||
DIMTALN => 0,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn inherited_real(doc: &CadDocument, handle: Handle, code: i16) -> f64 {
|
||||
let Some(EntityType::Dimension(dimension)) = doc.get_entity(handle) else {
|
||||
return 0.0;
|
||||
};
|
||||
if let Some(value) = real(&dimension.base().common.extended_data, code) {
|
||||
return value;
|
||||
}
|
||||
let Some(style) = doc.dim_styles.iter().find(|style| {
|
||||
style.name.eq_ignore_ascii_case(&dimension.base().style_name)
|
||||
|| (dimension.base().style_name.trim().is_empty()
|
||||
&& style.name.eq_ignore_ascii_case("Standard"))
|
||||
}) else {
|
||||
return 0.0;
|
||||
};
|
||||
match code {
|
||||
DIMGAP => style.dimgap,
|
||||
DIMTP => style.dimtp,
|
||||
DIMTM => style.dimtm,
|
||||
_ => 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
fn inherited_string(doc: &CadDocument, handle: Handle, code: i16) -> String {
|
||||
let Some(EntityType::Dimension(dimension)) = doc.get_entity(handle) else {
|
||||
return String::new();
|
||||
};
|
||||
if let Some(value) = string(&dimension.base().common.extended_data, code) {
|
||||
return value;
|
||||
}
|
||||
let Some(style) = doc.dim_styles.iter().find(|style| {
|
||||
style.name.eq_ignore_ascii_case(&dimension.base().style_name)
|
||||
|| (dimension.base().style_name.trim().is_empty()
|
||||
&& style.name.eq_ignore_ascii_case("Standard"))
|
||||
}) else {
|
||||
return String::new();
|
||||
};
|
||||
match code {
|
||||
DIMPOST => style.dimpost.clone(),
|
||||
DIMAPOST => style.dimapost.clone(),
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn split_template(value: &str) -> (&str, &str) {
|
||||
value.split_once("<>").unwrap_or(("", value))
|
||||
}
|
||||
|
||||
pub fn set_property(
|
||||
doc: &mut CadDocument,
|
||||
handle: Handle,
|
||||
field: &str,
|
||||
value: &str,
|
||||
) -> bool {
|
||||
let trimmed = value.trim();
|
||||
let handle_field = match field {
|
||||
"dim_arrowhead_1" => Some(DIMBLK1),
|
||||
"dim_arrowhead_2" => Some(DIMBLK2),
|
||||
"dim_linetype" => Some(DIMLTYPE),
|
||||
"dim_ext_linetype_1" => Some(DIMLTEX1),
|
||||
"dim_ext_linetype_2" => Some(DIMLTEX2),
|
||||
"dim_text_style" => Some(DIMTXSTY),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(code) = handle_field {
|
||||
let resolved = match field {
|
||||
"dim_arrowhead_1" | "dim_arrowhead_2" => {
|
||||
if trimmed == "Closed filled" {
|
||||
Some(Handle::NULL)
|
||||
} else {
|
||||
doc.block_records
|
||||
.iter()
|
||||
.find(|record| record.name == trimmed)
|
||||
.map(|record| record.handle)
|
||||
}
|
||||
}
|
||||
"dim_text_style" => doc
|
||||
.text_styles
|
||||
.iter()
|
||||
.find(|style| style.name == trimmed)
|
||||
.map(|style| style.handle),
|
||||
_ => doc
|
||||
.line_types
|
||||
.iter()
|
||||
.find(|line_type| line_type.name == trimmed)
|
||||
.map(|line_type| line_type.handle),
|
||||
};
|
||||
let Some(resolved) = resolved else {
|
||||
return false;
|
||||
};
|
||||
set(doc, handle, code, Some(XDataValue::Handle(resolved)));
|
||||
return true;
|
||||
}
|
||||
let inverse_boolean_code = match field {
|
||||
"dim_line_1" => Some(DIMSD1),
|
||||
"dim_line_2" => Some(DIMSD2),
|
||||
"dim_ext_line_1" => Some(DIMSE1),
|
||||
"dim_ext_line_2" => Some(DIMSE2),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(code) = inverse_boolean_code {
|
||||
let Some(visible) = yes(trimmed) else {
|
||||
return false;
|
||||
};
|
||||
set(doc, handle, code, Some(XDataValue::Integer16((visible == 0) as i16)));
|
||||
return true;
|
||||
}
|
||||
let bit_field = match field {
|
||||
"dim_suppress_leading_zeros" => Some((DIMZIN, 4)),
|
||||
"dim_suppress_trailing_zeros" => Some((DIMZIN, 8)),
|
||||
"dim_alt_suppress_leading_zeros" => Some((DIMALTZ, 4)),
|
||||
"dim_alt_suppress_trailing_zeros" => Some((DIMALTZ, 8)),
|
||||
"dim_tolerance_suppress_leading_zeros" => Some((DIMTZIN, 4)),
|
||||
"dim_tolerance_suppress_trailing_zeros" => Some((DIMTZIN, 8)),
|
||||
"dim_alt_tolerance_suppress_leading_zeros" => Some((DIMALTTZ, 4)),
|
||||
"dim_alt_tolerance_suppress_trailing_zeros" => Some((DIMALTTZ, 8)),
|
||||
_ => None,
|
||||
};
|
||||
if let Some((code, bit)) = bit_field {
|
||||
let Some(enabled) = yes(trimmed) else {
|
||||
return false;
|
||||
};
|
||||
let current = inherited_int(doc, handle, code);
|
||||
let value = if enabled != 0 {
|
||||
current | bit
|
||||
} else {
|
||||
current & !bit
|
||||
};
|
||||
set(doc, handle, code, Some(XDataValue::Integer16(value)));
|
||||
return true;
|
||||
}
|
||||
let zero_base = match field {
|
||||
"dim_suppress_zero_feet" => Some((DIMZIN, true)),
|
||||
"dim_suppress_zero_inches" => Some((DIMZIN, false)),
|
||||
"dim_alt_suppress_zero_feet" => Some((DIMALTZ, true)),
|
||||
"dim_alt_suppress_zero_inches" => Some((DIMALTZ, false)),
|
||||
"dim_tolerance_suppress_zero_feet" => Some((DIMTZIN, true)),
|
||||
"dim_tolerance_suppress_zero_inches" => Some((DIMTZIN, false)),
|
||||
"dim_alt_tolerance_suppress_zero_feet" => Some((DIMALTTZ, true)),
|
||||
"dim_alt_tolerance_suppress_zero_inches" => Some((DIMALTTZ, false)),
|
||||
_ => None,
|
||||
};
|
||||
if let Some((code, feet_field)) = zero_base {
|
||||
let Some(enabled) = yes(trimmed).map(|value| value != 0) else {
|
||||
return false;
|
||||
};
|
||||
let current = inherited_int(doc, handle, code);
|
||||
let mut suppress_feet = matches!(current & 3, 0 | 3);
|
||||
let mut suppress_inches = matches!(current & 3, 0 | 2);
|
||||
if feet_field {
|
||||
suppress_feet = enabled;
|
||||
} else {
|
||||
suppress_inches = enabled;
|
||||
}
|
||||
let base = match (suppress_feet, suppress_inches) {
|
||||
(true, true) => 0,
|
||||
(false, false) => 1,
|
||||
(false, true) => 2,
|
||||
(true, false) => 3,
|
||||
};
|
||||
set(
|
||||
doc,
|
||||
handle,
|
||||
code,
|
||||
Some(XDataValue::Integer16((current & !3) | base)),
|
||||
);
|
||||
return true;
|
||||
}
|
||||
if field == "dim_text_fill_color" {
|
||||
let mode = match trimmed.to_ascii_lowercase().as_str() {
|
||||
"none" => 0,
|
||||
"background" => 1,
|
||||
"color" => 2,
|
||||
_ => return false,
|
||||
};
|
||||
set(doc, handle, DIMTFILL, Some(XDataValue::Integer16(mode)));
|
||||
return true;
|
||||
}
|
||||
if matches!(
|
||||
field,
|
||||
"dim_prefix" | "dim_suffix" | "dim_alt_prefix" | "dim_alt_suffix"
|
||||
) {
|
||||
let code = if field.starts_with("dim_alt_") {
|
||||
DIMAPOST
|
||||
} else {
|
||||
DIMPOST
|
||||
};
|
||||
let current = inherited_string(doc, handle, code);
|
||||
let (old_prefix, old_suffix) = split_template(¤t);
|
||||
let (prefix, suffix) = if field.ends_with("prefix") {
|
||||
(value, old_suffix)
|
||||
} else {
|
||||
(old_prefix, value)
|
||||
};
|
||||
set(
|
||||
doc,
|
||||
handle,
|
||||
code,
|
||||
Some(XDataValue::String(format!("{prefix}<>{suffix}"))),
|
||||
);
|
||||
return true;
|
||||
}
|
||||
if field == "dim_tolerance_display" {
|
||||
let current_gap = inherited_real(doc, handle, DIMGAP).abs();
|
||||
let upper = inherited_real(doc, handle, DIMTP);
|
||||
let lower = inherited_real(doc, handle, DIMTM);
|
||||
let (tolerance, limits, basic) = match trimmed.to_ascii_lowercase().as_str() {
|
||||
"symmetrical" => {
|
||||
set(doc, handle, DIMTM, Some(XDataValue::Real(upper)));
|
||||
(1, 0, false)
|
||||
}
|
||||
"deviation" => {
|
||||
if (upper - lower).abs() <= 1e-12 {
|
||||
let distinct_lower = if upper.abs() > 1e-12 { 0.0 } else { 0.0001 };
|
||||
set(
|
||||
doc,
|
||||
handle,
|
||||
DIMTM,
|
||||
Some(XDataValue::Real(distinct_lower)),
|
||||
);
|
||||
}
|
||||
(1, 0, false)
|
||||
}
|
||||
"limits" => (0, 1, false),
|
||||
"basic" => (0, 0, true),
|
||||
"none" => (0, 0, false),
|
||||
_ => return false,
|
||||
};
|
||||
set(doc, handle, DIMTOL, Some(XDataValue::Integer16(tolerance)));
|
||||
set(doc, handle, DIMLIM, Some(XDataValue::Integer16(limits)));
|
||||
let gap = if basic {
|
||||
-current_gap.max(0.0001)
|
||||
} else {
|
||||
current_gap
|
||||
};
|
||||
set(doc, handle, DIMGAP, Some(XDataValue::Real(gap)));
|
||||
return true;
|
||||
}
|
||||
if let Some(code) = property_real_code(field) {
|
||||
if trimmed.is_empty() {
|
||||
set(doc, handle, code, None);
|
||||
} else if let Ok(number) = trimmed.parse::<f64>() {
|
||||
let number = if field == "dim_text_offset"
|
||||
&& inherited_real(doc, handle, DIMGAP) < 0.0
|
||||
{
|
||||
-number.abs()
|
||||
} else {
|
||||
number
|
||||
};
|
||||
set(doc, handle, code, Some(XDataValue::Real(number)));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if let Some(code) = property_string_code(field) {
|
||||
set(
|
||||
doc,
|
||||
handle,
|
||||
code,
|
||||
(!trimmed.is_empty()).then(|| XDataValue::String(value.to_string())),
|
||||
);
|
||||
return true;
|
||||
}
|
||||
if let Some(code) = property_int_code(field) {
|
||||
let parsed = match field {
|
||||
"dim_line_lineweight" | "dim_ext_line_lineweight" => {
|
||||
parse_lineweight_label(trimmed)
|
||||
}
|
||||
"dim_ext_line_fixed"
|
||||
| "dim_text_outside_align"
|
||||
| "dim_text_inside_align"
|
||||
| "dim_text_inside"
|
||||
| "dim_line_forced"
|
||||
| "dim_line_inside"
|
||||
| "dim_alt_enabled" => yes(trimmed),
|
||||
"dim_decimal_separator" => trimmed
|
||||
.chars()
|
||||
.next()
|
||||
.map(|character| character as i16),
|
||||
"dim_units" => match trimmed.to_ascii_lowercase().as_str() {
|
||||
"scientific" => Some(1),
|
||||
"decimal" => Some(2),
|
||||
"engineering" => Some(3),
|
||||
"architectural" => Some(4),
|
||||
"fractional" => Some(5),
|
||||
"desktop" => Some(6),
|
||||
_ => trimmed.parse().ok(),
|
||||
},
|
||||
"dim_fractional_type" => match trimmed.to_ascii_lowercase().as_str() {
|
||||
"horizontal" => Some(0),
|
||||
"diagonal" => Some(1),
|
||||
"not stacked" => Some(2),
|
||||
_ => trimmed.parse().ok(),
|
||||
},
|
||||
"dim_text_view_direction" => match trimmed.to_ascii_lowercase().as_str() {
|
||||
"left-to-right" => Some(0),
|
||||
"right-to-left" => Some(1),
|
||||
_ => trimmed.parse().ok(),
|
||||
},
|
||||
"dim_text_pos_vert" => match trimmed.to_ascii_lowercase().as_str() {
|
||||
"centered" => Some(0),
|
||||
"above" => Some(1),
|
||||
"outside" => Some(2),
|
||||
"jis" => Some(3),
|
||||
"below" => Some(4),
|
||||
_ => trimmed.parse().ok(),
|
||||
},
|
||||
"dim_text_pos_hor" => match trimmed.to_ascii_lowercase().as_str() {
|
||||
"centered" => Some(0),
|
||||
"at extension line 1" => Some(1),
|
||||
"at extension line 2" => Some(2),
|
||||
"over extension line 1" => Some(3),
|
||||
"over extension line 2" => Some(4),
|
||||
_ => trimmed.parse().ok(),
|
||||
},
|
||||
"dim_fit" => match trimmed.to_ascii_lowercase().as_str() {
|
||||
"both text and arrows" => Some(0),
|
||||
"arrows" => Some(1),
|
||||
"text" => Some(2),
|
||||
"best fit" => Some(3),
|
||||
_ => trimmed.parse().ok(),
|
||||
},
|
||||
"dim_text_movement" => match trimmed.to_ascii_lowercase().as_str() {
|
||||
"keep dim line with text" => Some(0),
|
||||
"move text, add leader" => Some(1),
|
||||
"move text, no leader" => Some(2),
|
||||
_ => trimmed.parse().ok(),
|
||||
},
|
||||
"dim_alt_format" => match trimmed.to_ascii_lowercase().as_str() {
|
||||
"scientific" => Some(1),
|
||||
"decimal" => Some(2),
|
||||
"engineering" => Some(3),
|
||||
"architectural stacked" => Some(4),
|
||||
"fractional stacked" => Some(5),
|
||||
"architectural" => Some(6),
|
||||
"fractional" => Some(7),
|
||||
"desktop" => Some(8),
|
||||
_ => trimmed.parse().ok(),
|
||||
},
|
||||
"dim_tolerance_pos_vert" => match trimmed.to_ascii_lowercase().as_str() {
|
||||
"bottom" => Some(0),
|
||||
"middle" => Some(1),
|
||||
"top" => Some(2),
|
||||
_ => trimmed.parse().ok(),
|
||||
},
|
||||
"dim_tolerance_alignment" => match trimmed.to_ascii_lowercase().as_str() {
|
||||
"align decimal separators" => Some(0),
|
||||
"align operational symbols" => Some(1),
|
||||
_ => trimmed.parse().ok(),
|
||||
},
|
||||
_ => trimmed.parse().ok(),
|
||||
};
|
||||
let Some(number) = parsed else {
|
||||
return false;
|
||||
};
|
||||
set(doc, handle, code, Some(XDataValue::Integer16(number)));
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn parse_lineweight_label(value: &str) -> Option<i16> {
|
||||
match value.trim() {
|
||||
"ByLayer" => Some(-1),
|
||||
"ByBlock" => Some(-2),
|
||||
"Default" => Some(-3),
|
||||
value => value
|
||||
.trim_end_matches("mm")
|
||||
.trim()
|
||||
.parse::<f64>()
|
||||
.ok()
|
||||
.map(|millimetres| (millimetres * 100.0).round() as i16),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,7 @@
|
|||
use acadrust::entities::{Dimension, DimensionLinear};
|
||||
use acadrust::types::Vector3;
|
||||
use acadrust::EntityType;
|
||||
use cadkernel::geom2d::{closest_point, Curve, Line as KernelLine};
|
||||
|
||||
use crate::command::{CadCommand, CmdResult, WorkingPlane};
|
||||
use crate::modules::{IconKind, ModuleEvent, ToolDef};
|
||||
|
|
@ -57,6 +58,25 @@ enum Step {
|
|||
DimensionLine { first: DVec3, second: DVec3 },
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum AxisMode {
|
||||
Automatic,
|
||||
Horizontal,
|
||||
Vertical,
|
||||
Rotated(f64),
|
||||
}
|
||||
|
||||
impl AxisMode {
|
||||
fn axis(self, first: DVec3, second: DVec3, point: DVec3) -> DVec3 {
|
||||
match self {
|
||||
Self::Automatic => measure_axis(first, second, point),
|
||||
Self::Horizontal => DVec3::X,
|
||||
Self::Vertical => DVec3::Y,
|
||||
Self::Rotated(angle) => DVec3::new(angle.cos(), angle.sin(), 0.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LinearDimensionCommand {
|
||||
step: Step,
|
||||
plane: WorkingPlane,
|
||||
|
|
@ -68,6 +88,13 @@ pub struct LinearDimensionCommand {
|
|||
text_angle: Option<f64>,
|
||||
/// True while the next typed value is captured as the text angle.
|
||||
awaiting_angle: bool,
|
||||
/// True while a rotation value for the Rotated option is being entered.
|
||||
awaiting_rotation: bool,
|
||||
axis_mode: AxisMode,
|
||||
selecting_object: bool,
|
||||
picked_entity: Option<EntityType>,
|
||||
source_handle: Option<acadrust::Handle>,
|
||||
mtext_override: bool,
|
||||
}
|
||||
|
||||
impl LinearDimensionCommand {
|
||||
|
|
@ -79,6 +106,12 @@ impl LinearDimensionCommand {
|
|||
awaiting_text: false,
|
||||
text_angle: None,
|
||||
awaiting_angle: false,
|
||||
awaiting_rotation: false,
|
||||
axis_mode: AxisMode::Automatic,
|
||||
selecting_object: false,
|
||||
picked_entity: None,
|
||||
source_handle: None,
|
||||
mtext_override: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -94,18 +127,32 @@ impl CadCommand for LinearDimensionCommand {
|
|||
|
||||
fn prompt(&self) -> String {
|
||||
if self.awaiting_text {
|
||||
return t!("DIMLINEAR Enter dimension text (blank = measured value):").into_owned();
|
||||
return if self.mtext_override {
|
||||
t!("DIMLINEAR Enter formatted dimension text (blank = measured value):")
|
||||
.into_owned()
|
||||
} else {
|
||||
t!("DIMLINEAR Enter dimension text (blank = measured value):").into_owned()
|
||||
};
|
||||
}
|
||||
if self.awaiting_angle {
|
||||
return t!("DIMLINEAR Specify text angle (degrees):").into_owned();
|
||||
}
|
||||
if self.awaiting_rotation {
|
||||
return t!("DIMLINEAR Specify dimension line angle (degrees):").into_owned();
|
||||
}
|
||||
if self.selecting_object {
|
||||
return t!("DIMLINEAR Select object to dimension:").into_owned();
|
||||
}
|
||||
match self.step {
|
||||
Step::FirstPoint => t!("DIMLINEAR Specify first extension line origin:").into_owned(),
|
||||
Step::FirstPoint => {
|
||||
t!("DIMLINEAR Specify first extension line origin or press Enter to select object:")
|
||||
.into_owned()
|
||||
}
|
||||
Step::SecondPoint(_) => {
|
||||
t!("DIMLINEAR Specify second extension line origin [Text/Angle]:").into_owned()
|
||||
t!("DIMLINEAR Specify second extension line origin:").into_owned()
|
||||
}
|
||||
Step::DimensionLine { .. } => {
|
||||
t!("DIMLINEAR Specify dimension line location [Text/Angle]:").into_owned()
|
||||
t!("DIMLINEAR Specify dimension line location [Mtext/Text/Angle/Horizontal/Vertical/Rotated]:").into_owned()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -117,6 +164,9 @@ impl CadCommand for LinearDimensionCommand {
|
|||
CmdResult::NeedPoint
|
||||
}
|
||||
Step::SecondPoint(first) => {
|
||||
if pt.distance_squared(first) <= 1e-24 {
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
self.step = Step::DimensionLine { first, second: pt };
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
|
|
@ -125,21 +175,29 @@ impl CadCommand for LinearDimensionCommand {
|
|||
let second = self.plane.to_local(second);
|
||||
let pt = self.plane.to_local(pt);
|
||||
let mut dim = DimensionLinear::new(v3(first), v3(second));
|
||||
let axis = measure_axis(first, second, pt);
|
||||
let axis = self.axis_mode.axis(first, second, pt);
|
||||
dim.rotation = axis.y.atan2(axis.x);
|
||||
dim.definition_point = v3(pt);
|
||||
dim.base.definition_point = v3(pt);
|
||||
dim.set_offset(dimension_line_offset(second, pt, axis));
|
||||
dim.base.definition_point = dim.definition_point;
|
||||
dim.base.text_middle_point = v3(linear_text_pos(first, second, pt, axis));
|
||||
dim.base.insertion_point = dim.base.text_middle_point;
|
||||
dim.base.actual_measurement = dim.measurement();
|
||||
dim.base.user_text = self.text_override.clone();
|
||||
crate::entities::dimension::set_dimension_text_override(
|
||||
&mut dim.base,
|
||||
self.text_override.clone(),
|
||||
);
|
||||
// An explicit text angle overrides the UCS-derived rotation.
|
||||
if let Some(a) = self.text_angle {
|
||||
dim.base.text_rotation = a;
|
||||
}
|
||||
CmdResult::CommitAndExit(self.plane.place_entity(EntityType::Dimension(
|
||||
let entity = self.plane.place_entity(EntityType::Dimension(
|
||||
Dimension::Linear(dim),
|
||||
)))
|
||||
));
|
||||
if let Some(source) = self.source_handle {
|
||||
CmdResult::CommitAssociativeDimension { entity, source }
|
||||
} else {
|
||||
CmdResult::CommitAndExit(entity)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -154,6 +212,14 @@ impl CadCommand for LinearDimensionCommand {
|
|||
self.awaiting_angle = false;
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
if self.awaiting_rotation {
|
||||
self.awaiting_rotation = false;
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
if matches!(self.step, Step::FirstPoint) {
|
||||
self.selecting_object = true;
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
CmdResult::Cancel
|
||||
}
|
||||
|
||||
|
|
@ -168,7 +234,7 @@ impl CadCommand for LinearDimensionCommand {
|
|||
fn point_step_accepts_keywords(&self) -> bool {
|
||||
// While typing the override text or angle, route input as a value, not
|
||||
// a point pick / keyword.
|
||||
!self.awaiting_text && !self.awaiting_angle
|
||||
!self.awaiting_text && !self.awaiting_angle && !self.awaiting_rotation
|
||||
}
|
||||
|
||||
fn wants_text_with_spaces(&self) -> bool {
|
||||
|
|
@ -199,8 +265,24 @@ impl CadCommand for LinearDimensionCommand {
|
|||
self.awaiting_angle = false;
|
||||
return Some(CmdResult::NeedPoint);
|
||||
}
|
||||
if self.awaiting_rotation {
|
||||
if let Some(angle) = crate::entities::common::parse_typed_angle(text.trim()) {
|
||||
self.axis_mode = AxisMode::Rotated(angle);
|
||||
}
|
||||
self.awaiting_rotation = false;
|
||||
return Some(CmdResult::NeedPoint);
|
||||
}
|
||||
if !matches!(self.step, Step::DimensionLine { .. }) {
|
||||
return None;
|
||||
}
|
||||
match text.trim().to_uppercase().as_str() {
|
||||
"T" | "TEXT" | "M" | "MTEXT" => {
|
||||
"T" | "TEXT" => {
|
||||
self.mtext_override = false;
|
||||
self.awaiting_text = true;
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
"M" | "MTEXT" => {
|
||||
self.mtext_override = true;
|
||||
self.awaiting_text = true;
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
|
|
@ -208,10 +290,54 @@ impl CadCommand for LinearDimensionCommand {
|
|||
self.awaiting_angle = true;
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
"H" | "HORIZONTAL" => {
|
||||
self.axis_mode = AxisMode::Horizontal;
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
"V" | "VERTICAL" => {
|
||||
self.axis_mode = AxisMode::Vertical;
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
"R" | "ROTATED" => {
|
||||
self.awaiting_rotation = true;
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn needs_entity_pick(&self) -> bool {
|
||||
self.selecting_object
|
||||
}
|
||||
|
||||
fn entity_pick_highlights_hover(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn inject_before_entity_pick(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn inject_picked_entity(&mut self, entity: EntityType) {
|
||||
self.picked_entity = Some(entity);
|
||||
}
|
||||
|
||||
fn on_entity_pick(&mut self, handle: acadrust::Handle, point: DVec3) -> CmdResult {
|
||||
let Some(entity) = self.picked_entity.take() else {
|
||||
return CmdResult::NeedPoint;
|
||||
};
|
||||
let Some((first, second)) = dimension_source_points(&entity, point) else {
|
||||
return CmdResult::NeedPoint;
|
||||
};
|
||||
if first.distance_squared(second) <= 1e-24 {
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
self.source_handle = Some(handle);
|
||||
self.selecting_object = false;
|
||||
self.step = Step::DimensionLine { first, second };
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
match self.step {
|
||||
Step::FirstPoint => None,
|
||||
|
|
@ -220,7 +346,7 @@ impl CadCommand for LinearDimensionCommand {
|
|||
let first = self.plane.to_local(first);
|
||||
let second = self.plane.to_local(second);
|
||||
let pt = self.plane.to_local(pt);
|
||||
let axis = measure_axis(first, second, pt);
|
||||
let axis = self.axis_mode.axis(first, second, pt);
|
||||
let points = linear_dimension_preview(first, second, pt, axis)
|
||||
.into_iter()
|
||||
.map(|point| self.plane.to_world(point))
|
||||
|
|
@ -235,6 +361,97 @@ fn v3(pt: DVec3) -> Vector3 {
|
|||
Vector3::new(pt.x, pt.y, pt.z)
|
||||
}
|
||||
|
||||
fn dimension_line_offset(second: DVec3, point: DVec3, axis: DVec3) -> f64 {
|
||||
let perpendicular = DVec3::new(-axis.y, axis.x, 0.0);
|
||||
(point - second).dot(perpendicular)
|
||||
}
|
||||
|
||||
fn dimension_source_points(entity: &EntityType, click: DVec3) -> Option<(DVec3, DVec3)> {
|
||||
let point = |p: Vector3| DVec3::new(p.x, p.y, p.z);
|
||||
match entity {
|
||||
EntityType::Line(line) => Some((point(line.start), point(line.end))),
|
||||
EntityType::Arc(arc) => Some((point(arc.start_point_wcs()), point(arc.end_point_wcs()))),
|
||||
EntityType::LwPolyline(polyline) => nearest_planar_source(
|
||||
polyline
|
||||
.vertices
|
||||
.iter()
|
||||
.map(|vertex| [vertex.location.x, vertex.location.y]),
|
||||
polyline.is_closed,
|
||||
polyline.elevation,
|
||||
polyline.normal,
|
||||
click,
|
||||
),
|
||||
EntityType::Polyline2D(polyline) => {
|
||||
let vertices = crate::entities::polyline::drawn_vertices2d(polyline)
|
||||
.unwrap_or_else(|| polyline.vertices.clone());
|
||||
nearest_planar_source(
|
||||
vertices
|
||||
.iter()
|
||||
.map(|vertex| [vertex.location.x, vertex.location.y]),
|
||||
polyline.is_closed(),
|
||||
polyline.elevation,
|
||||
polyline.normal,
|
||||
click,
|
||||
)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn nearest_planar_source(
|
||||
points: impl IntoIterator<Item = [f64; 2]>,
|
||||
closed: bool,
|
||||
elevation: f64,
|
||||
normal: Vector3,
|
||||
click: DVec3,
|
||||
) -> Option<(DVec3, DVec3)> {
|
||||
let click = crate::scene::view::transform::wcs_point_to_ocs(
|
||||
(click.x, click.y, click.z),
|
||||
(normal.x, normal.y, normal.z),
|
||||
);
|
||||
let (first, second) = nearest_segment(points, closed, [click.0, click.1])?;
|
||||
Some((
|
||||
ocs_point(first, elevation, normal),
|
||||
ocs_point(second, elevation, normal),
|
||||
))
|
||||
}
|
||||
|
||||
fn nearest_segment(
|
||||
points: impl IntoIterator<Item = [f64; 2]>,
|
||||
closed: bool,
|
||||
click: [f64; 2],
|
||||
) -> Option<([f64; 2], [f64; 2])> {
|
||||
let points: Vec<_> = points.into_iter().collect();
|
||||
if points.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
let count = if closed { points.len() } else { points.len() - 1 };
|
||||
(0..count)
|
||||
.map(|index| {
|
||||
let first = points[index];
|
||||
let second = points[(index + 1) % points.len()];
|
||||
let distance = closest_point(
|
||||
&Curve::Line(KernelLine {
|
||||
start: first,
|
||||
end: second,
|
||||
}),
|
||||
click,
|
||||
)
|
||||
.distance;
|
||||
(distance, first, second)
|
||||
})
|
||||
.min_by(|a, b| a.0.total_cmp(&b.0))
|
||||
.map(|(_, first, second)| (first, second))
|
||||
}
|
||||
|
||||
fn ocs_point(point: [f64; 2], elevation: f64, normal: Vector3) -> DVec3 {
|
||||
let point = crate::scene::view::transform::ocs_point_to_wcs(
|
||||
(point[0], point[1], elevation),
|
||||
(normal.x, normal.y, normal.z),
|
||||
);
|
||||
DVec3::new(point.0, point.1, point.2)
|
||||
}
|
||||
|
||||
fn preview_wire(points: Vec<DVec3>) -> WireModel {
|
||||
WireModel {
|
||||
point_marker: None,
|
||||
|
|
@ -290,7 +507,23 @@ fn dim_line_endpoints(first: DVec3, second: DVec3, def: DVec3, axis: DVec3) -> (
|
|||
fn linear_dimension_preview(first: DVec3, second: DVec3, def: DVec3, axis: DVec3) -> Vec<DVec3> {
|
||||
let (d1, d2) = dim_line_endpoints(first, second, def, axis);
|
||||
let nan = DVec3::new(f64::NAN, f64::NAN, f64::NAN);
|
||||
vec![first, d1, nan, second, d2, nan, d1, d2]
|
||||
let arrow = 0.22;
|
||||
let perp = DVec3::new(-axis.y, axis.x, 0.0);
|
||||
let text = linear_text_pos(first, second, def, axis);
|
||||
let half_width = ((second - first).length().log10().max(0.0) + 1.0) * 0.18;
|
||||
let half_height = 0.16;
|
||||
vec![
|
||||
first, d1, nan, second, d2, nan, d1, d2, nan,
|
||||
d1, d1 + axis * arrow + perp * arrow * 0.45, nan,
|
||||
d1, d1 + axis * arrow - perp * arrow * 0.45, nan,
|
||||
d2, d2 - axis * arrow + perp * arrow * 0.45, nan,
|
||||
d2, d2 - axis * arrow - perp * arrow * 0.45, nan,
|
||||
text - axis * half_width - perp * half_height,
|
||||
text + axis * half_width - perp * half_height,
|
||||
text + axis * half_width + perp * half_height,
|
||||
text - axis * half_width + perp * half_height,
|
||||
text - axis * half_width - perp * half_height,
|
||||
]
|
||||
}
|
||||
|
||||
fn linear_text_pos(first: DVec3, second: DVec3, def: DVec3, axis: DVec3) -> DVec3 {
|
||||
|
|
|
|||
250
src/scene/dimension_assoc.rs
Normal file
250
src/scene/dimension_assoc.rs
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
use acadrust::entities::Dimension;
|
||||
use acadrust::objects::{
|
||||
AssocDimensionAssociation, AssocDimensionReference, AssociativeData,
|
||||
AssociativeObject, ObjectType,
|
||||
};
|
||||
use acadrust::types::{Handle, Vector3};
|
||||
use acadrust::EntityType;
|
||||
|
||||
use super::{ChangeKind, Scene};
|
||||
|
||||
fn point_distance_squared(first: Vector3, second: Vector3) -> f64 {
|
||||
let dx = first.x - second.x;
|
||||
let dy = first.y - second.y;
|
||||
let dz = first.z - second.z;
|
||||
dx * dx + dy * dy + dz * dz
|
||||
}
|
||||
|
||||
fn ocs_point(x: f64, y: f64, elevation: f64, normal: Vector3) -> Vector3 {
|
||||
let point = crate::scene::view::transform::ocs_point_to_wcs(
|
||||
(x, y, elevation),
|
||||
(normal.x, normal.y, normal.z),
|
||||
);
|
||||
Vector3::new(point.0, point.1, point.2)
|
||||
}
|
||||
|
||||
fn source_points(entity: &EntityType) -> Vec<Vector3> {
|
||||
match entity {
|
||||
EntityType::Line(line) => vec![line.start, line.end],
|
||||
EntityType::Arc(arc) => vec![arc.start_point_wcs(), arc.end_point_wcs()],
|
||||
EntityType::LwPolyline(polyline) => polyline
|
||||
.vertices
|
||||
.iter()
|
||||
.map(|vertex| {
|
||||
ocs_point(
|
||||
vertex.location.x,
|
||||
vertex.location.y,
|
||||
polyline.elevation,
|
||||
polyline.normal,
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
EntityType::Polyline2D(polyline) => polyline
|
||||
.vertices
|
||||
.iter()
|
||||
.map(|vertex| {
|
||||
ocs_point(
|
||||
vertex.location.x,
|
||||
vertex.location.y,
|
||||
polyline.elevation,
|
||||
polyline.normal,
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn source_marker(entity: &EntityType, point: Vector3) -> Option<i32> {
|
||||
source_points(entity)
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.min_by(|(_, first), (_, second)| {
|
||||
point_distance_squared(*first, point)
|
||||
.total_cmp(&point_distance_squared(*second, point))
|
||||
})
|
||||
.map(|(index, _)| index as i32)
|
||||
}
|
||||
|
||||
fn resolve_reference(scene: &Scene, reference: &AssocDimensionReference) -> Option<Vector3> {
|
||||
let source = *reference.xrefs.first()?;
|
||||
let entity = scene.document.get_entity(source)?;
|
||||
source_points(entity)
|
||||
.get(reference.main_gs_marker.max(0) as usize)
|
||||
.copied()
|
||||
}
|
||||
|
||||
pub(crate) fn dimension_is_associative(
|
||||
document: &acadrust::CadDocument,
|
||||
dimension: Handle,
|
||||
) -> bool {
|
||||
document.objects.values().any(|object| {
|
||||
let ObjectType::Associative(object) = object else {
|
||||
return false;
|
||||
};
|
||||
let AssociativeData::DimensionAssociation(association) = &object.data else {
|
||||
return false;
|
||||
};
|
||||
association.dimension == dimension
|
||||
&& association.associativity != 0
|
||||
&& association.references.iter().flatten().any(|reference| {
|
||||
reference
|
||||
.xrefs
|
||||
.iter()
|
||||
.any(|source| document.get_entity(*source).is_some())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
impl Scene {
|
||||
pub(crate) fn attach_linear_dimension_association(
|
||||
&mut self,
|
||||
dimension: Handle,
|
||||
sources: [Option<Handle>; 2],
|
||||
) {
|
||||
let Some(EntityType::Dimension(Dimension::Linear(linear))) =
|
||||
self.document.get_entity(dimension)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let first_point = linear.first_point;
|
||||
let second_point = linear.second_point;
|
||||
let source_data = [first_point, second_point].map(|point| point);
|
||||
let resolved: [Option<(Handle, i32)>; 2] = std::array::from_fn(|index| {
|
||||
let source = sources[index]?;
|
||||
let entity = self.document.get_entity(source)?;
|
||||
source_marker(entity, source_data[index]).map(|marker| (source, marker))
|
||||
});
|
||||
if resolved.iter().all(Option::is_none) {
|
||||
return;
|
||||
}
|
||||
|
||||
let reference = |source: Handle, marker: i32, point: Vector3| AssocDimensionReference {
|
||||
class_name: "AcDbOsnapPointRef".to_string(),
|
||||
osnap_type: 1,
|
||||
xrefs: vec![source],
|
||||
main_subent_type: 1,
|
||||
main_gs_marker: marker,
|
||||
osnap_point: point,
|
||||
..AssocDimensionReference::default()
|
||||
};
|
||||
let mut references: [Vec<AssocDimensionReference>; 4] =
|
||||
std::array::from_fn(|_| Vec::new());
|
||||
let mut associativity = 0;
|
||||
for (index, resolved) in resolved.into_iter().enumerate() {
|
||||
if let Some((source, marker)) = resolved {
|
||||
associativity |= 1 << index;
|
||||
references[index].push(reference(source, marker, source_data[index]));
|
||||
}
|
||||
}
|
||||
|
||||
let association_handle = self.document.allocate_handle();
|
||||
let mut object = AssociativeObject::new("DIMASSOC", "AcDbDimAssoc");
|
||||
object.handle = association_handle;
|
||||
object.reactors.push(dimension);
|
||||
object.data = AssociativeData::DimensionAssociation(AssocDimensionAssociation {
|
||||
associativity,
|
||||
dimension,
|
||||
references,
|
||||
..AssocDimensionAssociation::default()
|
||||
});
|
||||
self.document
|
||||
.objects
|
||||
.insert(association_handle, ObjectType::Associative(object));
|
||||
|
||||
let mut reactor_targets = vec![dimension];
|
||||
reactor_targets.extend(sources.into_iter().flatten());
|
||||
reactor_targets.sort_by_key(|handle| handle.value());
|
||||
reactor_targets.dedup();
|
||||
for handle in reactor_targets {
|
||||
if let Some(entity) = self.document.get_entity_mut(handle) {
|
||||
if !entity.common().reactors.contains(&association_handle) {
|
||||
entity.common_mut().reactors.push(association_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn infer_linear_dimension_sources(
|
||||
&self,
|
||||
dimension: Handle,
|
||||
) -> [Option<Handle>; 2] {
|
||||
let Some(EntityType::Dimension(Dimension::Linear(linear))) =
|
||||
self.document.get_entity(dimension)
|
||||
else {
|
||||
return [None, None];
|
||||
};
|
||||
[linear.first_point, linear.second_point].map(|point| {
|
||||
self.document
|
||||
.entities()
|
||||
.filter(|entity| entity.common().handle != dimension)
|
||||
.filter_map(|entity| {
|
||||
source_points(entity)
|
||||
.into_iter()
|
||||
.map(|candidate| point_distance_squared(candidate, point))
|
||||
.min_by(f64::total_cmp)
|
||||
.map(|distance| (distance, entity.common().handle))
|
||||
})
|
||||
.filter(|(distance, _)| *distance <= 1e-16)
|
||||
.min_by(|first, second| first.0.total_cmp(&second.0))
|
||||
.map(|(_, handle)| handle)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn refresh_associative_dimensions(
|
||||
&mut self,
|
||||
changes: &[(Handle, ChangeKind)],
|
||||
) -> Vec<(Handle, ChangeKind)> {
|
||||
let changed: rustc_hash::FxHashSet<_> =
|
||||
changes.iter().map(|(handle, _)| *handle).collect();
|
||||
if changed.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
let associations: Vec<_> = self
|
||||
.document
|
||||
.objects
|
||||
.values()
|
||||
.filter_map(|object| {
|
||||
let ObjectType::Associative(object) = object else {
|
||||
return None;
|
||||
};
|
||||
let AssociativeData::DimensionAssociation(association) = &object.data else {
|
||||
return None;
|
||||
};
|
||||
association
|
||||
.references
|
||||
.iter()
|
||||
.flatten()
|
||||
.any(|reference| reference.xrefs.iter().any(|handle| changed.contains(handle)))
|
||||
.then_some(association.clone())
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut refreshed = Vec::new();
|
||||
for association in associations {
|
||||
let first = association.references[0]
|
||||
.first()
|
||||
.and_then(|reference| resolve_reference(self, reference));
|
||||
let second = association.references[1]
|
||||
.first()
|
||||
.and_then(|reference| resolve_reference(self, reference));
|
||||
if first.is_none() && second.is_none() {
|
||||
continue;
|
||||
}
|
||||
if let Some(EntityType::Dimension(Dimension::Linear(linear))) =
|
||||
self.document.get_entity_mut(association.dimension)
|
||||
{
|
||||
if let Some(first) = first {
|
||||
linear.first_point = first;
|
||||
}
|
||||
if let Some(second) = second {
|
||||
linear.second_point = second;
|
||||
}
|
||||
linear.base.actual_measurement = linear.measurement();
|
||||
linear.base.definition_point = linear.definition_point;
|
||||
refreshed.push((association.dimension, ChangeKind::Modified));
|
||||
}
|
||||
}
|
||||
refreshed
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ pub mod view;
|
|||
mod boundary;
|
||||
mod camera_ops;
|
||||
pub(crate) mod centerline;
|
||||
pub(crate) mod dimension_assoc;
|
||||
pub(crate) mod centermark;
|
||||
mod entity;
|
||||
mod group_layer;
|
||||
|
|
@ -2400,6 +2401,11 @@ impl Scene {
|
|||
changes.push(change);
|
||||
}
|
||||
}
|
||||
for change in self.refresh_associative_dimensions(&changes) {
|
||||
if !changes.iter().any(|(handle, _)| *handle == change.0) {
|
||||
changes.push(change);
|
||||
}
|
||||
}
|
||||
for change in self.refresh_associative_hatches(&changes) {
|
||||
if !changes.iter().any(|(handle, _)| *handle == change.0) {
|
||||
changes.push(change);
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ use crate::t;
|
|||
pub struct ColorExtras {
|
||||
pub by_layer: bool,
|
||||
pub by_block: bool,
|
||||
pub none: bool,
|
||||
pub background: bool,
|
||||
}
|
||||
|
||||
/// Encode a colour as the ACI integer string the style editors store
|
||||
|
|
@ -38,7 +40,7 @@ pub fn iced_to_acad_color(color: Color) -> AcadColor {
|
|||
AcadColor::Rgb { r, g, b }
|
||||
}
|
||||
|
||||
/// Return the closest AutoCAD Color Index for an RGB colour.
|
||||
/// Return the closest CAD Color Index for an RGB colour.
|
||||
pub fn nearest_aci(r: u8, g: u8, b: u8) -> u8 {
|
||||
let mut best = 7;
|
||||
let mut best_distance = u32::MAX;
|
||||
|
|
@ -209,7 +211,27 @@ pub fn color_list<'a>(
|
|||
.into()
|
||||
};
|
||||
|
||||
let logical_row = |color: AcadColor, label: &'static str| -> Element<'a, Message> {
|
||||
let (bg, _) = acad_color_display(color);
|
||||
button(
|
||||
row![swatch(bg), text(t!(label)).size(11)]
|
||||
.spacing(5)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(on_select(color))
|
||||
.style(list_row_style)
|
||||
.padding([2, 4])
|
||||
.width(Length::Fill)
|
||||
.into()
|
||||
};
|
||||
|
||||
let mut list = column![].spacing(1);
|
||||
if extras.none {
|
||||
list = list.push(named_row(AcadColor::None));
|
||||
}
|
||||
if extras.background {
|
||||
list = list.push(logical_row(AcadColor::ByBlock, "Background"));
|
||||
}
|
||||
if extras.by_layer {
|
||||
list = list.push(named_row(AcadColor::ByLayer));
|
||||
}
|
||||
|
|
@ -297,7 +319,7 @@ pub fn index_color_page<'a>(
|
|||
.into()
|
||||
};
|
||||
|
||||
// AutoCAD's first standard indexed colours.
|
||||
// The first standard indexed CAD colours.
|
||||
let mut standard = row![].spacing(4);
|
||||
for idx in 1u8..=9 {
|
||||
standard = standard.push(swatch_button(AcadColor::Index(idx), 22.0));
|
||||
|
|
@ -402,7 +424,7 @@ pub fn index_color_page<'a>(
|
|||
tabs,
|
||||
text("Standard colors").size(11),
|
||||
standard,
|
||||
text("AutoCAD Color Index (ACI) 10–249").size(11),
|
||||
text("CAD Color Index (ACI) 10–249").size(11),
|
||||
|
||||
text("Color family → · Shade / intensity ↓")
|
||||
.size(9)
|
||||
|
|
|
|||
|
|
@ -834,6 +834,7 @@ impl PropertiesPanel {
|
|||
crate::ui::color_select::ColorExtras {
|
||||
by_layer: true,
|
||||
by_block: true,
|
||||
..Default::default()
|
||||
},
|
||||
Message::PropBgColorChanged,
|
||||
Message::PropBgColorPickerToggle,
|
||||
|
|
@ -851,18 +852,35 @@ impl PropertiesPanel {
|
|||
// entity's main colour. Used by hatch gradient colours and the dim-line
|
||||
// colour override (Leader / Dimension). Dim colours legitimately take
|
||||
// ByLayer / ByBlock; gradient colours do not.
|
||||
if field == "gradient_color_1" || field == "gradient_color_2" || field == "dim_line_color" {
|
||||
if matches!(
|
||||
field,
|
||||
"gradient_color_1"
|
||||
| "gradient_color_2"
|
||||
| "dim_line_color"
|
||||
| "dim_ext_line_color"
|
||||
| "dim_text_color"
|
||||
| "dim_text_fill_color"
|
||||
) {
|
||||
let open = self.open_color_field.as_deref() == Some(field);
|
||||
let fsel = field.to_string();
|
||||
let extras = if field == "dim_line_color" {
|
||||
let full_palette_field = field.to_string();
|
||||
let extras = if field == "dim_text_fill_color" {
|
||||
crate::ui::color_select::ColorExtras {
|
||||
none: true,
|
||||
background: true,
|
||||
..Default::default()
|
||||
}
|
||||
} else if field.starts_with("dim_") {
|
||||
crate::ui::color_select::ColorExtras {
|
||||
by_layer: true,
|
||||
by_block: true,
|
||||
..Default::default()
|
||||
}
|
||||
} else {
|
||||
crate::ui::color_select::ColorExtras {
|
||||
by_layer: false,
|
||||
by_block: false,
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
let selector = crate::ui::color_select::color_selector(
|
||||
|
|
@ -874,7 +892,10 @@ impl PropertiesPanel {
|
|||
color: c,
|
||||
},
|
||||
Message::PropColorFieldToggle(field.to_string()),
|
||||
Message::PropColorFieldToggle(field.to_string()),
|
||||
Message::OpenColorWindow(
|
||||
crate::app::ColorPickTarget::PropertiesField(full_palette_field),
|
||||
color,
|
||||
),
|
||||
);
|
||||
return prop_row_widget(label, selector);
|
||||
}
|
||||
|
|
@ -884,6 +905,7 @@ impl PropertiesPanel {
|
|||
crate::ui::color_select::ColorExtras {
|
||||
by_layer: true,
|
||||
by_block: true,
|
||||
..Default::default()
|
||||
},
|
||||
Message::PropColorChanged,
|
||||
Message::PropColorPickerToggle,
|
||||
|
|
|
|||
|
|
@ -1135,6 +1135,7 @@ impl Ribbon {
|
|||
crate::ui::color_select::ColorExtras {
|
||||
by_layer: true,
|
||||
by_block: true,
|
||||
..Default::default()
|
||||
},
|
||||
Message::RibbonColorChanged,
|
||||
Message::OpenColorWindow(
|
||||
|
|
|
|||
|
|
@ -518,6 +518,7 @@ pub fn view_window<'a>(
|
|||
crate::ui::color_select::ColorExtras {
|
||||
by_layer: true,
|
||||
by_block: true,
|
||||
..Default::default()
|
||||
},
|
||||
move |c| Message::DsEdit(f_sel.clone(), crate::ui::color_select::color_to_aci_string(c)),
|
||||
Message::DsColorMore(fld.clone()),
|
||||
|
|
|
|||
|
|
@ -115,7 +115,11 @@ fn color_row<'a>(
|
|||
let selector = crate::ui::color_select::color_selector(
|
||||
current,
|
||||
open,
|
||||
crate::ui::color_select::ColorExtras { by_layer: true, by_block: true },
|
||||
crate::ui::color_select::ColorExtras {
|
||||
by_layer: true,
|
||||
by_block: true,
|
||||
..Default::default()
|
||||
},
|
||||
move |color| Message::MLeaderStyleEdit {
|
||||
field,
|
||||
value: crate::ui::color_select::color_to_aci_string(color),
|
||||
|
|
|
|||
|
|
@ -317,7 +317,11 @@ fn cell_editor<'a>(v: &TableStyleView<'a>, row_index: u8) -> Element<'a, Message
|
|||
let selector = crate::ui::color_select::color_selector(
|
||||
current,
|
||||
v.color_open == Some((row_index, field)),
|
||||
crate::ui::color_select::ColorExtras { by_layer: true, by_block: true },
|
||||
crate::ui::color_select::ColorExtras {
|
||||
by_layer: true,
|
||||
by_block: true,
|
||||
..Default::default()
|
||||
},
|
||||
move |color| Message::TableStyleCellEdit {
|
||||
row: row_index,
|
||||
field,
|
||||
|
|
|
|||
|
|
@ -412,6 +412,7 @@ fn editor_layer_row<'a>(
|
|||
crate::ui::color_select::ColorExtras {
|
||||
by_layer: false,
|
||||
by_block: false,
|
||||
..Default::default()
|
||||
},
|
||||
move |color| Message::LayerStateEditorLayerColor(index, color),
|
||||
Message::LayerStateEditorLayerColorToggle(index),
|
||||
|
|
|
|||
|
|
@ -813,6 +813,7 @@ fn layer_row<'a>(
|
|||
crate::ui::color_select::ColorExtras {
|
||||
by_layer: false,
|
||||
by_block: false,
|
||||
..Default::default()
|
||||
},
|
||||
Message::LayerColorSet,
|
||||
Message::LayerColorPickerToggle(index),
|
||||
|
|
|
|||
Loading…
Reference in a new issue