bspy 4.1__py3-none-any.whl → 4.3__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_block.py ADDED
@@ -0,0 +1,460 @@
1
+ import numpy as np
2
+ import bspy.spline
3
+ import bspy._spline_domain
4
+ import bspy._spline_evaluation
5
+ import bspy._spline_intersection
6
+ import bspy._spline_fitting
7
+ import bspy._spline_operations
8
+
9
+ class SplineBlock:
10
+ """
11
+ A class to process an array-like collection of splines that represent a system of equations.
12
+
13
+ Parameters
14
+ ----------
15
+ block : an array-like collection of rows of splines (a list of lists)
16
+ The block of splines represents a system of equations. Splines in the same row are treated as if they are added together.
17
+ Each row need not have the same number of splines, but all splines in a row must have the same
18
+ number of dependent variables (same nDep). Corresponding independent variables must have the same domain.
19
+
20
+ For example, say you wanted to represent the system:
21
+ F(u, v, w) + G(u) = 0, h(u, v) = 0, where F and G are 2D vector functions and h is a scalar function.
22
+ After fitting F, G, and h with splines, you'd form the following spline block: [[F, G], [h]].
23
+
24
+ You may optionally supply maps for independent variables to represent complex systems of equations. For example,
25
+ say you wanted to represent a similar systems as before, but with a more complicated relationship between the
26
+ independent variables: F(u, v, w) + G(v, s) = 0, h(u, t, w, s) = 0.
27
+ First, you'd assign consecutive indices for each variable starting at zero. For example, (s, t, u, v, w) could be indexed as (0, 1, 2, 3, 4, 5).
28
+ Then, you'd provide maps for each spline's independent variables to form the following spline block:
29
+ [[([3, 4, 5], F), ([4, 0], G)], [([3, 1, 5, 0], h)]].
30
+ Notice how each spline from the first example is replaced by a (map, spline) tuple in the complex second example.
31
+ """
32
+
33
+ @staticmethod
34
+ def _map_args(map, args):
35
+ return [arg[map] if isinstance(arg, np.ndarray) else [arg[index] for index in map] for arg in args]
36
+
37
+ def _block_evaluation(self, returnShape, splineFunction, args):
38
+ value = np.zeros(returnShape, self.coefsDtype)
39
+ nDep = 0
40
+ for row in self.block:
41
+ for map, spline in row:
42
+ value[nDep:nDep + spline.nDep] += splineFunction(spline, *SplineBlock._map_args(map, args))
43
+ nDep += spline.nDep
44
+ return value
45
+
46
+ def _block_operation(self, splineFunction, args):
47
+ newBlock = []
48
+ for row in self.block:
49
+ newRow = []
50
+ for map, spline in row:
51
+ newRow.append((map, splineFunction(spline, *SplineBlock._map_args(map, args))))
52
+ newBlock.append(newRow)
53
+ return SplineBlock(newBlock)
54
+
55
+ def __init__(self, block):
56
+ if isinstance(block, bspy.spline.Spline):
57
+ block = [[block]]
58
+ elif isinstance(block[0], bspy.spline.Spline) or (len(block) > 1 and isinstance(block[1], bspy.spline.Spline)):
59
+ block = [block]
60
+
61
+ self.block = []
62
+ self.nInd = 0
63
+ self.nDep = 0
64
+ self.knotsDtype = None
65
+ self.coefsDtype = None
66
+ self.size = 0
67
+ domain = {}
68
+ for row in block:
69
+ rowInd = 0
70
+ rowDep = 0
71
+ indSet = set()
72
+ newRow = []
73
+ for entry in row:
74
+ if isinstance(entry, bspy.spline.Spline):
75
+ spline = entry
76
+ map = list(range(rowInd, rowInd + spline.nInd))
77
+ else:
78
+ (map, spline) = entry
79
+ map = list(map) # Convert to list and make a copy
80
+ rowInd += spline.nInd
81
+ if rowDep == 0:
82
+ rowDep = spline.nDep
83
+ if self.nDep == 0:
84
+ self.knotsDtype = spline.knots[0].dtype
85
+ self.coefsDtype = spline.coefs.dtype
86
+ elif rowDep != spline.nDep:
87
+ raise ValueError("All splines in the same row must have the same nDep")
88
+ d = spline.domain()
89
+ for ind, i in enumerate(map):
90
+ if i in indSet:
91
+ raise ValueError(f"Multiple splines in the same row map to independent variable {i}")
92
+ else:
93
+ indSet.add(i)
94
+ if i in domain:
95
+ if domain[i][0] != d[ind, 0] or domain[i][1] != d[ind, 1]:
96
+ raise ValueError("Domains of independent variables must match")
97
+ else:
98
+ domain[i] = d[ind]
99
+ newRow.append((map, spline))
100
+
101
+ if rowDep > 0:
102
+ self.nDep += rowDep
103
+ self.size += len(row)
104
+ self.block.append(newRow)
105
+
106
+ self.nInd = len(domain)
107
+ self._domain = []
108
+ for i in range(self.nInd):
109
+ if i in domain:
110
+ self._domain.append(domain[i])
111
+ else:
112
+ raise ValueError(f"Block is missing independent variable {i}")
113
+ self._domain = np.array(self._domain, self.knotsDtype)
114
+
115
+ def __call__(self, uvw):
116
+ return self.evaluate(uvw)
117
+
118
+ def __repr__(self):
119
+ return f"SplineBlock({self.block})"
120
+
121
+ def contours(self):
122
+ """
123
+ Find all the contour curves of a block of splines whose `nInd` is one larger than its `nDep`.
124
+
125
+ Returns
126
+ -------
127
+ curves : `iterable`
128
+ A collection of `Spline` curves, `u(t)`, each of whose domain is [0, 1], whose range is
129
+ in the parameter space of the given spline, and which satisfy `self(u(t)) = 0`.
130
+
131
+ See Also
132
+ --------
133
+ `zeros` : Find the roots of a block of splines (nInd must match nDep).
134
+
135
+ Notes
136
+ -----
137
+ Uses `zeros` to find all intersection points and `Spline.contour` to find individual intersection curves.
138
+ The algorithm used to to find all intersection curves is from Grandine, Thomas A., and Frederick W. Klein IV.
139
+ "A new approach to the surface intersection problem." Computer Aided Geometric Design 14, no. 2 (1997): 111-134.
140
+ """
141
+ return bspy._spline_intersection.contours(self)
142
+
143
+ def contract(self, uvw):
144
+ """
145
+ Contract a spline block by assigning a fixed value to one or more of its independent variables.
146
+
147
+ Parameters
148
+ ----------
149
+ uvw : `iterable`
150
+ An iterable of length `nInd` that specifies the values of each independent variable to contract.
151
+ A value of `None` for an independent variable indicates that variable should remain unchanged.
152
+
153
+ Returns
154
+ -------
155
+ block : `SplineBlock`
156
+ The contracted spline block.
157
+ """
158
+ # First, remap the remaining independent variables (the ones not fixed by uvw).
159
+ remap = []
160
+ newIndex = 0
161
+ for value in uvw:
162
+ if value is None:
163
+ remap.append(newIndex)
164
+ newIndex += 1
165
+ else:
166
+ remap.append(None)
167
+
168
+ # Next, rebuild the block with contracted splines.
169
+ newBlock = []
170
+ for row in self.block:
171
+ newRow = []
172
+ for map, spline in row:
173
+ spline = spline.contract([uvw[index] for index in map])
174
+ map = [remap[ind] for ind in map if uvw[ind] is None]
175
+ newRow.append((map, spline))
176
+ newBlock.append(newRow)
177
+ return SplineBlock(newBlock)
178
+
179
+ def derivative(self, with_respect_to, uvw):
180
+ """
181
+ Compute the derivative of the spline block at given parameter values.
182
+
183
+ Parameters
184
+ ----------
185
+ with_respect_to : `iterable`
186
+ An iterable of length `nInd` that specifies the integer order of derivative for each independent variable.
187
+ A zero-order derivative just evaluates the spline normally.
188
+
189
+ uvw : `iterable`
190
+ An iterable of length `nInd` that specifies the values of each independent variable (the parameter values).
191
+
192
+ Returns
193
+ -------
194
+ value : `numpy.array`
195
+ The value of the derivative of the spline block at the given parameter values (array of size nDep).
196
+ """
197
+ return self._block_evaluation(self.nDep, bspy._spline_evaluation.derivative, (with_respect_to, uvw))
198
+
199
+ def domain(self):
200
+ """
201
+ Return the domain of a spline block.
202
+
203
+ Returns
204
+ -------
205
+ bounds : `numpy.array`
206
+ nInd x 2 array of the lower and upper bounds on each of the independent variables.
207
+ """
208
+ return self._domain
209
+
210
+ def evaluate(self, uvw):
211
+ """
212
+ Compute the value of the spline block at given parameter values.
213
+
214
+ Parameters
215
+ ----------
216
+ uvw : `iterable`
217
+ An iterable of length `nInd` that specifies the values of each independent variable (the parameter values).
218
+
219
+ Returns
220
+ -------
221
+ value : `numpy.array`
222
+ The value of the spline block at the given parameter values (array of size nDep).
223
+ """
224
+ return self._block_evaluation(self.nDep, bspy._spline_evaluation.evaluate, (uvw,))
225
+
226
+ def jacobian(self, uvw):
227
+ """
228
+ Compute the value of the spline block's Jacobian at given parameter values.
229
+
230
+ Parameters
231
+ ----------
232
+ uvw : `iterable`
233
+ An iterable of length `nInd` that specifies the values of each independent variable (the parameter values).
234
+
235
+ Returns
236
+ -------
237
+ value : `numpy.array`
238
+ The value of the spline block's Jacobian at the given parameter values. The shape of the return value is (nDep, nInd).
239
+ """
240
+ jacobian = np.zeros((self.nDep, self.nInd), self.coefsDtype)
241
+ uvw = np.atleast_1d(uvw)
242
+ nDep = 0
243
+ for row in self.block:
244
+ for map, spline in row:
245
+ jacobian[nDep:nDep + spline.nDep, map] += spline.jacobian(uvw[map])
246
+ nDep += spline.nDep
247
+ return jacobian
248
+
249
+ def normal(self, uvw, normalize=True, indices=None):
250
+ """
251
+ Compute the normal of the spline block at given parameter values. The number of independent variables must be
252
+ one different than the number of dependent variables.
253
+
254
+ Parameters
255
+ ----------
256
+ uvw : `iterable`
257
+ An iterable of length `nInd` that specifies the values of each independent variable (the parameter values).
258
+
259
+ normalize : `boolean`, optional
260
+ If True the returned normal will have unit length (the default). Otherwise, the normal's length will
261
+ be the area of the tangent space (for two independent variables, its the length of the cross product of tangent vectors).
262
+
263
+ indices : `iterable`, optional
264
+ An iterable of normal indices to calculate. For example, `indices=(0, 3)` will return a vector of length 2
265
+ with the first and fourth values of the normal. If `None`, all normal values are returned (the default).
266
+
267
+ Returns
268
+ -------
269
+ normal : `numpy.array`
270
+ The normal vector of the spline block at the given parameter values.
271
+
272
+ See Also
273
+ --------
274
+ `normal_spline` : Compute a spline that evaluates to the normal of the given spline block (not normalized).
275
+
276
+ Notes
277
+ -----
278
+ Attentive readers will notice that the number of independent variables could be one more than the number of
279
+ dependent variables (instead of one less, as is typical). In that case, the normal represents the null space of
280
+ the matrix formed by the tangents of the spline. If the null space is greater than one dimension, the normal will be zero.
281
+ """
282
+ return bspy._spline_evaluation.normal(self, uvw, normalize, indices)
283
+
284
+ def normal_spline(self, indices=None):
285
+ """
286
+ Compute a spline that evaluates to the normal of the given spline block. The length of the normal
287
+ is the area of the tangent space (for two independent variables, its the length of the cross product of tangent vectors).
288
+ The number of independent variables must be one different than the number of dependent variables.
289
+ Find all the contour curves of a block of splines whose `nInd` is one larger than its `nDep`.
290
+
291
+ Parameters
292
+ ----------
293
+ indices : `iterable`, optional
294
+ An iterable of normal indices to calculate. For example, `indices=(0, 3)` will make the returned spline compute a vector of length 2
295
+ with the first and fourth values of the normal. If `None`, all normal values are returned (the default).
296
+
297
+ Returns
298
+ -------
299
+ spline : `Spline`
300
+ The spline that evaluates to the normal of the given spline block.
301
+
302
+ See Also
303
+ --------
304
+ `normal` : Compute the normal of the spline block at given parameter values.
305
+
306
+ Notes
307
+ -----
308
+ Attentive readers will notice that the number of independent variables could be one more than the number of
309
+ dependent variables (instead of one less, as is typical). In that case, the normal represents the null space of
310
+ the matrix formed by the tangents of the spline block. If the null space is greater than one dimension, the normal will be zero.
311
+ """
312
+ return bspy._spline_operations.normal_spline(self, indices)
313
+
314
+ def range_bounds(self):
315
+ """
316
+ Return the range of a spline block as lower and upper bounds on each of the
317
+ dependent variables.
318
+ """
319
+ return self._block_evaluation((self.nDep, 2), bspy._spline_evaluation.range_bounds, [])
320
+
321
+ def reparametrize(self, newDomain):
322
+ """
323
+ Reparametrize a spline block to match new domain bounds. The number of knots and coefficients remain unchanged.
324
+
325
+ Parameters
326
+ ----------
327
+ newDomain : array-like
328
+ nInd x 2 array of the new lower and upper bounds on each of the independent variables (same form as
329
+ returned from `domain`). If a bound pair is `None` then the original bound (and knots) are left unchanged.
330
+ For example, `[[0.0, 1.0], None]` will reparametrize the first independent variable and leave the second unchanged)
331
+
332
+ Returns
333
+ -------
334
+ block : `SplineBlock`
335
+ Reparametrized spline block.
336
+
337
+ See Also
338
+ --------
339
+ `domain` : Return the domain of a spline block.
340
+ """
341
+ return self._block_operation(bspy._spline_domain.reparametrize, (newDomain,))
342
+
343
+ def split(self, minContinuity = 0, breaks = None):
344
+ """
345
+ Split a spline block into separate pieces.
346
+
347
+ Parameters
348
+ ----------
349
+ minContinuity : `int`, optional
350
+ The minimum expected continuity of each spline block piece. The default is zero, for C0 continuity.
351
+
352
+ breaks : `iterable` of length `nInd` or `None`, optional
353
+ An iterable that specifies the breaks at which to separate the spline block.
354
+ len(breaks[ind]) == 0 if there the spline block isn't separated for the `ind` independent variable.
355
+ If breaks is `None` (the default), the spline block will only be separated at discontinuities.
356
+
357
+ Returns
358
+ -------
359
+ splineBlockArray : array of `SplineBlock`
360
+ A array of spline blocks with nInd dimensions containing the spline block pieces.
361
+ """
362
+ if minContinuity < 0: raise ValueError("minContinuity must be >= 0")
363
+ if self.nInd < 1: return self
364
+
365
+ # Step 1: Determine all the breaks.
366
+ breakList = [set() for i in range(self.nInd)]
367
+ if breaks is not None:
368
+ if len(breaks) != self.nInd: raise ValueError("Invalid breaks")
369
+ for breakSet, knots, domain in zip(breakList, breaks, self.domain()):
370
+ for knot in knots:
371
+ if knot < domain[0] or knot > domain[1]: raise ValueError("Break outside of domain")
372
+ if domain[0] < knot < domain[1]:
373
+ breakSet.add(knot)
374
+
375
+ for row in self.block:
376
+ for map, spline in row:
377
+ for i, order, knots in zip(range(spline.nInd), spline.order, spline.knots):
378
+ unique, counts = np.unique(knots[order:], return_counts=True) # Skip left end
379
+ for knot, count in zip(unique, counts):
380
+ if count > order - 1 - minContinuity:
381
+ breakList[map[i]].add(knot)
382
+
383
+ # Step 2: Determine the size and shape of the splineBlockArray and allocate it.
384
+ size = 1
385
+ shape = []
386
+ for i, breakSet in enumerate(breakList):
387
+ count = len(breakSet)
388
+ shape.insert(0, count) # We build the transpose due to indexing arithmetic
389
+ size *= count
390
+ breakList[i] = sorted(breakSet)
391
+ splineBlockArray = np.empty(size, object)
392
+
393
+ # Step 3: Split up the spline block.
394
+ domain = self.domain().copy()
395
+ for i in range(size):
396
+ index = i
397
+ for j, knots in enumerate(breakList):
398
+ count = len(knots)
399
+ ix = index % count
400
+ index = index // count
401
+ if domain[j, 1] != knots[ix]:
402
+ if ix == 0:
403
+ domain[j, 0] = self._domain[j, 0]
404
+ else:
405
+ domain[j, 0] = domain[j, 1]
406
+ domain[j, 1] = knots[ix]
407
+
408
+ splineBlockArray[i] = self.trim(domain)
409
+
410
+ # Step 4: Reshape and transpose splineBlockArray
411
+ return splineBlockArray.reshape(shape).T
412
+
413
+ def trim(self, newDomain):
414
+ """
415
+ Trim the domain of a spline block.
416
+
417
+ Parameters
418
+ ----------
419
+ newDomain : array-like
420
+ nInd x 2 array of the new lower and upper bounds on each of the independent variables (same form as
421
+ returned from `domain`). If a bound is None or nan then the original bound (and knots) are left unchanged.
422
+
423
+ Returns
424
+ -------
425
+ block : `SplineBlock`
426
+ Trimmed spline block.
427
+ """
428
+ return self._block_operation(bspy._spline_domain.trim, (newDomain,))
429
+
430
+ def zeros(self, epsilon=None, initialScale=None):
431
+ """
432
+ Find the roots of a block of splines (nInd must match nDep).
433
+
434
+ Parameters
435
+ ----------
436
+ epsilon : `float`, optional
437
+ Tolerance for root precision. The root will be within epsilon of the actual root.
438
+ The default is the machine epsilon.
439
+
440
+ initialScale : array-like, optional
441
+ The initial scale of each dependent variable (as opposed to the current scale of
442
+ the spline block, which may have been normalized). The default is an array of ones (size nDep).
443
+
444
+ Returns
445
+ -------
446
+ roots : `iterable`
447
+ An iterable containing the roots of the block of splines. If the block is
448
+ zero over an interval, that root will appear as a tuple of the interval.
449
+ For curves (nInd == 1), the roots are ordered.
450
+
451
+ See Also
452
+ --------
453
+ `contours` : Find all the contour curves of a spline block whose `nInd` is one larger than its `nDep`.
454
+
455
+ Notes
456
+ -----
457
+ Implements a variation of the projected-polyhedron technique from Sherbrooke, Evan C., and Nicholas M. Patrikalakis.
458
+ "Computation of the solutions of nonlinear polynomial systems." Computer Aided Geometric Design 10, no. 5 (1993): 379-405.
459
+ """
460
+ return bspy._spline_intersection.zeros_using_projected_polyhedron(self, epsilon, initialScale)
bspy/viewer.py CHANGED
@@ -144,20 +144,26 @@ class Viewer(tk.Tk):
144
144
  solid = spline
