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/solid.py CHANGED
@@ -12,7 +12,7 @@ class Boundary:
12
12
  manifold : `Manifold`
13
13
  The differentiable function whose range is one dimension higher than its domain that defines the range of the boundary.
14
14
 
15
- domain : `Solid`, optional
15
+ trim : `Solid`, optional
16
16
  The region of the domain of the manifold that's within the boundary. The default is the full domain of the manifold.
17
17
 
18
18
  See also
@@ -20,14 +20,14 @@ class Boundary:
20
20
  `Solid` : A region that separates space into an inside and outside, defined by a collection of boundaries.
21
21
  `Manifold.full_domain` : Return a solid that represents the full domain of the manifold.
22
22
  """
23
- def __init__(self, manifold, domain = None):
24
- self.domain = manifold.full_domain() if domain is None else domain
25
- if manifold.domain_dimension() != self.domain.dimension: raise ValueError("Domain dimensions don't match")
23
+ def __init__(self, manifold, trim = None):
24
+ self.trim = manifold.full_domain() if trim is None else trim
25
+ if manifold.domain_dimension() != self.trim.dimension: raise ValueError("Domain dimensions don't match")
26
26
  if manifold.domain_dimension() + 1 != manifold.range_dimension(): raise ValueError("Manifold range is not one dimension higher than domain")
27
- self.manifold, self.bounds = manifold.trimmed_range_bounds(self.domain.bounds)
27
+ self.manifold, self.bounds = manifold.trimmed_range_bounds(self.trim.bounds)
28
28
 
29
29
  def __repr__(self):
30
- return "Boundary({0}, {1})".format(self.manifold.__repr__(), self.domain.__repr__())
30
+ return "Boundary({0}, {1})".format(self.manifold.__repr__(), self.trim.__repr__())
31
31
 
32
32
  def any_point(self):
33
33
  """
@@ -44,9 +44,9 @@ class Boundary:
44
44
 
45
45
  Notes
46
46
  -----
47
- The point is computed by evaluating the boundary manifold by an arbitrary point in the domain of the boundary.
47
+ The point is computed by evaluating the boundary manifold by an arbitrary point in the trim of the boundary.
48
48
  """
49
- return self.manifold.evaluate(self.domain.any_point())
49
+ return self.manifold.evaluate(self.trim.any_point())
50
50
 
51
51
  class Solid:
52
52
  """
@@ -71,7 +71,7 @@ class Solid:
71
71
  -----
72
72
  Solids also contain a `list` of `boundaries`. That list may be empty.
73
73
 
74
- Solids can be of zero dimension, typically acting as the domain of boundary endpoints. Zero-dimension solids have no boundaries, they only contain infinity or not.
74
+ Solids can be of zero dimension, typically acting as the trim of boundary endpoints. Zero-dimension solids have no boundaries, they only contain infinity or not.
75
75
  """
76
76
  def __init__(self, dimension, containsInfinity, metadata = {}):
77
77
  assert dimension >= 0
@@ -171,9 +171,123 @@ class Solid:
171
171
  """
172
172
  solid = Solid(self.dimension, not self.containsInfinity)
173
173
  for boundary in self.boundaries:
174
- solid.add_boundary(Boundary(boundary.manifold.flip_normal(), boundary.domain))
174
+ solid.add_boundary(Boundary(boundary.manifold.negate_normal(), boundary.trim))
175
175
  return solid
176
176
 
