forked from 1b-agent/opencadstudio-upstream
feat(mline): align creation and editing
This commit is contained in:
commit
343b6cda7d
15 files changed
+2857
-316
No files matched your search
@@ -1170,6 +1170,15 @@ draw =
|
|||||||
.tag-val = [__ocs_fmt_0__] = __ocs_fmt_1__
|
.tag-val = [__ocs_fmt_0__] = __ocs_fmt_1__
|
||||||
.no-closed-boundary-found = ⚠ No closed boundary found.
|
.no-closed-boundary-found = ⚠ No closed boundary found.
|
||||||
.inverted = , inverted
|
.inverted = , inverted
|
||||||
|
.loaded-multiline-styles = Loaded multiline styles: __ocs_fmt_0__
|
||||||
|
.mline-current-settings = MLINE Current settings: Justification = __ocs_fmt_0__, Scale = __ocs_fmt_1__, Style = __ocs_fmt_2__
|
||||||
|
Specify start point or [Justification/Scale/STyle]:
|
||||||
|
.mline-enter-justification = MLINE Enter justification type [Top/Zero/Bottom] <__ocs_fmt_0__>:
|
||||||
|
.mline-enter-scale-factor-default = MLINE Enter scale factor <__ocs_fmt_0__>:
|
||||||
|
.mline-enter-style-name = MLINE Enter style name or [?] <__ocs_fmt_0__>:
|
||||||
|
.mline-specify-next-point-close-undo = MLINE Specify next point or [Close/Undo]:
|
||||||
|
.mline-specify-next-point-undo = MLINE Specify next point or [Undo]:
|
||||||
|
.multiline-style-was-not-found = Multiline style "__ocs_arg_style__" was not found.
|
||||||
.value-2-point = 2-Point
|
.value-2-point = 2-Point
|
||||||
.value-2d-solid = 2D Solid
|
.value-2d-solid = 2D Solid
|
||||||
.value-3-point = 3-Point
|
.value-3-point = 3-Point
|
||||||
@@ -1452,6 +1461,24 @@ draw =
|
|||||||
modify =
|
modify =
|
||||||
.count-objects = (__ocs_arg_count__ objects)
|
.count-objects = (__ocs_arg_count__ objects)
|
||||||
.edge-extend-indented-title-case = [Edge: Extend]
|
.edge-extend-indented-title-case = [Edge: Extend]
|
||||||
|
.add-vertex = Add Vertex
|
||||||
|
.closed-cross = Closed Cross
|
||||||
|
.closed-tee = Closed Tee
|
||||||
|
.corner-joint = Corner Joint
|
||||||
|
.cut-all = Cut All
|
||||||
|
.cut-single = Cut Single
|
||||||
|
.delete-vertex = Delete Vertex
|
||||||
|
.merged-cross = Merged Cross
|
||||||
|
.merged-tee = Merged Tee
|
||||||
|
.mledit-choose-an-edit-tool = MLEDIT Choose an edit tool:
|
||||||
|
.mledit-select-first-multiline = MLEDIT Select first multiline:
|
||||||
|
.mledit-select-second-multiline = MLEDIT Select second multiline:
|
||||||
|
.mledit-specify-end-of-weld-range = MLEDIT Specify the end of the weld range:
|
||||||
|
.mledit-specify-second-cut-point = MLEDIT Specify the second cut point:
|
||||||
|
.open-cross = Open Cross
|
||||||
|
.open-tee = Open Tee
|
||||||
|
.remove-vertex = Remove Vertex
|
||||||
|
.weld-all = Weld All
|
||||||
.align-scale-objects-based-on-alignment-points = ALIGN Scale objects based on alignment points? [Y/N]:
|
.align-scale-objects-based-on-alignment-points = ALIGN Scale objects based on alignment points? [Y/N]:
|
||||||
.align-select-objects-count-selected-enter-when = ALIGN Select objects (__ocs_arg_count__ selected, Enter when done):
|
.align-select-objects-count-selected-enter-when = ALIGN Select objects (__ocs_arg_count__ selected, Enter when done):
|
||||||
.align-specify-1st-destination-point = ALIGN Specify 1st destination point:
|
.align-specify-1st-destination-point = ALIGN Specify 1st destination point:
|
||||||
|
|||||||
@@ -1152,6 +1152,15 @@ draw =
|
|||||||
.tag-val = [__ocs_fmt_0__] = __ocs_fmt_1__
|
.tag-val = [__ocs_fmt_0__] = __ocs_fmt_1__
|
||||||
.no-closed-boundary-found = ⚠ Kapalı sınır bulunamadı.
|
.no-closed-boundary-found = ⚠ Kapalı sınır bulunamadı.
|
||||||
.inverted = , ters çevrilmiş
|
.inverted = , ters çevrilmiş
|
||||||
|
.loaded-multiline-styles = Yüklü çoklu çizgi stilleri: __ocs_fmt_0__
|
||||||
|
.mline-current-settings = MLINE Geçerli ayarlar: Hizalama = __ocs_fmt_0__, Ölçek = __ocs_fmt_1__, Stil = __ocs_fmt_2__
|
||||||
|
Başlangıç noktasını belirtin veya [Hizalama/Ölçek/Stil]:
|
||||||
|
.mline-enter-justification = MLINE Hizalama türünü girin [Üst/Sıfır/Alt] <__ocs_fmt_0__>:
|
||||||
|
.mline-enter-scale-factor-default = MLINE Ölçek katsayısını girin <__ocs_fmt_0__>:
|
||||||
|
.mline-enter-style-name = MLINE Stil adını veya [?] girin <__ocs_fmt_0__>:
|
||||||
|
.mline-specify-next-point-close-undo = MLINE Sonraki noktayı belirtin veya [Kapat/Geri al]:
|
||||||
|
.mline-specify-next-point-undo = MLINE Sonraki noktayı belirtin veya [Geri al]:
|
||||||
|
.multiline-style-was-not-found = "__ocs_arg_style__" çoklu çizgi stili bulunamadı.
|
||||||
.value-2-point = 2 Nokta
|
.value-2-point = 2 Nokta
|
||||||
.value-2d-solid = 2B Katı
|
.value-2d-solid = 2B Katı
|
||||||
.value-3-point = 3 Nokta
|
.value-3-point = 3 Nokta
|
||||||
@@ -1434,6 +1443,24 @@ draw =
|
|||||||
modify =
|
modify =
|
||||||
.count-objects = (__ocs_arg_count__ nesne)
|
.count-objects = (__ocs_arg_count__ nesne)
|
||||||
.edge-extend-indented-title-case = [Kenar: Uzat]
|
.edge-extend-indented-title-case = [Kenar: Uzat]
|
||||||
|
.add-vertex = Tepe Noktası Ekle
|
||||||
|
.closed-cross = Kapalı Kesişim
|
||||||
|
.closed-tee = Kapalı T Birleşimi
|
||||||
|
.corner-joint = Köşe Birleşimi
|
||||||
|
.cut-all = Tümünü Kes
|
||||||
|
.cut-single = Tekini Kes
|
||||||
|
.delete-vertex = Tepe Noktasını Sil
|
||||||
|
.merged-cross = Birleşik Kesişim
|
||||||
|
.merged-tee = Birleşik T Birleşimi
|
||||||
|
.mledit-choose-an-edit-tool = MLEDIT Bir düzenleme aracı seçin:
|
||||||
|
.mledit-select-first-multiline = MLEDIT İlk çoklu çizgiyi seçin:
|
||||||
|
.mledit-select-second-multiline = MLEDIT İkinci çoklu çizgiyi seçin:
|
||||||
|
.mledit-specify-end-of-weld-range = MLEDIT Kaynak aralığının sonunu belirtin:
|
||||||
|
.mledit-specify-second-cut-point = MLEDIT İkinci kesim noktasını belirtin:
|
||||||
|
.open-cross = Açık Kesişim
|
||||||
|
.open-tee = Açık T Birleşimi
|
||||||
|
.remove-vertex = Tepe Noktasını Kaldır
|
||||||
|
.weld-all = Tümünü Birleştir
|
||||||
.align-scale-objects-based-on-alignment-points = ALIGN Nesneler hizalama noktalarına göre ölçeklensin mi? [E/H]:
|
.align-scale-objects-based-on-alignment-points = ALIGN Nesneler hizalama noktalarına göre ölçeklensin mi? [E/H]:
|
||||||
.align-select-objects-count-selected-enter-when = ALIGN Nesneleri seçin (__ocs_arg_count__ seçili, bitirmek için Enter):
|
.align-select-objects-count-selected-enter-when = ALIGN Nesneleri seçin (__ocs_arg_count__ seçili, bitirmek için Enter):
|
||||||
.align-specify-1st-destination-point = ALIGN 1. hedef noktasını belirtin:
|
.align-specify-1st-destination-point = ALIGN 1. hedef noktasını belirtin:
|
||||||
|
|||||||
@@ -662,6 +662,26 @@ impl OpenCADStudio {
|
|||||||
tab.dirty = true;
|
tab.dirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let mline_settings = self.tabs[self.active_tab]
|
||||||
|
.active_cmd
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|command| command.mline_settings());
|
||||||
|
if let Some((scale, justification, style_name, style_handle)) = mline_settings {
|
||||||
|
let tab = &mut self.tabs[self.active_tab];
|
||||||
|
let header = &mut tab.scene.document.header;
|
||||||
|
let style_handle = style_handle.unwrap_or(acadrust::Handle::NULL);
|
||||||
|
let changed = (header.multiline_scale - scale).abs() > f64::EPSILON
|
||||||
|
|| header.multiline_justification != justification
|
||||||
|
|| !header.multiline_style.eq_ignore_ascii_case(&style_name)
|
||||||
|
|| header.current_multiline_style_handle != style_handle;
|
||||||
|
if changed {
|
||||||
|
header.multiline_scale = scale;
|
||||||
|
header.multiline_justification = justification;
|
||||||
|
header.multiline_style = style_name;
|
||||||
|
header.current_multiline_style_handle = style_handle;
|
||||||
|
tab.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
let was_active = self.tabs[self.active_tab].active_cmd.is_some();
|
let was_active = self.tabs[self.active_tab].active_cmd.is_some();
|
||||||
let preserve_selection =
|
let preserve_selection =
|
||||||
matches!(result, CmdResult::Relaunch(..) | CmdResult::Dispatch(..));
|
matches!(result, CmdResult::Relaunch(..) | CmdResult::Dispatch(..));
|
||||||
|
|||||||
+14
-14
@@ -13,27 +13,27 @@ impl OpenCADStudio {
|
|||||||
|
|
||||||
"MLINE" => {
|
"MLINE" => {
|
||||||
use crate::modules::draw::draw::mline::MlineCommand;
|
use crate::modules::draw::draw::mline::MlineCommand;
|
||||||
let style_name = self.tabs[i].scene.document.header.multiline_style.clone();
|
let header = &self.tabs[i].scene.document.header;
|
||||||
let style = self.tabs[i]
|
let style_name = header.multiline_style.clone();
|
||||||
|
let scale = header.multiline_scale;
|
||||||
|
let justification = header.multiline_justification;
|
||||||
|
let styles = self.tabs[i]
|
||||||
.scene
|
.scene
|
||||||
.document
|
.document
|
||||||
.objects
|
.objects
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|(handle, object)| match object {
|
.filter_map(|(handle, object)| match object {
|
||||||
acadrust::objects::ObjectType::MLineStyle(style)
|
acadrust::objects::ObjectType::MLineStyle(style) => {
|
||||||
if style.name.eq_ignore_ascii_case(&style_name) =>
|
Some((*handle, style.clone()))
|
||||||
{
|
|
||||||
Some((*handle, style.elements.len()))
|
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
});
|
})
|
||||||
let (style_handle, element_count) = style
|
.collect();
|
||||||
.map(|(handle, count)| (Some(handle), count))
|
let cmd_obj = MlineCommand::with_styles(
|
||||||
.unwrap_or((None, 2));
|
styles,
|
||||||
let cmd_obj = MlineCommand::with_style(
|
|
||||||
style_name,
|
style_name,
|
||||||
style_handle,
|
scale,
|
||||||
element_count,
|
justification,
|
||||||
);
|
);
|
||||||
self.command_line.push_info(&cmd_obj.prompt());
|
self.command_line.push_info(&cmd_obj.prompt());
|
||||||
self.tabs[i].active_cmd = Some(Box::new(cmd_obj));
|
self.tabs[i].active_cmd = Some(Box::new(cmd_obj));
|
||||||
|
|||||||
@@ -286,6 +286,34 @@ impl OpenCADStudio {
|
|||||||
self.tabs[i].active_cmd = Some(Box::new(cmd_obj));
|
self.tabs[i].active_cmd = Some(Box::new(cmd_obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"MLEDIT" => {
|
||||||
|
use crate::modules::draw::modify::mledit::{
|
||||||
|
MlineEditCommand, MlineEditTarget,
|
||||||
|
};
|
||||||
|
let document = &self.tabs[i].scene.document;
|
||||||
|
let targets = document
|
||||||
|
.entities()
|
||||||
|
.filter_map(|entity| {
|
||||||
|
let acadrust::EntityType::MLine(mline) = entity else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let style = crate::entities::mline::resolved_mline_style(mline, document)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(acadrust::objects::MLineStyle::standard);
|
||||||
|
Some((
|
||||||
|
entity.common().handle.value(),
|
||||||
|
MlineEditTarget {
|
||||||
|
entity: mline.clone(),
|
||||||
|
style,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let command = MlineEditCommand::new(targets);
|
||||||
|
self.command_line.push_info(&command.prompt());
|
||||||
|
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||||
|
}
|
||||||
|
|
||||||
"SPLINEDIT" => {
|
"SPLINEDIT" => {
|
||||||
use crate::modules::draw::modify::splinedit::SplineditCommand;
|
use crate::modules::draw::modify::splinedit::SplineditCommand;
|
||||||
let cmd_obj = SplineditCommand::new();
|
let cmd_obj = SplineditCommand::new();
|
||||||
|
|||||||
@@ -512,6 +512,8 @@ inventory::submit!(crate::command::CommandRegistration {
|
|||||||
"SHADEDGE",
|
"SHADEDGE",
|
||||||
"MAXACTVP",
|
"MAXACTVP",
|
||||||
"CMLJUST",
|
"CMLJUST",
|
||||||
|
"CMLSCALE",
|
||||||
|
"CMLSTYLE",
|
||||||
"TEXTQLTY",
|
"TEXTQLTY",
|
||||||
"SORTENTS",
|
"SORTENTS",
|
||||||
"FRAME",
|
"FRAME",
|
||||||
|
|||||||
@@ -920,6 +920,8 @@ impl OpenCADStudio {
|
|||||||
| "SHADEDGE"
|
| "SHADEDGE"
|
||||||
| "MAXACTVP"
|
| "MAXACTVP"
|
||||||
| "CMLJUST"
|
| "CMLJUST"
|
||||||
|
| "CMLSCALE"
|
||||||
|
| "CMLSTYLE"
|
||||||
| "TEXTQLTY"
|
| "TEXTQLTY"
|
||||||
| "SORTENTS"
|
| "SORTENTS"
|
||||||
| "FRAME"
|
| "FRAME"
|
||||||
@@ -1697,17 +1699,35 @@ impl OpenCADStudio {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CMLJUST" => match &value {
|
"CMLJUST" => match &value {
|
||||||
Some(v) => v
|
Some(v) => match v.parse::<i16>() {
|
||||||
.parse::<i16>()
|
Ok(x @ 0..=2) => {
|
||||||
.map(|x| {
|
|
||||||
h.multiline_justification = x;
|
h.multiline_justification = x;
|
||||||
(format!("CMLJUST = {x}"), true)
|
Ok((format!("CMLJUST = {x}"), true))
|
||||||
})
|
}
|
||||||
.map_err(|_| "SETVAR: integer value required.".into()),
|
_ => Err("SETVAR: integer value from 0 to 2 required.".into()),
|
||||||
|
},
|
||||||
None => {
|
None => {
|
||||||
Ok((format!("CMLJUST = {}", h.multiline_justification), false))
|
Ok((format!("CMLJUST = {}", h.multiline_justification), false))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"CMLSCALE" => match &value {
|
||||||
|
Some(v) => match v.parse::<f64>() {
|
||||||
|
Ok(x) if x.is_finite() => {
|
||||||
|
let changed = h.multiline_scale != x;
|
||||||
|
h.multiline_scale = x;
|
||||||
|
Ok((format!("CMLSCALE = {x}"), changed))
|
||||||
|
}
|
||||||
|
_ => Err("SETVAR: finite numeric value required.".into()),
|
||||||
|
},
|
||||||
|
None => Ok((format!("CMLSCALE = {}", h.multiline_scale), false)),
|
||||||
|
},
|
||||||
|
"CMLSTYLE" => match &value {
|
||||||
|
Some(_) => Err(
|
||||||
|
"SETVAR: CMLSTYLE is read-only here — use the MLSTYLE command."
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
|
None => Ok((format!("CMLSTYLE = {}", h.multiline_style), false)),
|
||||||
|
},
|
||||||
"TEXTQLTY" => match &value {
|
"TEXTQLTY" => match &value {
|
||||||
Some(v) => v
|
Some(v) => v
|
||||||
.parse::<i16>()
|
.parse::<i16>()
|
||||||
|
|||||||
@@ -2195,6 +2195,20 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||||||
crate::command::WorkingPlane::default()
|
crate::command::WorkingPlane::default()
|
||||||
};
|
};
|
||||||
for &handle in &handles {
|
for &handle in &handles {
|
||||||
|
let mline_style = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.get_entity(handle)
|
||||||
|
.and_then(|entity| match entity {
|
||||||
|
acadrust::EntityType::MLine(mline) => {
|
||||||
|
crate::entities::mline::resolved_mline_style(
|
||||||
|
mline,
|
||||||
|
&self.tabs[i].scene.document,
|
||||||
|
)
|
||||||
|
.cloned()
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(handle)
|
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(handle)
|
||||||
{
|
{
|
||||||
crate::scene::view::dispatch::apply_geom_prop_in_working_plane(
|
crate::scene::view::dispatch::apply_geom_prop_in_working_plane(
|
||||||
@@ -2203,6 +2217,17 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||||||
&value,
|
&value,
|
||||||
plane,
|
plane,
|
||||||
);
|
);
|
||||||
|
if matches!(field, "ml_justification" | "ml_scale") {
|
||||||
|
if let (
|
||||||
|
acadrust::EntityType::MLine(mline),
|
||||||
|
Some(style),
|
||||||
|
) = (entity, mline_style.as_ref())
|
||||||
|
{
|
||||||
|
crate::modules::draw::draw::mline::sync_mline_element_parameters(
|
||||||
|
mline, style,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2399,6 +2424,20 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||||||
)
|
)
|
||||||
.is_none()
|
.is_none()
|
||||||
{
|
{
|
||||||
|
let mline_style = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.get_entity(handle)
|
||||||
|
.and_then(|entity| match entity {
|
||||||
|
acadrust::EntityType::MLine(mline) => {
|
||||||
|
crate::entities::mline::resolved_mline_style(
|
||||||
|
mline,
|
||||||
|
&self.tabs[i].scene.document,
|
||||||
|
)
|
||||||
|
.cloned()
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
if let Some(entity) = self.tabs[i]
|
if let Some(entity) = self.tabs[i]
|
||||||
.scene
|
.scene
|
||||||
.document
|
.document
|
||||||
@@ -2410,6 +2449,17 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||||||
&val,
|
&val,
|
||||||
plane,
|
plane,
|
||||||
);
|
);
|
||||||
|
if field == "ml_scale" {
|
||||||
|
if let (
|
||||||
|
acadrust::EntityType::MLine(mline),
|
||||||
|
Some(style),
|
||||||
|
) = (entity, mline_style.as_ref())
|
||||||
|
{
|
||||||
|
crate::modules::draw::draw::mline::sync_mline_element_parameters(
|
||||||
|
mline, style,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1855,6 +1855,11 @@ pub trait CadCommand: Send {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Current drawing-persisted multiline creation settings.
|
||||||
|
fn mline_settings(&self) -> Option<(f64, i16, String, Option<Handle>)> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns `true` when the active text prompt expects free-form prose
|
/// Returns `true` when the active text prompt expects free-form prose
|
||||||
/// that can legitimately contain whitespace (the body of a TEXT /
|
/// that can legitimately contain whitespace (the body of a TEXT /
|
||||||
/// MTEXT / DDEDIT entity, an attribute default value, etc.). For
|
/// MTEXT / DDEDIT entity, an attribute default value, etc.). For
|
||||||
|
|||||||
+1034
-128
File diff suppressed because it is too large.
Load diff
@@ -1536,7 +1536,28 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
|
|||||||
"MLEADERSTYLE: '{}' not found." => Some(("layers", "mleaderstyle-not-found")),
|
"MLEADERSTYLE: '{}' not found." => Some(("layers", "mleaderstyle-not-found")),
|
||||||
"MLEADERSTYLE: current style set to '{name}'." => Some(("layers", "mleaderstyle-current-style-set-to-name")),
|
"MLEADERSTYLE: current style set to '{name}'." => Some(("layers", "mleaderstyle-current-style-set-to-name")),
|
||||||
"MLEADERSTYLE: unknown property '{}'. Try: text_height arrowhead_size landing_distance landing_gap" => Some(("layers", "mleaderstyle-unknown-property-try-text-height")),
|
"MLEADERSTYLE: unknown property '{}'. Try: text_height arrowhead_size landing_distance landing_gap" => Some(("layers", "mleaderstyle-unknown-property-try-text-height")),
|
||||||
|
"Add Vertex" => Some(("modify", "add-vertex")),
|
||||||
|
"Closed Cross" => Some(("modify", "closed-cross")),
|
||||||
|
"Closed Tee" => Some(("modify", "closed-tee")),
|
||||||
|
"Corner Joint" => Some(("modify", "corner-joint")),
|
||||||
|
"Cut All" => Some(("modify", "cut-all")),
|
||||||
|
"Cut Single" => Some(("modify", "cut-single")),
|
||||||
|
"Delete Vertex" => Some(("modify", "delete-vertex")),
|
||||||
|
"Loaded multiline styles: {}" => Some(("draw", "loaded-multiline-styles")),
|
||||||
|
"Merged Cross" => Some(("modify", "merged-cross")),
|
||||||
|
"Merged Tee" => Some(("modify", "merged-tee")),
|
||||||
|
"MLEDIT Choose an edit tool:" => Some(("modify", "mledit-choose-an-edit-tool")),
|
||||||
|
"MLEDIT Select first multiline:" => Some(("modify", "mledit-select-first-multiline")),
|
||||||
|
"MLEDIT Select second multiline:" => Some(("modify", "mledit-select-second-multiline")),
|
||||||
|
"MLEDIT Specify the end of the weld range:" => Some(("modify", "mledit-specify-end-of-weld-range")),
|
||||||
|
"MLEDIT Specify the second cut point:" => Some(("modify", "mledit-specify-second-cut-point")),
|
||||||
|
"MLINE Current settings: Justification = {}, Scale = {}, Style = {}\nSpecify start point or [Justification/Scale/STyle]:" => Some(("draw", "mline-current-settings")),
|
||||||
|
"MLINE Enter justification type [Top/Zero/Bottom] <{}>:" => Some(("draw", "mline-enter-justification")),
|
||||||
|
"MLINE Enter scale factor <{}>:" => Some(("draw", "mline-enter-scale-factor-default")),
|
||||||
"MLINE Enter scale factor:" => Some(("draw", "mline-enter-scale-factor")),
|
"MLINE Enter scale factor:" => Some(("draw", "mline-enter-scale-factor")),
|
||||||
|
"MLINE Enter style name or [?] <{}>:" => Some(("draw", "mline-enter-style-name")),
|
||||||
|
"MLINE Specify next point or [Close/Undo]:" => Some(("draw", "mline-specify-next-point-close-undo")),
|
||||||
|
"MLINE Specify next point or [Undo]:" => Some(("draw", "mline-specify-next-point-undo")),
|
||||||
"MLINE Specify next point (%{count} pts, Enter to finish, C to close, S to set scale):" => Some(("draw", "mline-specify-next-point-count-pts-enter-to-fi")),
|
"MLINE Specify next point (%{count} pts, Enter to finish, C to close, S to set scale):" => Some(("draw", "mline-specify-next-point-count-pts-enter-to-fi")),
|
||||||
"MLINE Specify start point (scale=%{scale}):" => Some(("draw", "mline-specify-start-point-scale-scale")),
|
"MLINE Specify start point (scale=%{scale}):" => Some(("draw", "mline-specify-start-point-scale-scale")),
|
||||||
"MLSTYLE: '{}' already exists." => Some(("layers", "mlstyle-already-exists")),
|
"MLSTYLE: '{}' already exists." => Some(("layers", "mlstyle-already-exists")),
|
||||||
@@ -1646,7 +1667,12 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
|
|||||||
"Multileader Style Manager" => Some(("styles", "multileader-style-manager")),
|
"Multileader Style Manager" => Some(("styles", "multileader-style-manager")),
|
||||||
"Multileader draw order:" => Some(("annotate", "multileader-draw-order")),
|
"Multileader draw order:" => Some(("annotate", "multileader-draw-order")),
|
||||||
"Multileader style" => Some(("properties", "multileader-style")),
|
"Multileader style" => Some(("properties", "multileader-style")),
|
||||||
|
"Multiline style \"%{style}\" was not found." => Some(("draw", "multiline-style-was-not-found")),
|
||||||
"Multiline\nText" => Some(("annotate", "multiline-text")),
|
"Multiline\nText" => Some(("annotate", "multiline-text")),
|
||||||
|
"Open Cross" => Some(("modify", "open-cross")),
|
||||||
|
"Open Tee" => Some(("modify", "open-tee")),
|
||||||
|
"Remove Vertex" => Some(("modify", "remove-vertex")),
|
||||||
|
"Weld All" => Some(("modify", "weld-all")),
|
||||||
"Multiline Style Manager" => Some(("styles", "multiline-style-manager")),
|
"Multiline Style Manager" => Some(("styles", "multiline-style-manager")),
|
||||||
"Multiple" => Some(("common", "multiple")),
|
"Multiple" => Some(("common", "multiple")),
|
||||||
"Multiple lines" => Some(("properties", "multiple-lines")),
|
"Multiple lines" => Some(("properties", "multiple-lines")),
|
||||||
|
|||||||
+405
-168
@@ -1,58 +1,217 @@
|
|||||||
// MLINE command — create a multiline (parallel lines).
|
// MLINE command — create a styled group of parallel lines.
|
||||||
//
|
|
||||||
// Workflow: pick vertices, Enter to finish.
|
|
||||||
// Text input (when >= 1 point picked):
|
|
||||||
// C / CLOSE → close and commit
|
|
||||||
// S <value> → set scale factor then continue picking
|
|
||||||
|
|
||||||
use acadrust::entities::MLine;
|
use acadrust::entities::{MLine, MLineFlags, MLineJustification, MLineSegment, MLineVertex};
|
||||||
|
use acadrust::objects::MLineStyle;
|
||||||
use acadrust::types::Vector3;
|
use acadrust::types::Vector3;
|
||||||
use acadrust::EntityType;
|
use acadrust::{EntityType, Handle};
|
||||||
use glam::DVec3;
|
use glam::DVec3;
|
||||||
use crate::t;
|
|
||||||
|
|
||||||
use crate::command::{CadCommand, CmdResult, WorkingPlane};
|
use crate::command::{CadCommand, CmdOption, CmdResult, WorkingPlane};
|
||||||
use crate::scene::model::wire_model::WireModel;
|
use crate::scene::model::wire_model::WireModel;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
enum Step {
|
||||||
|
Start,
|
||||||
|
Next,
|
||||||
|
Justification,
|
||||||
|
Scale,
|
||||||
|
Style,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct MlineCommand {
|
pub struct MlineCommand {
|
||||||
points: Vec<DVec3>,
|
points: Vec<DVec3>,
|
||||||
scale: f64,
|
scale: f64,
|
||||||
waiting_scale: bool,
|
justification: MLineJustification,
|
||||||
|
step: Step,
|
||||||
style_name: String,
|
style_name: String,
|
||||||
style_handle: Option<acadrust::Handle>,
|
style_handle: Option<Handle>,
|
||||||
style_element_count: usize,
|
styles: Vec<(Handle, MLineStyle)>,
|
||||||
|
notice: Option<String>,
|
||||||
plane: WorkingPlane,
|
plane: WorkingPlane,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MlineCommand {
|
impl MlineCommand {
|
||||||
#[allow(dead_code)]
|
pub fn with_styles(
|
||||||
pub fn new() -> Self {
|
mut styles: Vec<(Handle, MLineStyle)>,
|
||||||
|
style_name: impl Into<String>,
|
||||||
|
scale: f64,
|
||||||
|
justification: i16,
|
||||||
|
) -> Self {
|
||||||
|
let requested = style_name.into();
|
||||||
|
if styles.is_empty() {
|
||||||
|
styles.push((Handle::NULL, MLineStyle::standard()));
|
||||||
|
}
|
||||||
|
let selected = styles
|
||||||
|
.iter()
|
||||||
|
.find(|(_, style)| style.name.eq_ignore_ascii_case(&requested))
|
||||||
|
.or_else(|| styles.first());
|
||||||
|
let (style_handle, style_name) = selected
|
||||||
|
.map(|(handle, style)| {
|
||||||
|
(
|
||||||
|
(!handle.is_null()).then_some(*handle),
|
||||||
|
style.name.clone(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap_or((None, requested));
|
||||||
Self {
|
Self {
|
||||||
points: vec![],
|
points: Vec::new(),
|
||||||
scale: 1.0,
|
scale: if scale.is_finite() && scale != 0.0 {
|
||||||
waiting_scale: false,
|
scale
|
||||||
style_name: "Standard".into(),
|
} else {
|
||||||
style_handle: None,
|
1.0
|
||||||
style_element_count: 2,
|
},
|
||||||
|
justification: MLineJustification::from(justification),
|
||||||
|
step: Step::Start,
|
||||||
|
style_name,
|
||||||
|
style_handle,
|
||||||
|
styles,
|
||||||
|
notice: None,
|
||||||
plane: WorkingPlane::default(),
|
plane: WorkingPlane::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_style(
|
fn selected_style(&self) -> Option<&MLineStyle> {
|
||||||
style_name: impl Into<String>,
|
self.styles
|
||||||
style_handle: Option<acadrust::Handle>,
|
.iter()
|
||||||
style_element_count: usize,
|
.find(|(handle, style)| {
|
||||||
) -> Self {
|
self.style_handle == Some(*handle)
|
||||||
Self {
|
|| style.name.eq_ignore_ascii_case(&self.style_name)
|
||||||
points: vec![],
|
})
|
||||||
scale: 1.0,
|
.map(|(_, style)| style)
|
||||||
waiting_scale: false,
|
}
|
||||||
style_name: style_name.into(),
|
|
||||||
style_handle,
|
fn justification_name(&self) -> String {
|
||||||
style_element_count: style_element_count.max(1),
|
match self.justification {
|
||||||
plane: WorkingPlane::default(),
|
MLineJustification::Top => crate::t!("Top").into_owned(),
|
||||||
|
MLineJustification::Zero => crate::t!("Zero").into_owned(),
|
||||||
|
MLineJustification::Bottom => crate::t!("Bottom").into_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn select_style(&mut self, name: &str) -> bool {
|
||||||
|
let Some((handle, style)) = self
|
||||||
|
.styles
|
||||||
|
.iter()
|
||||||
|
.find(|(_, style)| style.name.eq_ignore_ascii_case(name.trim()))
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
self.style_handle = (!handle.is_null()).then_some(*handle);
|
||||||
|
self.style_name = style.name.clone();
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn commit(&self, closed: bool) -> Option<EntityType> {
|
||||||
|
let style = self.selected_style()?;
|
||||||
|
let local: Vec<DVec3> = self
|
||||||
|
.points
|
||||||
|
.iter()
|
||||||
|
.map(|point| self.plane.to_local(*point))
|
||||||
|
.collect();
|
||||||
|
let entity = build_mline(
|
||||||
|
&local,
|
||||||
|
self.scale,
|
||||||
|
self.justification,
|
||||||
|
closed,
|
||||||
|
style,
|
||||||
|
self.style_handle,
|
||||||
|
);
|
||||||
|
Some(self.plane.place_entity(entity))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn preview(&self, cursor: DVec3) -> Option<WireModel> {
|
||||||
|
let style = self.selected_style()?;
|
||||||
|
let mut local: Vec<DVec3> = self
|
||||||
|
.points
|
||||||
|
.iter()
|
||||||
|
.map(|point| self.plane.to_local(*point))
|
||||||
|
.collect();
|
||||||
|
let cursor_local = self.plane.to_local(cursor);
|
||||||
|
if local
|
||||||
|
.last()
|
||||||
|
.is_none_or(|last| last.distance_squared(cursor_local) > 1.0e-20)
|
||||||
|
{
|
||||||
|
local.push(cursor_local);
|
||||||
|
}
|
||||||
|
if local.len() < 2 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let EntityType::MLine(mline) = build_mline(
|
||||||
|
&local,
|
||||||
|
self.scale,
|
||||||
|
self.justification,
|
||||||
|
false,
|
||||||
|
style,
|
||||||
|
self.style_handle,
|
||||||
|
) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let mut points = Vec::new();
|
||||||
|
for (line_index, line) in
|
||||||
|
crate::entities::mline::mline_lines_with_style(&mline, style)
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
if line_index > 0 {
|
||||||
|
points.push([f64::NAN; 3]);
|
||||||
|
}
|
||||||
|
points.extend(line.points.into_iter().map(|point| {
|
||||||
|
if point[0].is_nan() {
|
||||||
|
[f64::NAN; 3]
|
||||||
|
} else {
|
||||||
|
let world = self
|
||||||
|
.plane
|
||||||
|
.to_world(DVec3::new(point[0], point[1], point[2]));
|
||||||
|
world.to_array()
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
let (points, points_low) =
|
||||||
|
crate::scene::convert::tessellate::points_to_ds(points);
|
||||||
|
let fill_tris = crate::entities::mline::mline_fill_triangles_with_style(&mline, style)
|
||||||
|
.into_iter()
|
||||||
|
.map(|point| {
|
||||||
|
let world = self
|
||||||
|
.plane
|
||||||
|
.to_world(DVec3::new(point[0], point[1], point[2]));
|
||||||
|
world.to_array()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let (fill_tris, fill_tris_low) =
|
||||||
|
crate::scene::convert::tessellate::points_to_ds(fill_tris);
|
||||||
|
Some(WireModel {
|
||||||
|
point_marker: None,
|
||||||
|
taper_widths: Vec::new(),
|
||||||
|
world_width: 0.0,
|
||||||
|
depth_override: None,
|
||||||
|
display_visible: true,
|
||||||
|
plot_visible: true,
|
||||||
|
fill_is_3d: false,
|
||||||
|
fill_is_2d_solid: true,
|
||||||
|
render_instance: None,
|
||||||
|
pick_tris: Vec::new(),
|
||||||
|
pick_tris_low: Vec::new(),
|
||||||
|
dash_from_start: false,
|
||||||
|
dash_align_end: None,
|
||||||
|
text_verts: Vec::new(),
|
||||||
|
name: "mline_preview".into(),
|
||||||
|
points,
|
||||||
|
points_low,
|
||||||
|
color: WireModel::CYAN,
|
||||||
|
selected: false,
|
||||||
|
pattern_length: 0.0,
|
||||||
|
pattern: [0.0; 8],
|
||||||
|
line_weight_px: 1.0,
|
||||||
|
snap_pts: Vec::new(),
|
||||||
|
tangent_geoms: Vec::new(),
|
||||||
|
aci: 0,
|
||||||
|
key_vertices: Vec::new(),
|
||||||
|
aabb: WireModel::UNBOUNDED_AABB,
|
||||||
|
plinegen: true,
|
||||||
|
fill_tris,
|
||||||
|
fill_tris_low,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CadCommand for MlineCommand {
|
impl CadCommand for MlineCommand {
|
||||||
@@ -65,178 +224,256 @@ impl CadCommand for MlineCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn prompt(&self) -> String {
|
fn prompt(&self) -> String {
|
||||||
if self.waiting_scale {
|
let notice = self
|
||||||
t!("MLINE Enter scale factor:").into_owned()
|
.notice
|
||||||
} else if self.points.is_empty() {
|
.as_ref()
|
||||||
t!(
|
.map(|value| format!("{value}\n"))
|
||||||
"MLINE Specify start point (scale=%{scale}):",
|
.unwrap_or_default();
|
||||||
scale = format!("{:.2}", self.scale)
|
let body = match self.step {
|
||||||
|
Step::Start => crate::tf!(
|
||||||
|
"MLINE Current settings: Justification = {}, Scale = {}, Style = {}\nSpecify start point or [Justification/Scale/STyle]:",
|
||||||
|
self.justification_name(), self.scale, self.style_name
|
||||||
)
|
)
|
||||||
.into_owned()
|
.into_owned(),
|
||||||
} else {
|
Step::Next if self.points.len() >= 3 => {
|
||||||
t!(
|
crate::t!("MLINE Specify next point or [Close/Undo]:").into_owned()
|
||||||
"MLINE Specify next point (%{count} pts, Enter to finish, C to close, S to set scale):",
|
}
|
||||||
count = self.points.len()
|
Step::Next => crate::t!("MLINE Specify next point or [Undo]:").into_owned(),
|
||||||
|
Step::Justification => crate::tf!(
|
||||||
|
"MLINE Enter justification type [Top/Zero/Bottom] <{}>:",
|
||||||
|
self.justification_name()
|
||||||
)
|
)
|
||||||
.into_owned()
|
.into_owned(),
|
||||||
|
Step::Scale => {
|
||||||
|
crate::tf!("MLINE Enter scale factor <{}>:", self.scale).into_owned()
|
||||||
|
}
|
||||||
|
Step::Style => {
|
||||||
|
crate::tf!("MLINE Enter style name or [?] <{}>:", self.style_name).into_owned()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
format!("{notice}{body}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn options(&self) -> Vec<CmdOption> {
|
||||||
|
match self.step {
|
||||||
|
Step::Start => vec![
|
||||||
|
CmdOption::new(crate::t!("Justification").as_ref(), "J"),
|
||||||
|
CmdOption::new(crate::t!("Scale").as_ref(), "S"),
|
||||||
|
CmdOption::new(crate::t!("Style").as_ref(), "ST"),
|
||||||
|
],
|
||||||
|
Step::Next if self.points.len() >= 3 => vec![
|
||||||
|
CmdOption::new(crate::t!("Close").as_ref(), "C"),
|
||||||
|
CmdOption::new(crate::t!("Undo").as_ref(), "U"),
|
||||||
|
],
|
||||||
|
Step::Next => vec![CmdOption::new(crate::t!("Undo").as_ref(), "U")],
|
||||||
|
Step::Justification => vec![
|
||||||
|
CmdOption::new(crate::t!("Top").as_ref(), "T"),
|
||||||
|
CmdOption::new(crate::t!("Zero").as_ref(), "Z"),
|
||||||
|
CmdOption::new(crate::t!("Bottom").as_ref(), "B"),
|
||||||
|
],
|
||||||
|
Step::Scale => Vec::new(),
|
||||||
|
Step::Style => self
|
||||||
|
.styles
|
||||||
|
.iter()
|
||||||
|
.map(|(_, style)| CmdOption::new(&style.name, &style.name))
|
||||||
|
.collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wants_text_input(&self) -> bool {
|
fn wants_text_input(&self) -> bool {
|
||||||
self.waiting_scale || !self.points.is_empty()
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn point_step_accepts_keywords(&self) -> bool {
|
fn point_step_accepts_keywords(&self) -> bool {
|
||||||
// The vertex steps accept J / S keywords but are point picks, so keep
|
matches!(self.step, Step::Start | Step::Next)
|
||||||
// polar dynamic input. The scale prompt (`waiting_scale`) is genuine
|
}
|
||||||
// text entry and is excluded.
|
|
||||||
!self.waiting_scale && !self.points.is_empty()
|
fn mline_settings(&self) -> Option<(f64, i16, String, Option<Handle>)> {
|
||||||
|
Some((
|
||||||
|
self.scale,
|
||||||
|
self.justification as i16,
|
||||||
|
self.style_name.clone(),
|
||||||
|
self.style_handle,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
||||||
// Waiting for scale value
|
let token = text.trim();
|
||||||
if self.waiting_scale {
|
let upper = token.to_uppercase();
|
||||||
let v: f64 = text
|
self.notice = None;
|
||||||
.trim()
|
match self.step {
|
||||||
.replace(',', ".")
|
Step::Start => match upper.as_str() {
|
||||||
.parse()
|
"J" | "JUSTIFICATION" => self.step = Step::Justification,
|
||||||
.ok()
|
"S" | "SCALE" => self.step = Step::Scale,
|
||||||
.filter(|&v: &f64| v > 0.0)?;
|
"ST" | "STYLE" => self.step = Step::Style,
|
||||||
self.scale = v;
|
_ => return None,
|
||||||
self.waiting_scale = false;
|
},
|
||||||
return Some(CmdResult::NeedPoint);
|
Step::Next => match upper.as_str() {
|
||||||
}
|
"U" | "UNDO" => {
|
||||||
|
self.points.pop();
|
||||||
let up = text.trim().to_uppercase();
|
if self.points.is_empty() {
|
||||||
|
self.step = Step::Start;
|
||||||
// Close command
|
}
|
||||||
if (up == "C" || up == "CLOSE") && self.points.len() >= 3 {
|
}
|
||||||
let entity = build_mline(
|
"C" | "CLOSE" if self.points.len() >= 3 => {
|
||||||
&self
|
return self.commit(true).map(CmdResult::CommitAndExit);
|
||||||
.points
|
}
|
||||||
.iter()
|
_ => return None,
|
||||||
.map(|point| self.plane.to_local(*point))
|
},
|
||||||
.collect::<Vec<_>>(),
|
Step::Justification => {
|
||||||
self.scale,
|
self.justification = match upper.as_str() {
|
||||||
true,
|
"T" | "TOP" => MLineJustification::Top,
|
||||||
&self.style_name,
|
"Z" | "ZERO" => MLineJustification::Zero,
|
||||||
self.style_handle,
|
"B" | "BOTTOM" => MLineJustification::Bottom,
|
||||||
self.style_element_count,
|
_ => return None,
|
||||||
);
|
};
|
||||||
return Some(CmdResult::CommitAndExit(self.plane.place_entity(entity)));
|
self.step = Step::Start;
|
||||||
}
|
}
|
||||||
|
Step::Scale => {
|
||||||
// Scale: "S" alone → prompt for value
|
let value = token.replace(',', ".").parse::<f64>().ok()?;
|
||||||
if up == "S" {
|
if !value.is_finite() || value == 0.0 {
|
||||||
self.waiting_scale = true;
|
return None;
|
||||||
return Some(CmdResult::NeedPoint);
|
}
|
||||||
}
|
self.scale = value;
|
||||||
|
self.step = Step::Start;
|
||||||
// Scale: "S <value>" inline
|
}
|
||||||
if let Some(rest) = up.strip_prefix("S ") {
|
Step::Style => {
|
||||||
if let Ok(v) = rest.trim().replace(',', ".").parse::<f64>() {
|
if token == "?" {
|
||||||
if v > 0.0 {
|
self.notice = Some(crate::tf!(
|
||||||
self.scale = v;
|
"Loaded multiline styles: {}",
|
||||||
|
self.styles
|
||||||
|
.iter()
|
||||||
|
.map(|(_, style)| style.name.as_str())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
)
|
||||||
|
.into_owned());
|
||||||
|
} else if self.select_style(token) {
|
||||||
|
self.step = Step::Start;
|
||||||
|
} else {
|
||||||
|
self.notice = Some(
|
||||||
|
crate::t!(
|
||||||
|
"Multiline style \"%{style}\" was not found.",
|
||||||
|
style = token
|
||||||
|
)
|
||||||
|
.into_owned(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return Some(CmdResult::NeedPoint);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some(CmdResult::NeedPoint)
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
fn on_point(&mut self, point: DVec3) -> CmdResult {
|
||||||
self.points.push(pt);
|
if !matches!(self.step, Step::Start | Step::Next) {
|
||||||
|
return CmdResult::NeedPoint;
|
||||||
|
}
|
||||||
|
if self
|
||||||
|
.points
|
||||||
|
.last()
|
||||||
|
.is_some_and(|last| last.distance_squared(point) <= 1.0e-20)
|
||||||
|
{
|
||||||
|
return CmdResult::NeedPoint;
|
||||||
|
}
|
||||||
|
self.points.push(point);
|
||||||
|
self.step = Step::Next;
|
||||||
CmdResult::NeedPoint
|
CmdResult::NeedPoint
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_enter(&mut self) -> CmdResult {
|
fn on_enter(&mut self) -> CmdResult {
|
||||||
if self.points.len() < 2 {
|
match self.step {
|
||||||
return CmdResult::Cancel;
|
Step::Start if self.points.is_empty() => CmdResult::Cancel,
|
||||||
|
Step::Next if self.points.len() >= 2 => self
|
||||||
|
.commit(false)
|
||||||
|
.map(CmdResult::CommitAndExit)
|
||||||
|
.unwrap_or(CmdResult::Cancel),
|
||||||
|
Step::Justification | Step::Scale | Step::Style => {
|
||||||
|
self.step = Step::Start;
|
||||||
|
CmdResult::NeedPoint
|
||||||
|
}
|
||||||
|
_ => CmdResult::Cancel,
|
||||||
}
|
}
|
||||||
let entity = build_mline(
|
|
||||||
&self
|
|
||||||
.points
|
|
||||||
.iter()
|
|
||||||
.map(|point| self.plane.to_local(*point))
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
self.scale,
|
|
||||||
false,
|
|
||||||
&self.style_name,
|
|
||||||
self.style_handle,
|
|
||||||
self.style_element_count,
|
|
||||||
);
|
|
||||||
CmdResult::CommitAndExit(self.plane.place_entity(entity))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
fn on_undo_step(&mut self) -> Option<CmdResult> {
|
||||||
let pt = pt.as_vec3();
|
|
||||||
if self.points.is_empty() {
|
if self.points.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let mut pts: Vec<[f32; 3]> = self
|
self.points.pop();
|
||||||
.points
|
if self.points.is_empty() {
|
||||||
.iter()
|
self.step = Step::Start;
|
||||||
.map(|p| [p.x as f32, p.y as f32, p.z as f32])
|
}
|
||||||
.collect();
|
Some(CmdResult::NeedPoint)
|
||||||
pts.push([pt.x, pt.y, pt.z]);
|
|
||||||
Some(WireModel {
|
|
||||||
point_marker: None,
|
|
||||||
taper_widths: Vec::new(),
|
|
||||||
world_width: 0.0,
|
|
||||||
depth_override: None,
|
|
||||||
display_visible: true,
|
|
||||||
plot_visible: true,
|
|
||||||
fill_is_3d: false,
|
|
||||||
fill_is_2d_solid: false,
|
|
||||||
render_instance: None,
|
|
||||||
pick_tris: Vec::new(),
|
|
||||||
pick_tris_low: Vec::new(),
|
|
||||||
dash_from_start: false,
|
|
||||||
dash_align_end: None,
|
|
||||||
text_verts: Vec::new(),
|
|
||||||
name: "mline_preview".into(),
|
|
||||||
points: pts,
|
|
||||||
points_low: Vec::new(),
|
|
||||||
color: WireModel::CYAN,
|
|
||||||
selected: false,
|
|
||||||
pattern_length: 0.0,
|
|
||||||
pattern: [0.0; 8],
|
|
||||||
line_weight_px: 1.0,
|
|
||||||
snap_pts: vec![],
|
|
||||||
tangent_geoms: vec![],
|
|
||||||
aci: 0,
|
|
||||||
key_vertices: vec![],
|
|
||||||
aabb: WireModel::UNBOUNDED_AABB,
|
|
||||||
plinegen: true,
|
|
||||||
fill_tris: vec![],
|
|
||||||
fill_tris_low: Vec::new(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_mouse_move(&mut self, point: DVec3) -> Option<WireModel> {
|
||||||
|
(!self.points.is_empty()).then(|| self.preview(point)).flatten()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn sync_mline_element_parameters(mline: &mut MLine, style: &MLineStyle) {
|
||||||
|
let offsets: Vec<f64> = if style.elements.is_empty() {
|
||||||
|
vec![0.5, -0.5]
|
||||||
|
} else {
|
||||||
|
style.elements.iter().map(|element| element.offset).collect()
|
||||||
|
};
|
||||||
|
let minimum = offsets.iter().copied().fold(f64::INFINITY, f64::min);
|
||||||
|
let maximum = offsets
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.fold(f64::NEG_INFINITY, f64::max);
|
||||||
|
let shift = match mline.justification {
|
||||||
|
MLineJustification::Top => -maximum,
|
||||||
|
MLineJustification::Zero => 0.0,
|
||||||
|
MLineJustification::Bottom => -minimum,
|
||||||
|
};
|
||||||
|
let normal = glam::DVec3::new(mline.normal.x, mline.normal.y, mline.normal.z)
|
||||||
|
.normalize_or(glam::DVec3::Z);
|
||||||
|
for vertex in &mut mline.vertices {
|
||||||
|
let direction = glam::DVec3::new(vertex.direction.x, vertex.direction.y, vertex.direction.z)
|
||||||
|
.normalize_or(glam::DVec3::X);
|
||||||
|
let miter = glam::DVec3::new(vertex.miter.x, vertex.miter.y, vertex.miter.z)
|
||||||
|
.normalize_or(glam::DVec3::Y);
|
||||||
|
let factor = miter.dot(normal.cross(direction)).abs().max(1.0e-9);
|
||||||
|
vertex
|
||||||
|
.segments
|
||||||
|
.resize_with(offsets.len(), MLineSegment::new);
|
||||||
|
vertex.segments.truncate(offsets.len());
|
||||||
|
for (segment, offset) in vertex.segments.iter_mut().zip(&offsets) {
|
||||||
|
let value = (offset + shift) * mline.scale_factor / factor;
|
||||||
|
if let Some(first) = segment.parameters.first_mut() {
|
||||||
|
*first = value;
|
||||||
|
} else {
|
||||||
|
segment.parameters.extend([value, 0.0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mline.style_element_count = offsets.len();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_mline(
|
fn build_mline(
|
||||||
pts: &[DVec3],
|
points: &[DVec3],
|
||||||
scale: f64,
|
scale: f64,
|
||||||
|
justification: MLineJustification,
|
||||||
closed: bool,
|
closed: bool,
|
||||||
style_name: &str,
|
style: &MLineStyle,
|
||||||
style_handle: Option<acadrust::Handle>,
|
style_handle: Option<Handle>,
|
||||||
style_element_count: usize,
|
|
||||||
) -> EntityType {
|
) -> EntityType {
|
||||||
let mut mline = MLine::new();
|
let mut mline = MLine::new();
|
||||||
mline.scale_factor = scale;
|
mline.scale_factor = scale;
|
||||||
mline.style_name = style_name.to_string();
|
mline.justification = justification;
|
||||||
|
mline.style_name = style.name.clone();
|
||||||
mline.style_handle = style_handle;
|
mline.style_handle = style_handle;
|
||||||
mline.style_element_count = style_element_count;
|
mline.style_element_count = style.elements.len().max(1);
|
||||||
for point in pts {
|
for point in points {
|
||||||
mline.add_vertex(Vector3::new(point.x, point.y, point.z));
|
let mut vertex = MLineVertex::new(Vector3::new(point.x, point.y, point.z));
|
||||||
}
|
vertex.init_segments(mline.style_element_count);
|
||||||
if closed {
|
mline.vertices.push(vertex);
|
||||||
mline.close();
|
|
||||||
}
|
}
|
||||||
|
mline.flags.set(MLineFlags::CLOSED, closed);
|
||||||
|
crate::entities::mline::rebuild_mline_geometry(&mut mline);
|
||||||
|
sync_mline_element_parameters(&mut mline, style);
|
||||||
EntityType::MLine(mline)
|
EntityType::MLine(mline)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["MLINE"] });
|
||||||
// ── Autocomplete registry ─────────────────────────────────
|
|
||||||
inventory::submit!(crate::command::CommandRegistration { names: &["MLINE"] }); // MlineCommand
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -12,6 +12,7 @@ pub mod geom;
|
|||||||
pub mod join;
|
pub mod join;
|
||||||
pub mod lengthen;
|
pub mod lengthen;
|
||||||
pub mod mirror;
|
pub mod mirror;
|
||||||
|
pub mod mledit;
|
||||||
pub mod offset;
|
pub mod offset;
|
||||||
pub mod pedit;
|
pub mod pedit;
|
||||||
pub mod block_edit;
|
pub mod block_edit;
|
||||||
|
|||||||
@@ -369,9 +369,68 @@ pub fn tessellate(
|
|||||||
let dz = w[1].position.z - w[0].position.z;
|
let dz = w[1].position.z - w[0].position.z;
|
||||||
acc += (dx * dx + dy * dy + dz * dz).sqrt();
|
acc += (dx * dx + dy * dy + dz * dz).sqrt();
|
||||||
}
|
}
|
||||||
|
if m.is_closed() && m.vertices.len() > 1 {
|
||||||
|
let first = &m.vertices[0].position;
|
||||||
|
let last = &m.vertices[m.vertices.len() - 1].position;
|
||||||
|
let dx = first.x - last.x;
|
||||||
|
let dy = first.y - last.y;
|
||||||
|
let dz = first.z - last.z;
|
||||||
|
acc += (dx * dx + dy * dy + dz * dz).sqrt();
|
||||||
|
}
|
||||||
acc as f32
|
acc as f32
|
||||||
};
|
};
|
||||||
let mut out: Vec<WireModel> = Vec::with_capacity(lines.len());
|
let mut out: Vec<WireModel> = Vec::with_capacity(lines.len());
|
||||||
|
if let Some(style) = crate::entities::mline::resolved_mline_style(m, document) {
|
||||||
|
let triangles =
|
||||||
|
crate::entities::mline::mline_fill_triangles_with_style(m, style);
|
||||||
|
if !triangles.is_empty() {
|
||||||
|
let (fill_tris, fill_tris_low) = points_to_ds(triangles);
|
||||||
|
let fill_color = if selected {
|
||||||
|
WireModel::SELECTED
|
||||||
|
} else {
|
||||||
|
match style.fill_color {
|
||||||
|
AcadColor::ByLayer | AcadColor::ByBlock => entity_color,
|
||||||
|
other => {
|
||||||
|
let [r, g, b, _] =
|
||||||
|
crate::scene::convert::tess_util::aci_to_rgba(&other);
|
||||||
|
[r, g, b, entity_color[3]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
out.push(WireModel {
|
||||||
|
point_marker: None,
|
||||||
|
taper_widths: Vec::new(),
|
||||||
|
world_width: 0.0,
|
||||||
|
depth_override: None,
|
||||||
|
display_visible: true,
|
||||||
|
plot_visible: true,
|
||||||
|
fill_is_3d: false,
|
||||||
|
fill_is_2d_solid: true,
|
||||||
|
render_instance: None,
|
||||||
|
pick_tris: Vec::new(),
|
||||||
|
pick_tris_low: Vec::new(),
|
||||||
|
dash_from_start: false,
|
||||||
|
dash_align_end: None,
|
||||||
|
text_verts: Vec::new(),
|
||||||
|
name: name.clone(),
|
||||||
|
points: Vec::new(),
|
||||||
|
points_low: Vec::new(),
|
||||||
|
color: fill_color,
|
||||||
|
selected,
|
||||||
|
pattern_length: 0.0,
|
||||||
|
pattern: [0.0; 8],
|
||||||
|
line_weight_px,
|
||||||
|
snap_pts: Vec::new(),
|
||||||
|
tangent_geoms: Vec::new(),
|
||||||
|
aci: 0,
|
||||||
|
key_vertices: Vec::new(),
|
||||||
|
aabb: WireModel::UNBOUNDED_AABB,
|
||||||
|
plinegen: true,
|
||||||
|
fill_tris,
|
||||||
|
fill_tris_low,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
let mut snap_attached = false;
|
let mut snap_attached = false;
|
||||||
for l in lines {
|
for l in lines {
|
||||||
if l.points.is_empty() {
|
if l.points.is_empty() {
|
||||||
|
|||||||
Reference in new issue
Block a user