bspy 4.2__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 CHANGED
@@ -8,29 +8,38 @@ import bspy._spline_operations
8
8
 
9
9
  class SplineBlock:
10
10
  """
11
- A class to represent and process an array-like collection of splines.
12
- Spline blocks are useful for efficiently manipulating and solving systems of equations with splines.
11
+ A class to process an array-like collection of splines that represent a system of equations.
13
12
 
14
13
  Parameters
15
14
  ----------
16
- block : an array-like collection of splines (may be a list of lists)
17
- Splines in the same row are treated as if they are added together.
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.
18
17
  Each row need not have the same number of splines, but all splines in a row must have the same
19
18
  number of dependent variables (same nDep). Corresponding independent variables must have the same domain.
20
19
 
21
- For example, if F is a spline with nInd = 3 and nDep = 2, G is a spline with nInd = 1 and nDep = 2,
22
- and h is a spline with nInd = 2 and nDep = 1, then [[F, G], [h]] is a valid block (total nDep is 3
23
- and max nInd is 4).
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.
24
31
  """
25
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
+
26
37
  def _block_evaluation(self, returnShape, splineFunction, args):
27
38
  value = np.zeros(returnShape, self.coefsDtype)
28
39
  nDep = 0
29
40
  for row in self.block:
30
- nInd = 0
31
- for spline in row:
32
- value[nDep:nDep + spline.nDep] += splineFunction(spline, *[arg[nInd:nInd + spline.nInd] for arg in args])
33
- nInd += spline.nInd
41
+ for map, spline in row:
42
+ value[nDep:nDep + spline.nDep] += splineFunction(spline, *SplineBlock._map_args(map, args))
34
43
  nDep += spline.nDep
35
44
  return value
36
45
 
@@ -38,49 +47,69 @@ class SplineBlock:
38
47
  newBlock = []
39
48
  for row in self.block:
40
49
  newRow = []
41
- nInd = 0
42
- for spline in row:
43
- newRow.append(splineFunction(spline, *[arg[nInd:nInd + spline.nInd] for arg in args]))
44
- nInd += spline.nInd
50
+ for map, spline in row:
51
+ newRow.append((map, splineFunction(spline, *SplineBlock._map_args(map, args))))
45
52
  newBlock.append(newRow)
46
53
  return SplineBlock(newBlock)
47
54
 
48
55
  def __init__(self, block):
49
56
  if isinstance(block, bspy.spline.Spline):
50
- self.block = [[block]]
51
- elif isinstance(block[0], bspy.spline.Spline):
52
- self.block = [block]
53
- else:
54
- self.block = block
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 = []
55
62
  self.nInd = 0
56
63
  self.nDep = 0
57
64
  self.knotsDtype = None
58
65
  self.coefsDtype = None
59
66
  self.size = 0
60
- self._domain = []
61
- for row in self.block:
67
+ domain = {}
68
+ for row in block:
62
69
  rowInd = 0
63
70
  rowDep = 0
64
- for spline in row:
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
65
81
  if rowDep == 0:
66
82
  rowDep = spline.nDep
67
- if self.nInd == 0:
83
+ if self.nDep == 0:
68
84
  self.knotsDtype = spline.knots[0].dtype
69
85
  self.coefsDtype = spline.coefs.dtype
70
86
  elif rowDep != spline.nDep:
71
87
  raise ValueError("All splines in the same row must have the same nDep")
72
88
  d = spline.domain()
73
- for i in range(rowInd, rowInd + spline.nInd):
74
- ind = i - rowInd
75
- if i < self.nInd:
76
- if self._domain[i][0] != d[ind, 0] or self._domain[i][1] != d[ind, 1]:
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]:
77
96
  raise ValueError("Domains of independent variables must match")
78
97
  else:
79
- self._domain.append(d[ind])
80
- rowInd += spline.nInd
81
- self.nInd = max(self.nInd, rowInd)
82
- self.nDep += rowDep
83
- self.size += len(row)
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}")
84
113
  self._domain = np.array(self._domain, self.knotsDtype)
85
114
 
86
115
  def __call__(self, uvw):
@@ -126,7 +155,26 @@ class SplineBlock:
126
155
  block : `SplineBlock`
127
156
  The contracted spline block.
128
157
  """
129
- return self._block_operation(bspy._spline_operations.contract, (uvw,))
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)
130
178
 
131
179
  def derivative(self, with_respect_to, uvw):
132
180
  """
@@ -190,12 +238,11 @@ class SplineBlock:
190
238
  The value of the spline block's Jacobian at the given parameter values. The shape of the return value is (nDep, nInd).
191
239
  """
192
240
  jacobian = np.zeros((self.nDep, self.nInd), self.coefsDtype)
241
+ uvw = np.atleast_1d(uvw)
193
242
  nDep = 0
194
243
  for row in self.block:
195
- nInd = 0
196
- for spline in row:
197
- jacobian[nDep:nDep + spline.nDep, nInd:nInd + spline.nInd] += spline.jacobian(uvw[nInd:nInd + spline.nInd])
198
- nInd += spline.nInd
244
+ for map, spline in row:
245
+ jacobian[nDep:nDep + spline.nDep, map] += spline.jacobian(uvw[map])
199
246
  nDep += spline.nDep
200
247
  return jacobian
201
248
 
@@ -293,6 +340,76 @@ class SplineBlock:
293
340
  """
294
341
  return self._block_operation(bspy._spline_domain.reparametrize, (newDomain,))
295
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
+
296
413
  def trim(self, newDomain):
