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
+405
-168
@@ -1,58 +1,217 @@
|
||||
// MLINE command — create a multiline (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
|
||||
// MLINE command — create a styled group of parallel lines.
|
||||
|
||||
use acadrust::entities::MLine;
|
||||
use acadrust::entities::{MLine, MLineFlags, MLineJustification, MLineSegment, MLineVertex};
|
||||
use acadrust::objects::MLineStyle;
|
||||
use acadrust::types::Vector3;
|
||||
use acadrust::EntityType;
|
||||
use acadrust::{EntityType, Handle};
|
||||
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;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum Step {
|
||||
Start,
|
||||
Next,
|
||||
Justification,
|
||||
Scale,
|
||||
Style,
|
||||
}
|
||||
|
||||
pub struct MlineCommand {
|
||||
points: Vec<DVec3>,
|
||||
scale: f64,
|
||||
waiting_scale: bool,
|
||||
justification: MLineJustification,
|
||||
step: Step,
|
||||
style_name: String,
|
||||
style_handle: Option<acadrust::Handle>,
|
||||
style_element_count: usize,
|
||||
style_handle: Option<Handle>,
|
||||
styles: Vec<(Handle, MLineStyle)>,
|
||||
notice: Option<String>,
|
||||
plane: WorkingPlane,
|
||||
}
|
||||
|
||||
impl MlineCommand {
|
||||
#[allow(dead_code)]
|
||||
pub fn new() -> Self {
|
||||
pub fn with_styles(
|
||||
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 {
|
||||
points: vec![],
|
||||
scale: 1.0,
|
||||
waiting_scale: false,
|
||||
style_name: "Standard".into(),
|
||||
style_handle: None,
|
||||
style_element_count: 2,
|
||||
points: Vec::new(),
|
||||
scale: if scale.is_finite() && scale != 0.0 {
|
||||
scale
|
||||
} else {
|
||||
1.0
|
||||
},
|
||||
justification: MLineJustification::from(justification),
|
||||
step: Step::Start,
|
||||
style_name,
|
||||
style_handle,
|
||||
styles,
|
||||
notice: None,
|
||||
plane: WorkingPlane::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_style(
|
||||
style_name: impl Into<String>,
|
||||
style_handle: Option<acadrust::Handle>,
|
||||
style_element_count: usize,
|
||||
) -> Self {
|
||||
Self {
|
||||
points: vec![],
|
||||
scale: 1.0,
|
||||
waiting_scale: false,
|
||||
style_name: style_name.into(),
|
||||
style_handle,
|
||||
style_element_count: style_element_count.max(1),
|
||||
plane: WorkingPlane::default(),
|
||||
fn selected_style(&self) -> Option<&MLineStyle> {
|
||||
self.styles
|
||||
.iter()
|
||||
.find(|(handle, style)| {
|
||||
self.style_handle == Some(*handle)
|
||||
|| style.name.eq_ignore_ascii_case(&self.style_name)
|
||||
})
|
||||
.map(|(_, style)| style)
|
||||
}
|
||||
|
||||
fn justification_name(&self) -> String {
|
||||
match self.justification {
|
||||
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 {
|
||||
@@ -65,178 +224,256 @@ impl CadCommand for MlineCommand {
|
||||
}
|
||||
|
||||
fn prompt(&self) -> String {
|
||||
if self.waiting_scale {
|
||||
t!("MLINE Enter scale factor:").into_owned()
|
||||
} else if self.points.is_empty() {
|
||||
t!(
|
||||
"MLINE Specify start point (scale=%{scale}):",
|
||||
scale = format!("{:.2}", self.scale)
|
||||
let notice = self
|
||||
.notice
|
||||
.as_ref()
|
||||
.map(|value| format!("{value}\n"))
|
||||
.unwrap_or_default();
|
||||
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()
|
||||
} else {
|
||||
t!(
|
||||
"MLINE Specify next point (%{count} pts, Enter to finish, C to close, S to set scale):",
|
||||
count = self.points.len()
|
||||
.into_owned(),
|
||||
Step::Next if self.points.len() >= 3 => {
|
||||
crate::t!("MLINE Specify next point or [Close/Undo]:").into_owned()
|
||||
}
|
||||
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 {
|
||||
self.waiting_scale || !self.points.is_empty()
|
||||
true
|
||||
}
|
||||
|
||||
fn point_step_accepts_keywords(&self) -> bool {
|
||||
// The vertex steps accept J / S keywords but are point picks, so keep
|
||||
// polar dynamic input. The scale prompt (`waiting_scale`) is genuine
|
||||
// text entry and is excluded.
|
||||
!self.waiting_scale && !self.points.is_empty()
|
||||
matches!(self.step, Step::Start | Step::Next)
|
||||
}
|
||||
|
||||
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> {
|
||||
// Waiting for scale value
|
||||
if self.waiting_scale {
|
||||
let v: f64 = text
|
||||
.trim()
|
||||
.replace(',', ".")
|
||||
.parse()
|
||||
.ok()
|
||||
.filter(|&v: &f64| v > 0.0)?;
|
||||
self.scale = v;
|
||||
self.waiting_scale = false;
|
||||
return Some(CmdResult::NeedPoint);
|
||||
}
|
||||
|
||||
let up = text.trim().to_uppercase();
|
||||
|
||||
// Close command
|
||||
if (up == "C" || up == "CLOSE") && self.points.len() >= 3 {
|
||||
let entity = build_mline(
|
||||
&self
|
||||
.points
|
||||
.iter()
|
||||
.map(|point| self.plane.to_local(*point))
|
||||
.collect::<Vec<_>>(),
|
||||
self.scale,
|
||||
true,
|
||||
&self.style_name,
|
||||
self.style_handle,
|
||||
self.style_element_count,
|
||||
);
|
||||
return Some(CmdResult::CommitAndExit(self.plane.place_entity(entity)));
|
||||
}
|
||||
|
||||
// Scale: "S" alone → prompt for value
|
||||
if up == "S" {
|
||||
self.waiting_scale = true;
|
||||
return Some(CmdResult::NeedPoint);
|
||||
}
|
||||
|
||||
// Scale: "S <value>" inline
|
||||
if let Some(rest) = up.strip_prefix("S ") {
|
||||
if let Ok(v) = rest.trim().replace(',', ".").parse::<f64>() {
|
||||
if v > 0.0 {
|
||||
self.scale = v;
|
||||
let token = text.trim();
|
||||
let upper = token.to_uppercase();
|
||||
self.notice = None;
|
||||
match self.step {
|
||||
Step::Start => match upper.as_str() {
|
||||
"J" | "JUSTIFICATION" => self.step = Step::Justification,
|
||||
"S" | "SCALE" => self.step = Step::Scale,
|
||||
"ST" | "STYLE" => self.step = Step::Style,
|
||||
_ => return None,
|
||||
},
|
||||
Step::Next => match upper.as_str() {
|
||||
"U" | "UNDO" => {
|
||||
self.points.pop();
|
||||
if self.points.is_empty() {
|
||||
self.step = Step::Start;
|
||||
}
|
||||
}
|
||||
"C" | "CLOSE" if self.points.len() >= 3 => {
|
||||
return self.commit(true).map(CmdResult::CommitAndExit);
|
||||
}
|
||||
_ => return None,
|
||||
},
|
||||
Step::Justification => {
|
||||
self.justification = match upper.as_str() {
|
||||
"T" | "TOP" => MLineJustification::Top,
|
||||
"Z" | "ZERO" => MLineJustification::Zero,
|
||||
"B" | "BOTTOM" => MLineJustification::Bottom,
|
||||
_ => return None,
|
||||
};
|
||||
self.step = Step::Start;
|
||||
}
|
||||
Step::Scale => {
|
||||
let value = token.replace(',', ".").parse::<f64>().ok()?;
|
||||
if !value.is_finite() || value == 0.0 {
|
||||
return None;
|
||||
}
|
||||
self.scale = value;
|
||||
self.step = Step::Start;
|
||||
}
|
||||
Step::Style => {
|
||||
if token == "?" {
|
||||
self.notice = Some(crate::tf!(
|
||||
"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);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
self.points.push(pt);
|
||||
fn on_point(&mut self, point: DVec3) -> CmdResult {
|
||||
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
|
||||
}
|
||||
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
if self.points.len() < 2 {
|
||||
return CmdResult::Cancel;
|
||||
match self.step {
|
||||
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> {
|
||||
let pt = pt.as_vec3();
|
||||
fn on_undo_step(&mut self) -> Option<CmdResult> {
|
||||
if self.points.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let mut pts: Vec<[f32; 3]> = self
|
||||
.points
|
||||
.iter()
|
||||
.map(|p| [p.x as f32, p.y as f32, p.z as f32])
|
||||
.collect();
|
||||
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(),
|
||||
})
|
||||
self.points.pop();
|
||||
if self.points.is_empty() {
|
||||
self.step = Step::Start;
|
||||
}
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
|
||||
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(
|
||||
pts: &[DVec3],
|
||||
points: &[DVec3],
|
||||
scale: f64,
|
||||
justification: MLineJustification,
|
||||
closed: bool,
|
||||
style_name: &str,
|
||||
style_handle: Option<acadrust::Handle>,
|
||||
style_element_count: usize,
|
||||
style: &MLineStyle,
|
||||
style_handle: Option<Handle>,
|
||||
) -> EntityType {
|
||||
let mut mline = MLine::new();
|
||||
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_element_count = style_element_count;
|
||||
for point in pts {
|
||||
mline.add_vertex(Vector3::new(point.x, point.y, point.z));
|
||||
}
|
||||
if closed {
|
||||
mline.close();
|
||||
mline.style_element_count = style.elements.len().max(1);
|
||||
for point in points {
|
||||
let mut vertex = MLineVertex::new(Vector3::new(point.x, point.y, point.z));
|
||||
vertex.init_segments(mline.style_element_count);
|
||||
mline.vertices.push(vertex);
|
||||
}
|
||||
mline.flags.set(MLineFlags::CLOSED, closed);
|
||||
crate::entities::mline::rebuild_mline_geometry(&mut mline);
|
||||
sync_mline_element_parameters(&mut mline, style);
|
||||
EntityType::MLine(mline)
|
||||
}
|
||||
|
||||
|
||||
// ── Autocomplete registry ─────────────────────────────────
|
||||
inventory::submit!(crate::command::CommandRegistration { names: &["MLINE"] }); // MlineCommand
|
||||
inventory::submit!(crate::command::CommandRegistration { names: &["MLINE"] });
|
||||
File diff suppressed because it is too large.
Load diff
@@ -12,6 +12,7 @@ pub mod geom;
|
||||
pub mod join;
|
||||
pub mod lengthen;
|
||||
pub mod mirror;
|
||||
pub mod mledit;
|
||||
pub mod offset;
|
||||
pub mod pedit;
|
||||
pub mod block_edit;
|
||||
|
||||
Reference in new issue
Block a user