///|
/// ExtrudedArcProfile::new builds an extruded closed line-plus-arc profile.
pub fn ExtrudedArcProfile::new(
segments : Array[ProfileSegment],
height_mm : Double,
round_bore? : (Vec2, Double),
holes? : Array[Array[ProfileSegment]],
) -> ExtrudedArcProfile {
let hole_loops = match holes {
Some(hs) => hs
None => []
}
{ segments, height_mm, round_bore, holes: hole_loops, }
}
///|
/// ExtrudedArcProfile::as_shape wraps the profile as a `Shape`.
pub fn ExtrudedArcProfile::as_shape(self : ExtrudedArcProfile) -> Shape {
Shape::ExtrudedArcProfile(self)
}
///|
fn arc_dist2(a : Vec2, b : Vec2) -> Double {
let dx = a.0 - b.0
let dy = a.1 - b.1
Double::sqrt(dx * dx + dy * dy)
}
///|
/// arc_norm_sweep normalizes an angular difference to `[0, 2*pi)`.
fn arc_norm_sweep(a : Double) -> Double {
let two_pi = 2.0 * @math.PI
let mut x = a
while x < 0.0 {
x = x + two_pi
}
while x >= two_pi {
x = x - two_pi
}
x
}
///|
fn arc_seg_start(seg : ProfileSegment) -> Vec2 {
match seg {
SegmentLine(a, _) => a
SegmentArc(a, _, _) => a
}
}
///|
fn arc_seg_end(seg : ProfileSegment) -> Vec2 {
match seg {
SegmentLine(_, b) => b
SegmentArc(_, b, _) => b
}
}
///|
fn arc_close2(a : Vec2, b : Vec2) -> Bool {
(a.0 - b.0).abs() < CAD_LENGTH_EPS && (a.1 - b.1).abs() < CAD_LENGTH_EPS
}
///|
/// arc_loop_valid checks that `segs` is one closed counter-clockwise loop of
/// lines and counter-clockwise minor arcs with a positive enclosed area.
fn arc_loop_valid(segs : Array[ProfileSegment]) -> Bool {
let n = segs.length()
if n < 3 {
return false
}
for i in 0.. ()
SegmentArc(_, _, c) => {
let r1 = arc_dist2(a, c)
let r2 = arc_dist2(b, c)
if r1 < CAD_DEGENERATE_EPS || (r1 - r2).abs() > CAD_ANGLE_EPS {
return false
}
let a0 = @math.atan2(a.1 - c.1, a.0 - c.0)
let a1 = @math.atan2(b.1 - c.1, b.0 - c.0)
let delta = arc_norm_sweep(a1 - a0)
if delta <= CAD_DEGENERATE_EPS || delta > @math.PI + CAD_ANGLE_EPS {
return false
}
}
}
if i > 0 && !arc_close2(arc_seg_end(segs[i - 1]), a) {
return false
}
}
if !arc_close2(arc_seg_end(segs[n - 1]), arc_seg_start(segs[0])) {
return false
}
arc_profile_signed_area(segs) > CAD_DEGENERATE_EPS
}
///|
/// ExtrudedArcProfile::validate checks that the outer loop and every hole loop
/// are closed counter-clockwise loops of lines and counter-clockwise minor
/// arcs.
pub fn ExtrudedArcProfile::validate(self : ExtrudedArcProfile) -> Bool {
if self.height_mm <= 0.0 || !arc_loop_valid(self.segments) {
return false
}
for hole in self.holes {
if !arc_loop_valid(hole) {
return false
}
}
true
}
///|
/// arc_profile_signed_area returns the signed area of a line-plus-arc loop.
/// Positive means counter-clockwise.
fn arc_profile_signed_area(segs : Array[ProfileSegment]) -> Double {
let mut sum = 0.0
for seg in segs {
let a = arc_seg_start(seg)
let b = arc_seg_end(seg)
sum = sum + a.0 * b.1 - b.0 * a.1
match seg {
SegmentLine(_, _) => ()
SegmentArc(_, _, c) => {
let rho = arc_dist2(a, c)
let a0 = @math.atan2(a.1 - c.1, a.0 - c.0)
let a1 = @math.atan2(b.1 - c.1, b.0 - c.0)
let delta = arc_norm_sweep(a1 - a0)
sum = sum + rho * rho * (delta - @math.sin(delta))
}
}
}
sum / 2.0
}
///|
/// arc_profile_area_moments returns the exact area and first moments of a
/// line-plus-arc loop by Green's theorem.
fn arc_profile_area_moments(
segs : Array[ProfileSegment],
) -> (Double, Double, Double) {
let mut area2 = 0.0
let mut mx2 = 0.0
let mut my2 = 0.0
for seg in segs {
let a = arc_seg_start(seg)
let b = arc_seg_end(seg)
area2 = area2 + a.0 * b.1 - b.0 * a.1
match seg {
SegmentLine(_, _) => {
// Boundary integrals along the straight chord.
mx2 = mx2 + (b.1 - a.1) * (a.0 * a.0 + a.0 * b.0 + b.0 * b.0) / 3.0
my2 = my2 - (b.0 - a.0) * (a.1 * a.1 + a.1 * b.1 + b.1 * b.1) / 3.0
}
SegmentArc(_, _, c) => {
// Replace the chord integrals with the exact circular-arc integrals.
let rho = arc_dist2(a, c)
let a0 = @math.atan2(a.1 - c.1, a.0 - c.0)
let a1 = @math.atan2(b.1 - c.1, b.0 - c.0)
let delta = arc_norm_sweep(a1 - a0)
let t1 = a1
let t0 = a1 - delta
let dtsin = @math.sin(t1) - @math.sin(t0)
let dtcos = @math.cos(t0) - @math.cos(t1)
let dt2sin = @math.sin(2.0 * t1) - @math.sin(2.0 * t0)
let ic2 = delta / 2.0 + dt2sin / 4.0
let is2 = delta / 2.0 - dt2sin / 4.0
let ic3 = dtsin -
(
@math.sin(t1) * @math.sin(t1) * @math.sin(t1) -
@math.sin(t0) * @math.sin(t0) * @math.sin(t0)
) /
3.0
let is3 = dtcos +
(
@math.cos(t1) * @math.cos(t1) * @math.cos(t1) -
@math.cos(t0) * @math.cos(t0) * @math.cos(t0)
) /
3.0
area2 = area2 + rho * rho * (delta - @math.sin(delta))
mx2 = mx2 +
rho * (c.0 * c.0 * dtsin + 2.0 * c.0 * rho * ic2 + rho * rho * ic3)
my2 = my2 +
rho * (c.1 * c.1 * dtcos + 2.0 * c.1 * rho * is2 + rho * rho * is3)
}
}
}
(area2 / 2.0, mx2 / 2.0, my2 / 2.0)
}
///|
/// arc_profile_perimeter returns the exact boundary length.
fn arc_profile_perimeter(segs : Array[ProfileSegment]) -> Double {
let mut total = 0.0
for seg in segs {
let a = arc_seg_start(seg)
let b = arc_seg_end(seg)
match seg {
SegmentLine(_, _) => total = total + arc_dist2(a, b)
SegmentArc(_, _, c) => {
let rho = arc_dist2(a, c)
let a0 = @math.atan2(a.1 - c.1, a.0 - c.0)
let a1 = @math.atan2(b.1 - c.1, b.0 - c.0)
total = total + rho * arc_norm_sweep(a1 - a0)
}
}
}
total
}
///|
/// arc_profile_max_radius returns the largest distance from the local origin to
/// the boundary (sampling arcs).
fn arc_profile_max_radius(segs : Array[ProfileSegment]) -> Double {
let mut rmax = 0.0
let take = fn(x : Double, y : Double) -> Unit {
let r = Double::sqrt(x * x + y * y)
if r > rmax {
rmax = r
}
}
for seg in segs {
let a = arc_seg_start(seg)
let b = arc_seg_end(seg)
take(a.0, a.1)
take(b.0, b.1)
match seg {
SegmentLine(_, _) => ()
SegmentArc(_, _, c) => {
let rho = arc_dist2(a, c)
let a0 = @math.atan2(a.1 - c.1, a.0 - c.0)
let a1 = @math.atan2(b.1 - c.1, b.0 - c.0)
let delta = arc_norm_sweep(a1 - a0)
for k in 0..<=32 {
let t = Double::from_int(k) / 32.0
let ang = a0 + delta * t
take(c.0 + rho * @math.cos(ang), c.1 + rho * @math.sin(ang))
}
}
}
}
rmax
}
///|
/// arc_profile_min_radius returns the smallest distance from the local origin
/// to the boundary (sampling arcs).
fn arc_profile_min_radius(segs : Array[ProfileSegment]) -> Double {
let mut rmin = @double.max_value
let take = fn(x : Double, y : Double) -> Unit {
let r = Double::sqrt(x * x + y * y)
if r < rmin {
rmin = r
}
}
for seg in segs {
let a = arc_seg_start(seg)
let b = arc_seg_end(seg)
take(a.0, a.1)
take(b.0, b.1)
match seg {
SegmentLine(_, _) => ()
SegmentArc(_, _, c) => {
let rho = arc_dist2(a, c)
let a0 = @math.atan2(a.1 - c.1, a.0 - c.0)
let a1 = @math.atan2(b.1 - c.1, b.0 - c.0)
let delta = arc_norm_sweep(a1 - a0)
for k in 0..<=32 {
let t = Double::from_int(k) / 32.0
let ang = a0 + delta * t
take(c.0 + rho * @math.cos(ang), c.1 + rho * @math.sin(ang))
}
}
}
}
if rmin == @double.max_value {
0.0
} else {
rmin
}
}
///|
/// ExtrudedArcProfile::volume returns the exact volume.
pub fn ExtrudedArcProfile::volume(self : ExtrudedArcProfile) -> Double {
let (area, _mx, _my) = arc_profile_area_moments(self.segments)
let mut a = area
for hole in self.holes {
let (hole_area, _hx, _hy) = arc_profile_area_moments(hole)
a = a - hole_area
}
match self.round_bore {
Some((_c, d)) => {
let rb = d / 2.0
a = a - @math.PI * rb * rb
}
None => ()
}
if a <= 0.0 {
0.0
} else {
a * self.height_mm
}
}
///|
/// ExtrudedArcProfile::centroid returns the exact centroid in the local frame.
pub fn ExtrudedArcProfile::centroid(self : ExtrudedArcProfile) -> Vec3 {
let (area, mx, my) = arc_profile_area_moments(self.segments)
let mut a = area
let mut mx2 = mx
let mut my2 = my
for hole in self.holes {
let (hole_area, hx, hy) = arc_profile_area_moments(hole)
a = a - hole_area
mx2 = mx2 - hx
my2 = my2 - hy
}
match self.round_bore {
Some((c, d)) => {
let rb = d / 2.0
let ba = @math.PI * rb * rb
a = a - ba
mx2 = mx2 - ba * c.0
my2 = my2 - ba * c.1
}
None => ()
}
if a <= CAD_DEGENERATE_EPS {
(0.0, 0.0, 0.0)
} else {
(mx2 / a, my2 / a, self.height_mm / 2.0)
}
}
///|
/// an_extruded_arc_inventory mirrors the compiler's face emission.
fn an_extruded_arc_inventory(e : ExtrudedArcProfile) -> SurfaceInventory {
if !e.validate() {
return an_inventory(0, 0, 0, 0, 0, 0, 0, 0)
}
let mut planes = 2
let mut cylinders = 0
for seg in e.segments {
match seg {
SegmentLine(_, _) => planes = planes + 1
SegmentArc(_, _, _) => cylinders = cylinders + 1
}
}
for hole in e.holes {
for seg in hole {
match seg {
SegmentLine(_, _) => planes = planes + 1
SegmentArc(_, _, _) => cylinders = cylinders + 1
}
}
}
match e.round_bore {
Some((_c, d)) => if d > 0.0 { cylinders = cylinders + 1 }
None => ()
}
an_inventory(planes, cylinders, 0, 0, 0, 0, 0, 0)
}
///|
/// an_extruded_arc_section returns the exact section at local `z`.
fn an_extruded_arc_section(
e : ExtrudedArcProfile,
z : Double,
) -> Result[Section, AnalysisError] {
if !e.validate() {
return Err(DegenerateProfile)
}
if z < -CAD_LENGTH_EPS || z > e.height_mm + CAD_LENGTH_EPS {
return Err(EmptySection)
}
let (area, _mx, _my) = arc_profile_area_moments(e.segments)
let mut a = area
let mut p = arc_profile_perimeter(e.segments)
let outer = arc_profile_max_radius(e.segments)
let mut inner = 0.0
let rings : Array[SectionRing] = []
for hole in e.holes {
let (hole_area, _hx, _hy) = arc_profile_area_moments(hole)
a = a - hole_area
p = p + arc_profile_perimeter(hole)
let hole_outer = arc_profile_max_radius(hole)
rings.push({ inner_radius_mm: 0.0, outer_radius_mm: hole_outer, })
let hole_inner = arc_profile_min_radius(hole)
if inner == 0.0 || hole_inner < inner {
inner = hole_inner
}
}
match e.round_bore {
Some((_c, d)) => {
let rb = d / 2.0
a = a - @math.PI * rb * rb
p = p + 2.0 * @math.PI * rb
rings.push({ inner_radius_mm: 0.0, outer_radius_mm: rb, })
inner = rb
}
None => ()
}
if a <= CAD_DEGENERATE_EPS {
return Err(EmptySection)
}
if rings.is_empty() {
rings.push({ inner_radius_mm: 0.0, outer_radius_mm: outer, })
}
Ok({
z_mm: z,
area_mm2: a,
perimeter_mm: p,
outer_radius_mm: outer,
inner_radius_mm: inner,
rings,
})
}
///|
/// arc_sample_loop samples a line-plus-arc loop into a polygon, emitting the
/// start point of every line and `n` points per arc.
fn arc_sample_loop(segs : Array[ProfileSegment], n : Int32) -> Array[Vec2] {
let out : Array[Vec2] = []
for seg in segs {
match seg {
SegmentLine(a, _) => out.push(a)
SegmentArc(a, b, c) => {
let rho = arc_dist2(a, c)
let a0 = @math.atan2(a.1 - c.1, a.0 - c.0)
let a1 = @math.atan2(b.1 - c.1, b.0 - c.0)
let delta = arc_norm_sweep(a1 - a0)
for k in 0.. (Array[Vec3], Array[Array[Int32]]) {
let n = if arc_segments < 2 { 2 } else { arc_segments }
let outer = arc_sample_loop(e.segments, n)
let holes : Array[Array[Vec2]] = []
for hole in e.holes {
holes.push(arc_sample_loop(hole, n).rev())
}
match e.round_bore {
Some((c, d)) => {
let rb = d / 2.0
let h : Array[Vec2] = []
let count = 48
for k in 0.. ()
}
an_extruded_mesh({
profile: { outer, holes, },
height_mm: e.height_mm,
round_bore: None,
})
}
///|
/// arc_circular_hole recognizes a hole loop made only of arcs that share one
/// centre and radius and sweep a full turn. Such a hole can be emitted as one
/// analytic cylinder instead of one face per arc segment.
fn arc_circular_hole(hole : Array[ProfileSegment]) -> (Vec2, Double)? {
if hole.length() < 2 {
return None
}
let mut center : Vec2 = (0.0, 0.0)
let mut radius = 0.0
let mut total = 0.0
for i, seg in hole {
match seg {
SegmentLine(_, _) => return None
SegmentArc(a, b, c) => {
if i == 0 {
center = c
radius = arc_dist2(a, c)
}
if arc_dist2(c, center) > CAD_ANGLE_EPS ||
(arc_dist2(a, c) - radius).abs() > CAD_ANGLE_EPS {
return None
}
let a0 = @math.atan2(a.1 - c.1, a.0 - c.0)
let a1 = @math.atan2(b.1 - c.1, b.0 - c.0)
total = total + arc_norm_sweep(a1 - a0)
}
}
}
if (total - 2.0 * @math.PI).abs() > CAD_ANGLE_EPS {
return None
}
Some((center, radius))
}
///|
/// compile_extruded_arc_profile emits one closed solid with analytic cylindrical
/// faces for every circular arc and planar faces for every line and cap.
pub fn compile_extruded_arc_profile(
ctx : StepContext,
node : SceneNode,
e : ExtrudedArcProfile,
reverse~ : Bool = false,
parent~ : Transform? = None,
) -> Array[@step.Ref] {
if !e.validate() {
return []
}
let xform = node_transform(node, parent)
let segs = e.segments
let n = segs.length()
let h = e.height_mm
let up = xform.apply_dir((0.0, 0.0, 1.0))
let down = xform.apply_dir((0.0, 0.0, -1.0))
let right = xform.apply_dir((1.0, 0.0, 0.0))
let dir_up = ctx.direction(up.0, up.1, up.2)
let dir_right = ctx.direction(right.0, right.1, right.2)
let pts_raw : Array[Vec3] = []
for seg in segs {
let p = arc_seg_start(seg)
pts_raw.push((p.0, p.1, 0.0))
}
for seg in segs {
let p = arc_seg_start(seg)
pts_raw.push((p.0, p.1, h))
}
let pts = pts_raw.map(fn(pt) { xform.apply(pt) })
let cp_refs = pts.map(fn(pt) {
let (px, py, pz) = pt
ctx.cartesian_point(px, py, pz)
})
let vp_refs = cp_refs.map(fn(cp) { ctx.vertex_point(cp) })
let edges : Array[EdgeInfo] = []
let add_line = fn(a : Int32, b : Int32) -> Unit {
let dir_raw = v_norm(v_sub(pts[b], pts[a]))
let dir_ref = ctx.direction(dir_raw.0, dir_raw.1, dir_raw.2)
let vec_ref = ctx.vector(dir_ref, 1.0)
let line_ref = ctx.line(cp_refs[a], vec_ref)
let ec_ref = ctx.edge_curve(vp_refs[a], vp_refs[b], line_ref, true)
edges.push(EdgeInfo::{ start: a, end: b, edge: ec_ref, })
}
let add_arc = fn(a : Int32, b : Int32, c : Vec2, z : Double) -> Unit {
let rho = arc_dist2(arc_seg_start(segs[a % n]), c)
let center = xform.apply((c.0, c.1, z))
let center_cp = ctx.cartesian_point(center.0, center.1, center.2)
let a2p = ctx.axis2_placement_3d(center_cp, dir_up, dir_right)
let circle = ctx.add(
@repository.any_entity_circle(
@geometry.Circle::new("", a2p, @step.fmt_num(rho)),
),
)
let ec_ref = ctx.edge_curve(vp_refs[a], vp_refs[b], circle, true)
edges.push(EdgeInfo::{ start: a, end: b, edge: ec_ref, })
}
for i in 0.. {
add_line(i, j)
add_line(n + i, n + j)
}
SegmentArc(_, _, c) => {
add_arc(i, j, c, 0.0)
add_arc(n + i, n + j, c, h)
}
}
add_line(i, n + i)
}
// Cap outer loops.
let top_cycle : Array[Int32] = []
for i in 0.. {
// One analytic cylinder for a full circular hole, with a single seam
// edge, instead of one cylindrical face per arc segment.
let seam_b_pt = xform.apply((center.0 + radius, center.1, 0.0))
let seam_t_pt = xform.apply((center.0 + radius, center.1, h))
pts.push(seam_b_pt)
let cp_sb = ctx.cartesian_point(seam_b_pt.0, seam_b_pt.1, seam_b_pt.2)
cp_refs.push(cp_sb)
let vp_sb = ctx.vertex_point(cp_sb)
vp_refs.push(vp_sb)
pts.push(seam_t_pt)
let cp_st = ctx.cartesian_point(seam_t_pt.0, seam_t_pt.1, seam_t_pt.2)
cp_refs.push(cp_st)
let vp_st = ctx.vertex_point(cp_st)
vp_refs.push(vp_st)
let cb = xform.apply((center.0, center.1, 0.0))
let ct = xform.apply((center.0, center.1, h))
let cp_b = ctx.cartesian_point(cb.0, cb.1, cb.2)
let cp_t = ctx.cartesian_point(ct.0, ct.1, ct.2)
let a2p_b = ctx.axis2_placement_3d(cp_b, dir_up, dir_right)
let a2p_t = ctx.axis2_placement_3d(cp_t, dir_up, dir_right)
let radius_text = @step.fmt_num(radius)
let circle_b = ctx.add(
@repository.any_entity_circle(
@geometry.Circle::new("", a2p_b, radius_text),
),
)
let circle_t = ctx.add(
@repository.any_entity_circle(
@geometry.Circle::new("", a2p_t, radius_text),
),
)
let cylinder = ctx.add(
@repository.any_entity_cylindrical_surface(
@geometry.CylindricalSurface::new("", a2p_b, radius_text),
),
)
let seam = ctx.edge_curve(
vp_sb,
vp_st,
ctx.line(cp_sb, ctx.vector(dir_up, 1.0)),
true,
)
let e_b = ctx.edge_curve(vp_sb, vp_sb, circle_b, true)
let e_t = ctx.edge_curve(vp_st, vp_st, circle_t, true)
let lateral_loop = ctx.edge_loop([
ctx.oriented_edge(seam, true),
ctx.oriented_edge(e_t, true),
ctx.oriented_edge(seam, false),
ctx.oriented_edge(e_b, false),
])
faces.push(
ctx.advanced_face(
[ctx.face_outer_bound(lateral_loop, true)],
cylinder,
reverse,
),
)
bottom_holes.push(ctx.edge_loop([ctx.oriented_edge(e_b, true)]))
top_holes.push(ctx.edge_loop([ctx.oriented_edge(e_t, false)]))
continue
}
None => ()
}
let hm = hole.length()
let hbase = pts.length()
for seg in hole {
let p = arc_seg_start(seg)
let pt = xform.apply((p.0, p.1, 0.0))
pts.push(pt)
let cp = ctx.cartesian_point(pt.0, pt.1, pt.2)
cp_refs.push(cp)
vp_refs.push(ctx.vertex_point(cp))
}
for seg in hole {
let p = arc_seg_start(seg)
let pt = xform.apply((p.0, p.1, h))
pts.push(pt)
let cp = ctx.cartesian_point(pt.0, pt.1, pt.2)
cp_refs.push(cp)
vp_refs.push(ctx.vertex_point(cp))
}
for i in 0.. {
let add_hole_line = fn(a : Int32, b : Int32) -> Unit {
let dir = v_norm(v_sub(pts[b], pts[a]))
let line = ctx.line(
cp_refs[a],
ctx.vector(ctx.direction(dir.0, dir.1, dir.2), 1.0),
)
let edge = ctx.edge_curve(vp_refs[a], vp_refs[b], line, true)
edges.push(EdgeInfo::{ start: a, end: b, edge, })
}
add_hole_line(b_i, b_j)
add_hole_line(t_i, t_j)
}
SegmentArc(_, _, c) => {
let rho = arc_dist2(arc_seg_start(hole[i]), c)
let add_hole_arc = fn(a : Int32, b : Int32, z : Double) -> Unit {
let center = xform.apply((c.0, c.1, z))
let center_cp = ctx.cartesian_point(center.0, center.1, center.2)
let a2p = ctx.axis2_placement_3d(center_cp, dir_up, dir_right)
let circle = ctx.add(
@repository.any_entity_circle(
@geometry.Circle::new("", a2p, @step.fmt_num(rho)),
),
)
let edge = ctx.edge_curve(vp_refs[a], vp_refs[b], circle, true)
edges.push(EdgeInfo::{ start: a, end: b, edge, })
}
add_hole_arc(b_i, b_j, 0.0)
add_hole_arc(t_i, t_j, h)
}
}
// Vertical edge at this profile vertex.
let vdir = v_norm(v_sub(pts[t_i], pts[b_i]))
let vline = ctx.line(
cp_refs[b_i],
ctx.vector(ctx.direction(vdir.0, vdir.1, vdir.2), 1.0),
)
let vedge = ctx.edge_curve(vp_refs[b_i], vp_refs[t_i], vline, true)
edges.push(EdgeInfo::{ start: b_i, end: t_i, edge: vedge, })
}
// Lateral faces, with normals pointing into the hole.
for i in 0.. {
let p0 = pts[b_i]
let p1 = pts[b_j]
let p2 = pts[t_i]
let right = v_norm(v_cross(v_sub(p1, p0), v_sub(p2, p0)))
let expected = v_scale(right, -1.0)
faces.push(
add_plane_face_oriented(
ctx,
edges,
pts,
cp_refs,
[b_j, b_i, t_i, t_j],
expected,
reverse~,
),
)
}
SegmentArc(_, _, c) => {
let rho = arc_dist2(arc_seg_start(hole[i]), c)
let center = xform.apply((c.0, c.1, 0.0))
let center_cp = ctx.cartesian_point(center.0, center.1, center.2)
let a2p = ctx.axis2_placement_3d(center_cp, dir_up, dir_right)
let cylinder = ctx.add(
@repository.any_entity_cylindrical_surface(
@geometry.CylindricalSurface::new("", a2p, @step.fmt_num(rho)),
),
)
let (eb, _) = find_edge(edges, b_i, b_j)
let (et, _) = find_edge(edges, t_i, t_j)
let (ve_i, _) = find_edge(edges, b_i, t_i)
let (ve_j, _) = find_edge(edges, t_j, b_j)
let loop_ref = ctx.edge_loop([
ctx.oriented_edge(eb, false),
ctx.oriented_edge(ve_i, true),
ctx.oriented_edge(et, true),
ctx.oriented_edge(ve_j, false),
])
faces.push(
ctx.advanced_face(
[ctx.face_outer_bound(loop_ref, true)],
cylinder,
reverse,
),
)
}
}
}
// Cap inner bounds: bottom forward, top reversed.
let hb : Array[Int32] = []
for i in 0.. {
let rb = diameter / 2.0
let cb = xform.apply((center.0, center.1, 0.0))
let ct = xform.apply((center.0, center.1, h))
let cp_b = ctx.cartesian_point(cb.0, cb.1, cb.2)
let cp_t = ctx.cartesian_point(ct.0, ct.1, ct.2)
let a2p_b = ctx.axis2_placement_3d(cp_b, dir_up, dir_right)
let a2p_t = ctx.axis2_placement_3d(cp_t, dir_up, dir_right)
let radius = @step.fmt_num(rb)
let circle_b = ctx.add(
@repository.any_entity_circle(@geometry.Circle::new("", a2p_b, radius)),
)
let circle_t = ctx.add(
@repository.any_entity_circle(@geometry.Circle::new("", a2p_t, radius)),
)
let cylinder = ctx.add(
@repository.any_entity_cylindrical_surface(
@geometry.CylindricalSurface::new("", a2p_b, radius),
),
)
let seam_b = xform.apply((center.0 + rb, center.1, 0.0))
let seam_t = xform.apply((center.0 + rb, center.1, h))
let cp_sb = ctx.cartesian_point(seam_b.0, seam_b.1, seam_b.2)
let cp_st = ctx.cartesian_point(seam_t.0, seam_t.1, seam_t.2)
let vp_sb = ctx.vertex_point(cp_sb)
let vp_st = ctx.vertex_point(cp_st)
let seam = ctx.edge_curve(
vp_sb,
vp_st,
ctx.line(cp_sb, ctx.vector(dir_up, 1.0)),
true,
)
let e_b = ctx.edge_curve(vp_sb, vp_sb, circle_b, true)
let e_t = ctx.edge_curve(vp_st, vp_st, circle_t, true)
let lateral_loop = ctx.edge_loop([
ctx.oriented_edge(seam, true),
ctx.oriented_edge(e_t, true),
ctx.oriented_edge(seam, false),
ctx.oriented_edge(e_b, false),
])
faces.push(
ctx.advanced_face(
[ctx.face_outer_bound(lateral_loop, true)],
cylinder,
reverse,
),
)
bottom_holes.push(ctx.edge_loop([ctx.oriented_edge(e_b, true)]))
top_holes.push(ctx.edge_loop([ctx.oriented_edge(e_t, false)]))
}
None => ()
}
faces.push(
add_plane_face_from_loops(
ctx,
bottom_origin,
down,
right,
bottom_loop,
bottom_holes,
reverse~,
),
)
faces.push(
add_plane_face_from_loops(
ctx,
top_origin,
up,
right,
top_loop,
top_holes,
reverse~,
),
)
// Side faces.
for i in 0.. {
let p0 = pts[i]
let p1 = pts[j]
let p2 = pts[n + i]
let expected = v_norm(v_cross(v_sub(p1, p0), v_sub(p2, p0)))
faces.push(
add_plane_face_oriented(
ctx,
edges,
pts,
cp_refs,
[i, j, n + j, n + i],
expected,
reverse~,
),
)
}
SegmentArc(_, _, c) => {
let rho = arc_dist2(arc_seg_start(segs[i]), c)
let center = xform.apply((c.0, c.1, 0.0))
let center_cp = ctx.cartesian_point(center.0, center.1, center.2)
let a2p = ctx.axis2_placement_3d(center_cp, dir_up, dir_right)
let cylinder = ctx.add(
@repository.any_entity_cylindrical_surface(
@geometry.CylindricalSurface::new("", a2p, @step.fmt_num(rho)),
),
)
let (eb, _) = find_edge(edges, i, j)
let (et, _) = find_edge(edges, n + i, n + j)
let (ve, _) = find_edge(edges, j, n + j)
let (vs, _) = find_edge(edges, i, n + i)
let loop_ref = ctx.edge_loop([
ctx.oriented_edge(eb, true),
ctx.oriented_edge(ve, true),
ctx.oriented_edge(et, false),
ctx.oriented_edge(vs, false),
])
faces.push(
ctx.advanced_face(
[ctx.face_outer_bound(loop_ref, true)],
cylinder,
!reverse,
),
)
}
}
}
faces
}