feat(cad): integrate standard codec coverage

Render, edit, query, select, and explode newly decoded standard entity variants. Pin acadifc de8d6b5 and preserve None color semantics.
This commit is contained in:
Hakan Seven committed 2026-07-26 19:30:43 +03:00
1 parent 34d0c377e2
commit 731df4a725
19 files changed
+484 -72

No files matched your search

Generated
+1 -1
View File
@@ -75,7 +75,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
[[package]]
name = "acadrust"
version = "0.4.0"
source = "git+https://github.com/OpenAEC-Foundation/acadifc.git?rev=8cc4793#8cc479342635fe16694e17226670d800cb3a1dfe"
source = "git+https://github.com/OpenAEC-Foundation/acadifc.git?rev=de8d6b5#de8d6b5e262468fa8292c0bb7ecf4fbcb042d709"
dependencies = [
"ahash 0.8.12",
"anyhow",
+1 -1
View File
@@ -91,7 +91,7 @@ windows-sys = { version = "0.61", features = ["Win32_UI_Shell", "Win32_UI_Window
[patch.crates-io]
# Track the verified DWG round-trip, I/O, and unified PERF fixes.
acadrust = { git = "https://github.com/OpenAEC-Foundation/acadifc.git", rev = "8cc4793" }
acadrust = { git = "https://github.com/OpenAEC-Foundation/acadifc.git", rev = "de8d6b5" }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
# Native enables the plugin host runtime (out-of-process plugins).
+16
View File
@@ -1750,6 +1750,22 @@ impl OpenCADStudio {
stretched |= mv(&mut d.feature_location);
stretched |= mv(&mut d.leader_endpoint);
}
Dimension::Arc(d) => {
stretched |= mv(&mut d.definition_point);
stretched |= mv(&mut d.first_extension_point);
stretched |= mv(&mut d.second_extension_point);
stretched |= mv(&mut d.center_point);
if d.has_leader {
stretched |= mv(&mut d.first_leader_point);
stretched |= mv(&mut d.second_leader_point);
}
}
Dimension::LargeRadial(d) => {
stretched |= mv(&mut d.definition_point);
stretched |= mv(&mut d.chord_point);
stretched |= mv(&mut d.override_center);
stretched |= mv(&mut d.jog_point);
}
}
// Pinned text follows too; the zero sentinel means
// "auto placement" and must not be captured by a
+2
View File
@@ -1323,6 +1323,8 @@ fn add_selected_verb(entity: &acadrust::EntityType) -> Option<&'static str> {
DimensionType::Diameter => "DIMDIAMETER",
DimensionType::Radius => "DIMRADIUS",
DimensionType::Ordinate => "DIMORDINATE",
DimensionType::ArcLength => "DIMARC",
DimensionType::LargeRadial => "DIMJOGGED",
}
}
_ => return None,
+1
View File
@@ -383,6 +383,7 @@ pub(super) fn entity_type_key(entity: &acadrust::EntityType) -> String {
Light(_) => "light",
SectionSymbol(_) => "sectionsymbol",
ViewBorder(_) => "viewborder",
Extended(entity) => return entity.class_name().to_ascii_lowercase(),
Seqend(_) => "seqend",
Unknown(_) => "unknown",
}
+2 -3
View File
@@ -1117,7 +1117,7 @@ const QSELECT_ANY_PROP: &str = "(Any property)";
/// fields (Start X, Length, Radius, …) so type-specific filtering works.
pub(super) fn qselect_overlay<'a>(
state: &'a crate::app::QSelectState,
types: &[&'static str],
types: &[String],
properties: &[(String, String)],
) -> Element<'a, Message> {
use iced::widget::{checkbox, pick_list};
@@ -1165,7 +1165,7 @@ pub(super) fn qselect_overlay<'a>(
};
let mut type_options: Vec<String> = vec![QSELECT_ANY_TYPE.to_string()];
type_options.extend(types.iter().map(|s| (*s).to_string()));
type_options.extend(types.iter().cloned());
let mut prop_options: Vec<crate::app::QSelectPropertyChoice> =
vec![crate::app::QSelectPropertyChoice {
@@ -1337,4 +1337,3 @@ pub(super) fn qselect_overlay<'a>(
stack![catcher, centered].into()
}
+287 -8
View File
@@ -1,6 +1,7 @@
use acadrust::entities::{
Dimension, DimensionAligned, DimensionAngular2Ln, DimensionAngular3Pt, DimensionBase,
DimensionDiameter, DimensionLinear, DimensionOrdinate, DimensionRadius,
Dimension, DimensionAligned, DimensionAngular2Ln, DimensionAngular3Pt, DimensionArc,
DimensionBase, DimensionDiameter, DimensionLargeRadial, DimensionLinear, DimensionOrdinate,
DimensionRadius,
};
use acadrust::Entity;
use glam::{DVec3, Vec3};
@@ -128,6 +129,47 @@ fn properties(dim: &Dimension) -> Vec<PropSection> {
if d.is_ordinate_type_x { "X" } else { "Y" },
));
}
Dimension::Arc(d) => {
props.extend(angular_props(
d.center_point,
d.first_extension_point,
d.second_extension_point,
d.definition_point,
));
props.push(edit(
"Arc Start (deg)",
"arc_start_parameter",
d.arc_start_parameter.to_degrees(),
));
props.push(edit(
"Arc End (deg)",
"arc_end_parameter",
d.arc_end_parameter.to_degrees(),
));
props.push(ro("Partial", "is_partial", d.is_partial.to_string()));
props.push(ro("Has Leader", "has_leader", d.has_leader.to_string()));
props.push(edit("Leader 1 X", "leader1_x", d.first_leader_point.x));
props.push(edit("Leader 1 Y", "leader1_y", d.first_leader_point.y));
props.push(edit("Leader 1 Z", "leader1_z", d.first_leader_point.z));
props.push(edit("Leader 2 X", "leader2_x", d.second_leader_point.x));
props.push(edit("Leader 2 Y", "leader2_y", d.second_leader_point.y));
props.push(edit("Leader 2 Z", "leader2_z", d.second_leader_point.z));
}
Dimension::LargeRadial(d) => {
props.push(edit("Center X", "definition_x", d.definition_point.x));
props.push(edit("Center Y", "definition_y", d.definition_point.y));
props.push(edit("Center Z", "definition_z", d.definition_point.z));
props.push(edit("Chord X", "chord_x", d.chord_point.x));
props.push(edit("Chord Y", "chord_y", d.chord_point.y));
props.push(edit("Chord Z", "chord_z", d.chord_point.z));
props.push(edit("Override Center X", "override_x", d.override_center.x));
props.push(edit("Override Center Y", "override_y", d.override_center.y));
props.push(edit("Override Center Z", "override_z", d.override_center.z));
props.push(edit("Jog X", "jog_x", d.jog_point.x));
props.push(edit("Jog Y", "jog_y", d.jog_point.y));
props.push(edit("Jog Z", "jog_z", d.jog_point.z));
props.push(edit_angle("Jog Angle", "jog_angle", d.jog_angle.to_degrees()));
}
}
vec![PropSection {
title: "Geometry".into(),
@@ -259,6 +301,8 @@ fn apply_geom_prop(dim: &mut Dimension, field: &str, value: &str) {
Dimension::Angular2Ln(d) => apply_angular2_fields(d, field, value),
Dimension::Angular3Pt(d) => apply_angular3_fields(d, field, value),
Dimension::Ordinate(d) => apply_ordinate_fields(d, field, value),
Dimension::Arc(d) => apply_arc_fields(d, field, value),
Dimension::LargeRadial(d) => apply_large_radial_fields(d, field, value),
}
dim.base_mut().actual_measurement = dim.measurement();
}
@@ -495,6 +539,89 @@ fn apply_ordinate_fields(d: &mut DimensionOrdinate, field: &str, value: &str) {
}
}
fn apply_arc_fields(d: &mut DimensionArc, field: &str, value: &str) {
apply_angular_common(
&mut d.center_point,
&mut d.first_extension_point,
&mut d.second_extension_point,
&mut d.definition_point,
field,
value,
);
match field {
"arc_start_parameter" => {
let _ = assign_deg(value, &mut d.arc_start_parameter);
}
"arc_end_parameter" => {
let _ = assign_deg(value, &mut d.arc_end_parameter);
}
"leader1_x" => {
let _ = assign_f64(value, &mut d.first_leader_point.x);
}
"leader1_y" => {
let _ = assign_f64(value, &mut d.first_leader_point.y);
}
"leader1_z" => {
let _ = assign_f64(value, &mut d.first_leader_point.z);
}
"leader2_x" => {
let _ = assign_f64(value, &mut d.second_leader_point.x);
}
"leader2_y" => {
let _ = assign_f64(value, &mut d.second_leader_point.y);
}
"leader2_z" => {
let _ = assign_f64(value, &mut d.second_leader_point.z);
}
_ => {}
}
}
fn apply_large_radial_fields(d: &mut DimensionLargeRadial, field: &str, value: &str) {
match field {
"definition_x" => {
let _ = assign_f64(value, &mut d.definition_point.x);
}
"definition_y" => {
let _ = assign_f64(value, &mut d.definition_point.y);
}
"definition_z" => {
let _ = assign_f64(value, &mut d.definition_point.z);
}
"chord_x" => {
let _ = assign_f64(value, &mut d.chord_point.x);
}
"chord_y" => {
let _ = assign_f64(value, &mut d.chord_point.y);
}
"chord_z" => {
let _ = assign_f64(value, &mut d.chord_point.z);
}
"override_x" => {
let _ = assign_f64(value, &mut d.override_center.x);
}
"override_y" => {
let _ = assign_f64(value, &mut d.override_center.y);
}
"override_z" => {
let _ = assign_f64(value, &mut d.override_center.z);
}
"jog_x" => {
let _ = assign_f64(value, &mut d.jog_point.x);
}
"jog_y" => {
let _ = assign_f64(value, &mut d.jog_point.y);
}
"jog_z" => {
let _ = assign_f64(value, &mut d.jog_point.z);
}
"jog_angle" => {
let _ = assign_deg(value, &mut d.jog_angle);
}
_ => {}
}
}
fn apply_transform(dim: &mut Dimension, t: &EntityTransform) {
match t {
EntityTransform::Translate(d) => dim.translate(acadrust::types::Vector3::new(
@@ -556,6 +683,22 @@ where
f(&mut d.feature_location);
f(&mut d.leader_endpoint);
}
Dimension::Arc(d) => {
f(&mut d.definition_point);
f(&mut d.first_extension_point);
f(&mut d.second_extension_point);
f(&mut d.center_point);
if d.has_leader {
f(&mut d.first_leader_point);
f(&mut d.second_leader_point);
}
}
Dimension::LargeRadial(d) => {
f(&mut d.definition_point);
f(&mut d.chord_point);
f(&mut d.override_center);
f(&mut d.jog_point);
}
}
}
@@ -677,6 +820,29 @@ impl Grippable for Dimension {
center_grip(2, dv3(&d.leader_endpoint)),
center_grip(3, text),
],
Dimension::Arc(d) => {
let mut grips = vec![
square_grip(0, dv3(&d.center_point)),
center_grip(1, dv3(&d.first_extension_point)),
center_grip(2, dv3(&d.second_extension_point)),
center_grip(3, dv3(&d.definition_point)),
];
if d.has_leader {
grips.push(center_grip(4, dv3(&d.first_leader_point)));
grips.push(center_grip(5, dv3(&d.second_leader_point)));
grips.push(center_grip(6, text));
} else {
grips.push(center_grip(4, text));
}
grips
}
Dimension::LargeRadial(d) => vec![
square_grip(0, dv3(&d.definition_point)),
center_grip(1, dv3(&d.chord_point)),
center_grip(2, dv3(&d.override_center)),
center_grip(3, dv3(&d.jog_point)),
center_grip(4, text),
],
}
}
@@ -687,6 +853,8 @@ impl Grippable for Dimension {
Dimension::Radius(_) | Dimension::Diameter(_) => 2,
Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_) => 4,
Dimension::Ordinate(_) => 3,
Dimension::Arc(d) => if d.has_leader { 6 } else { 4 },
Dimension::LargeRadial(_) => 4,
};
if grip_id == text_grip {
apply_to_v3(&mut self.base_mut().text_middle_point, &apply);
@@ -739,6 +907,22 @@ impl Grippable for Dimension {
2 => apply_to_v3(&mut d.leader_endpoint, &apply),
_ => {}
},
Dimension::Arc(d) => match grip_id {
0 => apply_to_v3(&mut d.center_point, &apply),
1 => apply_to_v3(&mut d.first_extension_point, &apply),
2 => apply_to_v3(&mut d.second_extension_point, &apply),
3 => apply_to_v3(&mut d.definition_point, &apply),
4 => apply_to_v3(&mut d.first_leader_point, &apply),
5 => apply_to_v3(&mut d.second_leader_point, &apply),
_ => {}
},
Dimension::LargeRadial(d) => match grip_id {
0 => apply_to_v3(&mut d.definition_point, &apply),
1 => apply_to_v3(&mut d.chord_point, &apply),
2 => apply_to_v3(&mut d.override_center, &apply),
3 => apply_to_v3(&mut d.jog_point, &apply),
_ => {}
},
}
self.base_mut().actual_measurement = self.measurement();
}
@@ -750,6 +934,8 @@ impl Grippable for Dimension {
Dimension::Radius(_) | Dimension::Diameter(_) => (1, 2),
Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_) => (3, 4),
Dimension::Ordinate(_) => (0, 3),
Dimension::Arc(d) => (3, if d.has_leader { 6 } else { 4 }),
Dimension::LargeRadial(_) => (3, 4),
};
if grip_id == text_grip {
vec![
@@ -816,6 +1002,8 @@ impl Grippable for Dimension {
Dimension::Radius(_) | Dimension::Diameter(_) => (1, 2),
Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_) => (3, 4),
Dimension::Ordinate(_) => (0, 3),
Dimension::Arc(d) => (3, if d.has_leader { 6 } else { 4 }),
Dimension::LargeRadial(_) => (3, 4),
};
match action {
A::ResetText if grip_id == text_grip => {
@@ -1860,6 +2048,7 @@ fn dimension_geometry(
lv(d.dimension_arc),
arrow1,
arrow2,
None,
);
}
Dimension::Angular3Pt(d) => {
@@ -1871,6 +2060,7 @@ fn dimension_geometry(
lv(d.definition_point),
arrow1,
arrow2,
None,
);
}
Dimension::Ordinate(d) => {
@@ -1885,10 +2075,74 @@ fn dimension_geometry(
lv(d.leader_endpoint),
);
}
Dimension::Arc(d) => {
append_angular_dimension(
&mut g,
lv(d.center_point),
lv(d.first_extension_point),
lv(d.second_extension_point),
lv(d.definition_point),
arrow1,
arrow2,
d.is_partial.then_some((
d.arc_start_parameter as f32,
d.arc_end_parameter as f32,
)),
);
if d.has_leader {
add_segment(
&mut g.dim_lines,
lv(d.first_leader_point),
lv(d.second_leader_point),
);
}
}
Dimension::LargeRadial(d) => {
let chord = lv(d.chord_point);
let jog = lv(d.jog_point);
let override_center = lv(d.override_center);
let (near, far) =
jogged_radial_break(chord, jog, override_center, d.jog_angle as f32);
add_segment(&mut g.dim_lines, chord, near);
add_segment(&mut g.dim_lines, near, far);
add_segment(&mut g.dim_lines, far, override_center);
append_arrow(
&mut g,
chord,
normalized_or(near - chord, Vec3::X),
arrow1,
);
}
}
g
}
fn jogged_radial_break(
chord: Vec3,
jog: Vec3,
override_center: Vec3,
jog_angle: f32,
) -> (Vec3, Vec3) {
let radial = normalized_or(chord - override_center, Vec3::X);
let (sin, cos) = jog_angle.sin_cos();
let transverse = normalized_or(
Vec3::new(
radial.x * cos - radial.y * sin,
radial.x * sin + radial.y * cos,
0.0,
),
Vec3::Y,
);
let half = ((chord - override_center).length() * 0.04).max(1e-3);
let first = jog - transverse * half;
let second = jog + transverse * half;
if chord.distance_squared(first) <= chord.distance_squared(second) {
(first, second)
} else {
(second, first)
}
}
fn append_linear_dimension(
g: &mut DimGeom,
first: Vec3,
@@ -2058,6 +2312,7 @@ fn append_angular_dimension(
arc_point: Vec3,
arrow1: &ArrowKind,
arrow2: &ArrowKind,
explicit_sweep: Option<(f32, f32)>,
) {
let radius = vertex.distance(arc_point);
if radius <= 1e-6 {
@@ -2067,18 +2322,19 @@ fn append_angular_dimension(
}
// Extension lines run from each measured point out to the dimension arc so
// there is no gap between a ray and its arc endpoint. (#181 / DIM-027)
let dir1 = normalized_or(first - vertex, Vec3::X);
let dir2 = normalized_or(second - vertex, Vec3::X);
let measured_start = (first.y - vertex.y).atan2(first.x - vertex.x);
let measured_end = (second.y - vertex.y).atan2(second.x - vertex.x);
let (start, mut end) = explicit_sweep.unwrap_or((measured_start, measured_end));
let dir1 = Vec3::new(start.cos(), start.sin(), 0.0);
let dir2 = Vec3::new(end.cos(), end.sin(), 0.0);
add_segment(&mut g.ext_lines, first, vertex + dir1 * radius);
add_segment(&mut g.ext_lines, second, vertex + dir2 * radius);
let start = (first.y - vertex.y).atan2(first.x - vertex.x);
let mut end = (second.y - vertex.y).atan2(second.x - vertex.x);
let mut delta = end - start;
while delta <= 0.0 {
delta += std::f32::consts::TAU;
}
if delta > std::f32::consts::PI {
if explicit_sweep.is_none() && delta > std::f32::consts::PI {
end -= std::f32::consts::TAU;
delta = end - start;
}
@@ -2142,6 +2398,25 @@ fn dimension_snap_pts(dim: &Dimension) -> Vec<(glam::DVec3, SnapHint)> {
node(d.feature_location),
node(d.leader_endpoint),
],
Dimension::Arc(d) => {
let mut points = vec![
node(d.center_point),
node(d.first_extension_point),
node(d.second_extension_point),
node(d.definition_point),
];
if d.has_leader {
points.push(node(d.first_leader_point));
points.push(node(d.second_leader_point));
}
points
}
Dimension::LargeRadial(d) => vec![
node(d.definition_point),
node(d.chord_point),
node(d.override_center),
node(d.jog_point),
],
}
}
@@ -2383,7 +2658,7 @@ fn dimension_text_parts(
} else {
let v = format_linear_value(dim.measurement(), style);
match dim {
Dimension::Radius(_) => format!("R{}", v),
Dimension::Radius(_) | Dimension::LargeRadial(_) => format!("R{}", v),
Dimension::Diameter(_) => format!("Ø{}", v),
_ => v,
}
@@ -2858,6 +3133,8 @@ fn dimension_text_position(dim: &Dimension) -> Vec3 {
Dimension::Angular2Ln(d) => lv(d.dimension_arc),
Dimension::Angular3Pt(d) => lv(d.definition_point),
Dimension::Ordinate(d) => lv(d.leader_endpoint),
Dimension::Arc(d) => lv(d.definition_point),
Dimension::LargeRadial(d) => lv(d.jog_point),
}
}
@@ -3037,6 +3314,8 @@ fn dimension_text_pos_f64(
Dimension::Angular2Ln(d) => d.dimension_arc,
Dimension::Angular3Pt(d) => d.definition_point,
Dimension::Ordinate(d) => d.leader_endpoint,
Dimension::Arc(d) => d.definition_point,
Dimension::LargeRadial(d) => d.jog_point,
_ => base.text_middle_point,
};
Vector3::new(mid.x, mid.y + perp_off * perp_sign_default(), mid.z)
+4
View File
@@ -57,6 +57,7 @@ pub fn ui_name(e: &EntityType) -> &'static str {
EntityType::Light(_) => "Light",
EntityType::SectionSymbol(_) => "Section Symbol",
EntityType::ViewBorder(_) => "View Border",
EntityType::Extended(_) => "Extended Entity",
EntityType::Seqend(_) => "Seqend",
EntityType::Unknown(_) => "Unknown",
}
@@ -68,6 +69,9 @@ pub fn ui_name(e: &EntityType) -> &'static str {
/// defers to [`ui_name`]. Falls back to "Unknown" for a class we couldn't name
/// (the numeric `DWG_TYPE_<n>` placeholder).
pub fn ui_name_or_class(e: &EntityType) -> String {
if let EntityType::Extended(entity) = e {
return entity.class_name().to_string();
}
if let EntityType::Unknown(u) = e {
let n = u.dxf_name.trim();
if !n.is_empty() && !n.starts_with("DWG_TYPE_") {
+2 -1
View File
@@ -97,7 +97,7 @@ pub trait TextContent {
/// Human-readable variant name used by Quick Select / Select Similar
/// filtering. Stable across releases — the strings here are the canonical
/// type identifiers throughout the UI.
pub fn entity_type_name(et: &EntityType) -> &'static str {
pub fn entity_type_name(et: &EntityType) -> &str {
match et {
EntityType::Point(_) => "Point",
EntityType::Line(_) => "Line",
@@ -145,6 +145,7 @@ pub fn entity_type_name(et: &EntityType) -> &'static str {
EntityType::Light(_) => "Light",
EntityType::SectionSymbol(_) => "SectionSymbol",
EntityType::ViewBorder(_) => "ViewBorder",
EntityType::Extended(entity) => entity.class_name(),
EntityType::Unknown(_) => "Unknown",
}
}
-14
View File
@@ -527,20 +527,6 @@ pub fn dropped_on_save_count(
.dwg_source_version
.is_some_and(|source| source != target_version)
}
acadrust::EntityType::SectionSymbol(entity) => {
is_dxf
|| entity.raw_dwg_data.is_none()
|| entity
.dwg_source_version
.is_some_and(|source| source != target_version)
}
acadrust::EntityType::ViewBorder(entity) => {
is_dxf
|| entity.raw_dwg_data.is_none()
|| entity
.dwg_source_version
.is_some_and(|source| source != target_version)
}
_ => false,
};
if dropped {
+129 -10
View File
@@ -502,12 +502,23 @@ fn dim_arc_segs(
while sweep <= -PI {
sweep += 2.0 * PI;
}
dim_arc_segs_with_sweep(center, radius, a1, sweep, common)
}
fn dim_arc_segs_with_sweep(
center: Vector3,
radius: f64,
start: f64,
sweep: f64,
common: &acadrust::entities::EntityCommon,
) -> Vec<EntityType> {
use std::f64::consts::PI;
let steps = 12usize.max((sweep.abs() / (PI / 36.0)).ceil() as usize);
let pt = |a: f64| Vector3::new(center.x + radius * a.cos(), center.y + radius * a.sin(), center.z);
let mut out = Vec::new();
let mut prev = pt(a1);
let mut prev = pt(start);
for i in 1..=steps {
let cur = pt(a1 + sweep * (i as f64 / steps as f64));
let cur = pt(start + sweep * (i as f64 / steps as f64));
out.push(dim_seg(prev, cur, common));
prev = cur;
}
@@ -631,10 +642,12 @@ fn angular_block_segs(
met: &DimMetrics,
ext_c: &acadrust::entities::EntityCommon,
dim_c: &acadrust::entities::EntityCommon,
explicit_sweep: Option<(f64, f64)>,
) -> Vec<EntityType> {
use std::f64::consts::PI;
let a1 = (p1.y - vertex.y).atan2(p1.x - vertex.x);
let a2 = (p2.y - vertex.y).atan2(p2.x - vertex.x);
let measured_a1 = (p1.y - vertex.y).atan2(p1.x - vertex.x);
let measured_a2 = (p2.y - vertex.y).atan2(p2.x - vertex.x);
let (a1, a2) = explicit_sweep.unwrap_or((measured_a1, measured_a2));
let radius = ((arc_loc.x - vertex.x).powi(2) + (arc_loc.y - vertex.y).powi(2)).sqrt();
let mut out = Vec::new();
if radius < 1e-9 {
@@ -647,14 +660,29 @@ fn angular_block_segs(
let e2 = Vector3::new(vertex.x + a2.cos() * radius, vertex.y + a2.sin() * radius, vertex.z);
out.push(dim_seg(p1, e1, ext_c));
out.push(dim_seg(p2, e2, ext_c));
out.extend(dim_arc_segs(vertex, radius, a1, a2, dim_c));
// Terminators tangent to the arc at each end, pointing along the sweep.
let mut sweep = a2 - a1;
while sweep > PI {
sweep -= 2.0 * PI;
if explicit_sweep.is_some() {
while sweep <= 0.0 {
sweep += 2.0 * PI;
}
out.extend(dim_arc_segs_with_sweep(
vertex,
radius,
a1,
sweep,
dim_c,
));
} else {
out.extend(dim_arc_segs(vertex, radius, a1, a2, dim_c));
}
while sweep <= -PI {
sweep += 2.0 * PI;
// Terminators tangent to the arc at each end, pointing along the sweep.
if explicit_sweep.is_none() {
while sweep > PI {
sweep -= 2.0 * PI;
}
while sweep <= -PI {
sweep += 2.0 * PI;
}
}
let sgn = if sweep >= 0.0 { 1.0 } else { -1.0 };
out.extend(dim_terminator(e1, -a1.sin() * sgn, a1.cos() * sgn, &met.arrow1, dim_c));
@@ -662,6 +690,49 @@ fn angular_block_segs(
out
}
fn jogged_radial_break(
chord: Vector3,
jog: Vector3,
override_center: Vector3,
jog_angle: f64,
) -> (Vector3, Vector3) {
let radial = norm2(
chord.x - override_center.x,
chord.y - override_center.y,
1.0,
0.0,
);
let (sin, cos) = jog_angle.sin_cos();
let transverse = (
radial.0 * cos - radial.1 * sin,
radial.0 * sin + radial.1 * cos,
);
let length = ((chord.x - override_center.x).powi(2)
+ (chord.y - override_center.y).powi(2))
.sqrt();
let half = (length * 0.04).max(1e-6);
let first = Vector3::new(
jog.x - transverse.0 * half,
jog.y - transverse.1 * half,
jog.z,
);
let second = Vector3::new(
jog.x + transverse.0 * half,
jog.y + transverse.1 * half,
jog.z,
);
let distance_squared = |point: Vector3| {
(point.x - chord.x).powi(2)
+ (point.y - chord.y).powi(2)
+ (point.z - chord.z).powi(2)
};
if distance_squared(first) <= distance_squared(second) {
(first, second)
} else {
(second, first)
}
}
/// The text anchor for a radial leader: the saved text middle point when set,
/// else the midpoint of `a` and `b` — mirroring the live `dimension_text_position`.
fn dim_text_anchor(
@@ -902,6 +973,7 @@ fn explode_dimension(dim: &Dimension, doc: &CadDocument) -> Vec<EntityType> {
&met,
&ext_c,
&dim_c,
None,
));
}
Dimension::Angular3Pt(d) => {
@@ -913,12 +985,57 @@ fn explode_dimension(dim: &Dimension, doc: &CadDocument) -> Vec<EntityType> {
&met,
&ext_c,
&dim_c,
None,
));
}
Dimension::Ordinate(d) => {
result.push(make_seg(&d.feature_location, &d.definition_point, &dim_c));
result.push(make_seg(&d.definition_point, &d.leader_endpoint, &dim_c));
}
Dimension::Arc(d) => {
result.extend(angular_block_segs(
d.center_point,
d.first_extension_point,
d.second_extension_point,
d.definition_point,
&met,
&ext_c,
&dim_c,
d.is_partial.then_some((
d.arc_start_parameter,
d.arc_end_parameter,
)),
));
if d.has_leader {
result.push(make_seg(
&d.first_leader_point,
&d.second_leader_point,
&dim_c,
));
}
}
Dimension::LargeRadial(d) => {
let (near, far) = jogged_radial_break(
d.chord_point,
d.jog_point,
d.override_center,
d.jog_angle,
);
result.push(make_seg(&d.chord_point, &near, &dim_c));
result.push(make_seg(&near, &far, &dim_c));
result.push(make_seg(&far, &d.override_center, &dim_c));
let len = ((near.x - d.chord_point.x).powi(2)
+ (near.y - d.chord_point.y).powi(2))
.sqrt()
.max(1e-12);
result.extend(dim_terminator(
d.chord_point,
(near.x - d.chord_point.x) / len,
(near.y - d.chord_point.y) / len,
&met.arrow1,
&dim_c,
));
}
}
// Measurement text is the live render's own Text/MText entity (value,
@@ -963,6 +1080,8 @@ fn sync_dimension_base_points_and_collect_pending(doc: &mut CadDocument) -> Vec<
Dimension::Diameter(value) => Some(value.definition_point),
Dimension::Ordinate(value) => Some(value.definition_point),
Dimension::Angular3Pt(value) => Some(value.definition_point),
Dimension::Arc(value) => Some(value.definition_point),
Dimension::LargeRadial(value) => Some(value.definition_point),
Dimension::Angular2Ln(_) => None,
};
let base = dimension.base();
+25 -27
View File
@@ -62,33 +62,31 @@ impl Scene {
let eye_dir = cam.rotation * glam::Vec3::Z;
let height = cam.ortho_size() * 2.0;
let width = height; // caller can adjust; rough square
acadrust::tables::View {
handle: acadrust::types::Handle::NULL,
name: name.to_string(),
center: Vector3 {
x: cam.target.x as f64,
y: cam.target.y as f64,
z: 0.0,
},
target: Vector3 {
x: cam.target.x as f64,
y: cam.target.y as f64,
z: cam.target.z as f64,
},
direction: Vector3 {
x: eye_dir.x as f64,
y: eye_dir.y as f64,
z: eye_dir.z as f64,
},
height: height as f64,
width: width as f64,
lens_length: 50.0,
front_clip: 0.0,
back_clip: 0.0,
twist_angle: 0.0,
// OCS saves an orthographic model view, not a perspective camera.
perspective: false,
}
let mut view = acadrust::tables::View::new(name);
view.center = Vector3 {
x: cam.target.x as f64,
y: cam.target.y as f64,
z: 0.0,
};
view.target = Vector3 {
x: cam.target.x as f64,
y: cam.target.y as f64,
z: cam.target.z as f64,
};
view.direction = Vector3 {
x: eye_dir.x as f64,
y: eye_dir.y as f64,
z: eye_dir.z as f64,
};
view.height = height as f64;
view.width = width as f64;
view.lens_length = 50.0;
view.front_clip = 0.0;
view.back_clip = 0.0;
view.twist_angle = 0.0;
// OCS saves an orthographic model view, not a perspective camera.
view.perspective = false;
view
}
/// Zoom the model-space camera in/out by a percentage.
+2 -2
View File
@@ -37,7 +37,7 @@ fn section_arrow_dir_from_views(
s: &acadrust::entities::SectionSymbol,
) -> Option<[f64; 2]> {
use acadrust::types::Handle as AHandle;
if s.view_rep_handle == 0 {
if s.view_rep_handle.is_null() {
return None;
}
// A view's active viewport, via its border entity among the ViewRep refs.
@@ -47,7 +47,7 @@ fn section_arrow_dir_from_views(
_ => None,
}
};
let parent_vr = AHandle::from(s.view_rep_handle);
let parent_vr = s.view_rep_handle;
let parent_refs = document.view_rep_refs.get(&parent_vr)?;
let parent_vp_h = parent_refs.iter().find_map(border_vp)?;
let sect_vr = parent_refs
+2 -1
View File
@@ -298,7 +298,8 @@ impl ImageModel {
/// the frame is degenerate or nothing in the blob decodes, so the caller
/// falls back to the frame placeholder.
pub fn from_ole2frame(ole: &acadrust::entities::Ole2Frame) -> Option<Self> {
let (pixels, width, height) = super::ole_pres::decode(&ole.binary_data)?;
let payload = ole.encoded_payload();
let (pixels, width, height) = super::ole_pres::decode(&payload)?;
// Frame rectangle in WCS. `upper_left`/`lower_right` name the diagonal;
// normalise to left/right/top/bottom so the bitmap sits upright.
+5 -4
View File
@@ -55,7 +55,7 @@ impl Scene {
if self.selected.is_empty() {
return 0;
}
let pairs: rustc_hash::FxHashSet<(&'static str, String)> = self
let pairs: rustc_hash::FxHashSet<(&str, String)> = self
.selected
.iter()
.filter_map(|h| self.document.get_entity(*h))
@@ -182,13 +182,13 @@ impl Scene {
/// Returns the sorted set of entity-type names present in the active
/// layout. Used to populate the Quick Select "Object type" dropdown
/// with only the types that actually exist in the drawing.
pub fn entity_type_names_in_layout(&self) -> Vec<&'static str> {
pub fn entity_type_names_in_layout(&self) -> Vec<String> {
use crate::entities::traits::entity_type_name;
let mut names: std::collections::BTreeSet<&'static str> =
let mut names: std::collections::BTreeSet<String> =
std::collections::BTreeSet::new();
for h in self.current_layout_entity_handles() {
if let Some(e) = self.document.get_entity(h) {
names.insert(entity_type_name(e));
names.insert(entity_type_name(e).to_string());
}
}
names.into_iter().collect()
@@ -311,6 +311,7 @@ impl Scene {
use acadrust::types::Color;
match c {
Color::ByLayer => "ByLayer".to_string(),
Color::None => "None".to_string(),
Color::ByBlock => "ByBlock".to_string(),
Color::Index(i) => i.to_string(),
Color::Rgb { r, g, b } => format!("{},{},{}", r, g, b),
+2
View File
@@ -45,6 +45,7 @@ pub fn color_to_aci_string(c: AcadColor) -> String {
match c {
AcadColor::ByBlock => "0".to_string(),
AcadColor::ByLayer => "256".to_string(),
AcadColor::None => "257".to_string(),
AcadColor::Index(i) => i.to_string(),
AcadColor::Rgb { .. } => "256".to_string(),
}
@@ -55,6 +56,7 @@ pub fn aci_string_to_color(s: &str) -> AcadColor {
match s.trim().parse::<i16>().unwrap_or(256) {
0 => AcadColor::ByBlock,
256 => AcadColor::ByLayer,
257 => AcadColor::None,
n if (1..=255).contains(&n) => AcadColor::Index(n as u8),
_ => AcadColor::ByLayer,
}
+1
View File
@@ -1356,6 +1356,7 @@ fn picker_text_btn(label: &str, msg: Message) -> Element<'_, Message> {
/// Returns an (iced::Color swatch_bg, display_label) pair for an AcadColor.
pub fn acad_color_display(c: AcadColor) -> (Color, &'static str) {
match c {
AcadColor::None => (Color::TRANSPARENT, "None"),
AcadColor::ByLayer => (
Color {
r: 0.35,
+1
View File
@@ -38,6 +38,7 @@ pub fn view_window<'a>(
.map(|(idx, e)| {
let color_str = match &e.color {
acadrust::types::Color::ByLayer => "ByLayer".into(),
acadrust::types::Color::None => "None".into(),
acadrust::types::Color::ByBlock => "ByBlock".into(),
acadrust::types::Color::Index(i) => format!("ACI {i}"),
acadrust::types::Color::Rgb { r, g, b } => format!("#{r:02X}{g:02X}{b:02X}"),
+1
View File
@@ -116,6 +116,7 @@ const COLOR_OPTIONS: &[&str] = &[
pub fn color_label(c: AcadColor) -> String {
match c {
AcadColor::ByLayer => "ByLayer".into(),
AcadColor::None => "None".into(),
AcadColor::ByBlock => "ByBlock".into(),
AcadColor::Index(1) => "Red".into(),
AcadColor::Index(2) => "Yellow".into(),