///|
// Straight tube with a full-length parallel-sided slot (an ISO 8752 slotted
// spring pin). Outer and inner surfaces are analytic cylinders, the slot walls
// are planes (y = +/- slot_width/2), and the end faces are C-shaped planes.
///|
priv struct SlitTubeGeometry {
ro : Double
ri : Double
len : Double
half : Double
delta_o : Double
delta_i : Double
slot_area : Double
}
///|
fn slit_tube_geometry(t : SlitTube) -> SlitTubeGeometry? {
let ro = t.outer_diameter_mm / 2.0
let ri = t.inner_diameter_mm / 2.0
let len = t.length_mm
let half = t.slot_width_mm / 2.0
if ro <= 0.0 ||
ri <= 0.0 ||
ri >= ro ||
len <= 0.0 ||
half <= 0.0 ||
half >= ri {
return None
}
let delta_o = @math.asin(half / ro)
let delta_i = @math.asin(half / ri)
let f = fn(rad : Double) -> Double {
0.5 *
(
half * Double::sqrt(rad * rad - half * half) +
rad * rad * @math.asin(half / rad)
)
}
Some({
ro,
ri,
len,
half,
delta_o,
delta_i,
slot_area: 2.0 * (f(ro) - f(ri)),
})
}
///|
/// slit_tube_material_area returns the C-section area at any interior `z`.
fn slit_tube_material_area(g : SlitTubeGeometry) -> Double {
@math.PI * (g.ro * g.ro - g.ri * g.ri) - g.slot_area
}
///|
/// SlitTube::volume returns the exact solid volume.
pub fn SlitTube::volume(self : SlitTube) -> Double {
match slit_tube_geometry(self) {
Some(g) => slit_tube_material_area(g) * g.len
None => 0.0
}
}
///|
/// SlitTube::centroid returns the exact centroid.
pub fn SlitTube::centroid(self : SlitTube) -> Vec3 {
match slit_tube_geometry(self) {
Some(g) => {
let area = slit_tube_material_area(g)
if area <= CAD_DEGENERATE_EPS {
(0.0, 0.0, 0.0)
} else {
(-g.half * (g.ro * g.ro - g.ri * g.ri) / area, 0.0, g.len / 2.0)
}
}
None => (0.0, 0.0, 0.0)
}
}
///|
/// an_slit_tube_inventory mirrors the compiler's face emission: two analytic
/// cylinders, two slot-wall planes and two C-shaped end planes.
fn an_slit_tube_inventory(t : SlitTube) -> SurfaceInventory {
match slit_tube_geometry(t) {
Some(_) => an_inventory(4, 2, 0, 0, 0, 0, 0, 0)
None => an_inventory(0, 0, 0, 0, 0, 0, 0, 0)
}
}
///|
/// an_slit_tube_section returns the C-section at local `z`.
fn an_slit_tube_section(
t : SlitTube,
z : Double,
) -> Result[Section, AnalysisError] {
let g = match slit_tube_geometry(t) {
Some(v) => v
None => return Err(DegenerateProfile)
}
if z < -CAD_LENGTH_EPS || z > g.len + CAD_LENGTH_EPS {
return Err(EmptySection)
}
let area = slit_tube_material_area(g)
if area <= CAD_AREA_EPS {
return Err(EmptySection)
}
let outer_perimeter = (2.0 * @math.PI - 2.0 * g.delta_o) * g.ro
let inner_perimeter = (2.0 * @math.PI - 2.0 * g.delta_i) * g.ri
let wall = Double::sqrt(g.ro * g.ro - g.half * g.half) -
Double::sqrt(g.ri * g.ri - g.half * g.half)
Ok({
z_mm: z,
area_mm2: area,
perimeter_mm: outer_perimeter + inner_perimeter + 2.0 * wall,
outer_radius_mm: g.ro,
inner_radius_mm: g.ri,
rings: [{ inner_radius_mm: g.ri, outer_radius_mm: g.ro, }],
})
}
///|
/// compile_slit_tube emits the slotted tube as one closed shell.
pub fn compile_slit_tube(
ctx : StepContext,
node : SceneNode,
t : SlitTube,
reverse~ : Bool = false,
parent~ : Transform? = None,
) -> Array[@step.Ref] {
let xform = node_transform(node, parent)
let g = match slit_tube_geometry(t) {
Some(v) => v
None => return []
}
let up = 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 xo = Double::sqrt(g.ro * g.ro - g.half * g.half)
let xi = Double::sqrt(g.ri * g.ri - g.half * g.half)
let pts = {
let obp = xform.apply((xo, g.half, 0.0))
let obm = xform.apply((xo, -g.half, 0.0))
let otp = xform.apply((xo, g.half, g.len))
let otm = xform.apply((xo, -g.half, g.len))
let ibp = xform.apply((xi, g.half, 0.0))
let ibm = xform.apply((xi, -g.half, 0.0))
let itp = xform.apply((xi, g.half, g.len))
let itm = xform.apply((xi, -g.half, g.len))
[obp, obm, otp, otm, ibp, ibm, itp, itm]
}
let cps = pts.map(fn(p) { hhwf_cp(ctx, p) })
let vps = cps.map(fn(c) { ctx.vertex_point(c) })
let line_edge = fn(a : Int32, b : Int32) -> @step.Ref {
let dir = v_norm(v_sub(pts[b], pts[a]))
let curve = ctx.line(
cps[a],
ctx.vector(ctx.direction(dir.0, dir.1, dir.2), 1.0),
)
ctx.edge_curve(vps[a], vps[b], curve, true)
}
let arc_edge = fn(
z : Double,
rad : Double,
a : Int32,
b : Int32,
) -> @step.Ref {
let center = hhwf_cp(ctx, xform.apply((0.0, 0.0, z)))
let a2p = ctx.axis2_placement_3d(center, dir_up, dir_right)
let circle = ctx.add(
@repository.any_entity_circle(
@geometry.Circle::new("", a2p, @step.fmt_num(rad)),
),
)
ctx.edge_curve(vps[a], vps[b], circle, true)
}
// Canonical edges.
let eB = arc_edge(0.0, g.ro, 0, 1)
let eT = arc_edge(g.len, g.ro, 2, 3)
let iB = arc_edge(0.0, g.ri, 4, 5)
let iT = arc_edge(g.len, g.ri, 6, 7)
let vOP = line_edge(0, 2)
let vOM = line_edge(1, 3)
let vIP = line_edge(4, 6)
let vIM = line_edge(5, 7)
let sBp = line_edge(0, 4)
let sBm = line_edge(1, 5)
let sTp = line_edge(2, 6)
let sTm = line_edge(3, 7)
let face = fn(
edges : Array[(@step.Ref, Bool)],
surface : @step.Ref,
same : Bool,
) -> @step.Ref {
let oriented = edges.map(fn(e) { ctx.oriented_edge(e.0, e.1) })
let edge_loop_ref = ctx.edge_loop(oriented)
ctx.advanced_face(
[ctx.face_outer_bound(edge_loop_ref, true)],
surface,
same,
)
}
let cylinder_at = fn(z : Double, rad : Double) -> @step.Ref {
let center = hhwf_cp(ctx, xform.apply((0.0, 0.0, z)))
let a2p = ctx.axis2_placement_3d(center, dir_up, dir_right)
ctx.add(
@repository.any_entity_cylindrical_surface(
@geometry.CylindricalSurface::new("", a2p, @step.fmt_num(rad)),
),
)
}
let faces : Array[@step.Ref] = []
// Outer cylinder, outward normal.
faces.push(
face(
[(eB, true), (vOM, true), (eT, false), (vOP, false)],
cylinder_at(0.0, g.ro),
true,
),
)
// Inner cylinder, normal toward the axis.
faces.push(
face(
[(iB, false), (vIP, true), (iT, true), (vIM, false)],
cylinder_at(0.0, g.ri),
false,
),
)
// Slot walls.
let slot_plus = hhwf_plane(
ctx,
xform.apply((0.0, g.half, 0.0)),
xform.apply_dir((0.0, -1.0, 0.0)),
xform.apply_dir((1.0, 0.0, 0.0)),
)
faces.push(
face(
[(vOP, true), (sTp, true), (vIP, false), (sBp, false)],
slot_plus,
true,
),
)
let slot_minus = hhwf_plane(
ctx,
xform.apply((0.0, -g.half, 0.0)),
xform.apply_dir((0.0, 1.0, 0.0)),
xform.apply_dir((1.0, 0.0, 0.0)),
)
faces.push(
face(
[(sBm, true), (vIM, true), (sTm, false), (vOM, false)],
slot_minus,
true,
),
)
// End faces.
let bottom_plane = hhwf_plane(
ctx,
xform.apply((0.0, 0.0, 0.0)),
xform.apply_dir((0.0, 0.0, -1.0)),
xform.apply_dir((1.0, 0.0, 0.0)),
)
faces.push(
face(
[(eB, false), (sBp, true), (iB, true), (sBm, false)],
bottom_plane,
true,
),
)
let top_plane = hhwf_plane(
ctx,
xform.apply((0.0, 0.0, g.len)),
xform.apply_dir((0.0, 0.0, 1.0)),
xform.apply_dir((1.0, 0.0, 0.0)),
)
faces.push(
face([(eT, true), (sTm, true), (iT, false), (sTp, false)], top_plane, true),
)
let _ = reverse
faces
}
///|
/// slit_tube_mesh builds a visualization mesh of the slotted tube.
pub fn slit_tube_mesh(
t : SlitTube,
arc_segments~ : Int32 = 32,
) -> (Array[Vec3], Array[Array[Int32]]) {
let g = match slit_tube_geometry(t) {
Some(v) => v
None => return ([], [])
}
let n = if arc_segments < 4 { 4 } else { arc_segments }
let outline : Array[(Double, Double)] = []
for k in 0..<=n {
let a = g.delta_o +
(2.0 * @math.PI - 2.0 * g.delta_o) *
Double::from_int(k) /
Double::from_int(n)
outline.push((g.ro * @math.cos(a), g.ro * @math.sin(a)))
}
for k in 0..<=n {
let a = 2.0 * @math.PI -
g.delta_i -
(2.0 * @math.PI - 2.0 * g.delta_i) *
Double::from_int(k) /
Double::from_int(n)
outline.push((g.ri * @math.cos(a), g.ri * @math.sin(a)))
}
let m = outline.length()
let verts : Array[Vec3] = []
for p in outline {
verts.push((p.0, p.1, 0.0))
}
for p in outline {
verts.push((p.0, p.1, g.len))
}
let faces : Array[Array[Int32]] = []
for i in 0.. Array[(Int32, Int32, Int32)] {
let n = poly.length()
let out : Array[(Int32, Int32, Int32)] = []
if n < 3 {
return out
}
let mut area = 0.0
for i in 0..= 0.0 {
idx.push(i)
} else {
idx.push(n - 1 - i)
}
}
let cross = fn(a : Int32, b : Int32, c : Int32) -> Double {
let pa = poly[a]
let pb = poly[b]
let pc = poly[c]
(pb.0 - pa.0) * (pc.1 - pa.1) - (pb.1 - pa.1) * (pc.0 - pa.0)
}
let inside = fn(a : Int32, b : Int32, c : Int32, p : Int32) -> Bool {
let d1 = cross(a, b, p)
let d2 = cross(b, c, p)
let d3 = cross(c, a, p)
(d1 >= 0.0 && d2 >= 0.0 && d3 >= 0.0) ||
(d1 <= 0.0 && d2 <= 0.0 && d3 <= 0.0)
}
while idx.length() > 3 {
let k = idx.length()
let mut clipped = false
for i in 0.. SlitTube {
{ outer_diameter_mm, inner_diameter_mm, length_mm, slot_width_mm, }
}
///|
/// SlitTube::as_shape wraps the intent as a `Shape`.
pub fn SlitTube::as_shape(self : SlitTube) -> Shape {
Shape::SlitTube(self)
}