177
+ def compute_cutout(self, manifold, cache = None, trimTwin = False):
178
+ """
179
+ Compute the cutout portion of the manifold within the solid.
180
+
181
+ Parameters
182
+ ----------
183
+ manifold : `Manifold`
184
+ The `Manifold` for which we are computing a cutout within this solid.
185
+
186
+ cache : `dict`, optional
187
+ A dictionary to cache `Manifold` intersections, speeding computation.
188
+
189
+ trimTwin : `bool`, default: False
190
+ Trim coincident boundary twins on subsequent calls to cutout (avoids duplication of overlapping regions).
191
+ Trimming twins is typically only used in conjunction with `intersection`.
192
+
193
+ Returns
194
+ -------
195
+ cutout : `Solid`
196
+ A region in the domain of `manifold` that intersects with the solid. The region may contain infinity.
197
+
198
+ See Also
199
+ --------
200
+ `intersection` : Intersect two solids.
201
+ `Manifold.intersect` : Intersect two manifolds.
202
+ `Manifold.cached_intersect` : Intersect two manifolds, caching the result for twins.
203
+ `Manifold.complete_cutout` : Add any missing inherent (implicit) boundaries of this manifold's domain to the given cutout.
204
+
205
+ Notes
206
+ -----
207
+ The dimension of the cutout is always one less than the dimension of the solid, since the cutout is a region in the domain of the manifold.
208
+
209
+ To compute the cutout of a manifold intersecting the solid, we intersect the manifold with each boundary of the solid. There may be multiple intersections
210
+ between the manifold and the boundary. Each is either a crossing or a coincident region.
211
+
212
+ Crossings result in two intersection manifolds: one in the domain of the manifold and one in the trim of the boundary. By construction, both intersection manifolds have the
213
+ same domain and the same range of the manifold and boundary (the crossing itself). The intersection manifold in the domain of the manifold becomes a boundary of the cutout,
214
+ but we must determine the intersection's trim. For that, we compute the cutout of the boundary's intersection manifold with the boundary's trim. This recursion continues
215
+ until the cutout is just a point with no trim.
216
+
217
+ Coincident regions appear in the trims of the manifold and the boundary. We intersect the boundary's coincident region with the trim of the boundary and then map
218
+ it to the domain of the manifold. If the coincident regions have normals in opposite directions, they cancel each other out, so we subtract them from the cutout by
219
+ inverting the region and intersecting it with the cutout. We use this same technique for removing overlapping coincident regions. If the coincident regions have normals
220
+ in the same direction, we union them with the cutout.
221
+ """
222
+ assert manifold.range_dimension() == self.dimension
223
+
224
+ # Start with an empty cutout and no domain coincidences.
225
+ cutout = Solid(self.dimension-1, self.containsInfinity)
226
+ bounds = manifold.range_bounds()
227
+ if Solid.disjoint_bounds(bounds, self.bounds):
228
+ manifold.complete_cutout(cutout, self)
229
+ return cutout
230
+ coincidences = []
231
+
232
+ # Intersect each of this solid's boundaries with the manifold.
233
+ for boundary in self.boundaries:
234
+ if Solid.disjoint_bounds(boundary.bounds, bounds):
235
+ continue
236
+
237
+ # Intersect manifolds, checking if the intersection is already in the cache.
238
+ intersections, isTwin = boundary.manifold.cached_intersect(manifold, cache)
239
+ if intersections is NotImplemented:
240
+ raise NotImplementedError()
241
+
242
+ # Each intersection is either a crossing (domain manifold) or a coincidence (solid within the domain).
243
+ for intersection in intersections:
244
+ (firstPart, secondPart) = (intersection.secondPart, intersection.firstPart) if isTwin else (intersection.firstPart, intersection.secondPart)
245
+
246
+ if isinstance(intersection, Manifold.Crossing):
247
+ trimCutout = boundary.trim.compute_cutout(firstPart, cache)
248
+ if trimCutout:
249
+ cutout.add_boundary(Boundary(secondPart, trimCutout))
250
+
251
+ elif isinstance(intersection, Manifold.Coincidence):
252
+ # Intersect domain coincidence with the boundary's domain.
253
+ firstPart = firstPart.intersection(boundary.trim)
254
+ # Invert the domain coincidence (which will remove it) if this is a twin or if the normals point in opposite directions.
255
+ #invertCoincidence = trimTwin and (isTwin or intersection.alignment < 0.0)
256
+ invertCoincidence = (trimTwin and isTwin) or intersection.alignment < 0.0
257
+ # Create the coincidence to hold the trimmed and transformed domain coincidence (firstPart).
258
+ coincidence = Solid(firstPart.dimension, firstPart.containsInfinity)
259
+ if invertCoincidence:
260
+ coincidence.containsInfinity = not coincidence.containsInfinity
261
+ # Next, transform the domain coincidence from the boundary to the given manifold.
262
+ # Create copies of the manifolds and boundaries, since we are changing them.
263
+ for coincidenceBoundary in firstPart.boundaries:
264
+ coincidenceManifold = coincidenceBoundary.manifold
265
+ if invertCoincidence:
266
+ coincidenceManifold = coincidenceManifold.negate_normal()
267
+ if isTwin:
268
+ coincidenceManifold = coincidenceManifold.translate(-intersection.translation)
269
+ coincidenceManifold = coincidenceManifold.transform(intersection.inverse, intersection.transform.T)
270
+ else:
271
+ coincidenceManifold = coincidenceManifold.transform(intersection.transform, intersection.inverse.T)
272
+ coincidenceManifold = coincidenceManifold.translate(intersection.translation)
273
+ coincidence.add_boundary(Boundary(coincidenceManifold, coincidenceBoundary.trim))
274
+ # Finally, add the domain coincidence to the list of coincidences.
275
+ coincidences.append((invertCoincidence, coincidence))
276
+
277
+ # Ensure the cutout includes the manifold's inherent (implicit) boundaries, making it valid and complete.
278
+ manifold.complete_cutout(cutout, self)
279
+
280
+ # Now that we have a complete cutout, join it with each domain coincidence.
281
+ for coincidence in coincidences:
282
+ if coincidence[0]:
283
+ # If the domain coincidence is inverted (coincidence[0]), intersect it with the cutout, thus removing it.
284
+ cutout = cutout.intersection(coincidence[1], cache)
285
+ else:
286
+ # Otherwise, union the domain coincidence with the cutout, thus adding it.
287
+ cutout = cutout.union(coincidence[1])
288
+
289
+ return cutout
290
+
177
291
  def contains_point(self, point):