145
145
  if solid.dimension != 3:
146
146
  return
147
- if name is None:
148
- name = "Solid"
149
- iid = self.treeview.insert('', 'end', text=name, open=False)
147
+ if name is not None:
148
+ solid.metadata["Name"] = name
149
+ elif "Name" not in solid.metadata:
150
+ solid.metadata["Name"] = f"Solid({solid.dimension}, {solid.containsInfinity})"
151
+ iid = self.treeview.insert('', 'end', text=solid.metadata["Name"], open=False)
150
152
  self.splineList[iid] = solid
151
153
  self.solidList.append(solid)
152
154
  if draw:
153
155
  self.treeview.selection_add(iid)
154
156
  for i, boundary in enumerate(solid.boundaries):
155
- self.list(boundary, f"{name} boundary {i+1}", fillColor, lineColor, options, False, iid)
157
+ if "Name" not in boundary.manifold.metadata:
158
+ boundary.manifold.metadata["Name"] = f"Boundary {i}"
159
+ self.list(boundary, None, fillColor, lineColor, options, False, iid)
156
160
  elif isinstance(spline, Boundary):
157
161
  boundary = spline
158
162
  if isinstance(boundary.manifold, Hyperplane):
159
163
  uvMin = boundary.domain.bounds[:,0]
