mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 18:45:55 +00:00
Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d4f923a3e6 | |||
| bbb98928fb | |||
| 8e2b0cd60d | |||
| afd81380ab | |||
| 3a5195b0a0 | |||
| ee6b85605a | |||
| aa1fe28ddf | |||
| 5b0eb08473 | |||
| 9df8837d43 | |||
| 409ab1ed45 | |||
| 5e03fe2f4b | |||
| 37a586dd61 | |||
| a79f40a711 | |||
| b5728f284b | |||
| 5965439a15 | |||
| 586a358ed4 | |||
| 7770f8982d | |||
| 72ad6383ef | |||
| 0537623a75 | |||
| 65d288227e | |||
| fedb3bcdb1 | |||
| 097b8b7550 | |||
| af7ca3a8a2 | |||
| 27ddb725c7 | |||
| a06bdf5cfc | |||
| 33fdcdb3a0 | |||
| c07ab5f8f9 | |||
| fea32f8aac | |||
| 4927214976 | |||
| b621819c0c | |||
| ec552a5300 | |||
| e0cc740cc3 | |||
| 5de48e916d | |||
| 80af7bbe3e | |||
| 191e983423 | |||
| 2b9445b049 | |||
| 7ddf48ca16 | |||
| d2f6848377 | |||
| 0b6d6fecd8 | |||
| fa691707c2 | |||
| b9a55c74e2 | |||
| 2bb9c81069 | |||
| cedb51b8ea | |||
| 66d13059dd | |||
| 800ea40c0d | |||
| 20442eecd3 | |||
| 22d1cd7032 | |||
| 6a0dc18655 | |||
| e006dbf1af | |||
| 8241a750fa | |||
| 41779cf7bb | |||
| b01b9689c8 | |||
| 4426da6f5d | |||
| 5ead82e627 | |||
| c29b0d2f13 | |||
| 480fd8d517 | |||
| 4c09fa5355 |
@@ -1,6 +1,5 @@
|
||||
# The documentation site on GitHub Pages: the book (docs/book) at https://docs.solidcraft.io/
|
||||
# and the web app at /app/ (the latest release), /app-rc/ (the newest release candidate) and
|
||||
# /app-edge/ (this commit). Needs Settings → Pages → Source = GitHub Actions; skipped while the
|
||||
# and the bundled web app at /app/. Needs Settings → Pages → Source = GitHub Actions; skipped while the
|
||||
# repository is private.
|
||||
name: Pages
|
||||
|
||||
@@ -67,10 +66,7 @@ jobs:
|
||||
mkdir -p site
|
||||
cp -r target/book/. site/
|
||||
bash docs/book/seo.sh site # canonical links, robots.txt, sitemap.xml
|
||||
- name: Add the web app channels (stable and release candidate from their releases, edge from this build)
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: bash packaging/web-channels.sh site dist/web
|
||||
cp -r dist/web site/app
|
||||
- uses: actions/configure-pages@v5
|
||||
id: pages
|
||||
- name: Install Flatpak repository tools
|
||||
|
||||
Generated
-23
@@ -4059,16 +4059,6 @@ dependencies = [
|
||||
"thiserror 2.0.21",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcore-record"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/tangency-labs/solidcore?rev=f5e8e9a29e0c9b7cf6c4040a5bc8878382b3c62f#f5e8e9a29e0c9b7cf6c4040a5bc8878382b3c62f"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.21",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft"
|
||||
version = "0.40.0-rc.1"
|
||||
@@ -4117,7 +4107,6 @@ dependencies = [
|
||||
"image",
|
||||
"log",
|
||||
"serde_json",
|
||||
"solidcore-record",
|
||||
"solidcraft-engine",
|
||||
"solidcraft-gpu",
|
||||
"solidcraft-kernel",
|
||||
@@ -4141,7 +4130,6 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"solidcore-record",
|
||||
"solidcraft-derive",
|
||||
"solidcraft-geom",
|
||||
"solidcraft-kernel",
|
||||
@@ -4162,7 +4150,6 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"solidcore-record",
|
||||
"solidcraft-doc",
|
||||
"solidcraft-geom",
|
||||
"solidcraft-io",
|
||||
@@ -4181,7 +4168,6 @@ version = "0.40.0-rc.1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"solidcore-record",
|
||||
"thiserror 2.0.21",
|
||||
]
|
||||
|
||||
@@ -4196,7 +4182,6 @@ dependencies = [
|
||||
"robust",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"solidcore-record",
|
||||
"solidcraft-engine",
|
||||
"solidcraft-geom",
|
||||
"solidcraft-io",
|
||||
@@ -4223,7 +4208,6 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"solidcore-record",
|
||||
"solidcraft-doc",
|
||||
"solidcraft-geom",
|
||||
"solidcraft-kernel",
|
||||
@@ -4242,7 +4226,6 @@ dependencies = [
|
||||
"rayon",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"solidcore-record",
|
||||
"solidcraft-geom",
|
||||
"solidcraft-profile",
|
||||
"solidcraft-smlib",
|
||||
@@ -4280,7 +4263,6 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"solidcore-record",
|
||||
"solidcraft-geom",
|
||||
"web-time",
|
||||
"weezl",
|
||||
@@ -4293,7 +4275,6 @@ dependencies = [
|
||||
"harfrust",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"solidcore-record",
|
||||
"solidcraft-geom",
|
||||
"thiserror 2.0.21",
|
||||
"ttf-parser",
|
||||
@@ -4308,7 +4289,6 @@ dependencies = [
|
||||
"log",
|
||||
"serde_json",
|
||||
"solidcore",
|
||||
"solidcore-record",
|
||||
"solidcraft-geom",
|
||||
"solidcraft-profile",
|
||||
]
|
||||
@@ -4324,7 +4304,6 @@ dependencies = [
|
||||
name = "solidcraft-step"
|
||||
version = "0.40.0-rc.1"
|
||||
dependencies = [
|
||||
"solidcore-record",
|
||||
"solidcraft-geom",
|
||||
"solidcraft-profile",
|
||||
]
|
||||
@@ -4350,7 +4329,6 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"solidcore-record",
|
||||
"solidcraft-engine",
|
||||
"solidcraft-gpu",
|
||||
"solidcraft-space-mouse",
|
||||
@@ -5776,7 +5754,6 @@ checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7"
|
||||
name = "xtask"
|
||||
version = "0.40.0-rc.1"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"proc-macro2",
|
||||
"quick-xml",
|
||||
"serde",
|
||||
|
||||
@@ -36,9 +36,6 @@ solidcraft-3dx-client = { path = "crates/threedx-client" }
|
||||
# local checkout, add `[patch."https://github.com/tangency-labs/solidcore"] solidcore = { path =
|
||||
# "../solidcore/crates/solidcore" }` to a `.cargo/config.toml` outside the repository.
|
||||
solidcore = { git = "https://github.com/tangency-labs/solidcore", rev = "8ddce901671f1cebd9214a31d21895d725e886c8" }
|
||||
# Geometry as data (solidcore's layers §1, "Record"): the B-rep record, its structural validation
|
||||
# and the native body file. Pinned like solidcore; for a local checkout, patch it as above.
|
||||
solidcore-record = { git = "https://github.com/tangency-labs/solidcore", rev = "f5e8e9a29e0c9b7cf6c4040a5bc8878382b3c62f" }
|
||||
# test tooling: a dev-dependency only (xtask/src/layers.rs)
|
||||
solidcraft-testkit = { path = "crates/testkit" }
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Third-party software in SolidCraft
|
||||
|
||||
SolidCraft (the desktop app, solidcraft-cli and the web app) is built from the 537 crates below.
|
||||
SolidCraft (the desktop app, solidcraft-cli and the web app) is built from the 536 crates below.
|
||||
Generated by `cargo xtask licences` from Cargo.lock and the crates' own licence files.
|
||||
SolidCraft's own licence is in LICENSE-MIT and LICENSE-APACHE, and NOTICE.
|
||||
|
||||
@@ -379,7 +379,6 @@ smithay-client-toolkit 0.20.0 (MIT)
|
||||
smithay-clipboard 0.7.3 (MIT)
|
||||
smol_str 0.2.2 (MIT OR Apache-2.0)
|
||||
solidcore 1.0.1 (Apache-2.0)
|
||||
solidcore-record 0.1.0 (Apache-2.0)
|
||||
spade 2.15.1 (MIT OR Apache-2.0)
|
||||
spirv 0.4.0+sdk-1.4.341.0 (Apache-2.0)
|
||||
stable_deref_trait 1.2.1 (MIT OR Apache-2.0)
|
||||
@@ -16032,7 +16031,6 @@ profiling 1.0.18: MIT OR Apache-2.0 (https://github.com/aclysma/profiling)
|
||||
r-efi 5.3.0: MIT OR Apache-2.0 OR LGPL-2.1-or-later (https://github.com/r-efi/r-efi)
|
||||
r-efi 6.0.0: MIT OR Apache-2.0 OR LGPL-2.1-or-later (https://github.com/r-efi/r-efi)
|
||||
rustls-platform-verifier-android 0.2.0: MIT OR Apache-2.0 (https://github.com/rustls/rustls-platform-verifier)
|
||||
solidcore-record 0.1.0: Apache-2.0 (https://github.com/tangency-labs/solidcore)
|
||||
spirv 0.4.0+sdk-1.4.341.0: Apache-2.0 (https://github.com/gfx-rs/rspirv)
|
||||
|
||||
================================================================================
|
||||
|
||||
@@ -28,7 +28,6 @@ raytrace-cpu = ["solidcraft-engine/raytrace-cpu"]
|
||||
profile = ["solidcraft-engine/profile"]
|
||||
|
||||
[dependencies]
|
||||
solidcore-record = { workspace = true }
|
||||
solidcraft-engine = { workspace = true }
|
||||
solidcraft-gpu = { workspace = true }
|
||||
solidcraft-mcp = { workspace = true }
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
//! are never compared with each other. See tests/kernel-cases/README.md.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solidcraft_engine::geom::prelude::*;
|
||||
|
||||
use crate::oracle::{ABS_FRAC, Checks, REL_TOL, totals, v3};
|
||||
use crate::{read_json, recipe};
|
||||
@@ -63,10 +62,10 @@ const MESH_TOL: f64 = 0.01;
|
||||
/// A mesh's edges used by one triangle only (open) and by more than two (over-shared). Vertices
|
||||
/// closer than `weld` (mm) are one vertex: a mesh repeats a vertex for each face, and faces
|
||||
/// meeting at an edge may place it a rounding error apart.
|
||||
fn open_edges(positions: &[solidcore_record::Vec3], triangles: &[[u32; 3]], weld: f64) -> (usize, usize) {
|
||||
fn open_edges(positions: &[solidcraft_engine::geom::Vec3], triangles: &[[u32; 3]], weld: f64) -> (usize, usize) {
|
||||
use std::collections::HashMap;
|
||||
let weld = if weld.is_finite() && weld > 0.0 { weld } else { 1e-4 };
|
||||
let cell = |p: &solidcore_record::Vec3| [p.x, p.y, p.z].map(|x| (x / weld).floor() as i64);
|
||||
let cell = |p: &solidcraft_engine::geom::Vec3| [p.x, p.y, p.z].map(|x| (x / weld).floor() as i64);
|
||||
// Union-find over positions, joining each vertex with any within `weld` in the 27 cells round it.
|
||||
let mut parent: Vec<usize> = (0..positions.len()).collect();
|
||||
fn find(parent: &mut [usize], mut i: usize) -> usize {
|
||||
@@ -126,7 +125,7 @@ const SEAM_ANGLE_DEG: f64 = 1.0;
|
||||
/// the patch's boundary, where its neighbours' own G1 seams end and no surface is G2 to both),
|
||||
/// with how many seams and stations clear there were. An error where the seams can't be measured.
|
||||
fn curvature_jumps(body: &solidcraft_engine::kernel::Body, near: [f64; 3], within: f64, clear: f64) -> Result<Value, String> {
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
let tol =
|
||||
solidcraft_engine::kernel::ContinuityTolerances { gap_mm: 1e-3, angle_deg: SEAM_ANGLE_DEG, curvature_per_mm: 0.0, ..Default::default() };
|
||||
let report = solidcraft_engine::kernel::seam_diagnostics(body, &[], SEAM_SAMPLES, tol).map_err(|e| e.to_string())?;
|
||||
@@ -183,7 +182,7 @@ fn continuity_seams(b: &solidcraft_engine::doc::ModelBody, edges: &Value) -> Res
|
||||
if let Some(points) = edges.as_array() {
|
||||
let picks = points
|
||||
.iter()
|
||||
.map(|p| v3(p).map(|[x, y, z]| solidcraft_engine::kernel::EdgePick::Point(solidcore_record::Vec3::new(x, y, z))))
|
||||
.map(|p| v3(p).map(|[x, y, z]| solidcraft_engine::kernel::EdgePick::Point(solidcraft_engine::geom::Vec3::new(x, y, z))))
|
||||
.collect::<Option<Vec<_>>>()
|
||||
.ok_or("continuity `edges` points must each be [x,y,z]")?;
|
||||
let picked = solidcraft_engine::kernel::pick_edges(&b.body, &picks).map_err(|e| e.to_string())?;
|
||||
@@ -481,8 +480,8 @@ fn output_checks(c: &mut Checks, want: &Value, out: &str, results: &[Value], rel
|
||||
/// The word a `surface_types` check names a face's surface by, as the record holds it: an
|
||||
/// analytic kind, `ball_patch` (a corner's patch on a sphere), `bspline` (a B-spline or NURBS
|
||||
/// surface) or `revolution` (a revolved curve).
|
||||
fn surface_word(s: &solidcore_record::brep::Surface) -> &'static str {
|
||||
use solidcore_record::brep::Surface as S;
|
||||
fn surface_word(s: &solidcraft_engine::geom::brep::Surface) -> &'static str {
|
||||
use solidcraft_engine::geom::brep::Surface as S;
|
||||
match s {
|
||||
S::Plane { .. } | S::PlaneThrough { .. } => "plane",
|
||||
S::Cylinder { .. } => "cylinder",
|
||||
@@ -498,8 +497,8 @@ fn surface_word(s: &solidcore_record::brep::Surface) -> &'static str {
|
||||
/// A B-spline face's two directions as `[degree, spans]` (spans: the distinct knot intervals of
|
||||
/// the modelled range), the lower first, so a surface parameterised the other way round reads the
|
||||
/// same. `None` for a surface that is not a B-spline.
|
||||
fn spline_shape(s: &solidcore_record::brep::Surface) -> Option<[[u64; 2]; 2]> {
|
||||
use solidcore_record::brep::Surface as S;
|
||||
fn spline_shape(s: &solidcraft_engine::geom::brep::Surface) -> Option<[[u64; 2]; 2]> {
|
||||
use solidcraft_engine::geom::brep::Surface as S;
|
||||
let (u, v, nu, nv) = match s {
|
||||
S::Spline { u_knots, v_knots, points } => (u_knots, v_knots, points.len(), points.first().map_or(0, Vec::len)),
|
||||
S::RationalSpline { u_knots, v_knots, homogeneous } => (u_knots, v_knots, homogeneous.len(), homogeneous.first().map_or(0, Vec::len)),
|
||||
@@ -554,7 +553,7 @@ const CONSTRUCT_DIR_TOL: f64 = 1e-6;
|
||||
/// parallel to `normal` (either way) and which passes within `tol` of `through`. An axis or plane
|
||||
/// is graded as the line or plane it is, never by the origin it happens to keep.
|
||||
fn check_construction(s: &solidcraft_engine::Session, want: &Value, tol: f64, c: &mut Checks) {
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
use solidcraft_engine::view::{construction_axes, construction_planes, construction_points};
|
||||
let Some(named) = want.as_object() else {
|
||||
c.push("construction", json!("`construction` must map names to {point} | {axis} | {plane}"), json!("an object"), false);
|
||||
@@ -658,34 +657,7 @@ fn flat_pattern_checks(c: &mut Checks, want: &Value, results: &[Value], rel: f64
|
||||
}
|
||||
}
|
||||
|
||||
/// [`check_case_in`], and with `export` the case's corpus entry written under it
|
||||
/// ([`crate::kernel_cases_export`]).
|
||||
fn check_case(
|
||||
dir: &str,
|
||||
dumps: Option<&std::path::Path>,
|
||||
validation: solidcraft_engine::kernel::Validation,
|
||||
export: Option<&std::path::Path>,
|
||||
) -> Value {
|
||||
let mut capture = export.map(|_| crate::kernel_cases_export::Capture::default());
|
||||
let out = check_case_in(dir, dumps, validation, capture.as_mut());
|
||||
if let (Some(root), Some(capture)) = (export, capture)
|
||||
&& let Err(e) = crate::kernel_cases_export::write(root, dir, &out, capture)
|
||||
{
|
||||
let mut out = out;
|
||||
out["export_error"] = json!(e);
|
||||
return out;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// One case run and graded. With `capture`, the kernel operations its commands make are traced
|
||||
/// and kept there with the bodies the case ends with.
|
||||
fn check_case_in(
|
||||
dir: &str,
|
||||
dumps: Option<&std::path::Path>,
|
||||
validation: solidcraft_engine::kernel::Validation,
|
||||
mut capture: Option<&mut crate::kernel_cases_export::Capture>,
|
||||
) -> Value {
|
||||
fn check_case(dir: &str, dumps: Option<&std::path::Path>, validation: solidcraft_engine::kernel::Validation) -> Value {
|
||||
let (area, name) = case_name(dir);
|
||||
let mut out = json!({"case": name, "area": area, "kernel": kernel()});
|
||||
let want = match read_json(&format!("{dir}/expected.json")) {
|
||||
@@ -739,18 +711,7 @@ fn check_case_in(
|
||||
let mut s = solidcraft_engine::Session::default();
|
||||
s.set_validation(validation);
|
||||
let clock = std::time::Instant::now();
|
||||
if capture.is_some() {
|
||||
solidcraft_engine::kernel::smlib_trace::start();
|
||||
}
|
||||
let ran = s.run_script(&commands);
|
||||
// The trace ends with the commands: grading reads the bodies through the kernel too.
|
||||
if let Some(c) = &mut capture {
|
||||
c.events = solidcraft_engine::kernel::smlib_trace::finish();
|
||||
c.ran = Some(match &ran {
|
||||
Ok(_) => s.world_state().map(|w| w.bodies.iter().map(|b| (b.name.clone(), b.body.record().clone())).collect()).map_err(|e| e.to_string()),
|
||||
Err(e) => Err(e.to_string()),
|
||||
});
|
||||
}
|
||||
// The commands' own time, without process start or grading: the ladder's timing golden.
|
||||
out["seconds"] = json!(clock.elapsed().as_secs_f64());
|
||||
let answers = match (ran, may_refuse) {
|
||||
@@ -1015,13 +976,7 @@ pub fn run(args: &[String]) -> Result<(), String> {
|
||||
.ok_or_else(|| format!("kernel-cases: --validation= must be each_step, each_feature or off, not `{m}`"))?,
|
||||
None => solidcraft_engine::kernel::Validation::default(),
|
||||
};
|
||||
// `--export=DIR`: each case's kernel operations, their input records and its expected values
|
||||
// go to DIR/<area>/<case>.json, the corpus for solidcore's rig.
|
||||
let export = args.iter().find_map(|a| a.strip_prefix("--export=")).map(std::path::PathBuf::from);
|
||||
if export.as_ref().is_some_and(|d| d.as_os_str().is_empty()) {
|
||||
return Err("kernel-cases: --export= needs a folder".into());
|
||||
}
|
||||
let results: Vec<Value> = dirs.iter().map(|d| check_case(d, dumps.as_deref(), validation, export.as_deref())).collect();
|
||||
let results: Vec<Value> = dirs.iter().map(|d| check_case(d, dumps.as_deref(), validation)).collect();
|
||||
if dumps.is_some() {
|
||||
solidcraft_engine::kernel::smlib_dump_dir(None);
|
||||
}
|
||||
@@ -1072,7 +1027,7 @@ mod tests {
|
||||
/// either order of directions, spans only where given, and every face must be accounted for.
|
||||
#[test]
|
||||
fn splines_match_degrees_and_spans_in_either_order() {
|
||||
use solidcore_record::brep::Surface;
|
||||
use solidcraft_engine::geom::brep::Surface;
|
||||
let knots = |p: usize, spans: usize| -> Vec<f64> {
|
||||
let mut k = vec![0.0; p + 1];
|
||||
k.extend((1..spans).map(|i| i as f64));
|
||||
|
||||
@@ -1,299 +0,0 @@
|
||||
//! `kernel-cases --export=DIR`: one kernel rig case as a corpus entry for solidcore's rig. The
|
||||
//! case's commands run as the rig runs them while the kernel's operations are traced
|
||||
//! (`smlib_trace`); the entry holds the operation (or the chain of operations) that made the
|
||||
//! bodies the case ends with, each operand that came in as a record given as that record (the
|
||||
//! B-rep record, solidcore-record's JSON), the fix set, the case's expected values and the rig's
|
||||
//! own result. A case whose result no kernel operation alone accounts for is written too, with
|
||||
//! the reasons, so nothing is dropped silently.
|
||||
//!
|
||||
//! An entry's operation is expressible (`operation_expressible`) when the run failed in a kernel
|
||||
//! operation (the last one traced failed), or when every body the case ends with is the record a
|
||||
//! traced operation's output was written as, or an operand some traced operation took, as its
|
||||
//! record. The entry is wholly expressible (`expressible`) when, besides, every value it grades is
|
||||
//! the kernel's: none of `continuity`, `watertight`, `files`, `results`, `construction` or
|
||||
//! `flat_pattern` (SolidCraft computes them), no face, edge or vertex count that the rig's merging
|
||||
//! across analytic surfaces (`merged_topology`) changes, and no refusal graded on SolidCraft's
|
||||
//! wording of the kernel's error. Those are listed in `ungraded`.
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solidcore_record::brep::Brep;
|
||||
use solidcraft_engine::kernel::smlib_trace::{self, Event};
|
||||
|
||||
use crate::read_json;
|
||||
|
||||
/// What a traced case leaves for its entry: the trace of its commands, and the bodies it ends
|
||||
/// with by name or the engine's error.
|
||||
#[derive(Default)]
|
||||
pub struct Capture {
|
||||
pub events: Vec<Event>,
|
||||
pub ran: Option<Result<Vec<(String, Brep)>, String>>,
|
||||
}
|
||||
|
||||
/// The expected values SolidCraft computes, never the kernel.
|
||||
const GRADER_ONLY: [&str; 6] = ["continuity", "watertight", "files", "results", "construction", "flat_pattern"];
|
||||
|
||||
/// The case directory's area (its path under `tests/kernel-cases/`, a ladder case's tier under
|
||||
/// `ladder/`) and its id.
|
||||
fn area_and_id(dir: &str) -> Result<(String, String), String> {
|
||||
let p = Path::new(dir);
|
||||
let parts: Vec<String> = p.components().map(|c| c.as_os_str().to_string_lossy().to_string()).collect();
|
||||
let at = parts.iter().rposition(|c| c == "kernel-cases").ok_or_else(|| format!("{dir}: not under tests/kernel-cases"))?;
|
||||
let rest = parts.get(at + 1..).unwrap_or_default();
|
||||
match rest.split_last() {
|
||||
Some((id, area)) if !area.is_empty() => Ok((area.join("/"), id.clone())),
|
||||
_ => Err(format!("{dir}: no area")),
|
||||
}
|
||||
}
|
||||
|
||||
/// The case's line in its area's `KNOWN_FAILING`: why it fails today; `None` when not listed.
|
||||
fn known_failing(dir: &str, id: &str) -> Option<String> {
|
||||
let list = Path::new(dir).parent()?.join("KNOWN_FAILING");
|
||||
let text = std::fs::read_to_string(list).ok()?;
|
||||
text.lines().filter(|l| !l.trim_start().starts_with('#')).find_map(|l| {
|
||||
let (case, why) = l.split_once(char::is_whitespace).unwrap_or((l, ""));
|
||||
(case == id).then(|| why.trim().to_string())
|
||||
})
|
||||
}
|
||||
|
||||
/// The trace read as operations and the records that went in and came out.
|
||||
struct Trace<'a> {
|
||||
/// Each operation: its name, operands, parameters and outputs or error.
|
||||
calls: Vec<(&'static str, &'a [u64], &'a Value, &'a Result<Vec<u64>, String>)>,
|
||||
/// The record each body made from a record was made from.
|
||||
imports: HashMap<u64, (&'a Brep, bool, bool)>,
|
||||
/// The records bodies were written as, in order.
|
||||
exports: Vec<(u64, &'a Brep)>,
|
||||
/// The operation that made each body.
|
||||
producer: HashMap<u64, usize>,
|
||||
}
|
||||
|
||||
impl<'a> Trace<'a> {
|
||||
fn of(events: &'a [Event]) -> Trace<'a> {
|
||||
let mut t = Trace { calls: Vec::new(), imports: HashMap::new(), exports: Vec::new(), producer: HashMap::new() };
|
||||
for e in events {
|
||||
match e {
|
||||
Event::Import { id, record, trusted, keeping_splines } => {
|
||||
t.imports.insert(*id, (record, *trusted, *keeping_splines));
|
||||
}
|
||||
Event::Export { id, record } => t.exports.push((*id, record)),
|
||||
Event::Call { op, inputs, params, outputs } => {
|
||||
if let Ok(made) = outputs {
|
||||
for id in made {
|
||||
t.producer.insert(*id, t.calls.len());
|
||||
}
|
||||
}
|
||||
t.calls.push((*op, inputs, params, outputs));
|
||||
}
|
||||
}
|
||||
}
|
||||
t
|
||||
}
|
||||
|
||||
/// Bodies some operation took as an operand.
|
||||
fn operands(&self) -> BTreeSet<u64> {
|
||||
self.calls.iter().flat_map(|c| c.1.iter().copied()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// The operations `ends` need, in the order they ran: each body's maker, and its operands' makers
|
||||
/// in turn, back to bodies made from records. Operands made by nothing traced are listed in
|
||||
/// `unknown`.
|
||||
fn chain(t: &Trace<'_>, ends: &[u64], unknown: &mut BTreeSet<u64>) -> Vec<usize> {
|
||||
let mut need = BTreeSet::new();
|
||||
let mut todo: Vec<u64> = ends.to_vec();
|
||||
while let Some(id) = todo.pop() {
|
||||
if t.imports.contains_key(&id) {
|
||||
continue;
|
||||
}
|
||||
match t.producer.get(&id) {
|
||||
Some(&i) => {
|
||||
if need.insert(i)
|
||||
&& let Some(c) = t.calls.get(i)
|
||||
{
|
||||
todo.extend(c.1.iter().copied());
|
||||
}
|
||||
}
|
||||
None => {
|
||||
unknown.insert(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
need.into_iter().collect()
|
||||
}
|
||||
|
||||
/// The entry of case `dir`, run with result `out`, into `root/<area>/<id>.json`, and the fix set
|
||||
/// into `root/fixes.json`.
|
||||
pub fn write(root: &Path, dir: &str, out: &Value, capture: Capture) -> Result<(), String> {
|
||||
let (area, id) = area_and_id(dir)?;
|
||||
let want = read_json(&format!("{dir}/expected.json"))?;
|
||||
let t = Trace::of(&capture.events);
|
||||
// Why the operation is not the kernel's alone, and what the entry cannot grade without
|
||||
// SolidCraft.
|
||||
let mut why_not: Vec<String> = Vec::new();
|
||||
let mut ungraded: Vec<String> = Vec::new();
|
||||
let has = |k: &str| want.get(k).is_some_and(|v| !v.is_null());
|
||||
|
||||
if let Some(missing) = want["missing"].as_str() {
|
||||
why_not.push(format!("SolidCraft has no command for it yet: {missing}"));
|
||||
}
|
||||
for k in GRADER_ONLY.iter().filter(|k| has(k)) {
|
||||
ungraded.push(format!("{k}: SolidCraft computes it"));
|
||||
}
|
||||
|
||||
let mut result: Vec<Value> = Vec::new();
|
||||
let mut ends: Vec<u64> = Vec::new();
|
||||
let mut failed_call: Option<usize> = None;
|
||||
let mut outcome = json!({"ran": true});
|
||||
match (&capture.ran, has("missing")) {
|
||||
(_, true) => {}
|
||||
(None, false) => why_not.push("the case did not run".into()),
|
||||
(Some(Err(e)), false) => {
|
||||
// The run stopped. It stopped in the kernel when the last operation that failed is
|
||||
// followed by no operation but copies (a copy or a tagged copy, which build nothing):
|
||||
// nothing was built after it, so the engine's error reports that failure in its own
|
||||
// words. An operation that ran after it means the engine went on and failed itself.
|
||||
outcome = json!({"ran": false, "engine_error": e});
|
||||
let last_failed = t.calls.iter().rposition(|c| c.3.is_err());
|
||||
let built_after = |i: usize| t.calls.iter().skip(i + 1).any(|c| !matches!(c.0, "copy" | "tagged"));
|
||||
match last_failed.filter(|&i| !built_after(i)).and_then(|i| t.calls.get(i).map(|c| (i, c))) {
|
||||
Some((i, c)) => {
|
||||
failed_call = Some(i);
|
||||
ends.extend(c.1.iter().copied());
|
||||
if let Err(k) = c.3 {
|
||||
outcome["kernel_error"] = json!(k);
|
||||
if let Some(why) = want["refusal"].as_str().filter(|w| !k.contains(*w)) {
|
||||
ungraded.push(format!("refusal {why:?}: SolidCraft's wording of the kernel's error"));
|
||||
}
|
||||
}
|
||||
}
|
||||
None if t.calls.is_empty() => why_not.push(format!("no kernel operation ran before the failure: {e}")),
|
||||
None => why_not.push(format!("the failure is SolidCraft's, not a kernel operation's: {e}")),
|
||||
}
|
||||
}
|
||||
(Some(Ok(world)), false) => {
|
||||
let operands = t.operands();
|
||||
let mut made = 0;
|
||||
for (name, record) in world {
|
||||
let from_op = t.exports.iter().rev().find(|(id, r)| *r == record && t.producer.contains_key(id)).map(|(id, _)| *id);
|
||||
let operand = t.imports.iter().filter(|(i, (r, _, _))| *r == record && operands.contains(i)).map(|(i, _)| *i).min();
|
||||
match (from_op, operand) {
|
||||
(Some(id), _) => {
|
||||
made += 1;
|
||||
ends.push(id);
|
||||
result.push(json!({"body": name, "made": format!("b{id}")}));
|
||||
}
|
||||
(None, Some(id)) => {
|
||||
ends.push(id);
|
||||
result.push(json!({"body": name, "operand": format!("b{id}")}));
|
||||
}
|
||||
(None, None) => why_not.push(format!(
|
||||
"body {name:?} is no kernel operation's output nor an operand: SolidCraft built it or changed the kernel's record"
|
||||
)),
|
||||
}
|
||||
}
|
||||
if world.is_empty() {
|
||||
why_not.push("the case ends with no body".into());
|
||||
} else if made == 0 && why_not.is_empty() {
|
||||
why_not.push("no kernel operation made a body the case ends with".into());
|
||||
}
|
||||
// Counts graded as SolidCraft merges them, where that differs from the records'.
|
||||
let raw = |k: &str| -> u64 {
|
||||
world
|
||||
.iter()
|
||||
.map(|(_, r)| match k {
|
||||
"faces" => r.faces.len(),
|
||||
"edges" => r.edges.len(),
|
||||
_ => r.vertices.len(),
|
||||
} as u64)
|
||||
.sum()
|
||||
};
|
||||
for k in ["faces", "edges", "vertices"] {
|
||||
if let Some(merged) = out["measured"][k].as_u64().filter(|&m| has(k) && m != raw(k)) {
|
||||
ungraded.push(format!("{k}: graded merged across analytic surfaces ({merged}), the records have {}", raw(k)));
|
||||
}
|
||||
}
|
||||
outcome["raw_counts"] = json!({"faces": raw("faces"), "edges": raw("edges"), "vertices": raw("vertices")});
|
||||
}
|
||||
}
|
||||
|
||||
let mut unknown = BTreeSet::new();
|
||||
let mut order = chain(&t, &ends, &mut unknown);
|
||||
if !unknown.is_empty() {
|
||||
why_not.push(format!("operands made by nothing traced: {unknown:?}"));
|
||||
}
|
||||
if let Some(i) = failed_call.filter(|i| !order.contains(i)) {
|
||||
order.push(i);
|
||||
}
|
||||
// Operand records: every body the chain takes, or the case ends with, that came in as a record.
|
||||
let mut bodies = BTreeMap::new();
|
||||
let given = |id: u64, bodies: &mut BTreeMap<String, Value>| {
|
||||
if let Some((record, trusted, splines)) = t.imports.get(&id) {
|
||||
bodies.insert(format!("b{id}"), json!({"trusted": trusted, "keeping_splines": splines, "record": record}));
|
||||
}
|
||||
};
|
||||
let mut calls: Vec<Value> = Vec::new();
|
||||
for (op, inputs, params, outputs) in order.iter().filter_map(|&i| t.calls.get(i)) {
|
||||
for &id in inputs.iter() {
|
||||
given(id, &mut bodies);
|
||||
}
|
||||
let mut c = json!({"op": op, "inputs": inputs.iter().map(|i| format!("b{i}")).collect::<Vec<_>>(), "params": params});
|
||||
match outputs {
|
||||
Ok(made) => c["outputs"] = json!(made.iter().map(|i| format!("b{i}")).collect::<Vec<_>>()),
|
||||
Err(e) => c["error"] = json!(e),
|
||||
}
|
||||
calls.push(c);
|
||||
}
|
||||
for r in &result {
|
||||
if let Some(id) = r["operand"].as_str().and_then(|b| b.strip_prefix('b')).and_then(|n| n.parse().ok()) {
|
||||
given(id, &mut bodies);
|
||||
}
|
||||
}
|
||||
let rig = json!({
|
||||
"status": out["status"], "error": out["error"], "refused_with": out["refused_with"], "measured": out["measured"],
|
||||
"known_failing": known_failing(dir, &id),
|
||||
});
|
||||
let operation_expressible = why_not.is_empty();
|
||||
let entry = json!({
|
||||
"case": id,
|
||||
"area": area,
|
||||
"sources": want["sources"],
|
||||
"what": want["what"],
|
||||
"expressible": operation_expressible && ungraded.is_empty(),
|
||||
"operation_expressible": operation_expressible,
|
||||
"why_not": why_not,
|
||||
"ungraded": ungraded,
|
||||
"expected": want,
|
||||
"rig": rig,
|
||||
"outcome": outcome,
|
||||
"calls": calls,
|
||||
"bodies": bodies,
|
||||
"result": result,
|
||||
"traced_ops": t.calls.iter().map(|c| c.0).collect::<Vec<_>>(),
|
||||
});
|
||||
let folder: PathBuf = root.join(&area);
|
||||
std::fs::create_dir_all(&folder).map_err(|e| format!("{}: {e}", folder.display()))?;
|
||||
let path = folder.join(format!("{id}.json"));
|
||||
let text = serde_json::to_string(&entry).map_err(|e| e.to_string())?;
|
||||
std::fs::write(&path, text).map_err(|e| format!("{}: {e}", path.display()))?;
|
||||
// Cases run in parallel processes: each writes its own file and renames it over the shared
|
||||
// one, so no reader sees a torn file (every process writes the same fix set).
|
||||
let fixes = serde_json::to_string_pretty(&smlib_trace::fixes()).map_err(|e| e.to_string())?;
|
||||
let own = root.join(format!("fixes.json.{}", std::process::id()));
|
||||
std::fs::write(&own, fixes).map_err(|e| format!("{}: {e}", own.display()))?;
|
||||
std::fs::rename(&own, root.join("fixes.json")).map_err(|e| format!("{}: {e}", root.display()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn the_area_is_the_path_under_kernel_cases() {
|
||||
assert_eq!(area_and_id("tests/kernel-cases/booleans/1894-x").unwrap(), ("booleans".into(), "1894-x".into()));
|
||||
assert_eq!(area_and_id("/r/tests/kernel-cases/ladder/07-fillets/003-y").unwrap(), ("ladder/07-fillets".into(), "003-y".into()));
|
||||
assert!(area_and_id("/r/other/x").is_err());
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,6 @@ mod bench_assistant;
|
||||
mod crashes;
|
||||
mod fuzz;
|
||||
mod kernel_cases;
|
||||
mod kernel_cases_export;
|
||||
mod logger;
|
||||
mod oracle;
|
||||
mod preview;
|
||||
@@ -86,7 +85,7 @@ const USAGE_RECIPE: &str = " solidcraft-cli recipe <recipe.json>\n";
|
||||
/// `oracle`'s lines in the usage.
|
||||
const USAGE_ORACLE: &str = " solidcraft-cli oracle <case-dir>... [--json]\n";
|
||||
/// `kernel-cases`'s lines in the usage.
|
||||
const USAGE_KERNEL_CASES: &str = " solidcraft-cli kernel-cases <case-dir>... [--json] [--dumps=DIR] [--export=DIR] [--validation=each_step|each_feature|off]\n (the kernel regression rig, tests/kernel-cases, on SMLib; --dumps: each case's\n failing SMLib calls leave their inputs for replay in DIR/<area>-<case>; --export: each case's\n kernel operations, input records and expected values for solidcore's rig, in DIR/<area>/<case>.json; --validation: when SMLib's\n full check runs (diagnostics.validation), default each_feature)\n";
|
||||
const USAGE_KERNEL_CASES: &str = " solidcraft-cli kernel-cases <case-dir>... [--json] [--dumps=DIR] [--validation=each_step|each_feature|off]\n (the kernel regression rig, tests/kernel-cases, on SMLib; --dumps: each case's\n failing SMLib calls leave their inputs for replay in DIR/<area>-<case>; --validation: when SMLib's\n full check runs (diagnostics.validation), default each_feature)\n";
|
||||
/// `fuzz`'s lines in the usage.
|
||||
const USAGE_FUZZ: &str =
|
||||
" solidcraft-cli fuzz [--from N] [--count N] [--jobs J] [--timeout S] [--steps N] [--minimise] [--out DIR] [--report FILE]\n";
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#![allow(clippy::needless_range_loop)]
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
|
||||
use crate::spatial::{self, Tri};
|
||||
use crate::{read_json, recipe};
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
//! Each design runs in a child process (this test binary, [`action_matrix_worker`]) so an action
|
||||
//! that hangs or aborts is recorded as such and the rest of the design still runs.
|
||||
|
||||
use solidcraft_engine::geom::prelude::*;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::io::{BufRead, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -29,10 +28,10 @@ use std::process::{Command, Stdio};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_engine::Session;
|
||||
use solidcraft_engine::doc::kernel::{FaceSurface, face_surface};
|
||||
use solidcraft_engine::doc::{FaceAttachmentStatus, ModelBody, naming};
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
|
||||
/// One in this many actions runs on pull requests (by a stable hash of its key).
|
||||
const PR_SAMPLE: u64 = 40;
|
||||
@@ -1657,7 +1656,7 @@ impl TipConstruction {
|
||||
other => panic!("expected an axis, got {other:?}"),
|
||||
}
|
||||
}
|
||||
fn plane(&self) -> solidcore_record::Plane {
|
||||
fn plane(&self) -> solidcraft_engine::geom::Plane {
|
||||
match &self.0 {
|
||||
solidcraft_engine::doc::construct::ConstructGeom::Plane(p) => *p,
|
||||
other => panic!("expected a plane, got {other:?}"),
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use std::f64::consts::PI;
|
||||
|
||||
use serde_json::Value;
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
use solidcraft_engine::{Session, io, kernel};
|
||||
|
||||
fn assert_two_through_holes(session: &Session, stage: &str) {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
//! the current format and evaluate (what they rebuild to today is today's, not what the build
|
||||
//! that saved them made); damaged and hostile design files never panic.
|
||||
|
||||
use solidcraft_engine::geom::prelude::*;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use serde_json::{Value, json};
|
||||
|
||||
@@ -16,14 +16,13 @@
|
||||
//! designs, the engine's design fixtures and the older designs kept by the hole matrix, taper
|
||||
//! and projection tests.
|
||||
|
||||
use solidcraft_engine::geom::prelude::*;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_engine::Session;
|
||||
use solidcraft_engine::doc::{FaceAttachmentStatus, Feature, FeatureKind, ModelBody, ModelState, PlaneRef, naming};
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
|
||||
fn root() -> PathBuf {
|
||||
Path::new(env!("CARGO_MANIFEST_DIR")).join("../..").canonicalize().unwrap()
|
||||
|
||||
@@ -9,7 +9,6 @@ use std::path::{Path, PathBuf};
|
||||
use serde_json::{Value, json};
|
||||
use solidcraft_engine::Session;
|
||||
use solidcraft_engine::doc::kernel::{FaceSurface, face_surface};
|
||||
use solidcraft_engine::geom::prelude::*;
|
||||
|
||||
fn root() -> PathBuf {
|
||||
Path::new(env!("CARGO_MANIFEST_DIR")).join("../..").canonicalize().unwrap()
|
||||
|
||||
@@ -288,7 +288,7 @@ fn a_snapshot_shows_the_part_where_the_camera_puts_it() {
|
||||
let vp = c.view_proj(f64::from(SIZE.0) / f64::from(SIZE.1), 1000.0);
|
||||
let (mut elo, mut ehi) = ([f64::MAX; 2], [f64::MIN; 2]);
|
||||
for k in 0..8 {
|
||||
let p = solidcore_record::Vec3::new(
|
||||
let p = solidcraft_engine::geom::Vec3::new(
|
||||
corner[0] + size[0] * f64::from(k & 1),
|
||||
corner[1] + size[1] * f64::from((k >> 1) & 1),
|
||||
corner[2] + size[2] * f64::from((k >> 2) & 1),
|
||||
|
||||
@@ -5,14 +5,13 @@
|
||||
//! vertices nothing else used, so an edge that is one straight line was drawn (and lit on hover)
|
||||
//! as many pieces, and a sketch on such a face projected every piece.
|
||||
|
||||
use solidcraft_engine::geom::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use serde_json::json;
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_engine::Session;
|
||||
use solidcraft_engine::doc::kernel::{FaceSurface, face_surface};
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
|
||||
fn design() -> PathBuf {
|
||||
Path::new(env!("CARGO_MANIFEST_DIR")).join("../../tests/action_matrix/designs/crazy-geom-cuts.sc")
|
||||
|
||||
@@ -36,7 +36,7 @@ js-sys = "0.3"
|
||||
serde_json = { workspace = true }
|
||||
web-sys = { version = "0.3", features = [
|
||||
"Window", "Document", "Element", "HtmlElement", "HtmlCanvasElement", "Location", "Navigator", "StorageManager",
|
||||
"FileSystemHandle", "FileSystemDirectoryHandle", "FileSystemFileHandle", "FileSystemGetFileOptions", "FileSystemGetDirectoryOptions", "FileSystemWritableFileStream",
|
||||
"FileSystemHandle", "FileSystemDirectoryHandle", "FileSystemFileHandle", "FileSystemGetFileOptions", "FileSystemWritableFileStream",
|
||||
"WritableStream", "File", "FileList", "Blob", "BlobPropertyBag", "Url", "HtmlAnchorElement", "HtmlInputElement", "Storage", "StorageEvent",
|
||||
"IdbFactory", "IdbDatabase", "IdbObjectStore", "IdbOpenDbRequest", "IdbRequest", "IdbTransaction", "IdbTransactionMode",
|
||||
"Headers", "ReadableStream", "ReadableStreamDefaultReader",
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
This build keeps its designs and settings apart per release channel (src/channel.rs).
|
||||
packaging/web-channels.sh only offers a release candidate that has this file.
|
||||
@@ -34,7 +34,6 @@
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="icons/32x32/io.tangencylabs.solidcraft.png">
|
||||
<link rel="apple-touch-icon" href="icons/256x256/io.tangencylabs.solidcraft.png">
|
||||
<link data-trunk rel="copy-file" href="manifest.webmanifest" />
|
||||
<link data-trunk rel="copy-file" href="channel-storage.txt" />
|
||||
<link data-trunk rel="copy-file" href="../../assets/icons/app/linux/exports/hicolor/32x32/apps/io.tangencylabs.solidcraft.png" data-target-path="icons/32x32" />
|
||||
<link data-trunk rel="copy-file" href="../../assets/icons/app/linux/exports/hicolor/256x256/apps/io.tangencylabs.solidcraft.png" data-target-path="icons/256x256" />
|
||||
<link data-trunk rel="copy-file" href="../../assets/icons/app/linux/exports/hicolor/512x512/apps/io.tangencylabs.solidcraft.png" data-target-path="icons/512x512" />
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
//! Release channels. The site serves the stable web app at `/app/`, the release candidate at
|
||||
//! `/app-rc/` and the bleeding-edge build at `/app-edge/`: one origin, so one set of browser
|
||||
//! storage. A channel that kept its designs and settings in the same places could save a design
|
||||
//! in a file format the stable app can't open, or overwrite stable's settings. So each
|
||||
//! pre-release channel keeps everything under its own name, and stable (and any copy served from
|
||||
//! another path: the release zip, `trunk serve`) keeps the names it has always used, so what
|
||||
//! people have saved at `/app/` is untouched.
|
||||
//!
|
||||
//! The channel comes from the directory the app is served from, not from the build, so a build
|
||||
//! taken from a release zip is as separate as one built on the spot.
|
||||
|
||||
/// The channel the app is served as, from the page's path: `"rc"`, `"edge"`, or `""` (stable).
|
||||
pub fn of_path(pathname: &str) -> &'static str {
|
||||
match pathname.trim_end_matches("index.html").trim_end_matches('/').rsplit('/').next() {
|
||||
Some("app-rc") => "rc",
|
||||
Some("app-edge") => "edge",
|
||||
_ => "",
|
||||
}
|
||||
}
|
||||
|
||||
/// `name` as this channel stores it: unchanged for stable, `rc:name` and `edge:name` otherwise.
|
||||
pub fn name_in(channel: &str, name: &str) -> String {
|
||||
if channel.is_empty() { name.to_owned() } else { format!("{channel}:{name}") }
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn current() -> &'static str {
|
||||
web_sys::window().and_then(|w| w.location().pathname().ok()).map_or("", |p| of_path(&p))
|
||||
}
|
||||
|
||||
/// A storage key (local storage, IndexedDB database name) of the running channel.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn key(name: &str) -> String {
|
||||
name_in(current(), name)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn channel_follows_the_directory() {
|
||||
assert_eq!(of_path("/app/"), "");
|
||||
assert_eq!(of_path("/app/index.html"), "");
|
||||
assert_eq!(of_path("/app-rc/"), "rc");
|
||||
assert_eq!(of_path("/app-rc"), "rc");
|
||||
assert_eq!(of_path("/app-rc/index.html"), "rc");
|
||||
assert_eq!(of_path("/app-edge/"), "edge");
|
||||
assert_eq!(of_path("/"), "");
|
||||
assert_eq!(of_path("/solidcraft-web-0.30.1/"), "");
|
||||
assert_eq!(of_path("/app/app-rc/"), "rc", "only the directory's own name counts");
|
||||
assert_eq!(of_path("/app-rc/app/"), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stable_keeps_its_names() {
|
||||
assert_eq!(name_in("", "solidcraft.prefs"), "solidcraft.prefs");
|
||||
assert_eq!(name_in("rc", "solidcraft.prefs"), "rc:solidcraft.prefs");
|
||||
assert_ne!(name_in("rc", "x"), name_in("edge", "x"));
|
||||
}
|
||||
}
|
||||
@@ -467,7 +467,7 @@ mod web {
|
||||
|
||||
async fn open_db() -> Result<IdbDatabase, String> {
|
||||
let factory = web_sys::window().ok_or("no window")?.indexed_db().map_err(err)?.ok_or("this browser has no IndexedDB")?;
|
||||
let request: IdbOpenDbRequest = factory.open_with_u32(&crate::channel::key(DB), 1).map_err(err)?;
|
||||
let request: IdbOpenDbRequest = factory.open_with_u32(DB, 1).map_err(err)?;
|
||||
let target = request.clone();
|
||||
let upgrade = Closure::once_into_js(move || {
|
||||
if let Ok(db) = target.result().and_then(|r| r.dyn_into::<IdbDatabase>()) {
|
||||
|
||||
@@ -16,9 +16,7 @@ use solidcraft_engine::io::vfs;
|
||||
use crate::disk;
|
||||
use wasm_bindgen::{JsCast as _, JsValue, closure::Closure};
|
||||
use wasm_bindgen_futures::{JsFuture, spawn_local};
|
||||
use web_sys::{
|
||||
FileSystemDirectoryHandle, FileSystemFileHandle, FileSystemGetDirectoryOptions, FileSystemGetFileOptions, FileSystemWritableFileStream,
|
||||
};
|
||||
use web_sys::{FileSystemDirectoryHandle, FileSystemFileHandle, FileSystemGetFileOptions, FileSystemWritableFileStream};
|
||||
|
||||
/// Where OPFS files appear to the engine.
|
||||
pub const OPFS: &str = disk::OPFS_PREFIX;
|
||||
@@ -113,7 +111,7 @@ thread_local! {
|
||||
static LOCAL: Cell<bool> = const { Cell::new(false) };
|
||||
}
|
||||
|
||||
/// Local storage keys of stored files (under the channel's name, see `channel.rs`).
|
||||
/// Local storage keys of stored files.
|
||||
const LOCAL_PREFIX: &str = "solidcraft.file.";
|
||||
/// Local-storage files kept by earlier releases (compatibility shim, docs/compatibility.md):
|
||||
/// still listed and opened; removing one removes either key.
|
||||
@@ -171,17 +169,7 @@ fn has(sm: &web_sys::StorageManager, method: &str) -> bool {
|
||||
async fn root() -> Result<FileSystemDirectoryHandle, String> {
|
||||
let sm = storage_manager().filter(|sm| has(sm, "getDirectory")).ok_or("this page has no private file storage (it needs https or localhost)")?;
|
||||
let dir = JsFuture::from(sm.get_directory()).await.map_err(err)?;
|
||||
let dir = dir.dyn_into::<FileSystemDirectoryHandle>().map_err(|_| "this browser has no private file storage (OPFS)".to_string())?;
|
||||
// Stable keeps its files in the root, where they have always been; a pre-release channel has a folder of its own.
|
||||
match crate::channel::current() {
|
||||
"" => Ok(dir),
|
||||
channel => {
|
||||
let opts = FileSystemGetDirectoryOptions::new();
|
||||
opts.set_create(true);
|
||||
let sub = JsFuture::from(dir.get_directory_handle_with_options(&format!("channel-{channel}"), &opts)).await.map_err(err)?;
|
||||
sub.dyn_into::<FileSystemDirectoryHandle>().map_err(|_| "not a folder".to_string())
|
||||
}
|
||||
}
|
||||
dir.dyn_into::<FileSystemDirectoryHandle>().map_err(|_| "this browser has no private file storage (OPFS)".to_string())
|
||||
}
|
||||
|
||||
pub(crate) async fn read_handle(h: &FileSystemFileHandle) -> Result<Vec<u8>, String> {
|
||||
@@ -203,7 +191,7 @@ async fn file_handle(dir: &FileSystemDirectoryHandle, name: &str, create: bool)
|
||||
async fn write_file(name: &str, bytes: &[u8]) -> Result<(), String> {
|
||||
if local_only() {
|
||||
return local()?
|
||||
.set_item(&crate::channel::key(&format!("{LOCAL_PREFIX}{name}")), &encode(bytes))
|
||||
.set_item(&format!("{LOCAL_PREFIX}{name}"), &encode(bytes))
|
||||
.map_err(|e| format!("local storage is full or blocked: {}", err(e)));
|
||||
}
|
||||
let dir = root().await?;
|
||||
@@ -219,7 +207,7 @@ async fn remove_file(name: &str) -> Result<(), String> {
|
||||
if local_only() {
|
||||
let st = local()?;
|
||||
st.remove_item(&format!("{LEGACY_LOCAL_PREFIX}{name}")).map_err(err)?;
|
||||
return st.remove_item(&crate::channel::key(&format!("{LOCAL_PREFIX}{name}"))).map_err(err);
|
||||
return st.remove_item(&format!("{LOCAL_PREFIX}{name}")).map_err(err);
|
||||
}
|
||||
let dir = root().await?;
|
||||
JsFuture::from(dir.remove_entry(name)).await.map_err(err)?;
|
||||
@@ -232,10 +220,7 @@ fn read_local() -> Result<Vec<(String, Vec<u8>, f64)>, String> {
|
||||
let mut out = Vec::new();
|
||||
for i in 0..st.length().unwrap_or(0).min(10_000) {
|
||||
let Some(k) = st.key(i).ok().flatten() else { continue };
|
||||
let own = crate::channel::key(LOCAL_PREFIX);
|
||||
// The old names belong to stable; a pre-release channel has none.
|
||||
let legacy = crate::channel::current().is_empty().then_some(LEGACY_LOCAL_PREFIX);
|
||||
let Some(name) = k.strip_prefix(own.as_str()).or_else(|| legacy.and_then(|l| k.strip_prefix(l))) else { continue };
|
||||
let Some(name) = k.strip_prefix(LOCAL_PREFIX).or_else(|| k.strip_prefix(LEGACY_LOCAL_PREFIX)) else { continue };
|
||||
if let Some(v) = st.get_item(&k).ok().flatten() {
|
||||
out.push((name.to_string(), decode(&v), 0.0));
|
||||
}
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod api;
|
||||
#[cfg(any(target_arch = "wasm32", test))]
|
||||
mod channel;
|
||||
#[cfg(any(target_arch = "wasm32", test))]
|
||||
mod disk;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod easyprint;
|
||||
@@ -154,15 +152,8 @@ impl PrefsWriter {
|
||||
/// tab of the page when one tab writes). Other keys, and settings taken away (clearing the site's
|
||||
/// data), leave this tab's settings as they are.
|
||||
#[cfg(any(target_arch = "wasm32", test))]
|
||||
fn settings_written(own_key: &str, key: Option<&str>, value: Option<String>) -> Option<String> {
|
||||
// Tabs of another channel write to the same storage, under their own key.
|
||||
value.filter(|_| key == Some(own_key))
|
||||
}
|
||||
|
||||
/// The settings key of `channel` (see [`channel`]).
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn prefs_key() -> String {
|
||||
channel::key(solidcraft_ui_egui::prefs::STORAGE_KEY)
|
||||
fn settings_written(key: Option<&str>, value: Option<String>) -> Option<String> {
|
||||
value.filter(|_| key == Some(solidcraft_ui_egui::prefs::STORAGE_KEY))
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@@ -271,7 +262,7 @@ mod web {
|
||||
fn hear_settings_from_other_tabs(window: &web_sys::Window, ctx: &egui::Context, inbox: PrefsInbox) {
|
||||
let ctx = ctx.clone();
|
||||
let on_storage = wasm_bindgen::closure::Closure::<dyn FnMut(web_sys::StorageEvent)>::new(move |e: web_sys::StorageEvent| {
|
||||
if let Some(prefs) = crate::settings_written(&crate::prefs_key(), e.key().as_deref(), e.new_value()) {
|
||||
if let Some(prefs) = crate::settings_written(e.key().as_deref(), e.new_value()) {
|
||||
inbox.set(Some(prefs));
|
||||
ctx.request_repaint();
|
||||
}
|
||||
@@ -343,7 +334,8 @@ mod web {
|
||||
}
|
||||
let prefs = self.persisted_prefs();
|
||||
let Some(prefs) = self.prefs_writer.take(prefs) else { return };
|
||||
let stored = web_sys::window().and_then(|w| w.local_storage().ok().flatten()).map(|s| s.set_item(&crate::prefs_key(), &prefs));
|
||||
let stored =
|
||||
web_sys::window().and_then(|w| w.local_storage().ok().flatten()).map(|s| s.set_item(solidcraft_ui_egui::prefs::STORAGE_KEY, &prefs));
|
||||
if !matches!(stored, Some(Ok(()))) {
|
||||
log::warn!("settings: the browser's storage did not take them; they are saved again later");
|
||||
self.prefs_writer = crate::PrefsWriter::default();
|
||||
@@ -842,15 +834,14 @@ mod web {
|
||||
self.store_prefs(&ctx);
|
||||
}
|
||||
fn persist_egui_memory(&self) -> bool {
|
||||
// eframe keeps this under one fixed key: only stable uses it, so a pre-release never rewrites it.
|
||||
self.launch.persists() && crate::channel::current().is_empty()
|
||||
self.launch.persists()
|
||||
}
|
||||
fn save(&mut self, storage: &mut dyn eframe::Storage) {
|
||||
if !self.launch.persists() {
|
||||
return;
|
||||
}
|
||||
let prefs = self.persisted_prefs();
|
||||
storage.set_string(&crate::prefs_key(), prefs);
|
||||
storage.set_string(solidcraft_ui_egui::prefs::STORAGE_KEY, prefs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -860,7 +851,7 @@ mod web {
|
||||
|
||||
/// Where `?webgl` is remembered. The installed app starts from its manifest's start URL, which
|
||||
/// has no query, so the choice has to outlive the page it was made on.
|
||||
const WEBGL_KEY: &str = "solidcraft.webgl"; // stored under crate::channel::key
|
||||
const WEBGL_KEY: &str = "solidcraft.webgl";
|
||||
|
||||
/// Render with WebGL2: asked for now (`?webgl`, remembered) or before (until `?webgpu`).
|
||||
fn webgl() -> bool {
|
||||
@@ -868,17 +859,17 @@ mod web {
|
||||
let q = query();
|
||||
if crate::query_flag(&q, "webgl") {
|
||||
if let Some(s) = &storage {
|
||||
let _ = s.set_item(&crate::channel::key(WEBGL_KEY), "1");
|
||||
let _ = s.set_item(WEBGL_KEY, "1");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if crate::query_flag(&q, "webgpu") {
|
||||
if let Some(s) = &storage {
|
||||
let _ = s.remove_item(&crate::channel::key(WEBGL_KEY));
|
||||
let _ = s.remove_item(WEBGL_KEY);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
storage.and_then(|s| s.get_item(&crate::channel::key(WEBGL_KEY)).ok().flatten()).is_some()
|
||||
storage.and_then(|s| s.get_item(WEBGL_KEY).ok().flatten()).is_some()
|
||||
}
|
||||
|
||||
/// Fetch font bytes asynchronously through the browser.
|
||||
@@ -977,7 +968,7 @@ mod web {
|
||||
if launch.persists() {
|
||||
hear_settings_from_other_tabs(&window, &cc.egui_ctx, other_tab_prefs.clone());
|
||||
}
|
||||
if launch.persists() && let Some(p) = cc.storage.and_then(|s| solidcraft_ui_egui::prefs::stored(|k| s.get_string(&crate::channel::key(k)))) {
|
||||
if launch.persists() && let Some(p) = cc.storage.and_then(|s| solidcraft_ui_egui::prefs::stored(|k| s.get_string(k))) {
|
||||
app.load_prefs(&p);
|
||||
}
|
||||
if let Some(rs) = &cc.wgpu_render_state {
|
||||
@@ -1180,10 +1171,9 @@ mod tests {
|
||||
#[test]
|
||||
fn only_settings_another_tab_wrote_are_loaded() {
|
||||
let key = solidcraft_ui_egui::prefs::STORAGE_KEY;
|
||||
assert_eq!(settings_written(key, Some(key), Some("{}".into())).as_deref(), Some("{}"));
|
||||
assert_eq!(settings_written(key, Some("solidcraft.webgl"), Some("1".into())), None);
|
||||
assert_eq!(settings_written(key, Some(key), None), None, "removed settings leave this tab's");
|
||||
assert_eq!(settings_written(key, None, None), None, "clearing all storage leaves this tab's settings");
|
||||
assert_eq!(settings_written(key, Some("rc:solidcraft.prefs"), Some("{}".into())), None, "another channel's settings are not loaded");
|
||||
assert_eq!(settings_written(Some(key), Some("{}".into())).as_deref(), Some("{}"));
|
||||
assert_eq!(settings_written(Some("solidcraft.webgl"), Some("1".into())), None);
|
||||
assert_eq!(settings_written(Some(key), None), None, "removed settings leave this tab's");
|
||||
assert_eq!(settings_written(None, None), None, "clearing all storage leaves this tab's settings");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
//! Navigation drawing definitions. Camera projection and picking remain in the UI widgets.
|
||||
use crate::theme::Tokens;
|
||||
use egui::{Color32, FontId, Pos2, Rect, Shape, Stroke, pos2, vec2};
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_engine::geom::Ring;
|
||||
use solidcraft_engine::geom::{Ring, Vec3};
|
||||
pub(crate) struct Facet {
|
||||
pub(crate) direction: Vec3,
|
||||
pub(crate) points: Ring<Pos2>,
|
||||
|
||||
@@ -9,7 +9,6 @@ license.workspace = true
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
solidcore-record = { workspace = true }
|
||||
solidcraft-geom = { workspace = true }
|
||||
solidcraft-sketch = { workspace = true }
|
||||
solidcraft-kernel = { workspace = true }
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
//! a component's over the physical material's look; a body with none of these keeps the colour
|
||||
//! it was imported with.
|
||||
|
||||
use solidcraft_geom::prelude::*;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::Vec3;
|
||||
|
||||
use crate::Document;
|
||||
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
//! and the engine maps a new feature's geometry into the active component's frame.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solidcore_record::{Plane, Vec3};
|
||||
use solidcraft_geom::prelude::*;
|
||||
use solidcraft_geom::{Plane, Vec3};
|
||||
|
||||
use crate::{DocError, Document, FeatureKind, PlaneRef, Result};
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
//! World-frame metadata at a timeline body ownership boundary.
|
||||
use solidcore_record::Plane;
|
||||
use solidcraft_geom::prelude::*;
|
||||
use solidcraft_geom::Plane;
|
||||
|
||||
use crate::{DocError, Mat, ModelState, Result, apply_point, apply_vector};
|
||||
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
//! The image bytes travel inside the design file (base64), so it reopens without the original.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solidcore_record::{Plane, Vec2, Vec3};
|
||||
use solidcraft_geom::prelude::*;
|
||||
use solidcraft_geom::{Plane, Vec2, Vec3};
|
||||
|
||||
use crate::PlaneRef;
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
//! Certificates are producer identities, never a nearest-surface selection.
|
||||
use crate::{DocError, Document, FeatureKind, ModelBody, Result, SolvedSketch};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solidcore_record::Seg2;
|
||||
use solidcraft_geom::prelude::*;
|
||||
use solidcraft_geom::Seg2;
|
||||
|
||||
fn valid_signature(outer: &[String], holes: &[Vec<String>]) -> bool {
|
||||
let valid = |ids: &[String]| {
|
||||
@@ -69,11 +68,11 @@ pub(crate) fn cap_key(outer: &[String], holes: &[Vec<String>]) -> String {
|
||||
}
|
||||
format!("{:x}", hash.finalize())
|
||||
}
|
||||
fn exact_region_signature(curves: &ExactSketchCurves, region: &solidcore_record::Region2) -> Option<solidcraft_kernel::CapSignature> {
|
||||
fn exact_region_signature(curves: &ExactSketchCurves, region: &solidcraft_geom::Region2) -> Option<solidcraft_kernel::CapSignature> {
|
||||
if region.outer.segs.len() + region.holes.iter().map(|hole| hole.segs.len()).sum::<usize>() > 4096 || region.holes.len() > 128 {
|
||||
return None;
|
||||
}
|
||||
let signature = |outline: &solidcore_record::Loop2| {
|
||||
let signature = |outline: &solidcraft_geom::Loop2| {
|
||||
let mut ids = outline.segs.iter().map(|segment| curves.find(segment)).collect::<Option<Vec<_>>>()?;
|
||||
ids.sort();
|
||||
ids.dedup();
|
||||
@@ -117,7 +116,7 @@ pub(crate) fn extrude_cap_bindings(sketch: &SolvedSketch, bodies: &[solidcraft_k
|
||||
let mut counts = std::collections::BTreeMap::new();
|
||||
let mut bindings = Vec::with_capacity(bodies.len());
|
||||
for body in bodies {
|
||||
let mut cached: Option<(&solidcore_record::Region2, Option<solidcraft_kernel::CapSignature>)> = None;
|
||||
let mut cached: Option<(&solidcraft_geom::Region2, Option<solidcraft_kernel::CapSignature>)> = None;
|
||||
let mut values = Vec::new();
|
||||
for source in body.face_sources().into_iter().flatten() {
|
||||
let signature = if let Some(FaceSource::ExtrudeCap { region, high, .. }) = source {
|
||||
@@ -615,7 +614,7 @@ fn exact_segment_contains(original: &Seg2, generated: &Seg2) -> bool {
|
||||
(Seg2::Line { a, b }, Seg2::Line { a: c, b: d }) => {
|
||||
let direction = *b - *a;
|
||||
let scale = direction.len().max((*d - *c).len()).max(1.);
|
||||
let interval = |point: solidcore_record::Vec2| {
|
||||
let interval = |point: solidcraft_geom::Vec2| {
|
||||
let t = (point - *a).dot(direction) / direction.len2();
|
||||
(-1e-10..=1. + 1e-10).contains(&t)
|
||||
};
|
||||
@@ -651,12 +650,7 @@ fn exact_segment_contains(original: &Seg2, generated: &Seg2) -> bool {
|
||||
}
|
||||
|
||||
impl Document {
|
||||
pub fn configuration_face_target(
|
||||
&self,
|
||||
body: &ModelBody,
|
||||
index: usize,
|
||||
point: solidcore_record::Vec3,
|
||||
) -> Result<crate::config::ConfigFaceTarget> {
|
||||
pub fn configuration_face_target(&self, body: &ModelBody, index: usize, point: solidcraft_geom::Vec3) -> Result<crate::config::ConfigFaceTarget> {
|
||||
let names = crate::naming::face_names(body);
|
||||
let mut name = names
|
||||
.get(index)
|
||||
@@ -725,7 +719,7 @@ impl Document {
|
||||
certificate.validate()?;
|
||||
Ok(Some(certificate))
|
||||
}
|
||||
pub fn strong_face_target(&self, body: &ModelBody, index: usize, point: solidcore_record::Vec3) -> Result<crate::config::ConfigFaceTarget> {
|
||||
pub fn strong_face_target(&self, body: &ModelBody, index: usize, point: solidcraft_geom::Vec3) -> Result<crate::config::ConfigFaceTarget> {
|
||||
let mut target = self.configuration_face_target(body, index, point)?;
|
||||
match body.body.face_sources().and_then(|sources| sources.get(index)).and_then(Option::as_ref) {
|
||||
Some(solidcraft_kernel::FaceSource::Box { producer: Some(producer), axis, high, .. }) => {
|
||||
@@ -758,27 +752,27 @@ impl Document {
|
||||
/// is), projected onto it. A face that moves along its normal keeps the frame where it was;
|
||||
/// one that moves within its plane (a Move feature) keeps it too, as a sketch on a face does
|
||||
/// in the definition's coordinates.
|
||||
pub fn lineage_frame(body: &ModelBody, plane: &solidcore_record::Plane) -> Option<solidcore_record::Plane> {
|
||||
use solidcore_record::Vec3;
|
||||
pub fn lineage_frame(body: &ModelBody, plane: &solidcraft_geom::Plane) -> Option<solidcraft_geom::Plane> {
|
||||
use solidcraft_geom::Vec3;
|
||||
let n = plane.normal();
|
||||
let m = &body.source_transform;
|
||||
let origin = crate::apply_point(m, Vec3::ZERO);
|
||||
let axis = [Vec3::X, Vec3::Y, Vec3::Z].into_iter().filter_map(|a| crate::apply_vector(m, a).normalized()).find(|a| a.dot(n).abs() < 0.6)?;
|
||||
let x = axis - n * axis.dot(n);
|
||||
let origin = origin - n * (origin - plane.origin).dot(n);
|
||||
solidcore_record::Plane::new(origin, x, n.cross(x))
|
||||
solidcraft_geom::Plane::new(origin, x, n.cross(x))
|
||||
}
|
||||
|
||||
/// Whether Split Face split `body`'s faces on `plane` (a split line lies on it).
|
||||
fn split_on_purpose(body: &ModelBody, plane: &solidcore_record::Plane) -> bool {
|
||||
fn split_on_purpose(body: &ModelBody, plane: &solidcraft_geom::Plane) -> bool {
|
||||
let tolerance = 1e-6 * (1. + body.body.size());
|
||||
let on_plane =
|
||||
|line: &Vec<solidcore_record::Vec3>| !line.is_empty() && line.iter().all(|p| (*p - plane.origin).dot(plane.normal()).abs() <= tolerance);
|
||||
|line: &Vec<solidcraft_geom::Vec3>| !line.is_empty() && line.iter().all(|p| (*p - plane.origin).dot(plane.normal()).abs() <= tolerance);
|
||||
body.body.split_lines().is_some_and(|lines| lines.iter().any(on_plane))
|
||||
}
|
||||
|
||||
/// Whether two exact planes are one oriented plane (pieces of one face).
|
||||
fn same_oriented_plane(a: &solidcore_record::Plane, b: &solidcore_record::Plane, scale: f64) -> bool {
|
||||
fn same_oriented_plane(a: &solidcraft_geom::Plane, b: &solidcraft_geom::Plane, scale: f64) -> bool {
|
||||
a.normal().dot(b.normal()) >= 1. - 1e-8 && (b.origin - a.origin).dot(a.normal()).abs() <= 1e-7 * (1. + scale)
|
||||
}
|
||||
|
||||
@@ -786,7 +780,7 @@ impl Document {
|
||||
/// The exact plane an anchor on face `index` of `body` is measured in: the construction
|
||||
/// certificate's plane, or for a [`FaceCertificate::Lineage`] face its current exact plane in
|
||||
/// the [`lineage_frame`].
|
||||
pub fn anchor_face_plane(&self, body: &ModelBody, index: usize, certificate: Option<&FaceCertificate>) -> Option<solidcore_record::Plane> {
|
||||
pub fn anchor_face_plane(&self, body: &ModelBody, index: usize, certificate: Option<&FaceCertificate>) -> Option<solidcraft_geom::Plane> {
|
||||
match certificate {
|
||||
Some(FaceCertificate::Lineage) => body.body.planar_face(index).and_then(|plane| lineage_frame(body, &plane)),
|
||||
_ => body.body.face_plane(index),
|
||||
@@ -802,7 +796,7 @@ impl Document {
|
||||
&self,
|
||||
body: &ModelBody,
|
||||
index: usize,
|
||||
point: solidcore_record::Vec3,
|
||||
point: solidcraft_geom::Vec3,
|
||||
) -> Option<crate::config::ConfigFaceTarget> {
|
||||
let names = crate::naming::names(body);
|
||||
if !names.verified(index) {
|
||||
@@ -961,7 +955,7 @@ mod tests {
|
||||
SolvedSketch {
|
||||
feature: 1,
|
||||
name: "sources".into(),
|
||||
plane: solidcore_record::Plane::XY,
|
||||
plane: solidcraft_geom::Plane::XY,
|
||||
sketch,
|
||||
report,
|
||||
profiles: Vec::new(),
|
||||
@@ -996,7 +990,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn prepared_sources_keep_exact_identity_and_reject_ambiguity() {
|
||||
use solidcore_record::Vec2;
|
||||
use solidcraft_geom::Vec2;
|
||||
let mut sketch = solidcraft_sketch::Sketch::new();
|
||||
sketch.add_line(Vec2::ZERO, Vec2::new(10., 0.), None, None, Some("line")).unwrap();
|
||||
sketch.add_circle(Vec2::new(20., 0.), 2., None, Some("circle")).unwrap();
|
||||
@@ -1021,7 +1015,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn a_curve_drawn_over_a_projected_boundary_is_the_source() {
|
||||
use solidcore_record::Vec2;
|
||||
use solidcraft_geom::Vec2;
|
||||
// A face sketch's live boundary, with a profile edge traced over it (issue #489).
|
||||
let mut sketch = solidcraft_sketch::Sketch::new();
|
||||
let projected = sketch.add_line(Vec2::ZERO, Vec2::new(10., 0.), None, None, Some("edge")).unwrap();
|
||||
@@ -1045,7 +1039,7 @@ mod tests {
|
||||
#[test]
|
||||
#[ignore = "manual optimized certification microbenchmark; no timing assertion"]
|
||||
fn benchmark_prepared_extrude_sources() {
|
||||
use solidcore_record::Vec2;
|
||||
use solidcraft_geom::Vec2;
|
||||
let mut sketch = solidcraft_sketch::Sketch::new();
|
||||
for i in 0..1000 {
|
||||
let y = i as f64;
|
||||
@@ -1098,7 +1092,7 @@ mod tests {
|
||||
#[test]
|
||||
fn analytic_arc_interval_refuses_unsampled_gaps_and_accepts_wrap_reversal() {
|
||||
let radians = |degrees: f64| degrees.to_radians();
|
||||
let arc = |start, sweep| Seg2::Arc { center: solidcore_record::Vec2::default(), radius: 10., start: radians(start), sweep: radians(sweep) };
|
||||
let arc = |start, sweep| Seg2::Arc { center: solidcraft_geom::Vec2::default(), radius: 10., start: radians(start), sweep: radians(sweep) };
|
||||
assert!(!exact_segment_contains(&arc(10., 350.), &arc(20., 360.)), "five point probes missed the missing ten-degree gap");
|
||||
assert!(!exact_segment_contains(&arc(10., 350.), &arc(20., -360.)));
|
||||
assert!(exact_segment_contains(&arc(350., 40.), &arc(355., 20.)));
|
||||
|
||||
@@ -142,7 +142,7 @@ mod tests {
|
||||
body: "Old".into(),
|
||||
origin_feature: 3,
|
||||
name: "F3:+z".into(),
|
||||
point: solidcore_record::Vec3::new(1., 1., 2.),
|
||||
point: solidcraft_geom::Vec3::new(1., 1., 2.),
|
||||
certificate: None,
|
||||
}),
|
||||
],
|
||||
@@ -199,7 +199,7 @@ mod tests {
|
||||
body: "Foreign".into(),
|
||||
origin_feature: 3,
|
||||
name: "F3:+z".into(),
|
||||
point: solidcore_record::Vec3::ZERO,
|
||||
point: solidcraft_geom::Vec3::ZERO,
|
||||
certificate: None,
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::*;
|
||||
use crate::{FeatureKind, Model, PlaneRef};
|
||||
use serde_json::json;
|
||||
use solidcore_record::{Vec2, Vec3};
|
||||
use solidcraft_geom::{Vec2, Vec3};
|
||||
|
||||
fn shell_document() -> (Document, u64, u64) {
|
||||
let mut doc = Document::new("certified shell");
|
||||
@@ -43,15 +43,15 @@ fn face_index(body: &ModelBody, inner: bool) -> usize {
|
||||
|
||||
#[test]
|
||||
fn shell_planar_raw_bounds_flat_identity_and_clone_remap() {
|
||||
let plane = solidcore_record::Plane::XY;
|
||||
let same = |planes: &[Option<solidcore_record::Plane>]| super::super::styles::shell_plane_family(planes.iter().copied());
|
||||
let plane = solidcraft_geom::Plane::XY;
|
||||
let same = |planes: &[Option<solidcraft_geom::Plane>]| super::super::styles::shell_plane_family(planes.iter().copied());
|
||||
assert!(same(&[Some(plane), Some(plane)]));
|
||||
assert!(!same(&[]));
|
||||
assert!(!same(&[Some(plane), None]));
|
||||
for different in [
|
||||
solidcore_record::Plane { origin: Vec3::X, ..plane },
|
||||
solidcore_record::Plane { x: -plane.x, y: -plane.y, ..plane },
|
||||
solidcore_record::Plane { y: -plane.y, ..plane },
|
||||
solidcraft_geom::Plane { origin: Vec3::X, ..plane },
|
||||
solidcraft_geom::Plane { x: -plane.x, y: -plane.y, ..plane },
|
||||
solidcraft_geom::Plane { y: -plane.y, ..plane },
|
||||
] {
|
||||
assert!(!same(&[Some(plane), Some(different)]));
|
||||
}
|
||||
@@ -355,7 +355,7 @@ fn saved_curved_strip_projection_asks_for_its_face_again_after_exact_wall_coales
|
||||
sketch.add_link(LinkKind::Project, source, &geometry).unwrap();
|
||||
sketch.links[0].face_name = Some(former);
|
||||
let sk = doc
|
||||
.add_feature(FeatureKind::Sketch { plane: PlaneRef::Custom { plane: solidcore_record::Plane::YZ }, sketch }, Some("Saved corner strip"))
|
||||
.add_feature(FeatureKind::Sketch { plane: PlaneRef::Custom { plane: solidcraft_geom::Plane::YZ }, sketch }, Some("Saved corner strip"))
|
||||
.unwrap();
|
||||
let boss = doc
|
||||
.add_feature(
|
||||
@@ -431,7 +431,7 @@ fn saved_stepped_tab_cut_piece_projection_asks_for_its_face_again_without_expand
|
||||
// the patch y 10..20, z 0..20 by its piece name. The kernel leaves the wall one face now: the
|
||||
// patch is only that saved name and its rectangle on YZ.
|
||||
let at = Vec3::new(0., 15., 1.);
|
||||
let plane = solidcore_record::Plane::YZ;
|
||||
let plane = solidcraft_geom::Plane::YZ;
|
||||
let current_state = model.state();
|
||||
let current = current_state.body("Case").unwrap();
|
||||
let stem = crate::naming::face_name_at_body(current, at).unwrap();
|
||||
|
||||
@@ -3,10 +3,9 @@ use super::Column;
|
||||
use crate::{DocError, Document, FeatureKind, ModelState, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::prelude::*;
|
||||
use solidcraft_geom::Vec3;
|
||||
|
||||
pub(crate) fn shell_plane_family(planes: impl IntoIterator<Item = Option<solidcore_record::Plane>>) -> bool {
|
||||
pub(crate) fn shell_plane_family(planes: impl IntoIterator<Item = Option<solidcraft_geom::Plane>>) -> bool {
|
||||
let mut planes = planes.into_iter();
|
||||
let Some(Some(first)) = planes.next() else { return false };
|
||||
first.origin.is_finite() && first.x.is_finite() && first.y.is_finite() && planes.all(|plane| plane == Some(first))
|
||||
|
||||
@@ -6,11 +6,10 @@
|
||||
//! vertex the names of the edges that meet there, so they follow the model when earlier features
|
||||
//! change; the picked point is the fallback.
|
||||
|
||||
use solidcraft_geom::prelude::*;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solidcore_record::{Plane, Vec2, Vec3};
|
||||
use solidcraft_geom::{Plane, Vec2, Vec3};
|
||||
use solidcraft_kernel::{self as kernel, FaceSurface};
|
||||
|
||||
use crate::eval::{ModelBody, ModelState};
|
||||
@@ -460,7 +459,7 @@ fn path_segs(ctx: &Ctx, g: &GeoRef) -> Result<Vec<kernel::PathSeg>> {
|
||||
GeoRef::Edge { at, name } => {
|
||||
let (b, i, pts) = edge_pick(ctx.st, *at, name)?;
|
||||
let procedural =
|
||||
b.body.record().edges.get(i).is_some_and(|e| matches!(e.parameters(), solidcore_record::brep::Curve::SurfaceIntersection { .. }));
|
||||
b.body.record().edges.get(i).is_some_and(|e| matches!(e.parameters(), solidcraft_geom::brep::Curve::SurfaceIntersection { .. }));
|
||||
if procedural {
|
||||
return Ok(lines(&pts));
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ mod tests {
|
||||
/// closer to the surface, and the reference mesh the model reads never changes.
|
||||
#[test]
|
||||
fn each_level_has_its_own_plan_and_the_reference_mesh_stays() {
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::Vec3;
|
||||
let body = solidcraft_kernel::cylinder(Vec3::ZERO, Vec3::Z, 20.0, 50.0).unwrap();
|
||||
let mb = crate::ModelBody::new("Body1".into(), body, 1);
|
||||
let reference = mb.mesh();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solidcore_record::{Plane, Vec2, Vec3};
|
||||
use solidcraft_geom::prelude::*;
|
||||
use solidcraft_geom::{Plane, Vec2, Vec3};
|
||||
use solidcraft_kernel::Continuity;
|
||||
use solidcraft_sketch::Sketch;
|
||||
|
||||
@@ -3187,7 +3186,7 @@ impl Visibility {
|
||||
pub struct NamedView {
|
||||
#[serde(default)]
|
||||
pub name: String,
|
||||
pub target: solidcore_record::Vec3,
|
||||
pub target: solidcraft_geom::Vec3,
|
||||
pub yaw: f64,
|
||||
pub pitch: f64,
|
||||
pub distance: f64,
|
||||
|
||||
+22
-24
@@ -5,16 +5,14 @@ mod placed_mesh;
|
||||
#[path = "placement_reuse.rs"]
|
||||
mod placement_reuse;
|
||||
|
||||
use solidcraft_geom::prelude::*;
|
||||
use std::collections::BTreeMap;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solidcore_record::brep::part::Contact;
|
||||
use solidcore_record::{Plane, Region2, Vec2, Vec3};
|
||||
use solidcraft_geom::brep::part::Contact;
|
||||
use solidcraft_geom::tess::TessPlan;
|
||||
use solidcraft_geom::{Mesh, UnitVec3, regions_apart};
|
||||
use solidcraft_geom::{Mesh, Plane, Region2, UnitVec3, Vec2, Vec3, regions_apart};
|
||||
use solidcraft_kernel::{self as kernel, Body, BoolOp};
|
||||
use solidcraft_sketch::{CurveKind, Profile, SolveReport, find_profiles};
|
||||
|
||||
@@ -3420,7 +3418,7 @@ fn groups_from_body(mb: &ModelBody, groups: &[Vec<usize>]) -> Option<Vec<bool>>
|
||||
let holds = |k: usize| {
|
||||
let shells = regions.get(k).and_then(|&r| record.regions.get(r)).map(|r| r.shells.as_slice()).unwrap_or_default();
|
||||
shells.iter().any(|&s| match record.shells.get(s as usize) {
|
||||
Some(solidcore_record::brep::Shell::Faces { faces, .. }) => faces.iter().any(|f| from.get(f.face as usize) == Some(&true)),
|
||||
Some(solidcraft_geom::brep::Shell::Faces { faces, .. }) => faces.iter().any(|f| from.get(f.face as usize) == Some(&true)),
|
||||
_ => false,
|
||||
})
|
||||
};
|
||||
@@ -4586,7 +4584,7 @@ fn hole_tools_and_prisms(
|
||||
let seam = std::cell::Cell::new(0.0);
|
||||
let disc = |rad: f64| {
|
||||
seam.set(seam.get() + 0.613);
|
||||
Region2 { outer: solidcore_record::Loop2::circle_from(Vec2::ZERO, rad, seam.get()), holes: vec![] }
|
||||
Region2 { outer: solidcraft_geom::Loop2::circle_from(Vec2::ZERO, rad, seam.get()), holes: vec![] }
|
||||
};
|
||||
let cyl = |rad: f64, lo: f64, hi: f64, part: &str| -> Result<(Traced, Option<more::PrimitivePrism>)> {
|
||||
let tool = kernel::extrude(&plane, &[disc(rad)], lo, hi)?.pop().ok_or_else(|| DocError::Invalid("hole tool".into()))?;
|
||||
@@ -4616,7 +4614,7 @@ fn hole_tools_and_prisms(
|
||||
let side = up.any_perp();
|
||||
let rp = Plane::new(*position, side, up).ok_or_else(|| DocError::Invalid("hole axis".into()))?;
|
||||
let pts = [Vec2::new(0.0, top), Vec2::new(r, top), Vec2::new(r, bottom), Vec2::new(0.0, bottom - tip)];
|
||||
let region = Region2 { outer: solidcore_record::Loop2::polygon(&pts), holes: vec![] };
|
||||
let region = Region2 { outer: solidcraft_geom::Loop2::polygon(&pts), holes: vec![] };
|
||||
return Ok(kernel::revolve(&rp, &[region], Vec2::ZERO, Vec2::Y, std::f64::consts::TAU)?
|
||||
.into_iter()
|
||||
.map(|tool| (crate::lineage::part_tool(tool, "bore"), None))
|
||||
@@ -4652,7 +4650,7 @@ fn hole_tools_and_prisms(
|
||||
let up = -dir;
|
||||
let rp = Plane::new(*position, up.any_perp(), up).ok_or_else(|| DocError::Invalid("hole axis".into()))?;
|
||||
let pts = [Vec2::new(rc, 0.0), Vec2::new(r_end, -len), Vec2::new(0.0, -len), Vec2::new(0.0, top), Vec2::new(rc, top)];
|
||||
let region = Region2 { outer: solidcore_record::Loop2::polygon(&pts), holes: vec![] };
|
||||
let region = Region2 { outer: solidcraft_geom::Loop2::polygon(&pts), holes: vec![] };
|
||||
tools.extend(
|
||||
kernel::revolve(&rp, &[region], Vec2::ZERO, Vec2::Y, std::f64::consts::TAU)?
|
||||
.into_iter()
|
||||
@@ -5100,7 +5098,7 @@ fn feature_tools_with_constructor(
|
||||
cap_bindings.as_ref().and_then(|bindings| bindings.get(body_index)).cloned().unwrap_or_else(|| vec![None; curves.len()]);
|
||||
// A cap moved off its designed height (into a Join target, or past a
|
||||
// curved To face) is not the feature's start or end face.
|
||||
let nominal = |plane: &solidcore_record::Plane| {
|
||||
let nominal = |plane: &solidcraft_geom::Plane| {
|
||||
let height = (plane.origin - ss.plane.origin).dot(n);
|
||||
let tolerance = 1e-9 * (1.0 + height.abs() + (lo + off).abs() + (hi + off).abs());
|
||||
(height - (lo + off)).abs() <= tolerance || (height - (hi + off)).abs() <= tolerance
|
||||
@@ -5304,10 +5302,10 @@ fn feature_tools_with_constructor(
|
||||
let plane = Plane { origin: *base, x, y: k };
|
||||
let c = Vec2::new(r, 0.0);
|
||||
let outer = match section {
|
||||
crate::CoilSection::Circular => solidcore_record::Loop2::circle(c, s / 2.0),
|
||||
crate::CoilSection::Circular => solidcraft_geom::Loop2::circle(c, s / 2.0),
|
||||
crate::CoilSection::Square => {
|
||||
let h = s / 2.0;
|
||||
solidcore_record::Loop2::polygon(&[c + Vec2::new(-h, -h), c + Vec2::new(h, -h), c + Vec2::new(h, h), c + Vec2::new(-h, h)])
|
||||
solidcraft_geom::Loop2::polygon(&[c + Vec2::new(-h, -h), c + Vec2::new(h, -h), c + Vec2::new(h, h), c + Vec2::new(-h, h)])
|
||||
}
|
||||
};
|
||||
let region = Region2 { outer, holes: Vec::new() };
|
||||
@@ -5505,11 +5503,11 @@ fn pipe_region(vals: &BTreeMap<String, Value>, r: f64, wall: &Option<crate::Expr
|
||||
if !(wt > 0.0 && wt < r) {
|
||||
return Err(DocError::Invalid("the wall must be thinner than the radius".into()));
|
||||
}
|
||||
vec![solidcore_record::Loop2::circle(Vec2::ZERO, r - wt).reversed()]
|
||||
vec![solidcraft_geom::Loop2::circle(Vec2::ZERO, r - wt).reversed()]
|
||||
}
|
||||
None => Vec::new(),
|
||||
};
|
||||
Ok(Region2 { outer: solidcore_record::Loop2::circle(Vec2::ZERO, r), holes })
|
||||
Ok(Region2 { outer: solidcraft_geom::Loop2::circle(Vec2::ZERO, r), holes })
|
||||
}
|
||||
|
||||
pub fn map_wire(wire: &mut solidcraft_sketch::Wire, matrix: &Mat) {
|
||||
@@ -5587,8 +5585,8 @@ fn path_points(ps: &SolvedSketch, ids: &[String], start: Vec3) -> Result<Option<
|
||||
Some(solidcraft_sketch::Wire { arc: Some(arc), .. }) => {
|
||||
let x = (arc.start - arc.center).normalized().ok_or_else(|| DocError::Invalid("a 3D arc of no radius".into()))?;
|
||||
let y = arc.normal.cross(x);
|
||||
let frame = solidcore_record::brep::Frame { origin: arc.center.into(), x_axis: x.into(), y_axis: y.into() };
|
||||
let circle = solidcore_record::brep::Curve::Circle { frame, radius: arc.radius };
|
||||
let frame = solidcraft_geom::brep::Frame { origin: arc.center.into(), x_axis: x.into(), y_axis: y.into() };
|
||||
let circle = solidcraft_geom::brep::Curve::Circle { frame, radius: arc.radius };
|
||||
kernel::PathPiece::Segs(kernel::curve_path(&circle, [0.0, solidcraft_sketch::arc_sweep(arc)])?)
|
||||
}
|
||||
Some(w) if w.fit.len() >= 3 => kernel::PathPiece::Spline { fit: w.fit.clone(), reversed: false },
|
||||
@@ -5658,14 +5656,14 @@ pub(crate) fn path_segments_at(ps: &SolvedSketch, ids: &[String], start: Vec3, p
|
||||
}
|
||||
// Each curve as its exact pieces (Béziers, conics) where it has them, in its own direction
|
||||
// of travel. A curve is turned round whole: its pieces stay one run.
|
||||
let mut curves: Vec<Vec<solidcore_record::Seg2>> = Vec::with_capacity(ids.len());
|
||||
let mut curves: Vec<Vec<solidcraft_geom::Seg2>> = Vec::with_capacity(ids.len());
|
||||
for id in ids {
|
||||
let ci = ps.sketch.curve_index(id).ok_or_else(|| DocError::Unknown(format!("path curve `{id}`")))?;
|
||||
curves.push(ps.sketch.exact_segs(ci).unwrap_or_else(|| ps.sketch.segs(ci)));
|
||||
}
|
||||
curves.retain(|c| !c.is_empty());
|
||||
let ends = |c: &[solidcore_record::Seg2]| c.first().zip(c.last()).map(|(f, l)| (f.start(), l.end()));
|
||||
let reversed = |c: Vec<solidcore_record::Seg2>| c.into_iter().rev().map(|s| s.reversed()).collect::<Vec<_>>();
|
||||
let ends = |c: &[solidcraft_geom::Seg2]| c.first().zip(c.last()).map(|(f, l)| (f.start(), l.end()));
|
||||
let reversed = |c: Vec<solidcraft_geom::Seg2>| c.into_iter().rev().map(|s| s.reversed()).collect::<Vec<_>>();
|
||||
// Where two curves meet: one tolerance for telling which ends touch and for the joint.
|
||||
const JOINT: f64 = 1e-5;
|
||||
// Orient the first curve: it runs toward the second where it meets it at one end only;
|
||||
@@ -5684,9 +5682,9 @@ pub(crate) fn path_segments_at(ps: &SolvedSketch, ids: &[String], start: Vec3, p
|
||||
}
|
||||
}
|
||||
// Each next curve starts where the previous one ended.
|
||||
let mut segs: Vec<solidcore_record::Seg2> = Vec::new();
|
||||
let mut segs: Vec<solidcraft_geom::Seg2> = Vec::new();
|
||||
for curve in curves {
|
||||
let (Some(prev_end), Some((a, b))) = (segs.last().map(solidcore_record::Seg2::end), ends(&curve)) else {
|
||||
let (Some(prev_end), Some((a, b))) = (segs.last().map(solidcraft_geom::Seg2::end), ends(&curve)) else {
|
||||
segs.extend(curve);
|
||||
continue;
|
||||
};
|
||||
@@ -5706,9 +5704,9 @@ pub(crate) fn path_segments_at(ps: &SolvedSketch, ids: &[String], start: Vec3, p
|
||||
/// `start`: the curve there split in two, its second part first and its first part last (or the
|
||||
/// chain begun at the joint there). Unchanged when the chain is open, already starts on the
|
||||
/// profile's plane, or its point nearest `start` is off that plane.
|
||||
fn from_profile_on_closed_path(segs: Vec<solidcore_record::Seg2>, path: &Plane, profile: &Plane, start: Vec3) -> Vec<solidcore_record::Seg2> {
|
||||
fn from_profile_on_closed_path(segs: Vec<solidcraft_geom::Seg2>, path: &Plane, profile: &Plane, start: Vec3) -> Vec<solidcraft_geom::Seg2> {
|
||||
let (Some(first), Some(last)) = (segs.first(), segs.last()) else { return segs };
|
||||
let size: f64 = segs.iter().map(solidcore_record::Seg2::length).sum();
|
||||
let size: f64 = segs.iter().map(solidcraft_geom::Seg2::length).sum();
|
||||
let on_plane = |p: Vec2| profile.height(path.to_world(p)).abs() <= (size * 1e-9).max(1e-9);
|
||||
if !start.is_finite() || !(size > 0.0 && size.is_finite()) || last.end().dist(first.start()) > 1e-5 || on_plane(first.start()) {
|
||||
return segs;
|
||||
@@ -7198,7 +7196,7 @@ fn eval_feature(
|
||||
(None, Some(sid)) => {
|
||||
let ss = st.sketch(sid.0).ok_or_else(|| DocError::Unknown(format!("sketch {sid}")))?;
|
||||
// Each curve's exact segments (lines and arcs split by their own plane and cylinder).
|
||||
let curves: Vec<Vec<solidcore_record::Seg2>> = (0..ss.sketch.curves.len())
|
||||
let curves: Vec<Vec<solidcraft_geom::Seg2>> = (0..ss.sketch.curves.len())
|
||||
.filter(|ci| ss.sketch.curves.get(*ci).is_some_and(|c| !c.construction))
|
||||
.map(|ci| ss.sketch.segs(ci))
|
||||
.filter(|segs| !segs.is_empty())
|
||||
@@ -8666,7 +8664,7 @@ pub fn sketch_line_plane(st: &ModelState, sketch: u64, line: &str) -> Result<Pla
|
||||
let ss = st.sketch(sketch).ok_or_else(|| DocError::Unknown(format!("sketch {sketch}")))?;
|
||||
let ci = ss.sketch.curve_index(line).ok_or_else(|| DocError::Unknown(format!("curve `{line}`")))?;
|
||||
let (a, b) = match ss.sketch.segs(ci).as_slice() {
|
||||
[solidcore_record::Seg2::Line { a, b }] => (*a, *b),
|
||||
[solidcraft_geom::Seg2::Line { a, b }] => (*a, *b),
|
||||
_ => return Err(DocError::Invalid(format!("`{line}` is not a straight line: split by a sketch line"))),
|
||||
};
|
||||
let (wa, wb) = (ss.plane.to_world(a), ss.plane.to_world(b));
|
||||
|
||||
@@ -4,12 +4,11 @@
|
||||
//! component into its frame, and every evaluation wrote every imported STEP file out again to
|
||||
//! fingerprint it, so each sketch command took longer the more parts were inserted.
|
||||
|
||||
use solidcraft_geom::prelude::*;
|
||||
use std::cell::Cell;
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use solidcore_record::{Plane, Vec2, Vec3};
|
||||
use solidcraft_geom::{Plane, Vec2, Vec3};
|
||||
use solidcraft_kernel as kernel;
|
||||
use solidcraft_sketch::Sketch;
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
//! Frame members (#4003): one body per straight line of a frame sketch, its cross-section a
|
||||
//! profile laid square to the line (`frame::member_axes`) and run from one end to the other.
|
||||
|
||||
use solidcraft_geom::prelude::*;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
|
||||
use solidcore_record::{Plane, Region2, Vec2, Vec3};
|
||||
use solidcraft_geom::{Aabb2, Ring};
|
||||
use solidcraft_geom::{Aabb2, Plane, Region2, Ring, Vec2, Vec3};
|
||||
use solidcraft_kernel as kernel;
|
||||
use solidcraft_sketch::CurveKind;
|
||||
|
||||
|
||||
@@ -5,8 +5,7 @@ use super::*;
|
||||
use crate::EmbossFace;
|
||||
use crate::apply_point;
|
||||
use crate::sheet;
|
||||
use solidcore_record::{Loop2, Seg2};
|
||||
use solidcraft_geom::{Aabb3, Ring};
|
||||
use solidcraft_geom::{Aabb3, Loop2, Ring, Seg2};
|
||||
|
||||
pub(super) fn eval(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st: &mut ModelState) -> Result<()> {
|
||||
match &f.kind {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
use super::*;
|
||||
use crate::{RuledCorners, RuledMode, SheetSection};
|
||||
use solidcore_record::Seg2;
|
||||
use solidcraft_geom::Seg2;
|
||||
|
||||
pub(super) fn eval(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st: &mut ModelState, warning: &mut Option<String>) -> Result<()> {
|
||||
match &f.kind {
|
||||
@@ -233,7 +233,7 @@ pub(super) fn eval(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature,
|
||||
return Err(DocError::Invalid("a match needs a chain of the target's edges".into()));
|
||||
}
|
||||
// Picked by a point on the face; without picks, the body's first face.
|
||||
let picked: Vec<solidcore_record::Vec3> = if faces.is_empty() {
|
||||
let picked: Vec<solidcraft_geom::Vec3> = if faces.is_empty() {
|
||||
let faces = mb.body.faces(mb.body.size() * 1e-2)?;
|
||||
vec![faces.first().ok_or_else(|| DocError::Invalid(format!("`{body}` has no face to match")))?.centroid]
|
||||
} else {
|
||||
@@ -434,7 +434,7 @@ pub(super) fn eval(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature,
|
||||
kernel::loft_sheet_with_rails(&secs, &rails, crate::eval::kernel_rail_fit(*rail_fit))?
|
||||
};
|
||||
// Each wall named by the section curves it runs between, as a loft's sides are.
|
||||
let loops: Vec<solidcore_record::Loop2> = secs.iter().map(|(_, c)| solidcore_record::Loop2 { segs: c.clone() }).collect();
|
||||
let loops: Vec<solidcraft_geom::Loop2> = secs.iter().map(|(_, c)| solidcraft_geom::Loop2 { segs: c.clone() }).collect();
|
||||
let traced = crate::lineage::SectionRoles { sections: sketches.iter().zip(&loops).map(|(s, lp)| (s, vec![lp])).collect() }.tool(body);
|
||||
push_sheet(st, f, traced);
|
||||
Ok(())
|
||||
@@ -572,7 +572,7 @@ fn loop_patch(ss: &SolvedSketch, curves: &[String]) -> Result<kernel::Body> {
|
||||
|
||||
/// A network curve resolved in the feature's frame, owning what it is built from.
|
||||
enum NetSource {
|
||||
Sketch(solidcore_record::Plane, Vec<Seg2>),
|
||||
Sketch(solidcraft_geom::Plane, Vec<Seg2>),
|
||||
Points(Vec<Vec3>),
|
||||
Edges(kernel::Body, Vec<Vec3>),
|
||||
Point(Vec3),
|
||||
@@ -662,7 +662,7 @@ fn boundary_frames(
|
||||
use solidcraft_kernel::{Boundary, Frame};
|
||||
// The stations along each curve, evenly in its own arclength, so every side carries the same
|
||||
// number of frames and the patch's net is square.
|
||||
let mut sides: Vec<Vec<solidcore_record::Vec3>> = Vec::with_capacity(curves.len());
|
||||
let mut sides: Vec<Vec<solidcraft_geom::Vec3>> = Vec::with_capacity(curves.len());
|
||||
for id in curves {
|
||||
let ci = ss.sketch.curve_index(id).ok_or_else(|| DocError::Unknown(format!("sketch curve `{id}`")))?;
|
||||
let pieces = ss.sketch.exact_pieces(ci);
|
||||
@@ -690,7 +690,7 @@ fn boundary_frames(
|
||||
}
|
||||
// The middle of the loop, so every side leaves towards the inside of the patch.
|
||||
let count = sides.iter().map(Vec::len).sum::<usize>().max(1) as f64;
|
||||
let centre = sides.iter().flatten().fold(solidcore_record::Vec3::ZERO, |a, p| a + *p) * (1.0 / count);
|
||||
let centre = sides.iter().flatten().fold(solidcraft_geom::Vec3::ZERO, |a, p| a + *p) * (1.0 / count);
|
||||
let mut out = Vec::with_capacity(sides.len());
|
||||
for (side, pts) in sides.iter().enumerate() {
|
||||
let mut frames = Vec::with_capacity(pts.len());
|
||||
@@ -829,8 +829,8 @@ fn loft_blend(
|
||||
) -> Result<()> {
|
||||
/// One section: a chain of sketch curves in its plane, or a body's edge chain.
|
||||
enum Piece {
|
||||
Sketch(solidcore_record::Plane, Vec<solidcore_record::Seg2>),
|
||||
Body(String, Vec<solidcore_record::Vec3>),
|
||||
Sketch(solidcraft_geom::Plane, Vec<solidcraft_geom::Seg2>),
|
||||
Body(String, Vec<solidcraft_geom::Vec3>),
|
||||
}
|
||||
let mut pieces = Vec::with_capacity(sections.len());
|
||||
let mut centres = Vec::with_capacity(sections.len());
|
||||
@@ -840,7 +840,7 @@ fn loft_blend(
|
||||
return Err(DocError::Invalid("a loft section picked from a body needs a chain of its edges".into()));
|
||||
}
|
||||
let n = sec.edges.len() as f64;
|
||||
centres.push(sec.edges.iter().fold(solidcore_record::Vec3::ZERO, |a, p| a + **p) * (1.0 / n));
|
||||
centres.push(sec.edges.iter().fold(solidcraft_geom::Vec3::ZERO, |a, p| a + **p) * (1.0 / n));
|
||||
pieces.push(Piece::Body(name.to_string(), crate::plain(&sec.edges)));
|
||||
continue;
|
||||
}
|
||||
@@ -858,7 +858,7 @@ fn loft_blend(
|
||||
return Err(DocError::Invalid("each loft section must be one chain: one profile without holes, or curves meeting end to end".into()));
|
||||
};
|
||||
let pts: Vec<Vec2> = chain.iter().flat_map(|s| s.polyline(1e-2)).collect();
|
||||
let centre = pts.iter().fold(solidcore_record::Vec3::ZERO, |a, p| a + ss.plane.to_world(*p)) * (1.0 / pts.len().max(1) as f64);
|
||||
let centre = pts.iter().fold(solidcraft_geom::Vec3::ZERO, |a, p| a + ss.plane.to_world(*p)) * (1.0 / pts.len().max(1) as f64);
|
||||
centres.push(centre);
|
||||
pieces.push(Piece::Sketch(ss.plane, chain));
|
||||
}
|
||||
@@ -870,7 +870,7 @@ fn loft_blend(
|
||||
let (from, to) = if i < last { (i, i + 1) } else { (last, last.saturating_sub(1)) };
|
||||
let leave = match (centres.get(from), centres.get(to)) {
|
||||
(Some(a), Some(b)) => *b - *a,
|
||||
_ => solidcore_record::Vec3::ZERO,
|
||||
_ => solidcraft_geom::Vec3::ZERO,
|
||||
};
|
||||
match piece {
|
||||
Piece::Body(name, edges) => {
|
||||
@@ -889,9 +889,9 @@ fn loft_blend(
|
||||
/// The frames of one sketch chain, sampled by arclength at `stations` stations: a sketch has no
|
||||
/// face behind it, so the surface leaves along the run with no curvature to repeat there.
|
||||
fn chain_frames(
|
||||
plane: &solidcore_record::Plane,
|
||||
chain: &[solidcore_record::Seg2],
|
||||
leave: solidcore_record::Vec3,
|
||||
plane: &solidcraft_geom::Plane,
|
||||
chain: &[solidcraft_geom::Seg2],
|
||||
leave: solidcraft_geom::Vec3,
|
||||
stations: usize,
|
||||
) -> Vec<kernel::Frame> {
|
||||
let mut pts: Vec<Vec2> = Vec::new();
|
||||
@@ -920,19 +920,14 @@ fn chain_frames(
|
||||
}
|
||||
let (Some(a), Some(b)) = (pts.get(k).copied(), pts.get(k + 1).copied()) else {
|
||||
if let Some(p) = pts.get(k).copied() {
|
||||
out.push(kernel::Frame {
|
||||
p: plane.to_world(p),
|
||||
leave,
|
||||
bend: solidcore_record::Vec3::ZERO,
|
||||
bend_change: solidcore_record::Vec3::ZERO,
|
||||
});
|
||||
out.push(kernel::Frame { p: plane.to_world(p), leave, bend: solidcraft_geom::Vec3::ZERO, bend_change: solidcraft_geom::Vec3::ZERO });
|
||||
}
|
||||
continue;
|
||||
};
|
||||
let span = (b - a).len();
|
||||
let t = if span > 1e-12 { ((want - walked) / span).clamp(0.0, 1.0) } else { 0.0 };
|
||||
let p = a * (1.0 - t) + b * t;
|
||||
out.push(kernel::Frame { p: plane.to_world(p), leave, bend: solidcore_record::Vec3::ZERO, bend_change: solidcore_record::Vec3::ZERO });
|
||||
out.push(kernel::Frame { p: plane.to_world(p), leave, bend: solidcraft_geom::Vec3::ZERO, bend_change: solidcraft_geom::Vec3::ZERO });
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
@@ -674,7 +674,7 @@ fn taper_envelope(t: &ThreadInfo, radius: f64, ends: [End; 2], (d0, from): (f64,
|
||||
vec![Vec2::new(rc(-e), -e), Vec2::new(rc(len), len), Vec2::new(r_out, len), Vec2::new(r_out, -e)]
|
||||
}
|
||||
};
|
||||
let region = Region2 { outer: solidcore_record::Loop2::polygon(&pts), holes: Vec::new() };
|
||||
let region = Region2 { outer: solidcraft_geom::Loop2::polygon(&pts), holes: Vec::new() };
|
||||
let mut ring = kernel::revolve(&plane, &[region], Vec2::new(0.0, 0.0), Vec2::new(0.0, 1.0), std::f64::consts::TAU)?;
|
||||
Ok(ring.pop())
|
||||
}
|
||||
@@ -768,7 +768,7 @@ pub(super) fn cut(st: &mut ModelState, on: ThreadOn, (tip, angle, depth): (Threa
|
||||
};
|
||||
let mut grooves = Vec::new();
|
||||
for g in thread_grooves(&t, radius, (cyl.start, cyl.end), ends, steep, ease, taper)? {
|
||||
let section = Region2 { outer: solidcore_record::Loop2::polygon(&g.section), holes: Vec::new() };
|
||||
let section = Region2 { outer: solidcraft_geom::Loop2::polygon(&g.section), holes: Vec::new() };
|
||||
grooves.push(kernel::sweep_helix_radial(&g.plane, §ion, g.base, g.axis, g.pitch, g.turns, &g.law)?);
|
||||
}
|
||||
if let Some(taper) = taper
|
||||
@@ -872,7 +872,7 @@ fn tip_cones(
|
||||
// In the plane: x radial, y outward along the axis from the end.
|
||||
let plane = Plane { origin: t.axis_point + k * at, x: u, y: out_dir };
|
||||
let pts = [Vec2::new(r_f, 0.0), Vec2::new(r_f, e), Vec2::new(r_out, e), Vec2::new(r_out, -(r_out - r_f) / slope)];
|
||||
let region = Region2 { outer: solidcore_record::Loop2::polygon(&pts), holes: Vec::new() };
|
||||
let region = Region2 { outer: solidcraft_geom::Loop2::polygon(&pts), holes: Vec::new() };
|
||||
let mut ring = kernel::revolve(&plane, &[region], Vec2::new(0.0, 0.0), Vec2::new(0.0, 1.0), std::f64::consts::TAU)?;
|
||||
out.push(ring.pop().ok_or_else(|| DocError::Invalid("chamfer".into()))?);
|
||||
}
|
||||
|
||||
@@ -4,8 +4,7 @@ use crate::config::ConfigFaceTarget;
|
||||
use crate::{DocError, Document, EmbossFace, FeatureKind, Model, ModelBody, ModelState, PlaneRef, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use solidcore_record::Plane;
|
||||
use solidcraft_geom::prelude::*;
|
||||
use solidcraft_geom::Plane;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
@@ -500,7 +499,7 @@ pub fn validate_face_anchors_json(value: &Value) -> Result<()> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::Vec3;
|
||||
fn fixture() -> (Document, ModelBody, usize, Plane) {
|
||||
let mut doc = Document::default();
|
||||
let kind: FeatureKind =
|
||||
@@ -627,7 +626,7 @@ mod tests {
|
||||
}
|
||||
#[test]
|
||||
fn extrude_cap_profile_reordering_never_retargets_an_ordinal_body() {
|
||||
use solidcore_record::Vec2;
|
||||
use solidcraft_geom::Vec2;
|
||||
let mut sketch = solidcraft_sketch::Sketch::new();
|
||||
for offset in [0., 30.] {
|
||||
let points: Vec<_> = [(0., 0.), (10., 0.), (10., 10.), (0., 10.)]
|
||||
@@ -720,7 +719,7 @@ mod tests {
|
||||
}
|
||||
#[test]
|
||||
fn warm_root_body_offset_edit_matches_cold_attached_sketch_and_downstream_bounds() {
|
||||
use solidcore_record::Vec2;
|
||||
use solidcraft_geom::Vec2;
|
||||
let (mut doc, _, _, _) = fixture();
|
||||
let mut warm = Model::new();
|
||||
warm.evaluate(&doc);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//! The walk (`crate::walk`) is behind `transform_geometry`, `face_points`, `profile_sels` and
|
||||
//! `point_expr`.
|
||||
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::Vec3;
|
||||
|
||||
use crate::walk::Walk;
|
||||
use crate::{FeatureKind, PointExpr, ProfileSel};
|
||||
|
||||
+15
-16
@@ -1246,7 +1246,6 @@ fn normalise(d: &mut Document) -> Result<()> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use solidcraft_geom::prelude::*;
|
||||
|
||||
/// A pending change that records that its step ran, in `trace`.
|
||||
fn traced(name: &'static str) -> Part {
|
||||
@@ -1677,7 +1676,7 @@ mod tests {
|
||||
#[test]
|
||||
fn version_twenty_three_designs_open_and_an_edge_flange_relief_is_saved_in_twenty_four() {
|
||||
let flange = |shape: Option<&str>, size: Option<&str>| crate::FeatureKind::SheetFlange {
|
||||
edges: vec![crate::FlangeEdge::at(solidcore_record::Vec3::ZERO)],
|
||||
edges: vec![crate::FlangeEdge::at(solidcraft_geom::Vec3::ZERO)],
|
||||
height: "10 mm".into(),
|
||||
angle: "90 deg".into(),
|
||||
radius: None,
|
||||
@@ -1783,7 +1782,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn version_nine_wire_spaces_preserve_saved_points_and_explicit_markers() {
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::Vec3;
|
||||
use solidcraft_sketch::{LinkKind, LinkSource, Sketch, WireSpace};
|
||||
let mut sketch = Sketch::new();
|
||||
sketch.add_drawn_wire(&[Vec3::new(12.0, 3.0, 4.0)]).unwrap();
|
||||
@@ -1897,9 +1896,9 @@ mod tests {
|
||||
through_all: false,
|
||||
taper: Some("0 deg".into()),
|
||||
corner: Default::default(),
|
||||
to: Some(crate::FacePick(solidcore_record::Vec3::new(0.0, 0.0, 10.0))),
|
||||
to: Some(crate::FacePick(solidcraft_geom::Vec3::new(0.0, 0.0, 10.0))),
|
||||
to_offset: Some("offset + 1 mm".into()),
|
||||
from: Some(crate::FacePick(solidcore_record::Vec3::new(0.0, 0.0, 5.0))),
|
||||
from: Some(crate::FacePick(solidcraft_geom::Vec3::new(0.0, 0.0, 5.0))),
|
||||
to_body_apart: false,
|
||||
},
|
||||
operation: Operation::NewBody,
|
||||
@@ -1945,7 +1944,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn saved_offset_cannot_alias_and_overwrite_its_source_geometry() {
|
||||
use solidcore_record::Vec2;
|
||||
use solidcraft_geom::Vec2;
|
||||
use solidcraft_sketch::{CurveKind, Sketch};
|
||||
let mut sketch = Sketch::new();
|
||||
let center = sketch.add_point(Vec2::ZERO, None).unwrap();
|
||||
@@ -2084,7 +2083,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn periodic_file_validation_refuses_damaged_closed_geometry() {
|
||||
use solidcore_record::Vec2;
|
||||
use solidcraft_geom::Vec2;
|
||||
use solidcraft_sketch::{CurveKind, Sketch};
|
||||
let mut sketch = Sketch::new();
|
||||
let mut pts = Vec::new();
|
||||
@@ -2234,7 +2233,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn version_seven_origin_sketch_keeps_geometry_dimension_ids_and_expressions() {
|
||||
use solidcore_record::Vec2;
|
||||
use solidcraft_geom::Vec2;
|
||||
use solidcraft_sketch::{ConstraintKind, Sketch};
|
||||
let mut d = Document::new("legacy origin dimensions");
|
||||
d.set_param("reach", "25 mm", Some("mm"), None).unwrap();
|
||||
@@ -2264,7 +2263,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn version_eight_persistent_angle_sweep_and_slot_dimensions_roundtrip() {
|
||||
use solidcore_record::Vec2;
|
||||
use solidcraft_geom::Vec2;
|
||||
use solidcraft_sketch::{ConstraintKind, Sketch};
|
||||
let mut d = Document::new("persistent typed dimensions");
|
||||
let heading = d.new_model_param("45 deg", "deg");
|
||||
@@ -2383,12 +2382,12 @@ mod tests {
|
||||
let (values, errors) = migrated.param_values();
|
||||
assert!(errors.is_empty());
|
||||
let p = migrated.resolve_plane(&values, plane, 0).unwrap();
|
||||
assert_eq!(p.y, solidcore_record::Vec3::Z);
|
||||
assert_eq!(p.normal(), -solidcore_record::Vec3::Y);
|
||||
assert_eq!(p.origin, solidcore_record::Vec3::new(0.0, -7.0, 0.0));
|
||||
assert_eq!(p.y, solidcraft_geom::Vec3::Z);
|
||||
assert_eq!(p.normal(), -solidcraft_geom::Vec3::Y);
|
||||
assert_eq!(p.origin, solidcraft_geom::Vec3::new(0.0, -7.0, 0.0));
|
||||
assert_eq!(read(&migrated.to_json()).unwrap(), migrated);
|
||||
let fresh = doc.resolve_plane(&values, &crate::PlaneRef::Origin { name: "XZ".into() }, 0).unwrap();
|
||||
assert_eq!(fresh.normal(), solidcore_record::Vec3::Y);
|
||||
assert_eq!(fresh.normal(), solidcraft_geom::Vec3::Y);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2436,7 +2435,7 @@ mod tests {
|
||||
let mut sketch = Sketch::new();
|
||||
let mut previous = 0;
|
||||
while sketch.points.len() < MAX_POINTS {
|
||||
let i = sketch.add_point(solidcore_record::Vec2::new(0.001 * sketch.points.len() as f64, 0.0), None).unwrap();
|
||||
let i = sketch.add_point(solidcraft_geom::Vec2::new(0.001 * sketch.points.len() as f64, 0.0), None).unwrap();
|
||||
sketch.add_constraint(solidcraft_sketch::ConstraintKind::Coincident { p: previous, q: i }, None).unwrap();
|
||||
previous = i;
|
||||
}
|
||||
@@ -2548,7 +2547,7 @@ mod tests {
|
||||
#[test]
|
||||
fn integer_valued_coordinates_are_range_checked_and_large_ids_still_load() {
|
||||
let mut sketch = solidcraft_sketch::Sketch::new();
|
||||
sketch.add_point(solidcore_record::Vec2::new(40.0, 0.0), None).unwrap();
|
||||
sketch.add_point(solidcraft_geom::Vec2::new(40.0, 0.0), None).unwrap();
|
||||
let mut d = Document::new("range");
|
||||
d.add_feature(crate::FeatureKind::Sketch { plane: crate::PlaneRef::Origin { name: "XY".into() }, sketch }, None).unwrap();
|
||||
let last = |v: &Value| v["features"][0]["sketch"]["points"].as_array().map_or(0, |a| a.len() - 1);
|
||||
@@ -2605,7 +2604,7 @@ mod tests {
|
||||
"orientation": {"direction": [0.0, 1.0, 0.0]}, "twist": "90 deg", "scale": "0.5"});
|
||||
let k: FeatureKind = serde_json::from_value(new.clone()).unwrap();
|
||||
let FeatureKind::SurfaceSweep { orientation, .. } = &k else { panic!("{k:?}") };
|
||||
assert_eq!(*orientation, SweepOrientation::Direction(crate::Dir(solidcore_record::Vec3::Y)));
|
||||
assert_eq!(*orientation, SweepOrientation::Direction(crate::Dir(solidcraft_geom::Vec3::Y)));
|
||||
assert_eq!(serde_json::to_value(&k).unwrap(), new);
|
||||
let parallel: SweepOrientation = serde_json::from_value(serde_json::json!("parallel")).unwrap();
|
||||
assert_eq!(parallel, SweepOrientation::Parallel);
|
||||
|
||||
@@ -411,9 +411,9 @@ mod tests {
|
||||
#[test]
|
||||
fn formats_one_through_eleven_keep_legacy_offset_geometry_and_ids() {
|
||||
let mut sketch = solidcraft_sketch::Sketch::new();
|
||||
let center = sketch.add_point(solidcore_record::Vec2::new(4., 7.), Some("center")).unwrap();
|
||||
sketch.add_circle(solidcore_record::Vec2::new(4., 7.), 10., Some(center), Some("source")).unwrap();
|
||||
sketch.add_circle(solidcore_record::Vec2::new(4., 7.), 9., Some(center), Some("offset")).unwrap();
|
||||
let center = sketch.add_point(solidcraft_geom::Vec2::new(4., 7.), Some("center")).unwrap();
|
||||
sketch.add_circle(solidcraft_geom::Vec2::new(4., 7.), 10., Some(center), Some("source")).unwrap();
|
||||
sketch.add_circle(solidcraft_geom::Vec2::new(4., 7.), 9., Some(center), Some("offset")).unwrap();
|
||||
sketch.offsets.push(solidcraft_sketch::OffsetChain {
|
||||
sources: vec![("source".into(), true)],
|
||||
outputs: vec!["offset".into()],
|
||||
@@ -447,7 +447,7 @@ mod tests {
|
||||
fn missing_boundary_or_defining_sketch_reopens_as_a_model_error_without_retarget() {
|
||||
for missing_sketch in [false, true] {
|
||||
let mut sketch = solidcraft_sketch::Sketch::new();
|
||||
sketch.add_circle(solidcore_record::Vec2::new(0., 0.), 10., None, Some("unrelated-circle")).unwrap();
|
||||
sketch.add_circle(solidcraft_geom::Vec2::new(0., 0.), 10., None, Some("unrelated-circle")).unwrap();
|
||||
let mut document = Document::new("repairable lost boundary");
|
||||
let id = document.add_feature(FeatureKind::Sketch { plane: crate::PlaneRef::Origin { name: "XY".into() }, sketch }, None).unwrap();
|
||||
document
|
||||
|
||||
@@ -13,11 +13,10 @@
|
||||
//! of the profile's bounding box at the origin. The outline runs counter-clockwise and holes
|
||||
//! clockwise, as [`Region2`] expects.
|
||||
|
||||
use solidcraft_geom::prelude::*;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solidcore_record::{Loop2, Region2, Seg2, Vec2, Vec3};
|
||||
use solidcraft_geom::{Loop2, Region2, Seg2, Vec2, Vec3};
|
||||
|
||||
use crate::expr::Kind;
|
||||
use crate::{DocError, Expr, Result};
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::Vec3;
|
||||
use solidcraft_kernel::history::{FaceOrigin, FaceProv, History};
|
||||
|
||||
use crate::ModelBody;
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
//! measured in the face's plane. The edges are found again by their persistent names, else by
|
||||
//! their stored points, so the hole follows them when the part changes.
|
||||
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::Mesh;
|
||||
use solidcraft_geom::prelude::*;
|
||||
use solidcraft_geom::{Mesh, Vec3};
|
||||
|
||||
use crate::{DocError, ModelBody, ModelState, Result};
|
||||
|
||||
|
||||
@@ -14,12 +14,10 @@
|
||||
//! linkage, a slider-crank) are met by moving the values of the other joints around the loop;
|
||||
//! those that still disagree are reported.
|
||||
|
||||
use solidcraft_geom::prelude::*;
|
||||
use std::collections::{BTreeMap, BTreeSet, VecDeque};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::Aabb3;
|
||||
use solidcraft_geom::{Aabb3, Vec3};
|
||||
|
||||
use solidcraft_sketch::linalg;
|
||||
|
||||
|
||||
@@ -12,12 +12,10 @@
|
||||
//! oscillating one from its arm pointing away from the cam's axis) and stops where it first
|
||||
//! touches the profile: the follower stays in contact, as a spring would hold it.
|
||||
|
||||
use solidcraft_geom::prelude::*;
|
||||
use std::f64::consts::TAU;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solidcore_record::{Vec2, Vec3};
|
||||
use solidcraft_geom::{Aabb2, Aabb3, Ring};
|
||||
use solidcraft_geom::{Aabb2, Aabb3, Ring, Vec2, Vec3};
|
||||
|
||||
use super::{JointKind, JointOrigin, MotionLink, Size, component_bodies, component_of, flip_of, occurrence_world, size_of};
|
||||
use crate::{DocError, Document, IDENTITY, Mat, ModelState, Result, apply_point, apply_vector, mat_inverse, mat_mul};
|
||||
@@ -488,7 +486,7 @@ pub fn sketch_profile<'a>(sk: &'a crate::SolvedSketch, curves: &[String]) -> Opt
|
||||
}
|
||||
|
||||
/// A loop's polyline within `tol`, segment by segment: `None` past `MAX_PROFILE_POINTS` points.
|
||||
fn sampled(lp: &solidcore_record::Loop2, tol: f64) -> Option<Vec<Vec2>> {
|
||||
fn sampled(lp: &solidcraft_geom::Loop2, tol: f64) -> Option<Vec<Vec2>> {
|
||||
let mut out = Vec::new();
|
||||
for seg in &lp.segs {
|
||||
let p = seg.polyline(tol);
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
//! corner x and y run along the two edges, away from the corner into the face, in the order that
|
||||
//! makes the frame right-handed.
|
||||
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::prelude::*;
|
||||
use solidcraft_geom::Vec3;
|
||||
|
||||
use super::{EdgeAxis, Snap, component_bodies, frame};
|
||||
use crate::{DocError, Document, IDENTITY, Mat, ModelBody, ModelState, Result, apply_point, mat_inverse, mat_mul};
|
||||
|
||||
@@ -4,11 +4,9 @@
|
||||
//! A round origin's end is kept by the name of the face across the rim at that end, so it is the
|
||||
//! same end after the design changes; an x reference keeps the origin's turn about its axis.
|
||||
|
||||
use solidcraft_geom::prelude::*;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use solidcore_record::{Loop2, Plane, Vec2, Vec3};
|
||||
use solidcraft_geom::{Mesh, Ring};
|
||||
use solidcraft_geom::{Loop2, Mesh, Plane, Ring, Vec2, Vec3};
|
||||
use solidcraft_kernel::CylinderFace;
|
||||
|
||||
use super::{Snap, XRef, component_bodies};
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
//! The instantaneous motion a joint value gives a selected occurrence. Probe the same
|
||||
//! placement graph and loop solver as a drive, including reversed joints and motion links.
|
||||
use solidcraft_geom::prelude::*;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use super::{Analysis, Held, MAX_LOOP_VALUES, Solution, apply_links, build, joint_values, link_chains, solve_holding_with_analysis};
|
||||
use crate::{Document, Mat, ModelState, apply_point, apply_vector, mat_inverse, mat_mul};
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::Vec3;
|
||||
|
||||
/// A driveable value, expressed as an axis of the selected occurrence in world coordinates.
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
//! point of it reaches a target (and an axis of it points a given way), every value within its
|
||||
//! limits, motion links followed, and the closed loops those values are in kept closed.
|
||||
|
||||
use solidcraft_geom::prelude::*;
|
||||
use std::cell::Cell;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::Vec3;
|
||||
use solidcraft_sketch::linalg;
|
||||
|
||||
use super::{
|
||||
|
||||
@@ -76,7 +76,7 @@ mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
use super::*;
|
||||
use crate::joints::{JointKind, JointOrigin, Snap};
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::Vec3;
|
||||
|
||||
fn joint(kind: JointKind) -> Joint {
|
||||
let at = |occurrence| JointOrigin { occurrence, snap: Snap::Point { point: Vec3::ZERO, z: None, x: None } };
|
||||
|
||||
@@ -9,10 +9,9 @@
|
||||
//! bodies the feature built itself (whose faces are named by their role: the sketch curve they
|
||||
//! sweep, the cap they are). Naming (`history_names`) reads it; nothing here samples geometry.
|
||||
|
||||
use solidcraft_geom::prelude::*;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
use solidcore_record::{Region2, Vec3};
|
||||
use solidcraft_geom::{Region2, Vec3};
|
||||
use solidcraft_kernel::Body;
|
||||
use solidcraft_kernel::history::{BodyHistory, Change, FaceOrigin, FaceProv, History};
|
||||
|
||||
@@ -233,7 +232,7 @@ impl<'a> ProfileRoles<'a> {
|
||||
if history.operands != 0 {
|
||||
return None;
|
||||
}
|
||||
let segments: Vec<&solidcore_record::Seg2> =
|
||||
let segments: Vec<&solidcraft_geom::Seg2> =
|
||||
self.region.map(|r| std::iter::once(&r.outer).chain(&r.holes).flat_map(|l| &l.segs).collect()).unwrap_or_default();
|
||||
let curves = self.sketch.map(crate::config::ExactSketchCurves::new);
|
||||
// Every curve the segment is drawn on (one, or several coincident or collinear ones).
|
||||
@@ -295,7 +294,7 @@ impl<'a> ProfileRoles<'a> {
|
||||
/// numbered through the chains in order. It has no caps.
|
||||
pub(crate) struct ChainRoles<'a> {
|
||||
pub sketch: &'a SolvedSketch,
|
||||
pub chains: &'a [Vec<solidcore_record::Seg2>],
|
||||
pub chains: &'a [Vec<solidcraft_geom::Seg2>],
|
||||
}
|
||||
|
||||
impl ChainRoles<'_> {
|
||||
@@ -306,7 +305,7 @@ impl ChainRoles<'_> {
|
||||
}
|
||||
|
||||
fn of(&self, history: &History) -> Vec<Vec<String>> {
|
||||
let segments: Vec<&solidcore_record::Seg2> = self.chains.iter().flatten().collect();
|
||||
let segments: Vec<&solidcraft_geom::Seg2> = self.chains.iter().flatten().collect();
|
||||
let curves = crate::config::ExactSketchCurves::new(self.sketch);
|
||||
history
|
||||
.faces
|
||||
@@ -337,7 +336,7 @@ impl ChainRoles<'_> {
|
||||
/// answering to the others; its caps `start` and `end`.
|
||||
pub(crate) struct SectionRoles<'a> {
|
||||
/// Each section's sketch and loops (outer, then holes), in the order given.
|
||||
pub sections: Vec<(&'a SolvedSketch, Vec<&'a solidcore_record::Loop2>)>,
|
||||
pub sections: Vec<(&'a SolvedSketch, Vec<&'a solidcraft_geom::Loop2>)>,
|
||||
}
|
||||
|
||||
impl SectionRoles<'_> {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! shown without changing the design).
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::Vec3;
|
||||
|
||||
/// The motion retained by an associative trace. Joint bounds keep their expressions, in
|
||||
/// command units (degrees or millimetres); studies are referenced by their stable identity.
|
||||
|
||||
@@ -10,9 +10,7 @@
|
||||
//! ([`crate::DocError::Reselect`]). A face whose name was worked out from geometry, found where
|
||||
//! the reference's point no longer lies, is reported: only geometry decided.
|
||||
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::Mesh;
|
||||
use solidcraft_geom::prelude::*;
|
||||
use solidcraft_geom::{Mesh, Vec3};
|
||||
|
||||
use crate::history_names::{Match, Names};
|
||||
use crate::naming::{self, closest, edge_names, face_names, strip_piece};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::{Document, ModelBody, ModelState};
|
||||
use solidcore_record::{Plane, Vec3};
|
||||
use solidcraft_geom::prelude::*;
|
||||
use solidcraft_geom::{Plane, Vec3};
|
||||
use solidcraft_sketch::{LinkKind, LinkSource, Sketch};
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -15,11 +15,9 @@
|
||||
//! are looked up on the input's faces. Those names are marked unverified, and references that
|
||||
//! resolve through them say so. Names are worked out lazily, on first request.
|
||||
|
||||
use solidcraft_geom::prelude::*;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
use solidcore_record::{Vec2, Vec3};
|
||||
use solidcraft_geom::{Aabb3, Mesh};
|
||||
use solidcraft_geom::{Aabb3, Mesh, Vec2, Vec3};
|
||||
|
||||
use crate::history_names::Names;
|
||||
use crate::lineage::Lineage;
|
||||
@@ -1448,7 +1446,7 @@ mod tests {
|
||||
let ss = crate::SolvedSketch {
|
||||
feature: 1,
|
||||
name: "S".into(),
|
||||
plane: solidcore_record::Plane::XY,
|
||||
plane: solidcraft_geom::Plane::XY,
|
||||
sketch,
|
||||
report,
|
||||
profiles,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Persistable opaque optical parameters, independent of engineering material data.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::Vec3;
|
||||
use solidcraft_render::surface::{OpaqueSurface, SurfaceError, srgb8_to_linear};
|
||||
|
||||
/// Explicitly selected opaque response. Existing scalar looks have no such record.
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
//! Reference triangles belong to a definition, even while its occurrences move. Keep one lazy
|
||||
//! source mesh and map it into each pose; retain the exact placed B-rep separately in ModelBody.
|
||||
use solidcraft_geom::prelude::*;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::Mesh;
|
||||
use solidcraft_geom::tess::TessPlan;
|
||||
use solidcraft_geom::{Mesh, Vec3};
|
||||
use solidcraft_kernel::{self as kernel, Body};
|
||||
|
||||
use super::ModelBody;
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
//! cloud's own `origin` (double precision), so a scan far from the design's origin keeps its
|
||||
//! detail.
|
||||
|
||||
use solidcraft_geom::prelude::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::Vec3;
|
||||
|
||||
use crate::canvas::{base64_decode, base64_encode};
|
||||
pub use crate::point_cloud_complex::{Column, Complex, Filter, Floats, Keep, MAX_COLUMNS, MAX_NAMES, Names};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::Vec3;
|
||||
|
||||
use super::*;
|
||||
use crate::IDENTITY;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::Vec3;
|
||||
|
||||
macro_rules! point_type {
|
||||
($(#[$doc:meta])* $name:ident) => {
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
//! sketch on a face holds ([`Document::flat_face_target`]), so a rebuild that adds, removes or
|
||||
//! reorders faces finds the same face again, or says it is gone.
|
||||
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::prelude::*;
|
||||
use solidcraft_geom::Vec3;
|
||||
|
||||
use crate::config::ConfigFaceTarget;
|
||||
use crate::{DocError, Document, ModelBody, ModelState, Result};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//! Exact profile references through analytic offset topology transitions.
|
||||
use solidcraft_geom::prelude::*;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use crate::{DocError, ProfileSel, Result, SolvedSketch, WholeProfileBoundary};
|
||||
@@ -365,7 +364,7 @@ mod tests {
|
||||
#![allow(clippy::unwrap_used, clippy::panic)]
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
use solidcore_record::{Plane, Vec2};
|
||||
use solidcraft_geom::{Plane, Vec2};
|
||||
use solidcraft_sketch::{Sketch, find_profiles, solve};
|
||||
|
||||
fn rounded_source(sketch: &mut Sketch, radius: f64) -> Vec<(String, bool)> {
|
||||
|
||||
@@ -293,7 +293,7 @@ mod tests {
|
||||
fn partial_models_go_only_to_a_watcher_that_wants_them() {
|
||||
let mut doc = crate::Document::new("partial");
|
||||
let block = crate::FeatureKind::Box {
|
||||
corner: crate::point_expr(solidcore_record::Vec3::ZERO),
|
||||
corner: crate::point_expr(solidcraft_geom::Vec3::ZERO),
|
||||
length: "2 mm".into(),
|
||||
width: "3 mm".into(),
|
||||
height: "4 mm".into(),
|
||||
|
||||
@@ -10,11 +10,9 @@
|
||||
//! lines and circular arcs are recovered exactly, the rest (an ellipse from a tilted circle,
|
||||
//! a spline edge running out of the plane) becomes a chain of lines.
|
||||
|
||||
use solidcraft_geom::prelude::*;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use solidcore_record::{Plane, Seg2, Vec2, Vec3};
|
||||
use solidcraft_geom::Mesh;
|
||||
use solidcraft_geom::{Mesh, Plane, Seg2, Vec2, Vec3};
|
||||
use solidcraft_sketch::{CurveKind, LinkGeom, LinkKind, LinkSource, Sketch, WireCurve};
|
||||
|
||||
use crate::eval::ModelState;
|
||||
@@ -365,7 +363,7 @@ fn edge_curves(body: Option<&crate::ModelBody>, e: usize) -> Vec<WireCurve> {
|
||||
record
|
||||
.edges
|
||||
.get(e)
|
||||
.filter(|edge| !matches!(edge.curve, solidcore_record::brep::Curve::SurfaceIntersection { .. }))
|
||||
.filter(|edge| !matches!(edge.curve, solidcraft_geom::brep::Curve::SurfaceIntersection { .. }))
|
||||
.map(|edge| vec![WireCurve { curve: edge.parameters().clone(), interval: edge.interval }])
|
||||
.unwrap_or_default()
|
||||
}
|
||||
@@ -396,9 +394,9 @@ fn projected_wires(
|
||||
/// in front of it.
|
||||
fn first_hits(
|
||||
target: &crate::ModelBody,
|
||||
curves: Vec<(solidcore_record::brep::Curve, [f64; 2])>,
|
||||
curves: Vec<(solidcraft_geom::brep::Curve, [f64; 2])>,
|
||||
toward: Vec3,
|
||||
) -> Result<Vec<(solidcore_record::brep::Curve, [f64; 2])>> {
|
||||
) -> Result<Vec<(solidcraft_geom::brep::Curve, [f64; 2])>> {
|
||||
let Some(dir) = toward.normalized() else { return Ok(curves) };
|
||||
let mesh = target.mesh();
|
||||
let reach = 2.0 * mesh.bounds().diagonal().max(1e-9);
|
||||
@@ -420,7 +418,7 @@ fn first_hits(
|
||||
/// direction they come (a projection split at a sphere's pole into two pieces that both end there
|
||||
/// joins again there, the second run the other way), each wire its points (for display) and its
|
||||
/// exact curves.
|
||||
fn joined_wires(curves: Vec<(solidcore_record::brep::Curve, [f64; 2])>, tol: f64) -> Result<(Vec<Vec<Vec3>>, Vec<Vec<WireCurve>>)> {
|
||||
fn joined_wires(curves: Vec<(solidcraft_geom::brep::Curve, [f64; 2])>, tol: f64) -> Result<(Vec<Vec<Vec3>>, Vec<Vec<WireCurve>>)> {
|
||||
let (mut wires, mut exact): (Vec<Vec<Vec3>>, Vec<Vec<WireCurve>>) = (Vec::new(), Vec::new());
|
||||
for (curve, interval) in curves {
|
||||
let pts = crate::kernel::PathPiece::Segs(crate::kernel::curve_path(&curve, interval)?).points();
|
||||
@@ -468,7 +466,7 @@ fn joined_wires(curves: Vec<(solidcore_record::brep::Curve, [f64; 2])>, tol: f64
|
||||
/// own knots (a line's `1 - t`, a circle's or ellipse's angle negated with its y axis turned).
|
||||
/// Exact; `None` for a curve kind it doesn't turn.
|
||||
fn reversed(c: &WireCurve) -> Option<WireCurve> {
|
||||
use solidcore_record::brep::{Curve, Frame};
|
||||
use solidcraft_geom::brep::{Curve, Frame};
|
||||
let [t0, t1] = c.interval;
|
||||
let flip = |frame: &Frame| Frame { origin: frame.origin, x_axis: frame.x_axis, y_axis: frame.y_axis.map(|v| -v) };
|
||||
let (curve, interval) = match &c.curve {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
//! 3D sketch curves from the model: curves projected onto faces (on their display meshes), the
|
||||
//! bodies and faces an intersection curve is taken on, and spun profiles.
|
||||
|
||||
use solidcore_record::{Plane, Vec2, Vec3};
|
||||
use solidcraft_geom::Mesh;
|
||||
use solidcraft_geom::prelude::*;
|
||||
use solidcraft_geom::{Mesh, Plane, Vec2, Vec3};
|
||||
use solidcraft_sketch::LinkSource;
|
||||
|
||||
use crate::eval::ModelState;
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
//! Stable face projection order: outer boundary first, then holes by geometry.
|
||||
//! B-rep edge indices depend on face enumeration and must not choose sketch curve IDs.
|
||||
|
||||
use solidcraft_geom::prelude::*;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use solidcore_record::{Plane, Vec3};
|
||||
use solidcraft_geom::{Aabb3, Mesh};
|
||||
use solidcraft_geom::{Aabb3, Mesh, Plane, Vec3};
|
||||
|
||||
use crate::{DocError, Result};
|
||||
|
||||
@@ -230,7 +228,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn coarsely_tessellated_round_silhouette_fits_an_arc() {
|
||||
use solidcore_record::Vec2;
|
||||
use solidcraft_geom::Vec2;
|
||||
use solidcraft_sketch::LinkGeom;
|
||||
let on = |deg: f64| Vec2::new(5. + 5. * deg.to_radians().cos(), 5. + 5. * deg.to_radians().sin());
|
||||
let mut chain = vec![Vec2::new(0., -20.)];
|
||||
@@ -243,7 +241,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn tangent_snapping_leaves_unrelated_collinear_lines_alone() {
|
||||
use solidcore_record::Vec2;
|
||||
use solidcraft_geom::Vec2;
|
||||
use solidcraft_sketch::LinkGeom;
|
||||
let v = Vec2::new;
|
||||
// A rounded corner of radius 2 meeting the top edge, and an unrelated collinear edge.
|
||||
@@ -260,7 +258,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn tangent_snapping_leaves_a_line_further_along_alone() {
|
||||
use solidcore_record::Vec2;
|
||||
use solidcraft_geom::Vec2;
|
||||
use solidcraft_sketch::LinkGeom;
|
||||
let v = Vec2::new;
|
||||
let y = |x: f64| 2.0 - (4.0 - x * x).sqrt();
|
||||
@@ -281,7 +279,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn tangent_snapping_gives_one_line_to_one_end_of_an_arc() {
|
||||
use solidcore_record::Vec2;
|
||||
use solidcraft_geom::Vec2;
|
||||
use solidcraft_sketch::LinkGeom;
|
||||
let v = Vec2::new;
|
||||
let y = |x: f64| 2.0 - (4.0 - x * x).sqrt();
|
||||
@@ -295,7 +293,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn tangent_snapping_moves_the_other_end_of_an_arc_to_its_next_line() {
|
||||
use solidcore_record::Vec2;
|
||||
use solidcraft_geom::Vec2;
|
||||
use solidcraft_sketch::LinkGeom;
|
||||
let v = Vec2::new;
|
||||
let y = |x: f64| 2.0 - (4.0 - x * x).sqrt();
|
||||
@@ -314,7 +312,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn tangent_snapping_closes_a_conic_corner_in_any_order() {
|
||||
use solidcore_record::Vec2;
|
||||
use solidcraft_geom::Vec2;
|
||||
use solidcraft_sketch::LinkGeom;
|
||||
// A square of side 150 with a conic round of 10 mm legs at each corner, fitted with lines
|
||||
// and arcs the way a silhouette is, then snapped with the pieces in each order.
|
||||
@@ -412,7 +410,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn family_line_normalization_preserves_corners_arcs_branches_and_retracing() {
|
||||
use solidcore_record::Vec2;
|
||||
use solidcraft_geom::Vec2;
|
||||
use solidcraft_sketch::LinkGeom;
|
||||
let line = |a, b| LinkGeom::Line(Vec2::new(a, 0.), Vec2::new(b, 0.));
|
||||
assert_eq!(super::super::merge_family_lines(vec![line(0., 1.), line(2., 1.)]).unwrap(), vec![line(0., 2.)]);
|
||||
@@ -500,7 +498,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn rounded_boundary_arc_sweeps_survive_individual_edge_reversal() {
|
||||
use solidcore_record::{Seg2, Vec2};
|
||||
use solidcraft_geom::{Seg2, Vec2};
|
||||
use solidcraft_sketch::LinkGeom;
|
||||
let mut mesh = Mesh::default();
|
||||
for (extent, radius) in [(10.0, 3.0), (8.0, 1.0)] {
|
||||
@@ -595,7 +593,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn curve_ends_a_hair_apart_become_one_point_and_distant_ones_stay() {
|
||||
use solidcore_record::Vec2;
|
||||
use solidcraft_geom::Vec2;
|
||||
use solidcraft_sketch::LinkGeom;
|
||||
let mut geom = vec![
|
||||
LinkGeom::Line(Vec2::new(0., 0.), Vec2::new(10., 0.)),
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::{
|
||||
render_surface::{RenderBodyTarget, bounded_sequence},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::Vec3;
|
||||
use solidcraft_render::{
|
||||
surface::OpaqueSurface,
|
||||
transmission::{Transmission, subsurface::Subsurface},
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::Vec3;
|
||||
use solidcraft_render::{BackendId, Camera, RenderFormat, RenderLimits, RenderMode, UpAxis};
|
||||
|
||||
pub use crate::render_job::{CpuJob, GpuJob, RenderJob, SkyEnvironment};
|
||||
|
||||
@@ -10,9 +10,7 @@
|
||||
//! together; the flat solid is the outline extruded through the thickness.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solidcore_record::{Loop2, Plane, Region2, Seg2, Vec2, Vec3};
|
||||
use solidcraft_geom::prelude::*;
|
||||
use solidcraft_geom::{Aabb2, LINEAR_TOL, Ring};
|
||||
use solidcraft_geom::{Aabb2, LINEAR_TOL, Loop2, Plane, Region2, Ring, Seg2, Vec2, Vec3};
|
||||
use solidcraft_kernel::{self as kernel, Body, EdgeSpec, FaceSpec, SurfSpec};
|
||||
|
||||
use crate::expr::{self, Kind, Value};
|
||||
|
||||
@@ -47,12 +47,10 @@
|
||||
//! edges that end at a bend or a cut edge, loops of bends, two faces that meet along more than
|
||||
//! one edge, a wall on part of an edge of a face other than the fixed one.
|
||||
|
||||
use solidcraft_geom::prelude::*;
|
||||
use std::collections::BTreeMap;
|
||||
use std::f64::consts::{FRAC_PI_2, PI, TAU};
|
||||
|
||||
use solidcore_record::{Loop2, Plane, Region2, Seg2, Vec2, Vec3};
|
||||
use solidcraft_geom::{Mesh, Ring, UnitVec3};
|
||||
use solidcraft_geom::{Loop2, Mesh, Plane, Region2, Ring, Seg2, UnitVec3, Vec2, Vec3};
|
||||
use solidcraft_kernel::{Body, FaceSurface, face_surface};
|
||||
|
||||
use super::{FlatWork, ReliefOverride, RuleValues, SheetBody, SheetFlange, base_flange, polys_cross, signed_area, snap};
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
//! neighbouring flange), the rim along the bend there, `<flange>:rim.ext0` and `rim.ext1`. A panel is `base` or a flange's tag (`F<feature>.<k>`), so the names
|
||||
//! say what each face is and stay with it when sizes change or other flanges come and go.
|
||||
|
||||
use solidcore_record::{Plane, Vec2, Vec3};
|
||||
use solidcraft_geom::prelude::*;
|
||||
use solidcraft_geom::{Plane, Vec2, Vec3};
|
||||
|
||||
use super::SheetBody;
|
||||
use crate::{ModelBody, ModelState, apply_point, mat_inverse};
|
||||
|
||||
@@ -5,10 +5,9 @@
|
||||
//! between the points, up to the most a spline takes. When the parameters change, the same
|
||||
//! number of points is placed again (`Document::apply_equation_values`), before every solve.
|
||||
|
||||
use solidcraft_geom::prelude::*;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use solidcore_record::Vec2;
|
||||
use solidcraft_geom::Vec2;
|
||||
use solidcraft_sketch::{EquationCurve, MAX_COORD, MAX_EQUATION_TEXT, MAX_SPLINE_POINTS, Sketch, VariableUnit, fit_beziers_mode};
|
||||
|
||||
use crate::expr::{self, Defaults, Kind, Value};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use solidcore_record::{Vec2, Vec3};
|
||||
use solidcraft_geom::{Vec2, Vec3};
|
||||
use solidcraft_kernel::measure;
|
||||
use solidcraft_sketch::{ConstraintKind, Sketch};
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
//! traits here carry the walks through the types the fields have: options, lists, arrays, planes
|
||||
//! and construction definitions.
|
||||
|
||||
pub(crate) use solidcore_record::Vec3;
|
||||
pub(crate) use solidcraft_geom::Vec3;
|
||||
|
||||
pub(crate) use crate::ProfileSel;
|
||||
use crate::construct::{AxisDef, GeoRef, PointDef};
|
||||
|
||||
@@ -35,7 +35,6 @@ name = "sketch_target_bench"
|
||||
required-features = ["solver-profiling"]
|
||||
|
||||
[dependencies]
|
||||
solidcore-record = { workspace = true }
|
||||
solidcraft-geom = { workspace = true }
|
||||
solidcraft-sketch = { workspace = true }
|
||||
solidcraft-kernel = { workspace = true }
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
use std::{error::Error, io::Write, path::PathBuf, sync::Arc, time::Instant};
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solidcore_record::Vec2;
|
||||
use solidcraft_engine::Session;
|
||||
use solidcraft_geom::Vec2;
|
||||
use solidcraft_sketch::{ConstraintKind, Sketch, profiling::capture};
|
||||
|
||||
type Result<T> = std::result::Result<T, Box<dyn Error>>;
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
use std::{error::Error, io::Write, path::PathBuf, sync::Arc, time::Instant};
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solidcore_record::Vec2;
|
||||
use solidcraft_engine::Session;
|
||||
use solidcraft_geom::Vec2;
|
||||
use solidcraft_sketch::{Analysis, ConstraintKind, Hold, Sketch, profiling::capture, solve_with};
|
||||
|
||||
type Result<T> = std::result::Result<T, Box<dyn Error>>;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
mod four_bar;
|
||||
use four_bar::Result;
|
||||
use serde_json::json;
|
||||
use solidcore_record::Vec2;
|
||||
use solidcraft_geom::Vec2;
|
||||
use solidcraft_sketch::profiling::capture;
|
||||
use std::{io::Write, time::Instant};
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
//! `examples/regressions/` (each script's description says what went wrong).
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solidcraft_geom::prelude::*;
|
||||
|
||||
use crate::Session;
|
||||
|
||||
@@ -229,10 +228,10 @@ fn shallow_cuts_rebuild_after_inserting_an_earlier_pocket() {
|
||||
let actual = volume(&session, "Plate");
|
||||
assert!((actual - expected).abs() < 0.02, "{actual} vs {expected}");
|
||||
let mesh = body.mesh(&solidcraft_kernel::MeshTolerance::chord(0.001)).unwrap();
|
||||
assert!(!mesh.contains(solidcore_record::Vec3::new(12.0, 14.0, 9.0)));
|
||||
assert!(mesh.contains(solidcore_record::Vec3::new(12.0, 14.0, 8.0)));
|
||||
assert!(!mesh.contains(solidcore_record::Vec3::new(20.0, 20.0, 9.5)));
|
||||
assert!(mesh.contains(solidcore_record::Vec3::new(20.0, 20.0, 9.0)));
|
||||
assert!(!mesh.contains(solidcraft_geom::Vec3::new(12.0, 14.0, 9.0)));
|
||||
assert!(mesh.contains(solidcraft_geom::Vec3::new(12.0, 14.0, 8.0)));
|
||||
assert!(!mesh.contains(solidcraft_geom::Vec3::new(20.0, 20.0, 9.5)));
|
||||
assert!(mesh.contains(solidcraft_geom::Vec3::new(20.0, 20.0, 9.0)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,7 +256,7 @@ fn eighteen_tabs_join_the_ribs_of_a_chamfered_base() {
|
||||
println!("#2059 volume {got:.6} / {expected:.6} mm³; one valid shell, eighteen tabs");
|
||||
for k in 0..6 {
|
||||
let angle = k as f64 * std::f64::consts::TAU / 6.0;
|
||||
let p = |x: f64, y: f64, z| solidcore_record::Vec3::new(x * angle.cos() - y * angle.sin(), x * angle.sin() + y * angle.cos(), z);
|
||||
let p = |x: f64, y: f64, z| solidcraft_geom::Vec3::new(x * angle.cos() - y * angle.sin(), x * angle.sin() + y * angle.cos(), z);
|
||||
for x in [15.5, 25.5, 35.5] {
|
||||
assert!(mesh.contains(p(x, 0.0, 12.0)), "tab {k} at {x}");
|
||||
assert!(!mesh.contains(p(x, 1.2, 12.0)), "tab {k} grew sideways at {x}");
|
||||
@@ -800,7 +799,7 @@ fn closed_sweep_redefines_to_a_half_turn_torus_cut() {
|
||||
let mesh = body.mesh(&solidcraft_kernel::MeshTolerance::chord(0.02)).unwrap();
|
||||
let expected = (std::f64::consts::PI * 64.0 - 64.0) * std::f64::consts::TAU * 30.0;
|
||||
assert!((mesh.measure().volume - expected).abs() < expected * 0.015);
|
||||
for (point, material) in [(solidcore_record::Vec3::new(30.0, 0.0, 0.0), false), (solidcore_record::Vec3::new(30.0, 0.0, 7.0), true)] {
|
||||
for (point, material) in [(solidcraft_geom::Vec3::new(30.0, 0.0, 0.0), false), (solidcraft_geom::Vec3::new(30.0, 0.0, 7.0), true)] {
|
||||
assert_eq!(mesh.contains(point), material, "material at {point:?}");
|
||||
}
|
||||
}
|
||||
@@ -832,7 +831,7 @@ fn many_extrusion_profiles_preserve_material_and_body_identity() {
|
||||
assert!((volume(&s, "Stock") - expected).abs() < 1e-6);
|
||||
let stock = state.body("Stock").unwrap();
|
||||
for (point, present) in [([3.0, 3.0, 22.0], operation == "join"), ([3.0, 3.0, 18.0], operation != "cut"), ([7.0, 3.0, 18.0], true)] {
|
||||
assert_eq!(stock.mesh().contains(solidcore_record::Vec3::from(point)), present, "{operation}: {point:?}");
|
||||
assert_eq!(stock.mesh().contains(solidcraft_geom::Vec3::from(point)), present, "{operation}: {point:?}");
|
||||
}
|
||||
assert!(stock.body.validity().is_empty());
|
||||
assert!(s.revision > before);
|
||||
@@ -865,10 +864,10 @@ fn many_circular_extrusion_profiles_cut_a_curved_target() {
|
||||
let stock = state.body("Stock").unwrap();
|
||||
assert!(stock.body.validity().is_empty());
|
||||
let mesh = stock.mesh();
|
||||
assert!(mesh.contains(solidcore_record::Vec3::new(0.0, 0.0, 10.0)));
|
||||
assert!(mesh.contains(solidcraft_geom::Vec3::new(0.0, 0.0, 10.0)));
|
||||
for x in [-9.0, -3.0, 3.0, 9.0] {
|
||||
for y in [-9.0, -3.0, 3.0, 9.0] {
|
||||
assert!(!mesh.contains(solidcore_record::Vec3::new(x, y, 10.0)), "hole at {x}, {y}");
|
||||
assert!(!mesh.contains(solidcraft_geom::Vec3::new(x, y, 10.0)), "hole at {x}, {y}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -928,7 +927,7 @@ fn unsupported_face_groups_are_atomic() {
|
||||
|
||||
#[test]
|
||||
fn imported_rotated_face_extrusions_preserve_other_operations_and_bodies() {
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::Vec3;
|
||||
let mut source = Session::default();
|
||||
source.execute("solid.box", &json!({"corner":[-16,-16,-3],"length":32,"width":32,"height":1,"body_name":"Other"})).unwrap();
|
||||
source.execute("solid.cylinder", &json!({"radius":14,"height":8,"body_name":"Stock"})).unwrap();
|
||||
|
||||
@@ -2166,7 +2166,7 @@ mod tests {
|
||||
assert!(s.camera.is_none());
|
||||
let r = run(&mut s, "view.apply", json!({"view": "Photo"}));
|
||||
let c = s.camera.expect("a camera for the view");
|
||||
assert_eq!((c.target, c.yaw, c.pitch, c.distance), (solidcore_record::Vec3::new(1.0, 2.0, 3.0), 0.5, -0.6, 150.0));
|
||||
assert_eq!((c.target, c.yaw, c.pitch, c.distance), (solidcraft_geom::Vec3::new(1.0, 2.0, 3.0), 0.5, -0.6, 150.0));
|
||||
assert_eq!(r["camera"]["yaw"], 0.5);
|
||||
// A standard view looks from its direction and frames the model.
|
||||
run(&mut s, "view.apply", json!({"view": "top"}));
|
||||
|
||||
@@ -10,9 +10,8 @@
|
||||
//! size), and the decoder itself runs under the same limit.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solidcore_record::Vec2;
|
||||
use solidcraft_doc::canvas::{Canvas, base64_decode, image_info};
|
||||
use solidcraft_geom::prelude::*;
|
||||
use solidcraft_geom::Vec2;
|
||||
use solidcraft_io::Geom2;
|
||||
|
||||
use super::CommandSpec;
|
||||
|
||||
@@ -11,7 +11,6 @@ use std::sync::Arc;
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solidcraft_geom::mesh_distance::box_gap;
|
||||
use solidcraft_geom::prelude::*;
|
||||
use solidcraft_geom::{Aabb3, Mesh};
|
||||
use solidcraft_kernel::{BodyDistance, DistanceShape};
|
||||
|
||||
@@ -59,7 +58,7 @@ pub(crate) struct PairCache(HashMap<(u64, u64, [u64; 3]), PairResult>);
|
||||
#[derive(Clone, Default)]
|
||||
struct PairResult {
|
||||
/// Shared volume, area, box and centre.
|
||||
overlap: Option<(f64, f64, Aabb3, solidcore_record::Vec3)>,
|
||||
overlap: Option<(f64, f64, Aabb3, solidcraft_geom::Vec3)>,
|
||||
distance: Option<BodyDistance>,
|
||||
unchecked: Option<String>,
|
||||
}
|
||||
@@ -191,7 +190,7 @@ fn clearance(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
p.into_iter().for_each(|v| bx.add(v));
|
||||
}
|
||||
let chord = b.display_tol();
|
||||
let g = solidcore_record::Vec3::new(1.0, 1.0, 1.0) * if chord.is_finite() && chord > 0.0 { chord } else { 0.0 };
|
||||
let g = solidcraft_geom::Vec3::new(1.0, 1.0, 1.0) * if chord.is_finite() && chord > 0.0 { chord } else { 0.0 };
|
||||
if bx.is_empty() { bx } else { Aabb3 { min: bx.min - g, max: bx.max + g } }
|
||||
}
|
||||
_ => Aabb3::EMPTY,
|
||||
@@ -199,7 +198,7 @@ fn clearance(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
.collect();
|
||||
|
||||
// Pairs whose boxes, grown by half the clearance each, meet.
|
||||
let half = solidcore_record::Vec3::new(1.0, 1.0, 1.0) * (gap / 2.0);
|
||||
let half = solidcraft_geom::Vec3::new(1.0, 1.0, 1.0) * (gap / 2.0);
|
||||
let grown: Vec<Aabb3> = boxes.iter().map(|b| if b.is_empty() { *b } else { Aabb3 { min: b.min - half, max: b.max + half } }).collect();
|
||||
let (close, truncated) = super::joints::overlapping_pairs(&grown, MAX_PAIRS);
|
||||
let has_mesh = |i: usize| meshes.get(i).is_some_and(Option::is_some);
|
||||
@@ -407,14 +406,14 @@ fn shared_region(a: &DistanceShape, b: &DistanceShape) -> Aabb3 {
|
||||
return Aabb3::EMPTY;
|
||||
}
|
||||
let grow = 1.0 + 0.05 * min.dist(max);
|
||||
let g = solidcore_record::Vec3::new(grow, grow, grow);
|
||||
let g = solidcraft_geom::Vec3::new(grow, grow, grow);
|
||||
Aabb3 { min: min - g, max: max + g }
|
||||
}
|
||||
|
||||
/// [`solidcraft_kernel::measure`] of `body` moved by `-at` and back: far from the origin, the
|
||||
/// centre of a thin part loses its precision (its moments are large numbers that nearly cancel).
|
||||
fn measure_near_origin(body: &solidcraft_kernel::Body, at: solidcore_record::Vec3) -> solidcraft_kernel::Result<solidcraft_kernel::BodyMeasure> {
|
||||
let moved = solidcraft_kernel::transform(body, -at, solidcore_record::Vec3::ZERO, solidcore_record::Vec3::Z, 0.0)?;
|
||||
fn measure_near_origin(body: &solidcraft_kernel::Body, at: solidcraft_geom::Vec3) -> solidcraft_kernel::Result<solidcraft_kernel::BodyMeasure> {
|
||||
let moved = solidcraft_kernel::transform(body, -at, solidcraft_geom::Vec3::ZERO, solidcraft_geom::Vec3::Z, 0.0)?;
|
||||
let mut m = solidcraft_kernel::measure(&moved)?;
|
||||
m.centroid += at;
|
||||
m.bbox = Aabb3 { min: m.bbox.min + at, max: m.bbox.max + at };
|
||||
|
||||
@@ -41,7 +41,7 @@ fn copy(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
pub(in crate::cmd) fn explicit_planes(p: &mut PlaneRef) {
|
||||
match p {
|
||||
PlaneRef::Origin { name } => {
|
||||
if let Some(plane) = solidcore_record::Plane::named(name) {
|
||||
if let Some(plane) = solidcraft_geom::Plane::named(name) {
|
||||
*p = PlaneRef::Custom { plane };
|
||||
}
|
||||
}
|
||||
@@ -243,7 +243,6 @@ fn paste(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde_json::{Value, json};
|
||||
use solidcraft_geom::prelude::*;
|
||||
|
||||
use crate::Session;
|
||||
|
||||
@@ -310,7 +309,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn foreign_sketch_paste_preserves_world_position_in_a_placed_component() {
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::Vec3;
|
||||
|
||||
let mut source = dimensioned_sketch();
|
||||
let arc = [Vec3::new(1., 0., 0.), Vec3::new(0., 0., 1.), Vec3::new(-1., 0., 0.)];
|
||||
|
||||
@@ -32,7 +32,7 @@ fn independent_links(sketch: &solidcraft_sketch::Sketch, features: &BTreeSet<u64
|
||||
L::Text { .. } | L::SketchAxis { .. } => true,
|
||||
L::SketchCurve { sketch, .. } | L::SketchPoint { sketch, .. } => features.contains(sketch),
|
||||
L::Origin | L::Axis { .. } => !moved,
|
||||
L::Plane { name } => !moved && solidcore_record::Plane::named(name).is_some(),
|
||||
L::Plane { name } => !moved && solidcraft_geom::Plane::named(name).is_some(),
|
||||
_ => false,
|
||||
};
|
||||
if !supported {
|
||||
|
||||
@@ -6,10 +6,8 @@
|
||||
//! the two cuts a − b and b − a that lie there, as `inspect.measure` measures a body.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::Aabb3;
|
||||
use solidcraft_geom::compare::{self, CompareOptions, Side};
|
||||
use solidcraft_geom::prelude::*;
|
||||
use solidcraft_geom::{Aabb3, Vec3};
|
||||
|
||||
use super::CommandSpec;
|
||||
use crate::params::bad;
|
||||
|
||||
@@ -3,12 +3,11 @@
|
||||
//! belong to their feature's component unless moved; occurrences place components (instances
|
||||
//! share contents, "paste new" copies them) and can be grounded and moved.
|
||||
|
||||
use solidcraft_geom::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_doc::{Feature, FeatureKind, Mat, Occurrence};
|
||||
use solidcraft_geom::Vec3;
|
||||
|
||||
use super::CommandSpec;
|
||||
use crate::params::{bad, bool_, str_, string_list, vec3};
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
//! without modifying shared component geometry across other instances.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_doc::{IDENTITY, mat_inverse, mat_mul, rigid};
|
||||
use solidcraft_geom::prelude::*;
|
||||
use solidcraft_geom::Vec3;
|
||||
|
||||
use super::{occurrence, occurrence_path, valid_position};
|
||||
use crate::params::{bad, vec3};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
//! Independent copies of a component definition graph, assembled and evaluated atomically.
|
||||
use solidcraft_doc::BodyName;
|
||||
use solidcraft_geom::prelude::*;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use crate::params::bad;
|
||||
@@ -11,7 +10,7 @@ use solidcraft_doc::{Document, Feature, FeatureKind, Mat, Occurrence};
|
||||
const CMD: &str = "component.paste_new";
|
||||
const MAX_WORK: usize = 10_000;
|
||||
|
||||
fn mesh_distance(mesh: &solidcraft_geom::Mesh, point: solidcore_record::Vec3) -> f64 {
|
||||
fn mesh_distance(mesh: &solidcraft_geom::Mesh, point: solidcraft_geom::Vec3) -> f64 {
|
||||
mesh.triangles
|
||||
.iter()
|
||||
.filter_map(|triangle| mesh.tri(triangle))
|
||||
@@ -34,7 +33,7 @@ fn mesh_distance(mesh: &solidcraft_geom::Mesh, point: solidcore_record::Vec3) ->
|
||||
.fold(f64::INFINITY, f64::min)
|
||||
}
|
||||
|
||||
fn material_probe_matches(a: &solidcraft_geom::Mesh, b: &solidcraft_geom::Mesh, point: solidcore_record::Vec3, tol_a: f64, tol_b: f64) -> bool {
|
||||
fn material_probe_matches(a: &solidcraft_geom::Mesh, b: &solidcraft_geom::Mesh, point: solidcraft_geom::Vec3, tol_a: f64, tol_b: f64) -> bool {
|
||||
if !point.is_finite() || !tol_a.is_finite() || !tol_b.is_finite() || tol_a <= 0. || tol_b <= 0. {
|
||||
return false;
|
||||
}
|
||||
@@ -49,7 +48,7 @@ fn material_probe_matches(a: &solidcraft_geom::Mesh, b: &solidcraft_geom::Mesh,
|
||||
let offset = 4. * tol_a.max(tol_b);
|
||||
let mut material = false;
|
||||
let mut void = false;
|
||||
for axis in [solidcore_record::Vec3::X, solidcore_record::Vec3::Y, solidcore_record::Vec3::Z] {
|
||||
for axis in [solidcraft_geom::Vec3::X, solidcraft_geom::Vec3::Y, solidcraft_geom::Vec3::Z] {
|
||||
for sign in [-1., 1.] {
|
||||
let q = point + axis * (sign * offset);
|
||||
if !q.is_finite() {
|
||||
@@ -768,7 +767,7 @@ pub(super) fn build(
|
||||
for y in [0.2, 0.5, 0.8] {
|
||||
for z in [0.2, 0.5, 0.8] {
|
||||
let d = ma.bbox.max - ma.bbox.min;
|
||||
let p = ma.bbox.min + solidcore_record::Vec3::new(d.x * x, d.y * y, d.z * z);
|
||||
let p = ma.bbox.min + solidcraft_geom::Vec3::new(d.x * x, d.y * y, d.z * z);
|
||||
let roundoff = 64. * f64::EPSILON * (1. + p.len());
|
||||
material_matches &= material_probe_matches(&mesh_a, &mesh_b, p, tol_a + roundoff, tol_b + roundoff);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#![allow(clippy::unwrap_used, clippy::panic)]
|
||||
use super::*;
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_doc::joints::{Coupling, Joint, JointKind, MotionLink, NamedOrigin, Size, Snap, XRef};
|
||||
use solidcraft_doc::motion::{ContactSet, ExplodedView, MotionStudy, StudyKey};
|
||||
use solidcraft_doc::{IDENTITY, Occurrence};
|
||||
use solidcraft_geom::Vec3;
|
||||
|
||||
fn at(occurrence: u64) -> JointOrigin {
|
||||
JointOrigin { occurrence, snap: Snap::Point { point: Vec3::ZERO, z: None, x: None } }
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
use crate::Session;
|
||||
use serde_json::{Value, json};
|
||||
use solidcraft_geom::prelude::*;
|
||||
fn run(s: &mut Session, cmd: &str, p: Value) -> Value {
|
||||
s.execute(cmd, &p).unwrap()
|
||||
}
|
||||
@@ -257,7 +256,7 @@ fn configuration_rows_follow_copied_parameters_material_and_strict_face_lineage(
|
||||
body: "Cube".into(),
|
||||
origin_feature: source,
|
||||
name: format!("F{source}:+z"),
|
||||
point: solidcore_record::Vec3::new(1.0, 1.0, 2.0),
|
||||
point: solidcraft_geom::Vec3::new(1.0, 1.0, 2.0),
|
||||
certificate: None,
|
||||
};
|
||||
run(&mut s, "config.configure", json!({"params":[length],"materials":["Cube"],"component_appearances":[part],"face_appearances":[target]}));
|
||||
@@ -295,7 +294,7 @@ fn configured_extrude_cap_copy_keeps_independent_semantic_lineage() {
|
||||
body: body_name,
|
||||
origin_feature: feature,
|
||||
name: format!("F{feature}:top"),
|
||||
point: solidcore_record::Vec3::new(1.0, 1.0, 2.0),
|
||||
point: solidcraft_geom::Vec3::new(1.0, 1.0, 2.0),
|
||||
certificate: None,
|
||||
};
|
||||
run(&mut s, "config.configure", json!({"face_appearances":[target]}));
|
||||
@@ -359,7 +358,7 @@ fn reassigned_sample_plate_paste_new_preserves_complex_frozen_geometry() {
|
||||
run(&mut s, "occurrence.ground", json!({"occurrence":occurrence,"grounded":true}));
|
||||
let original_volume = volume(&mut s, part);
|
||||
let source_mesh = s.model.state().body(&body).unwrap().mesh();
|
||||
let boss_probe = solidcore_record::Vec3::new(40.0, 35.0, 12.0);
|
||||
let boss_probe = solidcraft_geom::Vec3::new(40.0, 35.0, 12.0);
|
||||
let source_boss = source_mesh.contains(boss_probe);
|
||||
let copied = run(&mut s, "component.paste_new", json!({"component":part,"translate":[10,10,0]}));
|
||||
assert_eq!(s.doc.components.len(), 2);
|
||||
@@ -374,7 +373,7 @@ fn reassigned_sample_plate_paste_new_preserves_complex_frozen_geometry() {
|
||||
for (point, expected) in
|
||||
[([5.0, 25.0, 4.0], true), ([12.0, 25.0, 4.0], false), ([68.0, 25.0, 4.0], false), ([40.0, 25.0, 12.0], false), ([0.0, 0.0, 4.0], false)]
|
||||
{
|
||||
assert_eq!(mesh.contains(solidcore_record::Vec3::new(point[0], point[1], point[2])), expected, "{point:?}");
|
||||
assert_eq!(mesh.contains(solidcraft_geom::Vec3::new(point[0], point[1], point[2])), expected, "{point:?}");
|
||||
}
|
||||
assert_eq!(mesh.contains(boss_probe), source_boss);
|
||||
assert_eq!(state.body(&body).unwrap().mesh().contains(boss_probe), source_boss);
|
||||
@@ -474,7 +473,7 @@ fn transferred_frozen_face_paint_ignores_consumed_legacy_offset() {
|
||||
run(&mut s, "appearance.assign", json!({"body":"Foreign","faces":[[1,1,2]],"color":"#00ff00"}));
|
||||
s.doc_mut().body_offsets.insert(
|
||||
"Foreign".into(),
|
||||
solidcraft_doc::rigid(solidcore_record::Vec3::new(7.0, 0.0, 0.0), solidcore_record::Vec3::ZERO, solidcore_record::Vec3::Z, 0.0),
|
||||
solidcraft_doc::rigid(solidcraft_geom::Vec3::new(7.0, 0.0, 0.0), solidcraft_geom::Vec3::ZERO, solidcraft_geom::Vec3::Z, 0.0),
|
||||
);
|
||||
s.refresh();
|
||||
let part = component(&mut s, "Part");
|
||||
@@ -488,7 +487,7 @@ fn transferred_frozen_face_paint_ignores_consumed_legacy_offset() {
|
||||
|
||||
#[test]
|
||||
fn boundary_material_proof_requires_resolvable_stock_and_void() {
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::Vec3;
|
||||
let a = solidcraft_kernel::box_solid(Vec3::ZERO, Vec3::new(10., 10., 10.)).unwrap().mesh(&solidcraft_kernel::MeshTolerance::chord(1e-3)).unwrap();
|
||||
let b = solidcraft_kernel::box_solid(Vec3::new(1e-5, 0., 0.), Vec3::new(10.00001, 10., 10.))
|
||||
.unwrap()
|
||||
@@ -517,7 +516,7 @@ fn boundary_material_proof_requires_resolvable_stock_and_void() {
|
||||
|
||||
#[test]
|
||||
fn frozen_bore_boundary_copy_preserves_stock_void_and_reopen() {
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::Vec3;
|
||||
let bore = |center: f64| {
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "solid.box", json!({"length":30,"width":20,"height":10,"body_name":"Drilled"}));
|
||||
@@ -553,7 +552,7 @@ fn frozen_bore_boundary_copy_preserves_stock_void_and_reopen() {
|
||||
|
||||
#[test]
|
||||
fn frozen_r6_bore_copy_uses_current_geometry_despite_mesh_measure_bias() {
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::Vec3;
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "solid.box", json!({"length":20,"width":20,"height":10,"body_name":"Drilled"}));
|
||||
run(&mut s, "solid.cylinder", json!({"base":[10,10,0],"radius":6,"height":10,"operation":"cut"}));
|
||||
|
||||
@@ -10,13 +10,12 @@
|
||||
//! as it did before. Anything that crosses the component's boundary (a feature outside that uses
|
||||
//! its sketches, bodies moved in or out, a joint to one of its subcomponents) is refused.
|
||||
|
||||
use solidcraft_geom::prelude::*;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::path::Path;
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_doc::{DesignLink, Document, FeatureKind, ModelBody, ModelState};
|
||||
use solidcraft_geom::Vec3;
|
||||
|
||||
use super::{absolute, component_key, file_sha256, inherit_properties, resolve, session_bodies, stored};
|
||||
use crate::params::{bad, str_};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
use crate::{ForeignCopy, Session};
|
||||
use serde_json::{Value, json};
|
||||
use solidcraft_geom::prelude::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn run(s: &mut Session, cmd: &str, p: Value) -> Value {
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
//! the design gives them (`Feature::link_faces`), so a joint on a linked face stays on that face
|
||||
//! when the design changes, as it would inside the design, and reports it when the face is gone.
|
||||
|
||||
use solidcraft_geom::prelude::*;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Mutex;
|
||||
@@ -497,7 +496,7 @@ fn insert_design(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let cmd = "component.insert_design";
|
||||
let given = str_(p, "path").filter(|x| !x.trim().is_empty() && x.len() < 4096).ok_or_else(|| bad(cmd, "`path` is required"))?;
|
||||
let translate = match p.get("translate") {
|
||||
None => solidcore_record::Vec3::ZERO,
|
||||
None => solidcraft_geom::Vec3::ZERO,
|
||||
Some(v) => vec3(v).filter(|t| t.is_finite() && t.len() < 1e9).ok_or_else(|| bad(cmd, "`translate` must be [x, y, z] in mm"))?,
|
||||
};
|
||||
// A network or device path is judged from its text, as given, before anything reads it.
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
//! Physical properties of an assembly placement, with original body material assignments.
|
||||
use solidcraft_geom::prelude::*;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_doc::{Document, Mat};
|
||||
use solidcraft_geom::Aabb3;
|
||||
use solidcraft_geom::inertia::{self, Tensor3};
|
||||
use solidcraft_geom::{Aabb3, Vec3};
|
||||
|
||||
use crate::params::bad;
|
||||
use crate::{Result, Session};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user