178
292
  """
179
293
  Test if a point lies within the solid.
@@ -270,19 +384,19 @@ class Solid:
270
384
 
271
385
  See Also
272
386
  --------
273
- `slice` : Slice a solid by a manifold.
387
+ `compute_cutout` : Compute the cutout portion of the manifold within the solid.
274
388
  `union` : Union two solids.
275
389
  `difference` : Subtract one solid from another.
276
390
 
277
391
  Notes
278
392
  -----
279
- To intersect two solids, we slice each solid with the boundaries of the other solid. The slices are the region
280
- of the domain that intersect the solid. We then intersect the domain of each boundary with its slice of the other solid. Thus,
281
- the intersection of two solids becomes a set of intersections within the domains of their boundaries. This recursion continues
282
- until we are intersecting points whose domains have no boundaries.
393
+ To intersect two solids, we compute the cutout portions of each solid's untrimmed boundaries (their manifolds) that lie within the other solid.
394
+ We then intersect the trim of each boundary (the trim of its manifold) with its cutout. Thus,
395
+ the intersection of two solids becomes a set of intersections within the trims of their boundaries. This recursion continues
396
+ until we are intersecting points whose trims have no boundaries.
283
397
 
284
398
  The only subtlety is when two boundaries are coincident. To avoid overlapping the coincident region, we keep that region
285
- for one slice and trim it away for the other. We use a manifold intersection cache to keep track of these pairs, as well as to reduce computation.
399
+ for one cutout and trim it away for the other. We use a manifold intersection cache to keep track of these pairs, as well as to reduce computation.
286
400
  """
287
401
  assert self.dimension == other.dimension
288
402
 
@@ -306,22 +420,22 @@ class Solid:
306
420
  return combinedSolid
307
421
 
308
422
  for boundary in self.boundaries:
309
- # Slice self boundary manifold by other.
310
- slice = other.slice(boundary.manifold, cache, True)
311
- # Intersect slice with the boundary's domain.
312
- newDomain = boundary.domain.intersection(slice, cache)
313
- if newDomain:
314
- # Self boundary intersects other, so create a new boundary with the intersected domain.
315
- combinedSolid.add_boundary(Boundary(boundary.manifold, newDomain))
423
+ # Compute the cutout portion of self's boundary manifold that lies within other.
424
+ cutout = other.compute_cutout(boundary.manifold, cache, True)
425
+ # Intersect cutout with the boundary's trim.
426
+ newTrim = boundary.trim.intersection(cutout, cache)
427
+ if newTrim:
428
+ # Self boundary intersects other, so create a new boundary with the intersected trim.
429
+ combinedSolid.add_boundary(Boundary(boundary.manifold, newTrim))
316
430
 
317
431
  for boundary in other.boundaries:
318
- # Slice other boundary manifold by self.
319
- slice = self.slice(boundary.manifold, cache, True)
320
- # Intersect slice with the boundary's domain.
321
- newDomain = boundary.domain.intersection(slice, cache)
322
- if newDomain:
323
- # Other boundary intersects self, so create a new boundary with the intersected domain.
324
- combinedSolid.add_boundary(Boundary(boundary.manifold, newDomain))
432
+ # Compute the cutout portion of other's boundary manifold that lies within self.
433
+ cutout = self.compute_cutout(boundary.manifold, cache, True)
434
+ # Intersect cutout with the boundary's trim.
435
+ newTrim = boundary.trim.intersection(cutout, cache)
436
+ if newTrim:
437
+ # Other boundary intersects self, so create a new boundary with the intersected trim.
438
+ combinedSolid.add_boundary(Boundary(boundary.manifold, newTrim))
325
439
 