160
164
  uvMax = boundary.domain.bounds[:,1]
165
+ if (uvMax - uvMin).min() < 1.0e-8:
166
+ return
161
167
  xyzMinMin = boundary.manifold.evaluate(uvMin)
162
168
  xyzMinMax = boundary.manifold.evaluate((uvMin[0], uvMax[1]))
163
169
  xyzMaxMin = boundary.manifold.evaluate((uvMax[0], uvMin[1]))
@@ -165,15 +171,20 @@ class Viewer(tk.Tk):
165
171
  spline = Spline(2, 3, (2, 2), (2, 2),
166
172
  np.array((uvMin, uvMin, uvMax, uvMax), np.float32).T,
167
173
  np.array(((xyzMinMin, xyzMaxMin), (xyzMinMax, xyzMaxMax)), np.float32).T)
174
+ spline.metadata = boundary.manifold.metadata # Ensure the spline representing the hyperplane shares the same metadata
168
175
  elif isinstance(boundary.manifold, Spline):
169
176
  spline = boundary.manifold
170
- if not hasattr(spline, "cache"):
171
- spline.cache = {}
172
- spline.cache["trim"] = self.frame.tessellate2DSolid(boundary.domain)
177
+ tesselation = self.frame.tessellate2DSolid(boundary.domain)
178
+ if tesselation is not None:
179
+ if not hasattr(spline, "cache"):
180
+ spline.cache = {}
181
+ spline.cache["trim"] = tesselation
173
182
  self.list(spline, name, fillColor, lineColor, options, draw, parentIID)
