forked from 1b-agent/opencadstudio-upstream
refactor(commands): per-entity MassProps + TextContent traits
Two free helpers in app/commands.rs lived as big match-on-EntityType blocks: massprop_entity (Circle/Arc/Line/LwPolyline/Ellipse area + perimeter + centroid, ~140 lines) and entity_text_content / replace_entity_text (Text/MText/AttributeDefinition/AttributeEntity). Move each to its own trait in entities/traits.rs and implement per-entity: MassPropsCalc — Circle, Arc, Line, LwPolyline, Ellipse TextContent — Text, MText, AttributeDefinition, AttributeEntity EntityTypeOps gets dispatch shims that route to the trait via the existing macro, so callsites just call entity.mass_props() / entity.text_content() / entity.replace_text(search, rep). The remaining free helpers in commands.rs (entity_list_details, entity_extra_info, entity_type_name, build_data_extraction_csv) are single-command formatting helpers tightly coupled to LIST / EXTRACT / text-format output. Distributing them as per-entity methods would inflate every entity file with command-side formatting concerns without making the command-side any cleaner — left in place. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
1 parent
40180e9089
commit
e78321190a
10 files changed
+266
-193
No files matched your search
+5
-193
@@ -2102,7 +2102,8 @@ impl OpenCADStudio {
|
|||||||
} else {
|
} else {
|
||||||
for (handle, _) in &selected {
|
for (handle, _) in &selected {
|
||||||
if let Some(entity) = self.tabs[i].scene.document.get_entity(*handle) {
|
if let Some(entity) = self.tabs[i].scene.document.get_entity(*handle) {
|
||||||
if let Some(props) = massprop_entity(entity) {
|
use crate::entities::traits::EntityTypeOps;
|
||||||
|
if let Some(props) = entity.mass_props() {
|
||||||
self.command_line.push_output(&format!(
|
self.command_line.push_output(&format!(
|
||||||
"{} Area={:.4} Perimeter={:.4} Centroid=({:.4},{:.4})",
|
"{} Area={:.4} Perimeter={:.4} Centroid=({:.4},{:.4})",
|
||||||
entity_type_name(entity),
|
entity_type_name(entity),
|
||||||
@@ -2264,7 +2265,7 @@ impl OpenCADStudio {
|
|||||||
.document
|
.document
|
||||||
.entities()
|
.entities()
|
||||||
.filter_map(|e| {
|
.filter_map(|e| {
|
||||||
let txt = entity_text_content(e)?;
|
use crate::entities::traits::EntityTypeOps; let txt = e.text_content()?;
|
||||||
if txt.to_lowercase().contains(&search_lc) {
|
if txt.to_lowercase().contains(&search_lc) {
|
||||||
Some(e.common().handle)
|
Some(e.common().handle)
|
||||||
} else {
|
} else {
|
||||||
@@ -2287,7 +2288,7 @@ impl OpenCADStudio {
|
|||||||
self.push_undo_snapshot(i, "FIND/REPLACE");
|
self.push_undo_snapshot(i, "FIND/REPLACE");
|
||||||
for h in &targets {
|
for h in &targets {
|
||||||
if let Some(e) = self.tabs[i].scene.document.get_entity_mut(*h) {
|
if let Some(e) = self.tabs[i].scene.document.get_entity_mut(*h) {
|
||||||
replace_entity_text(e, search, rep);
|
crate::entities::traits::EntityTypeOps::replace_text(e, search, rep);
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2306,7 +2307,7 @@ impl OpenCADStudio {
|
|||||||
} else {
|
} else {
|
||||||
for h in &handles {
|
for h in &handles {
|
||||||
if let Some(e) = self.tabs[i].scene.document.get_entity(*h) {
|
if let Some(e) = self.tabs[i].scene.document.get_entity(*h) {
|
||||||
let txt = entity_text_content(e).unwrap_or_default();
|
use crate::entities::traits::EntityTypeOps; let txt = e.text_content().unwrap_or_default();
|
||||||
self.command_line.push_output(&format!(
|
self.command_line.push_output(&format!(
|
||||||
" Handle {:X}: \"{}\"",
|
" Handle {:X}: \"{}\"",
|
||||||
h.value(),
|
h.value(),
|
||||||
@@ -5378,196 +5379,7 @@ fn entity_type_name(entity: &acadrust::EntityType) -> &'static str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn entity_text_content(entity: &acadrust::EntityType) -> Option<String> {
|
|
||||||
match entity {
|
|
||||||
acadrust::EntityType::Text(t) => Some(t.value.clone()),
|
|
||||||
acadrust::EntityType::MText(t) => Some(t.value.clone()),
|
|
||||||
acadrust::EntityType::AttributeDefinition(a) => Some(a.default_value.clone()),
|
|
||||||
acadrust::EntityType::AttributeEntity(a) => Some(a.get_value().to_string()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── MASSPROP helpers ───────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
struct MassProps {
|
|
||||||
area: f64,
|
|
||||||
perimeter: f64,
|
|
||||||
cx: f64,
|
|
||||||
cy: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn massprop_entity(entity: &acadrust::EntityType) -> Option<MassProps> {
|
|
||||||
use std::f64::consts::{PI, TAU};
|
|
||||||
|
|
||||||
match entity {
|
|
||||||
acadrust::EntityType::Circle(c) => {
|
|
||||||
let r = c.radius;
|
|
||||||
Some(MassProps {
|
|
||||||
area: PI * r * r,
|
|
||||||
perimeter: TAU * r,
|
|
||||||
cx: c.center.x,
|
|
||||||
cy: c.center.y,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
acadrust::EntityType::Arc(a) => {
|
|
||||||
let r = a.radius;
|
|
||||||
let span = {
|
|
||||||
let s = (a.end_angle - a.start_angle).rem_euclid(TAU);
|
|
||||||
if s < 1e-6 {
|
|
||||||
TAU
|
|
||||||
} else {
|
|
||||||
s
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Sector area (pie slice)
|
|
||||||
let area = 0.5 * r * r * span;
|
|
||||||
let arc_len = r * span;
|
|
||||||
// Centroid of arc (chord midpoint direction)
|
|
||||||
let mid_rad = a.start_angle + span / 2.0;
|
|
||||||
Some(MassProps {
|
|
||||||
area,
|
|
||||||
perimeter: arc_len,
|
|
||||||
cx: a.center.x + r * mid_rad.cos(),
|
|
||||||
cy: a.center.y + r * mid_rad.sin(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
acadrust::EntityType::Line(l) => {
|
|
||||||
let dx = l.end.x - l.start.x;
|
|
||||||
let dy = l.end.y - l.start.y;
|
|
||||||
let len = (dx * dx + dy * dy).sqrt();
|
|
||||||
Some(MassProps {
|
|
||||||
area: 0.0,
|
|
||||||
perimeter: len,
|
|
||||||
cx: (l.start.x + l.end.x) / 2.0,
|
|
||||||
cy: (l.start.y + l.end.y) / 2.0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
acadrust::EntityType::LwPolyline(p) => {
|
|
||||||
let n = p.vertices.len();
|
|
||||||
if n < 2 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
// Shoelace area + perimeter
|
|
||||||
let mut area_sum = 0.0f64;
|
|
||||||
let mut perimeter = 0.0f64;
|
|
||||||
let mut cx_sum = 0.0f64;
|
|
||||||
let mut cy_sum = 0.0f64;
|
|
||||||
let n_segs = if p.is_closed { n } else { n - 1 };
|
|
||||||
for idx in 0..n_segs {
|
|
||||||
let v0 = &p.vertices[idx];
|
|
||||||
let v1 = &p.vertices[(idx + 1) % n];
|
|
||||||
let x0 = v0.location.x;
|
|
||||||
let y0 = v0.location.y;
|
|
||||||
let x1 = v1.location.x;
|
|
||||||
let y1 = v1.location.y;
|
|
||||||
area_sum += x0 * y1 - x1 * y0;
|
|
||||||
perimeter += ((x1 - x0).powi(2) + (y1 - y0).powi(2)).sqrt();
|
|
||||||
cx_sum += (x0 + x1) * (x0 * y1 - x1 * y0);
|
|
||||||
cy_sum += (y0 + y1) * (x0 * y1 - x1 * y0);
|
|
||||||
}
|
|
||||||
let area = (area_sum / 2.0).abs();
|
|
||||||
let (cx, cy) = if area > 1e-12 {
|
|
||||||
(cx_sum / (6.0 * area), cy_sum / (6.0 * area))
|
|
||||||
} else {
|
|
||||||
let sx: f64 = p.vertices.iter().map(|v| v.location.x).sum::<f64>() / n as f64;
|
|
||||||
let sy: f64 = p.vertices.iter().map(|v| v.location.y).sum::<f64>() / n as f64;
|
|
||||||
(sx, sy)
|
|
||||||
};
|
|
||||||
Some(MassProps {
|
|
||||||
area,
|
|
||||||
perimeter,
|
|
||||||
cx,
|
|
||||||
cy,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
acadrust::EntityType::Ellipse(e) => {
|
|
||||||
let a = (e.major_axis.x.powi(2) + e.major_axis.y.powi(2)).sqrt();
|
|
||||||
let b = a * e.minor_axis_ratio;
|
|
||||||
let t0 = e.start_parameter;
|
|
||||||
let t1 = {
|
|
||||||
let mut t = e.end_parameter;
|
|
||||||
if t <= t0 {
|
|
||||||
t += TAU;
|
|
||||||
}
|
|
||||||
t
|
|
||||||
};
|
|
||||||
let span = t1 - t0;
|
|
||||||
let is_full = (span - TAU).abs() < 1e-6;
|
|
||||||
let area = if is_full {
|
|
||||||
PI * a * b
|
|
||||||
} else {
|
|
||||||
// Sector area of ellipse approximated via 256-pt integration
|
|
||||||
let n = 256usize;
|
|
||||||
let mut s = 0.0f64;
|
|
||||||
for k in 0..n {
|
|
||||||
let t = t0 + span * (k as f64 / n as f64);
|
|
||||||
let tp = t0 + span * ((k + 1) as f64 / n as f64);
|
|
||||||
let nx = e.major_axis.x / a;
|
|
||||||
let ny = e.major_axis.y / a;
|
|
||||||
let x0 = a * t.cos() * nx - b * t.sin() * ny;
|
|
||||||
let y0 = a * t.cos() * ny + b * t.sin() * nx;
|
|
||||||
let x1 = a * tp.cos() * nx - b * tp.sin() * ny;
|
|
||||||
let y1 = a * tp.cos() * ny + b * tp.sin() * nx;
|
|
||||||
s += x0 * y1 - x1 * y0;
|
|
||||||
}
|
|
||||||
(s / 2.0).abs()
|
|
||||||
};
|
|
||||||
// Arc length via 256-pt numerical integration
|
|
||||||
let nx = e.major_axis.x / a.max(1e-12);
|
|
||||||
let ny = e.major_axis.y / a.max(1e-12);
|
|
||||||
let perimeter = {
|
|
||||||
let n = 256usize;
|
|
||||||
let mut len = 0.0f64;
|
|
||||||
for k in 0..n {
|
|
||||||
let t = t0 + span * (k as f64 / n as f64);
|
|
||||||
let tp = t0 + span * ((k + 1) as f64 / n as f64);
|
|
||||||
let x0 = e.center.x + a * t.cos() * nx - b * t.sin() * ny;
|
|
||||||
let y0 = e.center.y + a * t.cos() * ny + b * t.sin() * nx;
|
|
||||||
let x1 = e.center.x + a * tp.cos() * nx - b * tp.sin() * ny;
|
|
||||||
let y1 = e.center.y + a * tp.cos() * ny + b * tp.sin() * nx;
|
|
||||||
len += (x1 - x0).hypot(y1 - y0);
|
|
||||||
}
|
|
||||||
len
|
|
||||||
};
|
|
||||||
Some(MassProps {
|
|
||||||
area,
|
|
||||||
perimeter,
|
|
||||||
cx: e.center.x,
|
|
||||||
cy: e.center.y,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn replace_entity_text(entity: &mut acadrust::EntityType, search: &str, rep: &str) {
|
|
||||||
let search_lc = search.to_lowercase();
|
|
||||||
match entity {
|
|
||||||
acadrust::EntityType::Text(t) => {
|
|
||||||
if t.value.to_lowercase().contains(&search_lc) {
|
|
||||||
t.value = t.value.replace(search, rep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
acadrust::EntityType::MText(t) => {
|
|
||||||
if t.value.to_lowercase().contains(&search_lc) {
|
|
||||||
t.value = t.value.replace(search, rep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
acadrust::EntityType::AttributeDefinition(a) => {
|
|
||||||
if a.default_value.to_lowercase().contains(&search_lc) {
|
|
||||||
a.default_value = a.default_value.replace(search, rep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
acadrust::EntityType::AttributeEntity(a) => {
|
|
||||||
let cur = a.get_value().to_string();
|
|
||||||
if cur.to_lowercase().contains(&search_lc) {
|
|
||||||
a.set_value(cur.replace(search, rep));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── DATAEXTRACTION ─────────────────────────────────────────────────────────
|
// ── DATAEXTRACTION ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
@@ -244,3 +244,29 @@ impl Transformable for Arc {
|
|||||||
apply_transform(self, t);
|
apply_transform(self, t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl crate::entities::traits::MassPropsCalc for acadrust::entities::Arc {
|
||||||
|
fn mass_props(&self) -> crate::entities::traits::MassProps {
|
||||||
|
use std::f64::consts::TAU;
|
||||||
|
let r = self.radius;
|
||||||
|
let span = {
|
||||||
|
let s = (self.end_angle - self.start_angle).rem_euclid(TAU);
|
||||||
|
if s < 1e-6 {
|
||||||
|
TAU
|
||||||
|
} else {
|
||||||
|
s
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Sector area (pie slice)
|
||||||
|
let area = 0.5 * r * r * span;
|
||||||
|
let arc_len = r * span;
|
||||||
|
// Centroid of arc (chord midpoint direction)
|
||||||
|
let mid_rad = self.start_angle + span / 2.0;
|
||||||
|
crate::entities::traits::MassProps {
|
||||||
|
area,
|
||||||
|
perimeter: arc_len,
|
||||||
|
cx: self.center.x + r * mid_rad.cos(),
|
||||||
|
cy: self.center.y + r * mid_rad.sin(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -792,3 +792,28 @@ impl Transformable for AttributeEntity {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl crate::entities::traits::TextContent for acadrust::entities::AttributeDefinition {
|
||||||
|
fn text_content(&self) -> Option<String> {
|
||||||
|
Some(self.default_value.clone())
|
||||||
|
}
|
||||||
|
fn replace_text(&mut self, search: &str, rep: &str) {
|
||||||
|
let search_lc = search.to_lowercase();
|
||||||
|
if self.default_value.to_lowercase().contains(&search_lc) {
|
||||||
|
self.default_value = self.default_value.replace(search, rep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::entities::traits::TextContent for acadrust::entities::AttributeEntity {
|
||||||
|
fn text_content(&self) -> Option<String> {
|
||||||
|
Some(self.get_value().to_string())
|
||||||
|
}
|
||||||
|
fn replace_text(&mut self, search: &str, rep: &str) {
|
||||||
|
let search_lc = search.to_lowercase();
|
||||||
|
let cur = self.get_value().to_string();
|
||||||
|
if cur.to_lowercase().contains(&search_lc) {
|
||||||
|
self.set_value(cur.replace(search, rep));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -227,3 +227,16 @@ impl Transformable for Circle {
|
|||||||
apply_transform(self, t);
|
apply_transform(self, t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl crate::entities::traits::MassPropsCalc for Circle {
|
||||||
|
fn mass_props(&self) -> crate::entities::traits::MassProps {
|
||||||
|
use std::f64::consts::{PI, TAU};
|
||||||
|
let r = self.radius;
|
||||||
|
crate::entities::traits::MassProps {
|
||||||
|
area: PI * r * r,
|
||||||
|
perimeter: TAU * r,
|
||||||
|
cx: self.center.x,
|
||||||
|
cy: self.center.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -276,3 +276,64 @@ impl Transformable for Ellipse {
|
|||||||
apply_transform(self, t);
|
apply_transform(self, t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl crate::entities::traits::MassPropsCalc for acadrust::entities::Ellipse {
|
||||||
|
fn mass_props(&self) -> crate::entities::traits::MassProps {
|
||||||
|
use std::f64::consts::{PI, TAU};
|
||||||
|
let e = self;
|
||||||
|
let a = (e.major_axis.x.powi(2) + e.major_axis.y.powi(2)).sqrt();
|
||||||
|
let b = a * e.minor_axis_ratio;
|
||||||
|
let t0 = e.start_parameter;
|
||||||
|
let t1 = {
|
||||||
|
let mut t = e.end_parameter;
|
||||||
|
if t <= t0 {
|
||||||
|
t += TAU;
|
||||||
|
}
|
||||||
|
t
|
||||||
|
};
|
||||||
|
let span = t1 - t0;
|
||||||
|
let is_full = (span - TAU).abs() < 1e-6;
|
||||||
|
let area = if is_full {
|
||||||
|
PI * a * b
|
||||||
|
} else {
|
||||||
|
// Sector area of ellipse approximated via 256-pt integration
|
||||||
|
let n = 256usize;
|
||||||
|
let mut s = 0.0f64;
|
||||||
|
for k in 0..n {
|
||||||
|
let t = t0 + span * (k as f64 / n as f64);
|
||||||
|
let tp = t0 + span * ((k + 1) as f64 / n as f64);
|
||||||
|
let nx = e.major_axis.x / a;
|
||||||
|
let ny = e.major_axis.y / a;
|
||||||
|
let x0 = a * t.cos() * nx - b * t.sin() * ny;
|
||||||
|
let y0 = a * t.cos() * ny + b * t.sin() * nx;
|
||||||
|
let x1 = a * tp.cos() * nx - b * tp.sin() * ny;
|
||||||
|
let y1 = a * tp.cos() * ny + b * tp.sin() * nx;
|
||||||
|
s += x0 * y1 - x1 * y0;
|
||||||
|
}
|
||||||
|
(s / 2.0).abs()
|
||||||
|
};
|
||||||
|
// Arc length via 256-pt numerical integration
|
||||||
|
let nx = e.major_axis.x / a.max(1e-12);
|
||||||
|
let ny = e.major_axis.y / a.max(1e-12);
|
||||||
|
let perimeter = {
|
||||||
|
let n = 256usize;
|
||||||
|
let mut len = 0.0f64;
|
||||||
|
for k in 0..n {
|
||||||
|
let t = t0 + span * (k as f64 / n as f64);
|
||||||
|
let tp = t0 + span * ((k + 1) as f64 / n as f64);
|
||||||
|
let x0 = e.center.x + a * t.cos() * nx - b * t.sin() * ny;
|
||||||
|
let y0 = e.center.y + a * t.cos() * ny + b * t.sin() * nx;
|
||||||
|
let x1 = e.center.x + a * tp.cos() * nx - b * tp.sin() * ny;
|
||||||
|
let y1 = e.center.y + a * tp.cos() * ny + b * tp.sin() * nx;
|
||||||
|
len += (x1 - x0).hypot(y1 - y0);
|
||||||
|
}
|
||||||
|
len
|
||||||
|
};
|
||||||
|
crate::entities::traits::MassProps {
|
||||||
|
area,
|
||||||
|
perimeter,
|
||||||
|
cx: e.center.x,
|
||||||
|
cy: e.center.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -181,3 +181,17 @@ impl Transformable for Line {
|
|||||||
apply_transform(self, t);
|
apply_transform(self, t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl crate::entities::traits::MassPropsCalc for acadrust::entities::Line {
|
||||||
|
fn mass_props(&self) -> crate::entities::traits::MassProps {
|
||||||
|
let dx = self.end.x - self.start.x;
|
||||||
|
let dy = self.end.y - self.start.y;
|
||||||
|
let len = (dx * dx + dy * dy).sqrt();
|
||||||
|
crate::entities::traits::MassProps {
|
||||||
|
area: 0.0,
|
||||||
|
perimeter: len,
|
||||||
|
cx: (self.start.x + self.end.x) / 2.0,
|
||||||
|
cy: (self.start.y + self.end.y) / 2.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -549,3 +549,50 @@ pub(crate) fn wide_fills(pl: &acadrust::entities::LwPolyline) -> Vec<Vec<[f32; 2
|
|||||||
}
|
}
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl crate::entities::traits::MassPropsCalc for acadrust::entities::LwPolyline {
|
||||||
|
fn mass_props(&self) -> crate::entities::traits::MassProps {
|
||||||
|
let p = self;
|
||||||
|
let n = p.vertices.len();
|
||||||
|
if n < 2 {
|
||||||
|
return crate::entities::traits::MassProps {
|
||||||
|
area: 0.0,
|
||||||
|
perimeter: 0.0,
|
||||||
|
cx: 0.0,
|
||||||
|
cy: 0.0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Shoelace area + perimeter
|
||||||
|
let mut area_sum = 0.0f64;
|
||||||
|
let mut perimeter = 0.0f64;
|
||||||
|
let mut cx_sum = 0.0f64;
|
||||||
|
let mut cy_sum = 0.0f64;
|
||||||
|
let n_segs = if p.is_closed { n } else { n - 1 };
|
||||||
|
for idx in 0..n_segs {
|
||||||
|
let v0 = &p.vertices[idx];
|
||||||
|
let v1 = &p.vertices[(idx + 1) % n];
|
||||||
|
let x0 = v0.location.x;
|
||||||
|
let y0 = v0.location.y;
|
||||||
|
let x1 = v1.location.x;
|
||||||
|
let y1 = v1.location.y;
|
||||||
|
area_sum += x0 * y1 - x1 * y0;
|
||||||
|
perimeter += ((x1 - x0).powi(2) + (y1 - y0).powi(2)).sqrt();
|
||||||
|
cx_sum += (x0 + x1) * (x0 * y1 - x1 * y0);
|
||||||
|
cy_sum += (y0 + y1) * (x0 * y1 - x1 * y0);
|
||||||
|
}
|
||||||
|
let area = (area_sum / 2.0).abs();
|
||||||
|
let (cx, cy) = if area > 1e-12 {
|
||||||
|
(cx_sum / (6.0 * area), cy_sum / (6.0 * area))
|
||||||
|
} else {
|
||||||
|
let sx: f64 = p.vertices.iter().map(|v| v.location.x).sum::<f64>() / n as f64;
|
||||||
|
let sy: f64 = p.vertices.iter().map(|v| v.location.y).sum::<f64>() / n as f64;
|
||||||
|
(sx, sy)
|
||||||
|
};
|
||||||
|
crate::entities::traits::MassProps {
|
||||||
|
area,
|
||||||
|
perimeter,
|
||||||
|
cx,
|
||||||
|
cy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -317,3 +317,15 @@ impl Transformable for MText {
|
|||||||
apply_transform(self, t);
|
apply_transform(self, t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl crate::entities::traits::TextContent for acadrust::entities::MText {
|
||||||
|
fn text_content(&self) -> Option<String> {
|
||||||
|
Some(self.value.clone())
|
||||||
|
}
|
||||||
|
fn replace_text(&mut self, search: &str, rep: &str) {
|
||||||
|
let search_lc = search.to_lowercase();
|
||||||
|
if self.value.to_lowercase().contains(&search_lc) {
|
||||||
|
self.value = self.value.replace(search, rep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -332,3 +332,15 @@ impl Transformable for Text {
|
|||||||
apply_transform(self, t);
|
apply_transform(self, t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl crate::entities::traits::TextContent for acadrust::entities::Text {
|
||||||
|
fn text_content(&self) -> Option<String> {
|
||||||
|
Some(self.value.clone())
|
||||||
|
}
|
||||||
|
fn replace_text(&mut self, search: &str, rep: &str) {
|
||||||
|
let search_lc = search.to_lowercase();
|
||||||
|
if self.value.to_lowercase().contains(&search_lc) {
|
||||||
|
self.value = self.value.replace(search, rep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,6 +31,30 @@ pub trait Transformable {
|
|||||||
fn apply_transform(&mut self, t: &EntityTransform);
|
fn apply_transform(&mut self, t: &EntityTransform);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Inquiry-time mass / area / perimeter properties for entities whose
|
||||||
|
/// 2D footprint has a meaningful area or perimeter (Circle, Arc, Line,
|
||||||
|
/// LwPolyline, Ellipse). Entities outside this set get `None` via the
|
||||||
|
/// dispatcher.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct MassProps {
|
||||||
|
pub area: f64,
|
||||||
|
pub perimeter: f64,
|
||||||
|
pub cx: f64,
|
||||||
|
pub cy: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait MassPropsCalc {
|
||||||
|
fn mass_props(&self) -> MassProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read / replace the visible string of text-like entities (Text, MText,
|
||||||
|
/// AttributeDefinition, AttributeEntity). `replace` rewrites every
|
||||||
|
/// occurrence of `search` with `rep`.
|
||||||
|
pub trait TextContent {
|
||||||
|
fn text_content(&self) -> Option<String>;
|
||||||
|
fn replace_text(&mut self, search: &str, rep: &str);
|
||||||
|
}
|
||||||
|
|
||||||
pub trait EntityTypeOps {
|
pub trait EntityTypeOps {
|
||||||
fn to_truck_entity(&self, document: &CadDocument) -> Option<TruckEntity>;
|
fn to_truck_entity(&self, document: &CadDocument) -> Option<TruckEntity>;
|
||||||
fn grips(&self) -> Vec<GripDef>;
|
fn grips(&self) -> Vec<GripDef>;
|
||||||
@@ -38,6 +62,9 @@ pub trait EntityTypeOps {
|
|||||||
fn apply_geom_prop(&mut self, field: &str, value: &str);
|
fn apply_geom_prop(&mut self, field: &str, value: &str);
|
||||||
fn apply_grip(&mut self, grip_id: usize, apply: GripApply);
|
fn apply_grip(&mut self, grip_id: usize, apply: GripApply);
|
||||||
fn apply_transform(&mut self, t: &EntityTransform);
|
fn apply_transform(&mut self, t: &EntityTransform);
|
||||||
|
fn mass_props(&self) -> Option<MassProps>;
|
||||||
|
fn text_content(&self) -> Option<String>;
|
||||||
|
fn replace_text(&mut self, search: &str, rep: &str);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Per-dispatch-function entity-variant lists. Adding a new entity that
|
/// Per-dispatch-function entity-variant lists. Adding a new entity that
|
||||||
@@ -148,4 +175,28 @@ impl EntityTypeOps for EntityType {
|
|||||||
_ => {},
|
_ => {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn mass_props(&self) -> Option<MassProps> {
|
||||||
|
dispatch!(self,
|
||||||
|
|e| Some(MassPropsCalc::mass_props(e)),
|
||||||
|
[Circle, Arc, Line, LwPolyline, Ellipse],
|
||||||
|
_ => None,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn text_content(&self) -> Option<String> {
|
||||||
|
dispatch!(self,
|
||||||
|
|e| TextContent::text_content(e),
|
||||||
|
[Text, MText, AttributeDefinition, AttributeEntity],
|
||||||
|
_ => None,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_text(&mut self, search: &str, rep: &str) {
|
||||||
|
dispatch!(self,
|
||||||
|
|e| TextContent::replace_text(e, search, rep),
|
||||||
|
[Text, MText, AttributeDefinition, AttributeEntity],
|
||||||
|
_ => {},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user