326
440
  return combinedSolid
327
441
 
@@ -363,7 +477,10 @@ class Solid:
363
477
  solid = Solid(dictionary["dimension"], dictionary["containsInfinity"], dictionary.get("metadata", {}))
364
478
  for boundary in dictionary["boundaries"]:
365
479
  manifold = boundary["manifold"]
366
- solid.add_boundary(Boundary(Manifold.factory[manifold.get("type", "Spline")].from_dict(manifold), from_dict(boundary["domain"])))
480
+ solid.add_boundary(
481
+ Boundary(
482
+ Manifold.factory[manifold.get("type", "Spline")].from_dict(manifold),
483
+ from_dict(boundary.get("trim", boundary.get("domain", None)))))
367
484
  return solid
368
485
 
369
486
  # Load json file.
@@ -430,7 +547,7 @@ class Solid:
430
547
  if isinstance(obj, Manifold):
431
548
  return obj.to_dict()
432
549
  if isinstance(obj, Boundary):
433
- return {"type" : "Boundary", "manifold" : obj.manifold, "domain" : obj.domain}
550
+ return {"type" : "Boundary", "manifold" : obj.manifold, "trim" : obj.trim}
434
551
  if isinstance(obj, Solid):
435
552
  return {"type" : "Solid", "dimension" : obj.dimension, "containsInfinity" : obj.containsInfinity, "boundaries" : obj.boundaries, "metadata" : obj.metadata}
436
553
  return super().default(obj)
@@ -438,120 +555,6 @@ class Solid:
438
555
  with open(fileName, 'w', encoding='utf-8') as file:
439
556
  json.dump(solids_or_manifolds, file, indent=4, cls=Encoder)
440
557
 
