Plastic: Rest (round or rectangular pad, solid or hollow wall, draft) and Plastic Rules (the six-rule library, design rules from expressions using Thickness, active rule, rules assigned to bodies; a body's rule gives Boss and Rest their draft and grooves their clearance), plastic.rules; docs/oracle.md lists the plastic features as no oracle (licence) with the analytic tests that cover them
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
1 parent
1db841aa76
commit
66df249c0f
10 files changed
+564
-8
No files matched your search
@@ -194,6 +194,13 @@ impl FeatureKind {
|
||||
dir(direction);
|
||||
}
|
||||
FeatureKind::Lip { face, .. } => pt(face),
|
||||
FeatureKind::Rest { position, direction, along, .. } => {
|
||||
pt(position);
|
||||
dir(direction);
|
||||
if let Some(a) = along {
|
||||
dir(a);
|
||||
}
|
||||
}
|
||||
FeatureKind::SnapFit { position, direction, hook, .. } => {
|
||||
pt(position);
|
||||
dir(direction);
|
||||
|
||||
@@ -533,6 +533,30 @@ pub enum FeatureKind {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
body: Option<String>,
|
||||
},
|
||||
/// Plastic rest: a raised pad on a face (round, or a rectangle), solid or a hollow wall,
|
||||
/// with draft.
|
||||
Rest {
|
||||
position: Vec3,
|
||||
/// Out of the face (the pad grows along it).
|
||||
#[serde(default = "z_axis")]
|
||||
direction: Vec3,
|
||||
/// The rectangle's length runs along this (default: any direction in the face).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
along: Option<Vec3>,
|
||||
/// Diameter of a round rest, or the rectangle's width.
|
||||
width: String,
|
||||
/// The rectangle's length (none: round).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
length: Option<String>,
|
||||
height: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
draft: Option<String>,
|
||||
/// Wall thickness of a hollow rest (none: solid).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
thickness: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
body: Option<String>,
|
||||
},
|
||||
/// Remove bodies from the model (from here on in the timeline).
|
||||
Remove {
|
||||
bodies: Vec<String>,
|
||||
@@ -703,6 +727,10 @@ fn z_axis() -> Vec3 {
|
||||
|
||||
impl FeatureKind {
|
||||
/// A sheet metal feature (its result depends on the sheet metal rules).
|
||||
pub fn is_plastic(&self) -> bool {
|
||||
matches!(self, FeatureKind::Boss { .. } | FeatureKind::Lip { .. } | FeatureKind::SnapFit { .. } | FeatureKind::Rest { .. })
|
||||
}
|
||||
|
||||
pub fn is_sheet(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
@@ -750,6 +778,7 @@ impl FeatureKind {
|
||||
FeatureKind::Lip { groove: false, .. } => "LipFeature",
|
||||
FeatureKind::Lip { .. } => "GrooveFeature",
|
||||
FeatureKind::SnapFit { .. } => "SnapFitFeature",
|
||||
FeatureKind::Rest { .. } => "RestFeature",
|
||||
FeatureKind::SheetBase { .. } => "BaseFlangeFeature",
|
||||
FeatureKind::SheetContour { .. } => "ContourFlangeFeature",
|
||||
FeatureKind::SheetFlange { .. } => "EdgeFlangeFeature",
|
||||
@@ -802,6 +831,7 @@ impl FeatureKind {
|
||||
FeatureKind::Lip { groove: false, .. } => "Lip",
|
||||
FeatureKind::Lip { .. } => "Groove",
|
||||
FeatureKind::SnapFit { .. } => "SnapFit",
|
||||
FeatureKind::Rest { .. } => "Rest",
|
||||
FeatureKind::SheetBase { .. } => "BaseFlange",
|
||||
FeatureKind::SheetContour { .. } => "ContourFlange",
|
||||
FeatureKind::SheetFlange { .. } => "EdgeFlange",
|
||||
@@ -903,6 +933,10 @@ impl FeatureKind {
|
||||
FeatureKind::SnapFit { length, thickness, width, catch_depth, catch_length, .. } => {
|
||||
v.extend([length.as_str(), thickness, width, catch_depth, catch_length]);
|
||||
}
|
||||
FeatureKind::Rest { width, length, height, draft, thickness, .. } => {
|
||||
v.extend([width.as_str(), height]);
|
||||
v.extend([length, draft, thickness].into_iter().flatten().map(String::as_str));
|
||||
}
|
||||
FeatureKind::SheetContour { distance, .. } => v.push(distance),
|
||||
FeatureKind::SheetFlange { height, angle, radius, .. } => {
|
||||
v.extend([height.as_str(), angle]);
|
||||
@@ -987,6 +1021,9 @@ pub struct Document {
|
||||
/// Sheet metal rules.
|
||||
#[serde(default, skip_serializing_if = "crate::sheet::SheetSettings::is_empty")]
|
||||
pub sheet: crate::sheet::SheetSettings,
|
||||
/// Plastic rules.
|
||||
#[serde(default, skip_serializing_if = "crate::plastic::PlasticSettings::is_empty")]
|
||||
pub plastic: crate::plastic::PlasticSettings,
|
||||
/// Favourite parameters (by name).
|
||||
#[serde(default, skip_serializing_if = "std::collections::BTreeSet::is_empty")]
|
||||
pub favorites: std::collections::BTreeSet<String>,
|
||||
@@ -1050,6 +1087,7 @@ impl Document {
|
||||
occurrences: Vec::new(),
|
||||
assembly: Default::default(),
|
||||
sheet: Default::default(),
|
||||
plastic: Default::default(),
|
||||
body_components: Default::default(),
|
||||
materials: Default::default(),
|
||||
favorites: Default::default(),
|
||||
|
||||
+17
-1
@@ -304,6 +304,20 @@ impl Model {
|
||||
}
|
||||
fp = h.finish();
|
||||
}
|
||||
// Plastic features take defaults from the plastic rules.
|
||||
if f.kind.is_plastic() && !doc.plastic.is_empty() {
|
||||
let mut h = std::collections::hash_map::DefaultHasher::new();
|
||||
fp.hash(&mut h);
|
||||
serde_json::to_string(&doc.plastic).unwrap_or_default().hash(&mut h);
|
||||
for r in &doc.plastic.rules {
|
||||
for (_, e, _) in r.fields() {
|
||||
for n in expr::references(e) {
|
||||
vals.get(&n).map(|v| v.v.to_bits()).hash(&mut h);
|
||||
}
|
||||
}
|
||||
}
|
||||
fp = h.finish();
|
||||
}
|
||||
prev_fp = fp;
|
||||
if reuse
|
||||
&& let Some(old) = self.results.get(i)
|
||||
@@ -1393,7 +1407,9 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
| FeatureKind::SheetHem { .. }
|
||||
| FeatureKind::SheetUnfold { .. }
|
||||
| FeatureKind::SheetConvert { .. } => more::sheet_eval(doc, vals, f, st),
|
||||
FeatureKind::Boss { .. } | FeatureKind::Lip { .. } | FeatureKind::SnapFit { .. } => more::plastic_eval(vals, f, st),
|
||||
FeatureKind::Boss { .. } | FeatureKind::Lip { .. } | FeatureKind::SnapFit { .. } | FeatureKind::Rest { .. } => {
|
||||
more::plastic_eval(doc, vals, f, st)
|
||||
}
|
||||
FeatureKind::Emboss { .. }
|
||||
| FeatureKind::Rib { .. }
|
||||
| FeatureKind::ReplaceFace { .. }
|
||||
|
||||
@@ -587,8 +587,12 @@ fn offset_left(p: &[Vec2], d: f64) -> Vec<Vec2> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(super) fn plastic_eval(vals: &BTreeMap<String, Value>, f: &Feature, st: &mut ModelState) -> Result<()> {
|
||||
pub(super) fn plastic_eval(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st: &mut ModelState) -> Result<()> {
|
||||
let len = |e: &str| val(vals, e, Kind::Length);
|
||||
// The plastic rule of the body a feature goes on gives its defaults (draft, clearance).
|
||||
let rule = |target: &str| -> Result<Option<crate::plastic::PlasticValues>> {
|
||||
doc.plastic_rule_for(target).map(|r| doc.plastic_values(vals, &r)).transpose()
|
||||
};
|
||||
// Tools start a little inside the material so they overlap it instead of touching a face.
|
||||
match &f.kind {
|
||||
FeatureKind::Boss {
|
||||
@@ -613,7 +617,7 @@ pub(super) fn plastic_eval(vals: &BTreeMap<String, Value>, f: &Feature, st: &mut
|
||||
}
|
||||
let dr = match draft {
|
||||
Some(e) => val(vals, e, Kind::Angle)?,
|
||||
None => 0.0,
|
||||
None => rule(&target)?.map_or(0.0, |r| r.draft),
|
||||
};
|
||||
let rf = match fillet {
|
||||
Some(e) => len(e)?.max(0.0),
|
||||
@@ -705,6 +709,7 @@ pub(super) fn plastic_eval(vals: &BTreeMap<String, Value>, f: &Feature, st: &mut
|
||||
let (w, h) = (len(width)?, len(height)?);
|
||||
let g = match gap {
|
||||
Some(e) => len(e)?,
|
||||
None if *groove => rule(&target)?.map_or(0.0, |r| r.clearance),
|
||||
None => 0.0,
|
||||
};
|
||||
if !(w > 0.0 && h > 0.0 && g >= 0.0) {
|
||||
@@ -757,6 +762,55 @@ pub(super) fn plastic_eval(vals: &BTreeMap<String, Value>, f: &Feature, st: &mut
|
||||
let tool = kernel::extrude(&pl, &[Region2 { outer: lp, holes: Vec::new() }], lo, hi)?;
|
||||
apply_op(st, f, tool, Operation::Join, std::slice::from_ref(&target))
|
||||
}
|
||||
FeatureKind::Rest { position, direction, along, width, length, height, draft, thickness, body } => {
|
||||
let target = body_under(st, body, *position)?;
|
||||
let (w, h) = (len(width)?, len(height)?);
|
||||
let l = length.as_deref().map(len).transpose()?;
|
||||
let dr = match draft {
|
||||
Some(e) => val(vals, e, Kind::Angle)?,
|
||||
None => rule(&target)?.map_or(0.0, |r| r.draft),
|
||||
};
|
||||
let t = thickness.as_deref().map(len).transpose()?;
|
||||
if !(w > 0.0 && h > 0.0 && l.is_none_or(|l| l > 0.0) && t.is_none_or(|t| t > 0.0 && 2.0 * t < w.min(l.unwrap_or(w)))) {
|
||||
return Err(DocError::Invalid("the rest's sizes must be positive and its wall thinner than half its width".into()));
|
||||
}
|
||||
if !(dr.abs() < 1.0) {
|
||||
return Err(DocError::Invalid("the rest's draft must be under 57°".into()));
|
||||
}
|
||||
let shrink = h * dr.tan();
|
||||
let closes = match t {
|
||||
Some(t) => 2.0 * shrink >= t,
|
||||
None => 2.0 * shrink >= w.min(l.unwrap_or(w)),
|
||||
};
|
||||
if closes {
|
||||
return Err(DocError::Invalid("the draft closes the rest before its top".into()));
|
||||
}
|
||||
let delta = (h * 0.05).clamp(1e-3, 0.5);
|
||||
let (x, y, z) = local_frame(*direction, *along)?;
|
||||
// Sized at the face: the tool starts a little inside, grown by the draft over that.
|
||||
let e = delta * dr.tan();
|
||||
let shape = |grow: f64| match l {
|
||||
Some(l) => Loop2::polygon(&[
|
||||
Vec2::new(-l / 2.0 - grow, -w / 2.0 - grow),
|
||||
Vec2::new(l / 2.0 + grow, -w / 2.0 - grow),
|
||||
Vec2::new(l / 2.0 + grow, w / 2.0 + grow),
|
||||
Vec2::new(-l / 2.0 - grow, w / 2.0 + grow),
|
||||
]),
|
||||
None => Loop2::circle(Vec2::ZERO, w / 2.0 + grow),
|
||||
};
|
||||
let holes = match t {
|
||||
Some(t) => vec![shape(-t - e).reversed()],
|
||||
None => Vec::new(),
|
||||
};
|
||||
let region = Region2 { outer: shape(e), holes };
|
||||
let base = Plane::new(*position - z * delta, x, y).ok_or_else(|| DocError::Invalid("rest frame".into()))?;
|
||||
let tool = if dr.abs() > 1e-12 {
|
||||
vec![kernel::extrude_tapered(&base, ®ion, h + delta, 1.0, -dr)?]
|
||||
} else {
|
||||
kernel::extrude(&base, &[region], 0.0, h + delta)?
|
||||
};
|
||||
apply_op(st, f, tool, Operation::Join, std::slice::from_ref(&target))
|
||||
}
|
||||
_ => Err(DocError::Invalid(format!("{} is not a plastic feature", f.name))),
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ mod eval;
|
||||
pub mod expr;
|
||||
pub mod joints;
|
||||
mod params;
|
||||
pub mod plastic;
|
||||
pub mod project;
|
||||
mod project3d;
|
||||
pub mod sheet;
|
||||
|
||||
@@ -163,6 +163,15 @@ impl FeatureKind {
|
||||
v.push(("Gap", g, L));
|
||||
}
|
||||
}
|
||||
FeatureKind::Rest { width, length, height, draft, thickness, .. } => {
|
||||
v.push(("Width", width, L));
|
||||
v.push(("Height", height, L));
|
||||
for (l, o, k) in [("Length", length, L), ("Draft angle", draft, A), ("Wall thickness", thickness, L)] {
|
||||
if let Some(e) = o {
|
||||
v.push((l, e, k));
|
||||
}
|
||||
}
|
||||
}
|
||||
FeatureKind::SnapFit { length, thickness, width, catch_depth, catch_length, .. } => {
|
||||
v.push(("Length", length, L));
|
||||
v.push(("Thickness", thickness, L));
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
//! Plastic rules: wall thickness, draft, radii and clearances for molded parts, as expressions
|
||||
//! (`Thickness` inside them is the rule's thickness). A body assigned a rule (or every body,
|
||||
//! when a rule is active) takes its draft and clearance as the defaults of plastic features.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::expr::{self, Kind, Value};
|
||||
use crate::{DocError, Document, Result};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct PlasticRule {
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
pub material: String,
|
||||
pub thickness: String,
|
||||
pub nominal_radius: String,
|
||||
pub clearance: String,
|
||||
pub knife_edge: String,
|
||||
pub reveal_height: String,
|
||||
pub thickness_variation: String,
|
||||
pub draft: String,
|
||||
pub max_thickness: String,
|
||||
pub min_thickness: String,
|
||||
pub min_draft: String,
|
||||
}
|
||||
|
||||
impl PlasticRule {
|
||||
fn new(name: &str, material: &str, inch: bool, thickness: &str, max: &str, min: &str) -> PlasticRule {
|
||||
let (clearance, knife, reveal) = if inch { ("0.004 in", "0.04 in", "0.02 in") } else { ("0.1 mm", "1 mm", "0.5 mm") };
|
||||
PlasticRule {
|
||||
name: name.into(),
|
||||
material: material.into(),
|
||||
thickness: thickness.into(),
|
||||
nominal_radius: "0.5 * Thickness".into(),
|
||||
clearance: clearance.into(),
|
||||
knife_edge: knife.into(),
|
||||
reveal_height: reveal.into(),
|
||||
thickness_variation: "0.15 * Thickness".into(),
|
||||
draft: "2 deg".into(),
|
||||
max_thickness: max.into(),
|
||||
min_thickness: min.into(),
|
||||
min_draft: "0.5 deg".into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The rule library (the first is the default).
|
||||
pub fn library() -> Vec<PlasticRule> {
|
||||
vec![
|
||||
PlasticRule::new("ABS (1.5mm)", "ABS Plastic", false, "1.5 mm", "3.5 mm", "1.2 mm"),
|
||||
PlasticRule::new("Nylon (PA6) (2.5mm)", "Nylon 6", false, "2.5 mm", "3 mm", "0.8 mm"),
|
||||
PlasticRule::new("Polypropylene (PP) (3 mm)", "Polypropylene", false, "3 mm", "3.8 mm", "0.8 mm"),
|
||||
PlasticRule::new("ABS (0.1 in)", "ABS Plastic", true, "0.1 in", "3.5 mm", "1.2 mm"),
|
||||
PlasticRule::new("Nylon (PA6) (0.1 in)", "Nylon 6", true, "0.1 in", "3 mm", "0.8 mm"),
|
||||
PlasticRule::new("Polypropylene (PP) (0.1 in)", "Polypropylene", true, "0.1 in", "3.8 mm", "0.8 mm"),
|
||||
]
|
||||
}
|
||||
|
||||
/// The expressions, by field name (as commands and the dialog name them).
|
||||
pub fn fields(&self) -> [(&'static str, &String, Kind); 10] {
|
||||
[
|
||||
("thickness", &self.thickness, Kind::Length),
|
||||
("nominal_radius", &self.nominal_radius, Kind::Length),
|
||||
("clearance", &self.clearance, Kind::Length),
|
||||
("knife_edge", &self.knife_edge, Kind::Length),
|
||||
("reveal_height", &self.reveal_height, Kind::Length),
|
||||
("thickness_variation", &self.thickness_variation, Kind::Length),
|
||||
("draft", &self.draft, Kind::Angle),
|
||||
("max_thickness", &self.max_thickness, Kind::Length),
|
||||
("min_thickness", &self.min_thickness, Kind::Length),
|
||||
("min_draft", &self.min_draft, Kind::Angle),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn field_mut(&mut self, name: &str) -> Option<&mut String> {
|
||||
Some(match name {
|
||||
"thickness" => &mut self.thickness,
|
||||
"nominal_radius" => &mut self.nominal_radius,
|
||||
"clearance" => &mut self.clearance,
|
||||
"knife_edge" => &mut self.knife_edge,
|
||||
"reveal_height" => &mut self.reveal_height,
|
||||
"thickness_variation" => &mut self.thickness_variation,
|
||||
"draft" => &mut self.draft,
|
||||
"max_thickness" => &mut self.max_thickness,
|
||||
"min_thickness" => &mut self.min_thickness,
|
||||
"min_draft" => &mut self.min_draft,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The design's own plastic rules, the active one and the rules assigned to bodies.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
pub struct PlasticSettings {
|
||||
/// Rules made or edited in this design (a library rule edited here is copied in).
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub rules: Vec<PlasticRule>,
|
||||
/// Applies to bodies with no rule of their own.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub active: Option<String>,
|
||||
/// Body name → rule name.
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub assigned: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
impl PlasticSettings {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.rules.is_empty() && self.active.is_none() && self.assigned.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// A rule's values (mm, radians).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
|
||||
pub struct PlasticValues {
|
||||
pub thickness: f64,
|
||||
pub nominal_radius: f64,
|
||||
pub clearance: f64,
|
||||
pub knife_edge: f64,
|
||||
pub reveal_height: f64,
|
||||
pub thickness_variation: f64,
|
||||
pub draft: f64,
|
||||
pub max_thickness: f64,
|
||||
pub min_thickness: f64,
|
||||
pub min_draft: f64,
|
||||
}
|
||||
|
||||
impl Document {
|
||||
/// A rule by name: the design's own first, then the library.
|
||||
pub fn plastic_rule(&self, name: &str) -> Option<PlasticRule> {
|
||||
self.plastic.rules.iter().find(|r| r.name == name).cloned().or_else(|| PlasticRule::library().into_iter().find(|r| r.name == name))
|
||||
}
|
||||
|
||||
/// Every rule: the library (as edited in this design) and the design's own.
|
||||
pub fn plastic_rules(&self) -> Vec<PlasticRule> {
|
||||
let mut out: Vec<PlasticRule> = PlasticRule::library().into_iter().map(|l| self.plastic_rule(&l.name).unwrap_or(l)).collect();
|
||||
let own: Vec<PlasticRule> = self.plastic.rules.iter().filter(|r| !out.iter().any(|o| o.name == r.name)).cloned().collect();
|
||||
out.extend(own);
|
||||
out
|
||||
}
|
||||
|
||||
/// The rule that applies to a body: its own, else the active one.
|
||||
pub fn plastic_rule_for(&self, body: &str) -> Option<PlasticRule> {
|
||||
self.plastic.assigned.get(body).or(self.plastic.active.as_ref()).and_then(|n| self.plastic_rule(n))
|
||||
}
|
||||
|
||||
pub fn plastic_values(&self, vals: &BTreeMap<String, Value>, r: &PlasticRule) -> Result<PlasticValues> {
|
||||
let look = |n: &str| vals.get(n).copied().ok_or_else(|| DocError::Expr(format!("unknown parameter `{n}`")));
|
||||
let t = expr::eval_with(&r.thickness, &look)?.to_kind(Kind::Length)?;
|
||||
if !(t > 0.0 && t < 1e4) {
|
||||
return Err(DocError::Invalid("the plastic thickness must be positive".into()));
|
||||
}
|
||||
let with_t = |e: &str, k: Kind| -> Result<f64> {
|
||||
let f = |n: &str| if n == "Thickness" && !vals.contains_key("Thickness") { Ok(Value::length(t)) } else { look(n) };
|
||||
let v = expr::eval_with(e, &f)?.to_kind(k)?;
|
||||
if !v.is_finite() || v < 0.0 {
|
||||
return Err(DocError::Invalid(format!("plastic rule `{}`: `{e}` must not be negative", r.name)));
|
||||
}
|
||||
Ok(v)
|
||||
};
|
||||
Ok(PlasticValues {
|
||||
thickness: t,
|
||||
nominal_radius: with_t(&r.nominal_radius, Kind::Length)?,
|
||||
clearance: with_t(&r.clearance, Kind::Length)?,
|
||||
knife_edge: with_t(&r.knife_edge, Kind::Length)?,
|
||||
reveal_height: with_t(&r.reveal_height, Kind::Length)?,
|
||||
thickness_variation: with_t(&r.thickness_variation, Kind::Length)?,
|
||||
draft: with_t(&r.draft, Kind::Angle)?,
|
||||
max_thickness: with_t(&r.max_thickness, Kind::Length)?,
|
||||
min_thickness: with_t(&r.min_thickness, Kind::Length)?,
|
||||
min_draft: with_t(&r.min_draft, Kind::Angle)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn library_values() {
|
||||
let d = Document::default();
|
||||
let vals = BTreeMap::new();
|
||||
let abs = d.plastic_values(&vals, &PlasticRule::library()[0]).unwrap();
|
||||
assert_eq!((abs.thickness, abs.nominal_radius, abs.clearance), (1.5, 0.75, 0.1));
|
||||
assert!((abs.thickness_variation - 0.225).abs() < 1e-12);
|
||||
assert!((abs.draft - 2f64.to_radians()).abs() < 1e-12);
|
||||
let pp_in = d.plastic_values(&vals, &d.plastic_rule("Polypropylene (PP) (0.1 in)").unwrap()).unwrap();
|
||||
assert!((pp_in.thickness - 2.54).abs() < 1e-9 && (pp_in.nominal_radius - 1.27).abs() < 1e-9 && (pp_in.clearance - 0.1016).abs() < 1e-9);
|
||||
assert_eq!(d.plastic_rules().len(), 6);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user