174
183
  else:
175
- if "Name" not in spline.metadata:
176
- spline.metadata["Name"] = f"Spline({spline.nInd}, {spline.nDep})" if name is None else name
184
+ if name is not None:
185
+ spline.metadata["Name"] = name
186
+ elif "Name" not in spline.metadata:
187
+ spline.metadata["Name"] = f"Spline({spline.nInd}, {spline.nDep})"
177
188
  if fillColor is not None:
178
189
  spline.metadata["fillColor"] = fillColor
179
190
  if lineColor is not None:
@@ -210,7 +221,7 @@ class Viewer(tk.Tk):
210
221
  if isinstance(spline, Spline):
211
222
  self.list(spline)
212
223
  else:
213
- self.list_solid(spline)
224
+ self.list(spline)
214
225
 
215
226
  def erase_all(self):
216
227
  """Stop drawing all splines. Splines remain in the treeview."""
@@ -223,8 +234,7 @@ class Viewer(tk.Tk):
223
234
  """Remove splines from the treeview."""
224
235
  for item in self.treeview.selection():
225
236
  spline = self.splineList.pop(item)
226
- self.splineDrawList.remove(spline)
227
- self.treeview.delete(*self.treeview.selection())
237
+ self.treeview.delete(item)
228
238
  self.update()
