mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-08 04:52:10 +00:00
port: geom.context (SmContext, SmNewMarkAndLock, SM_API context)
Adds geom::context (Context with marks/locks, Boolean flag, notify callbacks, own model size / zone tolerance; MarkLock; MassPropertyBehavior; create/get_or_create_context), Kernel::context, and wires the UNPORTED(geom.context) SmTol context overloads (Option<&Context>). Difftest: 2013 cases, all bit-identical. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,558 @@
|
||||
//! Runner for unit `geom.context`: context marks and mark locks, the Boolean flag,
|
||||
//! notify callbacks, the context model size estimate / zone tolerance and the
|
||||
//! `SmTol` context overloads, `SmMassPropertyBehavior`, the `SM_API` context.
|
||||
//!
|
||||
//! C++: `solidcore_ffi::units::geom_context` (upstream `SmContext`,
|
||||
//! `SmNewMarkAndLock`, `source/SMLib/{inc,src}/SmContext.*`).
|
||||
//! Rust: `solidcore::geom::context`, `solidcore::geom::tol`.
|
||||
//!
|
||||
//! Every case is a script of [`Step`]s run on `contexts` (model size, ratio) with the
|
||||
//! system tolerances set from `sys_model` / `sys_ratio` and `slots` mark-lock slots.
|
||||
//! Output: `i` (integers: marks, locks, flags, mark types, context indices) and `f`
|
||||
//! (floats: model sizes, ratios, tolerances), each step's results followed by a
|
||||
//! snapshot of every context, and a final snapshot after the live locks are dropped
|
||||
//! in slot order. Every run uses at least [`MIN_GENERATED`] generated scripts.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
use solidcore::geom::context::{
|
||||
self as ctxmod, Context, MarkLock, MarkType, MassPropertyBehavior, NotifyCallback,
|
||||
};
|
||||
use solidcore::geom::tol::{self, SmallTopoType};
|
||||
use solidcore::{Kernel, KernelOptions};
|
||||
use solidcore_ffi::units::geom_context::{self as ffi, op, Item, Step};
|
||||
|
||||
use crate::case::CaseContext;
|
||||
use crate::runner::default_cases;
|
||||
use crate::{Case, HarnessError, Output, Rng, UnitDiff};
|
||||
|
||||
/// Generated scripts per run, whatever `--seeds` says (it can only raise this).
|
||||
pub const MIN_GENERATED: usize = 2000;
|
||||
|
||||
/// `geom.context` runner.
|
||||
pub struct Runner;
|
||||
|
||||
/// One script step as written in fixtures (absent fields are 0).
|
||||
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
struct StepIn {
|
||||
op: i64,
|
||||
slot: i64,
|
||||
ctx: i64,
|
||||
ctx2: i64,
|
||||
m: i64,
|
||||
flag: i64,
|
||||
x: [f64; 4],
|
||||
}
|
||||
|
||||
impl From<StepIn> for Step {
|
||||
fn from(s: StepIn) -> Self {
|
||||
Self {
|
||||
op: s.op,
|
||||
slot: s.slot,
|
||||
ctx: s.ctx,
|
||||
ctx2: s.ctx2,
|
||||
m: s.m,
|
||||
flag: s.flag,
|
||||
x: s.x,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn default_size() -> f64 {
|
||||
tol::USE_DEFAULT
|
||||
}
|
||||
|
||||
fn default_contexts() -> Vec<[f64; 2]> {
|
||||
vec![[tol::USE_DEFAULT, tol::USE_DEFAULT]]
|
||||
}
|
||||
|
||||
fn default_slots() -> usize {
|
||||
4
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct Inputs {
|
||||
#[serde(default = "default_size")]
|
||||
sys_model: f64,
|
||||
#[serde(default = "default_size")]
|
||||
sys_ratio: f64,
|
||||
#[serde(default = "default_contexts")]
|
||||
contexts: Vec<[f64; 2]>,
|
||||
#[serde(default = "default_slots")]
|
||||
slots: usize,
|
||||
#[serde(default)]
|
||||
steps: Vec<StepIn>,
|
||||
}
|
||||
|
||||
impl Inputs {
|
||||
fn steps(&self) -> Vec<Step> {
|
||||
self.steps.iter().map(|&s| Step::from(s)).collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn output(items: &[Item]) -> Output {
|
||||
let mut ints = Vec::new();
|
||||
let mut floats = Vec::new();
|
||||
for it in items {
|
||||
match *it {
|
||||
Item::Int(v) => ints.push(v),
|
||||
Item::Float(v) => floats.push(v),
|
||||
}
|
||||
}
|
||||
Output::new().ints("i", ints).values("f", floats)
|
||||
}
|
||||
|
||||
fn harness(e: impl std::fmt::Display) -> HarnessError {
|
||||
HarnessError::new(e.to_string())
|
||||
}
|
||||
|
||||
// ---- C++ side ----------------------------------------------------------------------
|
||||
|
||||
fn cpp(i: &Inputs) -> Result<Output, HarnessError> {
|
||||
let contexts: Vec<(f64, f64)> = i.contexts.iter().map(|c| (c[0], c[1])).collect();
|
||||
let items =
|
||||
ffi::script(i.sys_model, i.sys_ratio, &contexts, i.slots, &i.steps()).map_err(harness)?;
|
||||
Ok(output(&items))
|
||||
}
|
||||
|
||||
// ---- Rust side ---------------------------------------------------------------------
|
||||
|
||||
/// The kernel `SmTol::SetModelSizeEstimate(model)` +
|
||||
/// `SmTol::SetLargeSmallSizeRatio(ratio)` would leave behind.
|
||||
fn kernel(model: f64, ratio: f64) -> Kernel {
|
||||
let model = if model == tol::USE_DEFAULT {
|
||||
tol::MODEL_SIZE_ESTIMATE
|
||||
} else {
|
||||
model
|
||||
};
|
||||
let ratio = if ratio == tol::USE_DEFAULT {
|
||||
tol::LARGE_SMALL_SIZE_RATIO
|
||||
} else {
|
||||
ratio
|
||||
};
|
||||
let zone = tol::size_estimate_to_zone_tol_3d(model);
|
||||
let mut o = KernelOptions::default();
|
||||
o.tolerances.model_size_estimate = model;
|
||||
o.tolerances.zone_tol_3d = zone;
|
||||
o.tolerances.xsect_tol_3d = zone * 2.0;
|
||||
o.tolerances.approx_tol_3d = zone / 2.0;
|
||||
o.tolerances.large_small_size_ratio = ratio;
|
||||
Kernel::new(&o)
|
||||
}
|
||||
|
||||
/// The callback object installed by `CALLBACK` steps.
|
||||
#[derive(Debug)]
|
||||
struct Notify;
|
||||
|
||||
impl NotifyCallback for Notify {}
|
||||
|
||||
const MARKS: [MarkType; 7] = [
|
||||
MarkType::NoMark,
|
||||
MarkType::Mark,
|
||||
MarkType::Mark2,
|
||||
MarkType::Mark3,
|
||||
MarkType::MarkIo,
|
||||
MarkType::MarkAssert,
|
||||
MarkType::AllMarks,
|
||||
];
|
||||
|
||||
struct Sim {
|
||||
k: Kernel,
|
||||
ctxs: Vec<Context>,
|
||||
slots: Vec<Option<MarkLock>>,
|
||||
out: Vec<Item>,
|
||||
api: Option<Context>,
|
||||
}
|
||||
|
||||
fn idx(v: i64) -> Result<usize, HarnessError> {
|
||||
usize::try_from(v).map_err(|_| harness(format!("index {v}")))
|
||||
}
|
||||
|
||||
fn mark(v: i64) -> Result<MarkType, HarnessError> {
|
||||
i32::try_from(v)
|
||||
.ok()
|
||||
.and_then(MarkType::from_code)
|
||||
.ok_or_else(|| harness(format!("mark type {v}")))
|
||||
}
|
||||
|
||||
impl Sim {
|
||||
fn int(&mut self, v: i64) {
|
||||
self.out.push(Item::Int(v));
|
||||
}
|
||||
|
||||
fn float(&mut self, v: f64) {
|
||||
self.out.push(Item::Float(v));
|
||||
}
|
||||
|
||||
fn ctx(&mut self, i: i64) -> Result<&mut Context, HarnessError> {
|
||||
let i = idx(i)?;
|
||||
self.ctxs.get_mut(i).ok_or_else(|| harness("context index"))
|
||||
}
|
||||
|
||||
fn opt_ctx(&self, i: i64) -> Option<&Context> {
|
||||
usize::try_from(i).ok().and_then(|i| self.ctxs.get(i))
|
||||
}
|
||||
|
||||
fn slot(&mut self, s: i64) -> Result<&mut Option<MarkLock>, HarnessError> {
|
||||
let s = idx(s)?;
|
||||
self.slots.get_mut(s).ok_or_else(|| harness("slot index"))
|
||||
}
|
||||
|
||||
/// `delete slot`: runs the lock's destructor.
|
||||
fn drop_slot(&mut self, s: i64) -> Result<(), HarnessError> {
|
||||
if let Some(mut l) = self.slot(s)?.take() {
|
||||
l.release(&mut self.ctxs).map_err(harness)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn snapshot(&mut self) {
|
||||
let mut v = Vec::new();
|
||||
for c in &self.ctxs {
|
||||
for m in MARKS {
|
||||
v.push(Item::Int(c.current_mark(m) as i64));
|
||||
v.push(Item::Int(i64::from(c.is_mark_locked(m))));
|
||||
}
|
||||
v.push(Item::Int(i64::from(c.doing_boolean())));
|
||||
v.push(Item::Int(i64::from(c.sys_notify_callback().is_some())));
|
||||
v.push(Item::Int(i64::from(c.user_notify_callback().is_some())));
|
||||
v.push(Item::Float(c.stored_model_size_estimate()));
|
||||
v.push(Item::Float(c.stored_large_small_size_ratio()));
|
||||
v.push(Item::Float(c.this_zone_tol_3d()));
|
||||
}
|
||||
self.out.extend(v);
|
||||
}
|
||||
|
||||
fn step(&mut self, s: &Step) -> Result<(), HarnessError> {
|
||||
let x = s.x;
|
||||
match s.op {
|
||||
op::CUR_MARK => {
|
||||
let m = mark(s.m)?;
|
||||
let v = self.ctx(s.ctx)?.current_mark(m);
|
||||
self.int(v as i64);
|
||||
}
|
||||
op::IS_LOCKED => {
|
||||
let m = mark(s.m)?;
|
||||
let v = self.ctx(s.ctx)?.is_mark_locked(m);
|
||||
self.int(i64::from(v));
|
||||
}
|
||||
op::LOCK_NEW => {
|
||||
self.drop_slot(s.slot)?;
|
||||
let l = MarkLock::new(&mut self.ctxs, idx(s.ctx)?, s.m as i32).map_err(harness)?;
|
||||
*self.slot(s.slot)? = Some(l);
|
||||
self.int(i64::from(l.mark_type().code()));
|
||||
}
|
||||
op::LOCK_PAIR => {
|
||||
self.drop_slot(s.slot)?;
|
||||
let l = MarkLock::new_pair(&mut self.ctxs, idx(s.ctx)?, idx(s.ctx2)?)
|
||||
.map_err(harness)?;
|
||||
*self.slot(s.slot)? = Some(l);
|
||||
self.int(i64::from(l.mark_type1().code()));
|
||||
self.int(i64::from(l.mark_type2().code()));
|
||||
}
|
||||
op::LOCK_DEFAULT => {
|
||||
self.drop_slot(s.slot)?;
|
||||
*self.slot(s.slot)? = Some(MarkLock::default());
|
||||
}
|
||||
op::LOCK_NEWMARK => match *self.slot(s.slot)? {
|
||||
Some(l) if l.context().is_some() => {
|
||||
l.new_mark(&mut self.ctxs).map_err(harness)?;
|
||||
self.int(1);
|
||||
}
|
||||
_ => self.int(-1),
|
||||
},
|
||||
op::LOCK_SETCTX | op::LOCK_SETCTX_PAIR => {
|
||||
let mut l = self.slot(s.slot)?.unwrap_or_else(MarkLock::unbound);
|
||||
let m = if s.op == op::LOCK_SETCTX {
|
||||
l.set_context(&mut self.ctxs, idx(s.ctx)?, s.m as i32)
|
||||
} else {
|
||||
l.set_context_pair(&mut self.ctxs, idx(s.ctx)?, idx(s.ctx2)?)
|
||||
}
|
||||
.map_err(harness)?;
|
||||
*self.slot(s.slot)? = Some(l);
|
||||
self.int(i64::from(m.code()));
|
||||
}
|
||||
op::LOCK_DROP => self.drop_slot(s.slot)?,
|
||||
op::LOCK_INFO => match *self.slot(s.slot)? {
|
||||
None => self.int(-2),
|
||||
Some(l) => {
|
||||
let ix = |c: Option<usize>| c.map_or(-1, |c| c as i64);
|
||||
self.int(i64::from(l.mark_type().code()));
|
||||
self.int(i64::from(l.mark_type1().code()));
|
||||
self.int(i64::from(l.mark_type2().code()));
|
||||
self.int(ix(l.context()));
|
||||
self.int(ix(l.context2()));
|
||||
}
|
||||
},
|
||||
op::DOING_BOOL => {
|
||||
let c = self.ctx(s.ctx)?;
|
||||
if s.flag != 0 {
|
||||
*c.doing_boolean_mut() = s.m != 0;
|
||||
} else {
|
||||
c.set_doing_boolean(s.m != 0);
|
||||
}
|
||||
let v = c.doing_boolean();
|
||||
self.int(i64::from(v));
|
||||
}
|
||||
op::GET_MODEL => {
|
||||
let c = self.opt_ctx(s.ctx).ok_or_else(|| harness("context index"))?;
|
||||
let a = c.this_model_size_estimate(&self.k);
|
||||
let b = tol::context_model_size_estimate(&self.k, c);
|
||||
self.float(a);
|
||||
self.float(b);
|
||||
}
|
||||
op::SET_MODEL => {
|
||||
let c = self.ctx(s.ctx)?;
|
||||
let z = if s.flag != 0 {
|
||||
tol::set_context_model_size_estimate(c, x[0])
|
||||
} else {
|
||||
c.set_this_model_size_estimate(x[0])
|
||||
};
|
||||
self.float(z);
|
||||
}
|
||||
op::SET_RATIO => self.ctx(s.ctx)?.set_this_large_small_size_ratio(x[0]),
|
||||
op::TOL_CTX => {
|
||||
let small = i32::try_from(s.m)
|
||||
.ok()
|
||||
.and_then(SmallTopoType::from_code)
|
||||
.ok_or_else(|| harness("small topology type"))?;
|
||||
let c = self.opt_ctx(s.ctx);
|
||||
let k = &self.k;
|
||||
let v = [
|
||||
tol::context_zone_tol_3d(k, c, small),
|
||||
tol::context_approx_tol_3d(k, c, small),
|
||||
tol::context_xsect_tol_3d(k, c, small),
|
||||
tol::calc_face_zone_tol_3d(k, c),
|
||||
];
|
||||
for f in v {
|
||||
self.float(f);
|
||||
}
|
||||
}
|
||||
op::CALC_EDGE | op::CALC_VERTEX => {
|
||||
let c = self.opt_ctx(s.ctx);
|
||||
let k = &self.k;
|
||||
let max = (s.flag != 0).then(|| tol::zone_tol3d(k, x[2]));
|
||||
let v = if s.op == op::CALC_EDGE {
|
||||
tol::calc_edge_zone_tol_3d(k, x[0], x[1], max, c)
|
||||
} else {
|
||||
tol::calc_vertex_zone_tol_3d(k, x[0], x[1], max, c)
|
||||
};
|
||||
self.float(v);
|
||||
}
|
||||
op::CALLBACK => {
|
||||
let c = self.ctx(s.ctx)?;
|
||||
let cb: Option<Box<dyn NotifyCallback>> = if s.m != 0 {
|
||||
Some(Box::new(Notify))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if s.flag != 0 {
|
||||
c.set_user_notify_callback(cb);
|
||||
} else {
|
||||
c.set_sys_notify_callback(cb);
|
||||
}
|
||||
let a = c.sys_notify_callback().is_some();
|
||||
let b = c.user_notify_callback().is_some();
|
||||
self.int(i64::from(a));
|
||||
self.int(i64::from(b));
|
||||
}
|
||||
op::MASS => {
|
||||
let r = if s.slot != 0 {
|
||||
MassPropertyBehavior::default()
|
||||
} else {
|
||||
MassPropertyBehavior::new(x[0], x[1], x[2], x[3], s.m as u64, s.flag as u64)
|
||||
};
|
||||
self.float(r.density);
|
||||
self.float(r.face_thickness);
|
||||
self.float(r.edge_cross_section_radius);
|
||||
self.float(r.vertex_sphere_radius);
|
||||
self.int(r.face_area_behavior as i64);
|
||||
self.int(r.face_volume_behavior as i64);
|
||||
}
|
||||
op::API => {
|
||||
ctxmod::create_context(&mut self.api);
|
||||
let p: *const Context = ctxmod::get_or_create_context(&mut self.api);
|
||||
let q: *const Context = ctxmod::get_or_create_context(&mut self.api);
|
||||
self.int(1);
|
||||
self.int(i64::from(std::ptr::eq(p, q)));
|
||||
}
|
||||
other => return Err(harness(format!("op {other}"))),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn rust(i: &Inputs) -> Result<Output, HarnessError> {
|
||||
let steps = i.steps();
|
||||
// reject what the C++ wrapper rejects, so both sides fail alike
|
||||
ffi::validate(i.contexts.len(), i.slots, &steps).map_err(harness)?;
|
||||
let mut sim = Sim {
|
||||
k: kernel(i.sys_model, i.sys_ratio),
|
||||
ctxs: i.contexts.iter().map(|c| Context::new(c[0], c[1])).collect(),
|
||||
slots: vec![None; i.slots],
|
||||
out: Vec::new(),
|
||||
api: None,
|
||||
};
|
||||
for s in &steps {
|
||||
sim.step(s)?;
|
||||
sim.snapshot();
|
||||
}
|
||||
for s in 0..i.slots {
|
||||
sim.drop_slot(s as i64)?;
|
||||
}
|
||||
sim.snapshot();
|
||||
Ok(output(&sim.out))
|
||||
}
|
||||
|
||||
// ---- generation --------------------------------------------------------------------
|
||||
|
||||
/// A model size estimate or ratio: the default sentinel, table boundaries, or
|
||||
/// log-uniform values over many decades (sometimes negative).
|
||||
fn size(rng: &mut Rng) -> f64 {
|
||||
const EDGES: [f64; 9] = [0.00005, 0.005, 0.5, 500.0, 50000.0, 5e6, 50.0, 1000.0, 1.0];
|
||||
let r = rng.unit();
|
||||
if r < 0.35 {
|
||||
tol::USE_DEFAULT
|
||||
} else if r < 0.6 {
|
||||
rng.choose(&EDGES).copied().unwrap_or(50.0)
|
||||
} else {
|
||||
let v = 10f64.powf(rng.uniform(-6.0, 8.0));
|
||||
if rng.chance(0.1) {
|
||||
-v
|
||||
} else {
|
||||
v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_step(rng: &mut Rng, n_ctx: i64, n_slots: i64) -> StepIn {
|
||||
const WEIGHTS: [(i64, u64); 20] = [
|
||||
(op::CUR_MARK, 3),
|
||||
(op::IS_LOCKED, 2),
|
||||
(op::LOCK_NEW, 8),
|
||||
(op::LOCK_PAIR, 4),
|
||||
(op::LOCK_DEFAULT, 1),
|
||||
(op::LOCK_NEWMARK, 4),
|
||||
(op::LOCK_SETCTX, 3),
|
||||
(op::LOCK_SETCTX_PAIR, 2),
|
||||
(op::LOCK_DROP, 6),
|
||||
(op::LOCK_INFO, 2),
|
||||
(op::DOING_BOOL, 2),
|
||||
(op::GET_MODEL, 2),
|
||||
(op::SET_MODEL, 2),
|
||||
(op::SET_RATIO, 1),
|
||||
(op::TOL_CTX, 2),
|
||||
(op::CALC_EDGE, 1),
|
||||
(op::CALC_VERTEX, 1),
|
||||
(op::CALLBACK, 1),
|
||||
(op::MASS, 1),
|
||||
(op::API, 1),
|
||||
];
|
||||
let total: u64 = WEIGHTS.iter().map(|w| w.1).sum();
|
||||
let mut r = rng.below(total);
|
||||
let mut code = op::CUR_MARK;
|
||||
for (c, w) in WEIGHTS {
|
||||
if r < w {
|
||||
code = c;
|
||||
break;
|
||||
}
|
||||
r -= w;
|
||||
}
|
||||
let mut s = StepIn {
|
||||
op: code,
|
||||
slot: rng.range_i64(0, n_slots - 1),
|
||||
ctx: rng.range_i64(0, n_ctx - 1),
|
||||
ctx2: rng.range_i64(0, n_ctx - 1),
|
||||
..StepIn::default()
|
||||
};
|
||||
let mark_type = |rng: &mut Rng| rng.choose(&ffi::MARK_TYPES).copied().unwrap_or(0);
|
||||
match code {
|
||||
op::CUR_MARK | op::IS_LOCKED | op::LOCK_SETCTX => s.m = mark_type(rng),
|
||||
op::LOCK_NEW => {
|
||||
s.m = if rng.chance(0.6) {
|
||||
ctxmod::ALL_MARKS.into()
|
||||
} else if rng.chance(0.1) {
|
||||
rng.range_i64(-40, 100)
|
||||
} else {
|
||||
rng.range_i64(0, 31)
|
||||
}
|
||||
}
|
||||
op::DOING_BOOL | op::CALLBACK => {
|
||||
s.m = rng.range_i64(0, 1);
|
||||
s.flag = rng.range_i64(0, 1);
|
||||
}
|
||||
op::SET_MODEL => {
|
||||
s.flag = rng.range_i64(0, 1);
|
||||
s.x[0] = size(rng);
|
||||
}
|
||||
op::SET_RATIO => s.x[0] = size(rng),
|
||||
op::TOL_CTX | op::CALC_EDGE | op::CALC_VERTEX => {
|
||||
if rng.chance(0.25) {
|
||||
s.ctx = -1;
|
||||
}
|
||||
s.m = rng.range_i64(0, 4);
|
||||
s.flag = rng.range_i64(0, 1);
|
||||
s.x = [
|
||||
rng.uniform(0.0, 1e-3),
|
||||
rng.uniform(0.0, 1e-3),
|
||||
rng.uniform(-1e-4, 1e-3),
|
||||
0.0,
|
||||
];
|
||||
}
|
||||
op::MASS => {
|
||||
s.slot = i64::from(rng.chance(0.2));
|
||||
s.m = rng.range_i64(0, 2);
|
||||
s.flag = rng.range_i64(0, 1);
|
||||
s.x = [
|
||||
rng.uniform(0.0, 10.0),
|
||||
rng.uniform(0.0, 1.0),
|
||||
rng.uniform(0.0, 1.0),
|
||||
rng.uniform(0.0, 1.0),
|
||||
];
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
impl UnitDiff for Runner {
|
||||
fn id(&self) -> &'static str {
|
||||
"geom.context"
|
||||
}
|
||||
|
||||
fn cases(&self, ctx: &CaseContext) -> Result<Vec<Case>, HarnessError> {
|
||||
let ctx = CaseContext {
|
||||
seeds: ctx.seeds.max(MIN_GENERATED),
|
||||
..ctx.clone()
|
||||
};
|
||||
default_cases(self, &ctx)
|
||||
}
|
||||
|
||||
fn run_cpp(&self, case: &Case) -> Result<Output, HarnessError> {
|
||||
cpp(&case.parse()?)
|
||||
}
|
||||
|
||||
fn run_rust(&self, case: &Case) -> Result<Output, HarnessError> {
|
||||
rust(&case.parse()?)
|
||||
}
|
||||
|
||||
fn generate(&self, rng: &mut Rng) -> Option<Value> {
|
||||
let n_ctx = rng.range_i64(1, 3);
|
||||
let n_slots = rng.range_i64(1, 6);
|
||||
let n_steps = rng.range_i64(1, 40);
|
||||
let contexts = (0..n_ctx).map(|_| [size(rng), size(rng)]).collect();
|
||||
let steps = (0..n_steps)
|
||||
.map(|_| gen_step(rng, n_ctx, n_slots))
|
||||
.collect();
|
||||
serde_json::to_value(Inputs {
|
||||
sys_model: size(rng),
|
||||
sys_ratio: size(rng),
|
||||
contexts,
|
||||
slots: n_slots as usize,
|
||||
steps,
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
}
|
||||
@@ -269,18 +269,20 @@ fn rust_values(op: Op, i: &Inputs) -> Result<solidcore::Result<Vec<f64>>, Harnes
|
||||
Ok(match op {
|
||||
Op::SizeEstimate => v1(tol::size_estimate_to_zone_tol_3d(a(0))),
|
||||
Op::InTol => v1(flag(tol::in_tol(a(0), tol::tol3d(a(1))))),
|
||||
Op::CalcFaceZone => v1(tol::calc_face_zone_tol_3d(&k)),
|
||||
Op::CalcFaceZone => v1(tol::calc_face_zone_tol_3d(&k, None)),
|
||||
Op::CalcEdgeZone => v1(tol::calc_edge_zone_tol_3d(
|
||||
&k,
|
||||
a(0),
|
||||
a(1),
|
||||
i.flag.then(|| tol::zone_tol3d(&k, a(2))),
|
||||
None,
|
||||
)),
|
||||
Op::CalcVertexZone => v1(tol::calc_vertex_zone_tol_3d(
|
||||
&k,
|
||||
a(0),
|
||||
a(1),
|
||||
i.flag.then(|| tol::zone_tol3d(&k, a(2))),
|
||||
None,
|
||||
)),
|
||||
Op::EffZeros => Ok(vec![
|
||||
tol::eff_zero(),
|
||||
@@ -313,11 +315,11 @@ fn rust_values(op: Op, i: &Inputs) -> Result<solidcore::Result<Vec<f64>>, Harnes
|
||||
))),
|
||||
Op::GapGain => v1(tol::gap_gain(&k)),
|
||||
Op::ZoneSys => v1(tol::zone_tol_3d(&k, small)),
|
||||
Op::ZoneCtx => v1(tol::context_zone_tol_3d(&k, small)),
|
||||
Op::ZoneCtx => v1(tol::context_zone_tol_3d(&k, None, small)),
|
||||
Op::ApproxSys => v1(tol::approx_tol_3d(&k, small)),
|
||||
Op::ApproxCtx => v1(tol::context_approx_tol_3d(&k, small)),
|
||||
Op::ApproxCtx => v1(tol::context_approx_tol_3d(&k, None, small)),
|
||||
Op::XSectSys => v1(tol::xsect_tol_3d(&k, small)),
|
||||
Op::XSectCtx => v1(tol::context_xsect_tol_3d(&k, small)),
|
||||
Op::XSectCtx => v1(tol::context_xsect_tol_3d(&k, None, small)),
|
||||
Op::ScaledZeroSys => Ok(vec![
|
||||
tol::scaled_zero(),
|
||||
tol::scaled_zero_sq(),
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
//! FFI wrappers for unit `geom.context`.
|
||||
//!
|
||||
//! Shim: `harness/cpp/shim/geom_context.cpp`.
|
||||
//! Upstream: `source/SMLib/inc/SmContext.h`, `source/SMLib/src/SmContext.cpp`
|
||||
//! (`SmContext`, `SmNewMarkAndLock`), the `SmContext` overloads of `SmTol`,
|
||||
//! `SmMassPropertyBehavior` (`SmTopoTypes.h`) and `SmApiCreateContext` /
|
||||
//! `SmApiGetOrCreateContext` (`SmApiGeneral.cpp`).
|
||||
//!
|
||||
//! | Rust | shim | upstream |
|
||||
//! |------------|--------------------------|--------------------------------------------|
|
||||
//! | [`script`] | `sc_geom_context_script` | a script of `SmContext` / `SmNewMarkAndLock` calls |
|
||||
|
||||
use crate::{call_with_buffer, FfiError};
|
||||
|
||||
mod sys {
|
||||
extern "C" {
|
||||
pub fn sc_geom_context_script(
|
||||
sys_model: f64,
|
||||
sys_ratio: f64,
|
||||
ctx_params: *const f64,
|
||||
n_ctx: i64,
|
||||
ops: *const i64,
|
||||
xs: *const f64,
|
||||
n_ops: i64,
|
||||
n_slots: i64,
|
||||
out: *mut i64,
|
||||
cap: i64,
|
||||
out_len: *mut i64,
|
||||
) -> i32;
|
||||
}
|
||||
}
|
||||
|
||||
/// Script operation codes (field use in brackets; see the shim for details).
|
||||
pub mod op {
|
||||
/// `GetCurrentMark(m)` [ctx, m].
|
||||
pub const CUR_MARK: i64 = 0;
|
||||
/// `IsMarkLocked(m)` [ctx, m].
|
||||
pub const IS_LOCKED: i64 = 1;
|
||||
/// `slot = new SmNewMarkAndLock(ctx, mask m)` [slot, ctx, m].
|
||||
pub const LOCK_NEW: i64 = 2;
|
||||
/// `slot = new SmNewMarkAndLock(ctx, ctx2)` [slot, ctx, ctx2].
|
||||
pub const LOCK_PAIR: i64 = 3;
|
||||
/// `slot = new SmNewMarkAndLock()` [slot].
|
||||
pub const LOCK_DEFAULT: i64 = 4;
|
||||
/// `slot->NewMark()` when bound [slot].
|
||||
pub const LOCK_NEWMARK: i64 = 5;
|
||||
/// `slot->SetContext(ctx, m)` [slot, ctx, m].
|
||||
pub const LOCK_SETCTX: i64 = 6;
|
||||
/// `slot->SetContext(ctx, ctx2)` [slot, ctx, ctx2].
|
||||
pub const LOCK_SETCTX_PAIR: i64 = 7;
|
||||
/// `delete slot` [slot].
|
||||
pub const LOCK_DROP: i64 = 8;
|
||||
/// `GetMarkType*`, `GetContext*` [slot].
|
||||
pub const LOCK_INFO: i64 = 9;
|
||||
/// `SetDoingBoolean(m)` or `GetDoingBooleanRef() = m` [ctx, m, flag].
|
||||
pub const DOING_BOOL: i64 = 10;
|
||||
/// `GetThisModelSizeEstimate()`, `SmTol::GetModelSizeEstimate(ctx)` [ctx].
|
||||
pub const GET_MODEL: i64 = 11;
|
||||
/// `SetThisModelSizeEstimate(x0)` or `SmTol::SetModelSizeEstimate(ctx, x0)` [ctx, flag].
|
||||
pub const SET_MODEL: i64 = 12;
|
||||
/// `SetThisLargeSmallSizeRatio(x0)` [ctx].
|
||||
pub const SET_RATIO: i64 = 13;
|
||||
/// `SmTol::GetZoneTol3d/GetApproxTol3d/GetXSectTol3d(ctx, m)`, `CalcFaceZoneTol3d(ctx)` [ctx|-1, m].
|
||||
pub const TOL_CTX: i64 = 14;
|
||||
/// `SmTol::CalcEdgeZoneTol3d(x0, x1, flag ? &x2 : NULL, ctx)` [ctx|-1, flag].
|
||||
pub const CALC_EDGE: i64 = 15;
|
||||
/// `SmTol::CalcVertexZoneTol3d(x0, x1, flag ? &x2 : NULL, ctx)` [ctx|-1, flag].
|
||||
pub const CALC_VERTEX: i64 = 16;
|
||||
/// `SetSysNotifyCallback` (flag 0) / `SetUserNotifyCallback` (flag 1), non-null if m [ctx, m, flag].
|
||||
pub const CALLBACK: i64 = 17;
|
||||
/// `SmMassPropertyBehavior(x0..x3, m, flag)`, or the default one if slot != 0.
|
||||
pub const MASS: i64 = 18;
|
||||
/// `SmApiCreateContext(); SmApiGetOrCreateContext()`.
|
||||
pub const API: i64 = 19;
|
||||
/// One past the last code.
|
||||
pub const COUNT: i64 = 20;
|
||||
}
|
||||
|
||||
/// The `SmMarkType` values.
|
||||
pub const MARK_TYPES: [i64; 7] = [0, 1, 2, 4, 8, 16, 31];
|
||||
|
||||
/// Maximum number of contexts in a script.
|
||||
pub const MAX_CONTEXTS: usize = 8;
|
||||
/// Maximum number of lock slots in a script.
|
||||
pub const MAX_SLOTS: usize = 16;
|
||||
|
||||
/// One script operation.
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq)]
|
||||
pub struct Step {
|
||||
/// Operation code ([`op`]).
|
||||
pub op: i64,
|
||||
/// Lock slot (or the "default" switch of [`op::MASS`]).
|
||||
pub slot: i64,
|
||||
/// Context index (`-1` = null where allowed).
|
||||
pub ctx: i64,
|
||||
/// Second context index.
|
||||
pub ctx2: i64,
|
||||
/// Mark type / mask / small-topology type / boolean / count operand.
|
||||
pub m: i64,
|
||||
/// Variant switch.
|
||||
pub flag: i64,
|
||||
/// Float operands.
|
||||
pub x: [f64; 4],
|
||||
}
|
||||
|
||||
/// One output item.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Item {
|
||||
/// An integer result.
|
||||
Int(i64),
|
||||
/// A floating-point result.
|
||||
Float(f64),
|
||||
}
|
||||
|
||||
/// Checks a script the way the shim does (anything it would reject or crash on).
|
||||
pub fn validate(n_ctx: usize, n_slots: usize, steps: &[Step]) -> Result<(), FfiError> {
|
||||
let bad = |m: &str| Err(FfiError::InvalidArgument(m.into()));
|
||||
if !(1..=MAX_CONTEXTS).contains(&n_ctx) {
|
||||
return bad("context count");
|
||||
}
|
||||
if n_slots > MAX_SLOTS {
|
||||
return bad("slot count");
|
||||
}
|
||||
let n_ctx = n_ctx as i64;
|
||||
let n_slots = n_slots as i64;
|
||||
for s in steps {
|
||||
let o = s.op;
|
||||
if !(0..op::COUNT).contains(&o) {
|
||||
return bad("op code");
|
||||
}
|
||||
let need_slot = (op::LOCK_NEW..=op::LOCK_INFO).contains(&o);
|
||||
let null_ok = matches!(o, op::TOL_CTX | op::CALC_EDGE | op::CALC_VERTEX);
|
||||
let no_ctx = matches!(
|
||||
o,
|
||||
op::LOCK_DEFAULT | op::LOCK_NEWMARK | op::LOCK_DROP | op::LOCK_INFO | op::MASS | op::API
|
||||
);
|
||||
if need_slot && !(0..n_slots).contains(&s.slot) {
|
||||
return bad("slot index");
|
||||
}
|
||||
let ctx_ok = (0..n_ctx).contains(&s.ctx) || (null_ok && s.ctx == -1);
|
||||
if !no_ctx && !ctx_ok {
|
||||
return bad("context index");
|
||||
}
|
||||
if matches!(o, op::LOCK_PAIR | op::LOCK_SETCTX_PAIR) && !(0..n_ctx).contains(&s.ctx2) {
|
||||
return bad("second context index");
|
||||
}
|
||||
if matches!(o, op::CUR_MARK | op::IS_LOCKED | op::LOCK_SETCTX) && !MARK_TYPES.contains(&s.m)
|
||||
{
|
||||
return bad("mark type");
|
||||
}
|
||||
if o == op::DOING_BOOL && !(0..=1).contains(&s.m) {
|
||||
return bad("boolean");
|
||||
}
|
||||
if o == op::TOL_CTX && !(0..=4).contains(&s.m) {
|
||||
return bad("small topology type");
|
||||
}
|
||||
if o == op::MASS && (s.m < 0 || s.flag < 0) {
|
||||
return bad("behavior");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Runs `steps` in upstream C++ on `contexts` (model size, ratio) with the system
|
||||
/// tolerances set from (`sys_model`, `sys_ratio`) and reset afterwards. Returns per
|
||||
/// step its results and a snapshot of every context, then a final snapshot after the
|
||||
/// live locks are destroyed in slot order.
|
||||
pub fn script(
|
||||
sys_model: f64,
|
||||
sys_ratio: f64,
|
||||
contexts: &[(f64, f64)],
|
||||
n_slots: usize,
|
||||
steps: &[Step],
|
||||
) -> Result<Vec<Item>, FfiError> {
|
||||
validate(contexts.len(), n_slots, steps)?;
|
||||
let params: Vec<f64> = contexts.iter().flat_map(|&(a, b)| [a, b]).collect();
|
||||
let ops: Vec<i64> = steps
|
||||
.iter()
|
||||
.flat_map(|s| [s.op, s.slot, s.ctx, s.ctx2, s.m, s.flag])
|
||||
.collect();
|
||||
let xs: Vec<f64> = steps.iter().flat_map(|s| s.x).collect();
|
||||
let raw = call_with_buffer(1024, |buf: &mut [i64], len| {
|
||||
// SAFETY: params/ops/xs hold 2*n_ctx, 6*n_ops and 4*n_ops values; the script
|
||||
// was validated above; buf is valid for buf.len() writes and len for one.
|
||||
unsafe {
|
||||
sys::sc_geom_context_script(
|
||||
sys_model,
|
||||
sys_ratio,
|
||||
params.as_ptr(),
|
||||
contexts.len() as i64,
|
||||
ops.as_ptr(),
|
||||
xs.as_ptr(),
|
||||
steps.len() as i64,
|
||||
n_slots as i64,
|
||||
buf.as_mut_ptr(),
|
||||
buf.len() as i64,
|
||||
&raw mut *len,
|
||||
)
|
||||
}
|
||||
})?;
|
||||
raw.chunks(2)
|
||||
.map(|c| match c {
|
||||
[0, v] => Ok(Item::Int(*v)),
|
||||
[1, v] => Ok(Item::Float(f64::from_bits(*v as u64))),
|
||||
_ => Err(FfiError::InvalidArgument("malformed shim output".into())),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
//! `SmPseudoBox`, `SmTol`, `SmTransform`, `SmMatrix` and related `geom.*` units of
|
||||
//! `plan/modules.toml`.
|
||||
|
||||
pub mod context;
|
||||
pub mod extent3d;
|
||||
pub mod tol;
|
||||
pub mod vector2d;
|
||||
|
||||
@@ -0,0 +1,814 @@
|
||||
//! Per-session modeling context (`SmContext`): traversal marks and their locks, the
|
||||
//! "doing a Boolean" flag, notify callbacks and the context's own model size estimate
|
||||
//! and zone tolerance; plus the mark-locking helper `SmNewMarkAndLock`, the
|
||||
//! `SmMassPropertyBehavior` options and the `SM_API` context accessors.
|
||||
//!
|
||||
//! Upstream: `source/SMLib/inc/SmContext.h`, `source/SMLib/src/SmContext.cpp`,
|
||||
//! `source/SMLib/inc/SmTopoTypes.h` (`SmMassPropertyBehavior`),
|
||||
//! `source/SM_API/src/SmApiGeneral.cpp` (`SmApiCreateContext`, `SmApiGetOrCreateContext`).
|
||||
//!
|
||||
//! | Rust | Upstream |
|
||||
//! |-------------------------------------------|-------------------------------------------------|
|
||||
//! | [`Context`] | `SmContext` |
|
||||
//! | [`Context::new`], [`Context::default`] | `SmContext::SmContext(double, double)` (no `SM_USE_GLOBAL_CACHE`) |
|
||||
//! | [`Context::doing_boolean`] | `SmContext::GetDoingBoolean` |
|
||||
//! | [`Context::doing_boolean_mut`] | `SmContext::GetDoingBooleanRef` |
|
||||
//! | [`Context::set_doing_boolean`] | `SmContext::SetDoingBoolean` |
|
||||
//! | [`Context::user_notify_callback`] | `SmContext::GetUserNotifyCallback` |
|
||||
//! | [`Context::set_user_notify_callback`] | `SmContext::SetUserNotifyCallback` (unused upstream) |
|
||||
//! | [`Context::sys_notify_callback`] | `SmContext::GetSysNotifyCallback` |
|
||||
//! | [`Context::set_sys_notify_callback`] | `SmContext::SetSysNotifyCallback` |
|
||||
//! | [`Context::this_model_size_estimate`] | `SmContext::GetThisModelSizeEstimate` |
|
||||
//! | [`Context::set_this_model_size_estimate`] | `SmContext::SetThisModelSizeEstimate` (inline, `SM_USE_OLDTOL` build) |
|
||||
//! | [`Context::set_this_large_small_size_ratio`] | `SmContext::SetThisLargeSmallSizeRatio` |
|
||||
//! | [`Context::this_zone_tol_3d`] | `SmContext::m_sThisZoneTol3d` (`GetThisZoneTol3d`, unused upstream) |
|
||||
//! | [`Context::stored_model_size_estimate`], [`Context::stored_large_small_size_ratio`] | the raw members `m_dThisModelSizeEstimate`, `m_dThisLargeSmallSizeRatio` |
|
||||
//! | [`Context::current_mark`] | `SmContext::GetCurrentMark` |
|
||||
//! | [`Context::is_mark_locked`] | `SmContext::IsMarkLocked` |
|
||||
//! | `Context::new_mark` (private) | `SmContext::NewMark` (private, friend `SmNewMarkAndLock`) |
|
||||
//! | [`MarkType`] | `SmMarkType` |
|
||||
//! | [`MarkLock`] | `SmNewMarkAndLock` |
|
||||
//! | [`MarkLock::new`] | `SmNewMarkAndLock(const SmContext*, SmBitArray)` |
|
||||
//! | [`MarkLock::new_pair`] | `SmNewMarkAndLock(SmContext*, SmContext*)` |
|
||||
//! | [`MarkLock::unbound`], [`MarkLock::default`] | `SmNewMarkAndLock()` |
|
||||
//! | [`MarkLock::release`] | `SmNewMarkAndLock::~SmNewMarkAndLock` |
|
||||
//! | [`MarkLock::mark_type`], [`MarkLock::mark_type1`], [`MarkLock::mark_type2`] | `GetMarkType`, `GetMarkType1`, `GetMarkType2` |
|
||||
//! | [`MarkLock::context`] | `SmNewMarkAndLock::GetContext` |
|
||||
//! | [`MarkLock::new_mark`] | `SmNewMarkAndLock::NewMark` |
|
||||
//! | [`MarkLock::set_context`] | `SmNewMarkAndLock::SetContext(SmContext*, SmMarkType)` |
|
||||
//! | [`MarkLock::set_context_pair`] | `SmNewMarkAndLock::SetContext(SmContext*, SmContext*)` |
|
||||
//! | `lock_mark`, `unlock_mark` (private) | `SmNewMarkAndLock::LockMark`, `UnlockMark` |
|
||||
//! | `unlocked_mark` (private) | `SmNewMarkAndLock::GetUnlockedMark` |
|
||||
//! | [`with_mark_lock`] | the scoped `{ SmNewMarkAndLock sMarkLock(pContext, mask); ... }` idiom |
|
||||
//! | [`MassPropertyBehavior`] | `SmMassPropertyBehavior` (and its constructor) |
|
||||
//! | [`create_context`] | `SmApiCreateContext` |
|
||||
//! | [`get_or_create_context`] | `SmApiGetOrCreateContext` |
|
||||
//!
|
||||
//! Deviations:
|
||||
//! - `SmNewMarkAndLock` holds `SmContext*` pointers and unlocks in its destructor.
|
||||
//! [`MarkLock`] instead stores the *indices* of its contexts in a caller-owned slice
|
||||
//! `&mut [Context]` (index equality plays pointer equality) and every method that
|
||||
//! touches a context takes that slice; the destructor is the explicit
|
||||
//! [`MarkLock::release`]. [`with_mark_lock`] gives the scoped form for a
|
||||
//! [`Kernel`]'s own context. A method that upstream would run through a null
|
||||
//! context pointer (a crash) returns [`SolidError::InvalidInput`], as does an index
|
||||
//! outside the slice.
|
||||
//! - The upstream two-context constructor locks the *first* context's mark type in the
|
||||
//! second context and increments the first context's mark twice; this is ported
|
||||
//! as is (see `plan/findings.md`).
|
||||
//! - `SM_ASSERT*`, `SM_ASSERT_MSG_BREAK` and `SM_DBG_WARN` only print messages in the
|
||||
//! release build and are not ported. `SmBoolean` flags are `bool`; `ULONG` marks are
|
||||
//! `u64` incremented with wrapping arithmetic, as in C.
|
||||
//! - Notify callbacks are boxed [`NotifyCallback`] trait objects owned by the context
|
||||
//! (upstream stores borrowed raw pointers). `Execute` is added to the trait by the
|
||||
//! unit that ports `SmObject::Notify`.
|
||||
//! - `SmApiGeneral.cpp` keeps one process-global `SmContext*`; the port takes the slot
|
||||
//! (`&mut Option<Context>`) from the caller.
|
||||
//! - The attribute-callback list, global user data (`void*`), `operator new/delete`,
|
||||
//! `Dump`, `GetTypeString` and `IsLockedMark` are unused upstream and not ported.
|
||||
|
||||
use crate::error::{Result, SolidError};
|
||||
use crate::geom::tol;
|
||||
use crate::kernel::Kernel;
|
||||
|
||||
/// Which traversal mark of a context to use (`SmMarkType`). The discriminants are the
|
||||
/// upstream bit values.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(i32)]
|
||||
pub enum MarkType {
|
||||
/// `SM_MT_NOMARK`: no mark (also the unset value of a [`MarkLock`]).
|
||||
NoMark = 0,
|
||||
/// `SM_MT_MARK`: the general mark.
|
||||
Mark = 1,
|
||||
/// `SM_MT_MARK2`: the second general mark.
|
||||
Mark2 = 2,
|
||||
/// `SM_MT_MARK3`: the third general mark.
|
||||
Mark3 = 4,
|
||||
/// `SM_MT_MARKIO`: the mark used by draw / IO methods.
|
||||
MarkIo = 8,
|
||||
/// `SM_MT_MARKASSERT`: the mark used by validity checks.
|
||||
MarkAssert = 16,
|
||||
/// `SM_MT_ALLMARKS`: all marks; as a single mark it behaves like an unknown
|
||||
/// value (the `default:` branches upstream).
|
||||
AllMarks = 31,
|
||||
}
|
||||
|
||||
impl MarkType {
|
||||
/// The upstream enum value (also the bit of the mark in a mark mask).
|
||||
#[must_use]
|
||||
pub const fn code(self) -> i32 {
|
||||
self as i32
|
||||
}
|
||||
|
||||
/// The mark type with upstream value `code`, if any.
|
||||
#[must_use]
|
||||
pub const fn from_code(code: i32) -> Option<Self> {
|
||||
match code {
|
||||
0 => Some(Self::NoMark),
|
||||
1 => Some(Self::Mark),
|
||||
2 => Some(Self::Mark2),
|
||||
4 => Some(Self::Mark3),
|
||||
8 => Some(Self::MarkIo),
|
||||
16 => Some(Self::MarkAssert),
|
||||
31 => Some(Self::AllMarks),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Index of the mark's counter and lock in a [`Context`]; `None` for
|
||||
/// [`MarkType::NoMark`] and [`MarkType::AllMarks`].
|
||||
const fn slot(self) -> Option<usize> {
|
||||
match self {
|
||||
Self::Mark => Some(0),
|
||||
Self::Mark2 => Some(1),
|
||||
Self::Mark3 => Some(2),
|
||||
Self::MarkIo => Some(3),
|
||||
Self::MarkAssert => Some(4),
|
||||
Self::NoMark | Self::AllMarks => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark mask selecting every mark (`SM_MT_ALLMARKS`).
|
||||
pub const ALL_MARKS: i32 = MarkType::AllMarks as i32;
|
||||
|
||||
/// Index of `SM_MT_MARKIO` in the mark arrays (the `default:` mark upstream).
|
||||
const MARK_IO: usize = 3;
|
||||
|
||||
/// Receiver of topology-change notifications (`SmNotifyCallback`).
|
||||
///
|
||||
/// The notification method (`SmNotifyCallback::Execute`) is added by the unit that
|
||||
/// ports `SmObject::Notify`.
|
||||
pub trait NotifyCallback: std::fmt::Debug + Send {}
|
||||
|
||||
/// Shared modeling state (`SmContext`): traversal marks, the Boolean flag, notify
|
||||
/// callbacks and the context's model size estimate and zone tolerance.
|
||||
#[derive(Debug)]
|
||||
pub struct Context {
|
||||
/// `m_lCurrentMark`, `m_lCurrentMark2`, `m_lCurrentMark3`, `m_lCurrentMarkIO`,
|
||||
/// `m_lCurrentMarkAssert`.
|
||||
current_marks: [u64; 5],
|
||||
/// `m_bMarkLock`, `m_bMark2Lock`, `m_bMark3Lock`, `m_bMarkIOLock`,
|
||||
/// `m_bMarkAssertLock`.
|
||||
mark_locks: [bool; 5],
|
||||
/// `m_bDoingBoolean`.
|
||||
doing_boolean: bool,
|
||||
/// `m_dThisModelSizeEstimate`.
|
||||
model_size_estimate: f64,
|
||||
/// `m_dThisLargeSmallSizeRatio`.
|
||||
large_small_size_ratio: f64,
|
||||
/// `m_sThisZoneTol3d`.
|
||||
zone_tol_3d: f64,
|
||||
/// `m_pUserNotifyCallback`.
|
||||
user_notify_callback: Option<Box<dyn NotifyCallback>>,
|
||||
/// `m_pSysNotifyCallBack`.
|
||||
sys_notify_callback: Option<Box<dyn NotifyCallback>>,
|
||||
}
|
||||
|
||||
impl Default for Context {
|
||||
/// `SmContext()`: both sizes [`tol::USE_DEFAULT`].
|
||||
fn default() -> Self {
|
||||
Self::new(tol::USE_DEFAULT, tol::USE_DEFAULT)
|
||||
}
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Creates a context (`SmContext::SmContext(double, double)`): every mark at 1 and
|
||||
/// unlocked, not in a Boolean, no callbacks; the model size estimate and its zone
|
||||
/// tolerance set by [`Context::set_this_model_size_estimate`]. Pass
|
||||
/// [`tol::USE_DEFAULT`] for the system values.
|
||||
#[must_use]
|
||||
pub fn new(model_size_estimate: f64, large_small_size_ratio: f64) -> Self {
|
||||
let mut c = Self {
|
||||
current_marks: [1; 5],
|
||||
mark_locks: [false; 5],
|
||||
doing_boolean: false,
|
||||
model_size_estimate: tol::USE_DEFAULT,
|
||||
large_small_size_ratio: tol::USE_DEFAULT,
|
||||
zone_tol_3d: tol::ZONE_TOL_3D,
|
||||
user_notify_callback: None,
|
||||
sys_notify_callback: None,
|
||||
};
|
||||
c.large_small_size_ratio = large_small_size_ratio;
|
||||
c.set_this_model_size_estimate(model_size_estimate);
|
||||
c
|
||||
}
|
||||
|
||||
/// Whether a Boolean operation is running (`GetDoingBoolean`).
|
||||
#[must_use]
|
||||
pub const fn doing_boolean(&self) -> bool {
|
||||
self.doing_boolean
|
||||
}
|
||||
|
||||
/// Mutable access to the Boolean flag (`GetDoingBooleanRef`).
|
||||
pub fn doing_boolean_mut(&mut self) -> &mut bool {
|
||||
&mut self.doing_boolean
|
||||
}
|
||||
|
||||
/// Sets the Boolean flag (`SetDoingBoolean`).
|
||||
pub fn set_doing_boolean(&mut self, doing_boolean: bool) {
|
||||
self.doing_boolean = doing_boolean;
|
||||
}
|
||||
|
||||
/// The application's notify callback (`GetUserNotifyCallback`).
|
||||
#[must_use]
|
||||
pub fn user_notify_callback(&self) -> Option<&dyn NotifyCallback> {
|
||||
self.user_notify_callback.as_deref()
|
||||
}
|
||||
|
||||
/// Sets the application's notify callback (`SetUserNotifyCallback`).
|
||||
pub fn set_user_notify_callback(&mut self, callback: Option<Box<dyn NotifyCallback>>) {
|
||||
self.user_notify_callback = callback;
|
||||
}
|
||||
|
||||
/// The system notify callback (`GetSysNotifyCallback`).
|
||||
#[must_use]
|
||||
pub fn sys_notify_callback(&self) -> Option<&dyn NotifyCallback> {
|
||||
self.sys_notify_callback.as_deref()
|
||||
}
|
||||
|
||||
/// Sets the system notify callback (`SetSysNotifyCallback`; upstream only warns
|
||||
/// in debug builds when one is already set).
|
||||
pub fn set_sys_notify_callback(&mut self, callback: Option<Box<dyn NotifyCallback>>) {
|
||||
self.sys_notify_callback = callback;
|
||||
}
|
||||
|
||||
/// The context's model size estimate, or the system one when it is
|
||||
/// [`tol::USE_DEFAULT`] (`GetThisModelSizeEstimate`).
|
||||
#[must_use]
|
||||
pub fn this_model_size_estimate(&self, k: &Kernel) -> f64 {
|
||||
if self.model_size_estimate == tol::USE_DEFAULT {
|
||||
tol::model_size_estimate(k)
|
||||
} else {
|
||||
self.model_size_estimate
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores a model size estimate and the zone tolerance it implies
|
||||
/// ([`tol::size_estimate_to_zone_tol_3d`]); returns that zone tolerance
|
||||
/// (`SetThisModelSizeEstimate`).
|
||||
pub fn set_this_model_size_estimate(&mut self, model_size_estimate: f64) -> f64 {
|
||||
self.model_size_estimate = model_size_estimate;
|
||||
self.zone_tol_3d = tol::size_estimate_to_zone_tol_3d(self.model_size_estimate);
|
||||
self.zone_tol_3d
|
||||
}
|
||||
|
||||
/// Stores the large/small size ratio (`SetThisLargeSmallSizeRatio`).
|
||||
pub fn set_this_large_small_size_ratio(&mut self, large_small_size_ratio: f64) {
|
||||
self.large_small_size_ratio = large_small_size_ratio;
|
||||
}
|
||||
|
||||
/// The stored model size estimate, possibly [`tol::USE_DEFAULT`]
|
||||
/// (`m_dThisModelSizeEstimate`).
|
||||
#[must_use]
|
||||
pub const fn stored_model_size_estimate(&self) -> f64 {
|
||||
self.model_size_estimate
|
||||
}
|
||||
|
||||
/// The stored large/small size ratio, possibly [`tol::USE_DEFAULT`]
|
||||
/// (`m_dThisLargeSmallSizeRatio`).
|
||||
#[must_use]
|
||||
pub const fn stored_large_small_size_ratio(&self) -> f64 {
|
||||
self.large_small_size_ratio
|
||||
}
|
||||
|
||||
/// The context's zone tolerance (`m_sThisZoneTol3d`).
|
||||
#[must_use]
|
||||
pub const fn this_zone_tol_3d(&self) -> f64 {
|
||||
self.zone_tol_3d
|
||||
}
|
||||
|
||||
/// The current value of a mark (`GetCurrentMark`): an object is marked when its
|
||||
/// mark equals this. [`MarkType::NoMark`] gives one more than the largest mark, so
|
||||
/// nothing is marked; [`MarkType::AllMarks`] gives the IO mark.
|
||||
#[must_use]
|
||||
pub fn current_mark(&self, mark_type: MarkType) -> u64 {
|
||||
match mark_type {
|
||||
MarkType::NoMark => {
|
||||
let [m1, m2, m3, mio, massert] = self.current_marks;
|
||||
1 + max5(m1, m2, m3, mio, massert)
|
||||
}
|
||||
_ => self
|
||||
.current_marks
|
||||
.get(mark_type.slot().unwrap_or(MARK_IO))
|
||||
.copied()
|
||||
.unwrap_or(0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Advances a mark by two and returns the new value (`SmContext::NewMark`; the
|
||||
/// lock check is a debug-only assertion upstream). [`MarkType::NoMark`] changes
|
||||
/// nothing and returns [`Context::current_mark`] of it.
|
||||
fn new_mark(&mut self, mark_type: MarkType) -> u64 {
|
||||
if mark_type == MarkType::NoMark {
|
||||
return self.current_mark(MarkType::NoMark);
|
||||
}
|
||||
match self
|
||||
.current_marks
|
||||
.get_mut(mark_type.slot().unwrap_or(MARK_IO))
|
||||
{
|
||||
Some(m) => {
|
||||
*m = m.wrapping_add(2);
|
||||
*m
|
||||
}
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a mark is locked (`IsMarkLocked`); never for [`MarkType::NoMark`] or
|
||||
/// [`MarkType::AllMarks`].
|
||||
#[must_use]
|
||||
pub fn is_mark_locked(&self, mark_type: MarkType) -> bool {
|
||||
mark_type
|
||||
.slot()
|
||||
.and_then(|s| self.mark_locks.get(s))
|
||||
.copied()
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Sets a mark's lock flag (no-op for marks without a slot).
|
||||
fn set_lock(&mut self, mark_type: MarkType, locked: bool) {
|
||||
if let Some(l) = mark_type.slot().and_then(|s| self.mark_locks.get_mut(s)) {
|
||||
*l = locked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `smos_5Max` = `smos_Max(smos_3Max(a, b, c), smos_Max(d, e))`.
|
||||
fn max5(a: u64, b: u64, c: u64, d: u64, e: u64) -> u64 {
|
||||
a.max(b).max(c).max(d.max(e))
|
||||
}
|
||||
|
||||
/// `SmNewMarkAndLock::LockMark`.
|
||||
fn lock_mark(ctx: &mut Context, mark_type: MarkType) {
|
||||
ctx.set_lock(mark_type, true);
|
||||
}
|
||||
|
||||
/// `SmNewMarkAndLock::UnlockMark`.
|
||||
fn unlock_mark(ctx: &mut Context, mark_type: MarkType) {
|
||||
ctx.set_lock(mark_type, false);
|
||||
}
|
||||
|
||||
/// The first unlocked mark selected by `mask` in the order mark, mark2, mark3, IO,
|
||||
/// assert; if all are locked, the mask itself when it names a single mark, else the IO
|
||||
/// mark (`SmNewMarkAndLock::GetUnlockedMark`).
|
||||
fn unlocked_mark(ctx: &Context, mask: i32) -> MarkType {
|
||||
const ORDER: [MarkType; 5] = [
|
||||
MarkType::Mark,
|
||||
MarkType::Mark2,
|
||||
MarkType::Mark3,
|
||||
MarkType::MarkIo,
|
||||
MarkType::MarkAssert,
|
||||
];
|
||||
if let Some(m) = ORDER
|
||||
.iter()
|
||||
.find(|m| (mask & m.code()) != 0 && !ctx.is_mark_locked(**m))
|
||||
{
|
||||
return *m;
|
||||
}
|
||||
ORDER
|
||||
.iter()
|
||||
.find(|m| mask == m.code())
|
||||
.copied()
|
||||
.unwrap_or(MarkType::MarkIo)
|
||||
}
|
||||
|
||||
const OP_LOCK: &str = "geom::context::MarkLock";
|
||||
|
||||
/// The context at `index` in `contexts`.
|
||||
fn ctx_at(contexts: &mut [Context], index: usize) -> Result<&mut Context> {
|
||||
contexts.get_mut(index).ok_or(SolidError::InvalidInput {
|
||||
operation: OP_LOCK,
|
||||
reason: "context index out of range",
|
||||
})
|
||||
}
|
||||
|
||||
/// Fails when a bound context index is outside `contexts`.
|
||||
fn check_index(contexts: &[Context], index: usize) -> Result<()> {
|
||||
if index < contexts.len() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(SolidError::InvalidInput {
|
||||
operation: OP_LOCK,
|
||||
reason: "context index out of range",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A claimed traversal mark (`SmNewMarkAndLock`): advances an unlocked mark of one or
|
||||
/// two contexts and locks it until [`MarkLock::release`].
|
||||
///
|
||||
/// Contexts are given as indices into a caller-owned `&mut [Context]` (see the module
|
||||
/// deviations); pass the same slice to every method.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct MarkLock {
|
||||
context1: Option<usize>,
|
||||
context2: Option<usize>,
|
||||
mark_type1: MarkType,
|
||||
mark_type2: MarkType,
|
||||
}
|
||||
|
||||
impl Default for MarkLock {
|
||||
fn default() -> Self {
|
||||
Self::unbound()
|
||||
}
|
||||
}
|
||||
|
||||
impl MarkLock {
|
||||
/// A lock bound to no context (`SmNewMarkAndLock()`).
|
||||
#[must_use]
|
||||
pub const fn unbound() -> Self {
|
||||
Self {
|
||||
context1: None,
|
||||
context2: None,
|
||||
mark_type1: MarkType::NoMark,
|
||||
mark_type2: MarkType::NoMark,
|
||||
}
|
||||
}
|
||||
|
||||
/// Picks the first unlocked mark of `contexts[ctx]` allowed by `mask` (usually
|
||||
/// [`ALL_MARKS`]), advances and locks it (`SmNewMarkAndLock(const SmContext*,
|
||||
/// SmBitArray)`).
|
||||
pub fn new(contexts: &mut [Context], ctx: usize, mask: i32) -> Result<Self> {
|
||||
let c = ctx_at(contexts, ctx)?;
|
||||
let mark_type1 = unlocked_mark(c, mask);
|
||||
c.new_mark(mark_type1);
|
||||
lock_mark(c, mark_type1);
|
||||
Ok(Self {
|
||||
context1: Some(ctx),
|
||||
context2: None,
|
||||
mark_type1,
|
||||
mark_type2: MarkType::NoMark,
|
||||
})
|
||||
}
|
||||
|
||||
/// Claims a mark in each of two contexts, e.g. the two operands of a Boolean
|
||||
/// (`SmNewMarkAndLock(SmContext*, SmContext*)`). When `ctx1 == ctx2` both marks
|
||||
/// are the same. Otherwise, as upstream, the second mark type is picked from
|
||||
/// `contexts[ctx2]` but the first mark is advanced again in `contexts[ctx1]` and
|
||||
/// the *first* mark type is locked in `contexts[ctx2]`.
|
||||
pub fn new_pair(contexts: &mut [Context], ctx1: usize, ctx2: usize) -> Result<Self> {
|
||||
check_index(contexts, ctx2)?;
|
||||
let c1 = ctx_at(contexts, ctx1)?;
|
||||
let mark_type1 = unlocked_mark(c1, ALL_MARKS);
|
||||
c1.new_mark(mark_type1);
|
||||
lock_mark(c1, mark_type1);
|
||||
let mark_type2 = if ctx1 == ctx2 {
|
||||
mark_type1
|
||||
} else {
|
||||
let m2 = unlocked_mark(ctx_at(contexts, ctx2)?, ALL_MARKS);
|
||||
ctx_at(contexts, ctx1)?.new_mark(mark_type1);
|
||||
lock_mark(ctx_at(contexts, ctx2)?, mark_type1);
|
||||
m2
|
||||
};
|
||||
Ok(Self {
|
||||
context1: Some(ctx1),
|
||||
context2: Some(ctx2),
|
||||
mark_type1,
|
||||
mark_type2,
|
||||
})
|
||||
}
|
||||
|
||||
/// Unlocks the claimed marks that are still locked and unbinds the lock
|
||||
/// (`~SmNewMarkAndLock`).
|
||||
pub fn release(&mut self, contexts: &mut [Context]) -> Result<()> {
|
||||
if let Some(i) = self.context1 {
|
||||
let c = ctx_at(contexts, i)?;
|
||||
if c.is_mark_locked(self.mark_type1) {
|
||||
unlock_mark(c, self.mark_type1);
|
||||
}
|
||||
}
|
||||
if let Some(i) = self.context2 {
|
||||
let c = ctx_at(contexts, i)?;
|
||||
if c.is_mark_locked(self.mark_type2) {
|
||||
unlock_mark(c, self.mark_type2);
|
||||
}
|
||||
}
|
||||
*self = Self::unbound();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The (first) claimed mark (`GetMarkType`).
|
||||
#[must_use]
|
||||
pub const fn mark_type(&self) -> MarkType {
|
||||
self.mark_type1
|
||||
}
|
||||
|
||||
/// The mark claimed in the first context (`GetMarkType1`).
|
||||
#[must_use]
|
||||
pub const fn mark_type1(&self) -> MarkType {
|
||||
self.mark_type1
|
||||
}
|
||||
|
||||
/// The mark claimed in the second context (`GetMarkType2`).
|
||||
#[must_use]
|
||||
pub const fn mark_type2(&self) -> MarkType {
|
||||
self.mark_type2
|
||||
}
|
||||
|
||||
/// Index of the (first) bound context (`GetContext`).
|
||||
#[must_use]
|
||||
pub const fn context(&self) -> Option<usize> {
|
||||
self.context1
|
||||
}
|
||||
|
||||
/// Index of the second bound context, if any (`GetContext2`).
|
||||
#[must_use]
|
||||
pub const fn context2(&self) -> Option<usize> {
|
||||
self.context2
|
||||
}
|
||||
|
||||
/// Advances the claimed mark(s) while keeping them locked, invalidating every
|
||||
/// mark set so far (`SmNewMarkAndLock::NewMark`).
|
||||
pub fn new_mark(&self, contexts: &mut [Context]) -> Result<()> {
|
||||
let i1 = self.context1.ok_or(SolidError::InvalidInput {
|
||||
operation: "geom::context::MarkLock::new_mark",
|
||||
reason: "no context bound",
|
||||
})?;
|
||||
ctx_at(contexts, i1)?.new_mark(self.mark_type1);
|
||||
if let Some(i2) = self.context2 {
|
||||
if i1 != i2 {
|
||||
ctx_at(contexts, i2)?.new_mark(self.mark_type2);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Rebinds the first context: unlocks the old first mark, then claims and locks
|
||||
/// (without advancing) the first unlocked mark of `contexts[ctx]` allowed by
|
||||
/// `mask`; returns it (`SmNewMarkAndLock::SetContext(SmContext*, SmMarkType)`).
|
||||
/// The second context binding is kept.
|
||||
pub fn set_context(
|
||||
&mut self,
|
||||
contexts: &mut [Context],
|
||||
ctx: usize,
|
||||
mask: i32,
|
||||
) -> Result<MarkType> {
|
||||
check_index(contexts, ctx)?;
|
||||
if let Some(i) = self.context1 {
|
||||
if self.mark_type1 != MarkType::NoMark {
|
||||
unlock_mark(ctx_at(contexts, i)?, self.mark_type1);
|
||||
}
|
||||
}
|
||||
self.context1 = Some(ctx);
|
||||
let c = ctx_at(contexts, ctx)?;
|
||||
self.mark_type1 = unlocked_mark(c, mask);
|
||||
lock_mark(c, self.mark_type1);
|
||||
Ok(self.mark_type1)
|
||||
}
|
||||
|
||||
/// Rebinds both contexts: unlocks the old marks, then claims and locks (without
|
||||
/// advancing) the first unlocked mark of each new context; returns the first
|
||||
/// (`SmNewMarkAndLock::SetContext(SmContext*, SmContext*)`).
|
||||
pub fn set_context_pair(
|
||||
&mut self,
|
||||
contexts: &mut [Context],
|
||||
ctx1: usize,
|
||||
ctx2: usize,
|
||||
) -> Result<MarkType> {
|
||||
check_index(contexts, ctx1)?;
|
||||
check_index(contexts, ctx2)?;
|
||||
if let Some(i) = self.context1 {
|
||||
if self.mark_type1 != MarkType::NoMark {
|
||||
unlock_mark(ctx_at(contexts, i)?, self.mark_type1);
|
||||
}
|
||||
}
|
||||
if let Some(i2) = self.context2 {
|
||||
if self.context1 != Some(i2) && self.mark_type2 != MarkType::NoMark {
|
||||
unlock_mark(ctx_at(contexts, i2)?, self.mark_type2);
|
||||
}
|
||||
}
|
||||
self.context1 = Some(ctx1);
|
||||
self.context2 = Some(ctx2);
|
||||
let c1 = ctx_at(contexts, ctx1)?;
|
||||
self.mark_type1 = unlocked_mark(c1, ALL_MARKS);
|
||||
lock_mark(c1, self.mark_type1);
|
||||
if ctx1 == ctx2 {
|
||||
self.mark_type2 = self.mark_type1;
|
||||
} else {
|
||||
let c2 = ctx_at(contexts, ctx2)?;
|
||||
self.mark_type2 = unlocked_mark(c2, ALL_MARKS);
|
||||
lock_mark(c2, self.mark_type2);
|
||||
}
|
||||
Ok(self.mark_type1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs `f` with a mark of the kernel's context claimed and locked, then releases it:
|
||||
/// the scoped `{ SmNewMarkAndLock sMarkLock(pContext, mask); ... }` idiom.
|
||||
pub fn with_mark_lock<R>(
|
||||
k: &mut Kernel,
|
||||
mask: i32,
|
||||
f: impl FnOnce(&mut Kernel, MarkType) -> R,
|
||||
) -> Result<R> {
|
||||
let mut lock = MarkLock::new(std::slice::from_mut(&mut k.context), 0, mask)?;
|
||||
let r = f(k, lock.mark_type());
|
||||
lock.release(std::slice::from_mut(&mut k.context))?;
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
/// How mass-property computations treat faces, edges and vertices
|
||||
/// (`SmMassPropertyBehavior`).
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct MassPropertyBehavior {
|
||||
/// Density of the material (`m_dDensity`, default 1).
|
||||
pub density: f64,
|
||||
/// Thickness given to faces (`m_dFaceThickness`, default 0).
|
||||
pub face_thickness: f64,
|
||||
/// Radius of the tube swept around edges (`m_dEdgeCrossSectionRadius`, default 0).
|
||||
pub edge_cross_section_radius: f64,
|
||||
/// Radius of the sphere around vertices (`m_dVertexSphereRadius`, default 0).
|
||||
pub vertex_sphere_radius: f64,
|
||||
/// Face area mode (`m_lFaceAreaBehavior`): 0 solid/void faces only, 1 every face
|
||||
/// once, 2 every face twice.
|
||||
pub face_area_behavior: u64,
|
||||
/// Face volume mode (`m_lFaceVolumeBehavior`): 0 region volumes, 1 thin shells.
|
||||
pub face_volume_behavior: u64,
|
||||
}
|
||||
|
||||
impl Default for MassPropertyBehavior {
|
||||
fn default() -> Self {
|
||||
Self::new(1.0, 0.0, 0.0, 0.0, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl MassPropertyBehavior {
|
||||
/// Builds the options (`SmMassPropertyBehavior::SmMassPropertyBehavior`).
|
||||
#[must_use]
|
||||
pub const fn new(
|
||||
density: f64,
|
||||
face_thickness: f64,
|
||||
edge_cross_section_radius: f64,
|
||||
vertex_sphere_radius: f64,
|
||||
face_area_behavior: u64,
|
||||
face_volume_behavior: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
density,
|
||||
face_thickness,
|
||||
edge_cross_section_radius,
|
||||
vertex_sphere_radius,
|
||||
face_area_behavior,
|
||||
face_volume_behavior,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the default context in `slot` unless one exists (`SmApiCreateContext`).
|
||||
pub fn create_context(slot: &mut Option<Context>) {
|
||||
if slot.is_none() {
|
||||
*slot = Some(Context::default());
|
||||
}
|
||||
}
|
||||
|
||||
/// The context in `slot`, created with defaults first if empty
|
||||
/// (`SmApiGetOrCreateContext`).
|
||||
pub fn get_or_create_context(slot: &mut Option<Context>) -> &mut Context {
|
||||
slot.get_or_insert_with(Context::default)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const ALL: [MarkType; 7] = [
|
||||
MarkType::NoMark,
|
||||
MarkType::Mark,
|
||||
MarkType::Mark2,
|
||||
MarkType::Mark3,
|
||||
MarkType::MarkIo,
|
||||
MarkType::MarkAssert,
|
||||
MarkType::AllMarks,
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn mark_type_codes_round_trip() {
|
||||
for m in ALL {
|
||||
assert_eq!(MarkType::from_code(m.code()), Some(m));
|
||||
}
|
||||
assert_eq!(MarkType::from_code(3), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_context_defaults() {
|
||||
let k = Kernel::default();
|
||||
let c = Context::default();
|
||||
for m in ALL {
|
||||
assert!(!c.is_mark_locked(m));
|
||||
}
|
||||
assert_eq!(c.current_mark(MarkType::Mark), 1);
|
||||
assert_eq!(c.current_mark(MarkType::NoMark), 2);
|
||||
assert_eq!(c.current_mark(MarkType::AllMarks), 1);
|
||||
assert!(!c.doing_boolean());
|
||||
assert_eq!(c.this_zone_tol_3d(), 1.0e-5);
|
||||
assert_eq!(c.this_model_size_estimate(&k), 50.0);
|
||||
assert!(c.user_notify_callback().is_none());
|
||||
assert!(c.sys_notify_callback().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn model_size_sets_zone_tol() {
|
||||
let k = Kernel::default();
|
||||
let mut c = Context::new(1000.0, tol::USE_DEFAULT);
|
||||
assert_eq!(c.this_zone_tol_3d(), 1.0e-4);
|
||||
assert_eq!(c.this_model_size_estimate(&k), 1000.0);
|
||||
assert_eq!(c.set_this_model_size_estimate(0.01), 1.0e-6);
|
||||
assert_eq!(c.set_this_model_size_estimate(tol::USE_DEFAULT), 1.0e-5);
|
||||
assert_eq!(c.this_model_size_estimate(&k), 50.0);
|
||||
c.set_this_large_small_size_ratio(10.0);
|
||||
*c.doing_boolean_mut() = true;
|
||||
assert!(c.doing_boolean());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_lock_cycle() {
|
||||
let mut cs = [Context::default()];
|
||||
let mut a = MarkLock::new(&mut cs, 0, ALL_MARKS).unwrap();
|
||||
assert_eq!(a.mark_type(), MarkType::Mark);
|
||||
assert_eq!(cs[0].current_mark(MarkType::Mark), 3);
|
||||
assert!(cs[0].is_mark_locked(MarkType::Mark));
|
||||
let mut b = MarkLock::new(&mut cs, 0, ALL_MARKS).unwrap();
|
||||
assert_eq!(b.mark_type(), MarkType::Mark2);
|
||||
b.new_mark(&mut cs).unwrap();
|
||||
assert_eq!(cs[0].current_mark(MarkType::Mark2), 5);
|
||||
b.release(&mut cs).unwrap();
|
||||
a.release(&mut cs).unwrap();
|
||||
assert!(!cs[0].is_mark_locked(MarkType::Mark));
|
||||
assert!(!cs[0].is_mark_locked(MarkType::Mark2));
|
||||
assert_eq!(cs[0].current_mark(MarkType::NoMark), 6);
|
||||
assert!(MarkLock::unbound().new_mark(&mut cs).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exhausted_marks_fall_back() {
|
||||
let mut cs = [Context::default()];
|
||||
let locks: Vec<MarkLock> = (0..5)
|
||||
.map(|_| MarkLock::new(&mut cs, 0, ALL_MARKS).unwrap())
|
||||
.collect();
|
||||
assert_eq!(locks[4].mark_type(), MarkType::MarkAssert);
|
||||
let x = MarkLock::new(&mut cs, 0, ALL_MARKS).unwrap();
|
||||
assert_eq!(x.mark_type(), MarkType::MarkIo);
|
||||
let y = MarkLock::new(&mut cs, 0, MarkType::Mark3.code()).unwrap();
|
||||
assert_eq!(y.mark_type(), MarkType::Mark3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pair_lock_upstream_quirk() {
|
||||
let mut cs = [Context::default(), Context::default()];
|
||||
let _first = MarkLock::new(&mut cs, 1, ALL_MARKS).unwrap();
|
||||
let mut p = MarkLock::new_pair(&mut cs, 0, 1).unwrap();
|
||||
assert_eq!(p.mark_type1(), MarkType::Mark);
|
||||
assert_eq!(p.mark_type2(), MarkType::Mark2);
|
||||
// context 0's mark advanced twice; context 1 gets mark type 1 locked
|
||||
assert_eq!(cs[0].current_mark(MarkType::Mark), 5);
|
||||
assert!(cs[1].is_mark_locked(MarkType::Mark));
|
||||
assert!(!cs[1].is_mark_locked(MarkType::Mark2));
|
||||
p.release(&mut cs).unwrap();
|
||||
assert!(!cs[0].is_mark_locked(MarkType::Mark));
|
||||
assert!(cs[1].is_mark_locked(MarkType::Mark));
|
||||
let same = MarkLock::new_pair(&mut cs, 0, 0).unwrap();
|
||||
assert_eq!(same.mark_type2(), same.mark_type1());
|
||||
assert!(MarkLock::new_pair(&mut cs, 0, 2).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_context_rebinds() {
|
||||
let mut cs = [Context::default(), Context::default()];
|
||||
let mut l = MarkLock::unbound();
|
||||
assert_eq!(
|
||||
l.set_context(&mut cs, 1, ALL_MARKS).unwrap(),
|
||||
MarkType::Mark
|
||||
);
|
||||
assert_eq!(cs[1].current_mark(MarkType::Mark), 1);
|
||||
assert_eq!(l.set_context_pair(&mut cs, 0, 1).unwrap(), MarkType::Mark);
|
||||
assert!(cs[0].is_mark_locked(MarkType::Mark));
|
||||
assert!(cs[1].is_mark_locked(MarkType::Mark));
|
||||
l.release(&mut cs).unwrap();
|
||||
assert!(!cs[0].is_mark_locked(MarkType::Mark));
|
||||
assert!(!cs[1].is_mark_locked(MarkType::Mark));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scoped_kernel_lock() {
|
||||
let mut k = Kernel::default();
|
||||
let m = with_mark_lock(&mut k, ALL_MARKS, |k, m| {
|
||||
assert!(k.context.is_mark_locked(m));
|
||||
m
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(m, MarkType::Mark);
|
||||
assert!(!k.context.is_mark_locked(m));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn api_context_slot() {
|
||||
let mut slot = None;
|
||||
create_context(&mut slot);
|
||||
get_or_create_context(&mut slot).set_doing_boolean(true);
|
||||
create_context(&mut slot);
|
||||
assert!(get_or_create_context(&mut slot).doing_boolean());
|
||||
let mp = MassPropertyBehavior::default();
|
||||
assert_eq!(mp.density, 1.0);
|
||||
assert_eq!(mp.face_area_behavior, 0);
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,8 @@
|
||||
//! | [`xsect_tol_3d_from_zones`] | `SmTol::GetXSectTol3d(const SmZoneTol3d&, const SmZoneTol3d&)` |
|
||||
//! | [`ang_tol_rad`] | `SmTol::GetAngTolRad()` |
|
||||
//! | [`model_size_estimate`] | `SmTol::GetModelSizeEstimate()` |
|
||||
//! | [`context_model_size_estimate`] | `SmTol::GetModelSizeEstimate(const SmContext&)` |
|
||||
//! | [`set_context_model_size_estimate`] | `SmTol::SetModelSizeEstimate(SmContext&, double)` |
|
||||
//! | [`are_parallel`] | `SmTol::AreParallel` |
|
||||
//! | [`are_perpendicular`] | `SmTol::ArePerpendicular` |
|
||||
//! | [`is_zero_speed`] | `SmTol::IsZeroSpeed` |
|
||||
@@ -56,17 +58,15 @@
|
||||
//! System state: upstream keeps the system tolerances in thread-local statics
|
||||
//! (`s_ZoneTol3d`, `s_TolGapGain`, `s_ModelSizeEstimate`, `s_LargeSmallSizeRatio`);
|
||||
//! here they are the [`Tolerances`] of the [`Kernel`] (`zone_tol_3d`, `tol_gap_gain`,
|
||||
//! `model_size_estimate`, `large_small_size_ratio`). The kernel also plays the
|
||||
//! `SmContext`, so the `const SmContext*` overloads are the `context_*` functions
|
||||
//! with a null context (the context's own `m_sThisZoneTol3d` is the kernel's
|
||||
//! `zone_tol_3d` as well). Note that the context overloads ignore the small-topology
|
||||
//! `model_size_estimate`, `large_small_size_ratio`). The `const SmContext*` overloads
|
||||
//! take an `Option<&`[`Context`]`>` (`None` = null pointer: the system value; usually
|
||||
//! `Some(&k.context)`). Note that the context overloads ignore the small-topology
|
||||
//! flag, as upstream does.
|
||||
//!
|
||||
//! Not here yet (they need types of later units; see the `UNPORTED` markers below):
|
||||
//! every overload taking an `SmObject`, `SmCurve`, `SmSurface`, `SmVolume`,
|
||||
//! `SmPseudoBox`, `SmExtent1d`, `SmExtent2d`, `SmBrep`, `SmBrepData`, a topology
|
||||
//! entity, a classification / interval object, `SmEvalNFunctionsObject`, or a
|
||||
//! non-null `SmContext` with its own tolerances.
|
||||
//! entity, a classification / interval object, or `SmEvalNFunctionsObject`.
|
||||
//!
|
||||
//! Deviations:
|
||||
//! - The tolerance classes (`SmTol3d`, `SmZoneTol3d`, ...) are plain `f64` values; their
|
||||
@@ -93,15 +93,12 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use crate::error::{Result, SolidError};
|
||||
use crate::geom::context::Context;
|
||||
use crate::geom::extent3d::Extent3d;
|
||||
use crate::geom::vector2d::Vec2;
|
||||
use crate::kernel::{Kernel, Tolerances};
|
||||
use crate::math::{Vec3, EFF_ZERO, EFF_ZERO_PARAM, EFF_ZERO_RAD, EFF_ZERO_SQ, EFF_ZERO_SQRT, PI};
|
||||
|
||||
// UNPORTED(geom.context): SmTol::GetModelSizeEstimate(const SmContext&),
|
||||
// SmTol::SetModelSizeEstimate(SmContext&, double) and the non-null-context branch of
|
||||
// SmTol::GetZoneTol3d(const SmContext*, ...) need a context with its own
|
||||
// m_dThisModelSizeEstimate / m_sThisZoneTol3d (SmContext::SetThisModelSizeEstimate).
|
||||
// UNPORTED(topology.brep_p1): SmTol::GetModelSizeEstimate(const SmBrep&),
|
||||
// SmTol::SetModelSizeEstimate(SmBrep&, double), SmTol::GetZoneTol3d(const SmBrep*),
|
||||
// SmTol::GetApproxTol3d(const SmBrep*), SmTol::CalcFaceZoneTol3d(SmBrep*),
|
||||
@@ -361,6 +358,19 @@ pub fn model_size_estimate(k: &Kernel) -> f64 {
|
||||
k.tolerances.model_size_estimate
|
||||
}
|
||||
|
||||
/// A context's model size estimate, the system one if it has none
|
||||
/// (`SmTol::GetModelSizeEstimate(const SmContext&)`).
|
||||
#[must_use]
|
||||
pub fn context_model_size_estimate(k: &Kernel, ctx: &Context) -> f64 {
|
||||
ctx.this_model_size_estimate(k)
|
||||
}
|
||||
|
||||
/// Sets a context's model size estimate and returns its new zone tolerance
|
||||
/// (`SmTol::SetModelSizeEstimate(SmContext&, double)`).
|
||||
pub fn set_context_model_size_estimate(ctx: &mut Context, model_size_estimate: f64) -> f64 {
|
||||
ctx.set_this_model_size_estimate(model_size_estimate)
|
||||
}
|
||||
|
||||
/// The gap gain: the over-size factor that keeps an object's zone tolerance a little
|
||||
/// larger than half its largest gap (`SmTol::GetGapGain`).
|
||||
#[must_use]
|
||||
@@ -384,16 +394,16 @@ pub fn zone_tol_3d(k: &Kernel, small: SmallTopoType) -> f64 {
|
||||
}
|
||||
|
||||
/// The context zone tolerance (`SmTol::GetZoneTol3d(const SmContext*,
|
||||
/// SmSmallTopoType)` with a null context); `small` is ignored, as upstream.
|
||||
/// SmSmallTopoType)`): the context's own zone tolerance, or the system one for `None`
|
||||
/// (a null context); `small` is ignored, as upstream.
|
||||
#[must_use]
|
||||
pub fn context_zone_tol_3d(k: &Kernel, small: SmallTopoType) -> f64 {
|
||||
pub fn context_zone_tol_3d(k: &Kernel, ctx: Option<&Context>, small: SmallTopoType) -> f64 {
|
||||
let _ = small;
|
||||
let z = k.tolerances.zone_tol_3d;
|
||||
if z == UNINIT_TOL {
|
||||
zone_tol3d(k, UNINIT_TOL)
|
||||
} else {
|
||||
// UNPORTED(geom.context): cpContext->m_sThisZoneTol3d for a non-null context.
|
||||
z
|
||||
ctx.map_or(z, Context::this_zone_tol_3d)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,10 +415,10 @@ pub fn approx_tol_3d(k: &Kernel, small: SmallTopoType) -> f64 {
|
||||
}
|
||||
|
||||
/// The context approximation tolerance (`SmTol::GetApproxTol3d(const SmContext*,
|
||||
/// SmSmallTopoType)` with a null context).
|
||||
/// SmSmallTopoType)`; `None` = null context).
|
||||
#[must_use]
|
||||
pub fn context_approx_tol_3d(k: &Kernel, small: SmallTopoType) -> f64 {
|
||||
approx_tol3d(k, 0.5 * context_zone_tol_3d(k, small))
|
||||
pub fn context_approx_tol_3d(k: &Kernel, ctx: Option<&Context>, small: SmallTopoType) -> f64 {
|
||||
approx_tol3d(k, 0.5 * context_zone_tol_3d(k, ctx, small))
|
||||
}
|
||||
|
||||
/// The system intersection tolerance, twice the zone tolerance
|
||||
@@ -419,10 +429,10 @@ pub fn xsect_tol_3d(k: &Kernel, small: SmallTopoType) -> f64 {
|
||||
}
|
||||
|
||||
/// The context intersection tolerance (`SmTol::GetXSectTol3d(const SmContext*,
|
||||
/// SmSmallTopoType)` with a null context).
|
||||
/// SmSmallTopoType)`; `None` = null context).
|
||||
#[must_use]
|
||||
pub fn context_xsect_tol_3d(k: &Kernel, small: SmallTopoType) -> f64 {
|
||||
xsect_tol3d(k, 2.0 * context_zone_tol_3d(k, small))
|
||||
pub fn context_xsect_tol_3d(k: &Kernel, ctx: Option<&Context>, small: SmallTopoType) -> f64 {
|
||||
xsect_tol3d(k, 2.0 * context_zone_tol_3d(k, ctx, small))
|
||||
}
|
||||
|
||||
/// The intersection tolerance of two objects with zone tolerances `zone1`, `zone2`:
|
||||
@@ -466,14 +476,15 @@ pub fn eff_zero_ang_rad() -> f64 {
|
||||
// ---- topology zone tolerances from gaps --------------------------------------------
|
||||
|
||||
/// A face's zone tolerance: the context zone tolerance
|
||||
/// (`SmTol::CalcFaceZoneTol3d(const SmContext*)` with a null context).
|
||||
/// (`SmTol::CalcFaceZoneTol3d(const SmContext*)`; `None` = null context).
|
||||
#[must_use]
|
||||
pub fn calc_face_zone_tol_3d(k: &Kernel) -> f64 {
|
||||
context_zone_tol_3d(k, SmallTopoType::NormalSize)
|
||||
pub fn calc_face_zone_tol_3d(k: &Kernel, ctx: Option<&Context>) -> f64 {
|
||||
context_zone_tol_3d(k, ctx, SmallTopoType::NormalSize)
|
||||
}
|
||||
|
||||
/// An edge's zone tolerance from its largest edge/edge and edge/face gaps
|
||||
/// (`SmTol::CalcEdgeZoneTol3d` with a null context):
|
||||
/// (`SmTol::CalcEdgeZoneTol3d(double, double, SmZoneTol3d*, const SmContext*)`;
|
||||
/// `None` = null context):
|
||||
/// `max(def, max_face_zone_tol?, gain * edge_edge_gap / 2, gain * edge_face_gap - def)`
|
||||
/// where `def` is the context zone tolerance and `gain` the [`gap_gain`].
|
||||
#[must_use]
|
||||
@@ -482,12 +493,20 @@ pub fn calc_edge_zone_tol_3d(
|
||||
edge_edge_max_gap: f64,
|
||||
edge_face_max_gap: f64,
|
||||
max_face_zone_tol: Option<f64>,
|
||||
ctx: Option<&Context>,
|
||||
) -> f64 {
|
||||
calc_gap_zone_tol(k, edge_edge_max_gap, edge_face_max_gap, max_face_zone_tol)
|
||||
calc_gap_zone_tol(
|
||||
k,
|
||||
edge_edge_max_gap,
|
||||
edge_face_max_gap,
|
||||
max_face_zone_tol,
|
||||
ctx,
|
||||
)
|
||||
}
|
||||
|
||||
/// A vertex's zone tolerance from its largest vertex/edge and vertex/face gaps
|
||||
/// (`SmTol::CalcVertexZoneTol3d` with a null context); same formula as
|
||||
/// (`SmTol::CalcVertexZoneTol3d(double, double, SmZoneTol3d*, const SmContext*)`;
|
||||
/// `None` = null context); same formula as
|
||||
/// [`calc_edge_zone_tol_3d`].
|
||||
#[must_use]
|
||||
pub fn calc_vertex_zone_tol_3d(
|
||||
@@ -495,18 +514,26 @@ pub fn calc_vertex_zone_tol_3d(
|
||||
vertex_edge_max_gap: f64,
|
||||
vertex_face_max_gap: f64,
|
||||
max_edge_zone_tol: Option<f64>,
|
||||
ctx: Option<&Context>,
|
||||
) -> f64 {
|
||||
calc_gap_zone_tol(
|
||||
k,
|
||||
vertex_edge_max_gap,
|
||||
vertex_face_max_gap,
|
||||
max_edge_zone_tol,
|
||||
ctx,
|
||||
)
|
||||
}
|
||||
|
||||
/// Shared body of `CalcEdgeZoneTol3d` / `CalcVertexZoneTol3d`.
|
||||
fn calc_gap_zone_tol(k: &Kernel, gap_1d: f64, gap_2d: f64, max_up: Option<f64>) -> f64 {
|
||||
let def = context_zone_tol_3d(k, SmallTopoType::NormalSize);
|
||||
fn calc_gap_zone_tol(
|
||||
k: &Kernel,
|
||||
gap_1d: f64,
|
||||
gap_2d: f64,
|
||||
max_up: Option<f64>,
|
||||
ctx: Option<&Context>,
|
||||
) -> f64 {
|
||||
let def = context_zone_tol_3d(k, ctx, SmallTopoType::NormalSize);
|
||||
let gain = gap_gain(k);
|
||||
let r = match max_up {
|
||||
Some(up) => smax4(def, up, gain * gap_1d / 2.0, gain * gap_2d - def),
|
||||
@@ -956,7 +983,7 @@ mod tests {
|
||||
assert_eq!(approx_tol_3d(&k, SmallTopoType::NormalSize), 5.0e-6);
|
||||
assert_eq!(zone_tol_3d(&k, SmallTopoType::SmallSize), 1000.0 * 1.0e-5);
|
||||
assert_eq!(
|
||||
context_zone_tol_3d(&k, SmallTopoType::ForceSmallSize),
|
||||
context_zone_tol_3d(&k, None, SmallTopoType::ForceSmallSize),
|
||||
1.0e-5
|
||||
);
|
||||
assert_eq!(gap_gain(&k), 1.1);
|
||||
@@ -979,10 +1006,13 @@ mod tests {
|
||||
#[test]
|
||||
fn gap_zone_tolerances() {
|
||||
let k = Kernel::default();
|
||||
assert_eq!(calc_edge_zone_tol_3d(&k, 0.0, 0.0, None), 1.0e-5);
|
||||
assert_eq!(calc_edge_zone_tol_3d(&k, 2.0, 0.0, None), 1.1);
|
||||
assert_eq!(calc_vertex_zone_tol_3d(&k, 0.0, 0.0, Some(0.25)), 0.25);
|
||||
assert_eq!(calc_face_zone_tol_3d(&k), 1.0e-5);
|
||||
assert_eq!(calc_edge_zone_tol_3d(&k, 0.0, 0.0, None, None), 1.0e-5);
|
||||
assert_eq!(calc_edge_zone_tol_3d(&k, 2.0, 0.0, None, None), 1.1);
|
||||
assert_eq!(
|
||||
calc_vertex_zone_tol_3d(&k, 0.0, 0.0, Some(0.25), None),
|
||||
0.25
|
||||
);
|
||||
assert_eq!(calc_face_zone_tol_3d(&k, None), 1.0e-5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
//! | Rust | Upstream |
|
||||
//! |----------------------------------------|-------------------------------------------|
|
||||
//! | [`Kernel`] | `SmContext` |
|
||||
//! | [`Kernel::context`] | `SmContext` marks, flags and own tolerance (see [`crate::geom::context`]) |
|
||||
//! | [`Tolerances::zone_tol_3d`] | `SmContext::m_sThisZoneTol3d`, `SM_ZONE_TOL_3D` |
|
||||
//! | [`Tolerances::xsect_tol_3d`] | `SM_XSECT_TOL_3D` |
|
||||
//! | [`Tolerances::approx_tol_3d`] | `SM_APPROX_TOL_3D` |
|
||||
@@ -24,6 +25,8 @@
|
||||
//! code must compare outcomes that are invariant to the draw (and say so in
|
||||
//! `plan/findings.md`).
|
||||
|
||||
use crate::geom::context::Context;
|
||||
|
||||
/// Geometric tolerances in model units (upstream defaults from `SmMath.h`).
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Tolerances {
|
||||
@@ -155,6 +158,9 @@ pub struct Kernel {
|
||||
pub rng: Rng,
|
||||
/// Memoisation caches.
|
||||
pub caches: Caches,
|
||||
/// The session's modeling context (marks, Boolean flag, callbacks, its own
|
||||
/// model size estimate); created with the defaults, as `SmApiGetOrCreateContext`.
|
||||
pub context: Context,
|
||||
}
|
||||
|
||||
impl Default for Kernel {
|
||||
@@ -171,6 +177,7 @@ impl Kernel {
|
||||
tolerances: options.tolerances,
|
||||
rng: Rng::new(options.seed),
|
||||
caches: Caches::default(),
|
||||
context: Context::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,604 @@
|
||||
{
|
||||
"cases": [
|
||||
{
|
||||
"name": "default_context",
|
||||
"upstream_ref": "tests/prog_test/src/smFeatureTest.cpp:26 (SmContext sContext)",
|
||||
"inputs": {
|
||||
"steps": [
|
||||
{
|
||||
"op": 0,
|
||||
"ctx": 0,
|
||||
"m": 0
|
||||
},
|
||||
{
|
||||
"op": 0,
|
||||
"ctx": 0,
|
||||
"m": 1
|
||||
},
|
||||
{
|
||||
"op": 0,
|
||||
"ctx": 0,
|
||||
"m": 2
|
||||
},
|
||||
{
|
||||
"op": 0,
|
||||
"ctx": 0,
|
||||
"m": 4
|
||||
},
|
||||
{
|
||||
"op": 0,
|
||||
"ctx": 0,
|
||||
"m": 8
|
||||
},
|
||||
{
|
||||
"op": 0,
|
||||
"ctx": 0,
|
||||
"m": 16
|
||||
},
|
||||
{
|
||||
"op": 0,
|
||||
"ctx": 0,
|
||||
"m": 31
|
||||
},
|
||||
{
|
||||
"op": 1,
|
||||
"ctx": 0,
|
||||
"m": 0
|
||||
},
|
||||
{
|
||||
"op": 1,
|
||||
"ctx": 0,
|
||||
"m": 1
|
||||
},
|
||||
{
|
||||
"op": 1,
|
||||
"ctx": 0,
|
||||
"m": 2
|
||||
},
|
||||
{
|
||||
"op": 1,
|
||||
"ctx": 0,
|
||||
"m": 4
|
||||
},
|
||||
{
|
||||
"op": 1,
|
||||
"ctx": 0,
|
||||
"m": 8
|
||||
},
|
||||
{
|
||||
"op": 1,
|
||||
"ctx": 0,
|
||||
"m": 16
|
||||
},
|
||||
{
|
||||
"op": 1,
|
||||
"ctx": 0,
|
||||
"m": 31
|
||||
},
|
||||
{
|
||||
"op": 11
|
||||
},
|
||||
{
|
||||
"op": 14,
|
||||
"m": 0
|
||||
},
|
||||
{
|
||||
"op": 14,
|
||||
"m": 2
|
||||
},
|
||||
{
|
||||
"op": 14,
|
||||
"ctx": -1,
|
||||
"m": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "api_create_context",
|
||||
"upstream_ref": "tests/SM_API_test/src/TestSmBrep.cpp:45 (SmApiCreateContext)",
|
||||
"inputs": {
|
||||
"steps": [
|
||||
{
|
||||
"op": 19
|
||||
},
|
||||
{
|
||||
"op": 19
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "scoped_lock_all_marks",
|
||||
"upstream_ref": "source/SMLib/src/SmAssembly.cpp:275 (SmNewMarkAndLock sMarkLock(GetContext(), SM_MT_ALLMARKS))",
|
||||
"inputs": {
|
||||
"steps": [
|
||||
{
|
||||
"op": 2,
|
||||
"slot": 0,
|
||||
"m": 31
|
||||
},
|
||||
{
|
||||
"op": 9,
|
||||
"slot": 0
|
||||
},
|
||||
{
|
||||
"op": 5,
|
||||
"slot": 0
|
||||
},
|
||||
{
|
||||
"op": 5,
|
||||
"slot": 0
|
||||
},
|
||||
{
|
||||
"op": 0,
|
||||
"m": 1
|
||||
},
|
||||
{
|
||||
"op": 8,
|
||||
"slot": 0
|
||||
},
|
||||
{
|
||||
"op": 1,
|
||||
"m": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "nested_locks_exhaust",
|
||||
"upstream_ref": "source/SMLib/inc/SmContext.h:496 (GetUnlockedMark: all marks locked -> SM_MT_MARKIO)",
|
||||
"inputs": {
|
||||
"slots": 8,
|
||||
"steps": [
|
||||
{
|
||||
"op": 2,
|
||||
"slot": 0,
|
||||
"m": 31
|
||||
},
|
||||
{
|
||||
"op": 2,
|
||||
"slot": 1,
|
||||
"m": 31
|
||||
},
|
||||
{
|
||||
"op": 2,
|
||||
"slot": 2,
|
||||
"m": 31
|
||||
},
|
||||
{
|
||||
"op": 2,
|
||||
"slot": 3,
|
||||
"m": 31
|
||||
},
|
||||
{
|
||||
"op": 2,
|
||||
"slot": 4,
|
||||
"m": 31
|
||||
},
|
||||
{
|
||||
"op": 2,
|
||||
"slot": 5,
|
||||
"m": 31
|
||||
},
|
||||
{
|
||||
"op": 2,
|
||||
"slot": 6,
|
||||
"m": 31
|
||||
},
|
||||
{
|
||||
"op": 9,
|
||||
"slot": 0
|
||||
},
|
||||
{
|
||||
"op": 9,
|
||||
"slot": 1
|
||||
},
|
||||
{
|
||||
"op": 9,
|
||||
"slot": 2
|
||||
},
|
||||
{
|
||||
"op": 9,
|
||||
"slot": 3
|
||||
},
|
||||
{
|
||||
"op": 9,
|
||||
"slot": 4
|
||||
},
|
||||
{
|
||||
"op": 9,
|
||||
"slot": 5
|
||||
},
|
||||
{
|
||||
"op": 9,
|
||||
"slot": 6
|
||||
},
|
||||
{
|
||||
"op": 8,
|
||||
"slot": 3
|
||||
},
|
||||
{
|
||||
"op": 2,
|
||||
"slot": 7,
|
||||
"m": 31
|
||||
},
|
||||
{
|
||||
"op": 9,
|
||||
"slot": 7
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "lock_single_mask_locked",
|
||||
"upstream_ref": "source/SMLib/src/SmContext.cpp:516 (GetUnlockedMark returns the single-mark mask)",
|
||||
"inputs": {
|
||||
"steps": [
|
||||
{
|
||||
"op": 2,
|
||||
"slot": 0,
|
||||
"m": 4
|
||||
},
|
||||
{
|
||||
"op": 2,
|
||||
"slot": 1,
|
||||
"m": 4
|
||||
},
|
||||
{
|
||||
"op": 9,
|
||||
"slot": 1
|
||||
},
|
||||
{
|
||||
"op": 2,
|
||||
"slot": 2,
|
||||
"m": 0
|
||||
},
|
||||
{
|
||||
"op": 9,
|
||||
"slot": 2
|
||||
},
|
||||
{
|
||||
"op": 2,
|
||||
"slot": 3,
|
||||
"m": 3
|
||||
},
|
||||
{
|
||||
"op": 9,
|
||||
"slot": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "default_lock_set_context",
|
||||
"upstream_ref": "source/SMLib/src/SmSphere.cpp:3600 (SmNewMarkAndLock sMarkLock; SetContext(GetContext()))",
|
||||
"inputs": {
|
||||
"steps": [
|
||||
{
|
||||
"op": 4,
|
||||
"slot": 0
|
||||
},
|
||||
{
|
||||
"op": 9,
|
||||
"slot": 0
|
||||
},
|
||||
{
|
||||
"op": 5,
|
||||
"slot": 0
|
||||
},
|
||||
{
|
||||
"op": 6,
|
||||
"slot": 0,
|
||||
"m": 31
|
||||
},
|
||||
{
|
||||
"op": 9,
|
||||
"slot": 0
|
||||
},
|
||||
{
|
||||
"op": 6,
|
||||
"slot": 0,
|
||||
"m": 2
|
||||
},
|
||||
{
|
||||
"op": 9,
|
||||
"slot": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "boolean_pair_same_context",
|
||||
"upstream_ref": "source/SMLib/src/SmPrimitiveCreation.cpp:7745 (SmNewMarkAndLock(BrepA ctx, BrepB ctx))",
|
||||
"inputs": {
|
||||
"steps": [
|
||||
{
|
||||
"op": 3,
|
||||
"slot": 0,
|
||||
"ctx": 0,
|
||||
"ctx2": 0
|
||||
},
|
||||
{
|
||||
"op": 9,
|
||||
"slot": 0
|
||||
},
|
||||
{
|
||||
"op": 5,
|
||||
"slot": 0
|
||||
},
|
||||
{
|
||||
"op": 10,
|
||||
"m": 1
|
||||
},
|
||||
{
|
||||
"op": 10,
|
||||
"m": 0,
|
||||
"flag": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "boolean_pair_two_contexts",
|
||||
"upstream_ref": "source/SMLib/src/SmPrimitiveCreation.cpp:7745 (SmNewMarkAndLock with distinct contexts)",
|
||||
"inputs": {
|
||||
"contexts": [
|
||||
[
|
||||
9876543.21,
|
||||
9876543.21
|
||||
],
|
||||
[
|
||||
9876543.21,
|
||||
9876543.21
|
||||
]
|
||||
],
|
||||
"steps": [
|
||||
{
|
||||
"op": 2,
|
||||
"slot": 1,
|
||||
"ctx": 1,
|
||||
"m": 31
|
||||
},
|
||||
{
|
||||
"op": 3,
|
||||
"slot": 0,
|
||||
"ctx": 0,
|
||||
"ctx2": 1
|
||||
},
|
||||
{
|
||||
"op": 9,
|
||||
"slot": 0
|
||||
},
|
||||
{
|
||||
"op": 5,
|
||||
"slot": 0
|
||||
},
|
||||
{
|
||||
"op": 8,
|
||||
"slot": 0
|
||||
},
|
||||
{
|
||||
"op": 7,
|
||||
"slot": 2,
|
||||
"ctx": 1,
|
||||
"ctx2": 0
|
||||
},
|
||||
{
|
||||
"op": 9,
|
||||
"slot": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "set_context_pair_rebind",
|
||||
"upstream_ref": "source/SMLib/inc/SmContext.h:600 (SmNewMarkAndLock::SetContext(ctx1, ctx2))",
|
||||
"inputs": {
|
||||
"contexts": [
|
||||
[
|
||||
9876543.21,
|
||||
9876543.21
|
||||
],
|
||||
[
|
||||
9876543.21,
|
||||
9876543.21
|
||||
],
|
||||
[
|
||||
9876543.21,
|
||||
9876543.21
|
||||
]
|
||||
],
|
||||
"steps": [
|
||||
{
|
||||
"op": 7,
|
||||
"slot": 0,
|
||||
"ctx": 0,
|
||||
"ctx2": 1
|
||||
},
|
||||
{
|
||||
"op": 7,
|
||||
"slot": 0,
|
||||
"ctx": 2,
|
||||
"ctx2": 2
|
||||
},
|
||||
{
|
||||
"op": 6,
|
||||
"slot": 0,
|
||||
"ctx": 1,
|
||||
"m": 31
|
||||
},
|
||||
{
|
||||
"op": 9,
|
||||
"slot": 0
|
||||
},
|
||||
{
|
||||
"op": 5,
|
||||
"slot": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "model_size_1000",
|
||||
"upstream_ref": "source/SMLib/src/SmContext.cpp:161 (SmContext(dThisModelSizeEstimate, dThisLargeSmallSizeRatio))",
|
||||
"inputs": {
|
||||
"contexts": [
|
||||
[
|
||||
1000.0,
|
||||
50.0
|
||||
],
|
||||
[
|
||||
0.01,
|
||||
9876543.21
|
||||
]
|
||||
],
|
||||
"steps": [
|
||||
{
|
||||
"op": 11,
|
||||
"ctx": 0
|
||||
},
|
||||
{
|
||||
"op": 11,
|
||||
"ctx": 1
|
||||
},
|
||||
{
|
||||
"op": 14,
|
||||
"ctx": 0,
|
||||
"m": 1
|
||||
},
|
||||
{
|
||||
"op": 14,
|
||||
"ctx": 1,
|
||||
"m": 3
|
||||
},
|
||||
{
|
||||
"op": 15,
|
||||
"ctx": 0,
|
||||
"x": [
|
||||
0.0002,
|
||||
0.0001,
|
||||
0.0003,
|
||||
0
|
||||
],
|
||||
"flag": 1
|
||||
},
|
||||
{
|
||||
"op": 16,
|
||||
"ctx": 1,
|
||||
"x": [
|
||||
2e-06,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "set_model_size",
|
||||
"upstream_ref": "source/SMLib/src/SmTol.cpp:80 (SmTol::SetModelSizeEstimate(SmContext&, double))",
|
||||
"inputs": {
|
||||
"sys_model": 5000.0,
|
||||
"steps": [
|
||||
{
|
||||
"op": 11
|
||||
},
|
||||
{
|
||||
"op": 12,
|
||||
"x": [
|
||||
0.001,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": 11
|
||||
},
|
||||
{
|
||||
"op": 12,
|
||||
"flag": 1,
|
||||
"x": [
|
||||
9876543.21,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": 11
|
||||
},
|
||||
{
|
||||
"op": 13,
|
||||
"x": [
|
||||
10.0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": 14,
|
||||
"m": 4
|
||||
},
|
||||
{
|
||||
"op": 14,
|
||||
"ctx": -1,
|
||||
"m": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "notify_callbacks",
|
||||
"upstream_ref": "source/SMLib/inc/SmContext.h:372 (SetSysNotifyCallback / GetUserNotifyCallback)",
|
||||
"inputs": {
|
||||
"steps": [
|
||||
{
|
||||
"op": 17,
|
||||
"m": 1
|
||||
},
|
||||
{
|
||||
"op": 17,
|
||||
"m": 0
|
||||
},
|
||||
{
|
||||
"op": 17,
|
||||
"m": 1,
|
||||
"flag": 1
|
||||
},
|
||||
{
|
||||
"op": 17,
|
||||
"m": 0,
|
||||
"flag": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mass_property_behavior",
|
||||
"upstream_ref": "source/SMLib/inc/SmTopoTypes.h:280 (SmMassPropertyBehavior)",
|
||||
"inputs": {
|
||||
"steps": [
|
||||
{
|
||||
"op": 18,
|
||||
"slot": 1
|
||||
},
|
||||
{
|
||||
"op": 18,
|
||||
"x": [
|
||||
7.8,
|
||||
0.1,
|
||||
0.2,
|
||||
0.3
|
||||
],
|
||||
"m": 2,
|
||||
"flag": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// difftest shim for unit `geom.context`. Wraps upstream SmContext, SmNewMarkAndLock
|
||||
// (source/SMLib/inc/SmContext.h, source/SMLib/src/SmContext.cpp), the SmContext
|
||||
// overloads of SmTol (source/SMLib/src/SmTol.cpp), SmMassPropertyBehavior
|
||||
// (source/SMLib/inc/SmTopoTypes.h) and SmApiCreateContext / SmApiGetOrCreateContext
|
||||
// (source/SM_API/src/SmApiGeneral.cpp).
|
||||
#include "sc_shim.h"
|
||||
#include <SmApiGeneral.h>
|
||||
#include <SmContext.h>
|
||||
#include <SmTol.h>
|
||||
#include <SmTopoTypes.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// Exposes SmContext's protected tolerance members and accessors.
|
||||
struct ScCtx : public SmContext
|
||||
{
|
||||
ScCtx(double dModel, double dRatio) : SmContext(dModel, dRatio) {}
|
||||
using SmContext::GetThisModelSizeEstimate;
|
||||
using SmContext::SetThisLargeSmallSizeRatio;
|
||||
using SmContext::SetThisModelSizeEstimate;
|
||||
double RawModel() const { return m_dThisModelSizeEstimate; }
|
||||
double RawRatio() const { return m_dThisLargeSmallSizeRatio; }
|
||||
double RawZone() const { return m_sThisZoneTol3d; }
|
||||
};
|
||||
|
||||
// A concrete notify callback (only its address is observed).
|
||||
struct ScNotify : public SmNotifyCallback
|
||||
{
|
||||
SmStatus Execute(SmNotifyOperation, SmObject*, SmObject*, SmObject*, SmObject*) override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Sets the system tolerance statics, restoring the defaults on scope exit.
|
||||
struct ScSystemTol
|
||||
{
|
||||
ScSystemTol(double dModelSize, double dRatio)
|
||||
{
|
||||
SmTol::SetModelSizeEstimate(dModelSize);
|
||||
SmTol::SetLargeSmallSizeRatio(dRatio);
|
||||
}
|
||||
~ScSystemTol()
|
||||
{
|
||||
SmTol::SetModelSizeEstimate(SM_USE_DEFAULT);
|
||||
SmTol::SetLargeSmallSizeRatio(SM_USE_DEFAULT);
|
||||
}
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
OP_CUR_MARK = 0,
|
||||
OP_IS_LOCKED = 1,
|
||||
OP_LOCK_NEW = 2,
|
||||
OP_LOCK_PAIR = 3,
|
||||
OP_LOCK_DEFAULT = 4,
|
||||
OP_LOCK_NEWMARK = 5,
|
||||
OP_LOCK_SETCTX = 6,
|
||||
OP_LOCK_SETCTX_PAIR = 7,
|
||||
OP_LOCK_DROP = 8,
|
||||
OP_LOCK_INFO = 9,
|
||||
OP_DOING_BOOL = 10,
|
||||
OP_GET_MODEL = 11,
|
||||
OP_SET_MODEL = 12,
|
||||
OP_SET_RATIO = 13,
|
||||
OP_TOL_CTX = 14,
|
||||
OP_CALC_EDGE = 15,
|
||||
OP_CALC_VERTEX = 16,
|
||||
OP_CALLBACK = 17,
|
||||
OP_MASS = 18,
|
||||
OP_API = 19,
|
||||
};
|
||||
|
||||
const int64_t kMarkTypes[7] = {SM_MT_NOMARK, SM_MT_MARK, SM_MT_MARK2, SM_MT_MARK3,
|
||||
SM_MT_MARKIO, SM_MT_MARKASSERT, SM_MT_ALLMARKS};
|
||||
|
||||
bool valid_mark(int64_t m)
|
||||
{
|
||||
for (int64_t v : kMarkTypes)
|
||||
if (v == m)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
int64_t f_bits(double d)
|
||||
{
|
||||
int64_t i;
|
||||
static_assert(sizeof(i) == sizeof(d), "f64 size");
|
||||
memcpy(&i, &d, sizeof(d));
|
||||
return i;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Runs a script of SmContext / SmNewMarkAndLock operations.
|
||||
// sys_model, sys_ratio : system SmTol::SetModelSizeEstimate / SetLargeSmallSizeRatio
|
||||
// ctx_params[2*n_ctx] : (model size, ratio) of each SmContext constructed
|
||||
// ops[6*n_ops] : per op (op, slot, ctx, ctx2, m, flag); ctx -1 = NULL where allowed
|
||||
// xs[4*n_ops] : per op float operands
|
||||
// n_slots : number of SmNewMarkAndLock slots
|
||||
// Output: tagged (tag, payload) int64 pairs; tag 0 = integer, tag 1 = double bits.
|
||||
// Per op: its results, then a snapshot of every context. At the end the live locks
|
||||
// are destroyed in slot order and a final snapshot is written.
|
||||
SC_EXPORT int32_t sc_geom_context_script(double sys_model, double sys_ratio, const double* ctx_params,
|
||||
int64_t n_ctx, const int64_t* ops, const double* xs,
|
||||
int64_t n_ops, int64_t n_slots, int64_t* out, int64_t cap,
|
||||
int64_t* out_len)
|
||||
{
|
||||
if (n_ctx < 1 || n_ctx > 8 || n_ops < 0 || n_slots < 0 || n_slots > 16)
|
||||
return SC_ERR_ARG;
|
||||
// validate everything before touching upstream
|
||||
for (int64_t i = 0; i < n_ops; ++i)
|
||||
{
|
||||
const int64_t* o = ops + 6 * i;
|
||||
int64_t op = o[0], s = o[1], c = o[2], c2 = o[3], m = o[4], fl = o[5];
|
||||
bool need_slot = op >= OP_LOCK_NEW && op <= OP_LOCK_INFO;
|
||||
bool null_ok = op == OP_TOL_CTX || op == OP_CALC_EDGE || op == OP_CALC_VERTEX;
|
||||
bool no_ctx = op == OP_LOCK_DEFAULT || op == OP_LOCK_NEWMARK || op == OP_LOCK_DROP ||
|
||||
op == OP_LOCK_INFO || op == OP_MASS || op == OP_API;
|
||||
if (op < 0 || op > OP_API)
|
||||
return SC_ERR_ARG;
|
||||
if (need_slot && (s < 0 || s >= n_slots))
|
||||
return SC_ERR_ARG;
|
||||
if (!no_ctx && !(c >= 0 && c < n_ctx) && !(null_ok && c == -1))
|
||||
return SC_ERR_ARG;
|
||||
if ((op == OP_LOCK_PAIR || op == OP_LOCK_SETCTX_PAIR) && (c2 < 0 || c2 >= n_ctx))
|
||||
return SC_ERR_ARG;
|
||||
if ((op == OP_CUR_MARK || op == OP_IS_LOCKED || op == OP_LOCK_SETCTX) && !valid_mark(m))
|
||||
return SC_ERR_ARG;
|
||||
if (op == OP_DOING_BOOL && (m < 0 || m > 1))
|
||||
return SC_ERR_ARG;
|
||||
if (op == OP_TOL_CTX && (m < 0 || m > 4))
|
||||
return SC_ERR_ARG;
|
||||
if (op == OP_MASS && (m < 0 || fl < 0))
|
||||
return SC_ERR_ARG;
|
||||
}
|
||||
SC_TRY
|
||||
ScSystemTol sSys(sys_model, sys_ratio);
|
||||
static ScNotify s_Notify;
|
||||
std::vector<ScCtx*> vCtx;
|
||||
for (int64_t i = 0; i < n_ctx; ++i)
|
||||
vCtx.push_back(new ScCtx(ctx_params[2 * i], ctx_params[2 * i + 1]));
|
||||
std::vector<SmNewMarkAndLock*> vSlots((size_t)n_slots, nullptr);
|
||||
std::vector<int64_t> res;
|
||||
auto put_i = [&](int64_t v) {
|
||||
res.push_back(0);
|
||||
res.push_back(v);
|
||||
};
|
||||
auto put_f = [&](double v) {
|
||||
res.push_back(1);
|
||||
res.push_back(f_bits(v));
|
||||
};
|
||||
auto ctx_index = [&](SmContext* p) -> int64_t {
|
||||
for (int64_t i = 0; i < n_ctx; ++i)
|
||||
if ((SmContext*)vCtx[(size_t)i] == p)
|
||||
return i;
|
||||
return -1;
|
||||
};
|
||||
auto snapshot = [&]() {
|
||||
for (ScCtx* p : vCtx)
|
||||
{
|
||||
for (int64_t m : kMarkTypes)
|
||||
{
|
||||
put_i((int64_t)p->GetCurrentMark((SmMarkType)m));
|
||||
put_i(p->IsMarkLocked((SmMarkType)m) ? 1 : 0);
|
||||
}
|
||||
put_i(p->GetDoingBoolean() ? 1 : 0);
|
||||
put_i(p->GetSysNotifyCallback() ? 1 : 0);
|
||||
put_i(p->GetUserNotifyCallback() ? 1 : 0);
|
||||
put_f(p->RawModel());
|
||||
put_f(p->RawRatio());
|
||||
put_f(p->RawZone());
|
||||
}
|
||||
};
|
||||
auto drop_slot = [&](int64_t s) {
|
||||
delete vSlots[(size_t)s];
|
||||
vSlots[(size_t)s] = nullptr;
|
||||
};
|
||||
for (int64_t i = 0; i < n_ops; ++i)
|
||||
{
|
||||
const int64_t* o = ops + 6 * i;
|
||||
const double* x = xs + 4 * i;
|
||||
int64_t op = o[0], s = o[1], fl = o[5];
|
||||
SmMarkType eM = (SmMarkType)o[4];
|
||||
ScCtx* pC = o[2] >= 0 && o[2] < n_ctx ? vCtx[(size_t)o[2]] : nullptr;
|
||||
ScCtx* pC2 = o[3] >= 0 && o[3] < n_ctx ? vCtx[(size_t)o[3]] : nullptr;
|
||||
switch (op)
|
||||
{
|
||||
case OP_CUR_MARK: put_i((int64_t)pC->GetCurrentMark(eM)); break;
|
||||
case OP_IS_LOCKED: put_i(pC->IsMarkLocked(eM) ? 1 : 0); break;
|
||||
case OP_LOCK_NEW:
|
||||
drop_slot(s);
|
||||
vSlots[(size_t)s] = new SmNewMarkAndLock(pC, (SmBitArray)o[4]);
|
||||
put_i(vSlots[(size_t)s]->GetMarkType());
|
||||
break;
|
||||
case OP_LOCK_PAIR:
|
||||
drop_slot(s);
|
||||
vSlots[(size_t)s] = new SmNewMarkAndLock((SmContext*)pC, (SmContext*)pC2);
|
||||
put_i(vSlots[(size_t)s]->GetMarkType1());
|
||||
put_i(vSlots[(size_t)s]->GetMarkType2());
|
||||
break;
|
||||
case OP_LOCK_DEFAULT:
|
||||
drop_slot(s);
|
||||
vSlots[(size_t)s] = new SmNewMarkAndLock();
|
||||
break;
|
||||
case OP_LOCK_NEWMARK:
|
||||
if (vSlots[(size_t)s] && vSlots[(size_t)s]->GetContext())
|
||||
{
|
||||
vSlots[(size_t)s]->NewMark();
|
||||
put_i(1);
|
||||
}
|
||||
else
|
||||
put_i(-1);
|
||||
break;
|
||||
case OP_LOCK_SETCTX:
|
||||
if (!vSlots[(size_t)s])
|
||||
vSlots[(size_t)s] = new SmNewMarkAndLock();
|
||||
put_i(vSlots[(size_t)s]->SetContext(pC, eM));
|
||||
break;
|
||||
case OP_LOCK_SETCTX_PAIR:
|
||||
if (!vSlots[(size_t)s])
|
||||
vSlots[(size_t)s] = new SmNewMarkAndLock();
|
||||
put_i(vSlots[(size_t)s]->SetContext((SmContext*)pC, (SmContext*)pC2));
|
||||
break;
|
||||
case OP_LOCK_DROP: drop_slot(s); break;
|
||||
case OP_LOCK_INFO:
|
||||
if (!vSlots[(size_t)s])
|
||||
{
|
||||
put_i(-2);
|
||||
break;
|
||||
}
|
||||
put_i(vSlots[(size_t)s]->GetMarkType());
|
||||
put_i(vSlots[(size_t)s]->GetMarkType1());
|
||||
put_i(vSlots[(size_t)s]->GetMarkType2());
|
||||
put_i(ctx_index(vSlots[(size_t)s]->GetContext()));
|
||||
put_i(ctx_index(vSlots[(size_t)s]->GetContext2()));
|
||||
break;
|
||||
case OP_DOING_BOOL:
|
||||
if (fl)
|
||||
pC->GetDoingBooleanRef() = (SmBoolean)o[4];
|
||||
else
|
||||
pC->SetDoingBoolean((SmBoolean)o[4]);
|
||||
put_i(pC->GetDoingBoolean() ? 1 : 0);
|
||||
break;
|
||||
case OP_GET_MODEL:
|
||||
put_f(pC->GetThisModelSizeEstimate());
|
||||
put_f(SmTol::GetModelSizeEstimate(*(const SmContext*)pC));
|
||||
break;
|
||||
case OP_SET_MODEL:
|
||||
if (fl)
|
||||
put_f(SmTol::SetModelSizeEstimate(*(SmContext*)pC, x[0]));
|
||||
else
|
||||
put_f(pC->SetThisModelSizeEstimate(x[0]));
|
||||
break;
|
||||
case OP_SET_RATIO: pC->SetThisLargeSmallSizeRatio(x[0]); break;
|
||||
case OP_TOL_CTX:
|
||||
{
|
||||
const SmContext* cp = pC;
|
||||
SmSmallTopoType eS = (SmSmallTopoType)o[4];
|
||||
put_f(SmTol::GetZoneTol3d(cp, eS));
|
||||
put_f(SmTol::GetApproxTol3d(cp, eS));
|
||||
put_f(SmTol::GetXSectTol3d(cp, eS));
|
||||
put_f(SmTol::CalcFaceZoneTol3d(cp));
|
||||
break;
|
||||
}
|
||||
case OP_CALC_EDGE:
|
||||
{
|
||||
SmZoneTol3d sMax(x[2]);
|
||||
put_f(SmTol::CalcEdgeZoneTol3d(x[0], x[1], fl ? &sMax : (SmZoneTol3d*)NULL,
|
||||
(const SmContext*)pC));
|
||||
break;
|
||||
}
|
||||
case OP_CALC_VERTEX:
|
||||
{
|
||||
SmZoneTol3d sMax(x[2]);
|
||||
put_f(SmTol::CalcVertexZoneTol3d(x[0], x[1], fl ? &sMax : (SmZoneTol3d*)NULL,
|
||||
(const SmContext*)pC));
|
||||
break;
|
||||
}
|
||||
case OP_CALLBACK:
|
||||
if (fl)
|
||||
pC->SetUserNotifyCallback(o[4] ? &s_Notify : NULL);
|
||||
else
|
||||
pC->SetSysNotifyCallback(o[4] ? &s_Notify : NULL);
|
||||
put_i(pC->GetSysNotifyCallback() ? 1 : 0);
|
||||
put_i(pC->GetUserNotifyCallback() ? 1 : 0);
|
||||
break;
|
||||
case OP_MASS:
|
||||
{
|
||||
SmMassPropertyBehavior sDef;
|
||||
SmMassPropertyBehavior sArg(x[0], x[1], x[2], x[3], (ULONG)o[4], (ULONG)fl);
|
||||
const SmMassPropertyBehavior& r = s ? sDef : sArg;
|
||||
put_f(r.m_dDensity);
|
||||
put_f(r.m_dFaceThickness);
|
||||
put_f(r.m_dEdgeCrossSectionRadius);
|
||||
put_f(r.m_dVertexSphereRadius);
|
||||
put_i((int64_t)r.m_lFaceAreaBehavior);
|
||||
put_i((int64_t)r.m_lFaceVolumeBehavior);
|
||||
break;
|
||||
}
|
||||
case OP_API:
|
||||
{
|
||||
SmApiCreateContext();
|
||||
SmContext* p = SmApiGetOrCreateContext();
|
||||
put_i(p != NULL ? 1 : 0);
|
||||
put_i(p == SmApiGetOrCreateContext() ? 1 : 0);
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
snapshot();
|
||||
}
|
||||
for (int64_t s = 0; s < n_slots; ++s)
|
||||
drop_slot(s);
|
||||
snapshot();
|
||||
for (ScCtx* p : vCtx)
|
||||
{
|
||||
p->SetSysNotifyCallback(NULL);
|
||||
delete p;
|
||||
}
|
||||
return sc_copy_out(res.data(), (int64_t)res.size(), out, cap, out_len);
|
||||
SC_CATCH
|
||||
}
|
||||
+29
-2
@@ -198,8 +198,8 @@ agents:
|
||||
- **System statics live in the `Kernel`.** `s_ZoneTol3d`, `s_TolGapGain`,
|
||||
`s_ModelSizeEstimate`, `s_LargeSmallSizeRatio` are `kernel.tolerances.{zone_tol_3d,
|
||||
tol_gap_gain, model_size_estimate, large_small_size_ratio}`. The kernel also stands in
|
||||
for `SmContext`, so the `context_*` functions are the null-context overloads; a context
|
||||
with its own `m_sThisZoneTol3d` is `UNPORTED(geom.context)`. The difftest shim sets the
|
||||
for `SmContext`; since `geom.context` the `context_*` / `calc_*_zone_tol_3d`
|
||||
functions take `Option<&Context>` (`None` = null pointer, `Some(&k.context)` usual). The difftest shim sets the
|
||||
statics with `SmTol::SetModelSizeEstimate` / `SetLargeSmallSizeRatio` per case and
|
||||
restores the defaults afterwards (the C++ statics are thread-local globals).
|
||||
- **Overloads needing later types are not in Rust yet** (markers at the top of
|
||||
@@ -218,3 +218,30 @@ agents:
|
||||
- **Co-ports.** `sm_sqrt` and `SmVector3d::GetMaxDimension` (unit `geom.vector3d`) are
|
||||
private helpers marked `// CO-PORT(geom.vector3d)`. The `SM_IS_ZERO_SQUARED` co-port in
|
||||
`geom/vector2d.rs` now calls `geom::tol` (vector2d difftest re-run: green, 4060 cases).
|
||||
|
||||
## geom.context
|
||||
|
||||
- **Two-context `SmNewMarkAndLock` constructor bug (ported as is).** In
|
||||
`SmNewMarkAndLock(SmContext* p1, SmContext* p2)` with `p1 != p2`, upstream picks
|
||||
`m_eMarkType2 = GetUnlockedMark(*p2)` but then calls `m_pContext1->NewMark(m_eMarkType1)`
|
||||
(advancing context 1's mark a second time, not context 2's) and
|
||||
`LockMark(m_pContext2, m_eMarkType1)` (locking mark type 1 in context 2, not type 2).
|
||||
The destructor then unlocks `m_eMarkType2` in context 2, so when the types differ a
|
||||
stray lock of type 1 stays on context 2. `geom::context::MarkLock::new_pair` reproduces
|
||||
this exactly (difftest case `boolean_pair_two_contexts`). `SetContext(p1, p2)` is
|
||||
correct (locks type 2 in context 2) and does not advance marks.
|
||||
- **Release build ignores locks.** `NewMark`'s lock checks and `LockMark` /
|
||||
`UnlockMark`'s state checks are `SM_ASSERT*` (messages only), so locked marks can still
|
||||
be advanced and double locks are silent; the port drops the assertions.
|
||||
- **`SmNewMarkAndLock` binds contexts by index.** Rust `MarkLock` stores indices into a
|
||||
caller-owned `&mut [Context]` instead of pointers and its destructor is the explicit
|
||||
`release`; `with_mark_lock(&mut Kernel, ..)` is the scoped form for the kernel's own
|
||||
context (`Kernel::context`). Topology porters: use `k.context` for
|
||||
`GetContext()->GetCurrentMark(..)` and `with_mark_lock` for `SmNewMarkAndLock`.
|
||||
- **Build flags.** The reference build has neither `SM_USE_GLOBAL_CACHE` nor
|
||||
`SM_USE_NEWTOL`, so the constructor is the two-double one and
|
||||
`SetThisModelSizeEstimate` is the inline header version
|
||||
(`m_sThisZoneTol3d = SizeEstimateToZoneTol3d(estimate)`, which maps `SM_USE_DEFAULT` to
|
||||
`SM_MODEL_SIZE_ESTIMATE`, not to the system statics).
|
||||
- **`SmApiGetOrCreateContext` global.** The SM_API keeps one process-global context;
|
||||
the port takes the `Option<Context>` slot from the caller.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user