bspy 4.4.1__py3-none-any.whl → 5.0.0__py3-none-any.whl
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/_spline_evaluation.py +1 -1
- bspy/_spline_intersection.py +66 -66
- bspy/_spline_milling.py +3 -3
- bspy/hyperplane.py +35 -35
- bspy/manifold.py +40 -40
- bspy/solid.py +162 -159
- bspy/spline.py +28 -28
- bspy/splineOpenGLFrame.py +7 -7
- bspy/viewer.py +3 -3
- {bspy-4.4.1.dist-info → bspy-5.0.0.dist-info}/METADATA +8 -2
- bspy-5.0.0.dist-info/RECORD +19 -0
- {bspy-4.4.1.dist-info → bspy-5.0.0.dist-info}/WHEEL +1 -1
- bspy-4.4.1.dist-info/RECORD +0 -19
- {bspy-4.4.1.dist-info → bspy-5.0.0.dist-info}/licenses/LICENSE +0 -0
- {bspy-4.4.1.dist-info → bspy-5.0.0.dist-info}/top_level.txt +0 -0
bspy/_spline_evaluation.py
CHANGED
|
@@ -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)
|
bspy/_spline_intersection.py
CHANGED
|
@@ -435,7 +435,7 @@ def zeros_using_projected_polyhedron(self, epsilon=None, initialScale=None):
|
|
|
435
435
|
return roots
|
|
436
436
|
|
|
437
437
|
def _turning_point_determinant(self, uvw, cosTheta, sinTheta):
|
|
438
|
-
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
|
|
439
439
|
tangentSpace = self.jacobian(uvw).T
|
|
440
440
|
return cosTheta * sign * np.linalg.det(tangentSpace[[j for j in range(self.nInd) if j != 0]]) - \
|
|
441
441
|
sinTheta * sign * np.linalg.det(tangentSpace[[j for j in range(self.nInd) if j != 1]])
|
|
@@ -444,7 +444,7 @@ def _turning_point_determinant_gradient(self, uvw, cosTheta, sinTheta):
|
|
|
444
444
|
dtype = self.coefs.dtype if hasattr(self, "coefs") else self.coefsDtype
|
|
445
445
|
gradient = np.zeros(self.nInd, dtype)
|
|
446
446
|
|
|
447
|
-
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
|
|
448
448
|
tangentSpace = self.jacobian(uvw).T
|
|
449
449
|
dTangentSpace = tangentSpace.copy()
|
|
450
450
|
|
|
@@ -698,7 +698,7 @@ def _contours_of_C1_spline_block(self, epsilon, evaluationEpsilon):
|
|
|
698
698
|
|
|
699
699
|
# Extra step not in paper.
|
|
700
700
|
# Run a checksum on the points, ensuring starting and ending points balance.
|
|
701
|
-
# Start by
|
|
701
|
+
# Start by negating endpoints as needed, since we can miss turning points near endpoints.
|
|
702
702
|
if points[0].det < 0.0:
|
|
703
703
|
point = points[0]
|
|
704
704
|
points[0] = Point(point.d, -point.det, point.onUVBoundary, point.turningPoint, point.uvw)
|
|
@@ -973,19 +973,19 @@ def intersect(self, other):
|
|
|
973
973
|
intersections.append(Manifold.Crossing(Hyperplane(1.0, zero[1] + epsilon, 0.0), Hyperplane(1.0, planeBounds[1], 0.0)))
|
|
974
974
|
|
|
975
975
|
# Now, create the coincidence.
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
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)
|
|
980
980
|
if planeBounds[0] > planeBounds[1]:
|
|
981
981
|
planeBounds = (planeBounds[1], planeBounds[0])
|
|
982
|
-
|
|
983
|
-
|
|
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)))
|
|
984
984
|
alignment = np.dot(self.normal((zero[0],)), other._normal) # Use the first zero, since B-splines are closed on the left
|
|
985
985
|
width = zero[1] - zero[0]
|
|
986
986
|
transform = (planeBounds[1] - planeBounds[0]) / width
|
|
987
987
|
translation = (planeBounds[0] * zero[1] - planeBounds[1] * zero[0]) / width
|
|
988
|
-
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)))
|
|
989
989
|
else:
|
|
990
990
|
# Intersection is a point, so create a Manifold.Crossing.
|
|
991
991
|
intersections.append(Manifold.Crossing(Hyperplane(1.0, zero, 0.0), Hyperplane(1.0, projection @ (self((zero,)) - other._point), 0.0)))
|
|
@@ -996,16 +996,16 @@ def intersect(self, other):
|
|
|
996
996
|
contours = spline.contours()
|
|
997
997
|
# Convert each contour into a Manifold.Crossing.
|
|
998
998
|
for contour in contours:
|
|
999
|
-
# The
|
|
1000
|
-
|
|
1001
|
-
# 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.
|
|
1002
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
|
|
1003
1003
|
points = []
|
|
1004
1004
|
for t in tValues:
|
|
1005
1005
|
zero = contour((t,))
|
|
1006
1006
|
points.append(projection @ (self(zero) - other._point))
|
|
1007
|
-
|
|
1008
|
-
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))
|
|
1009
1009
|
else:
|
|
1010
1010
|
return NotImplemented
|
|
1011
1011
|
|
|
@@ -1040,17 +1040,17 @@ def intersect(self, other):
|
|
|
1040
1040
|
intersections.append(Manifold.Crossing(Hyperplane(1.0, zero[1][0], 0.0), Hyperplane(1.0, zero[1][1] + epsilon, 0.0)))
|
|
1041
1041
|
|
|
1042
1042
|
# Now, create the coincidence.
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
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)))
|
|
1049
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
|
|
1050
1050
|
width = zero[1][0] - zero[0][0]
|
|
1051
1051
|
transform = (zero[1][1] - zero[0][1]) / width
|
|
1052
1052
|
translation = (zero[0][1] * zero[1][0] - zero[1][1] * zero[0][0]) / width
|
|
1053
|
-
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)))
|
|
1054
1054
|
else:
|
|
1055
1055
|
# Intersection is a point, so create a Manifold.Crossing.
|
|
1056
1056
|
intersections.append(Manifold.Crossing(Hyperplane(1.0, zero[:self.nInd], 0.0), Hyperplane(1.0, zero[self.nInd:], 0.0)))
|
|
@@ -1073,16 +1073,16 @@ def intersect(self, other):
|
|
|
1073
1073
|
contours = block.contours()
|
|
1074
1074
|
# Convert each contour into a Manifold.Crossing, swapping the manifolds back.
|
|
1075
1075
|
for contour in contours:
|
|
1076
|
-
# Swap
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
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))
|
|
1080
1080
|
else:
|
|
1081
1081
|
# Convert each contour into a Manifold.Crossing.
|
|
1082
1082
|
for contour in contours:
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
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))
|
|
1086
1086
|
else:
|
|
1087
1087
|
return NotImplemented
|
|
1088
1088
|
else:
|
|
@@ -1091,55 +1091,55 @@ def intersect(self, other):
|
|
|
1091
1091
|
# If self and other have normals, ensure they are pointing in the correct direction.
|
|
1092
1092
|
if self.nInd + 1 == self.nDep and other.domain_dimension() + 1 == self.nDep:
|
|
1093
1093
|
# Ensure the normals point outwards for both Manifolds in each crossing intersection.
|
|
1094
|
-
# Note that evaluating
|
|
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
1095
|
domainPoint = np.atleast_1d(0.5)
|
|
1096
1096
|
for i, intersection in enumerate(intersections):
|
|
1097
1097
|
if isinstance(intersection, Manifold.Crossing):
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
if np.dot(self.tangent_space(
|
|
1101
|
-
|
|
1102
|
-
if np.dot(other.tangent_space(
|
|
1103
|
-
|
|
1104
|
-
intersections[i] = 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)
|
|
1105
1105
|
|
|
1106
1106
|
return intersections
|
|
1107
1107
|
|
|
1108
|
-
def
|
|
1108
|
+
def complete_cutout(self, cutout, solid):
|
|
1109
1109
|
# Spline manifold domains have finite bounds.
|
|
1110
|
-
|
|
1110
|
+
cutout.containsInfinity = False
|
|
1111
1111
|
bounds = self.domain()
|
|
1112
1112
|
|
|
1113
1113
|
# If manifold (self) has no intersections with solid, just check containment.
|
|
1114
|
-
if not
|
|
1115
|
-
if
|
|
1114
|
+
if not cutout.boundaries:
|
|
1115
|
+
if cutout.dimension == 2:
|
|
1116
1116
|
if "Name" in self.metadata:
|
|
1117
1117
|
logging.info(f"check containment:{self.metadata['Name']}")
|
|
1118
1118
|
domain = bounds.T
|
|
1119
1119
|
if solid.contains_point(self(0.5 * (domain[0] + domain[1]))):
|
|
1120
1120
|
for boundary in Hyperplane.create_hypercube(bounds).boundaries:
|
|
1121
|
-
|
|
1121
|
+
cutout.add_boundary(boundary)
|
|
1122
1122
|
return
|
|
1123
1123
|
|
|
1124
1124
|
# For curves, add domain bounds as needed.
|
|
1125
|
-
if
|
|
1126
|
-
|
|
1125
|
+
if cutout.dimension == 1:
|
|
1126
|
+
cutout.boundaries.sort(key=lambda b: (b.manifold.evaluate(0.0), b.manifold.normal(0.0)))
|
|
1127
1127
|
# First, check right end since we add new boundary to the end.
|
|
1128
|
-
if abs(
|
|
1129
|
-
|
|
1130
|
-
|
|
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)))
|
|
1131
1131
|
# Next, check left end since it's still untouched.
|
|
1132
|
-
if abs(
|
|
1133
|
-
|
|
1134
|
-
|
|
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)))
|
|
1135
1135
|
|
|
1136
|
-
# For surfaces, intersect full spline domain with existing
|
|
1137
|
-
if
|
|
1136
|
+
# For surfaces, intersect full spline domain with existing cutout boundaries.
|
|
1137
|
+
if cutout.dimension == 2:
|
|
1138
1138
|
fullDomain = Hyperplane.create_hypercube(bounds)
|
|
1139
1139
|
for newBoundary in fullDomain.boundaries: # Mark full domain boundaries as untouched
|
|
1140
1140
|
newBoundary.touched = False
|
|
1141
1141
|
|
|
1142
|
-
# Define function for adding
|
|
1142
|
+
# Define function for adding cutout points to full domain boundaries.
|
|
1143
1143
|
def process_domain_point(boundary, domainPoint, adjustment):
|
|
1144
1144
|
point = boundary.manifold.evaluate(domainPoint)
|
|
1145
1145
|
# See if and where point touches full domain.
|
|
@@ -1148,24 +1148,24 @@ def complete_slice(self, slice, solid):
|
|
|
1148
1148
|
if abs(np.dot(newBoundary.manifold._normal, vector)) < Manifold.minSeparation:
|
|
1149
1149
|
# Add the point onto the new boundary (adjust normal evaluation point to move away from boundary).
|
|
1150
1150
|
normal = np.sign(newBoundary.manifold._tangentSpace.T @ boundary.manifold.normal(domainPoint + adjustment))
|
|
1151
|
-
newBoundary.
|
|
1151
|
+
newBoundary.trim.add_boundary(Boundary(Hyperplane(normal, newBoundary.manifold._tangentSpace.T @ vector, 0.0), Solid(0, True)))
|
|
1152
1152
|
newBoundary.touched = True
|
|
1153
1153
|
break
|
|
1154
1154
|
|
|
1155
1155
|
# Go through existing boundaries and check if either of their endpoints lies on the spline's bounds.
|
|
1156
|
-
for boundary in
|
|
1157
|
-
domainBoundaries = boundary.
|
|
1156
|
+
for boundary in cutout.boundaries:
|
|
1157
|
+
domainBoundaries = boundary.trim.boundaries
|
|
1158
1158
|
domainBoundaries.sort(key=lambda boundary: (boundary.manifold.evaluate(0.0), boundary.manifold.normal(0.0)))
|
|
1159
1159
|
process_domain_point(boundary, domainBoundaries[0].manifold._point, Manifold.minSeparation)
|
|
1160
1160
|
if len(domainBoundaries) > 1:
|
|
1161
1161
|
process_domain_point(boundary, domainBoundaries[-1].manifold._point, -Manifold.minSeparation)
|
|
1162
1162
|
|
|
1163
|
-
# 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.
|
|
1164
1164
|
boundaryWasTouched = False
|
|
1165
1165
|
for newBoundary in fullDomain.boundaries:
|
|
1166
1166
|
if newBoundary.touched:
|
|
1167
1167
|
boundaryWasTouched = True
|
|
1168
|
-
domainBoundaries = newBoundary.
|
|
1168
|
+
domainBoundaries = newBoundary.trim.boundaries
|
|
1169
1169
|
domainBoundaries.sort(key=lambda boundary: (boundary.manifold.evaluate(0.0), boundary.manifold.normal(0.0)))
|
|
1170
1170
|
# Ensure domain endpoints don't overlap and their normals are consistent.
|
|
1171
1171
|
if abs(domainBoundaries[0].manifold._point - domainBoundaries[1].manifold._point) < Manifold.minSeparation or \
|
|
@@ -1174,10 +1174,10 @@ def complete_slice(self, slice, solid):
|
|
|
1174
1174
|
if abs(domainBoundaries[-1].manifold._point - domainBoundaries[-2].manifold._point) < Manifold.minSeparation or \
|
|
1175
1175
|
domainBoundaries[-2].manifold._normal > 0.0:
|
|
1176
1176
|
del domainBoundaries[-1]
|
|
1177
|
-
|
|
1177
|
+
cutout.add_boundary(newBoundary)
|
|
1178
1178
|
|
|
1179
1179
|
if boundaryWasTouched:
|
|
1180
|
-
# 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.
|
|
1181
1181
|
boundaryMap = ((2, 3, 0), (2, 3, -1), (0, 1, 0), (0, 1, -1)) # Map of which full domain boundaries touch each other
|
|
1182
1182
|
while True:
|
|
1183
1183
|
noTouches = True
|
|
@@ -1185,21 +1185,21 @@ def complete_slice(self, slice, solid):
|
|
|
1185
1185
|
if not newBoundary.touched:
|
|
1186
1186
|
leftBoundary = fullDomain.boundaries[map[0]]
|
|
1187
1187
|
rightBoundary = fullDomain.boundaries[map[1]]
|
|
1188
|
-
if leftBoundary.touched and abs(leftBoundary.
|
|
1188
|
+
if leftBoundary.touched and abs(leftBoundary.trim.boundaries[map[2]].manifold._point - bound) < Manifold.minSeparation:
|
|
1189
1189
|
newBoundary.touched = True
|
|
1190
|
-
|
|
1190
|
+
cutout.add_boundary(newBoundary)
|
|
1191
1191
|
noTouches = False
|
|
1192
|
-
elif rightBoundary.touched and abs(rightBoundary.
|
|
1192
|
+
elif rightBoundary.touched and abs(rightBoundary.trim.boundaries[map[2]].manifold._point - bound) < Manifold.minSeparation:
|
|
1193
1193
|
newBoundary.touched = True
|
|
1194
|
-
|
|
1194
|
+
cutout.add_boundary(newBoundary)
|
|
1195
1195
|
noTouches = False
|
|
1196
1196
|
if noTouches:
|
|
1197
1197
|
break
|
|
1198
1198
|
else:
|
|
1199
|
-
# No
|
|
1199
|
+
# No cutout boundaries touched the full domain (a hole), so only add full domain if it is contained in the solid.
|
|
1200
1200
|
if solid.contains_point(self.evaluate(bounds[:,0])):
|
|
1201
1201
|
for newBoundary in fullDomain.boundaries:
|
|
1202
|
-
|
|
1202
|
+
cutout.add_boundary(newBoundary)
|
|
1203
1203
|
|
|
1204
1204
|
def full_domain(self):
|
|
1205
1205
|
return Hyperplane.create_hypercube(self.domain())
|
bspy/_spline_milling.py
CHANGED
|
@@ -254,10 +254,10 @@ def offset(self, edgeRadius, bitRadius=None, angle=np.pi / 2.2, path=None, subtr
|
|
|
254
254
|
tangent = self.jacobian(path(knot)) @ path.derivative((1,), knot)
|
|
255
255
|
else:
|
|
256
256
|
tangent = self.derivative((1,), knot)
|
|
257
|
-
|
|
258
|
-
if
|
|
257
|
+
negated = np.dot(tangent, offset.derivative((1,), knot)) < 0
|
|
258
|
+
if negated and start is None:
|
|
259
259
|
start = knot
|
|
260
|
-
if not
|
|
260
|
+
if not negated and start is not None:
|
|
261
261
|
cusps.append((start, previousKnot))
|
|
262
262
|
start = None
|
|
263
263
|
previousKnot = knot
|
bspy/hyperplane.py
CHANGED
|
@@ -41,19 +41,19 @@ class Hyperplane(Manifold):
|
|
|
41
41
|
def __repr__(self):
|
|
42
42
|
return "Hyperplane({0}, {1}, {2})".format(self._normal, self._point, self._tangentSpace)
|
|
43
43
|
|
|
44
|
-
def
|
|
44
|
+
def complete_cutout(self, cutout, solid):
|
|
45
45
|
"""
|
|
46
|
-
Add any missing inherent (implicit) boundaries of this manifold's domain to the given
|
|
47
|
-
given solid that are needed to make the
|
|
46
|
+
Add any missing inherent (implicit) boundaries of this manifold's domain to the given cutout of the
|
|
47
|
+
given solid that are needed to make the cutout valid and complete.
|
|
48
48
|
|
|
49
49
|
Parameters
|
|
50
50
|
----------
|
|
51
|
-
|
|
52
|
-
The
|
|
51
|
+
cutout : `Solid`
|
|
52
|
+
The cutout of the given solid formed by the manifold. The cutout may be incomplete, missing some of the
|
|
53
53
|
manifold's inherent domain boundaries. Its dimension must match `self.domain_dimension()`.
|
|
54
54
|
|
|
55
55
|
solid : `Solid`
|
|
56
|
-
The solid
|
|
56
|
+
The solid determining the cutout of the manifold. Its dimension must match `self.range_dimension()`.
|
|
57
57
|
|
|
58
58
|
Parameters
|
|
59
59
|
----------
|
|
@@ -63,17 +63,17 @@ class Hyperplane(Manifold):
|
|
|
63
63
|
|
|
64
64
|
See Also
|
|
65
65
|
--------
|
|
66
|
-
`Solid.
|
|
66
|
+
`Solid.compute_cutout` : Compute the cutout portion of the manifold within the solid.
|
|
67
67
|
|
|
68
68
|
Notes
|
|
69
69
|
-----
|
|
70
70
|
Since hyperplanes have no inherent domain boundaries, this operation only tests for
|
|
71
71
|
point containment for zero-dimension hyperplanes (points).
|
|
72
72
|
"""
|
|
73
|
-
assert self.domain_dimension() ==
|
|
73
|
+
assert self.domain_dimension() == cutout.dimension
|
|
74
74
|
assert self.range_dimension() == solid.dimension
|
|
75
|
-
if
|
|
76
|
-
|
|
75
|
+
if cutout.dimension == 0:
|
|
76
|
+
cutout.containsInfinity = solid.contains_point(self._point)
|
|
77
77
|
|
|
78
78
|
def copy(self):
|
|
79
79
|
"""
|
|
@@ -86,7 +86,7 @@ class Hyperplane(Manifold):
|
|
|
86
86
|
return Hyperplane(self._normal, self._point, self._tangentSpace)
|
|
87
87
|
|
|
88
88
|
@staticmethod
|
|
89
|
-
def create_axis_aligned(dimension, axis, offset,
|
|
89
|
+
def create_axis_aligned(dimension, axis, offset, negateNormal=False):
|
|
90
90
|
"""
|
|
91
91
|
Create an axis-aligned hyperplane.
|
|
92
92
|
|
|
@@ -101,7 +101,7 @@ class Hyperplane(Manifold):
|
|
|
101
101
|
offset : `float`
|
|
102
102
|
The offset from zero along the axis of a point on the hyperplane.
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
negateNormal : `bool`, optional
|
|
105
105
|
A Boolean indicating that the normal should point toward in the negative direction along the axis.
|
|
106
106
|
Default is False, meaning the normal points in the positive direction along the axis.
|
|
107
107
|
|
|
@@ -112,7 +112,7 @@ class Hyperplane(Manifold):
|
|
|
112
112
|
"""
|
|
113
113
|
assert dimension > 0
|
|
114
114
|
diagonal = np.identity(dimension)
|
|
115
|
-
sign = -1.0 if
|
|
115
|
+
sign = -1.0 if negateNormal else 1.0
|
|
116
116
|
normal = sign * diagonal[:,axis]
|
|
117
117
|
point = offset * normal
|
|
118
118
|
if dimension > 1:
|
|
@@ -178,21 +178,6 @@ class Hyperplane(Manifold):
|
|
|
178
178
|
"""
|
|
179
179
|
return np.dot(self._tangentSpace, np.atleast_1d(domainPoint)) + self._point
|
|
180
180
|
|
|
181
|
-
def flip_normal(self):
|
|
182
|
-
"""
|
|
183
|
-
Flip the direction of the normal.
|
|
184
|
-
|
|
185
|
-
Returns
|
|
186
|
-
-------
|
|
187
|
-
hyperplane : `Hyperplane`
|
|
188
|
-
The hyperplane with flipped normal. The hyperplane retains the same tangent space.
|
|
189
|
-
|
|
190
|
-
See Also
|
|
191
|
-
--------
|
|
192
|
-
`Solid.complement` : Return the complement of the solid: whatever was inside is outside and vice-versa.
|
|
193
|
-
"""
|
|
194
|
-
return Hyperplane(-self._normal, self._point, self._tangentSpace)
|
|
195
|
-
|
|
196
181
|
@staticmethod
|
|
197
182
|
def from_dict(dictionary):
|
|
198
183
|
"""
|
|
@@ -244,14 +229,14 @@ class Hyperplane(Manifold):
|
|
|
244
229
|
(Hyperplanes will have at most one intersection, but other types of manifolds can have several.)
|
|
245
230
|
Each intersection records either a crossing or a coincident region.
|
|
246
231
|
|
|
247
|
-
For a crossing, intersection is a `Manifold.Crossing`: (
|
|
248
|
-
*
|
|
249
|
-
*
|
|
232
|
+
For a crossing, intersection is a `Manifold.Crossing`: (firstPart, secondPart)
|
|
233
|
+
* firstPart : `Manifold` in the manifold's domain where the manifold and the other cross.
|
|
234
|
+
* secondPart : `Manifold` in the other's domain where the manifold and the other cross.
|
|
250
235
|
* Both intersection manifolds have the same domain and range (the crossing between the manifold and the other).
|
|
251
236
|
|
|
252
|
-
For a coincident region, intersection is a `Manifold.Coincidence`: (
|
|
253
|
-
*
|
|
254
|
-
*
|
|
237
|
+
For a coincident region, intersection is a `Manifold.Coincidence`: (firstPart, secondPart, alignment, transform, inverse, translation)
|
|
238
|
+
* firstPart : `Solid` in the manifold's domain within which the manifold and the other are coincident.
|
|
239
|
+
* secondPart : `Solid` in the other's domain within which the manifold and the other are coincident.
|
|
255
240
|
* alignment : scalar value holding the normal alignment between the manifold and the other (the dot product of their unit normals).
|
|
256
241
|
* transform : `numpy.array` holding the transform matrix from the manifold's domain to the other's domain.
|
|
257
242
|
* inverse : `numpy.array` holding the inverse transform matrix from the other's domain to the boundary's domain.
|
|
@@ -260,7 +245,7 @@ class Hyperplane(Manifold):
|
|
|
260
245
|
|
|
261
246
|
See Also
|
|
262
247
|
--------
|
|
263
|
-
`Solid.
|
|
248
|
+
`Solid.compute_cutout` : Compute the cutout portion of the manifold within the solid.
|
|
264
249
|
`numpy.linalg.svd` : Compute the singular value decomposition of a matrix array.
|
|
265
250
|
|
|
266
251
|
Notes
|
|
@@ -361,6 +346,21 @@ class Hyperplane(Manifold):
|
|
|
361
346
|
|
|
362
347
|
return intersections
|
|
363
348
|
|
|
349
|
+
def negate_normal(self):
|
|
350
|
+
"""
|
|
351
|
+
Negate the direction of the normal.
|
|
352
|
+
|
|
353
|
+
Returns
|
|
354
|
+
-------
|
|
355
|
+
hyperplane : `Hyperplane`
|
|
356
|
+
The hyperplane with negated normal. The hyperplane retains the same tangent space.
|
|
357
|
+
|
|
358
|
+
See Also
|
|
359
|
+
--------
|
|
360
|
+
`Solid.complement` : Return the complement of the solid: whatever was inside is outside and vice-versa.
|
|
361
|
+
"""
|
|
362
|
+
return Hyperplane(-self._normal, self._point, self._tangentSpace)
|
|
363
|
+
|
|
364
364
|
def normal(self, domainPoint, normalize=True, indices=None):
|
|
365
365
|
"""
|
|
366
366
|
Return the normal.
|
bspy/manifold.py
CHANGED
|
@@ -15,8 +15,8 @@ class Manifold:
|
|
|
15
15
|
minSeparation = 0.0001
|
|
16
16
|
"""If two points are within minSeparation of each each other, they are coincident."""
|
|
17
17
|
|
|
18
|
-
Crossing = namedtuple('Crossing', ('
|
|
19
|
-
Coincidence = namedtuple('Coincidence', ('
|
|
18
|
+
Crossing = namedtuple('Crossing', ('firstPart','secondPart'))
|
|
19
|
+
Coincidence = namedtuple('Coincidence', ('firstPart', 'secondPart', 'alignment', 'transform', 'inverse', 'translation'))
|
|
20
20
|
"""Return type for intersect."""
|
|
21
21
|
|
|
22
22
|
factory = {}
|
|
@@ -43,14 +43,14 @@ class Manifold:
|
|
|
43
43
|
A list of intersections between the two manifolds.
|
|
44
44
|
Each intersection records either a crossing or a coincident region.
|
|
45
45
|
|
|
46
|
-
For a crossing, intersection is a Manifold.Crossing: (
|
|
47
|
-
*
|
|
48
|
-
*
|
|
46
|
+
For a crossing, intersection is a Manifold.Crossing: (firstPart, secondPart)
|
|
47
|
+
* firstPart : `Manifold` in the manifold's domain where the manifold and the other cross.
|
|
48
|
+
* secondPart : `Manifold` in the other's domain where the manifold and the other cross.
|
|
49
49
|
* Both intersection manifolds have the same domain and range (the crossing between the manifold and the other).
|
|
50
50
|
|
|
51
|
-
For a coincident region, intersection is Manifold.Coincidence: (
|
|
52
|
-
*
|
|
53
|
-
*
|
|
51
|
+
For a coincident region, intersection is Manifold.Coincidence: (firstPart, secondPart, alignment, transform, inverse, translation)
|
|
52
|
+
* firstPart : `Solid` in the manifold's domain within which the manifold and the other are coincident.
|
|
53
|
+
* secondPart : `Solid` in the other's domain within which the manifold and the other are coincident.
|
|
54
54
|
* alignment : scalar value holding the normal alignment between the manifold and the other (the dot product of their unit normals).
|
|
55
55
|
* transform : `numpy.array` holding the matrix transform from the boundary's domain to the other's domain.
|
|
56
56
|
* inverse : `numpy.array` holding the matrix inverse transform from the other's domain to the boundary's domain.
|
|
@@ -63,7 +63,7 @@ class Manifold:
|
|
|
63
63
|
See Also
|
|
64
64
|
--------
|
|
65
65
|
`intersect` : Intersect two manifolds.
|
|
66
|
-
`Solid.
|
|
66
|
+
`Solid.compute_cutout` : Compute the cutout portion of the manifold within the solid.
|
|
67
67
|
|
|
68
68
|
Notes
|
|
69
69
|
-----
|
|
@@ -97,31 +97,31 @@ class Manifold:
|
|
|
97
97
|
|
|
98
98
|
return intersections, isTwin
|
|
99
99
|
|
|
100
|
-
def
|
|
100
|
+
def complete_cutout(self, cutout, solid):
|
|
101
101
|
"""
|
|
102
|
-
Add any missing inherent (implicit) boundaries of this manifold's domain to the given
|
|
103
|
-
given solid that are needed to make the
|
|
102
|
+
Add any missing inherent (implicit) boundaries of this manifold's domain to the given cutout of the
|
|
103
|
+
given solid that are needed to make the cutout valid and complete.
|
|
104
104
|
|
|
105
105
|
Parameters
|
|
106
106
|
----------
|
|
107
|
-
|
|
108
|
-
The
|
|
107
|
+
cutout : `Solid`
|
|
108
|
+
The cutout of the given solid formed by the manifold. The cutout may be incomplete, missing some of the
|
|
109
109
|
manifold's inherent domain boundaries. Its dimension must match `self.domain_dimension()`.
|
|
110
110
|
|
|
111
111
|
solid : `Solid`
|
|
112
|
-
The solid
|
|
112
|
+
The solid determining the cutout of the manifold. Its dimension must match `self.range_dimension()`.
|
|
113
113
|
|
|
114
114
|
See Also
|
|
115
115
|
--------
|
|
116
|
-
`Solid.
|
|
116
|
+
`Solid.compute_cutout` : Compute the cutout portion of the manifold within the solid.
|
|
117
117
|
|
|
118
118
|
Notes
|
|
119
119
|
-----
|
|
120
120
|
For manifolds without inherent domain boundaries (like hyperplanes), the operation does nothing.
|
|
121
121
|
"""
|
|
122
|
-
assert self.domain_dimension() ==
|
|
122
|
+
assert self.domain_dimension() == cutout.dimension
|
|
123
123
|
assert self.range_dimension() == solid.dimension
|
|
124
|
-
|
|
124
|
+
|
|
125
125
|
def copy(self):
|
|
126
126
|
"""
|
|
127
127
|
Copy the manifold.
|
|
@@ -157,21 +157,6 @@ class Manifold:
|
|
|
157
157
|
"""
|
|
158
158
|
return None
|
|
159
159
|
|
|
160
|
-
def flip_normal(self):
|
|
161
|
-
"""
|
|
162
|
-
Flip the direction of the normal.
|
|
163
|
-
|
|
164
|
-
Returns
|
|
165
|
-
-------
|
|
166
|
-
manifold : `Manifold`
|
|
167
|
-
The manifold with flipped normal. The manifold retains the same tangent space.
|
|
168
|
-
|
|
169
|
-
See Also
|
|
170
|
-
--------
|
|
171
|
-
`Solid.complement` : Return the complement of the solid: whatever was inside is outside and vice-versa.
|
|
172
|
-
"""
|
|
173
|
-
return None
|
|
174
|
-
|
|
175
160
|
@staticmethod
|
|
176
161
|
def from_dict(dictionary):
|
|
177
162
|
"""
|
|
@@ -222,14 +207,14 @@ class Manifold:
|
|
|
222
207
|
A list of intersections between the two manifolds.
|
|
223
208
|
Each intersection records either a crossing or a coincident region.
|
|
224
209
|
|
|
225
|
-
For a crossing, intersection is a `Manifold.Crossing`: (
|
|
226
|
-
*
|
|
227
|
-
*
|
|
210
|
+
For a crossing, intersection is a `Manifold.Crossing`: (firstPart, secondPart)
|
|
211
|
+
* firstPart : `Manifold` in the manifold's domain where the manifold and the other cross.
|
|
212
|
+
* secondPart : `Manifold` in the other's domain where the manifold and the other cross.
|
|
228
213
|
* Both intersection manifolds have the same domain and range (the crossing between the manifold and the other).
|
|
229
214
|
|
|
230
|
-
For a coincident region, intersection is a `Manifold.Coincidence`: (
|
|
231
|
-
*
|
|
232
|
-
*
|
|
215
|
+
For a coincident region, intersection is a `Manifold.Coincidence`: (firstPart, secondPart, alignment, transform, inverse, translation)
|
|
216
|
+
* firstPart : `Solid` in the manifold's domain within which the manifold and the other are coincident.
|
|
217
|
+
* secondPart : `Solid` in the other's domain within which the manifold and the other are coincident.
|
|
233
218
|
* alignment : scalar value holding the normal alignment between the manifold and the other (the dot product of their unit normals).
|
|
234
219
|
* transform : `numpy.array` holding the transform matrix from the manifold's domain to the other's domain.
|
|
235
220
|
* inverse : `numpy.array` holding the inverse transform matrix from the other's domain to the boundary's domain.
|
|
@@ -239,7 +224,7 @@ class Manifold:
|
|
|
239
224
|
See Also
|
|
240
225
|
--------
|
|
241
226
|
`cached_intersect` : Intersect two manifolds, caching the result for twins (same intersection but swapping self and other).
|
|
242
|
-
`Solid.
|
|
227
|
+
`Solid.compute_cutout` : Compute the cutout portion of the manifold within the solid.
|
|
243
228
|
|
|
244
229
|
Notes
|
|
245
230
|
-----
|
|
@@ -247,6 +232,21 @@ class Manifold:
|
|
|
247
232
|
"""
|
|
248
233
|
return NotImplemented
|
|
249
234
|
|
|
235
|
+
def negate_normal(self):
|
|
236
|
+
"""
|
|
237
|
+
Negate the direction of the normal.
|
|
238
|
+
|
|
239
|
+
Returns
|
|
240
|
+
-------
|
|
241
|
+
manifold : `Manifold`
|
|
242
|
+
The manifold with negated normal. The manifold retains the same tangent space.
|
|
243
|
+
|
|
244
|
+
See Also
|
|
245
|
+
--------
|
|
246
|
+
`Solid.complement` : Return the complement of the solid: whatever was inside is outside and vice-versa.
|
|
247
|
+
"""
|
|
248
|
+
return None
|
|
249
|
+
|
|
250
250
|
def normal(self, domainPoint, normalize=True, indices=None):
|
|
251
251
|
"""
|
|
252
252
|
Return the normal.
|