bspy 4.4__tar.gz → 5.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {bspy-4.4 → bspy-5.0.0}/PKG-INFO +8 -2
- {bspy-4.4 → bspy-5.0.0}/README.md +7 -1
- {bspy-4.4 → bspy-5.0.0}/bspy/_spline_domain.py +36 -5
- {bspy-4.4 → bspy-5.0.0}/bspy/_spline_evaluation.py +1 -1
- {bspy-4.4 → bspy-5.0.0}/bspy/_spline_fitting.py +6 -2
- {bspy-4.4 → bspy-5.0.0}/bspy/_spline_intersection.py +85 -84
- {bspy-4.4 → bspy-5.0.0}/bspy/_spline_milling.py +111 -56
- {bspy-4.4 → bspy-5.0.0}/bspy/hyperplane.py +35 -35
- {bspy-4.4 → bspy-5.0.0}/bspy/manifold.py +40 -40
- {bspy-4.4 → bspy-5.0.0}/bspy/solid.py +162 -159
- {bspy-4.4 → bspy-5.0.0}/bspy/spline.py +49 -30
- {bspy-4.4 → bspy-5.0.0}/bspy/splineOpenGLFrame.py +7 -7
- {bspy-4.4 → bspy-5.0.0}/bspy/viewer.py +3 -3
- {bspy-4.4 → bspy-5.0.0}/bspy.egg-info/PKG-INFO +8 -2
- {bspy-4.4 → bspy-5.0.0}/setup.cfg +1 -1
- {bspy-4.4 → bspy-5.0.0}/LICENSE +0 -0
- {bspy-4.4 → bspy-5.0.0}/bspy/__init__.py +0 -0
- {bspy-4.4 → bspy-5.0.0}/bspy/_spline_operations.py +0 -0
- {bspy-4.4 → bspy-5.0.0}/bspy/spline_block.py +0 -0
- {bspy-4.4 → bspy-5.0.0}/bspy.egg-info/SOURCES.txt +0 -0
- {bspy-4.4 → bspy-5.0.0}/bspy.egg-info/dependency_links.txt +0 -0
- {bspy-4.4 → bspy-5.0.0}/bspy.egg-info/requires.txt +0 -0
- {bspy-4.4 → bspy-5.0.0}/bspy.egg-info/top_level.txt +0 -0
- {bspy-4.4 → bspy-5.0.0}/pyproject.toml +0 -0
{bspy-4.4 → bspy-5.0.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bspy
|
|
3
|
-
Version:
|
|
3
|
+
Version: 5.0.0
|
|
4
4
|
Summary: Library for manipulating and rendering non-uniform B-splines
|
|
5
5
|
Home-page: http://github.com/ericbrec/BSpy
|
|
6
6
|
Author: Eric Brechner
|
|
@@ -50,7 +50,7 @@ The [Hyperplane](https://ericbrec.github.io/BSpy/bspy/hyperplane.html) class has
|
|
|
50
50
|
|
|
51
51
|
The [Manifold](https://ericbrec.github.io/BSpy/bspy/manifold.html) abstract base class for [Hyperplane](https://ericbrec.github.io/BSpy/bspy/hyperplane.html) and [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html).
|
|
52
52
|
|
|
53
|
-
The [Solid](https://ericbrec.github.io/BSpy/bspy/solid.html) class has methods to construct n-dimensional solids from trimmed [Manifold](https://ericbrec.github.io/BSpy/bspy/manifold.html) boundaries. Each solid consists of a list of boundaries and a Boolean value that indicates if the solid contains infinity. Each [Boundary](https://ericbrec.github.io/BSpy/bspy/solid.html) consists of a manifold (currently a [Hyperplane](https://ericbrec.github.io/BSpy/bspy/hyperplane.html) or [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html)) and a domain solid that trims the manifold. Solids have methods to form the intersection, union, difference, and complement of solids. There are methods to compute point containment, winding numbers, surface integrals, and volume integrals. There are also methods to translate, transform, and
|
|
53
|
+
The [Solid](https://ericbrec.github.io/BSpy/bspy/solid.html) class has methods to construct n-dimensional solids from trimmed [Manifold](https://ericbrec.github.io/BSpy/bspy/manifold.html) boundaries. Each solid consists of a list of boundaries and a Boolean value that indicates if the solid contains infinity. Each [Boundary](https://ericbrec.github.io/BSpy/bspy/solid.html) consists of a manifold (currently a [Hyperplane](https://ericbrec.github.io/BSpy/bspy/hyperplane.html) or [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html)) and a domain solid that trims the manifold. Solids have methods to form the intersection, union, difference, and complement of solids. There are methods to compute point containment, winding numbers, surface integrals, and volume integrals. There are also methods to translate, transform, and compute cutouts of solids. Solids can be saved and loaded in json format.
|
|
54
54
|
|
|
55
55
|
The [SplineOpenGLFrame](https://ericbrec.github.io/BSpy/bspy/splineOpenGLFrame.html) class is an
|
|
56
56
|
[OpenGLFrame](https://pypi.org/project/pyopengltk/) with custom shaders to render spline curves, surfaces, and solids. Spline surfaces with more
|
|
@@ -86,3 +86,9 @@ a set of examples, including a jupyter notebook, can be found [here](https://git
|
|
|
86
86
|
* Moved DrawableSpine methods for adjusting spline appearance to Viewer (see documentation for details)
|
|
87
87
|
* Spline.bspline_values changed arguments (see documentation for details)
|
|
88
88
|
* Spline.intersect changed return values (see documentation for details)
|
|
89
|
+
|
|
90
|
+
### Release 5.0 breaking changes
|
|
91
|
+
* Renamed Boundary.domain member to Boundary.trim
|
|
92
|
+
* Renamed Solid.slice method to Solid.compute_cutout
|
|
93
|
+
* Renamed Manifold.complete_slice method to Manifold.complete_cutout (also applies to Hyperplane and Spline classes)
|
|
94
|
+
* Renamed Manifold.flip_normal method to Manifold.negate_normal (also applies to Hyperplane and Spline classes)
|
|
@@ -18,7 +18,7 @@ The [Hyperplane](https://ericbrec.github.io/BSpy/bspy/hyperplane.html) class has
|
|
|
18
18
|
|
|
19
19
|
The [Manifold](https://ericbrec.github.io/BSpy/bspy/manifold.html) abstract base class for [Hyperplane](https://ericbrec.github.io/BSpy/bspy/hyperplane.html) and [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html).
|
|
20
20
|
|
|
21
|
-
The [Solid](https://ericbrec.github.io/BSpy/bspy/solid.html) class has methods to construct n-dimensional solids from trimmed [Manifold](https://ericbrec.github.io/BSpy/bspy/manifold.html) boundaries. Each solid consists of a list of boundaries and a Boolean value that indicates if the solid contains infinity. Each [Boundary](https://ericbrec.github.io/BSpy/bspy/solid.html) consists of a manifold (currently a [Hyperplane](https://ericbrec.github.io/BSpy/bspy/hyperplane.html) or [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html)) and a domain solid that trims the manifold. Solids have methods to form the intersection, union, difference, and complement of solids. There are methods to compute point containment, winding numbers, surface integrals, and volume integrals. There are also methods to translate, transform, and
|
|
21
|
+
The [Solid](https://ericbrec.github.io/BSpy/bspy/solid.html) class has methods to construct n-dimensional solids from trimmed [Manifold](https://ericbrec.github.io/BSpy/bspy/manifold.html) boundaries. Each solid consists of a list of boundaries and a Boolean value that indicates if the solid contains infinity. Each [Boundary](https://ericbrec.github.io/BSpy/bspy/solid.html) consists of a manifold (currently a [Hyperplane](https://ericbrec.github.io/BSpy/bspy/hyperplane.html) or [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html)) and a domain solid that trims the manifold. Solids have methods to form the intersection, union, difference, and complement of solids. There are methods to compute point containment, winding numbers, surface integrals, and volume integrals. There are also methods to translate, transform, and compute cutouts of solids. Solids can be saved and loaded in json format.
|
|
22
22
|
|
|
23
23
|
The [SplineOpenGLFrame](https://ericbrec.github.io/BSpy/bspy/splineOpenGLFrame.html) class is an
|
|
24
24
|
[OpenGLFrame](https://pypi.org/project/pyopengltk/) with custom shaders to render spline curves, surfaces, and solids. Spline surfaces with more
|
|
@@ -54,3 +54,9 @@ a set of examples, including a jupyter notebook, can be found [here](https://git
|
|
|
54
54
|
* Moved DrawableSpine methods for adjusting spline appearance to Viewer (see documentation for details)
|
|
55
55
|
* Spline.bspline_values changed arguments (see documentation for details)
|
|
56
56
|
* Spline.intersect changed return values (see documentation for details)
|
|
57
|
+
|
|
58
|
+
### Release 5.0 breaking changes
|
|
59
|
+
* Renamed Boundary.domain member to Boundary.trim
|
|
60
|
+
* Renamed Solid.slice method to Solid.compute_cutout
|
|
61
|
+
* Renamed Manifold.complete_slice method to Manifold.complete_cutout (also applies to Hyperplane and Spline classes)
|
|
62
|
+
* Renamed Manifold.flip_normal method to Manifold.negate_normal (also applies to Hyperplane and Spline classes)
|
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
import numpy as np
|
|
2
|
+
import bspy
|
|
2
3
|
from bspy.manifold import Manifold
|
|
3
4
|
|
|
5
|
+
def arc_length_map(self, tolerance):
|
|
6
|
+
if self.nInd != 1: raise ValueError("Spline doesn't have exactly one independent variable")
|
|
7
|
+
|
|
8
|
+
# Compute the length of the spline
|
|
9
|
+
curveLength = self.integral()
|
|
10
|
+
domain = self.domain()[0]
|
|
11
|
+
guess = bspy.Spline.line([0.0], [1.0])
|
|
12
|
+
guess = guess.elevate([4])
|
|
13
|
+
|
|
14
|
+
# Solve the ODE
|
|
15
|
+
def arcLengthF(t, uData):
|
|
16
|
+
uValue = (1.0 - uData[0][0]) * domain[0] + uData[0][0] * domain[1]
|
|
17
|
+
uValue = np.clip(uValue, domain[0], domain[1])
|
|
18
|
+
d1 = self.derivative([1], [uValue])
|
|
19
|
+
d2 = self.derivative([2], [uValue])
|
|
20
|
+
speed = np.sqrt(d1 @ d1)
|
|
21
|
+
d1d2 = d1 @ d2
|
|
22
|
+
return np.array([curveLength / speed]), np.array([-curveLength * d1d2 / speed ** 3]).reshape((1, 1, 1))
|
|
23
|
+
arcLengthMap = guess.solve_ode(1, 0, arcLengthF, tolerance)
|
|
24
|
+
|
|
25
|
+
# Adjust range to match domain
|
|
26
|
+
|
|
27
|
+
arcLengthMap *= (domain[1] - domain[0]) / arcLengthMap(1.0)[0]
|
|
28
|
+
arcLengthMap += domain[0]
|
|
29
|
+
arcLengthMap.coefs[0][-1] = domain[1]
|
|
30
|
+
return arcLengthMap
|
|
31
|
+
|
|
4
32
|
def clamp(self, left, right):
|
|
5
33
|
bounds = [[None, None] for i in range(self.nInd)]
|
|
6
34
|
|
|
@@ -404,12 +432,11 @@ def join(splineList):
|
|
|
404
432
|
splDomain = spl.domain()[0]
|
|
405
433
|
start2 = spl(splDomain[0])
|
|
406
434
|
end2 = spl(splDomain[1])
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
if minDist == gaps[0] or minDist == gaps[1]:
|
|
410
|
-
workingSpline = workingSpline.reverse()
|
|
411
|
-
if minDist == gaps[1] or minDist == gaps[3]:
|
|
435
|
+
ixMin = np.argmin([np.linalg.norm(vecDiff) for vecDiff in [end1 - start2, end1 - end2, start1 - start2, start1 - end2]])
|
|
436
|
+
if ixMin == 1 or ixMin == 3:
|
|
412
437
|
spl = spl.reverse()
|
|
438
|
+
if ixMin == 2 or ixMin == 3:
|
|
439
|
+
workingSpline = workingSpline.reverse()
|
|
413
440
|
maxOrder = max(workingSpline.order[0], spl.order[0])
|
|
414
441
|
workingSpline = workingSpline.elevate([maxOrder - workingSpline.order[0]])
|
|
415
442
|
spl = spl.elevate([maxOrder - spl.order[0]])
|
|
@@ -610,6 +637,8 @@ def trim(self, newDomain):
|
|
|
610
637
|
if multiplicity > 0:
|
|
611
638
|
newKnots.append((bounds[0], multiplicity))
|
|
612
639
|
noChange = False
|
|
640
|
+
if bounds[0] != knots[order - 1]:
|
|
641
|
+
noChange = False
|
|
613
642
|
|
|
614
643
|
if not np.isnan(bounds[1]):
|
|
615
644
|
if not(knots[order - 1] <= bounds[1] <= knots[-order]): raise ValueError("Invalid newDomain")
|
|
@@ -627,6 +656,8 @@ def trim(self, newDomain):
|
|
|
627
656
|
if multiplicity > 0:
|
|
628
657
|
newKnots.append((bounds[1], multiplicity))
|
|
629
658
|
noChange = False
|
|
659
|
+
if bounds[1] != knots[-order]:
|
|
660
|
+
noChange = False
|
|
630
661
|
|
|
631
662
|
newKnotsList.append(newKnots)
|
|
632
663
|
|
|
@@ -229,7 +229,7 @@ def normal(self, uvw, normalize=True, indices=None):
|
|
|
229
229
|
nDep = self.nDep
|
|
230
230
|
|
|
231
231
|
# Compute the normal using cofactors (determinants of subsets of the tangent space).
|
|
232
|
-
sign = -1 if hasattr(self, "metadata") and self.metadata.get("
|
|
232
|
+
sign = -1 if hasattr(self, "metadata") and self.metadata.get("negateNormal", False) else 1
|
|
233
233
|
dtype = self.coefs.dtype if hasattr(self, "coefs") else self.coefsDtype
|
|
234
234
|
if indices is None:
|
|
235
235
|
indices = range(nDep)
|
|
@@ -339,6 +339,7 @@ def fit(domain, f, order = None, knots = None, tolerance = 1.0e-4):
|
|
|
339
339
|
nInd = len(domain)
|
|
340
340
|
midPoint = f(0.5 * (domain.T[0] + domain.T[1]))
|
|
341
341
|
if not type(midPoint) is bspy.Spline:
|
|
342
|
+
midPoint = np.array(midPoint).flatten()
|
|
342
343
|
nDep = len(midPoint)
|
|
343
344
|
|
|
344
345
|
# Make sure order and knots conform to this
|
|
@@ -389,7 +390,10 @@ def fit(domain, f, order = None, knots = None, tolerance = 1.0e-4):
|
|
|
389
390
|
# Create a tuple for the u value (must be a tuple to use it as a dictionary key)
|
|
390
391
|
uValue = tuple([uvw[i][indices[i]] for i in range(nInd)])
|
|
391
392
|
if not uValue in fDictionary:
|
|
392
|
-
|
|
393
|
+
newValue = f(np.array(uValue))
|
|
394
|
+
if not type(newValue) is bspy.Spline:
|
|
395
|
+
newValue = np.array(newValue).flatten()
|
|
396
|
+
fDictionary[uValue] = newValue
|
|
393
397
|
fValues.append(fDictionary[uValue])
|
|
394
398
|
iLast = nInd - 1
|
|
395
399
|
while iLast >= 0:
|
|
@@ -1067,7 +1071,7 @@ def solve_ode(self, nLeft, nRight, FAndF_u, tolerance = 1.0e-6, args = (), inclu
|
|
|
1067
1071
|
|
|
1068
1072
|
# Is it time to give up?
|
|
1069
1073
|
|
|
1070
|
-
if (not done or continuation < 1.0) and n >
|
|
1074
|
+
if (not done or continuation < 1.0) and n > 10000:
|
|
1071
1075
|
raise RuntimeError("Can't find solution with given initial guess")
|
|
1072
1076
|
|
|
1073
1077
|
# Estimate the error
|
|
@@ -174,7 +174,6 @@ def _intersect_convex_hull_with_x_interval(lowerHull, upperHull, epsilon, xInter
|
|
|
174
174
|
for p1 in hull[1:]:
|
|
175
175
|
yDelta = p0[1] - p1[1]
|
|
176
176
|
if p0[1] * p1[1] <= 0.0 and yDelta != 0.0:
|
|
177
|
-
yDelta = p0[1] - p1[1]
|
|
178
177
|
alpha = p0[1] / yDelta
|
|
179
178
|
xNew = p0[0] * (1.0 - alpha) + p1[0] * alpha
|
|
180
179
|
if sign * yDelta > 0.0:
|
|
@@ -255,14 +254,13 @@ def _refine_projected_polyhedron(interval):
|
|
|
255
254
|
|
|
256
255
|
# Compute the coefficients for f(x) = x for the independent variable and its knots.
|
|
257
256
|
xData = spline.greville(ind)
|
|
258
|
-
|
|
257
|
+
if len(xData) == 1:
|
|
258
|
+
xData = spline.domain()[ind]
|
|
259
|
+
|
|
259
260
|
# Loop through each dependent variable in this row to refine the interval containing the root for this independent variable.
|
|
260
|
-
for yData, ySplineBounds, yBounds in zip(coefs, spline.range_bounds(),
|
|
261
|
-
interval.bounds[nDep:nDep + spline.nDep]):
|
|
261
|
+
for yData, ySplineBounds, yBounds in zip(coefs, spline.range_bounds(), interval.bounds[nDep:nDep + spline.nDep]):
|
|
262
262
|
# Compute the 2D convex hull of the knot coefficients and the spline's coefficients
|
|
263
263
|
lowerHull, upperHull = _convex_hull_2D(xData, yData.ravel(), yBounds, yBounds - ySplineBounds)
|
|
264
|
-
if lowerHull is None or upperHull is None:
|
|
265
|
-
return roots, intervals
|
|
266
264
|
|
|
267
265
|
# Intersect the convex hull with the xInterval along the x axis (the knot coefficients axis).
|
|
268
266
|
xInterval = _intersect_convex_hull_with_x_interval(lowerHull, upperHull, epsilon, xInterval)
|
|
@@ -437,7 +435,7 @@ def zeros_using_projected_polyhedron(self, epsilon=None, initialScale=None):
|
|
|
437
435
|
return roots
|
|
438
436
|
|
|
439
437
|
def _turning_point_determinant(self, uvw, cosTheta, sinTheta):
|
|
440
|
-
sign = -1 if hasattr(self, "metadata") and self.metadata.get("
|
|
438
|
+
sign = -1 if hasattr(self, "metadata") and self.metadata.get("negateNormal", False) else 1
|
|
441
439
|
tangentSpace = self.jacobian(uvw).T
|
|
442
440
|
return cosTheta * sign * np.linalg.det(tangentSpace[[j for j in range(self.nInd) if j != 0]]) - \
|
|
443
441
|
sinTheta * sign * np.linalg.det(tangentSpace[[j for j in range(self.nInd) if j != 1]])
|
|
@@ -446,7 +444,7 @@ def _turning_point_determinant_gradient(self, uvw, cosTheta, sinTheta):
|
|
|
446
444
|
dtype = self.coefs.dtype if hasattr(self, "coefs") else self.coefsDtype
|
|
447
445
|
gradient = np.zeros(self.nInd, dtype)
|
|
448
446
|
|
|
449
|
-
sign = -1 if hasattr(self, "metadata") and self.metadata.get("
|
|
447
|
+
sign = -1 if hasattr(self, "metadata") and self.metadata.get("negateNormal", False) else 1
|
|
450
448
|
tangentSpace = self.jacobian(uvw).T
|
|
451
449
|
dTangentSpace = tangentSpace.copy()
|
|
452
450
|
|
|
@@ -700,7 +698,7 @@ def _contours_of_C1_spline_block(self, epsilon, evaluationEpsilon):
|
|
|
700
698
|
|
|
701
699
|
# Extra step not in paper.
|
|
702
700
|
# Run a checksum on the points, ensuring starting and ending points balance.
|
|
703
|
-
# Start by
|
|
701
|
+
# Start by negating endpoints as needed, since we can miss turning points near endpoints.
|
|
704
702
|
if points[0].det < 0.0:
|
|
705
703
|
point = points[0]
|
|
706
704
|
points[0] = Point(point.d, -point.det, point.onUVBoundary, point.turningPoint, point.uvw)
|
|
@@ -943,7 +941,8 @@ def contours(self):
|
|
|
943
941
|
|
|
944
942
|
def intersect(self, other):
|
|
945
943
|
intersections = []
|
|
946
|
-
|
|
944
|
+
# Compute the number of degrees of freedom of the intersection.
|
|
945
|
+
dof = self.nInd + other.domain_dimension() - self.nDep
|
|
947
946
|
|
|
948
947
|
# Spline-Hyperplane intersection.
|
|
949
948
|
if isinstance(other, Hyperplane):
|
|
@@ -953,7 +952,7 @@ def intersect(self, other):
|
|
|
953
952
|
spline = self.dot(other._normal) - np.atleast_1d(np.dot(other._normal, other._point))
|
|
954
953
|
|
|
955
954
|
# Curve-Line intersection.
|
|
956
|
-
if
|
|
955
|
+
if dof == 0:
|
|
957
956
|
# Find the intersection points and intervals.
|
|
958
957
|
zeros = spline.zeros()
|
|
959
958
|
# Convert each intersection point into a Manifold.Crossing and each intersection interval into a Manifold.Coincidence.
|
|
@@ -974,39 +973,39 @@ def intersect(self, other):
|
|
|
974
973
|
intersections.append(Manifold.Crossing(Hyperplane(1.0, zero[1] + epsilon, 0.0), Hyperplane(1.0, planeBounds[1], 0.0)))
|
|
975
974
|
|
|
976
975
|
# Now, create the coincidence.
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
976
|
+
firstPart = Solid(1, False)
|
|
977
|
+
firstPart.add_boundary(Boundary(Hyperplane(-1.0, zero[0], 0.0), Solid(0, True)))
|
|
978
|
+
firstPart.add_boundary(Boundary(Hyperplane(1.0, zero[1], 0.0), Solid(0, True)))
|
|
979
|
+
secondPart = Solid(1, False)
|
|
981
980
|
if planeBounds[0] > planeBounds[1]:
|
|
982
981
|
planeBounds = (planeBounds[1], planeBounds[0])
|
|
983
|
-
|
|
984
|
-
|
|
982
|
+
secondPart.add_boundary(Boundary(Hyperplane(-1.0, planeBounds[0], 0.0), Solid(0, True)))
|
|
983
|
+
secondPart.add_boundary(Boundary(Hyperplane(1.0, planeBounds[1], 0.0), Solid(0, True)))
|
|
985
984
|
alignment = np.dot(self.normal((zero[0],)), other._normal) # Use the first zero, since B-splines are closed on the left
|
|
986
985
|
width = zero[1] - zero[0]
|
|
987
986
|
transform = (planeBounds[1] - planeBounds[0]) / width
|
|
988
987
|
translation = (planeBounds[0] * zero[1] - planeBounds[1] * zero[0]) / width
|
|
989
|
-
intersections.append(Manifold.Coincidence(
|
|
988
|
+
intersections.append(Manifold.Coincidence(firstPart, secondPart, alignment, np.atleast_2d(transform), np.atleast_2d(1.0 / transform), np.atleast_1d(translation)))
|
|
990
989
|
else:
|
|
991
990
|
# Intersection is a point, so create a Manifold.Crossing.
|
|
992
991
|
intersections.append(Manifold.Crossing(Hyperplane(1.0, zero, 0.0), Hyperplane(1.0, projection @ (self((zero,)) - other._point), 0.0)))
|
|
993
992
|
|
|
994
993
|
# Surface-Plane intersection.
|
|
995
|
-
elif
|
|
994
|
+
elif dof == 1:
|
|
996
995
|
# Find the intersection contours, which are returned as splines.
|
|
997
996
|
contours = spline.contours()
|
|
998
997
|
# Convert each contour into a Manifold.Crossing.
|
|
999
998
|
for contour in contours:
|
|
1000
|
-
# The
|
|
1001
|
-
|
|
1002
|
-
# The
|
|
999
|
+
# The firstPart portion is the contour returned for the spline-plane intersection.
|
|
1000
|
+
firstPart = contour
|
|
1001
|
+
# The secondPart portion is the contour projected onto the plane's domain, which we compute with samples and a least squares fit.
|
|
1003
1002
|
tValues = np.linspace(0.0, 1.0, contour.nCoef[0] + 5) # Over-sample a bit to reduce the condition number and avoid singular matrix
|
|
1004
1003
|
points = []
|
|
1005
1004
|
for t in tValues:
|
|
1006
1005
|
zero = contour((t,))
|
|
1007
1006
|
points.append(projection @ (self(zero) - other._point))
|
|
1008
|
-
|
|
1009
|
-
intersections.append(Manifold.Crossing(
|
|
1007
|
+
secondPart = bspy.Spline.least_squares(tValues, np.array(points).T, contour.order, contour.knots)
|
|
1008
|
+
intersections.append(Manifold.Crossing(firstPart, secondPart))
|
|
1010
1009
|
else:
|
|
1011
1010
|
return NotImplemented
|
|
1012
1011
|
|
|
@@ -1015,8 +1014,8 @@ def intersect(self, other):
|
|
|
1015
1014
|
# Construct a spline block that represents the intersection.
|
|
1016
1015
|
block = bspy.spline_block.SplineBlock([[self, -other]])
|
|
1017
1016
|
|
|
1018
|
-
# Curve-Curve intersection.
|
|
1019
|
-
if
|
|
1017
|
+
# Zero degrees of freedom, typically a Curve-Curve intersection.
|
|
1018
|
+
if dof == 0:
|
|
1020
1019
|
# Find the intersection points and intervals.
|
|
1021
1020
|
zeros = block.zeros()
|
|
1022
1021
|
# Convert each intersection point into a Manifold.Crossing and each intersection interval into a Manifold.Coincidence.
|
|
@@ -1041,23 +1040,23 @@ def intersect(self, other):
|
|
|
1041
1040
|
intersections.append(Manifold.Crossing(Hyperplane(1.0, zero[1][0], 0.0), Hyperplane(1.0, zero[1][1] + epsilon, 0.0)))
|
|
1042
1041
|
|
|
1043
1042
|
# Now, create the coincidence.
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1043
|
+
firstPart = Solid(self.nInd, False)
|
|
1044
|
+
firstPart.add_boundary(Boundary(Hyperplane(-1.0, zero[0][0], 0.0), Solid(0, True)))
|
|
1045
|
+
firstPart.add_boundary(Boundary(Hyperplane(1.0, zero[1][0], 0.0), Solid(0, True)))
|
|
1046
|
+
secondPart = Solid(other.nInd, False)
|
|
1047
|
+
secondPart.add_boundary(Boundary(Hyperplane(-1.0, zero[0][1], 0.0), Solid(0, True)))
|
|
1048
|
+
secondPart.add_boundary(Boundary(Hyperplane(1.0, zero[1][1], 0.0), Solid(0, True)))
|
|
1050
1049
|
alignment = np.dot(self.normal(zero[0][0]), other.normal(zero[0][1])) # Use the first zeros, since B-splines are closed on the left
|
|
1051
1050
|
width = zero[1][0] - zero[0][0]
|
|
1052
1051
|
transform = (zero[1][1] - zero[0][1]) / width
|
|
1053
1052
|
translation = (zero[0][1] * zero[1][0] - zero[1][1] * zero[0][0]) / width
|
|
1054
|
-
intersections.append(Manifold.Coincidence(
|
|
1053
|
+
intersections.append(Manifold.Coincidence(firstPart, secondPart, alignment, np.atleast_2d(transform), np.atleast_2d(1.0 / transform), np.atleast_1d(translation)))
|
|
1055
1054
|
else:
|
|
1056
1055
|
# Intersection is a point, so create a Manifold.Crossing.
|
|
1057
|
-
intersections.append(Manifold.Crossing(Hyperplane(1.0, zero[:
|
|
1056
|
+
intersections.append(Manifold.Crossing(Hyperplane(1.0, zero[:self.nInd], 0.0), Hyperplane(1.0, zero[self.nInd:], 0.0)))
|
|
1058
1057
|
|
|
1059
|
-
# Surface-Surface intersection.
|
|
1060
|
-
elif
|
|
1058
|
+
# One degree of freedom, typically a Surface-Surface intersection.
|
|
1059
|
+
elif dof == 1:
|
|
1061
1060
|
if "Name" in self.metadata and "Name" in other.metadata:
|
|
1062
1061
|
logging.info(f"intersect:{self.metadata['Name']}:{other.metadata['Name']}")
|
|
1063
1062
|
# Find the intersection contours, which are returned as splines.
|
|
@@ -1074,71 +1073,73 @@ def intersect(self, other):
|
|
|
1074
1073
|
contours = block.contours()
|
|
1075
1074
|
# Convert each contour into a Manifold.Crossing, swapping the manifolds back.
|
|
1076
1075
|
for contour in contours:
|
|
1077
|
-
# Swap
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
intersections.append(Manifold.Crossing(
|
|
1076
|
+
# Swap firstPart and secondPart, compared to not swapped.
|
|
1077
|
+
firstPart = bspy.Spline(contour.nInd, self.nInd, contour.order, contour.nCoef, contour.knots, contour.coefs[other.nInd:], contour.metadata)
|
|
1078
|
+
secondPart = bspy.Spline(contour.nInd, other.nInd, contour.order, contour.nCoef, contour.knots, contour.coefs[:other.nInd], contour.metadata)
|
|
1079
|
+
intersections.append(Manifold.Crossing(firstPart, secondPart))
|
|
1081
1080
|
else:
|
|
1082
1081
|
# Convert each contour into a Manifold.Crossing.
|
|
1083
1082
|
for contour in contours:
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
intersections.append(Manifold.Crossing(
|
|
1083
|
+
firstPart = bspy.Spline(contour.nInd, self.nInd, contour.order, contour.nCoef, contour.knots, contour.coefs[:self.nInd], contour.metadata)
|
|
1084
|
+
secondPart = bspy.Spline(contour.nInd, other.nInd, contour.order, contour.nCoef, contour.knots, contour.coefs[self.nInd:], contour.metadata)
|
|
1085
|
+
intersections.append(Manifold.Crossing(firstPart, secondPart))
|
|
1087
1086
|
else:
|
|
1088
1087
|
return NotImplemented
|
|
1089
1088
|
else:
|
|
1090
1089
|
return NotImplemented
|
|
1091
1090
|
|
|
1092
|
-
#
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1091
|
+
# If self and other have normals, ensure they are pointing in the correct direction.
|
|
1092
|
+
if self.nInd + 1 == self.nDep and other.domain_dimension() + 1 == self.nDep:
|
|
1093
|
+
# Ensure the normals point outwards for both Manifolds in each crossing intersection.
|
|
1094
|
+
# Note that evaluating firstPart and secondPart at 0.5 is always valid because either they are points or curves with [0.0, 1.0] domains.
|
|
1095
|
+
domainPoint = np.atleast_1d(0.5)
|
|
1096
|
+
for i, intersection in enumerate(intersections):
|
|
1097
|
+
if isinstance(intersection, Manifold.Crossing):
|
|
1098
|
+
firstPart = intersection.firstPart
|
|
1099
|
+
secondPart = intersection.secondPart
|
|
1100
|
+
if np.dot(self.tangent_space(firstPart.evaluate(domainPoint)) @ firstPart.normal(domainPoint), other.normal(secondPart.evaluate(domainPoint))) < 0.0:
|
|
1101
|
+
firstPart = firstPart.negate_normal()
|
|
1102
|
+
if np.dot(other.tangent_space(secondPart.evaluate(domainPoint)) @ secondPart.normal(domainPoint), self.normal(firstPart.evaluate(domainPoint))) < 0.0:
|
|
1103
|
+
secondPart = secondPart.negate_normal()
|
|
1104
|
+
intersections[i] = Manifold.Crossing(firstPart, secondPart)
|
|
1104
1105
|
|
|
1105
1106
|
return intersections
|
|
1106
1107
|
|
|
1107
|
-
def
|
|
1108
|
+
def complete_cutout(self, cutout, solid):
|
|
1108
1109
|
# Spline manifold domains have finite bounds.
|
|
1109
|
-
|
|
1110
|
+
cutout.containsInfinity = False
|
|
1110
1111
|
bounds = self.domain()
|
|
1111
1112
|
|
|
1112
1113
|
# If manifold (self) has no intersections with solid, just check containment.
|
|
1113
|
-
if not
|
|
1114
|
-
if
|
|
1114
|
+
if not cutout.boundaries:
|
|
1115
|
+
if cutout.dimension == 2:
|
|
1115
1116
|
if "Name" in self.metadata:
|
|
1116
1117
|
logging.info(f"check containment:{self.metadata['Name']}")
|
|
1117
1118
|
domain = bounds.T
|
|
1118
1119
|
if solid.contains_point(self(0.5 * (domain[0] + domain[1]))):
|
|
1119
1120
|
for boundary in Hyperplane.create_hypercube(bounds).boundaries:
|
|
1120
|
-
|
|
1121
|
+
cutout.add_boundary(boundary)
|
|
1121
1122
|
return
|
|
1122
1123
|
|
|
1123
1124
|
# For curves, add domain bounds as needed.
|
|
1124
|
-
if
|
|
1125
|
-
|
|
1125
|
+
if cutout.dimension == 1:
|
|
1126
|
+
cutout.boundaries.sort(key=lambda b: (b.manifold.evaluate(0.0), b.manifold.normal(0.0)))
|
|
1126
1127
|
# First, check right end since we add new boundary to the end.
|
|
1127
|
-
if abs(
|
|
1128
|
-
|
|
1129
|
-
|
|
1128
|
+
if abs(cutout.boundaries[-1].manifold._point - bounds[0][1]) >= Manifold.minSeparation and \
|
|
1129
|
+
cutout.boundaries[-1].manifold._normal < 0.0:
|
|
1130
|
+
cutout.add_boundary(Boundary(Hyperplane(-cutout.boundaries[-1].manifold._normal, bounds[0][1], 0.0), Solid(0, True)))
|
|
1130
1131
|
# Next, check left end since it's still untouched.
|
|
1131
|
-
if abs(
|
|
1132
|
-
|
|
1133
|
-
|
|
1132
|
+
if abs(cutout.boundaries[0].manifold._point - bounds[0][0]) >= Manifold.minSeparation and \
|
|
1133
|
+
cutout.boundaries[0].manifold._normal > 0.0:
|
|
1134
|
+
cutout.add_boundary(Boundary(Hyperplane(-cutout.boundaries[0].manifold._normal, bounds[0][0], 0.0), Solid(0, True)))
|
|
1134
1135
|
|
|
1135
|
-
# For surfaces, intersect full spline domain with existing
|
|
1136
|
-
if
|
|
1136
|
+
# For surfaces, intersect full spline domain with existing cutout boundaries.
|
|
1137
|
+
if cutout.dimension == 2:
|
|
1137
1138
|
fullDomain = Hyperplane.create_hypercube(bounds)
|
|
1138
1139
|
for newBoundary in fullDomain.boundaries: # Mark full domain boundaries as untouched
|
|
1139
1140
|
newBoundary.touched = False
|
|
1140
1141
|
|
|
1141
|
-
# Define function for adding
|
|
1142
|
+
# Define function for adding cutout points to full domain boundaries.
|
|
1142
1143
|
def process_domain_point(boundary, domainPoint, adjustment):
|
|
1143
1144
|
point = boundary.manifold.evaluate(domainPoint)
|
|
1144
1145
|
# See if and where point touches full domain.
|
|
@@ -1147,24 +1148,24 @@ def complete_slice(self, slice, solid):
|
|
|
1147
1148
|
if abs(np.dot(newBoundary.manifold._normal, vector)) < Manifold.minSeparation:
|
|
1148
1149
|
# Add the point onto the new boundary (adjust normal evaluation point to move away from boundary).
|
|
1149
1150
|
normal = np.sign(newBoundary.manifold._tangentSpace.T @ boundary.manifold.normal(domainPoint + adjustment))
|
|
1150
|
-
newBoundary.
|
|
1151
|
+
newBoundary.trim.add_boundary(Boundary(Hyperplane(normal, newBoundary.manifold._tangentSpace.T @ vector, 0.0), Solid(0, True)))
|
|
1151
1152
|
newBoundary.touched = True
|
|
1152
1153
|
break
|
|
1153
1154
|
|
|
1154
1155
|
# Go through existing boundaries and check if either of their endpoints lies on the spline's bounds.
|
|
1155
|
-
for boundary in
|
|
1156
|
-
domainBoundaries = boundary.
|
|
1156
|
+
for boundary in cutout.boundaries:
|
|
1157
|
+
domainBoundaries = boundary.trim.boundaries
|
|
1157
1158
|
domainBoundaries.sort(key=lambda boundary: (boundary.manifold.evaluate(0.0), boundary.manifold.normal(0.0)))
|
|
1158
1159
|
process_domain_point(boundary, domainBoundaries[0].manifold._point, Manifold.minSeparation)
|
|
1159
1160
|
if len(domainBoundaries) > 1:
|
|
1160
1161
|
process_domain_point(boundary, domainBoundaries[-1].manifold._point, -Manifold.minSeparation)
|
|
1161
1162
|
|
|
1162
|
-
# For touched boundaries, remove domain bounds that aren't needed and then add boundary to
|
|
1163
|
+
# For touched boundaries, remove domain bounds that aren't needed and then add boundary to cutout.
|
|
1163
1164
|
boundaryWasTouched = False
|
|
1164
1165
|
for newBoundary in fullDomain.boundaries:
|
|
1165
1166
|
if newBoundary.touched:
|
|
1166
1167
|
boundaryWasTouched = True
|
|
1167
|
-
domainBoundaries = newBoundary.
|
|
1168
|
+
domainBoundaries = newBoundary.trim.boundaries
|
|
1168
1169
|
domainBoundaries.sort(key=lambda boundary: (boundary.manifold.evaluate(0.0), boundary.manifold.normal(0.0)))
|
|
1169
1170
|
# Ensure domain endpoints don't overlap and their normals are consistent.
|
|
1170
1171
|
if abs(domainBoundaries[0].manifold._point - domainBoundaries[1].manifold._point) < Manifold.minSeparation or \
|
|
@@ -1173,10 +1174,10 @@ def complete_slice(self, slice, solid):
|
|
|
1173
1174
|
if abs(domainBoundaries[-1].manifold._point - domainBoundaries[-2].manifold._point) < Manifold.minSeparation or \
|
|
1174
1175
|
domainBoundaries[-2].manifold._normal > 0.0:
|
|
1175
1176
|
del domainBoundaries[-1]
|
|
1176
|
-
|
|
1177
|
+
cutout.add_boundary(newBoundary)
|
|
1177
1178
|
|
|
1178
1179
|
if boundaryWasTouched:
|
|
1179
|
-
# Touch untouched boundaries that are connected to touched boundary endpoints and add them to
|
|
1180
|
+
# Touch untouched boundaries that are connected to touched boundary endpoints and add them to cutout.
|
|
1180
1181
|
boundaryMap = ((2, 3, 0), (2, 3, -1), (0, 1, 0), (0, 1, -1)) # Map of which full domain boundaries touch each other
|
|
1181
1182
|
while True:
|
|
1182
1183
|
noTouches = True
|
|
@@ -1184,21 +1185,21 @@ def complete_slice(self, slice, solid):
|
|
|
1184
1185
|
if not newBoundary.touched:
|
|
1185
1186
|
leftBoundary = fullDomain.boundaries[map[0]]
|
|
1186
1187
|
rightBoundary = fullDomain.boundaries[map[1]]
|
|
1187
|
-
if leftBoundary.touched and abs(leftBoundary.
|
|
1188
|
+
if leftBoundary.touched and abs(leftBoundary.trim.boundaries[map[2]].manifold._point - bound) < Manifold.minSeparation:
|
|
1188
1189
|
newBoundary.touched = True
|
|
1189
|
-
|
|
1190
|
+
cutout.add_boundary(newBoundary)
|
|
1190
1191
|
noTouches = False
|
|
1191
|
-
elif rightBoundary.touched and abs(rightBoundary.
|
|
1192
|
+
elif rightBoundary.touched and abs(rightBoundary.trim.boundaries[map[2]].manifold._point - bound) < Manifold.minSeparation:
|
|
1192
1193
|
newBoundary.touched = True
|
|
1193
|
-
|
|
1194
|
+
cutout.add_boundary(newBoundary)
|
|
1194
1195
|
noTouches = False
|
|
1195
1196
|
if noTouches:
|
|
1196
1197
|
break
|
|
1197
1198
|
else:
|
|
1198
|
-
# No
|
|
1199
|
+
# No cutout boundaries touched the full domain (a hole), so only add full domain if it is contained in the solid.
|
|
1199
1200
|
if solid.contains_point(self.evaluate(bounds[:,0])):
|
|
1200
1201
|
for newBoundary in fullDomain.boundaries:
|
|
1201
|
-
|
|
1202
|
+
cutout.add_boundary(newBoundary)
|
|
1202
1203
|
|
|
1203
1204
|
def full_domain(self):
|
|
1204
1205
|
return Hyperplane.create_hypercube(self.domain())
|