main
Drew Owens 0.11.4 source 8889116 1d ago
//! OFFSET ENTITIES AND SKETCH REGIONS (0.11.4) through the whole bridge, in the
//! SIMULATOR. What the simulator can honestly model it models exactly: the
//! offset of a plain block's face (a rectangle moved in or out) and the
//! regions of profiles that nest or stand apart. Anything else — a face whose
//! outline only SOLIDWORKS knows, contours that CROSS — it refuses, saying so;
//! the CSWA Step 4 spec runs those live. Every step says proven.live:false
//! until a live run measures it.

mod common;

use common::{Bridge, TempDir};
use serde_json::{json, Value};

const PI: f64 = std::f64::consts::PI;

fn s(v: &Value) -> String {
    serde_json::to_string_pretty(v).unwrap_or_default()
}

fn boot(tag: &str) -> (Bridge, TempDir) {
    let machine = TempDir::new(tag);
    std::fs::write(machine.join("windows.json"), "[]").unwrap();
    let b = Bridge::start_with(&[("SWX_LAUNCH_FAKE_DIR", &machine.path())]);
    b.call("swx_start", json!({}));
    (b, machine)
}

fn near(a: &Value, b: f64, rel: f64) -> bool {
    a.as_f64().map(|x| (x - b).abs() <= rel * b.abs().max(1e-9)).unwrap_or(false)
}

fn volume(b: &Bridge) -> Value {
    b.call("swx_mass_properties", json!({}))["volumeMm3"].clone()
}

#[test]
fn an_offset_of_a_plain_blocks_face_is_its_rectangle_moved_in_or_out() {
    let (b, _m) = boot("regions-offset");
    let block = json!([
        {"op": "new_part"},
        {"op": "sketch_rect", "plane": "Top Plane", "widthMm": 60, "heightMm": 40, "anchor": "center"},
        {"op": "extrude", "depthMm": 20}
    ]);
    // INWARD: the top face's outline 2 mm in, cut 5 deep — a pocket 56 x 36.
    let mut script = block.as_array().unwrap().clone();
    script.push(json!({"op": "sketch", "face": {"atPointMm": [0, 20, 0]}, "entities": [{"offsetEntities": {"fromFace": {"atPointMm": [0, 20, 0]}, "offsetMm": -2}}]}));
    script.push(json!({"op": "cut", "depthMm": 5}));
    let r = b.call("swx_run_script", json!({"script": script}));
    check!(b, "the inward offset of a block's top face pockets it", r["success"] == true, s(&r));
    let sk = &r["steps"][3]["result"];
    check!(
        b,
        "the step names the offset, the side it landed on, and that the simulator drew a rectangle",
        sk["offsets"][0]["offsetEntities"]["side"] == "inward" && sk["offsets"][0]["drawn"]["side"] == "inward"
            && [(0usize, 0usize, -28.0), (0, 1, -18.0), (1, 0, 28.0), (1, 1, 18.0)].iter().all(|(i, j, want)| near(&sk["offsets"][0]["drawn"]["rectMm"][*i][*j], *want, 1e-12))
            && near(&sk["profileAreaMm2"], 56.0 * 36.0, 1e-12),
        s(sk)
    );
    check!(
        b,
        "...and is classified as an outer-loop offset, not yet measured live",
        sk["proven"]["variants"].as_array().map(|a| a.iter().any(|v| v == "sketch.offsetEntities.outer")).unwrap_or(false)
            && sk["proven"]["live"] == false,
        s(&sk["proven"])
    );
    check!(b, "the pocket removed 56 x 36 x 5 (swx_mass_properties)", near(&volume(&b), 60.0 * 40.0 * 20.0 - 56.0 * 36.0 * 5.0, 1e-6), s(&volume(&b)));
    // OUTWARD, every loop: the bottom face's outline 3 mm out, a 4-deep boss below.
    let mut script = block.as_array().unwrap().clone();
    script.push(json!({"op": "sketch", "face": {"atPointMm": [0, 0, 0]}, "entities": [{"offsetEntities": {"fromFace": {"atPointMm": [0, 0, 0]}, "offsetMm": 3, "loops": "all"}}]}));
    script.push(json!({"op": "extrude", "depthMm": 4}));
    let r = b.call("swx_run_script", json!({"script": script}));
    check!(
        b,
        "the outward offset of the bottom face is a 66 x 46 boss below it, classified as a face offset",
        r["success"] == true && near(&volume(&b), 60.0 * 40.0 * 20.0 + 66.0 * 46.0 * 4.0, 1e-6)
            && r["steps"][3]["result"]["proven"]["variants"].as_array().map(|a| a.iter().any(|v| v == "sketch.offsetEntities.face")).unwrap_or(false),
        format!("{}\n{}", s(&r), s(&volume(&b)))
    );
    // A face whose outline is SOLIDWORKS' own (a cylinder's end, a cut block)
    // is refused — the simulator never invents an outline.
    let r = b.call("swx_run_script", json!({"script": [
        {"op": "new_part"},
        {"op": "sketch_circle", "plane": "Top Plane", "radiusMm": 10},
        {"op": "extrude", "depthMm": 5},
        {"op": "sketch", "face": {"atPointMm": [0, 5, 0]}, "entities": [{"offsetEntities": {"fromFace": {"atPointMm": [0, 5, 0]}, "offsetMm": -1}}]},
        {"op": "cut", "depthMm": 1}
    ]}));
    check!(
        b,
        "an offset of a face that is not a plain block's is refused, naming why (and saying it runs live)",
        r["success"] == false && r["error"].as_str().unwrap_or("").contains("plain block") && r["error"].as_str().unwrap_or("").contains("run it live"),
        s(&r)
    );
}