229
239
 
230
240
  def empty(self):
@@ -263,7 +273,7 @@ class Viewer(tk.Tk):
263
273
  for item in self.treeview.selection():
264
274
  spline = self.splineList[item]
265
275
  if isinstance(spline, Spline):
266
- coefs = spline.cache["coefs32"].T[:3]
276
+ coefs = spline.cache["xyzCoefs32"].T
267
277
  coefsAxis = tuple(range(1, spline.nInd + 1))
268
278
  if gotOne:
269
279
  splineMin = np.minimum(splineMin, coefs.min(axis=coefsAxis))
@@ -276,7 +286,7 @@ class Viewer(tk.Tk):
276
286
  elif isinstance(spline, Solid):
277
287
  for subitem in self.treeview.get_children(item):
278
288
  spline = self.splineList[subitem]
279
- coefs = spline.cache["coefs32"].T[:3]
289
+ coefs = spline.cache["xyzCoefs32"].T
280
290
  coefsAxis = tuple(range(1, spline.nInd + 1))
281
291
  if gotOne:
282
292
  splineMin = np.minimum(splineMin, coefs.min(axis=coefsAxis))
@@ -426,7 +436,7 @@ class Viewer(tk.Tk):
426
436
  fillColor : `numpy.array`
427
437
  Array of four floats (r, g, b, a) in the range [0, 1].