441
- def slice(self, manifold, cache = None, trimTwin = False):
442
- """
443
- Slice the solid by a manifold.
444
-
445
- Parameters
446
- ----------
447
- manifold : `Manifold`
448
- The `Manifold` used to slice the solid.
449
-
450
- cache : `dict`, optional
451
- A dictionary to cache `Manifold` intersections, speeding computation.
452
-
453
- trimTwin : `bool`, default: False
454
- Trim coincident boundary twins on subsequent calls to slice (avoids duplication of overlapping regions).
455
- Trimming twins is typically only used in conjunction with `intersection`.
456
-
457
- Returns
458
- -------
459
- slice : `Solid`
460
- A region in the domain of `manifold` that intersects with the solid. The region may contain infinity.
461
-
462
- See Also
463
- --------
464
- `intersection` : Intersect two solids.
465
- `Manifold.intersect` : Intersect two manifolds.
466
- `Manifold.cached_intersect` : Intersect two manifolds, caching the result for twins.
467
- `Manifold.complete_slice` : Add any missing inherent (implicit) boundaries of this manifold's domain to the given slice.
468
-
469
- Notes
470
- -----
471
- The dimension of the slice is always one less than the dimension of the solid, since the slice is a region in the domain of the manifold slicing the solid.
472
-
473
- To compute the slice of a manifold intersecting the solid, we intersect the manifold with each boundary of the solid. There may be multiple intersections
474
- between the manifold and the boundary. Each is either a crossing or a coincident region.
475
-
476
- Crossings result in two intersection manifolds: one in the domain of the manifold and one in the domain of the boundary. By construction, both intersection manifolds have the
477
- same domain and the same range of the manifold and boundary (the crossing itself). The intersection manifold in the domain of the manifold becomes a boundary of the slice,
478
- but we must determine the intersection's domain. For that, we slice the boundary's intersection manifold with the boundary's domain. This recursion continues
479
- until the slice is just a point with no domain.
480
-
481
- Coincident regions appear in the domains of the manifold and the boundary. We intersect the boundary's coincident region with the domain of the boundary and then map
482
- it to the domain of the manifold. If the coincident regions have normals in opposite directions, they cancel each other out, so we subtract them from the slice by
483
- inverting the region and intersecting it with the slice. We use this same technique for removing overlapping coincident regions. If the coincident regions have normals
484
- in the same direction, we union them with the slice.
485
- """
486
- assert manifold.range_dimension() == self.dimension
487
-
488
- # Start with an empty slice and no domain coincidences.
489
- slice = Solid(self.dimension-1, self.containsInfinity)
490
- bounds = manifold.range_bounds()
491
- if Solid.disjoint_bounds(bounds, self.bounds):
492
- manifold.complete_slice(slice, self)
493
- return slice
494
- coincidences = []
495
-
496
- # Intersect each of this solid's boundaries with the manifold.
497
- for boundary in self.boundaries:
498
- if Solid.disjoint_bounds(boundary.bounds, bounds):
499
- continue
500
-
501
- # Intersect manifolds, checking if the intersection is already in the cache.
502
- intersections, isTwin = boundary.manifold.cached_intersect(manifold, cache)
503
- if intersections is NotImplemented:
504
- raise NotImplementedError()
505
-
506
- # Each intersection is either a crossing (domain manifold) or a coincidence (solid within the domain).
507
- for intersection in intersections:
508
- (left, right) = (intersection.right, intersection.left) if isTwin else (intersection.left, intersection.right)
509
-
510
- if isinstance(intersection, Manifold.Crossing):
511
- domainSlice = boundary.domain.slice(left, cache)
512
- if domainSlice:
513
- slice.add_boundary(Boundary(right, domainSlice))
514
-
515
- elif isinstance(intersection, Manifold.Coincidence):
516
- # Intersect domain coincidence with the boundary's domain.
517
- left = left.intersection(boundary.domain)
518
- # Invert the domain coincidence (which will remove it) if this is a twin or if the normals point in opposite directions.
519
- #invertCoincidence = trimTwin and (isTwin or intersection.alignment < 0.0)
520
- invertCoincidence = (trimTwin and isTwin) or intersection.alignment < 0.0
521
- # Create the coincidence to hold the trimmed and transformed domain coincidence (left).
522
- coincidence = Solid(left.dimension, left.containsInfinity)
523
- if invertCoincidence:
524
- coincidence.containsInfinity = not coincidence.containsInfinity
525
- # Next, transform the domain coincidence from the boundary to the given manifold.
526
- # Create copies of the manifolds and boundaries, since we are changing them.
527
- for coincidenceBoundary in left.boundaries:
528
- coincidenceManifold = coincidenceBoundary.manifold
529
- if invertCoincidence:
530
- coincidenceManifold = coincidenceManifold.flip_normal()
531
- if isTwin:
532
- coincidenceManifold = coincidenceManifold.translate(-intersection.translation)
533
- coincidenceManifold = coincidenceManifold.transform(intersection.inverse, intersection.transform.T)
534
- else:
535
- coincidenceManifold = coincidenceManifold.transform(intersection.transform, intersection.inverse.T)
536
- coincidenceManifold = coincidenceManifold.translate(intersection.translation)
537
- coincidence.add_boundary(Boundary(coincidenceManifold, coincidenceBoundary.domain))
538
- # Finally, add the domain coincidence to the list of coincidences.
539
- coincidences.append((invertCoincidence, coincidence))
540
-
541
- # Ensure the slice includes the manifold's inherent (implicit) boundaries, making it valid and complete.
542
- manifold.complete_slice(slice, self)
543
-
544
- # Now that we have a complete manifold domain, join it with each domain coincidence.
545
- for coincidence in coincidences:
546
- if coincidence[0]:
547
- # If the domain coincidence is inverted (coincidence[0]), intersect it with the slice, thus removing it.
548
- slice = slice.intersection(coincidence[1], cache)
549
- else:
550
- # Otherwise, union the domain coincidence with the slice, thus adding it.
551
- slice = slice.union(coincidence[1])
552
-
553
- return slice
554
-
555
558
  def surface_integral(self, f, args=(), epsabs=None, epsrel=None, *quadArgs):
556
559
  """
557
560
  Compute the surface integral of a vector field on the boundary of the solid.
@@ -581,7 +584,7 @@ class Solid:
581
584
  -----
582
585
  To compute the surface integral of a scalar function on the boundary, have `f` return the product of the `normal` times the scalar function for the `point`.
583
586
 
584
- `surface_integral` sums the `volume_integral` over the domain of the solid's boundaries, using the integrand: `numpy.dot(f(point, normal), normal)`,
587
+ `surface_integral` sums the `volume_integral` over the trim of the solid's boundaries, using the integrand: `numpy.dot(f(point, normal), normal)`,
585
588
  where `normal` is the cross-product of the boundary tangents (the normal before normalization).
586
589
  """
587
590
  if not isinstance(args, tuple):
