forked from 1b-agent/opencadstudio-upstream
fix(dimension): harden jogged radius workflow
This commit is contained in:
commit
bc750c6d76
71 files changed
+5834
-860
No files matched your search
+415
-9
@@ -146,6 +146,11 @@ fn properties(dim: &Dimension) -> Vec<PropSection> {
|
||||
"jog_angle",
|
||||
d.jog_angle.to_degrees(),
|
||||
),
|
||||
ro(
|
||||
t!("Measurement").as_ref(),
|
||||
"measurement",
|
||||
format!("{:.4}", d.measurement()),
|
||||
),
|
||||
],
|
||||
}];
|
||||
}
|
||||
@@ -216,6 +221,16 @@ fn properties(dim: &Dimension) -> Vec<PropSection> {
|
||||
],
|
||||
}];
|
||||
}
|
||||
if let Dimension::Arc(d) = dim {
|
||||
return vec![PropSection {
|
||||
title: t!("Misc").into_owned(),
|
||||
props: vec![Property {
|
||||
label: t!("Dimension style").into_owned(),
|
||||
field: "style_name",
|
||||
value: PropValue::PlainText(d.base.style_name.clone()),
|
||||
}],
|
||||
}];
|
||||
}
|
||||
let mut props = base_props(dim.base());
|
||||
match dim {
|
||||
Dimension::Aligned(d) => {
|
||||
@@ -2638,6 +2653,33 @@ pub fn style_sections(
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(dimension, Dimension::Arc(_)) {
|
||||
if let Some(lines) = sections
|
||||
.iter_mut()
|
||||
.find(|section| section.title == t!("Lines & Arrows").as_ref())
|
||||
{
|
||||
let symbol = match int(ov::DIMARCSYM, s.dimarcsym) {
|
||||
1 => "Above dimension text",
|
||||
2 => "None",
|
||||
_ => "Preceding dimension text",
|
||||
};
|
||||
lines.props.insert(
|
||||
3,
|
||||
choice(
|
||||
t!("Arc length symbol").as_ref(),
|
||||
"dim_arc_symbol",
|
||||
symbol,
|
||||
&[
|
||||
"Preceding dimension text",
|
||||
"Above dimension text",
|
||||
"None",
|
||||
],
|
||||
true,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(dimension, Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_)) {
|
||||
if let Some(text_section) = sections
|
||||
.iter_mut()
|
||||
@@ -2914,7 +2956,7 @@ use crate::scene::convert::tessellate::{
|
||||
add_polyline, add_segment, append_arrow, arrow_from_block_with_deferred_hatch,
|
||||
normalized_or, ArrowKind, DimGeom,
|
||||
};
|
||||
use crate::scene::model::wire_model::{SnapHint, WireModel};
|
||||
use crate::scene::model::wire_model::{SnapHint, TangentGeom, WireModel};
|
||||
|
||||
fn apply_dimension_breaks(
|
||||
document: &CadDocument,
|
||||
@@ -3078,6 +3120,50 @@ fn tessellate_dimension_inner(
|
||||
view_aabb: Option<[f32; 4]>,
|
||||
world_per_pixel: Option<f32>,
|
||||
) -> Vec<WireModel> {
|
||||
if let Dimension::Arc(arc) = dim {
|
||||
if let Some((local_arc, normal)) = arc_dimension_in_ocs(arc) {
|
||||
let mut wires = tessellate_dimension_inner(
|
||||
document,
|
||||
handle,
|
||||
&Dimension::Arc(local_arc),
|
||||
selected,
|
||||
entity_color,
|
||||
line_weight_px,
|
||||
anno_scale,
|
||||
selected_set,
|
||||
active_viewport,
|
||||
bg_color,
|
||||
None,
|
||||
world_per_pixel,
|
||||
);
|
||||
for wire in &mut wires {
|
||||
map_wire_ocs_to_wcs(wire, normal);
|
||||
}
|
||||
return wires;
|
||||
}
|
||||
}
|
||||
if let Dimension::LargeRadial(radial) = dim {
|
||||
if let Some((local_radial, normal)) = large_radial_dimension_in_ocs(radial) {
|
||||
let mut wires = tessellate_dimension_inner(
|
||||
document,
|
||||
handle,
|
||||
&Dimension::LargeRadial(local_radial),
|
||||
selected,
|
||||
entity_color,
|
||||
line_weight_px,
|
||||
anno_scale,
|
||||
selected_set,
|
||||
active_viewport,
|
||||
bg_color,
|
||||
None,
|
||||
world_per_pixel,
|
||||
);
|
||||
for wire in &mut wires {
|
||||
map_wire_ocs_to_wcs(wire, normal);
|
||||
}
|
||||
return wires;
|
||||
}
|
||||
}
|
||||
let name = handle.value().to_string();
|
||||
// (Baked-block fast path moved up into scene::tessellate_entity so the
|
||||
// recursive call goes through the LOD ladder, not the kernel path.)
|
||||
@@ -3445,9 +3531,6 @@ fn tessellate_dimension_inner(
|
||||
// DIMUPT governs interactive creation-time text placement; saved
|
||||
// geometry already carries the resulting position.
|
||||
let _ = s.dimupt;
|
||||
// Per-entity arc-symbol and jog-angle values are consumed by their
|
||||
// native dimension variants; the resolved style values remain the
|
||||
// creation defaults.
|
||||
let _ = (s.dimarcsym, s.dimjogang);
|
||||
// DIMUNIT is the obsolete pre-R2000 linear unit format; DIMLUNIT
|
||||
// supersedes it. Read but not honoured.
|
||||
@@ -3674,6 +3757,46 @@ fn tessellate_dimension_inner(
|
||||
fill_tris_low: Vec::new(),
|
||||
});
|
||||
|
||||
if let Some(symbol) = style.and_then(|style| {
|
||||
arc_length_symbol_points(dim, Some(style), dim_txt, dim_scale, style.dimarcsym)
|
||||
}) {
|
||||
let mut points = Vec::new();
|
||||
add_polyline(&mut points, &symbol);
|
||||
wires.push(WireModel {
|
||||
point_marker: None,
|
||||
taper_widths: Vec::new(),
|
||||
pattern_stations: 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: name.clone(),
|
||||
points,
|
||||
points_low: Vec::new(),
|
||||
color: if selected { WireModel::SELECTED } else { text_color },
|
||||
selected,
|
||||
aci: 0,
|
||||
pattern_length: 0.0,
|
||||
pattern: [0.0; 8],
|
||||
line_weight_px: 1.0,
|
||||
snap_pts: vec![],
|
||||
tangent_geoms: vec![],
|
||||
key_vertices: vec![],
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
fill_tris: vec![],
|
||||
fill_tris_low: Vec::new(),
|
||||
});
|
||||
}
|
||||
|
||||
// DIMTFILL: 0=none, 1=drawing background (mask), 2=DIMTFILLCLR.
|
||||
if let Some(s) = style {
|
||||
if s.dimtfill == 1 || s.dimtfill == 2 {
|
||||
@@ -3975,6 +4098,57 @@ fn text_fill_rect(
|
||||
let p4 = corner(-hx, hy);
|
||||
Some(vec![p1, p2, p3, p1, p3, p4])
|
||||
}
|
||||
|
||||
fn arc_length_symbol_points(
|
||||
dim: &Dimension,
|
||||
style: Option<&DimStyle>,
|
||||
text_height: f64,
|
||||
dim_scale: f64,
|
||||
symbol_position: i16,
|
||||
) -> Option<Vec<Vec3>> {
|
||||
if !matches!(dim, Dimension::Arc(_)) || symbol_position == 2 {
|
||||
return None;
|
||||
}
|
||||
let value = dimension_text_value(dim, style)?;
|
||||
if value.is_empty() || text_height <= 1.0e-12 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let position = dimension_text_pos_f64(dim, style, text_height, dim_scale);
|
||||
let rotation = dimension_text_rotation(dim, style);
|
||||
let (sin_rotation, cos_rotation) = rotation.sin_cos();
|
||||
let text_width = value.chars().count() as f64 * text_height * 0.6;
|
||||
let symbol_width = text_height * 0.62;
|
||||
let symbol_height = text_height * 0.20;
|
||||
let (center_x, center_y) = if symbol_position == 1 {
|
||||
(0.0, text_height * 0.72)
|
||||
} else {
|
||||
(-(text_width * 0.5 + symbol_width * 0.70), text_height * 0.02)
|
||||
};
|
||||
let transform = |x: f64, y: f64| {
|
||||
let local_x = center_x + x;
|
||||
let local_y = center_y + y;
|
||||
Vec3::new(
|
||||
(position.x + local_x * cos_rotation - local_y * sin_rotation) as f32,
|
||||
(position.y + local_x * sin_rotation + local_y * cos_rotation) as f32,
|
||||
position.z as f32,
|
||||
)
|
||||
};
|
||||
|
||||
let steps = 8usize;
|
||||
Some(
|
||||
(0..=steps)
|
||||
.map(|index| {
|
||||
let t = index as f64 / steps as f64;
|
||||
let x = (t - 0.5) * symbol_width;
|
||||
let normalized = x / (symbol_width * 0.5);
|
||||
let y = symbol_height * (1.0 - normalized * normalized);
|
||||
transform(x, y)
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
struct SuppressFlags {
|
||||
ext1: bool,
|
||||
ext2: bool,
|
||||
@@ -4210,6 +4384,7 @@ fn dimension_geometry(
|
||||
}
|
||||
}
|
||||
Dimension::Arc(d) => {
|
||||
let explicit_sweep = arc_dimension_angles(d);
|
||||
append_angular_dimension(
|
||||
&mut g,
|
||||
lv(d.center_point),
|
||||
@@ -4218,10 +4393,7 @@ fn dimension_geometry(
|
||||
lv(d.definition_point),
|
||||
arrow1,
|
||||
arrow2,
|
||||
d.is_partial.then_some((
|
||||
d.arc_start_parameter as f32,
|
||||
d.arc_end_parameter as f32,
|
||||
)),
|
||||
explicit_sweep,
|
||||
params,
|
||||
suppress,
|
||||
);
|
||||
@@ -4633,6 +4805,195 @@ fn two_line_angle_frame(
|
||||
Some((vertex, start, end))
|
||||
}
|
||||
|
||||
pub(crate) fn arc_dimension_angles(dimension: &DimensionArc) -> Option<(f32, f32)> {
|
||||
let raw = dimension.arc_end_parameter - dimension.arc_start_parameter;
|
||||
let mut sweep = raw.rem_euclid(std::f64::consts::TAU);
|
||||
if sweep <= 1.0e-12 && raw.abs() > 1.0e-12 {
|
||||
sweep = std::f64::consts::TAU;
|
||||
}
|
||||
if sweep > 1.0e-12 {
|
||||
let start = dimension.arc_start_parameter as f32;
|
||||
return Some((start, start + sweep as f32));
|
||||
}
|
||||
|
||||
let start = (dimension.first_extension_point.y - dimension.center_point.y)
|
||||
.atan2(dimension.first_extension_point.x - dimension.center_point.x);
|
||||
let end = (dimension.second_extension_point.y - dimension.center_point.y)
|
||||
.atan2(dimension.second_extension_point.x - dimension.center_point.x);
|
||||
let sweep = (end - start).rem_euclid(std::f64::consts::TAU);
|
||||
(sweep > 1.0e-12).then_some((start as f32, (start + sweep) as f32))
|
||||
}
|
||||
|
||||
pub(crate) fn arc_dimension_in_ocs(
|
||||
dimension: &DimensionArc,
|
||||
) -> Option<(DimensionArc, Vector3)> {
|
||||
let length = dimension.base.normal.length();
|
||||
if !length.is_finite() || length <= 1.0e-12 {
|
||||
return None;
|
||||
}
|
||||
let normal = dimension.base.normal / length;
|
||||
if (normal - Vector3::UNIT_Z).length() <= 1.0e-12 {
|
||||
return None;
|
||||
}
|
||||
let normal_tuple = (normal.x, normal.y, normal.z);
|
||||
let to_ocs = |point: Vector3| {
|
||||
let point = crate::scene::view::transform::wcs_point_to_ocs(
|
||||
(point.x, point.y, point.z),
|
||||
normal_tuple,
|
||||
);
|
||||
Vector3::new(point.0, point.1, point.2)
|
||||
};
|
||||
let mut local = dimension.clone();
|
||||
local.center_point = to_ocs(dimension.center_point);
|
||||
let elevation = local.center_point.z;
|
||||
let on_plane = |point: Vector3| {
|
||||
let mut point = to_ocs(point);
|
||||
point.z = elevation;
|
||||
point
|
||||
};
|
||||
local.definition_point = on_plane(dimension.definition_point);
|
||||
local.first_extension_point = on_plane(dimension.first_extension_point);
|
||||
local.second_extension_point = on_plane(dimension.second_extension_point);
|
||||
local.first_leader_point = on_plane(dimension.first_leader_point);
|
||||
local.second_leader_point = on_plane(dimension.second_leader_point);
|
||||
local.base.definition_point = local.definition_point;
|
||||
local.base.text_middle_point = on_plane(dimension.base.text_middle_point);
|
||||
local.base.insertion_point = on_plane(dimension.base.insertion_point);
|
||||
local.base.normal = Vector3::UNIT_Z;
|
||||
Some((local, normal))
|
||||
}
|
||||
|
||||
pub(crate) fn large_radial_dimension_in_ocs(
|
||||
dimension: &DimensionLargeRadial,
|
||||
) -> Option<(DimensionLargeRadial, Vector3)> {
|
||||
let length = dimension.base.normal.length();
|
||||
if !length.is_finite() || length <= 1.0e-12 {
|
||||
return None;
|
||||
}
|
||||
let normal = dimension.base.normal / length;
|
||||
if (normal - Vector3::UNIT_Z).length() <= 1.0e-12 {
|
||||
return None;
|
||||
}
|
||||
let normal_tuple = (normal.x, normal.y, normal.z);
|
||||
let to_ocs = |point: Vector3| {
|
||||
let point = crate::scene::view::transform::wcs_point_to_ocs(
|
||||
(point.x, point.y, point.z),
|
||||
normal_tuple,
|
||||
);
|
||||
Vector3::new(point.0, point.1, point.2)
|
||||
};
|
||||
let mut local = dimension.clone();
|
||||
local.definition_point = to_ocs(dimension.definition_point);
|
||||
let elevation = local.definition_point.z;
|
||||
let on_plane = |point: Vector3| {
|
||||
let mut point = to_ocs(point);
|
||||
point.z = elevation;
|
||||
point
|
||||
};
|
||||
local.chord_point = on_plane(dimension.chord_point);
|
||||
local.override_center = on_plane(dimension.override_center);
|
||||
local.jog_point = on_plane(dimension.jog_point);
|
||||
local.base.definition_point = local.definition_point;
|
||||
local.base.text_middle_point = on_plane(dimension.base.text_middle_point);
|
||||
local.base.insertion_point = on_plane(dimension.base.insertion_point);
|
||||
local.base.normal = Vector3::UNIT_Z;
|
||||
Some((local, normal))
|
||||
}
|
||||
|
||||
fn map_wire_ocs_to_wcs(wire: &mut WireModel, normal: Vector3) {
|
||||
let normal_tuple = (normal.x, normal.y, normal.z);
|
||||
let map = |x: f64, y: f64, z: f64| {
|
||||
crate::scene::view::transform::ocs_point_to_wcs((x, y, z), normal_tuple)
|
||||
};
|
||||
let map_split = |high: &mut Vec<[f32; 3]>, low: &mut Vec<[f32; 3]>| {
|
||||
let mut mapped_high = Vec::with_capacity(high.len());
|
||||
let mut mapped_low = Vec::with_capacity(high.len());
|
||||
for (index, point) in high.iter().enumerate() {
|
||||
if point[0].is_nan() {
|
||||
mapped_high.push(*point);
|
||||
mapped_low.push([0.0; 3]);
|
||||
continue;
|
||||
}
|
||||
let residual = low.get(index).copied().unwrap_or([0.0; 3]);
|
||||
let point = map(
|
||||
point[0] as f64 + residual[0] as f64,
|
||||
point[1] as f64 + residual[1] as f64,
|
||||
point[2] as f64 + residual[2] as f64,
|
||||
);
|
||||
let (xh, xl) = WireModel::split_ds(point.0);
|
||||
let (yh, yl) = WireModel::split_ds(point.1);
|
||||
let (zh, zl) = WireModel::split_ds(point.2);
|
||||
mapped_high.push([xh, yh, zh]);
|
||||
mapped_low.push([xl, yl, zl]);
|
||||
}
|
||||
*high = mapped_high;
|
||||
*low = mapped_low;
|
||||
};
|
||||
|
||||
map_split(&mut wire.points, &mut wire.points_low);
|
||||
map_split(&mut wire.fill_tris, &mut wire.fill_tris_low);
|
||||
map_split(&mut wire.pick_tris, &mut wire.pick_tris_low);
|
||||
wire.text_verts = crate::scene::model::wire_model::map_text_verts(
|
||||
&wire.text_verts,
|
||||
map,
|
||||
);
|
||||
for (point, _) in &mut wire.snap_pts {
|
||||
let mapped = map(point.x, point.y, point.z);
|
||||
*point = DVec3::new(mapped.0, mapped.1, mapped.2);
|
||||
}
|
||||
for point in &mut wire.key_vertices {
|
||||
let mapped = map(point[0], point[1], point[2]);
|
||||
*point = [mapped.0, mapped.1, mapped.2];
|
||||
}
|
||||
if let Some(marker) = &mut wire.point_marker {
|
||||
let origin = map(marker.origin.x, marker.origin.y, marker.origin.z);
|
||||
let vector = |value: DVec3| {
|
||||
let mapped = map(value.x, value.y, value.z);
|
||||
DVec3::new(mapped.0, mapped.1, mapped.2)
|
||||
};
|
||||
marker.origin = DVec3::new(origin.0, origin.1, origin.2);
|
||||
marker.normal = vector(marker.normal).normalize_or(DVec3::Z);
|
||||
marker.axis_x = vector(marker.axis_x).normalize_or(DVec3::X);
|
||||
marker.axis_y = vector(marker.axis_y).normalize_or(DVec3::Y);
|
||||
}
|
||||
for tangent in &mut wire.tangent_geoms {
|
||||
match tangent {
|
||||
TangentGeom::Line { p1, p2 } => {
|
||||
let first = map(p1[0] as f64, p1[1] as f64, p1[2] as f64);
|
||||
let second = map(p2[0] as f64, p2[1] as f64, p2[2] as f64);
|
||||
*p1 = [first.0 as f32, first.1 as f32, first.2 as f32];
|
||||
*p2 = [second.0 as f32, second.1 as f32, second.2 as f32];
|
||||
}
|
||||
TangentGeom::Circle { center, radius } => {
|
||||
let center = map(
|
||||
center[0] as f64,
|
||||
center[1] as f64,
|
||||
center[2] as f64,
|
||||
);
|
||||
let (x_axis, y_axis) = crate::scene::view::transform::ocs_axes(normal_tuple);
|
||||
*tangent = TangentGeom::PlanarCircle {
|
||||
center: [center.0, center.1, center.2],
|
||||
axis_x: [x_axis.0, x_axis.1, x_axis.2],
|
||||
axis_y: [y_axis.0, y_axis.1, y_axis.2],
|
||||
radius: *radius as f64,
|
||||
};
|
||||
}
|
||||
TangentGeom::PlanarCircle { center, axis_x, axis_y, .. }
|
||||
| TangentGeom::Arc { center, axis_x, axis_y, .. } => {
|
||||
let mapped = map(center[0], center[1], center[2]);
|
||||
let map_vector = |axis: [f64; 3]| {
|
||||
let mapped = map(axis[0], axis[1], axis[2]);
|
||||
[mapped.0, mapped.1, mapped.2]
|
||||
};
|
||||
*center = [mapped.0, mapped.1, mapped.2];
|
||||
*axis_x = map_vector(*axis_x);
|
||||
*axis_y = map_vector(*axis_y);
|
||||
}
|
||||
}
|
||||
}
|
||||
wire.aabb = WireModel::UNBOUNDED_AABB;
|
||||
}
|
||||
|
||||
fn angular_dimension_frame(dim: &Dimension) -> Option<(Vec3, f32, f32, f32)> {
|
||||
let (vertex, start, end, arc_point) = match dim {
|
||||
Dimension::Angular2Ln(value) => {
|
||||
@@ -4659,6 +5020,12 @@ fn angular_dimension_frame(dim: &Dimension) -> Option<(Vec3, f32, f32, f32)> {
|
||||
two_line_angle_frame(vertex, first, vertex, second, arc_point)?;
|
||||
(vertex, start, end, arc_point)
|
||||
}
|
||||
Dimension::Arc(value) => {
|
||||
let vertex = vec3_local(value.center_point);
|
||||
let arc_point = vec3_local(value.definition_point);
|
||||
let (start, end) = arc_dimension_angles(value)?;
|
||||
(vertex, start, end, arc_point)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
let radius = vertex.distance(arc_point);
|
||||
@@ -5249,7 +5616,7 @@ fn dimension_text_natural_rotation(dim: &Dimension) -> f64 {
|
||||
let dy = d.second_point.y - d.first_point.y;
|
||||
dy.atan2(dx)
|
||||
}
|
||||
Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_) => angular_dimension_frame(dim)
|
||||
Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_) | Dimension::Arc(_) => angular_dimension_frame(dim)
|
||||
.map(|(_, start, end, _)| {
|
||||
((start + end) * 0.5 + std::f32::consts::FRAC_PI_2) as f64
|
||||
})
|
||||
@@ -6183,6 +6550,45 @@ pub(crate) fn baked_dimension_text_entity(
|
||||
Some(ent)
|
||||
}
|
||||
|
||||
pub(crate) fn baked_arc_length_symbol_points(
|
||||
dim: &Dimension,
|
||||
document: &CadDocument,
|
||||
anno_scale: f64,
|
||||
) -> Vec<Vector3> {
|
||||
if !matches!(dim, Dimension::Arc(_)) {
|
||||
return Vec::new();
|
||||
}
|
||||
let style_name = dim.base().style_name.as_str();
|
||||
let style = document.dim_styles.iter().find(|style| {
|
||||
style.name.eq_ignore_ascii_case(style_name)
|
||||
|| (style_name.trim().is_empty() && style.name.eq_ignore_ascii_case("Standard"))
|
||||
});
|
||||
let dim_scale = style
|
||||
.map(|style| {
|
||||
if style.dimscale > 1.0e-6 {
|
||||
style.dimscale
|
||||
} else {
|
||||
anno_scale
|
||||
}
|
||||
})
|
||||
.unwrap_or(1.0);
|
||||
let text_height = style
|
||||
.map(|style| style.dimtxt * dim_scale)
|
||||
.unwrap_or(2.5 * dim_scale);
|
||||
let symbol_position = crate::entities::dim_override::int(
|
||||
&dim.base().common.extended_data,
|
||||
crate::entities::dim_override::DIMARCSYM,
|
||||
)
|
||||
.or_else(|| style.map(|style| style.dimarcsym))
|
||||
.unwrap_or(0);
|
||||
|
||||
arc_length_symbol_points(dim, style, text_height, dim_scale, symbol_position)
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|point| Vector3::new(point.x as f64, point.y as f64, point.z as f64))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn dimension_text_grip_position(
|
||||
dim: &Dimension,
|
||||
document: &CadDocument,
|
||||
|
||||
Reference in new issue
Block a user