428
438
  """
429
- return spline.metadata["fillColor"]
439
+ return np.array(spline.metadata["fillColor"], np.float32)
430
440
 
431
441
  @staticmethod
432
442
  def set_fill_color(spline, r, g=None, b=None, a=None):
@@ -467,7 +477,7 @@ class Viewer(tk.Tk):
467
477
  lineColor : `numpy.array`
468
478
  Array of four floats (r, g, b, a) in the range [0, 1].
469
479
  """
470
- return spline.metadata["lineColor"]
480
+ return np.array(spline.metadata["lineColor"], np.float32)
471
481
 
472
482
  @staticmethod
473
483
  def set_line_color(spline, r, g=None, b=None, a=None):
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bspy
3
- Version: 4.1
3
+ Version: 4.3
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
7
7
  Author-email: ericbrec@msn.com
8
8
  License: MIT
9
9
  Project-URL: Bug Tracker, http://github.com/ericbrec/BSpy/issues
10
- Keywords: opengl,bspline,B-spline,nub,tkinter
10
+ Keywords: bspline,B-spline,nub,solid,solid modeling,geometry,csg,opengl,tkinter
11
11
  Classifier: License :: OSI Approved :: MIT License
12
12
  Classifier: Environment :: Win32 (MS Windows)
13
13
  Classifier: Environment :: Console