@@ -595,17 +598,17 @@ class Solid:
595
598
  sum = 0.0
596
599
 
597
600
  for boundary in self.boundaries:
598
- def integrand(domainPoint):
599
- evalPoint = np.atleast_1d(domainPoint)
601
+ def integrand(trimPoint):
602
+ evalPoint = np.atleast_1d(trimPoint)
600
603
  point = boundary.manifold.evaluate(evalPoint)
601
604
  cofactorNormal = boundary.manifold.normal(evalPoint, False)
602
605
  normal = cofactorNormal / np.linalg.norm(cofactorNormal)
603
606
  fValue = f(point, normal, *args)
604
607
  return np.dot(fValue, cofactorNormal)
605
608
 
606
- if boundary.domain.dimension > 0:
609
+ if boundary.trim.dimension > 0:
607
610
  # Add the contribution to the Volume integral from this boundary.
608
- sum += boundary.domain.volume_integral(integrand)
611
+ sum += boundary.trim.volume_integral(integrand)
609
612
  else:
610
613
  # This is a 1-D boundary (line interval, no domain), so just add the integrand.
611
614
  sum += integrand(0.0)
@@ -636,7 +639,7 @@ class Solid:
636
639
 
637
640
  solid = Solid(self.dimension, self.containsInfinity)
638
641
  for boundary in self.boundaries:
639
- solid.add_boundary(Boundary(boundary.manifold.transform(matrix, matrixInverseTranspose), boundary.domain))
642
+ solid.add_boundary(Boundary(boundary.manifold.transform(matrix, matrixInverseTranspose), boundary.trim))
640
643
  return solid
641
644
 
642
645
  def translate(self, delta):
@@ -657,7 +660,7 @@ class Solid:
657
660
 
658
661
  solid = Solid(self.dimension, self.containsInfinity)
659
662
  for boundary in self.boundaries:
660
- solid.add_boundary(Boundary(boundary.manifold.translate(delta), boundary.domain))
663
+ solid.add_boundary(Boundary(boundary.manifold.translate(delta), boundary.trim))
661
664
  return solid
662
665
 
663
666
  def union(self, other):
@@ -723,7 +726,7 @@ class Solid:
723
726
  And so, `surface_integral(Integral(f) * n[0]) = volume_integral(Integral(f) * first cofactor)` over each boundary manifold's domain.
724
727
 
725
728
  So, we have `volume_integral(f) = volume_integral(Integral(f) * first cofactor)` over each boundary manifold's domain.
726
- To compute the volume integral we sum `volume_integral` over the domain of the solid's boundaries, using the integrand:
729
+ To compute the volume integral we sum `volume_integral` over the trim of the solid's boundaries, using the integrand:
727
730
  `scipy.integrate.quad(f, x0, x [other coordinates fixed]) * first cofactor`.
728
731
  This recursion continues until the boundaries are only points, where we can just sum the integrand.
729
732
  """
@@ -741,8 +744,8 @@ class Solid:
741
744
  x0 = self.any_point()[0]
742
745
 
743
746
  for boundary in self.boundaries:
744
- def domainF(domainPoint):
745
- evalPoint = np.atleast_1d(domainPoint)
747
+ def trimF(trimPoint):
748
+ evalPoint = np.atleast_1d(trimPoint)
746
749
  point = boundary.manifold.evaluate(evalPoint)
747
750
 
748
751
  # fHat passes the scalar given by integrate.quad into the first coordinate of the vector for f.
@@ -758,12 +761,12 @@ class Solid:
758
761
  returnValue = integrate.quad(fHat, x0, point[0], epsabs=epsabs, epsrel=epsrel, *quadArgs)[0] * firstCofactor
759
762
  return returnValue
760
763
 
761
- if boundary.domain.dimension > 0:
764
+ if boundary.trim.dimension > 0:
762
765
  # Add the contribution to the Volume integral from this boundary.
763
- sum += boundary.domain.volume_integral(domainF)
766
+ sum += boundary.trim.volume_integral(trimF)
764
767
  else:
765
768
  # This is a 1-D boundary (line interval, no domain), so just add the integrand.
766
- sum += domainF(0.0)
769
+ sum += trimF(0.0)
767
770
 
768
771
  return sum
769
772
 
bspy/spline.py CHANGED
@@ -338,23 +338,23 @@ class Spline(Manifold):
338
338
  """
339
339
  return bspy._spline_domain.common_basis(splines, indMap)
340
340
 
