mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 18:45:55 +00:00
solidcraft-smlib: the fork's sweeps, from lists of curves
Brep::extrude (straight or drafted), Brep::revolve, Brep::sweep (turning with the path or only moving along it), Brep::loft and Brep::screw_sweep wrap the fork's new smlib_sweeps.cpp shims. Profiles, paths and sections are lists of sweeps::Curve (lines, circular arcs, NURBS), turned into the C API's curve lists here; the FFI is ffi/sweeps.rs. SMLIB_REV moves to the fork head that has the shims (c33df0f, on capi-solidcraft 1499681). Tests check each sweep against closed-form volumes and SMLib's check, and hostile curve lists. (The crate's test build currently stops at import/tests.rs, which calls import::surface without its new analytic_planes argument; that is the import's, not this change's.) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWhMwdqJYQKQq8RCLSL1LT
This commit is contained in:
@@ -1 +1 @@
|
||||
1f4d4a173e9fae1f149d5457b2e1cb0a8d62d005
|
||||
ffcf56d195d1d0c98c5190dd993d61732f099642
|
||||
|
||||
@@ -185,6 +185,7 @@ pub mod history;
|
||||
pub mod measure;
|
||||
pub mod ops;
|
||||
pub mod ssi;
|
||||
pub mod sweeps;
|
||||
|
||||
/// Where [`set_log`] sends SMLib's log lines.
|
||||
#[cfg(feature = "native")]
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
//! The sweeps block of `capi/include/smlib.h` (`smlib_extrude`, `smlib_revolve`, `smlib_sweep`,
|
||||
//! `smlib_loft`, `smlib_screw_sweep`; the fork's `capi/src/smlib_sweeps.cpp`), declared by hand.
|
||||
//! Part of the FFI boundary, like its parent: called only through [`crate::sweeps`], which builds
|
||||
//! the curve lists.
|
||||
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
use super::Handle;
|
||||
#[cfg(not(feature = "native"))]
|
||||
use super::NOT_BUILT;
|
||||
#[cfg(feature = "native")]
|
||||
use super::{ERR_ARG, RawBrep, new_brep};
|
||||
|
||||
pub const CURVE_LINE: i64 = 1;
|
||||
pub const CURVE_ARC: i64 = 2;
|
||||
pub const CURVE_NURBS: i64 = 3;
|
||||
pub const CURVE_MAX_CURVES: usize = 4096;
|
||||
pub const CURVE_MAX_DEGREE: usize = 25;
|
||||
pub const CURVE_MAX_POINTS: usize = 100_000;
|
||||
|
||||
pub const SWEEP_FOLLOW: i32 = 0;
|
||||
pub const SWEEP_TRANSLATE: i32 = 1;
|
||||
|
||||
/// A curve list as the C API reads it: kinds (with a NURBS curve's degree and point count) and
|
||||
/// each curve's numbers.
|
||||
pub struct List<'a> {
|
||||
pub kinds: &'a [i64],
|
||||
pub reals: &'a [f64],
|
||||
}
|
||||
|
||||
#[cfg(feature = "native")]
|
||||
unsafe extern "C" {
|
||||
fn smlib_extrude(
|
||||
kinds: *const i64,
|
||||
n_kinds: i64,
|
||||
reals: *const f64,
|
||||
n_reals: i64,
|
||||
direction: *const f64,
|
||||
distance: f64,
|
||||
draft: f64,
|
||||
cap: i32,
|
||||
out: *mut *mut RawBrep,
|
||||
) -> i32;
|
||||
fn smlib_revolve(
|
||||
kinds: *const i64,
|
||||
n_kinds: i64,
|
||||
reals: *const f64,
|
||||
n_reals: i64,
|
||||
origin: *const f64,
|
||||
direction: *const f64,
|
||||
angle: f64,
|
||||
cap: i32,
|
||||
out: *mut *mut RawBrep,
|
||||
) -> i32;
|
||||
fn smlib_sweep(
|
||||
kinds: *const i64,
|
||||
n_kinds: i64,
|
||||
reals: *const f64,
|
||||
n_reals: i64,
|
||||
path_kinds: *const i64,
|
||||
n_path_kinds: i64,
|
||||
path_reals: *const f64,
|
||||
n_path_reals: i64,
|
||||
frame: i32,
|
||||
cap: i32,
|
||||
out: *mut *mut RawBrep,
|
||||
) -> i32;
|
||||
fn smlib_loft(
|
||||
kinds: *const i64,
|
||||
n_kinds: i64,
|
||||
reals: *const f64,
|
||||
n_reals: i64,
|
||||
section_sizes: *const i64,
|
||||
n_sections: i64,
|
||||
degree: i32,
|
||||
cap: i32,
|
||||
out: *mut *mut RawBrep,
|
||||
) -> i32;
|
||||
fn smlib_screw_sweep(
|
||||
kinds: *const i64,
|
||||
n_kinds: i64,
|
||||
reals: *const f64,
|
||||
n_reals: i64,
|
||||
origin: *const f64,
|
||||
direction: *const f64,
|
||||
pitch: f64,
|
||||
turns: f64,
|
||||
tolerance: f64,
|
||||
out: *mut *mut RawBrep,
|
||||
) -> i32;
|
||||
}
|
||||
|
||||
/// The list's arrays and lengths as the C API takes them.
|
||||
#[cfg(feature = "native")]
|
||||
fn raw(l: &List) -> Result<(*const i64, i64, *const f64, i64), i32> {
|
||||
let nk = i64::try_from(l.kinds.len()).map_err(|_| ERR_ARG)?;
|
||||
let nr = i64::try_from(l.reals.len()).map_err(|_| ERR_ARG)?;
|
||||
Ok((l.kinds.as_ptr(), nk, l.reals.as_ptr(), nr))
|
||||
}
|
||||
|
||||
#[cfg(feature = "native")]
|
||||
pub fn extrude(profile: &List, direction: &[f64; 3], distance: f64, draft: f64, cap: bool) -> Result<Handle, i32> {
|
||||
let (k, nk, r, nr) = raw(profile)?;
|
||||
new_brep(|out| unsafe { smlib_extrude(k, nk, r, nr, direction.as_ptr(), distance, draft, i32::from(cap), out) })
|
||||
}
|
||||
|
||||
#[cfg(feature = "native")]
|
||||
pub fn revolve(profile: &List, origin: &[f64; 3], direction: &[f64; 3], angle: f64, cap: bool) -> Result<Handle, i32> {
|
||||
let (k, nk, r, nr) = raw(profile)?;
|
||||
new_brep(|out| unsafe { smlib_revolve(k, nk, r, nr, origin.as_ptr(), direction.as_ptr(), angle, i32::from(cap), out) })
|
||||
}
|
||||
|
||||
#[cfg(feature = "native")]
|
||||
pub fn sweep(profile: &List, path: &List, frame: i32, cap: bool) -> Result<Handle, i32> {
|
||||
let (k, nk, r, nr) = raw(profile)?;
|
||||
let (pk, npk, pr, npr) = raw(path)?;
|
||||
new_brep(|out| unsafe { smlib_sweep(k, nk, r, nr, pk, npk, pr, npr, frame, i32::from(cap), out) })
|
||||
}
|
||||
|
||||
#[cfg(feature = "native")]
|
||||
pub fn loft(sections: &List, sizes: &[i64], degree: i32, cap: bool) -> Result<Handle, i32> {
|
||||
let (k, nk, r, nr) = raw(sections)?;
|
||||
let ns = i64::try_from(sizes.len()).map_err(|_| ERR_ARG)?;
|
||||
new_brep(|out| unsafe { smlib_loft(k, nk, r, nr, sizes.as_ptr(), ns, degree, i32::from(cap), out) })
|
||||
}
|
||||
|
||||
#[cfg(feature = "native")]
|
||||
pub fn screw_sweep(profile: &List, origin: &[f64; 3], direction: &[f64; 3], pitch: f64, turns: f64, tolerance: f64) -> Result<Handle, i32> {
|
||||
let (k, nk, r, nr) = raw(profile)?;
|
||||
new_brep(|out| unsafe { smlib_screw_sweep(k, nk, r, nr, origin.as_ptr(), direction.as_ptr(), pitch, turns, tolerance, out) })
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "native"))]
|
||||
pub fn extrude(_: &List, _: &[f64; 3], _: f64, _: f64, _: bool) -> Result<Handle, i32> {
|
||||
Err(NOT_BUILT)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "native"))]
|
||||
pub fn revolve(_: &List, _: &[f64; 3], _: &[f64; 3], _: f64, _: bool) -> Result<Handle, i32> {
|
||||
Err(NOT_BUILT)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "native"))]
|
||||
pub fn sweep(_: &List, _: &List, _: i32, _: bool) -> Result<Handle, i32> {
|
||||
Err(NOT_BUILT)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "native"))]
|
||||
pub fn loft(_: &List, _: &[i64], _: i32, _: bool) -> Result<Handle, i32> {
|
||||
Err(NOT_BUILT)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "native"))]
|
||||
pub fn screw_sweep(_: &List, _: &[f64; 3], _: &[f64; 3], _: f64, _: f64, _: f64) -> Result<Handle, i32> {
|
||||
Err(NOT_BUILT)
|
||||
}
|
||||
@@ -24,6 +24,7 @@ pub mod import;
|
||||
mod measure;
|
||||
mod ops;
|
||||
pub mod ssi;
|
||||
pub mod sweeps;
|
||||
|
||||
pub use check::{CheckLevel, CheckReport};
|
||||
pub use measure::{MEASURE_ACCURACY, MassProperties};
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
//! SMLib's sweeps: extrusion (straight or drafted), revolution, sweep along a path, loft through
|
||||
//! sections and the screw sweep of a thread or coil. Profiles, paths and sections are lists of
|
||||
//! [`Curve`]s; each sweep returns a new body.
|
||||
//!
|
||||
//! With `cap`, the profile's loops close the ends and the result must be a closed manifold solid
|
||||
//! ([`Error::NotSolid`] otherwise); without, it is the swept sheet.
|
||||
|
||||
use crate::{Brep, Error, Result, error, ffi, init};
|
||||
|
||||
/// A 3D curve of a profile, path or section.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Curve {
|
||||
Line {
|
||||
start: [f64; 3],
|
||||
end: [f64; 3],
|
||||
},
|
||||
/// Counter-clockwise about the unit `axis` from angle `start` to `end` (radians,
|
||||
/// 0 < end - start <= 2 pi), angle 0 along the unit `reference` (square to `axis`).
|
||||
Arc {
|
||||
center: [f64; 3],
|
||||
axis: [f64; 3],
|
||||
reference: [f64; 3],
|
||||
radius: f64,
|
||||
start: f64,
|
||||
end: f64,
|
||||
},
|
||||
/// Control points (x, y, z, weight > 0) and the clamped knot vector (points + degree + 1
|
||||
/// knots).
|
||||
Nurbs {
|
||||
degree: usize,
|
||||
points: Vec<[f64; 4]>,
|
||||
knots: Vec<f64>,
|
||||
},
|
||||
}
|
||||
|
||||
/// How the profile is carried along a [`Brep::sweep`] path.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum SweepFrame {
|
||||
/// It turns with the path, keeping its attitude to the path's tangent.
|
||||
Follow,
|
||||
/// It only moves along the path, keeping its orientation.
|
||||
Translate,
|
||||
}
|
||||
|
||||
/// Most curves one list may hold (the C API's limit).
|
||||
pub const MAX_CURVES: usize = ffi::sweeps::CURVE_MAX_CURVES;
|
||||
|
||||
/// The curves as the C API's curve list.
|
||||
#[derive(Default)]
|
||||
struct List {
|
||||
kinds: Vec<i64>,
|
||||
reals: Vec<f64>,
|
||||
}
|
||||
|
||||
impl List {
|
||||
fn of<'a>(curves: impl IntoIterator<Item = &'a Curve>) -> Result<List> {
|
||||
let mut l = List::default();
|
||||
for c in curves {
|
||||
l.push(c)?;
|
||||
}
|
||||
Ok(l)
|
||||
}
|
||||
|
||||
fn push(&mut self, c: &Curve) -> Result<()> {
|
||||
if self.kinds.len() >= 3 * MAX_CURVES {
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
match c {
|
||||
Curve::Line { start, end } => {
|
||||
self.kinds.push(ffi::sweeps::CURVE_LINE);
|
||||
self.reals.extend_from_slice(start);
|
||||
self.reals.extend_from_slice(end);
|
||||
}
|
||||
Curve::Arc { center, axis, reference, radius, start, end } => {
|
||||
self.kinds.push(ffi::sweeps::CURVE_ARC);
|
||||
self.reals.extend_from_slice(center);
|
||||
self.reals.extend_from_slice(axis);
|
||||
self.reals.extend_from_slice(reference);
|
||||
self.reals.extend_from_slice(&[*radius, *start, *end]);
|
||||
}
|
||||
Curve::Nurbs { degree, points, knots } => {
|
||||
if *degree == 0
|
||||
|| *degree > ffi::sweeps::CURVE_MAX_DEGREE
|
||||
|| points.len() > ffi::sweeps::CURVE_MAX_POINTS
|
||||
|| knots.len() != points.len() + degree + 1
|
||||
{
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
let d = i64::try_from(*degree).map_err(|_| Error::Argument)?;
|
||||
let n = i64::try_from(points.len()).map_err(|_| Error::Argument)?;
|
||||
self.kinds.extend_from_slice(&[ffi::sweeps::CURVE_NURBS, d, n]);
|
||||
for p in points {
|
||||
self.reals.extend_from_slice(p);
|
||||
}
|
||||
self.reals.extend_from_slice(knots);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn raw(&self) -> ffi::sweeps::List<'_> {
|
||||
ffi::sweeps::List { kinds: &self.kinds, reals: &self.reals }
|
||||
}
|
||||
}
|
||||
|
||||
impl Brep {
|
||||
/// The planar `profile` extruded `distance` along the unit `direction` (square to its plane).
|
||||
/// `draft` (radians) leans the walls: positive inward, negative outward; a drafted profile is
|
||||
/// one closed loop, its curves in order, and `direction` is its normal (either way round).
|
||||
pub fn extrude(profile: &[Curve], direction: [f64; 3], distance: f64, draft: f64, cap: bool) -> Result<Brep> {
|
||||
init()?;
|
||||
let l = List::of(profile)?;
|
||||
ffi::sweeps::extrude(&l.raw(), &direction, distance, draft, cap).map(Brep).map_err(error)
|
||||
}
|
||||
|
||||
/// The planar `profile` turned `angle` radians (0 < angle <= 2 pi) right-handed about the
|
||||
/// axis through `origin` along `direction`, in the profile's plane (touching it at most).
|
||||
pub fn revolve(profile: &[Curve], origin: [f64; 3], direction: [f64; 3], angle: f64, cap: bool) -> Result<Brep> {
|
||||
init()?;
|
||||
let l = List::of(profile)?;
|
||||
ffi::sweeps::revolve(&l.raw(), &origin, &direction, angle, cap).map(Brep).map_err(error)
|
||||
}
|
||||
|
||||
/// `profile` swept along `path` (end-to-end curves), carried with the path's start: the
|
||||
/// profile keeps its place relative to it. The walls are B-spline approximations, one per
|
||||
/// stretch of profile between tangent breaks.
|
||||
pub fn sweep(profile: &[Curve], path: &[Curve], frame: SweepFrame, cap: bool) -> Result<Brep> {
|
||||
init()?;
|
||||
let (l, p) = (List::of(profile)?, List::of(path)?);
|
||||
let frame = match frame {
|
||||
SweepFrame::Follow => ffi::sweeps::SWEEP_FOLLOW,
|
||||
SweepFrame::Translate => ffi::sweeps::SWEEP_TRANSLATE,
|
||||
};
|
||||
ffi::sweeps::sweep(&l.raw(), &p.raw(), frame, cap).map(Brep).map_err(error)
|
||||
}
|
||||
|
||||
/// A loft (skin) through `sections` (at least two closed loops), `degree` (1 ruled, 2 or 3)
|
||||
/// across them.
|
||||
pub fn loft(sections: &[Vec<Curve>], degree: u32, cap: bool) -> Result<Brep> {
|
||||
init()?;
|
||||
if sections.len() < 2 || sections.len() > MAX_CURVES || !(1..=3).contains(°ree) {
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
let l = List::of(sections.iter().flatten())?;
|
||||
let sizes: Vec<i64> = sections.iter().map(|s| i64::try_from(s.len()).map_err(|_| Error::Argument)).collect::<Result<_>>()?;
|
||||
let degree = i32::try_from(degree).map_err(|_| Error::Argument)?;
|
||||
ffi::sweeps::loft(&l.raw(), &sizes, degree, cap).map(Brep).map_err(error)
|
||||
}
|
||||
|
||||
/// `profile` carried by a screw motion about the axis through `origin` along `direction`:
|
||||
/// `turns` right-handed turns about it, rising `pitch` (signed) per turn, capped at both ends,
|
||||
/// its walls within about `tolerance` of the true screw surfaces.
|
||||
pub fn screw_sweep(profile: &[Curve], origin: [f64; 3], direction: [f64; 3], pitch: f64, turns: f64, tolerance: f64) -> Result<Brep> {
|
||||
init()?;
|
||||
let l = List::of(profile)?;
|
||||
ffi::sweeps::screw_sweep(&l.raw(), &origin, &direction, pitch, turns, tolerance).map(Brep).map_err(error)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "sweeps_tests.rs"]
|
||||
mod tests;
|
||||
@@ -0,0 +1,78 @@
|
||||
//! The sweeps against analytic volumes, each result a valid closed solid (built with SMLib only).
|
||||
#![cfg(feature = "native")]
|
||||
|
||||
use std::f64::consts::{PI, TAU};
|
||||
|
||||
use super::*;
|
||||
use crate::CheckLevel;
|
||||
|
||||
fn solid_volume(b: &Brep, what: &str) -> f64 {
|
||||
let report = b.check(CheckLevel::All).unwrap();
|
||||
assert!(report.is_valid(), "{what}: {} failed validity tests:\n{}", report.failures, report.text);
|
||||
b.volume(1e-7).unwrap()
|
||||
}
|
||||
|
||||
fn assert_close(got: f64, want: f64, rel: f64, what: &str) {
|
||||
assert!((got - want).abs() <= rel * want.abs(), "{what}: {got} vs analytic {want}");
|
||||
}
|
||||
|
||||
fn polygon(pts: &[[f64; 3]]) -> Vec<Curve> {
|
||||
(0..pts.len()).map(|i| Curve::Line { start: pts[i], end: pts[(i + 1) % pts.len()] }).collect()
|
||||
}
|
||||
|
||||
fn circle(center: [f64; 3], radius: f64) -> Curve {
|
||||
Curve::Arc { center, axis: [0.0, 0.0, 1.0], reference: [1.0, 0.0, 0.0], radius, start: 0.0, end: TAU }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extrusions_straight_and_drafted() {
|
||||
let rect = polygon(&[[0.0, 0.0, 0.0], [10.0, 0.0, 0.0], [10.0, 6.0, 0.0], [0.0, 6.0, 0.0]]);
|
||||
let b = Brep::extrude(&rect, [0.0, 0.0, 1.0], 5.0, 0.0, true).unwrap();
|
||||
assert_close(solid_volume(&b, "prism"), 300.0, 1e-9, "prism");
|
||||
// A disc drafted 5 degrees in over 4 mm: a frustum of radii 5 and 5 - 4 tan 5.
|
||||
let r2 = 5.0 - 4.0 * 5f64.to_radians().tan();
|
||||
let b = Brep::extrude(&[circle([0.0; 3], 5.0)], [0.0, 0.0, 1.0], 4.0, 5f64.to_radians(), true).unwrap();
|
||||
assert_close(solid_volume(&b, "frustum"), PI * 4.0 / 3.0 * (25.0 + r2 * r2 + 5.0 * r2), 1e-6, "drafted disc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn revolutions_whole_and_partial() {
|
||||
// A half disc on the z axis, in the xz plane: a sphere; a quarter turn of a square off it.
|
||||
let half = vec![
|
||||
Curve::Line { start: [0.0, 0.0, -5.0], end: [0.0, 0.0, 5.0] },
|
||||
Curve::Arc { center: [0.0; 3], axis: [0.0, -1.0, 0.0], reference: [0.0, 0.0, 1.0], radius: 5.0, start: 0.0, end: PI },
|
||||
];
|
||||
let b = Brep::revolve(&half, [0.0; 3], [0.0, 0.0, 1.0], TAU, true).unwrap();
|
||||
assert_close(solid_volume(&b, "sphere"), 4.0 / 3.0 * PI * 125.0, 1e-5, "sphere");
|
||||
let sq = polygon(&[[6.0, 0.0, 0.0], [8.0, 0.0, 0.0], [8.0, 0.0, 2.0], [6.0, 0.0, 2.0]]);
|
||||
let b = Brep::revolve(&sq, [0.0; 3], [0.0, 0.0, 1.0], PI / 2.0, true).unwrap();
|
||||
assert_close(solid_volume(&b, "quarter ring"), PI / 2.0 * 7.0 * 4.0, 1e-6, "quarter ring");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sweeps_lofts_and_screws() {
|
||||
let disc = [circle([5.0, 0.0, 0.0], 2.0)];
|
||||
let bend =
|
||||
[Curve::Arc { center: [-20.0, 0.0, 0.0], axis: [0.0, -1.0, 0.0], reference: [1.0, 0.0, 0.0], radius: 20.0, start: 0.0, end: PI / 2.0 }];
|
||||
// The disc keeps its 5 mm offset from the path: Pappus about the arc's centre at 25.
|
||||
let b = Brep::sweep(&disc, &bend, SweepFrame::Follow, true).unwrap();
|
||||
assert_close(solid_volume(&b, "bent tube"), PI * 4.0 * (PI / 2.0 * 25.0), 1e-4, "bent tube");
|
||||
let sections = vec![vec![circle([0.0; 3], 5.0)], vec![circle([0.0, 0.0, 6.0], 3.0)]];
|
||||
let b = Brep::loft(§ions, 1, true).unwrap();
|
||||
assert_close(solid_volume(&b, "frustum"), PI * 2.0 * (25.0 + 9.0 + 15.0), 1e-5, "lofted frustum");
|
||||
let tooth = polygon(&[[4.0, 0.0, 0.0], [4.4, 0.0, 0.0], [4.4, 0.0, 0.4], [4.0, 0.0, 0.4]]);
|
||||
let b = Brep::screw_sweep(&tooth, [0.0; 3], [0.0, 0.0, 1.0], 1.0, 3.0, 1e-4).unwrap();
|
||||
assert_close(solid_volume(&b, "screw"), 0.16 * TAU * 4.2 * 3.0, 1e-4, "screw sweep");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hostile_curve_lists_are_refused() {
|
||||
let nan = [Curve::Line { start: [0.0, f64::NAN, 0.0], end: [1.0, 0.0, 0.0] }];
|
||||
assert_eq!(Brep::extrude(&nan, [0.0, 0.0, 1.0], 1.0, 0.0, true).err(), Some(Error::Argument));
|
||||
let bad = [Curve::Nurbs { degree: 3, points: vec![[0.0, 0.0, 0.0, 1.0]; 2], knots: vec![0.0; 6] }];
|
||||
assert_eq!(Brep::extrude(&bad, [0.0, 0.0, 1.0], 1.0, 0.0, true).err(), Some(Error::Argument));
|
||||
assert_eq!(Brep::loft(&[vec![circle([0.0; 3], 1.0)]], 1, true).err(), Some(Error::Argument));
|
||||
let sq = polygon(&[[0.0; 3], [1.0, 0.0, 0.0], [1.0, 1.0, 0.0], [0.0, 1.0, 0.0]]);
|
||||
assert_eq!(Brep::revolve(&sq, [0.0; 3], [0.0, 1.0, 0.0], 7.0, true).err(), Some(Error::Argument));
|
||||
assert_eq!(Brep::screw_sweep(&sq, [0.0; 3], [0.0, 0.0, 1.0], 0.0, 1.0, 1e-4).err(), Some(Error::Argument));
|
||||
}
|
||||
Reference in New Issue
Block a user