#[test]
fn a_region_pick_extrudes_only_the_regions_that_hold_its_points() {
    let (b, _m) = boot("regions-pick");
    let run = |b: &Bridge, entities: Value, extrude: Value| -> Value {
        b.call("swx_run_script", json!({"script": [
            {"op": "new_part"},
            {"op": "sketch", "plane": "Front Plane", "entities": entities},
            extrude
        ]}))
    };
    let two = json!([{"rect": {"xMm": 0, "yMm": 0, "widthMm": 10, "heightMm": 10}}, {"rect": {"xMm": 20, "yMm": 0, "widthMm": 10, "heightMm": 10}}]);
    let r = run(&b, two.clone(), json!({"op": "extrude", "depthMm": 5, "regionsAtMm": [[25, 5]]}));
    check!(b, "one of two rectangles picked: only it is extruded", r["success"] == true && near(&volume(&b), 500.0, 1e-9), format!("{}\n{}", s(&r), s(&volume(&b))));
    check!(
        b,
        "the extrude is classified as a region pick (not yet measured live)",
        r["steps"][2]["result"]["proven"]["variants"] == json!(["extrude.one", "extrude.regions"]) && r["steps"][2]["result"]["proven"]["live"] == false,
        s(&r["steps"][2])
    );
    let r = run(&b, two, json!({"op": "extrude", "depthMm": 5, "regionsAtMm": [[25, 5], [5, 5]]}));
    check!(b, "both picked: both", r["success"] == true && near(&volume(&b), 1000.0, 1e-9), s(&volume(&b)));
    let ring = json!([{"circle": {"radiusMm": 10}}, {"circle": {"radiusMm": 5}}]);
    let r = run(&b, ring.clone(), json!({"op": "extrude", "depthMm": 2, "regionAtMm": [0, 0]}));
    check!(b, "a point inside the inner circle picks the DISC", r["success"] == true && near(&volume(&b), PI * 25.0 * 2.0, 1e-6), s(&volume(&b)));
    let r = run(&b, ring, json!({"op": "extrude", "depthMm": 2, "regionAtMm": [7, 0]}));
    check!(b, "a point between the circles picks the RING", r["success"] == true && near(&volume(&b), PI * 75.0 * 2.0, 1e-6), s(&volume(&b)));
    let r = run(&b, json!([{"rect": {"xMm": 0, "yMm": 0, "widthMm": 10, "heightMm": 10}}]), json!({"op": "extrude", "depthMm": 2, "regionAtMm": [50, 50]}));
    check!(b, "a point in no profile is refused, naming it", r["success"] == false && r["error"].as_str().unwrap_or("").contains("in no closed profile"), s(&r));
    let crossing = json!([{"rect": {"xMm": 0, "yMm": 0, "widthMm": 10, "heightMm": 10}}, {"circle": {"centerMm": [10, 5], "radiusMm": 4}}]);
    let r = run(&b, crossing, json!({"op": "extrude", "depthMm": 2, "regionAtMm": [2, 2]}));
    check!(
        b,
        "CROSSING contours are SOLIDWORKS' to resolve: refused in the simulator, saying so",
        r["success"] == false && r["error"].as_str().unwrap_or("").contains("CROSS") && r["error"].as_str().unwrap_or("").contains("run it live"),
        s(&r)
    );
}