use crate::feature_pipeline::sheet_metal::{self, Bend, Edge, Flat, Hole, HoleBend, HoleBendKind};
use crate::feature_pipeline::{FeatureContext, FeatureResult};
use crate::{make_arc, make_line, NurbsCurve, Vec3};
const GEOM_EPS: f64 = 1e-9;
const MIN_SPAN: f64 = 1e-6;
const KNIFE_RADIUS: f64 = 1e-4;
pub struct FlangeOptions {
pub angle_deg: Option<f64>,
pub default_leg: f64,
pub length_reference: Option<&'static str>,
pub knife_zero_radius: bool,
}
impl Default for FlangeOptions {
fn default() -> Self {
FlangeOptions {
angle_deg: None,
default_leg: 10.0,
length_reference: None,
knife_zero_radius: false,
}
}
}
struct FlangeParams {
signed_angle: f64,
inside_radius: f64,
k_factor: f64,
thickness: f64,
leg: f64,
shift: f64,
setback_start: f64,
setback_end: f64,
relief: ReliefType,
relief_width: f64,
relief_depth: f64,
corner: CornerType,
}
#[derive(Clone, Copy, PartialEq)]
enum ReliefType {
None,
Rectangular,
Obround,
Tear,
}
#[derive(Clone, Copy, PartialEq)]
enum CornerType {
Open,
Closed,
Miter,
}
#[derive(Clone, Copy)]
enum WallEnd {
Flat,
Extend(f64),
Miter,
}
#[derive(PartialEq)]
enum Target {
Outline {
flat_id: String,
edge_id: String,
},
Hole {
flat_id: String,
hole: usize,
segment: usize,
},
}
mod build;
mod hole;
mod outline;
pub use build::{execute, run};
use build::corner_child_flat;
use hole::graft_hole_flange;
use outline::{add_relief_slot, graft_outline_group, OutlinePick, ReliefSide};
pub fn context_applicable(probe: &crate::feature_pipeline::SelectionProbe) -> bool {
probe.all_sheet_metal && (probe.faces > 0 || probe.edges > 0)
}
pub fn schema() -> serde_json::Value {
serde_json::json!({
"type": "SM.F",
"shortName": "SM.F",
"longName": "SM Flange",
"ribbonPath": "Home/Sheet Metal/Flange",
"commandSize": "Compact",
"displayBuilder": false,
"inputParamsSchema": {
"id": {
"type": "string",
"default_value": null,
"hint": "Unique identifier for the flange feature"
},
"faces": {
"type": "reference_selection",
"selectionFilter": [
"FACE",
"EDGE"
],
"multiple": true,
"default_value": null,
"hint": "Select one or more sheet-metal edges (side faces) or hole bore segments where flanges will be constructed. All picks must be on one sheet-metal body."
},
"useOppositeCenterline": {
"label": "Reverse direction",
"type": "boolean",
"default_value": false,
"hint": "Flip the fold direction (up/down) for the selected hinge."
},
"flangeLength": {
"type": "number",
"default_value": 10,
"min": 0,
"hint": "Flange length, measured per the length reference (Outer Virtual Sharp by default)."
},
"edgeStartSetback": {
"label": "Start setback",
"type": "number",
"default_value": 0,
"min": 0,
"hint": "Distance from the selected edge start point where the flange span begins (the rest stays straight rim)."
},
"edgeEndSetback": {
"label": "End setback",
"type": "number",
"default_value": 0,
"min": 0,
"hint": "Distance from the selected edge end point where the flange span ends (the rest stays straight rim)."
},
"flangeLengthReference": {
"label": "Length reference",
"type": "options",
"options": [
"Inner Virtual Sharp",
"Outer Virtual Sharp",
"Tangent to Bend"
],
"default_value": "Outer Virtual Sharp",
"hint": "Datum flangeLength is measured from: Inner Virtual Sharp = inner mold-line corner (leg = length − R·tan(|angle|/2)); Outer Virtual Sharp = outer mold-line corner (leg = length − (R+thickness)·tan(|angle|/2)); Tangent to Bend = the bend-tangent line, so leg = length (bends ≥ 90° only)."
},
"angle": {
"type": "number",
"default_value": 90,
"min": 0,
"max": 180,
"hint": "Flange angle relative to the parent sheet (0° = flat, 90° = perpendicular)."
},
"inset": {
"label": "Flange position",
"type": "options",
"options": [
"Material Inside",
"Material Outside",
"Bend Outside"
],
"default_value": "Material Inside",
"hint": "Where the flange sits relative to the reference edge (inward fold-line shift before the bend): Material Inside = thickness + bendRadius (flange stays within the original footprint at 90°); Material Outside = bendRadius; Bend Outside = 0 (the whole bend is added outside the edge)."
},
"bendRadius": {
"type": "number",
"default_value": 0,
"min": 0,
"hint": "Inside bend radius override. 0 inherits the model’s stored value (a stored 0 is treated as 0.0001)."
},
"offset": {
"type": "number",
"default_value": 0,
"hint": "Additional signed offset for bend-edge repositioning (positive = outward, negative = inward)."
},
"reliefType": {
"type": "options",
"options": [
"none",
"rectangular",
"obround",
"tear"
],
"default_value": "none",
"hint": "Bend relief cut at each band end that abuts remaining rim (setback ends), baked into the parent flat so it shows in the flat pattern. none = no cut (the default); rectangular = square slot; obround = stadium slot (rounded deep end, exact arcs); tear = triangular notch flush with the band end (a zero-width tear is not manifold)."
},
"reliefWidth": {
"type": "number",
"default_value": 0,
"min": 0,
"hint": "Relief slot width along the edge, cut into the rim flush with the band end. 0 = default: the sheet thickness."
},
"reliefDepth": {
"type": "number",
"default_value": 0,
"min": 0,
"hint": "How far the relief reaches past the fold line into the web. 0 = default: the neutral-fiber bend allowance |angle|·(R + k·t)."
},
"cornerType": {
"type": "options",
"options": [
"open",
"closed",
"miter"
],
"default_value": "open",
"hint": "Where two flanges of THIS feature meet at a convex flat corner: open = each band trimmed to the inset fold-line intersection, corner square removed (natural gap; the bends can never overlap); closed = the earlier-selected wall extends across the corner and the other butts against it; miter = both walls cut at 45°. closed/miter need a perpendicular corner and a 90° fold."
}
}
})
}