forked from 1b-agent/opencadstudio-upstream
Fix 3D polyline behavior and properties
This commit is contained in:
1 parent
cc430cdbe9
commit
3ca959a34b
7 files changed
+305
-43
No files matched your search
@@ -2829,7 +2829,7 @@ properties =
|
||||
.polyfacemesh = PolyfaceMesh
|
||||
.polygonmesh = PolygonMesh
|
||||
.polyline2d = Polyline2D
|
||||
.polyline3d = Polyline3D
|
||||
.polyline3d = 3D Polyline
|
||||
.predefined = Predefined
|
||||
.property-bag = Property Bag
|
||||
.proxy-entity = Proxy Entity
|
||||
|
||||
@@ -121,6 +121,9 @@ impl OpenCADStudio {
|
||||
.and_then(|entity| match entity {
|
||||
acadrust::EntityType::LwPolyline(polyline) => Some(polyline.vertices.len()),
|
||||
acadrust::EntityType::Polyline2D(polyline) => Some(polyline.vertices.len()),
|
||||
acadrust::EntityType::Polyline3D(polyline) => Some(
|
||||
crate::entities::polyline::polyline3d_control_vertex_count(polyline),
|
||||
),
|
||||
acadrust::EntityType::Leader(leader) => Some(leader.vertices.len()),
|
||||
acadrust::EntityType::Spline(spline) => {
|
||||
Some(if crate::entities::spline::shows_fit_points(spline) {
|
||||
|
||||
@@ -4901,6 +4901,9 @@ impl OpenCADStudio {
|
||||
match self.tabs[i].scene.document.get_entity(handles[0]) {
|
||||
Some(acadrust::EntityType::LwPolyline(p)) => p.vertices.len(),
|
||||
Some(acadrust::EntityType::Polyline2D(p)) => p.vertices.len(),
|
||||
Some(acadrust::EntityType::Polyline3D(p)) => {
|
||||
crate::entities::polyline::polyline3d_control_vertex_count(p)
|
||||
}
|
||||
Some(acadrust::EntityType::Table(table)) => {
|
||||
table.row_count().saturating_mul(table.column_count())
|
||||
}
|
||||
|
||||
@@ -432,6 +432,7 @@ impl OpenCADStudio {
|
||||
let indexed = match tab.scene.document.get_entity(h) {
|
||||
Some(acadrust::EntityType::LwPolyline(_))
|
||||
| Some(acadrust::EntityType::Polyline2D(_))
|
||||
| Some(acadrust::EntityType::Polyline3D(_))
|
||||
| Some(acadrust::EntityType::Spline(_)) => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
+289
-37
@@ -3,12 +3,14 @@ use crate::t;
|
||||
|
||||
use crate::command::EntityTransform;
|
||||
use crate::entities::common::{
|
||||
edit_prop as edit, parse_f64, ro_prop as ro, square_grip, stepper_prop as stepper,
|
||||
edit_prop as edit, format_length, parse_f64, ro_prop as ro, square_grip,
|
||||
stepper_prop as stepper,
|
||||
};
|
||||
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, RenderConvertible};
|
||||
use crate::scene::convert::acad_to_render::{extrusion_wall_tris, RenderEntity, RenderObject};
|
||||
use crate::scene::model::object::{GripApply, GripDef, PropSection, PropValue, Property};
|
||||
use crate::scene::model::wire_model::TangentGeom;
|
||||
use cadkernel::space::NurbsCurve3;
|
||||
|
||||
// ── Polyline (old-style 3D heavy polyline) ────────────────────────────────────
|
||||
|
||||
@@ -783,6 +785,159 @@ impl Transformable for Polyline2D {
|
||||
|
||||
// ── Polyline3D ────────────────────────────────────────────────────────────────
|
||||
|
||||
const POLY3D_SPLINE_POINT: i32 = 8;
|
||||
const POLY3D_SPLINE_CONTROL: i32 = 16;
|
||||
const POLY3D_VERTEX: i32 = 32;
|
||||
|
||||
fn polyline3d_control_indices(pl: &Polyline3D) -> Vec<usize> {
|
||||
if pl
|
||||
.vertices
|
||||
.iter()
|
||||
.any(|vertex| vertex.flags & POLY3D_SPLINE_CONTROL != 0)
|
||||
{
|
||||
return pl
|
||||
.vertices
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(index, vertex)| {
|
||||
(vertex.flags & POLY3D_SPLINE_CONTROL != 0).then_some(index)
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
let controls: Vec<usize> = pl
|
||||
.vertices
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(index, vertex)| {
|
||||
(vertex.flags & POLY3D_SPLINE_POINT == 0).then_some(index)
|
||||
})
|
||||
.collect();
|
||||
if controls.is_empty() {
|
||||
(0..pl.vertices.len()).collect()
|
||||
} else {
|
||||
controls
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn polyline3d_control_vertex_count(pl: &Polyline3D) -> usize {
|
||||
polyline3d_control_indices(pl).len()
|
||||
}
|
||||
|
||||
fn polyline3d_controls(pl: &Polyline3D) -> Vec<acadrust::entities::Vertex3DPolyline> {
|
||||
polyline3d_control_indices(pl)
|
||||
.into_iter()
|
||||
.filter_map(|index| pl.vertices.get(index).cloned())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn polyline3d_wire_points(pl: &Polyline3D) -> Vec<[f64; 3]> {
|
||||
let curve: Vec<[f64; 3]> = pl
|
||||
.vertices
|
||||
.iter()
|
||||
.filter(|vertex| vertex.flags & POLY3D_SPLINE_POINT != 0)
|
||||
.map(|vertex| [vertex.position.x, vertex.position.y, vertex.position.z])
|
||||
.collect();
|
||||
if !curve.is_empty() {
|
||||
curve
|
||||
} else {
|
||||
polyline3d_controls(pl)
|
||||
.iter()
|
||||
.map(|vertex| [vertex.position.x, vertex.position.y, vertex.position.z])
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn polyline3d_length(pl: &Polyline3D) -> f64 {
|
||||
let points = polyline3d_wire_points(pl);
|
||||
let mut length = points
|
||||
.windows(2)
|
||||
.map(|pair| {
|
||||
let dx = pair[1][0] - pair[0][0];
|
||||
let dy = pair[1][1] - pair[0][1];
|
||||
let dz = pair[1][2] - pair[0][2];
|
||||
(dx * dx + dy * dy + dz * dz).sqrt()
|
||||
})
|
||||
.sum::<f64>();
|
||||
if pl.is_closed() && points.len() >= 2 {
|
||||
let first = points[0];
|
||||
let last = points[points.len() - 1];
|
||||
let dx = first[0] - last[0];
|
||||
let dy = first[1] - last[1];
|
||||
let dz = first[2] - last[2];
|
||||
length += (dx * dx + dy * dy + dz * dz).sqrt();
|
||||
}
|
||||
length
|
||||
}
|
||||
|
||||
fn polyline3d_spline_samples(
|
||||
controls: &[[f64; 3]],
|
||||
degree: usize,
|
||||
closed: bool,
|
||||
) -> Option<Vec<[f64; 3]>> {
|
||||
let curve = if closed {
|
||||
let mut periodic_controls = controls.to_vec();
|
||||
periodic_controls.extend(controls.iter().take(degree).copied());
|
||||
let knots = (0..periodic_controls.len() + degree + 1)
|
||||
.map(|index| index as f64)
|
||||
.collect();
|
||||
NurbsCurve3::new(degree, periodic_controls, knots, None)?.with_periodicity(true)
|
||||
} else {
|
||||
NurbsCurve3::new(degree, controls.to_vec(), Vec::new(), None)?
|
||||
};
|
||||
Some(curve.tessellate_angle(cadkernel::tessellation::DEFAULT_ANGLE))
|
||||
}
|
||||
|
||||
fn rebuild_polyline3d_fit(pl: &mut Polyline3D) -> bool {
|
||||
use acadrust::entities::polyline3d::SmoothSurfaceType as SST;
|
||||
|
||||
let mut controls = polyline3d_controls(pl);
|
||||
if pl.smooth_type == SST::None {
|
||||
for vertex in &mut controls {
|
||||
vertex.flags = POLY3D_VERTEX;
|
||||
}
|
||||
pl.vertices = controls;
|
||||
pl.flags.spline_fit = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
let degree = match pl.smooth_type {
|
||||
SST::QuadraticBSpline => 2,
|
||||
SST::CubicBSpline | SST::Bezier => 3,
|
||||
SST::None => unreachable!(),
|
||||
};
|
||||
if controls.len() <= degree {
|
||||
return false;
|
||||
}
|
||||
let points: Vec<[f64; 3]> = controls
|
||||
.iter()
|
||||
.map(|vertex| [vertex.position.x, vertex.position.y, vertex.position.z])
|
||||
.collect();
|
||||
let Some(samples) = polyline3d_spline_samples(&points, degree, pl.is_closed()) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
for vertex in &mut controls {
|
||||
vertex.flags = POLY3D_VERTEX | POLY3D_SPLINE_CONTROL;
|
||||
}
|
||||
let vertex_layer = controls
|
||||
.first()
|
||||
.map(|vertex| vertex.layer.clone())
|
||||
.unwrap_or_else(|| "0".to_string());
|
||||
let curve_vertices = samples.into_iter().map(|point| {
|
||||
let mut vertex = acadrust::entities::Vertex3DPolyline::from_xyz(
|
||||
point[0], point[1], point[2],
|
||||
);
|
||||
vertex.layer = vertex_layer.clone();
|
||||
vertex.flags = POLY3D_VERTEX | POLY3D_SPLINE_POINT;
|
||||
vertex
|
||||
});
|
||||
controls.extend(curve_vertices);
|
||||
pl.vertices = controls;
|
||||
pl.flags.spline_fit = true;
|
||||
true
|
||||
}
|
||||
|
||||
fn tessellate_polyline3d(pl: &Polyline3D) -> RenderEntity {
|
||||
let to_pt = |v: &acadrust::entities::Vertex3DPolyline| -> [f64; 3] {
|
||||
[v.position.x, v.position.y, v.position.z]
|
||||
@@ -791,8 +946,16 @@ fn tessellate_polyline3d(pl: &Polyline3D) -> RenderEntity {
|
||||
// DXF vertex flags: 8 = spline-fit curve point, 16 = spline frame control point.
|
||||
// When spline-fit vertices are present use them for the wire and control points for snap;
|
||||
// otherwise treat all vertices uniformly.
|
||||
let spline_curve: Vec<_> = pl.vertices.iter().filter(|v| v.flags & 8 != 0).collect();
|
||||
let ctrl_pts: Vec<_> = pl.vertices.iter().filter(|v| v.flags & 16 != 0).collect();
|
||||
let spline_curve: Vec<_> = pl
|
||||
.vertices
|
||||
.iter()
|
||||
.filter(|v| v.flags & POLY3D_SPLINE_POINT != 0)
|
||||
.collect();
|
||||
let ctrl_pts: Vec<_> = pl
|
||||
.vertices
|
||||
.iter()
|
||||
.filter(|v| v.flags & POLY3D_SPLINE_CONTROL != 0)
|
||||
.collect();
|
||||
|
||||
let (wire_pts, key_verts) = if !spline_curve.is_empty() {
|
||||
let wire: Vec<[f64; 3]> = spline_curve.iter().map(|v| to_pt(v)).collect();
|
||||
@@ -826,8 +989,9 @@ impl RenderConvertible for Polyline3D {
|
||||
|
||||
impl Grippable for Polyline3D {
|
||||
fn grips(&self) -> Vec<GripDef> {
|
||||
self.vertices
|
||||
.iter()
|
||||
polyline3d_control_indices(self)
|
||||
.into_iter()
|
||||
.filter_map(|index| self.vertices.get(index))
|
||||
.enumerate()
|
||||
.map(|(i, v)| {
|
||||
square_grip(
|
||||
@@ -839,7 +1003,8 @@ impl Grippable for Polyline3D {
|
||||
}
|
||||
|
||||
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
||||
if let Some(v) = self.vertices.get_mut(grip_id) {
|
||||
let raw_index = polyline3d_control_indices(self).get(grip_id).copied();
|
||||
if let Some(v) = raw_index.and_then(|index| self.vertices.get_mut(index)) {
|
||||
match apply {
|
||||
GripApply::Translate(d) => {
|
||||
v.position.x += d.x as f64;
|
||||
@@ -852,6 +1017,11 @@ impl Grippable for Polyline3D {
|
||||
v.position.z = p.z as f64;
|
||||
}
|
||||
}
|
||||
if self.smooth_type
|
||||
!= acadrust::entities::polyline3d::SmoothSurfaceType::None
|
||||
{
|
||||
let _ = rebuild_polyline3d_fit(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -875,31 +1045,44 @@ impl Grippable for Polyline3D {
|
||||
|
||||
fn apply_grip_menu(&mut self, grip_id: usize, action: crate::scene::model::object::GripMenuAction) {
|
||||
use crate::scene::model::object::GripMenuAction as A;
|
||||
let n = self.vertices.len();
|
||||
use acadrust::entities::polyline3d::SmoothSurfaceType as SST;
|
||||
|
||||
let mut controls = polyline3d_controls(self);
|
||||
let n = controls.len();
|
||||
match action {
|
||||
A::AddVertex if grip_id < n => {
|
||||
if grip_id == n - 1 && !self.is_closed() {
|
||||
let mut new_v = self.vertices[grip_id].clone();
|
||||
let mut new_v = controls[grip_id].clone();
|
||||
new_v.handle = acadrust::Handle::NULL;
|
||||
self.vertices.push(new_v);
|
||||
return;
|
||||
controls.push(new_v);
|
||||
} else {
|
||||
let i1 = (grip_id + 1) % n;
|
||||
let v0 = &controls[grip_id];
|
||||
let v1 = &controls[i1];
|
||||
let mx = (v0.position.x + v1.position.x) * 0.5;
|
||||
let my = (v0.position.y + v1.position.y) * 0.5;
|
||||
let mz = (v0.position.z + v1.position.z) * 0.5;
|
||||
let mut new_v = v0.clone();
|
||||
new_v.position.x = mx;
|
||||
new_v.position.y = my;
|
||||
new_v.position.z = mz;
|
||||
new_v.handle = acadrust::Handle::NULL;
|
||||
controls.insert(grip_id + 1, new_v);
|
||||
}
|
||||
let i1 = (grip_id + 1) % n;
|
||||
let v0 = &self.vertices[grip_id];
|
||||
let v1 = &self.vertices[i1];
|
||||
let mx = (v0.position.x + v1.position.x) * 0.5;
|
||||
let my = (v0.position.y + v1.position.y) * 0.5;
|
||||
let mz = (v0.position.z + v1.position.z) * 0.5;
|
||||
let mut new_v = v0.clone();
|
||||
new_v.position.x = mx;
|
||||
new_v.position.y = my;
|
||||
new_v.position.z = mz;
|
||||
new_v.handle = acadrust::Handle::NULL;
|
||||
let insert_at = (grip_id + 1).min(self.vertices.len());
|
||||
self.vertices.insert(insert_at, new_v);
|
||||
self.vertices = controls;
|
||||
let _ = rebuild_polyline3d_fit(self);
|
||||
}
|
||||
A::RemoveVertex if grip_id < n && n > 2 => {
|
||||
self.vertices.remove(grip_id);
|
||||
A::RemoveVertex if grip_id < n => {
|
||||
let minimum = match self.smooth_type {
|
||||
SST::None => 2,
|
||||
SST::QuadraticBSpline => 3,
|
||||
SST::CubicBSpline | SST::Bezier => 4,
|
||||
};
|
||||
if n > minimum {
|
||||
controls.remove(grip_id);
|
||||
self.vertices = controls;
|
||||
let _ = rebuild_polyline3d_fit(self);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -909,31 +1092,64 @@ impl Grippable for Polyline3D {
|
||||
impl PropertyEditable for Polyline3D {
|
||||
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
|
||||
use acadrust::entities::polyline3d::SmoothSurfaceType as SST;
|
||||
let n = self.vertices.len();
|
||||
let v0 = self.vertices.first();
|
||||
let vertex_x = v0.map(|v| v.position.x).unwrap_or_default();
|
||||
let vertex_y = v0.map(|v| v.position.y).unwrap_or_default();
|
||||
let vertex_z = v0.map(|v| v.position.z).unwrap_or_default();
|
||||
let control_indices = polyline3d_control_indices(self);
|
||||
let n = control_indices.len();
|
||||
let vi = if n == 0 {
|
||||
0
|
||||
} else {
|
||||
crate::scene::view::dispatch::prop_current_vertex().min(n - 1)
|
||||
};
|
||||
let vertex = control_indices
|
||||
.get(vi)
|
||||
.and_then(|index| self.vertices.get(*index));
|
||||
let vertex_x = vertex.map(|v| v.position.x).unwrap_or_default();
|
||||
let vertex_y = vertex.map(|v| v.position.y).unwrap_or_default();
|
||||
let vertex_z = vertex.map(|v| v.position.z).unwrap_or_default();
|
||||
let vertex_label = if n == 0 {
|
||||
"—".to_string()
|
||||
} else {
|
||||
format!("{} / {}", vi + 1, n)
|
||||
};
|
||||
let fit_smooth = match self.smooth_type {
|
||||
SST::None => "None",
|
||||
SST::QuadraticBSpline => "Quadratic",
|
||||
SST::CubicBSpline => "Cubic",
|
||||
SST::Bezier => "Bezier",
|
||||
SST::CubicBSpline | SST::Bezier => "Cubic",
|
||||
};
|
||||
|
||||
vec![
|
||||
PropSection {
|
||||
title: t!("Geometry").into_owned(),
|
||||
props: vec![
|
||||
ro(t!("Vertex").as_ref(), "pl3_vertex", if n > 0 { "1" } else { "" }),
|
||||
stepper(
|
||||
t!("Current Vertex").as_ref(),
|
||||
"pl3_current_vertex",
|
||||
vertex_label,
|
||||
),
|
||||
edit(t!("Vertex X").as_ref(), "pl3_vertex_x", vertex_x),
|
||||
edit(t!("Vertex Y").as_ref(), "pl3_vertex_y", vertex_y),
|
||||
edit(t!("Vertex Z").as_ref(), "pl3_vertex_z", vertex_z),
|
||||
ro(
|
||||
t!("Length").as_ref(),
|
||||
"pl3_length",
|
||||
format_length(polyline3d_length(self)),
|
||||
),
|
||||
],
|
||||
},
|
||||
PropSection {
|
||||
title: t!("Misc").into_owned(),
|
||||
props: vec![
|
||||
Property {
|
||||
label: t!("Fit/Smooth").into_owned(),
|
||||
field: "pl3_smooth",
|
||||
value: PropValue::Choice {
|
||||
selected: fit_smooth.to_string(),
|
||||
options: vec![
|
||||
"None".to_string(),
|
||||
"Quadratic".to_string(),
|
||||
"Cubic".to_string(),
|
||||
],
|
||||
},
|
||||
},
|
||||
Property {
|
||||
label: t!("Closed").into_owned(),
|
||||
field: "pl3_closed",
|
||||
@@ -942,7 +1158,6 @@ impl PropertyEditable for Polyline3D {
|
||||
value: self.is_closed(),
|
||||
},
|
||||
},
|
||||
ro(t!("Fit/Smooth").as_ref(), "pl3_smooth", fit_smooth),
|
||||
],
|
||||
},
|
||||
]
|
||||
@@ -961,20 +1176,57 @@ impl PropertyEditable for Polyline3D {
|
||||
} else {
|
||||
self.open();
|
||||
}
|
||||
if self.smooth_type
|
||||
!= acadrust::entities::polyline3d::SmoothSurfaceType::None
|
||||
{
|
||||
let _ = rebuild_polyline3d_fit(self);
|
||||
}
|
||||
}
|
||||
"pl3_smooth" => {
|
||||
use acadrust::entities::polyline3d::SmoothSurfaceType as SST;
|
||||
let next = match value {
|
||||
"None" => SST::None,
|
||||
"Quadratic" => SST::QuadraticBSpline,
|
||||
"Cubic" => SST::CubicBSpline,
|
||||
_ => return,
|
||||
};
|
||||
let previous = self.smooth_type;
|
||||
self.smooth_type = next;
|
||||
if !rebuild_polyline3d_fit(self) {
|
||||
self.smooth_type = previous;
|
||||
}
|
||||
}
|
||||
"pl3_vertex_x" => {
|
||||
if let (Some(v), Some(vert)) = (parse_f64(value), self.vertices.first_mut()) {
|
||||
let vi = crate::scene::view::dispatch::prop_current_vertex();
|
||||
let raw_index = polyline3d_control_indices(self).get(vi).copied();
|
||||
if let (Some(v), Some(vert)) = (
|
||||
parse_f64(value),
|
||||
raw_index.and_then(|index| self.vertices.get_mut(index)),
|
||||
) {
|
||||
vert.position.x = v;
|
||||
let _ = rebuild_polyline3d_fit(self);
|
||||
}
|
||||
}
|
||||
"pl3_vertex_y" => {
|
||||
if let (Some(v), Some(vert)) = (parse_f64(value), self.vertices.first_mut()) {
|
||||
let vi = crate::scene::view::dispatch::prop_current_vertex();
|
||||
let raw_index = polyline3d_control_indices(self).get(vi).copied();
|
||||
if let (Some(v), Some(vert)) = (
|
||||
parse_f64(value),
|
||||
raw_index.and_then(|index| self.vertices.get_mut(index)),
|
||||
) {
|
||||
vert.position.y = v;
|
||||
let _ = rebuild_polyline3d_fit(self);
|
||||
}
|
||||
}
|
||||
"pl3_vertex_z" => {
|
||||
if let (Some(v), Some(vert)) = (parse_f64(value), self.vertices.first_mut()) {
|
||||
let vi = crate::scene::view::dispatch::prop_current_vertex();
|
||||
let raw_index = polyline3d_control_indices(self).get(vi).copied();
|
||||
if let (Some(v), Some(vert)) = (
|
||||
parse_f64(value),
|
||||
raw_index.and_then(|index| self.vertices.get_mut(index)),
|
||||
) {
|
||||
vert.position.z = v;
|
||||
let _ = rebuild_polyline3d_fit(self);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
||||
@@ -67,7 +67,7 @@ impl CadCommand for Poly3dCommand {
|
||||
fn prompt(&self) -> String {
|
||||
match self.points.len() {
|
||||
0 => t!("3DPOLY Specify start point of polyline:").into_owned(),
|
||||
1 => t!("3DPOLY Specify next point [Undo]:").into_owned(),
|
||||
1 | 2 => t!("3DPOLY Specify next point [Undo]:").into_owned(),
|
||||
_ => t!("3DPOLY Specify next point [Close/Undo]:").into_owned(),
|
||||
}
|
||||
}
|
||||
@@ -98,10 +98,10 @@ impl CadCommand for Poly3dCommand {
|
||||
|
||||
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
||||
match text.trim().to_uppercase().as_str() {
|
||||
"C" | "CLOSE" => match self.build(true) {
|
||||
Some(e) => Some(CmdResult::CommitAndExit(e)),
|
||||
None => Some(CmdResult::NeedPoint),
|
||||
},
|
||||
"C" | "CLOSE" if self.points.len() >= 3 => self
|
||||
.build(true)
|
||||
.map(CmdResult::CommitAndExit),
|
||||
"C" | "CLOSE" => Some(CmdResult::NeedPoint),
|
||||
"U" | "UNDO" => {
|
||||
self.points.pop();
|
||||
Some(CmdResult::NeedPoint)
|
||||
|
||||
@@ -1701,6 +1701,9 @@ fn coord_suffix(label: &str) -> Option<(&str, usize)> {
|
||||
/// Length of the coordinate group starting at `idx`: consecutive text rows
|
||||
/// labelled "<Base> X", "<Base> Y" and optionally "<Base> Z". 0/1 = no group.
|
||||
fn coord_group_len(props: &[crate::scene::model::object::Property], idx: usize) -> usize {
|
||||
if props[idx].field == "pl3_vertex_x" {
|
||||
return 0;
|
||||
}
|
||||
let groupable = |p: &crate::scene::model::object::Property| {
|
||||
matches!(
|
||||
p.value,
|
||||
|
||||
Reference in new issue
Block a user