forked from 1b-agent/opencadstudio-upstream
Merge branch 'main' into feature/parametric-constraint-system
# Conflicts: # src/app/automation.rs # src/app/mod.rs # src/ui/ribbon/widgets.rs
This commit is contained in:
commit
e2d39b049d
114 files changed
+13641
-1685
No files matched your search
+145
-3
@@ -10,7 +10,39 @@ use crate::scene::convert::acad_to_render::{extrusion_wall_tris, RenderEntity, R
|
||||
use crate::scene::model::object::{GripApply, GripDef, PropSection};
|
||||
use crate::scene::model::wire_model::TangentGeom;
|
||||
|
||||
fn to_render(circle: &Circle) -> RenderEntity {
|
||||
/// The number of segments to tessellate a circle into, based on its radius and the
|
||||
/// current viewport's world units per pixel (`wpp`).
|
||||
///
|
||||
/// Targets a maximum chord sagitta of ~0.25 pixels on screen so circles stay visually
|
||||
/// round at any zoom level, while bounded between a minimum of 48 (preventing
|
||||
/// coarse polygons when small or far away) and a maximum of 4096 (capping buffer size
|
||||
/// on extreme zoom).
|
||||
pub fn circle_segments(radius: f64, wpp: Option<f32>) -> usize {
|
||||
const MIN_SEGMENTS: usize = 48;
|
||||
const MAX_SEGMENTS: usize = 4096;
|
||||
let Some(w) = wpp.filter(|&w| w.is_finite() && w > 0.0) else {
|
||||
return MIN_SEGMENTS;
|
||||
};
|
||||
let r = radius.abs();
|
||||
if !r.is_finite() || r <= 0.0 {
|
||||
return MIN_SEGMENTS;
|
||||
}
|
||||
// Target chord sagitta of ~0.25 px on screen:
|
||||
// sag = r * (1 - cos(theta / 2)) <= tolerance
|
||||
let tolerance = (w as f64) * 0.25;
|
||||
if r <= tolerance {
|
||||
return MIN_SEGMENTS;
|
||||
}
|
||||
let cos_half = (1.0 - tolerance / r).clamp(-1.0, 1.0);
|
||||
let step = 2.0 * cos_half.acos();
|
||||
if step <= 0.0 {
|
||||
return MAX_SEGMENTS;
|
||||
}
|
||||
let n = (std::f64::consts::TAU / step).ceil() as usize;
|
||||
n.clamp(MIN_SEGMENTS, MAX_SEGMENTS)
|
||||
}
|
||||
|
||||
pub fn to_render_with_wpp(circle: &Circle, wpp: Option<f32>) -> RenderEntity {
|
||||
let cx = circle.center.x;
|
||||
let cy = circle.center.y;
|
||||
let cz = circle.center.z;
|
||||
@@ -31,10 +63,12 @@ fn to_render(circle: &Circle) -> RenderEntity {
|
||||
radius: r,
|
||||
};
|
||||
|
||||
let n = circle_segments(r, wpp);
|
||||
|
||||
if circle.thickness.abs() > 1e-10 {
|
||||
let t = circle.thickness;
|
||||
let (nx, ny, nz) = normal;
|
||||
let n = 64usize;
|
||||
let n = n.max(64);
|
||||
let tau = std::f64::consts::TAU;
|
||||
let circ_pt = |a: f64| -> (f64, f64, f64) {
|
||||
let (c, s) = (a.cos(), a.sin());
|
||||
@@ -76,7 +110,8 @@ fn to_render(circle: &Circle) -> RenderEntity {
|
||||
}
|
||||
|
||||
// Keep tessellation on the entity curve for large-coordinate precision.
|
||||
let pts = crate::entities::curve::curve_points(&curve);
|
||||
let max_angle = std::f64::consts::TAU / (n as f64);
|
||||
let pts = curve.tessellate_angle(max_angle);
|
||||
|
||||
RenderEntity {
|
||||
pick_tris: Vec::new(),
|
||||
@@ -88,6 +123,22 @@ fn to_render(circle: &Circle) -> RenderEntity {
|
||||
}
|
||||
}
|
||||
|
||||
fn to_render(circle: &Circle) -> RenderEntity {
|
||||
to_render_with_wpp(circle, None)
|
||||
}
|
||||
|
||||
/// Zoom-aware render converter for EntityType::Circle.
|
||||
pub fn relative_render(
|
||||
entity: &acadrust::EntityType,
|
||||
_document: &acadrust::CadDocument,
|
||||
wpp: Option<f32>,
|
||||
) -> Option<RenderEntity> {
|
||||
let acadrust::EntityType::Circle(circle) = entity else {
|
||||
return None;
|
||||
};
|
||||
Some(to_render_with_wpp(circle, wpp))
|
||||
}
|
||||
|
||||
fn grips(circle: &Circle) -> Vec<GripDef> {
|
||||
let center = circle.center_wcs();
|
||||
let (axis_x, axis_y) = circle.axes_wcs();
|
||||
@@ -220,3 +271,94 @@ impl crate::entities::traits::MassPropsCalc for Circle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_circle_segments_scales_with_zoom() {
|
||||
let r = 10.0;
|
||||
// Default / invalid cases return baseline floor of 48.
|
||||
assert_eq!(circle_segments(r, None), 48);
|
||||
assert_eq!(circle_segments(r, Some(-1.0)), 48);
|
||||
assert_eq!(circle_segments(r, Some(0.0)), 48);
|
||||
assert_eq!(circle_segments(r, Some(f32::NAN)), 48);
|
||||
assert_eq!(circle_segments(r, Some(f32::INFINITY)), 48);
|
||||
|
||||
// Zoomed far out: screen radius is tiny, capped at floor 48.
|
||||
assert_eq!(circle_segments(r, Some(10.0)), 48);
|
||||
assert_eq!(circle_segments(r, Some(2.0)), 48);
|
||||
|
||||
// Zooming closer increases segments monotonically.
|
||||
let seg_0_1 = circle_segments(r, Some(0.1));
|
||||
let seg_0_01 = circle_segments(r, Some(0.01));
|
||||
let seg_0_001 = circle_segments(r, Some(0.001));
|
||||
let seg_0_0001 = circle_segments(r, Some(0.0001));
|
||||
|
||||
assert!(seg_0_1 >= 48, "seg_0_1 was {seg_0_1}");
|
||||
assert!(seg_0_01 > seg_0_1, "seg_0_01 ({seg_0_01}) <= seg_0_1 ({seg_0_1})");
|
||||
assert!(seg_0_001 > seg_0_01, "seg_0_001 ({seg_0_001}) <= seg_0_01 ({seg_0_01})");
|
||||
assert!(seg_0_0001 > seg_0_001, "seg_0_0001 ({seg_0_0001}) <= seg_0_001 ({seg_0_001})");
|
||||
|
||||
// Extreme zoom in hits maximum cap of 4096.
|
||||
assert_eq!(circle_segments(r, Some(1e-9)), 4096);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_circle_segments_scales_with_radius() {
|
||||
let wpp = Some(0.1);
|
||||
let seg_small = circle_segments(1.0, wpp);
|
||||
let seg_med = circle_segments(20.0, wpp);
|
||||
let seg_large = circle_segments(100.0, wpp);
|
||||
let seg_huge = circle_segments(500.0, wpp);
|
||||
|
||||
assert_eq!(seg_small, 48);
|
||||
assert!(seg_med > seg_small, "{seg_med} <= {seg_small}");
|
||||
assert!(seg_large > seg_med, "{seg_large} <= {seg_med}");
|
||||
assert!(seg_huge > seg_large, "{seg_huge} <= {seg_large}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_circle_to_render_points_increase_with_zoom() {
|
||||
let mut circle = Circle::default();
|
||||
circle.radius = 10.0;
|
||||
|
||||
let far_render = to_render_with_wpp(&circle, Some(1.0));
|
||||
let close_render = to_render_with_wpp(&circle, Some(0.01));
|
||||
|
||||
let far_count = match far_render.object {
|
||||
RenderObject::Lines(pts) => pts.len(),
|
||||
_ => panic!("Expected RenderObject::Lines"),
|
||||
};
|
||||
let close_count = match close_render.object {
|
||||
RenderObject::Lines(pts) => pts.len(),
|
||||
_ => panic!("Expected RenderObject::Lines"),
|
||||
};
|
||||
|
||||
assert_eq!(far_count, 49); // 48 segments + 1 closing vertex
|
||||
assert!(close_count > far_count, "close_count was {close_count} vs far_count {far_count}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_thick_circle_to_render_points_increase_with_zoom() {
|
||||
let mut circle = Circle::default();
|
||||
circle.radius = 10.0;
|
||||
circle.thickness = 2.0;
|
||||
|
||||
let far_render = to_render_with_wpp(&circle, Some(1.0));
|
||||
let close_render = to_render_with_wpp(&circle, Some(0.01));
|
||||
|
||||
let far_count = match far_render.object {
|
||||
RenderObject::Lines(pts) => pts.len(),
|
||||
_ => panic!("Expected RenderObject::Lines"),
|
||||
};
|
||||
let close_count = match close_render.object {
|
||||
RenderObject::Lines(pts) => pts.len(),
|
||||
_ => panic!("Expected RenderObject::Lines"),
|
||||
};
|
||||
|
||||
assert!(close_count > far_count, "{close_count} <= {far_count}");
|
||||
}
|
||||
}
|
||||
|
||||
+38
-3
@@ -114,7 +114,11 @@ pub fn format_length(value: f64) -> String {
|
||||
let per_foot = 12.0 * scale;
|
||||
let feet = (total / per_foot).trunc();
|
||||
let rem = (total - feet * per_foot) / scale;
|
||||
format!("{}{:.0}'-{:.*}\"", sign, feet, prec, rem)
|
||||
if feet == 0.0 {
|
||||
format!("{}{:.*}\"", sign, prec, rem)
|
||||
} else {
|
||||
format!("{}{:.0}'-{:.*}\"", sign, feet, prec, rem)
|
||||
}
|
||||
}
|
||||
4 | 5 => {
|
||||
// Architectural and fractional formats use 1/64-inch resolution.
|
||||
@@ -142,6 +146,9 @@ pub fn format_length(value: f64) -> String {
|
||||
};
|
||||
let unit_suffix = if ctx.lunits == 4 { "\"" } else { "" };
|
||||
match feet {
|
||||
Some(f) if f == 0.0 => {
|
||||
format!("{}{:.0}{}{}", sign, whole, frac_str, unit_suffix)
|
||||
}
|
||||
Some(f) => format!("{}{:.0}'-{:.0}{}{}", sign, f, whole, frac_str, unit_suffix),
|
||||
None => format!("{}{:.0}{}", sign, whole, frac_str),
|
||||
}
|
||||
@@ -702,6 +709,30 @@ pub fn parse_angle_deg(value: &str) -> Option<f64> {
|
||||
/// there is one obvious place to see that the maths moved out.
|
||||
pub use cadkernel::geom2d::BulgeArc;
|
||||
|
||||
/// Convert a 2D BulgeArc into a 3D TangentGeom::Arc with its world center,
|
||||
/// plane axes, radius, and counter-clockwise start/end sweep angles.
|
||||
pub fn bulge_arc_to_tangent(
|
||||
arc: &BulgeArc,
|
||||
to_wcs: &dyn Fn(f64, f64) -> (f64, f64, f64),
|
||||
normal: (f64, f64, f64),
|
||||
) -> crate::scene::model::wire_model::TangentGeom {
|
||||
let (cwx, cwy, cwz) = to_wcs(arc.center[0], arc.center[1]);
|
||||
let (ax, ay) = crate::scene::view::transform::ocs_axes(normal);
|
||||
let (sa, ea) = if arc.sweep >= 0.0 {
|
||||
(arc.start_angle, arc.end_angle)
|
||||
} else {
|
||||
(arc.end_angle, arc.start_angle)
|
||||
};
|
||||
crate::scene::model::wire_model::TangentGeom::Arc {
|
||||
center: [cwx, cwy, cwz],
|
||||
axis_x: [ax.0, ax.1, ax.2],
|
||||
axis_y: [ay.0, ay.1, ay.2],
|
||||
radius: arc.radius,
|
||||
start_angle: sa,
|
||||
end_angle: ea,
|
||||
}
|
||||
}
|
||||
|
||||
/// Triangulate the solid bands a `wide_fills` returns into the flat WCS f64
|
||||
/// triangle list `RenderEntity::pick_tris` carries, so a wide polyline is
|
||||
/// selectable across the band it draws and not just along its centreline.
|
||||
@@ -1078,7 +1109,9 @@ mod length_format_tests {
|
||||
|
||||
#[test]
|
||||
fn architectural_carries_into_feet() {
|
||||
assert_eq!(with_units(4, 4, 11.99), "0'-11 63/64\"");
|
||||
assert_eq!(with_units(4, 4, 11.99), "11 63/64\"");
|
||||
assert_eq!(with_units(4, 4, 0.5), "0 1/2\"");
|
||||
assert_eq!(with_units(4, 4, -9.25), "-9 1/4\"");
|
||||
assert_eq!(with_units(4, 4, 11.999), "1'-0\"");
|
||||
assert_eq!(with_units(4, 4, 23.999), "2'-0\"");
|
||||
assert_eq!(with_units(4, 4, 66.5), "5'-6 1/2\"");
|
||||
@@ -1087,7 +1120,9 @@ mod length_format_tests {
|
||||
|
||||
#[test]
|
||||
fn engineering_carries_into_feet() {
|
||||
assert_eq!(with_units(3, 1, 11.94), "0'-11.9\"");
|
||||
assert_eq!(with_units(3, 1, 11.94), "11.9\"");
|
||||
assert_eq!(with_units(3, 1, 0.5), "0.5\"");
|
||||
assert_eq!(with_units(3, 1, -9.25), "-9.3\"");
|
||||
assert_eq!(with_units(3, 1, 11.99), "1'-0.0\"");
|
||||
assert_eq!(with_units(3, 1, 23.99), "2'-0.0\"");
|
||||
assert_eq!(with_units(3, 2, 11.999), "1'-0.00\"");
|
||||
|
||||
@@ -294,16 +294,16 @@ fn apply_axis_grip(ell: &mut Ellipse, grip_id: usize, point: glam::DVec3) {
|
||||
fn apply_grip(ell: &mut Ellipse, grip_id: usize, apply: GripApply) {
|
||||
match (grip_id, apply) {
|
||||
(0, GripApply::Translate(d)) => {
|
||||
ell.center.x += d.x as f64;
|
||||
ell.center.y += d.y as f64;
|
||||
ell.center.z += d.z as f64;
|
||||
ell.center.x += d.x;
|
||||
ell.center.y += d.y;
|
||||
ell.center.z += d.z;
|
||||
}
|
||||
(0, GripApply::Absolute(p)) => {
|
||||
ell.center.x = p.x as f64;
|
||||
ell.center.y = p.y as f64;
|
||||
ell.center.z = p.z as f64;
|
||||
ell.center.x = p.x;
|
||||
ell.center.y = p.y;
|
||||
ell.center.z = p.z;
|
||||
}
|
||||
(1 | 2 | 3 | 4, GripApply::Absolute(p)) => apply_axis_grip(ell, grip_id, p),
|
||||
(1..=4, GripApply::Absolute(p)) => apply_axis_grip(ell, grip_id, p),
|
||||
(5 | 6, GripApply::Absolute(p)) => {
|
||||
let Some(parameter) = parameter_at_point(ell, p) else {
|
||||
return;
|
||||
|
||||
@@ -351,11 +351,7 @@ fn to_render(pline: &LwPolyline, fill_mode: bool) -> RenderEntity {
|
||||
} else if let Some(arc) =
|
||||
crate::entities::common::BulgeArc::from_bulge([ox0, oy0], [ox1, oy1], bulge)
|
||||
{
|
||||
let (wcx, wcy, wcz) = to_wcs(arc.center[0], arc.center[1]);
|
||||
tgs.push(TangentGeom::Circle {
|
||||
center: [wcx as f32, wcy as f32, wcz as f32],
|
||||
radius: arc.radius as f32,
|
||||
});
|
||||
tgs.push(crate::entities::common::bulge_arc_to_tangent(&arc, &to_wcs, normal));
|
||||
for s in arc
|
||||
.tessellate_angle(cadkernel::tessellation::DEFAULT_ANGLE)
|
||||
.into_iter()
|
||||
@@ -407,11 +403,7 @@ fn to_render(pline: &LwPolyline, fill_mode: bool) -> RenderEntity {
|
||||
[v1.location.x, v1.location.y],
|
||||
v0.bulge,
|
||||
) {
|
||||
let (wcx, wcy, wcz) = to_wcs(arc.center[0], arc.center[1]);
|
||||
tgs.push(TangentGeom::Circle {
|
||||
center: [wcx as f32, wcy as f32, wcz as f32],
|
||||
radius: arc.radius as f32,
|
||||
});
|
||||
tgs.push(crate::entities::common::bulge_arc_to_tangent(&arc, &to_wcs, normal));
|
||||
}
|
||||
}
|
||||
let (pts, widths) = crate::entities::common::tapered_band_points(
|
||||
@@ -470,11 +462,7 @@ fn to_render(pline: &LwPolyline, fill_mode: bool) -> RenderEntity {
|
||||
}
|
||||
let (wx1, wy1, wz1) = to_wcs(ox1, oy1);
|
||||
kv.push([wx1, wy1, wz1]);
|
||||
let (wcx, wcy, wcz) = to_wcs(arc.center[0], arc.center[1]);
|
||||
tgs.push(TangentGeom::Circle {
|
||||
center: [wcx as f32, wcy as f32, wcz as f32],
|
||||
radius: arc.radius as f32,
|
||||
});
|
||||
tgs.push(crate::entities::common::bulge_arc_to_tangent(&arc, &to_wcs, normal));
|
||||
}
|
||||
if i + 1 < seg_count {
|
||||
pts.push([f64::NAN; 3]);
|
||||
@@ -565,11 +553,8 @@ fn centerline_metadata(
|
||||
[end.location.x, end.location.y],
|
||||
start.bulge,
|
||||
) {
|
||||
let center = to_wcs(arc.center[0], arc.center[1]);
|
||||
tangents.push(TangentGeom::Circle {
|
||||
center: [center.0 as f32, center.1 as f32, center.2 as f32],
|
||||
radius: arc.radius as f32,
|
||||
});
|
||||
let normal = (pline.normal.x, pline.normal.y, pline.normal.z);
|
||||
tangents.push(crate::entities::common::bulge_arc_to_tangent(&arc, to_wcs, normal));
|
||||
}
|
||||
if index == 0 {
|
||||
key_vertices.push([p0.0, p0.1, p0.2]);
|
||||
|
||||
@@ -991,6 +991,67 @@ fn display_mesh(mesh: &Mesh) -> RefinedMesh {
|
||||
refined
|
||||
}
|
||||
|
||||
/// Convert a closed indexed mesh entity to an exact planar-faced B-rep.
|
||||
/// Open, non-manifold, non-planar, and degenerate meshes return `None` so a
|
||||
/// modelling command can leave the source untouched.
|
||||
pub(crate) fn closed_mesh_body(entity: &acadrust::EntityType) -> Option<cadkernel::brep::Body> {
|
||||
match entity {
|
||||
acadrust::EntityType::Mesh(mesh) => {
|
||||
let base = base_refined_mesh(mesh);
|
||||
cadkernel::brep::make::faceted_solid(&base.vertices, &base.faces)
|
||||
}
|
||||
acadrust::EntityType::PolygonMesh(mesh) => {
|
||||
if !mesh.is_closed_m() || !mesh.is_closed_n() {
|
||||
return None;
|
||||
}
|
||||
let m = mesh.m_vertex_count.max(0) as usize;
|
||||
let n = mesh.n_vertex_count.max(0) as usize;
|
||||
let vertices = mesh
|
||||
.vertices
|
||||
.iter()
|
||||
.take(m.saturating_mul(n))
|
||||
.map(|vertex| v3(&vertex.location))
|
||||
.collect::<Vec<_>>();
|
||||
if m < 2 || n < 2 || vertices.len() != m.saturating_mul(n) {
|
||||
return None;
|
||||
}
|
||||
let mut faces = Vec::with_capacity(m.saturating_mul(n));
|
||||
for row in 0..m {
|
||||
for column in 0..n {
|
||||
faces.push(vec![
|
||||
row * n + column,
|
||||
((row + 1) % m) * n + column,
|
||||
((row + 1) % m) * n + (column + 1) % n,
|
||||
row * n + (column + 1) % n,
|
||||
]);
|
||||
}
|
||||
}
|
||||
cadkernel::brep::make::faceted_solid(&vertices, &faces)
|
||||
}
|
||||
acadrust::EntityType::PolyfaceMesh(mesh) => {
|
||||
let vertices = mesh
|
||||
.vertices
|
||||
.iter()
|
||||
.map(|vertex| v3(&vertex.location))
|
||||
.collect::<Vec<_>>();
|
||||
let faces = mesh
|
||||
.faces
|
||||
.iter()
|
||||
.filter_map(|face| {
|
||||
let indices = [face.index1, face.index2, face.index3, face.index4]
|
||||
.into_iter()
|
||||
.filter(|index| *index != 0)
|
||||
.map(|index| (index.unsigned_abs() as usize).checked_sub(1))
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
(indices.len() >= 3).then_some(indices)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
cadkernel::brep::make::faceted_solid(&vertices, &faces)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn face_triangle_indices(
|
||||
vertices: &[[f64; 3]],
|
||||
faces: &[Vec<usize>],
|
||||
|
||||
@@ -248,7 +248,7 @@ fn tessellate_polyline2d(pl: &Polyline2D, fill_mode: bool) -> RenderEntity {
|
||||
);
|
||||
}
|
||||
let (tangent_geoms, key_vertices) =
|
||||
centerline_metadata_2d(verts, pl.is_closed(), &to_wcs);
|
||||
centerline_metadata_2d(verts, pl.is_closed(), &to_wcs, normal);
|
||||
return RenderEntity {
|
||||
pick_tris: Vec::new(),
|
||||
object: RenderObject::BoundaryLines {
|
||||
@@ -296,11 +296,7 @@ fn tessellate_polyline2d(pl: &Polyline2D, fill_mode: bool) -> RenderEntity {
|
||||
} else if let Some(arc) =
|
||||
crate::entities::common::BulgeArc::from_bulge([ox0, oy0], [ox1, oy1], bulge)
|
||||
{
|
||||
let (wcx, wcy, wcz) = to_wcs(arc.center[0], arc.center[1]);
|
||||
tgs.push(TangentGeom::Circle {
|
||||
center: [wcx as f32, wcy as f32, wcz as f32],
|
||||
radius: arc.radius as f32,
|
||||
});
|
||||
tgs.push(crate::entities::common::bulge_arc_to_tangent(&arc, &to_wcs, normal));
|
||||
for s in arc
|
||||
.tessellate_angle(cadkernel::tessellation::DEFAULT_ANGLE)
|
||||
.into_iter()
|
||||
@@ -378,11 +374,7 @@ fn tessellate_polyline2d(pl: &Polyline2D, fill_mode: bool) -> RenderEntity {
|
||||
[v1.location.x, v1.location.y],
|
||||
bulge,
|
||||
) {
|
||||
let (wcx, wcy, wcz) = to_wcs(arc.center[0], arc.center[1]);
|
||||
tangents.push(TangentGeom::Circle {
|
||||
center: [wcx as f32, wcy as f32, wcz as f32],
|
||||
radius: arc.radius as f32,
|
||||
});
|
||||
tangents.push(crate::entities::common::bulge_arc_to_tangent(&arc, &to_wcs, normal));
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
@@ -464,6 +456,7 @@ fn centerline_metadata_2d(
|
||||
verts: &[acadrust::entities::Vertex2D],
|
||||
closed: bool,
|
||||
to_wcs: &dyn Fn(f64, f64) -> (f64, f64, f64),
|
||||
normal: (f64, f64, f64),
|
||||
) -> (Vec<TangentGeom>, Vec<[f64; 3]>) {
|
||||
let count = verts.len();
|
||||
let segment_count = if closed {
|
||||
@@ -488,11 +481,7 @@ fn centerline_metadata_2d(
|
||||
[end.location.x, end.location.y],
|
||||
start.bulge,
|
||||
) {
|
||||
let center = to_wcs(arc.center[0], arc.center[1]);
|
||||
tangents.push(TangentGeom::Circle {
|
||||
center: [center.0 as f32, center.1 as f32, center.2 as f32],
|
||||
radius: arc.radius as f32,
|
||||
});
|
||||
tangents.push(crate::entities::common::bulge_arc_to_tangent(&arc, to_wcs, normal));
|
||||
}
|
||||
if index == 0 {
|
||||
key_vertices.push([p0.0, p0.1, p0.2]);
|
||||
|
||||
Reference in new issue
Block a user