@@ -34,14 +34,22 @@ Library for manipulating and rendering B-spline curves, surfaces, and multidimen
34
34
 
35
35
  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).
36
36
 
37
- The [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html) class has a method to fit multidimensional data for
38
- scalar and vector functions of single and multiple variables. It also has methods to create points, lines, circular arcs, spheres, cones, cylinders, tori, ruled surfaces, surfaces of revolution, and four-sided patches.
37
+ The [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html) class has a method to fit multidimensional data for scalar and vector functions of single and multiple variables. It also can fit splines to functions, to solutions for ordinary differential equations (ODEs), and to geodesics.
38
+ Spline has methods to create points, lines, circular arcs, spheres, cones, cylinders, tori, ruled surfaces, surfaces of revolution, and four-sided patches.
39
39
  Other methods add, subtract, and multiply splines, as well as confine spline curves to a given range.
40
- There are methods to evaluate spline values, derivatives, integrals, normals, curvature, and the Jacobian, as well as methods that return spline representations of derivatives, normals, integrals, graphs, and convolutions. In addition, there are methods to manipulate the domain of splines, including trim, join, reparametrize, transpose, reverse, add and remove knots, elevate and extrapolate, and fold and unfold. There are methods to manipulate the range of splines, including dot product, cross product, translate, rotate, scale, and transform. Finally, there are methods to compute the zeros and contours of a spline and to intersect two splines.
40
+ There are methods to evaluate spline values, derivatives, normals, integrals, continuity, curvature, and the Jacobian, as well as methods that return spline representations of derivatives, normals, integrals, graphs, and convolutions.
41
+ In addition, there are methods to manipulate the domain of splines, including trim, join, split, reparametrize, transpose, reverse, add and remove knots, elevate and extrapolate, and fold and unfold.
42
+ There are methods to manipulate the range of splines, including dot product, cross product, translate, rotate, scale, and transform.
43
+ Finally, there are methods to compute the zeros and contours of a spline and to intersect two splines.
44
+ Splines can be saved and loaded in json format.
41
45
 