297
414
  """
298
415
  Trim the domain of a spline block.
bspy/viewer.py CHANGED
@@ -144,15 +144,19 @@ 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"Boundary {i}", 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):
@@ -167,15 +171,20 @@ class Viewer(tk.Tk):
167
171
  spline = Spline(2, 3, (2, 2), (2, 2),
168
172
  np.array((uvMin, uvMin, uvMax, uvMax), np.float32).T,
169
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
170
175
  elif isinstance(boundary.manifold, Spline):
171
176
  spline = boundary.manifold
172
- if not hasattr(spline, "cache"):
173
- spline.cache = {}
174
- 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
175
182
  self.list(spline, name, fillColor, lineColor, options, draw, parentIID)
176
183
  else:
177
- if "Name" not in spline.metadata:
178
- 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})"
179
188
  if fillColor is not None:
180
189
  spline.metadata["fillColor"] = fillColor
181
190
  if lineColor is not None:
@@ -264,7 +273,7 @@ class Viewer(tk.Tk):
264
273
  for item in self.treeview.selection():
265
274
  spline = self.splineList[item]
266
275
  if isinstance(spline, Spline):
267
- coefs = spline.cache["coefs32"].T[:3]
276
+ coefs = spline.cache["xyzCoefs32"].T
268
277
  coefsAxis = tuple(range(1, spline.nInd + 1))
269
278
  if gotOne:
270
279
  splineMin = np.minimum(splineMin, coefs.min(axis=coefsAxis))
@@ -277,7 +286,7 @@ class Viewer(tk.Tk):
277
286
  elif isinstance(spline, Solid):
278
287
  for subitem in self.treeview.get_children(item):
279
288
  spline = self.splineList[subitem]
280
- coefs = spline.cache["coefs32"].T[:3]
289
+ coefs = spline.cache["xyzCoefs32"].T
281
290
  coefsAxis = tuple(range(1, spline.nInd + 1))
282
291
  if gotOne:
283
292
  splineMin = np.minimum(splineMin, coefs.min(axis=coefsAxis))
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bspy
3
- Version: 4.2
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
@@ -47,7 +47,9 @@ The [Hyperplane](https://ericbrec.github.io/BSpy/bspy/hyperplane.html) class has
47
47
 
48
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
49
 
50
- The [SplineBlock](https://ericbrec.github.io/BSpy/bspy/spline_block.html) class has methods to represent and process an array-like collection of splines, including ones to compute the contours and zeros of a spline block, as well as a variety of methods to evaluate a spline block and its derivatives. Spline blocks are useful for efficiently manipulating and solving systems of equations with splines.
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.
51
53
 
52
54
  The [SplineOpenGLFrame](https://ericbrec.github.io/BSpy/bspy/splineOpenGLFrame.html) class is an
53
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: setuptools (70.1.0)
2
+ Generator: setuptools (75.6.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
bspy-4.2.dist-info/RECORD DELETED
@@ -1,18 +0,0 @@
1
- bspy/__init__.py,sha256=ruWkrHgrjGFeB2uATnCNY-J9hpgK5R6vsXrtshBIFDk,1476
2
- bspy/_spline_domain.py,sha256=xbXOlRxFSwbM55dNiy6RpSOJsgHc1xD3hgACBxYiifk,33647
3
- bspy/_spline_evaluation.py,sha256=MQmF6hhxHXBEOWCSTGZOTmecP5uOn7VrI9Aj8NUCSb8,9622
4
- bspy/_spline_fitting.py,sha256=EbM_FI-QW5PjZKbpAF4r4dP8DZOLn8QX3dYvG9zzuFo,50753
5
- bspy/_spline_intersection.py,sha256=cFciLdc1wTFZuKixfWoOlN8PJhQHWt_nN6N9Wt4EMhw,65438
6
- bspy/_spline_operations.py,sha256=KNkWSg27jFjoewsaNqMfNLrMAD4MUQJvCkXOfQbEHmI,43088
7
- bspy/hyperplane.py,sha256=wngJw_9x358gf_S1O3sPqodnUx7HHdJunAp4ceOq7Fw,24605
8
- bspy/manifold.py,sha256=Ecrm7LjcfttOJ617mKMXdJSWqVxt2eToNvpq5skLliA,14255
9
- bspy/solid.py,sha256=E8iAcIFV5r_SrzcY_5UB0hKitE-bcrI0-dPzXcvD6iw,36763
10
- bspy/spline.py,sha256=Bt1WDF69ZYO--rz9Myclb26f0fDnr62q1NWYTxcwFvw,100398
11
- bspy/splineOpenGLFrame.py,sha256=ezPUyDr9QKF0KU7H6XjHL-HhN23ffC8KbxGhGgGJhQ0,95843
12
- bspy/spline_block.py,sha256=gfoK_-QVqQCrv9acDCkQf-deHZntjOw1VgyPWAxDWcU,14656
13
- bspy/viewer.py,sha256=S0yij3LwHIll0ZA-bZevpkNFYSzs41a5xb8EgpHTW_M,33760
14
- bspy-4.2.dist-info/LICENSE,sha256=nLfJULN68Jw6GfCJp4xeMksGuRdyWNdgEsZGjw2twig,1091
15
- bspy-4.2.dist-info/METADATA,sha256=a7T__b4_Kyf9wKg7ba8_s_51FdKB-XgaQg0zLfLWGI8,6792
16
- bspy-4.2.dist-info/WHEEL,sha256=cpQTJ5IWu9CdaPViMhC9YzF8gZuS5-vlfoFihTBC86A,91
17
- bspy-4.2.dist-info/top_level.txt,sha256=fotZnJn6aCwgUbBEV3hslIko7Nw-eqtHLq2eyJLlFsY,5
18
- bspy-4.2.dist-info/RECORD,,
File without changes
File without changes