341
- def complete_slice(self, slice, solid):
341
+ def complete_cutout(self, cutout, solid):
342
342
  """
343
- Add any missing inherent (implicit) boundaries of this spline's domain to the given slice of the
344
- given solid that are needed to make the slice valid and complete.
343
+ Add any missing inherent (implicit) boundaries of this spline's domain to the given cutout of the
344
+ given solid that are needed to make the cutout valid and complete.
345
345
 
346
346
  Parameters
347
347
  ----------
348
- slice : `solid.Solid`
349
- The slice of the given solid formed by the spline. The slice may be incomplete, missing some of the
348
+ cutout : `solid.Solid`
349
+ The cutout of the given solid formed by the spline. The cutout may be incomplete, missing some of the
350
350
  spline's inherent domain boundaries. Its dimension must match `self.domain_dimension()`.
351
351
 
352
352
  solid : `solid.Solid`
353
- The solid being sliced by the manifold. Its dimension must match `self.range_dimension()`.
353
+ The solid determining the cutout of the manifold. Its dimension must match `self.range_dimension()`.
354
354
 
355
355
  See Also
356
356
  --------
357
- `solid.Solid.slice` : slice the solid by a manifold.
357
+ `solid.Solid.compute_cutout` : Compute the cutout portion of the manifold within the solid.
358
358
  `domain` : Return the domain of a spline.
359
359
 
360
360
  Notes
@@ -362,10 +362,10 @@ class Spline(Manifold):
362
362
  A spline's inherent domain is determined by its knot array for each dimension. This method only works for
363
363
  nInd of 1 or 2.
364
364
  """
365
- if self.domain_dimension() != slice.dimension: raise ValueError("Spline domain dimension must match slice dimension")
365
+ if self.domain_dimension() != cutout.dimension: raise ValueError("Spline domain dimension must match cutout dimension")
366
366
  if self.range_dimension() != solid.dimension: raise ValueError("Spline range dimension must match solid dimension")
367
- if slice.dimension != 1 and slice.dimension != 2: raise ValueError("Only works for nInd = 1 or 2")
368
- return bspy._spline_intersection.complete_slice(self, slice, solid)
367
+ if cutout.dimension != 1 and cutout.dimension != 2: raise ValueError("Only works for nInd = 1 or 2")
368
+ return bspy._spline_intersection.complete_cutout(self, cutout, solid)
369
369
 
370
370
  @staticmethod
371
371
  def composition(splines, tolerance = 1.0e-6):
@@ -1017,23 +1017,6 @@ class Spline(Manifold):
1017
1017
  `least_squares` : Fit a least squares approximation to given data.
1018
1018
  """
1019
1019
  return bspy._spline_fitting.fit(domain, f, order, knots, tolerance)
1020
-
1021
- def flip_normal(self):
1022
- """
1023
- Flip the direction of the normal.
1024
-
1025
- Returns
1026
- -------
1027
- spline : `Spline`
1028
- The spline with flipped normal. The spline retains the same tangent space.
1029
-
1030
- See Also
1031
- --------
1032
- `solid.Solid.complement` : Return the complement of the solid: whatever was inside is outside and vice-versa.
1033
- """
1034
- spline = self.copy()
1035
- spline.metadata["flipNormal"] = not self.metadata.get("flipNormal", False)
1036
- return spline
1037
1020
 
1038
1021
  def fold(self, foldedInd):
1039
1022
  """
@@ -1352,7 +1335,7 @@ class Spline(Manifold):
1352
1335
  --------
1353
1336
  `zeros` : Find the roots of a spline (nInd must match nDep).
1354
1337
  `contours` : Find all the contour curves of a spline.
1355
- `solid.Solid.slice` : slice the solid by a manifold.
1338
+ `solid.Solid.compute_cutout` : Compute the cutout portion of the manifold within the solid.
1356
1339
 
1357
1340
  Notes
1358
1341
  -----
@@ -1637,6 +1620,23 @@ class Spline(Manifold):
1637
1620
  if indMap is not None:
1638
1621
  indMap = [(mapping, mapping, False) if np.isscalar(mapping) else (*mapping, False) for mapping in indMap]
1639
1622
  return bspy._spline_operations.multiplyAndConvolve(self, other, indMap, productType)
