Files
solvecraft/crates/gpu/tests/thin_lines.rs
jyviko dc38349cbf Layer split stage 1: the B-rep record, native body file and Vec/Plane come from solidcore-record (#11496)
* Kernel: the B-rep record and the native body file come from solidcore-record

SolidCraft's portable B-rep record (`solidcraft_geom::brep`: brep.rs, topology, history,
envelope, part, curve2) and the native body file (`solidcraft_kernel::native`: the tagged binary
codec and the container) move to solidcore's `solidcore-record` crate: geometry as data, with
structural validation and no maths (solidcore's docs/layers.md, stage 1). Every caller uses
`solidcore_record::brep` and `solidcore_record::native`; the old copies are deleted.

What computes from coordinates stays with the kernel, in geom's `brep`:
- `brep::check`: the record's geometric checks (orthonormal frames, non-degenerate planes,
  unit revolution axes, non-singular placements). `check::validate` and
  `check::validate_built` are the record's validation and these together, and every caller of
  `Brep::validate` uses them, so what is accepted is unchanged;
- `brep::label`: a surface's natural ranges (a cone's apex), `curve_interval`,
  `edge_curve_interval` and `form_turns`;
- `brep::pcurve::eval`: a pcurve's point;
- `geometry::check`, `placement::moved` and `split_edges` become functions on the record.

The kernel's native module keeps the conversion between a body and its saved record
(metadata.rs) and checks the decoded record's geometry. `native/analytic.rs`, which no module
declared, is deleted.

solidcore-record is pinned to its rev on solidcore's `layer-record` branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NvoxKMbMFSQU8tzRtoJhtT

* Geometry: Vec2, Vec3, Plane and the profile types come from solidcore-record

The plain data of vectors and points, sketch planes and planar profiles (`Seg2`, `Loop2`,
`Region2`) moves to `solidcore-record`, with its constants and component-wise operators only
(owner decision, solidcore layer split). Their geometry stays in geom as traits:
`Vec2Geom`, `Vec3Geom`, `PlaneGeom`, `Seg2Geom`, `Loop2Geom`, `Region2Geom`, `Seg2ArcFit`,
`Loop2ArcFit` and `Region2ArcFit`, in scope with `use solidcraft_geom::prelude::*`. Every caller
names the types from `solidcore_record`; geom no longer defines or re-exports them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NvoxKMbMFSQU8tzRtoJhtT

* Kernel: pin solidcore-record to solidcore main f5e8e9a29

solidcore-record is merged on solidcore main (tangency-labs/solidcore#2519); its crate there is
identical to the rev pinned before but for its clippy configuration. The lock is regenerated
against the merged rev.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NvoxKMbMFSQU8tzRtoJhtT

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 09:04:26 -05:00

55 lines
2.4 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Lines thinner than a pixel (the grid's minor lines are 0.7 px) are drawn wherever they fall,
//! without multisampling (#2823). A quad narrower than a pixel covers no pixel centre when it
//! falls between two rows, so such lines came and went as the view zoomed. Skipped, with the
//! reason printed, without a GPU adapter (`SOLIDCRAFT_REQUIRE_GPU=1` makes that a failure).
use solidcore_record::Vec3;
use solidcraft_engine::render::{Camera, Rgb, Scene, SceneLine, StandardView};
use solidcraft_gpu::GpuError;
use solidcraft_gpu::capture::Capturer;
/// Pictures drawn with one sample per pixel: with 4× multisampling a thin line covers some
/// samples wherever it falls, which hides the fault.
fn capturer() -> Option<&'static Capturer> {
static SINGLE: std::sync::OnceLock<solidcraft_gpu::Result<Capturer>> = std::sync::OnceLock::new();
match SINGLE.get_or_init(Capturer::headless_single_sample).as_ref() {
Ok(c) => {
assert_eq!(c.samples(), 1);
Some(c)
}
Err(e @ GpuError::NoAdapter(_)) if std::env::var_os("SOLIDCRAFT_REQUIRE_GPU").is_none() => {
eprintln!("skipping: {e}");
None
}
Err(e) => panic!("{e}"),
}
}
/// Lines `width` px wide across a front view, each drawn alone at its own height so it falls at
/// its own fraction of a pixel (0.23 mm apart, about 1.15 px at 5 px per mm); returns the ones
/// that left no pixel.
fn missing(c: &Capturer, width: f64) -> Vec<usize> {
const N: usize = 40;
let mut cam = Camera { distance: 62.5, fov: 0.0, ..Default::default() };
cam.set_view(StandardView::Front);
(0..N)
.filter(|&i| {
let z = -4.0 + i as f64 * 0.23;
let line =
SceneLine { points: vec![Vec3::new(-8.0, 0.0, z), Vec3::new(8.0, 0.0, z)], color: Rgb(255, 0, 0), alpha: 1.0, width, on_top: true };
let scene = Scene { lines: vec![line], radius: 30.0, ..Default::default() };
let img = c.rgba(&scene, &cam, 200, 200).unwrap();
!img.as_chunks::<4>().0.iter().any(|p| p[0] > 60 && p[0] > p[1].saturating_add(40) && p[0] > p[2].saturating_add(40))
})
.collect()
}
#[test]
fn lines_thinner_than_a_pixel_are_drawn_wherever_they_fall() {
let Some(c) = capturer() else { return };
for width in [0.4, 0.7, 1.0, 1.5] {
let gone = missing(c, width);
assert!(gone.is_empty(), "{width} px lines: {} of 40 left no pixel ({gone:?})", gone.len());
}
}