mirror of
https://github.com/hannobraun/Fornjot
synced 2025-01-11 10:47:09 +00:00
Merge pull request #1616 from hannobraun/curve
Merge `Curve` into `HalfEdge`
This commit is contained in:
commit
dffd7bbe22
@ -9,7 +9,7 @@ use std::collections::BTreeMap;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
geometry::path::{GlobalPath, SurfacePath},
|
geometry::path::{GlobalPath, SurfacePath},
|
||||||
objects::{Curve, GlobalEdge, HalfEdge, Surface},
|
objects::{GlobalEdge, HalfEdge, Surface},
|
||||||
storage::{Handle, ObjectId},
|
storage::{Handle, ObjectId},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ impl Approx for (&Handle<HalfEdge>, &Surface) {
|
|||||||
Some(approx) => approx,
|
Some(approx) => approx,
|
||||||
None => {
|
None => {
|
||||||
let approx = approx_edge(
|
let approx = approx_edge(
|
||||||
half_edge.curve(),
|
&half_edge.curve(),
|
||||||
surface,
|
surface,
|
||||||
range,
|
range,
|
||||||
tolerance,
|
tolerance,
|
||||||
@ -56,14 +56,10 @@ impl Approx for (&Handle<HalfEdge>, &Surface) {
|
|||||||
.map(|point| {
|
.map(|point| {
|
||||||
let point_surface = half_edge
|
let point_surface = half_edge
|
||||||
.curve()
|
.curve()
|
||||||
.path()
|
|
||||||
.point_from_path_coords(point.local_form);
|
.point_from_path_coords(point.local_form);
|
||||||
|
|
||||||
ApproxPoint::new(point_surface, point.global_form)
|
ApproxPoint::new(point_surface, point.global_form)
|
||||||
.with_source((
|
.with_source((half_edge.clone(), point.local_form))
|
||||||
half_edge.curve().clone(),
|
|
||||||
point.local_form,
|
|
||||||
))
|
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
};
|
};
|
||||||
@ -95,7 +91,7 @@ impl HalfEdgeApprox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn approx_edge(
|
fn approx_edge(
|
||||||
curve: &Curve,
|
curve: &SurfacePath,
|
||||||
surface: &Surface,
|
surface: &Surface,
|
||||||
range: RangeOnPath,
|
range: RangeOnPath,
|
||||||
tolerance: impl Into<Tolerance>,
|
tolerance: impl Into<Tolerance>,
|
||||||
@ -106,14 +102,14 @@ fn approx_edge(
|
|||||||
// This will probably all be unified eventually, as `SurfacePath` and
|
// This will probably all be unified eventually, as `SurfacePath` and
|
||||||
// `GlobalPath` grow APIs that are better suited to implementing this code
|
// `GlobalPath` grow APIs that are better suited to implementing this code
|
||||||
// in a more abstract way.
|
// in a more abstract way.
|
||||||
let points = match (curve.path(), surface.geometry().u) {
|
let points = match (curve, surface.geometry().u) {
|
||||||
(SurfacePath::Circle(_), GlobalPath::Circle(_)) => {
|
(SurfacePath::Circle(_), GlobalPath::Circle(_)) => {
|
||||||
todo!(
|
todo!(
|
||||||
"Approximating a circle on a curved surface not supported yet."
|
"Approximating a circle on a curved surface not supported yet."
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
(SurfacePath::Circle(_), GlobalPath::Line(_)) => {
|
(SurfacePath::Circle(_), GlobalPath::Line(_)) => {
|
||||||
(curve.path(), range)
|
(curve, range)
|
||||||
.approx_with_cache(tolerance, &mut ())
|
.approx_with_cache(tolerance, &mut ())
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(point_curve, point_surface)| {
|
.map(|(point_curve, point_surface)| {
|
||||||
@ -142,7 +138,7 @@ fn approx_edge(
|
|||||||
(SurfacePath::Line(line), _) => {
|
(SurfacePath::Line(line), _) => {
|
||||||
let range_u =
|
let range_u =
|
||||||
RangeOnPath::from(range.boundary.map(|point_curve| {
|
RangeOnPath::from(range.boundary.map(|point_curve| {
|
||||||
[curve.path().point_from_path_coords(point_curve).u]
|
[curve.point_from_path_coords(point_curve).u]
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let approx_u = (surface.geometry().u, range_u)
|
let approx_u = (surface.geometry().u, range_u)
|
||||||
@ -151,7 +147,7 @@ fn approx_edge(
|
|||||||
let mut points = Vec::new();
|
let mut points = Vec::new();
|
||||||
for (u, _) in approx_u {
|
for (u, _) in approx_u {
|
||||||
let t = (u.t - line.origin().u) / line.direction().u;
|
let t = (u.t - line.origin().u) / line.direction().u;
|
||||||
let point_surface = curve.path().point_from_path_coords([t]);
|
let point_surface = curve.point_from_path_coords([t]);
|
||||||
let point_global =
|
let point_global =
|
||||||
surface.geometry().point_from_surface_coords(point_surface);
|
surface.geometry().point_from_surface_coords(point_surface);
|
||||||
points.push((u, point_global));
|
points.push((u, point_global));
|
||||||
@ -323,10 +319,8 @@ mod tests {
|
|||||||
.approx(tolerance)
|
.approx(tolerance)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(point_local, _)| {
|
.map(|(point_local, _)| {
|
||||||
let point_surface = half_edge
|
let point_surface =
|
||||||
.curve()
|
half_edge.curve().point_from_path_coords(point_local);
|
||||||
.path()
|
|
||||||
.point_from_path_coords(point_local);
|
|
||||||
let point_global =
|
let point_global =
|
||||||
surface.geometry().point_from_surface_coords(point_surface);
|
surface.geometry().point_from_surface_coords(point_surface);
|
||||||
ApproxPoint::new(point_surface, point_global)
|
ApproxPoint::new(point_surface, point_global)
|
||||||
@ -355,7 +349,7 @@ mod tests {
|
|||||||
let approx = (&half_edge, surface.deref()).approx(tolerance);
|
let approx = (&half_edge, surface.deref()).approx(tolerance);
|
||||||
|
|
||||||
let expected_approx =
|
let expected_approx =
|
||||||
(half_edge.curve().path(), RangeOnPath::from([[0.], [TAU]]))
|
(&half_edge.curve(), RangeOnPath::from([[0.], [TAU]]))
|
||||||
.approx(tolerance)
|
.approx(tolerance)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(_, point_surface)| {
|
.map(|(_, point_surface)| {
|
||||||
|
@ -19,10 +19,7 @@ use std::{
|
|||||||
|
|
||||||
use fj_math::Point;
|
use fj_math::Point;
|
||||||
|
|
||||||
use crate::{
|
use crate::{objects::HalfEdge, storage::Handle};
|
||||||
objects::{Curve, HalfEdge},
|
|
||||||
storage::Handle,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use self::tolerance::{InvalidTolerance, Tolerance};
|
pub use self::tolerance::{InvalidTolerance, Tolerance};
|
||||||
|
|
||||||
@ -121,5 +118,4 @@ impl<const D: usize> PartialOrd for ApproxPoint<D> {
|
|||||||
/// The source of an [`ApproxPoint`]
|
/// The source of an [`ApproxPoint`]
|
||||||
pub trait Source: Any + Debug {}
|
pub trait Source: Any + Debug {}
|
||||||
|
|
||||||
impl Source for (Handle<Curve>, Point<1>) {}
|
|
||||||
impl Source for (Handle<HalfEdge>, Point<1>) {}
|
impl Source for (Handle<HalfEdge>, Point<1>) {}
|
||||||
|
@ -36,7 +36,7 @@ use crate::geometry::path::{GlobalPath, SurfacePath};
|
|||||||
|
|
||||||
use super::{Approx, Tolerance};
|
use super::{Approx, Tolerance};
|
||||||
|
|
||||||
impl Approx for (SurfacePath, RangeOnPath) {
|
impl Approx for (&SurfacePath, RangeOnPath) {
|
||||||
type Approximation = Vec<(Point<1>, Point<2>)>;
|
type Approximation = Vec<(Point<1>, Point<2>)>;
|
||||||
type Cache = ();
|
type Cache = ();
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ impl Approx for (SurfacePath, RangeOnPath) {
|
|||||||
|
|
||||||
match path {
|
match path {
|
||||||
SurfacePath::Circle(circle) => {
|
SurfacePath::Circle(circle) => {
|
||||||
approx_circle(&circle, range, tolerance.into())
|
approx_circle(circle, range, tolerance.into())
|
||||||
}
|
}
|
||||||
SurfacePath::Line(_) => vec![],
|
SurfacePath::Line(_) => vec![],
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ impl CurveEdgeIntersection {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let edge_as_segment = {
|
let edge_as_segment = {
|
||||||
let edge_curve_as_line = match half_edge.curve().path() {
|
let edge_curve_as_line = match half_edge.curve() {
|
||||||
SurfacePath::Line(line) => line,
|
SurfacePath::Line(line) => line,
|
||||||
_ => {
|
_ => {
|
||||||
todo!("Curve-edge intersection only supports line segments")
|
todo!("Curve-edge intersection only supports line segments")
|
||||||
|
@ -17,7 +17,7 @@ impl Intersect for (&HorizontalRayToTheRight<2>, &Handle<HalfEdge>) {
|
|||||||
fn intersect(self) -> Option<Self::Intersection> {
|
fn intersect(self) -> Option<Self::Intersection> {
|
||||||
let (ray, edge) = self;
|
let (ray, edge) = self;
|
||||||
|
|
||||||
let line = match edge.curve().path() {
|
let line = match edge.curve() {
|
||||||
SurfacePath::Line(line) => line,
|
SurfacePath::Line(line) => line,
|
||||||
SurfacePath::Circle(_) => {
|
SurfacePath::Circle(_) => {
|
||||||
todo!("Casting rays against circles is not supported yet")
|
todo!("Casting rays against circles is not supported yet")
|
||||||
|
@ -18,11 +18,7 @@ impl Reverse for Handle<HalfEdge> {
|
|||||||
[b, a]
|
[b, a]
|
||||||
};
|
};
|
||||||
|
|
||||||
HalfEdge::new(
|
HalfEdge::new(self.curve(), vertices, self.global_form().clone())
|
||||||
self.curve().clone(),
|
.insert(objects)
|
||||||
vertices,
|
|
||||||
self.global_form().clone(),
|
|
||||||
)
|
|
||||||
.insert(objects)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ use crate::{
|
|||||||
builder::SurfaceBuilder,
|
builder::SurfaceBuilder,
|
||||||
geometry::path::{GlobalPath, SurfacePath},
|
geometry::path::{GlobalPath, SurfacePath},
|
||||||
insert::Insert,
|
insert::Insert,
|
||||||
objects::{Curve, Objects, Surface},
|
objects::{Objects, Surface},
|
||||||
partial::{PartialObject, PartialSurface},
|
partial::{PartialObject, PartialSurface},
|
||||||
services::Service,
|
services::Service,
|
||||||
storage::Handle,
|
storage::Handle,
|
||||||
@ -12,7 +12,7 @@ use crate::{
|
|||||||
|
|
||||||
use super::{Sweep, SweepCache};
|
use super::{Sweep, SweepCache};
|
||||||
|
|
||||||
impl Sweep for (Handle<Curve>, &Surface) {
|
impl Sweep for (SurfacePath, &Surface) {
|
||||||
type Swept = Handle<Surface>;
|
type Swept = Handle<Surface>;
|
||||||
|
|
||||||
fn sweep_with_cache(
|
fn sweep_with_cache(
|
||||||
@ -47,7 +47,7 @@ impl Sweep for (Handle<Curve>, &Surface) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let u = match curve.path() {
|
let u = match curve {
|
||||||
SurfacePath::Circle(circle) => {
|
SurfacePath::Circle(circle) => {
|
||||||
let center = surface
|
let center = surface
|
||||||
.geometry()
|
.geometry()
|
||||||
|
@ -35,8 +35,7 @@ impl Sweep for (Handle<HalfEdge>, &Surface, Color) {
|
|||||||
// we're sweeping.
|
// we're sweeping.
|
||||||
{
|
{
|
||||||
let surface = Partial::from(
|
let surface = Partial::from(
|
||||||
(edge.curve().clone(), surface)
|
(edge.curve(), surface).sweep_with_cache(path, cache, objects),
|
||||||
.sweep_with_cache(path, cache, objects),
|
|
||||||
);
|
);
|
||||||
face.surface = surface;
|
face.surface = surface;
|
||||||
}
|
}
|
||||||
|
@ -109,8 +109,7 @@ impl Sweep for Handle<Face> {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(top_cycle.write().half_edges.iter_mut())
|
.zip(top_cycle.write().half_edges.iter_mut())
|
||||||
{
|
{
|
||||||
top.write().curve.write().path =
|
top.write().curve = Some(bottom.curve().into());
|
||||||
Some(bottom.curve().path().into());
|
|
||||||
|
|
||||||
let boundary = bottom.boundary();
|
let boundary = bottom.boundary();
|
||||||
|
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
use fj_math::Transform;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
objects::{Curve, Objects},
|
|
||||||
services::Service,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{TransformCache, TransformObject};
|
|
||||||
|
|
||||||
impl TransformObject for Curve {
|
|
||||||
fn transform_with_cache(
|
|
||||||
self,
|
|
||||||
_: &Transform,
|
|
||||||
_: &mut Service<Objects>,
|
|
||||||
_: &mut TransformCache,
|
|
||||||
) -> Self {
|
|
||||||
// Don't need to transform path, as that's defined in surface
|
|
||||||
// coordinates, and thus transforming `surface` takes care of it.
|
|
||||||
let path = self.path();
|
|
||||||
|
|
||||||
Self::new(path)
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,10 +15,9 @@ impl TransformObject for HalfEdge {
|
|||||||
objects: &mut Service<Objects>,
|
objects: &mut Service<Objects>,
|
||||||
cache: &mut TransformCache,
|
cache: &mut TransformCache,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let curve = self
|
// Don't need to transform curve, as that's defined in surface
|
||||||
.curve()
|
// coordinates.
|
||||||
.clone()
|
let curve = self.curve();
|
||||||
.transform_with_cache(transform, objects, cache);
|
|
||||||
let boundary = self.boundary().zip_ext(self.surface_vertices()).map(
|
let boundary = self.boundary().zip_ext(self.surface_vertices()).map(
|
||||||
|(point, surface_vertex)| {
|
|(point, surface_vertex)| {
|
||||||
let surface_vertex = surface_vertex
|
let surface_vertex = surface_vertex
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
//! API for transforming objects
|
//! API for transforming objects
|
||||||
|
|
||||||
mod curve;
|
|
||||||
mod cycle;
|
mod cycle;
|
||||||
mod edge;
|
mod edge;
|
||||||
mod face;
|
mod face;
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
use crate::partial::PartialCurve;
|
|
||||||
|
|
||||||
/// Builder API for [`PartialCurve`]
|
|
||||||
pub trait CurveBuilder {
|
|
||||||
// No methods are currently defined. This trait serves as a placeholder, to
|
|
||||||
// make it clear where to add such methods, once necessary.
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CurveBuilder for PartialCurve {}
|
|
@ -76,13 +76,13 @@ pub trait HalfEdgeBuilder {
|
|||||||
impl HalfEdgeBuilder for PartialHalfEdge {
|
impl HalfEdgeBuilder for PartialHalfEdge {
|
||||||
fn update_as_u_axis(&mut self) -> SurfacePath {
|
fn update_as_u_axis(&mut self) -> SurfacePath {
|
||||||
let path = SurfacePath::u_axis();
|
let path = SurfacePath::u_axis();
|
||||||
self.curve.write().path = Some(path.into());
|
self.curve = Some(path.into());
|
||||||
path
|
path
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_as_v_axis(&mut self) -> SurfacePath {
|
fn update_as_v_axis(&mut self) -> SurfacePath {
|
||||||
let path = SurfacePath::v_axis();
|
let path = SurfacePath::v_axis();
|
||||||
self.curve.write().path = Some(path.into());
|
self.curve = Some(path.into());
|
||||||
path
|
path
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +91,7 @@ impl HalfEdgeBuilder for PartialHalfEdge {
|
|||||||
radius: impl Into<Scalar>,
|
radius: impl Into<Scalar>,
|
||||||
) -> SurfacePath {
|
) -> SurfacePath {
|
||||||
let path = SurfacePath::circle_from_radius(radius);
|
let path = SurfacePath::circle_from_radius(radius);
|
||||||
self.curve.write().path = Some(path.into());
|
self.curve = Some(path.into());
|
||||||
|
|
||||||
let [a_curve, b_curve] =
|
let [a_curve, b_curve] =
|
||||||
[Scalar::ZERO, Scalar::TAU].map(|coord| Point::from([coord]));
|
[Scalar::ZERO, Scalar::TAU].map(|coord| Point::from([coord]));
|
||||||
@ -133,7 +133,7 @@ impl HalfEdgeBuilder for PartialHalfEdge {
|
|||||||
|
|
||||||
let path =
|
let path =
|
||||||
SurfacePath::circle_from_center_and_radius(arc.center, arc.radius);
|
SurfacePath::circle_from_center_and_radius(arc.center, arc.radius);
|
||||||
self.curve.write().path = Some(path.into());
|
self.curve = Some(path.into());
|
||||||
|
|
||||||
let [a_curve, b_curve] =
|
let [a_curve, b_curve] =
|
||||||
[arc.start_angle, arc.end_angle].map(|coord| Point::from([coord]));
|
[arc.start_angle, arc.end_angle].map(|coord| Point::from([coord]));
|
||||||
@ -175,12 +175,12 @@ impl HalfEdgeBuilder for PartialHalfEdge {
|
|||||||
let points = [start, end].zip_ext(points_surface);
|
let points = [start, end].zip_ext(points_surface);
|
||||||
|
|
||||||
let path = SurfacePath::from_points_with_line_coords(points);
|
let path = SurfacePath::from_points_with_line_coords(points);
|
||||||
self.curve.write().path = Some(path.into());
|
self.curve = Some(path.into());
|
||||||
|
|
||||||
path
|
path
|
||||||
} else {
|
} else {
|
||||||
let (path, _) = SurfacePath::line_from_points(points_surface);
|
let (path, _) = SurfacePath::line_from_points(points_surface);
|
||||||
self.curve.write().path = Some(path.into());
|
self.curve = Some(path.into());
|
||||||
|
|
||||||
for (vertex, position) in
|
for (vertex, position) in
|
||||||
self.vertices.each_mut_ext().zip_ext([0., 1.])
|
self.vertices.each_mut_ext().zip_ext([0., 1.])
|
||||||
@ -211,8 +211,6 @@ impl HalfEdgeBuilder for PartialHalfEdge {
|
|||||||
) {
|
) {
|
||||||
let path = self
|
let path = self
|
||||||
.curve
|
.curve
|
||||||
.read()
|
|
||||||
.path
|
|
||||||
.expect("Can't infer vertex positions without curve");
|
.expect("Can't infer vertex positions without curve");
|
||||||
let MaybeSurfacePath::Defined(path) = path else {
|
let MaybeSurfacePath::Defined(path) = path else {
|
||||||
panic!("Can't infer vertex positions with undefined path");
|
panic!("Can't infer vertex positions with undefined path");
|
||||||
@ -254,83 +252,79 @@ impl HalfEdgeBuilder for PartialHalfEdge {
|
|||||||
other: &Partial<HalfEdge>,
|
other: &Partial<HalfEdge>,
|
||||||
surface: &SurfaceGeometry,
|
surface: &SurfaceGeometry,
|
||||||
) {
|
) {
|
||||||
self.curve.write().path =
|
self.curve = other.read().curve.as_ref().and_then(|path| {
|
||||||
other.read().curve.read().path.as_ref().and_then(|path| {
|
// We have information about the other edge's surface available. We
|
||||||
// We have information about the other edge's surface available.
|
// need to use that to interpret what the other edge's curve path
|
||||||
// We need to use that to interpret what the other edge's curve
|
// means for our curve path.
|
||||||
// path means for our curve path.
|
match surface.u {
|
||||||
match surface.u {
|
GlobalPath::Circle(circle) => {
|
||||||
GlobalPath::Circle(circle) => {
|
// The other surface is curved. We're entering some dodgy
|
||||||
// The other surface is curved. We're entering some
|
// territory here, as only some edge cases can be
|
||||||
// dodgy territory here, as only some edge cases can be
|
// represented using our current curve/surface
|
||||||
// represented using our current curve/surface
|
// representation.
|
||||||
// representation.
|
match path {
|
||||||
match path {
|
MaybeSurfacePath::Defined(SurfacePath::Line(_))
|
||||||
MaybeSurfacePath::Defined(SurfacePath::Line(_))
|
| MaybeSurfacePath::UndefinedLine => {
|
||||||
| MaybeSurfacePath::UndefinedLine => {
|
// We're dealing with a line on a rounded surface.
|
||||||
// We're dealing with a line on a rounded
|
//
|
||||||
// surface.
|
// Based on the current uses of this method, we can
|
||||||
//
|
// make some assumptions:
|
||||||
// Based on the current uses of this method, we
|
//
|
||||||
// can make some assumptions:
|
// 1. The line is parallel to the u-axis of the
|
||||||
//
|
// other surface.
|
||||||
// 1. The line is parallel to the u-axis of the
|
// 2. The surface that *our* edge is in is a plane
|
||||||
// other surface.
|
// that is parallel to the the plane of the
|
||||||
// 2. The surface that *our* edge is in is a
|
// circle that defines the curvature of the other
|
||||||
// plane that is parallel to the the plane of
|
// surface.
|
||||||
// the circle that defines the curvature of
|
//
|
||||||
// the other surface.
|
// These assumptions are necessary preconditions for
|
||||||
//
|
// the following code to work. But unfortunately, I
|
||||||
// These assumptions are necessary preconditions
|
// see no way to check those preconditions here, as
|
||||||
// for the following code to work. But
|
// neither the other line nor our surface is
|
||||||
// unfortunately, I see no way to check those
|
// necessarily defined yet.
|
||||||
// preconditions here, as neither the other line
|
//
|
||||||
// nor our surface is necessarily defined yet.
|
// Handling this case anyway feels like a grave sin,
|
||||||
//
|
// but I don't know what else to do. If you tracked
|
||||||
// Handling this case anyway feels like a grave
|
// some extremely subtle and annoying bug back to
|
||||||
// sin, but I don't know what else to do. If you
|
// this code, I apologize.
|
||||||
// tracked some extremely subtle and annoying
|
//
|
||||||
// bug back to this code, I apologize.
|
// I hope that I'll come up with a better curve/
|
||||||
//
|
// surface representation before this becomes a
|
||||||
// I hope that I'll come up with a better curve/
|
// problem.
|
||||||
// surface representation before this becomes a
|
Some(MaybeSurfacePath::UndefinedCircle {
|
||||||
// problem.
|
radius: circle.radius(),
|
||||||
Some(MaybeSurfacePath::UndefinedCircle {
|
})
|
||||||
radius: circle.radius(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// The other edge is a line segment in a curved
|
|
||||||
// surface. No idea how to deal with this.
|
|
||||||
todo!(
|
|
||||||
"Can't connect edge to circle on curved \
|
|
||||||
surface"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
_ => {
|
||||||
GlobalPath::Line(_) => {
|
// The other edge is a line segment in a curved
|
||||||
// The other edge is defined on a plane.
|
// surface. No idea how to deal with this.
|
||||||
match path {
|
todo!(
|
||||||
MaybeSurfacePath::Defined(SurfacePath::Line(_))
|
"Can't connect edge to circle on curved \
|
||||||
| MaybeSurfacePath::UndefinedLine => {
|
surface"
|
||||||
// The other edge is a line segment on a plane.
|
)
|
||||||
// That means our edge must be a line segment
|
|
||||||
// too.
|
|
||||||
Some(MaybeSurfacePath::UndefinedLine)
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// The other edge is a circle or arc on a plane.
|
|
||||||
// I'm actually not sure what that means for our
|
|
||||||
// edge. We might be able to represent it
|
|
||||||
// somehow, but let's leave that as an exercise
|
|
||||||
// for later.
|
|
||||||
todo!("Can't connect edge to circle on plane")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
GlobalPath::Line(_) => {
|
||||||
|
// The other edge is defined on a plane.
|
||||||
|
match path {
|
||||||
|
MaybeSurfacePath::Defined(SurfacePath::Line(_))
|
||||||
|
| MaybeSurfacePath::UndefinedLine => {
|
||||||
|
// The other edge is a line segment on a plane. That
|
||||||
|
// means our edge must be a line segment too.
|
||||||
|
Some(MaybeSurfacePath::UndefinedLine)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// The other edge is a circle or arc on a plane. I'm
|
||||||
|
// actually not sure what that means for our edge.
|
||||||
|
// We might be able to represent it somehow, but
|
||||||
|
// let's leave that as an exercise for later.
|
||||||
|
todo!("Can't connect edge to circle on plane")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
for (this, other) in self
|
for (this, other) in self
|
||||||
.vertices
|
.vertices
|
||||||
|
@ -96,10 +96,9 @@ impl FaceBuilder for PartialFace {
|
|||||||
for half_edge in &mut self.exterior.write().half_edges {
|
for half_edge in &mut self.exterior.write().half_edges {
|
||||||
let mut half_edge = half_edge.write();
|
let mut half_edge = half_edge.write();
|
||||||
|
|
||||||
let mut curve = half_edge.curve.clone();
|
let mut curve = half_edge.curve;
|
||||||
let mut curve = curve.write();
|
|
||||||
|
|
||||||
if let Some(path) = &mut curve.path {
|
if let Some(path) = &mut curve {
|
||||||
match path {
|
match path {
|
||||||
MaybeSurfacePath::Defined(_) => {
|
MaybeSurfacePath::Defined(_) => {
|
||||||
// Path is already defined. Nothing to infer.
|
// Path is already defined. Nothing to infer.
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
//! API for building objects
|
//! API for building objects
|
||||||
|
|
||||||
// These are new-style builders that build on top of the partial object API.
|
// These are new-style builders that build on top of the partial object API.
|
||||||
mod curve;
|
|
||||||
mod cycle;
|
mod cycle;
|
||||||
mod edge;
|
mod edge;
|
||||||
mod face;
|
mod face;
|
||||||
@ -14,7 +13,6 @@ mod vertex;
|
|||||||
use std::array;
|
use std::array;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
curve::CurveBuilder,
|
|
||||||
cycle::CycleBuilder,
|
cycle::CycleBuilder,
|
||||||
edge::{GlobalEdgeBuilder, HalfEdgeBuilder},
|
edge::{GlobalEdgeBuilder, HalfEdgeBuilder},
|
||||||
face::FaceBuilder,
|
face::FaceBuilder,
|
||||||
|
@ -1,21 +1,6 @@
|
|||||||
//! Paths through 2D and 3D space
|
//! Paths through 2D and 3D space
|
||||||
//!
|
//!
|
||||||
//! See [`SurfacePath`] and [`GlobalPath`].
|
//! See [`SurfacePath`] and [`GlobalPath`].
|
||||||
//!
|
|
||||||
//! # Implementation Note
|
|
||||||
//!
|
|
||||||
//! This is a bit of an in-between module. It is closely associated with
|
|
||||||
//! [`Curve`] and [`Surface`]s, but paths are not really objects themselves, as
|
|
||||||
//! logically speaking, they are owned and not referenced.
|
|
||||||
//!
|
|
||||||
//! On the other hand, the types in this module don't follow the general style
|
|
||||||
//! of types in `fj-math`.
|
|
||||||
//!
|
|
||||||
//! We'll see how it shakes out. Maybe it will stay its own thing, maybe it will
|
|
||||||
//! move to `fj-math`, maybe something else entirely will happen.
|
|
||||||
//!
|
|
||||||
//! [`Curve`]: crate::objects::Curve
|
|
||||||
//! [`Surface`]: crate::objects::Surface
|
|
||||||
|
|
||||||
use fj_math::{Circle, Line, Point, Scalar, Transform, Vector};
|
use fj_math::{Circle, Line, Point, Scalar, Transform, Vector};
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
objects::{
|
objects::{
|
||||||
Curve, Cycle, Face, GlobalEdge, GlobalVertex, HalfEdge, Objects, Shell,
|
Cycle, Face, GlobalEdge, GlobalVertex, HalfEdge, Objects, Shell,
|
||||||
Sketch, Solid, Surface, SurfaceVertex,
|
Sketch, Solid, Surface, SurfaceVertex,
|
||||||
},
|
},
|
||||||
services::{Service, ServiceObjectsExt},
|
services::{Service, ServiceObjectsExt},
|
||||||
@ -34,7 +34,6 @@ macro_rules! impl_insert {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl_insert!(
|
impl_insert!(
|
||||||
Curve, curves;
|
|
||||||
Cycle, cycles;
|
Cycle, cycles;
|
||||||
Face, faces;
|
Face, faces;
|
||||||
GlobalEdge, global_edges;
|
GlobalEdge, global_edges;
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
use crate::geometry::path::SurfacePath;
|
|
||||||
|
|
||||||
/// A curve, defined in local surface coordinates
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
|
||||||
pub struct Curve {
|
|
||||||
path: SurfacePath,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Curve {
|
|
||||||
/// Construct a new instance of `Curve`
|
|
||||||
pub fn new(path: SurfacePath) -> Self {
|
|
||||||
Self { path }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Access the path that defines the curve
|
|
||||||
pub fn path(&self) -> SurfacePath {
|
|
||||||
self.path
|
|
||||||
}
|
|
||||||
}
|
|
@ -55,7 +55,7 @@ impl Cycle {
|
|||||||
let [a, b] = first.boundary();
|
let [a, b] = first.boundary();
|
||||||
let edge_direction_positive = a < b;
|
let edge_direction_positive = a < b;
|
||||||
|
|
||||||
let circle = match first.curve().path() {
|
let circle = match first.curve() {
|
||||||
SurfacePath::Circle(circle) => circle,
|
SurfacePath::Circle(circle) => circle,
|
||||||
SurfacePath::Line(_) => unreachable!(
|
SurfacePath::Line(_) => unreachable!(
|
||||||
"Invalid cycle: less than 3 edges, but not all are circles"
|
"Invalid cycle: less than 3 edges, but not all are circles"
|
||||||
|
@ -2,7 +2,8 @@ use fj_interop::ext::ArrayExt;
|
|||||||
use fj_math::Point;
|
use fj_math::Point;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
objects::{Curve, GlobalVertex, SurfaceVertex},
|
geometry::path::SurfacePath,
|
||||||
|
objects::{GlobalVertex, SurfaceVertex},
|
||||||
storage::Handle,
|
storage::Handle,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -44,7 +45,7 @@ use crate::{
|
|||||||
/// <https://github.com/hannobraun/Fornjot/issues/1608>
|
/// <https://github.com/hannobraun/Fornjot/issues/1608>
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||||
pub struct HalfEdge {
|
pub struct HalfEdge {
|
||||||
curve: Handle<Curve>,
|
curve: SurfacePath,
|
||||||
boundary: [(Point<1>, Handle<SurfaceVertex>); 2],
|
boundary: [(Point<1>, Handle<SurfaceVertex>); 2],
|
||||||
global_form: Handle<GlobalEdge>,
|
global_form: Handle<GlobalEdge>,
|
||||||
}
|
}
|
||||||
@ -52,7 +53,7 @@ pub struct HalfEdge {
|
|||||||
impl HalfEdge {
|
impl HalfEdge {
|
||||||
/// Create an instance of `HalfEdge`
|
/// Create an instance of `HalfEdge`
|
||||||
pub fn new(
|
pub fn new(
|
||||||
curve: Handle<Curve>,
|
curve: SurfacePath,
|
||||||
boundary: [(Point<1>, Handle<SurfaceVertex>); 2],
|
boundary: [(Point<1>, Handle<SurfaceVertex>); 2],
|
||||||
global_form: Handle<GlobalEdge>,
|
global_form: Handle<GlobalEdge>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@ -64,8 +65,8 @@ impl HalfEdge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Access the curve that defines the half-edge's geometry
|
/// Access the curve that defines the half-edge's geometry
|
||||||
pub fn curve(&self) -> &Handle<Curve> {
|
pub fn curve(&self) -> SurfacePath {
|
||||||
&self.curve
|
self.curve
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access the boundary points of the half-edge on the curve
|
/// Access the boundary points of the half-edge on the curve
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
pub mod curve;
|
|
||||||
pub mod cycle;
|
pub mod cycle;
|
||||||
pub mod edge;
|
pub mod edge;
|
||||||
pub mod face;
|
pub mod face;
|
||||||
|
@ -79,7 +79,6 @@ mod stores;
|
|||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
full::{
|
full::{
|
||||||
curve::Curve,
|
|
||||||
cycle::{Cycle, HalfEdgesOfCycle},
|
cycle::{Cycle, HalfEdgesOfCycle},
|
||||||
edge::{GlobalEdge, HalfEdge, VerticesInNormalizedOrder},
|
edge::{GlobalEdge, HalfEdge, VerticesInNormalizedOrder},
|
||||||
face::{Face, FaceSet, Handedness},
|
face::{Face, FaceSet, Handedness},
|
||||||
|
@ -2,7 +2,7 @@ use std::any::Any;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
objects::{
|
objects::{
|
||||||
Curve, Cycle, Face, GlobalEdge, GlobalVertex, HalfEdge, Objects, Shell,
|
Cycle, Face, GlobalEdge, GlobalVertex, HalfEdge, Objects, Shell,
|
||||||
Sketch, Solid, Surface, SurfaceVertex,
|
Sketch, Solid, Surface, SurfaceVertex,
|
||||||
},
|
},
|
||||||
storage::{Handle, ObjectId},
|
storage::{Handle, ObjectId},
|
||||||
@ -108,7 +108,6 @@ macro_rules! object {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object!(
|
object!(
|
||||||
Curve, "curve", curves;
|
|
||||||
Cycle, "cycle", cycles;
|
Cycle, "cycle", cycles;
|
||||||
Face, "face", faces;
|
Face, "face", faces;
|
||||||
GlobalEdge, "global edge", global_edges;
|
GlobalEdge, "global edge", global_edges;
|
||||||
|
@ -6,16 +6,13 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
Curve, Cycle, Face, GlobalEdge, GlobalVertex, HalfEdge, Shell, Sketch,
|
Cycle, Face, GlobalEdge, GlobalVertex, HalfEdge, Shell, Sketch, Solid,
|
||||||
Solid, Surface, SurfaceVertex,
|
Surface, SurfaceVertex,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The available object stores
|
/// The available object stores
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Objects {
|
pub struct Objects {
|
||||||
/// Store for [`Curve`]s
|
|
||||||
pub curves: Store<Curve>,
|
|
||||||
|
|
||||||
/// Store for [`Cycle`]s
|
/// Store for [`Cycle`]s
|
||||||
pub cycles: Store<Cycle>,
|
pub cycles: Store<Cycle>,
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ mod wrapper;
|
|||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
objects::{
|
objects::{
|
||||||
curve::{MaybeSurfacePath, PartialCurve},
|
curve::MaybeSurfacePath,
|
||||||
cycle::PartialCycle,
|
cycle::PartialCycle,
|
||||||
edge::{PartialGlobalEdge, PartialHalfEdge},
|
edge::{PartialGlobalEdge, PartialHalfEdge},
|
||||||
face::PartialFace,
|
face::PartialFace,
|
||||||
|
@ -1,43 +1,8 @@
|
|||||||
use fj_math::Scalar;
|
use fj_math::Scalar;
|
||||||
|
|
||||||
use crate::{
|
use crate::geometry::path::SurfacePath;
|
||||||
geometry::path::SurfacePath,
|
|
||||||
objects::{Curve, Objects},
|
|
||||||
partial::{FullToPartialCache, PartialObject},
|
|
||||||
services::Service,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A partial [`Curve`]
|
/// The definition of a surface path within a partial object
|
||||||
#[derive(Clone, Debug, Default)]
|
|
||||||
pub struct PartialCurve {
|
|
||||||
/// The path that defines the curve
|
|
||||||
pub path: Option<MaybeSurfacePath>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialObject for PartialCurve {
|
|
||||||
type Full = Curve;
|
|
||||||
|
|
||||||
fn from_full(curve: &Self::Full, _: &mut FullToPartialCache) -> Self {
|
|
||||||
Self {
|
|
||||||
path: Some(curve.path().into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build(self, _: &mut Service<Objects>) -> Self::Full {
|
|
||||||
let path = match self.path.expect("Need path to build curve") {
|
|
||||||
MaybeSurfacePath::Defined(path) => path,
|
|
||||||
undefined => {
|
|
||||||
panic!(
|
|
||||||
"Trying to build curve with undefined path: {undefined:?}"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Curve::new(path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The definition of a surface path within [`PartialCurve`]
|
|
||||||
///
|
///
|
||||||
/// Can be a fully defined [`SurfacePath`], or just the type of path might be
|
/// Can be a fully defined [`SurfacePath`], or just the type of path might be
|
||||||
/// known.
|
/// known.
|
||||||
|
@ -4,10 +4,8 @@ use fj_interop::ext::ArrayExt;
|
|||||||
use fj_math::Point;
|
use fj_math::Point;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
objects::{
|
objects::{GlobalEdge, GlobalVertex, HalfEdge, Objects, SurfaceVertex},
|
||||||
Curve, GlobalEdge, GlobalVertex, HalfEdge, Objects, SurfaceVertex,
|
partial::{FullToPartialCache, MaybeSurfacePath, Partial, PartialObject},
|
||||||
},
|
|
||||||
partial::{FullToPartialCache, Partial, PartialObject},
|
|
||||||
services::Service,
|
services::Service,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -15,7 +13,7 @@ use crate::{
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct PartialHalfEdge {
|
pub struct PartialHalfEdge {
|
||||||
/// The curve that the half-edge is defined in
|
/// The curve that the half-edge is defined in
|
||||||
pub curve: Partial<Curve>,
|
pub curve: Option<MaybeSurfacePath>,
|
||||||
|
|
||||||
/// The vertices that bound the half-edge on the curve
|
/// The vertices that bound the half-edge on the curve
|
||||||
pub vertices: [(Option<Point<1>>, Partial<SurfaceVertex>); 2],
|
pub vertices: [(Option<Point<1>>, Partial<SurfaceVertex>); 2],
|
||||||
@ -32,7 +30,7 @@ impl PartialObject for PartialHalfEdge {
|
|||||||
cache: &mut FullToPartialCache,
|
cache: &mut FullToPartialCache,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
curve: Partial::from_full(half_edge.curve().clone(), cache),
|
curve: Some(half_edge.curve().into()),
|
||||||
vertices: half_edge
|
vertices: half_edge
|
||||||
.boundary()
|
.boundary()
|
||||||
.zip_ext(half_edge.surface_vertices())
|
.zip_ext(half_edge.surface_vertices())
|
||||||
@ -50,7 +48,14 @@ impl PartialObject for PartialHalfEdge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn build(self, objects: &mut Service<Objects>) -> Self::Full {
|
fn build(self, objects: &mut Service<Objects>) -> Self::Full {
|
||||||
let curve = self.curve.build(objects);
|
let curve = match self.curve.expect("Need path to build curve") {
|
||||||
|
MaybeSurfacePath::Defined(path) => path,
|
||||||
|
undefined => {
|
||||||
|
panic!(
|
||||||
|
"Trying to build curve with undefined path: {undefined:?}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
let vertices = self.vertices.map(|vertex| {
|
let vertices = self.vertices.map(|vertex| {
|
||||||
let position_curve = vertex
|
let position_curve = vertex
|
||||||
.0
|
.0
|
||||||
@ -67,7 +72,7 @@ impl PartialObject for PartialHalfEdge {
|
|||||||
|
|
||||||
impl Default for PartialHalfEdge {
|
impl Default for PartialHalfEdge {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let curve = Partial::<Curve>::new();
|
let curve = None;
|
||||||
let vertices = array::from_fn(|_| {
|
let vertices = array::from_fn(|_| {
|
||||||
let surface_form = Partial::default();
|
let surface_form = Partial::default();
|
||||||
(None, surface_form)
|
(None, surface_form)
|
||||||
|
@ -33,7 +33,6 @@ macro_rules! impl_trait {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl_trait!(
|
impl_trait!(
|
||||||
Curve, PartialCurve;
|
|
||||||
Cycle, PartialCycle;
|
Cycle, PartialCycle;
|
||||||
Face, PartialFace;
|
Face, PartialFace;
|
||||||
GlobalEdge, PartialGlobalEdge;
|
GlobalEdge, PartialGlobalEdge;
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
use crate::objects::Curve;
|
|
||||||
|
|
||||||
use super::{Validate, ValidationConfig, ValidationError};
|
|
||||||
|
|
||||||
impl Validate for Curve {
|
|
||||||
fn validate_with_config(
|
|
||||||
&self,
|
|
||||||
_: &ValidationConfig,
|
|
||||||
_: &mut Vec<ValidationError>,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
}
|
|
@ -101,10 +101,8 @@ impl CycleValidationError {
|
|||||||
.boundary()
|
.boundary()
|
||||||
.zip_ext([half_edge.start_vertex(), next.start_vertex()]);
|
.zip_ext([half_edge.start_vertex(), next.start_vertex()]);
|
||||||
for (position_on_curve, surface_vertex) in boundary_and_vertices {
|
for (position_on_curve, surface_vertex) in boundary_and_vertices {
|
||||||
let curve_position_on_surface = half_edge
|
let curve_position_on_surface =
|
||||||
.curve()
|
half_edge.curve().point_from_path_coords(position_on_curve);
|
||||||
.path()
|
|
||||||
.point_from_path_coords(position_on_curve);
|
|
||||||
let surface_position_from_vertex = surface_vertex.position();
|
let surface_position_from_vertex = surface_vertex.position();
|
||||||
|
|
||||||
let distance = curve_position_on_surface
|
let distance = curve_position_on_surface
|
||||||
|
@ -184,7 +184,7 @@ mod tests {
|
|||||||
.boundary()
|
.boundary()
|
||||||
.zip_ext(valid.surface_vertices().map(Clone::clone));
|
.zip_ext(valid.surface_vertices().map(Clone::clone));
|
||||||
|
|
||||||
HalfEdge::new(valid.curve().clone(), vertices, global_form)
|
HalfEdge::new(valid.curve(), vertices, global_form)
|
||||||
};
|
};
|
||||||
|
|
||||||
valid.validate_and_return_first_error()?;
|
valid.validate_and_return_first_error()?;
|
||||||
@ -211,11 +211,7 @@ mod tests {
|
|||||||
(Point::from([0.]), surface_vertex.clone())
|
(Point::from([0.]), surface_vertex.clone())
|
||||||
});
|
});
|
||||||
|
|
||||||
HalfEdge::new(
|
HalfEdge::new(valid.curve(), vertices, valid.global_form().clone())
|
||||||
valid.curve().clone(),
|
|
||||||
vertices,
|
|
||||||
valid.global_form().clone(),
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
valid.validate_and_return_first_error()?;
|
valid.validate_and_return_first_error()?;
|
||||||
|
@ -291,7 +291,7 @@ mod tests {
|
|||||||
let boundary = boundary.zip_ext(surface_vertices);
|
let boundary = boundary.zip_ext(surface_vertices);
|
||||||
|
|
||||||
HalfEdge::new(
|
HalfEdge::new(
|
||||||
half_edge.curve().clone(),
|
half_edge.curve(),
|
||||||
boundary,
|
boundary,
|
||||||
half_edge.global_form().clone(),
|
half_edge.global_form().clone(),
|
||||||
)
|
)
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
//! Infrastructure for validating objects
|
//! Infrastructure for validating objects
|
||||||
|
|
||||||
mod curve;
|
|
||||||
mod cycle;
|
mod cycle;
|
||||||
mod edge;
|
mod edge;
|
||||||
mod face;
|
mod face;
|
||||||
|
Loading…
Reference in New Issue
Block a user