42
46
  The [Hyperplane](https://ericbrec.github.io/BSpy/bspy/hyperplane.html) class has methods to create individual hyperplanes in any dimension, along with axis-aligned hyperplanes and hypercubes.
43
47
 
44
- 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.
48
+ 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.
49
+
50
+ The [SplineBlock](https://ericbrec.github.io/BSpy/bspy/spline_block.html) class has methods to process an array-like collection of splines that represent a system of equations. There are highly-optimized methods to compute the contours and zeros of a spline block, as well as a variety of methods to manipulate and evaluate a spline block and its derivatives.
51
+
52
+ The [BSpyConvert](https://pypi.org/project/BSpyConvert/) package converts BSpy splines and solid models to and from [OpenCascade (OCCT)](https://dev.opencascade.org/) equivalents and a variety of geometry and CAD file formats, including STEP, IGES, and STL.
45
53
 
46
54
  The [SplineOpenGLFrame](https://ericbrec.github.io/BSpy/bspy/splineOpenGLFrame.html) class is an
47
55
  [OpenGLFrame](https://pypi.org/project/pyopengltk/) with custom shaders to render spline curves and surfaces. Spline surfaces with more
@@ -0,0 +1,18 @@
1
+ bspy/__init__.py,sha256=LnJx7iHah7A4vud9y64LR61rBtYnuhV4Wno9O2IEK1I,1499
2
+ bspy/_spline_domain.py,sha256=bQQsJlKstIYdbEKIW7vr-7nTKat8y9thYc7jxzZHNFQ,33238
3
+ bspy/_spline_evaluation.py,sha256=WIv0tLZNLy0uHNj9YwR7vbputgT2Mn5QDXZzlD6ousk,9638
4
+ bspy/_spline_fitting.py,sha256=CEkmUQalXTT5N5ZOJpFF4Y2DI-ARlZap9IaCJ_Zg2Ws,50762
5
+ bspy/_spline_intersection.py,sha256=8FPTh4IDtzkRpieNtlnw8VhLabPzY4E_LWDaGxHIMTM,66973
6
+ bspy/_spline_operations.py,sha256=8yJGp4iVVvQ1zcUHAKgNq2TMIjDUhacf5XSoHp2jmVo,42799
7
+ bspy/hyperplane.py,sha256=gnVZ7rjisGpzHfm1moItyzq8mO7HguzzpY4dpFwyDiw,24840
8
+ bspy/manifold.py,sha256=vjgyz0M1mkgenUnTIbX7NFg1fUCgXtStr6ofF4oSLgg,14470
9
+ bspy/solid.py,sha256=ufNs5JV0jQ1A13pUY61N0pcW6Ep-DZmXUau7GHHcKk4,36992
10
+ bspy/spline.py,sha256=1USitAm1FIQg5JuWa1xLrVQQ2cWRGfP4VhQnS8XOKc8,101377
11
+ bspy/splineOpenGLFrame.py,sha256=N8elVJrt24_utOSoTaM5Ue5De2M4DxrquyB7o2lLLD4,96256
12
+ bspy/spline_block.py,sha256=O8MzfBEygVdAx57DoJMwzjkw349BQqht7_RVu8MO0Fg,20127
13
+ bspy/viewer.py,sha256=_iQCyEpsBFPBLLuHq7tc43IPVvlcqxdp0Hig0uvpQns,34349
14
+ bspy-4.3.dist-info/LICENSE,sha256=nLfJULN68Jw6GfCJp4xeMksGuRdyWNdgEsZGjw2twig,1091
15
+ bspy-4.3.dist-info/METADATA,sha256=lV6eciVqRf3FqWslbTD5OWb3gMD6-gv6WB-hkSRadPM,7044
16
+ bspy-4.3.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
17
+ bspy-4.3.dist-info/top_level.txt,sha256=fotZnJn6aCwgUbBEV3hslIko7Nw-eqtHLq2eyJLlFsY,5
18
+ bspy-4.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (75.6.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
bspy-4.1.dist-info/RECORD DELETED
@@ -1,17 +0,0 @@
1
- bspy/__init__.py,sha256=q8XOJK3FsbHybfy2CSPufY5Z5SQJNxK_tH-d9zXR5vY,1308
2
- bspy/_spline_domain.py,sha256=kklvRrSeEEUUQ48yXvCCDzHMRPd4_BHICxAsOhyxhjk,30178
3
- bspy/_spline_evaluation.py,sha256=3cWD3sL4odnZ4w1DBQFmUnPg2PuPpSfaGfNcBl8ZrW0,7079
4
- bspy/_spline_fitting.py,sha256=hFhPyL39d87IGZiAs8teFhYZHxPN1EvzQYxHcDTeBLw,45318
5
- bspy/_spline_intersection.py,sha256=EV19Qt-kccr5K17aB1EETJXENdz-_UJlYTKWjK-ueJU,58324
6
- bspy/_spline_operations.py,sha256=y98zl-r31xsZzD6X0VYbuEPUabg3UNC_ERCbkXZRZXU,42530
7
- bspy/hyperplane.py,sha256=gRConjCEdMAjP8UXA7kaYU--NQHr1tYiVHtmZzLNnqY,24639
8
- bspy/manifold.py,sha256=29KkZXvv759-aywGd7Ek4Egm-DCBNDTEfusW_Az1kC4,14244
9
- bspy/solid.py,sha256=qbHofcdNbMTaoCCfKfxloYiEqZDsxfOIJD3MwZguEJ8,36444
10
- bspy/spline.py,sha256=747e_kChnGnN2Y93EcP9XymDrf2T4NFFweFB_YghEvw,94776
11
- bspy/splineOpenGLFrame.py,sha256=ezPUyDr9QKF0KU7H6XjHL-HhN23ffC8KbxGhGgGJhQ0,95843
12
- bspy/viewer.py,sha256=HdtjAj5CZnv4VKcfS8MhT-ciPEPRVNUTp-02TW1NQSU,33717
13
- bspy-4.1.dist-info/LICENSE,sha256=nLfJULN68Jw6GfCJp4xeMksGuRdyWNdgEsZGjw2twig,1091
14
- bspy-4.1.dist-info/METADATA,sha256=Fe5wy39gSJOVk7Z3Ruz1Utm3oYZQlPyyJJMYGu6YvP4,6152
15
- bspy-4.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
16
- bspy-4.1.dist-info/top_level.txt,sha256=fotZnJn6aCwgUbBEV3hslIko7Nw-eqtHLq2eyJLlFsY,5
17
- bspy-4.1.dist-info/RECORD,,
File without changes
File without changes