1623
+
1624
+ def negate_normal(self):
1625
+ """
1626
+ Negate the direction of the normal.
1627
+
1628
+ Returns
1629
+ -------
1630
+ spline : `Spline`
1631
+ The spline with negated normal. The spline retains the same tangent space.
1632
+
1633
+ See Also
1634
+ --------
1635
+ `solid.Solid.complement` : Return the complement of the solid: whatever was inside is outside and vice-versa.
1636
+ """
1637
+ spline = self.copy()
1638
+ spline.metadata["negateNormal"] = not self.metadata.get("negateNormal", False)
1639
+ return spline
1640
1640
 
1641
1641
  def normal(self, uvw, normalize=True, indices=None):
1642
1642
  """
bspy/splineOpenGLFrame.py CHANGED
@@ -1543,16 +1543,16 @@ class SplineOpenGLFrame(OpenGLFrame):
1543
1543
  self.connection = None
1544
1544
  endpoints = []
1545
1545
  for curve in solid.boundaries:
1546
- curve.domain.boundaries.sort(key=lambda boundary: (boundary.manifold.evaluate(0.0), -boundary.manifold.normal(0.0)))
1546
+ curve.trim.boundaries.sort(key=lambda boundary: (boundary.manifold.evaluate(0.0), -boundary.manifold.normal(0.0)))
1547
1547
  leftB = 0
1548
1548
  rightB = 0
1549
- boundaryCount = len(curve.domain.boundaries)
1549
+ boundaryCount = len(curve.trim.boundaries)
1550
1550
  while leftB < boundaryCount:
1551
- if curve.domain.boundaries[leftB].manifold.normal(0.0) < 0.0:
1552
- leftPoint = curve.domain.boundaries[leftB].manifold.evaluate(0.0)[0]
1551
+ if curve.trim.boundaries[leftB].manifold.normal(0.0) < 0.0:
1552
+ leftPoint = curve.trim.boundaries[leftB].manifold.evaluate(0.0)[0]
1553
1553
  while rightB < boundaryCount:
1554
- rightPoint = curve.domain.boundaries[rightB].manifold.evaluate(0.0)[0]
1555
- if leftPoint - Manifold.minSeparation < rightPoint and curve.domain.boundaries[rightB].manifold.normal(0.0) > 0.0:
1554
+ rightPoint = curve.trim.boundaries[rightB].manifold.evaluate(0.0)[0]
1555
+ if leftPoint - Manifold.minSeparation < rightPoint and curve.trim.boundaries[rightB].manifold.normal(0.0) > 0.0:
1556
1556
  t = curve.manifold.tangent_space(leftPoint)[:,0]
1557
1557
  n = curve.manifold.normal(leftPoint)
1558
1558
  clockwise = t[0] * n[1] - t[1] * n[0] > 0.0
@@ -1567,7 +1567,7 @@ class SplineOpenGLFrame(OpenGLFrame):
1567
1567
  rightB += 1
1568
1568
  leftB += 1
1569
1569
 
1570
- # Second, collect all valid pairings of endpoints (normal not flipped between segments).
1570
+ # Second, collect all valid pairings of endpoints (normal not negated between segments).
1571
1571
  Connection = namedtuple('Connection', ('distance', 'ep1', 'ep2'))
1572
1572
  connections = []
1573
1573
  for i, ep1 in enumerate(endpoints[:-1]):
bspy/viewer.py CHANGED
@@ -160,8 +160,8 @@ class Viewer(tk.Tk):
160
160
  elif isinstance(spline, Boundary):
161
161
  boundary = spline
162
162
  if isinstance(boundary.manifold, Hyperplane):
163
- uvMin = boundary.domain.bounds[:,0]
164
- uvMax = boundary.domain.bounds[:,1]
163
+ uvMin = boundary.trim.bounds[:,0]
164
+ uvMax = boundary.trim.bounds[:,1]
165
165
  if (uvMax - uvMin).min() < 1.0e-8:
166
166
  return
167
167
  xyzMinMin = boundary.manifold.evaluate(uvMin)
@@ -174,7 +174,7 @@ class Viewer(tk.Tk):
174
174
  spline.metadata = boundary.manifold.metadata # Ensure the spline representing the hyperplane shares the same metadata
175
175
  elif isinstance(boundary.manifold, Spline):
176
176
  spline = boundary.manifold
177
- tesselation = self.frame.tessellate2DSolid(boundary.domain)
177
+ tesselation = self.frame.tessellate2DSolid(boundary.trim)
178
178
  if tesselation is not None:
179
179
  if not hasattr(spline, "cache"):
180
180
  spline.cache = {}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bspy
3
- Version: 4.4.1
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 slice solids. Solids can be saved and loaded in json format.
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)