{
  "schema": "vextcad.vxl.v1",
  "id": "centrifugal_impeller",
  "units": {"angle": "deg", "length": "mm"},
  "needs": [
    {"id": "n_body", "source": "user", "text": "The open impeller is one connected exact solid with a closed outward-oriented delivery mesh."},
    {"id": "n_size", "source": "user", "text": "The backplate is 86 mm in diameter and the overall height is 36 mm."},
    {"id": "n_bore", "source": "user", "text": "A concentric 10 mm shaft bore passes through the full hub and backplate."},
    {"id": "n_vanes", "source": "user", "text": "Ten backward-swept main vanes alternate with ten mid-radius splitter vanes; all twenty have 1.8 mm normal thickness."},
    {"id": "n_crowns", "source": "user", "text": "The main vane crowns slope from Z=31 mm at their inner centre points to Z=10 mm at their outlet centre points; splitter crowns slope from Z=18 mm to Z=9.5 mm."},
    {"id": "n_datums", "source": "user", "text": "The backplate mounts on Z=0 and the concave arc-taper hub terminates in a narrow bore lip at Z=36 mm."},
    {"id": "n_material", "source": "user", "text": "The sloped-crown impeller material volume lies between 40000 and 46000 cubic millimetres."}
  ],
  "params": [
    {"id": "disc_d", "type": "length", "value": "86"},
    {"id": "plate", "type": "length", "value": "4"},
    {"id": "hub_r", "type": "length", "value": "14"},
    {"id": "hub_top", "type": "length", "value": "36"},
    {"id": "nose_r", "type": "length", "value": "6"},
    {"id": "bore_d", "type": "length", "value": "10"},
    {"id": "rim_bevel", "type": "length", "value": "0.6"},
    {"id": "vane_count", "type": "count", "value": "10"},
    {"id": "vane_thick", "type": "length", "value": "1.8"},
    {"id": "arc_radius", "type": "length", "value": "22"},
    {"id": "arc_cx", "type": "length", "value": "12"},
    {"id": "tip_angle", "type": "angle", "value": "-5"},
    {"id": "split_angle", "type": "angle", "value": "55"},
    {"id": "main_root_top", "type": "length", "value": "31"},
    {"id": "main_tip_top", "type": "length", "value": "10"},
    {"id": "split_root_top", "type": "length", "value": "18"},
    {"id": "split_tip_top", "type": "length", "value": "9.5"}
  ],
  "lets": [
    {"id": "disc_r", "expr": "disc_d / 2"},
    {"id": "bore_r", "expr": "bore_d / 2"},
    {"id": "arc_outer", "expr": "arc_radius + vane_thick / 2"},
    {"id": "arc_inner", "expr": "arc_radius - vane_thick / 2"},
    {"id": "vane_start", "expr": "plate - 0.5mm"},
    {"id": "pitch", "expr": "360deg / vane_count"},
    {"id": "tip_cos", "expr": "cos(tip_angle)"},
    {"id": "tip_sin", "expr": "sin(tip_angle)"},
    {"id": "tip_px", "expr": "arc_cx + arc_radius * tip_cos"},
    {"id": "tip_py", "expr": "-arc_radius + arc_radius * tip_sin"},
    {"id": "split_px", "expr": "arc_cx + arc_radius * cos(split_angle)"},
    {"id": "split_py", "expr": "-arc_radius + arc_radius * sin(split_angle)"},
    {"id": "nose_drop", "expr": "hub_r - nose_r"},
    {"id": "nose_rise", "expr": "hub_top - plate"},
    {"id": "hub_curve_r", "expr": "(nose_rise * nose_rise + nose_drop * nose_drop) / (2 * nose_drop)"},
    {"id": "hub_curve_cx", "expr": "nose_r + hub_curve_r"},
    {"id": "main_dx", "expr": "tip_px - arc_cx"},
    {"id": "main_dy", "expr": "tip_py"},
    {"id": "main_grad_u", "expr": "(main_root_top - main_tip_top) * main_dx / (main_dx * main_dx + main_dy * main_dy)"},
    {"id": "main_grad_v", "expr": "(main_root_top - main_tip_top) * main_dy / (main_dx * main_dx + main_dy * main_dy)"},
    {"id": "split_dx", "expr": "tip_px - split_px"},
    {"id": "split_dy", "expr": "tip_py - split_py"},
    {"id": "split_grad_u", "expr": "(split_root_top - split_tip_top) * split_dx / (split_dx * split_dx + split_dy * split_dy)"},
    {"id": "split_grad_v", "expr": "(split_root_top - split_tip_top) * split_dy / (split_dx * split_dx + split_dy * split_dy)"}
  ],
  "checks": [
    {"id": "hub_covers_roots", "expr": "hub_r > sqrt(arc_cx * arc_cx + vane_thick * vane_thick / 4)", "message": "The hub must overlap the main vane roots."},
    {"id": "rim_margin", "expr": "hypot(arc_cx + arc_outer * tip_cos, -arc_radius + arc_outer * tip_sin) < disc_r - rim_bevel", "message": "The vane tips must remain inside the flat backplate rim."},
    {"id": "splitter_station", "expr": "hypot(split_px, split_py) > hub_r + 5mm", "message": "The splitter vanes must begin clear of the hub."},
    {"id": "nose_lip", "expr": "nose_r > bore_r", "message": "The bore must retain a positive nose lip."}
  ],
  "functions": [
    {
      "id": "spin_u",
      "expr": "px * cos(turn) - py * sin(turn)",
      "params": [
        {"id": "px", "type": "length"},
        {"id": "py", "type": "length"},
        {"id": "turn", "type": "angle"}
      ],
      "returns": "length"
    },
    {
      "id": "spin_v",
      "expr": "px * sin(turn) + py * cos(turn)",
      "params": [
        {"id": "px", "type": "length"},
        {"id": "py", "type": "length"},
        {"id": "turn", "type": "angle"}
      ],
      "returns": "length"
    }
  ],
  "profiles": [
    {"id": "main_vane", "path": {"from": ["arc_cx", "vane_thick / 2"], "segments": [{"arc": {"center": ["arc_cx", "-arc_radius"], "dir": "cw", "to": ["arc_cx + arc_outer * tip_cos", "-arc_radius + arc_outer * tip_sin"]}, "label": "pressure"}, {"label": "outlet", "line": ["arc_cx + arc_inner * tip_cos", "-arc_radius + arc_inner * tip_sin"]}, {"arc": {"center": ["arc_cx", "-arc_radius"], "dir": "ccw", "to": ["arc_cx", "-vane_thick / 2"]}, "label": "suction"}, {"label": "inlet", "line": ["arc_cx", "vane_thick / 2"]}]}},
    {"id": "splitter_vane", "path": {"from": ["arc_cx + arc_outer * cos(split_angle)", "-arc_radius + arc_outer * sin(split_angle)"], "segments": [{"arc": {"center": ["arc_cx", "-arc_radius"], "dir": "cw", "to": ["arc_cx + arc_outer * tip_cos", "-arc_radius + arc_outer * tip_sin"]}, "label": "pressure"}, {"label": "outlet", "line": ["arc_cx + arc_inner * tip_cos", "-arc_radius + arc_inner * tip_sin"]}, {"arc": {"center": ["arc_cx", "-arc_radius"], "dir": "ccw", "to": ["arc_cx + arc_inner * cos(split_angle)", "-arc_radius + arc_inner * sin(split_angle)"]}, "label": "suction"}, {"label": "inlet", "line": ["arc_cx + arc_outer * cos(split_angle)", "-arc_radius + arc_outer * sin(split_angle)"]}]}}
  ],
  "parts": [
    {
      "id": "impeller",
      "acceptance": "exact_only_v1",
      "deliver": "body",
      "role": "deliverable",
      "steps": [
        {"id": "hub_blank", "op": "revolve", "bind": "body", "axis": {"direction": ["0", "1"], "through": ["0", "0"]}, "plane": "xz", "profile": {"path": {"from": ["bore_r", "0"], "segments": [{"line": ["disc_r - rim_bevel", "0"]}, {"line": ["disc_r", "rim_bevel"]}, {"line": ["disc_r", "plate - rim_bevel"]}, {"line": ["disc_r - rim_bevel", "plate"]}, {"line": ["hub_r", "plate"]}, {"arc": {"center": ["hub_curve_cx", "hub_top"], "dir": "cw", "to": ["nose_r", "hub_top"]}, "label": "hub_taper"}, {"line": ["bore_r", "hub_top"]}, {"line": ["bore_r", "0"]}]}}},
        {"id": "main_blank", "op": "extrude", "bind": "main", "at": "vane_start", "length": "hub_top + 5mm - vane_start", "plane": "xy", "profile": {"profile": "main_vane"}, "toward": "+z"},
        {"id": "splitter_blank", "op": "extrude", "bind": "splitter", "at": "vane_start", "length": "hub_top + 5mm - vane_start", "plane": "xy", "profile": {"profile": "splitter_vane"}, "toward": "+z"},
        {"id": "slope_main", "op": "direct_edit", "target": "main", "edit": {"keep": "negative", "kind": "cut_plane", "plane": {"normal": ["main_grad_u", "main_grad_v", "1"], "through": ["arc_cx", "0", "main_root_top"]}}},
        {"id": "slope_splitter", "op": "direct_edit", "target": "splitter", "edit": {"keep": "negative", "kind": "cut_plane", "plane": {"normal": ["split_grad_u", "split_grad_v", "1"], "through": ["split_px", "split_py", "split_root_top"]}}},
        {"id": "join_vanes", "op": "union", "target": "body", "tools": [{"each": [{"range": ["0", "vane_count - 1"], "var": "i"}], "emit": {"alias": "main_${i}", "binding": "main", "place": {"rotate": [{"axis": "+z", "angle": "i * pitch"}]}}}, {"each": [{"range": ["0", "vane_count - 1"], "var": "i"}], "emit": {"alias": "splitter_${i}", "binding": "splitter", "place": {"rotate": [{"axis": "+z", "angle": "(i + 0.5) * pitch"}]}}}]}
      ]
    }
  ],
  "requirements": [
    {"id": "one_solid", "kind": "solid_count", "subject": "impeller", "covers": ["n_body"], "required": true},
    {"id": "closed_surface", "kind": "closed_delivered_mesh", "subject": "impeller", "covers": ["n_body"], "required": true},
    {"id": "overall_envelope", "kind": "envelope", "subject": "impeller", "bounds": {"max": ["disc_r", "disc_r", "hub_top"], "min": ["-disc_r", "-disc_r", "0"]}, "covers": ["n_size"], "required": true},
    {"id": "shaft_bore", "kind": "hole", "subject": "impeller", "axis": "z", "axis_at": ["0", "0", "0"], "diameter": "bore_d", "through": ["0", "hub_top"], "covers": ["n_bore"], "required": true},
    {"id": "backplate_diameter", "kind": "outer_diameter", "subject": "impeller", "axis": "z", "axis_at": ["0", "0", "0"], "diameter": "disc_d", "span": {"from": "rim_bevel", "to": "plate - rim_bevel"}, "covers": ["n_size"], "required": true},
    {"id": "mounting_face", "kind": "planar_face", "subject": "impeller", "at": "0", "normal": "-z", "covers": ["n_datums"], "required": true},
    {"id": "nose_lip", "kind": "planar_face", "subject": "impeller", "at": "hub_top", "normal": "+z", "covers": ["n_datums"], "required": true},
    {"id": "outlet_face", "kind": "planar_face", "subject": "impeller", "near": {"x": ["spin_u(tip_px, tip_py, i * pitch / 2) - 2mm", "spin_u(tip_px, tip_py, i * pitch / 2) + 2mm"], "y": ["spin_v(tip_px, tip_py, i * pitch / 2) - 2mm", "spin_v(tip_px, tip_py, i * pitch / 2) + 2mm"], "z": ["4", "12"]}, "normal": ["tip_sin * cos(i * pitch / 2) + tip_cos * sin(i * pitch / 2)", "tip_sin * sin(i * pitch / 2) - tip_cos * cos(i * pitch / 2)", "0"], "through": ["spin_u(tip_px, tip_py, i * pitch / 2)", "spin_v(tip_px, tip_py, i * pitch / 2)", "6"], "covers": ["n_vanes"], "required": true, "at_each": [{"range": ["0", "2 * vane_count - 1"], "var": "i"}]},
    {"id": "splitter_inlet", "kind": "planar_face", "subject": "impeller", "near": {"x": ["spin_u(split_px, split_py, (i + 0.5) * pitch) - 2mm", "spin_u(split_px, split_py, (i + 0.5) * pitch) + 2mm"], "y": ["spin_v(split_px, split_py, (i + 0.5) * pitch) - 2mm", "spin_v(split_px, split_py, (i + 0.5) * pitch) + 2mm"], "z": ["4", "25"]}, "normal": ["-sin(split_angle) * cos((i + 0.5) * pitch) - cos(split_angle) * sin((i + 0.5) * pitch)", "-sin(split_angle) * sin((i + 0.5) * pitch) + cos(split_angle) * cos((i + 0.5) * pitch)", "0"], "through": ["spin_u(split_px, split_py, (i + 0.5) * pitch)", "spin_v(split_px, split_py, (i + 0.5) * pitch)", "6"], "covers": ["n_vanes"], "required": true, "at_each": [{"range": ["0", "vane_count - 1"], "var": "i"}]},
    {"id": "main_crown", "kind": "planar_face", "subject": "impeller", "near": {"x": ["-43", "43"], "y": ["-43", "43"], "z": ["8", "33"]}, "normal": ["main_grad_u", "main_grad_v", "1"], "through": ["arc_cx", "0", "main_root_top"], "covers": ["n_crowns", "n_vanes"], "required": true},
    {"id": "splitter_crown", "kind": "planar_face", "subject": "impeller", "near": {"x": ["-43", "43"], "y": ["-43", "43"], "z": ["8", "20"]}, "normal": ["split_grad_u * cos(pitch / 2) - split_grad_v * sin(pitch / 2)", "split_grad_u * sin(pitch / 2) + split_grad_v * cos(pitch / 2)", "1"], "through": ["spin_u(split_px, split_py, pitch / 2)", "spin_v(split_px, split_py, pitch / 2)", "split_root_top"], "covers": ["n_crowns", "n_vanes"], "required": true},
    {"id": "material_volume", "kind": "volume_range", "subject": "impeller", "max": "46000", "min": "40000", "covers": ["n_material", "n_vanes"], "required": true}
  ]
}
