bspy 1.2.1__tar.gz → 1.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {bspy-1.2.1 → bspy-1.2.3}/PKG-INFO +1 -1
- {bspy-1.2.1 → bspy-1.2.3}/bspy/_spline_intersection.py +81 -33
- {bspy-1.2.1 → bspy-1.2.3}/bspy/_spline_operations.py +3 -3
- {bspy-1.2.1 → bspy-1.2.3}/bspy/spline.py +56 -0
- {bspy-1.2.1 → bspy-1.2.3}/bspy.egg-info/PKG-INFO +1 -1
- {bspy-1.2.1 → bspy-1.2.3}/setup.cfg +1 -1
- {bspy-1.2.1 → bspy-1.2.3}/tests/test_bspy.py +41 -24
- {bspy-1.2.1 → bspy-1.2.3}/LICENSE +0 -0
- {bspy-1.2.1 → bspy-1.2.3}/README.md +0 -0
- {bspy-1.2.1 → bspy-1.2.3}/bspy/__init__.py +0 -0
- {bspy-1.2.1 → bspy-1.2.3}/bspy/_spline_domain.py +0 -0
- {bspy-1.2.1 → bspy-1.2.3}/bspy/_spline_evaluation.py +0 -0
- {bspy-1.2.1 → bspy-1.2.3}/bspy/_spline_fitting.py +0 -0
- {bspy-1.2.1 → bspy-1.2.3}/bspy/bspyApp.py +0 -0
- {bspy-1.2.1 → bspy-1.2.3}/bspy/drawableSpline.py +0 -0
- {bspy-1.2.1 → bspy-1.2.3}/bspy/splineOpenGLFrame.py +0 -0
- {bspy-1.2.1 → bspy-1.2.3}/bspy.egg-info/SOURCES.txt +0 -0
- {bspy-1.2.1 → bspy-1.2.3}/bspy.egg-info/dependency_links.txt +0 -0
- {bspy-1.2.1 → bspy-1.2.3}/bspy.egg-info/requires.txt +0 -0
- {bspy-1.2.1 → bspy-1.2.3}/bspy.egg-info/top_level.txt +0 -0
- {bspy-1.2.1 → bspy-1.2.3}/pyproject.toml +0 -0
|
@@ -67,12 +67,15 @@ def zeros_using_interval_newton(self):
|
|
|
67
67
|
adjustedLeftStep = min(leftNewtonStep, rightNewtonStep) - 0.5 * epsilon
|
|
68
68
|
adjustedRightStep = max(leftNewtonStep, rightNewtonStep) + 0.5 * epsilon
|
|
69
69
|
if derivativeBounds[0] * derivativeBounds[1] >= 0.0: # Refine interval
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
projectedLeftStep = max(0.0, adjustedLeftStep)
|
|
71
|
+
projectedRightStep = min(1.0, adjustedRightStep)
|
|
72
|
+
if projectedLeftStep <= projectedRightStep:
|
|
73
|
+
if projectedRightStep - projectedLeftStep <= epsilon:
|
|
74
|
+
myZeros = [0.5 * (projectedLeftStep + projectedRightStep)]
|
|
75
|
+
else:
|
|
76
|
+
trimmedSpline = mySpline.trim(((projectedLeftStep, projectedRightStep),))
|
|
77
|
+
myZeros = refine(trimmedSpline, intervalSize, functionMax)
|
|
78
|
+
else:
|
|
76
79
|
return []
|
|
77
80
|
else: # . . . or split as needed
|
|
78
81
|
myZeros = []
|
|
@@ -206,7 +209,16 @@ def _refine_projected_polyhedron(interval):
|
|
|
206
209
|
machineEpsilon = np.finfo(interval.spline.coefs.dtype).eps
|
|
207
210
|
root = None
|
|
208
211
|
intervals = []
|
|
209
|
-
scale =
|
|
212
|
+
scale = 0.0
|
|
213
|
+
# Go through each nDep of the spline, checking bounds.
|
|
214
|
+
for coefs in interval.spline.coefs:
|
|
215
|
+
coefsMin = coefs.min()
|
|
216
|
+
coefsMax = coefs.max()
|
|
217
|
+
if coefsMax < -evaluationEpsilon or coefsMin > evaluationEpsilon:
|
|
218
|
+
# No roots in this interval.
|
|
219
|
+
return root, intervals
|
|
220
|
+
scale = max(scale, abs(coefsMin), abs(coefsMax))
|
|
221
|
+
|
|
210
222
|
if scale < epsilon:
|
|
211
223
|
# Return the bounds of the interval within which the spline is zero.
|
|
212
224
|
root = (interval.intercept, interval.slope + interval.intercept)
|
|
@@ -349,9 +361,24 @@ def contours(self):
|
|
|
349
361
|
Point = namedtuple('Point', ('d', 'det', 'onBoundary', 'uvw'))
|
|
350
362
|
epsilon = np.sqrt(np.finfo(self.coefs.dtype).eps)
|
|
351
363
|
evaluationEpsilon = np.sqrt(epsilon)
|
|
364
|
+
|
|
365
|
+
# Go through each nDep of the spline, checking bounds.
|
|
366
|
+
for coefs in self.coefs:
|
|
367
|
+
coefsMin = coefs.min()
|
|
368
|
+
coefsMax = coefs.max()
|
|
369
|
+
if coefsMax < -evaluationEpsilon or coefsMin > evaluationEpsilon:
|
|
370
|
+
# No contours for this spline.
|
|
371
|
+
return []
|
|
372
|
+
|
|
373
|
+
# Record self's original domain and then reparametrize self's domain to [0, 1]^nInd.
|
|
374
|
+
domain = self.domain().T
|
|
375
|
+
self = self.reparametrize(((0.0, 1.0),) * self.nInd)
|
|
376
|
+
|
|
377
|
+
# Construct self's tangents and normal.
|
|
352
378
|
tangents = []
|
|
353
379
|
for nInd in range(self.nInd):
|
|
354
380
|
tangents.append(self.differentiate(nInd))
|
|
381
|
+
normal = self.normal_spline((0, 1)) # We only need the first two indices
|
|
355
382
|
|
|
356
383
|
theta = np.sqrt(2) # Arbitrary starting value for theta (picked one in [0, pi/2] unlikely to be a stationary point)
|
|
357
384
|
# Try different theta values until no border or turning points are degenerate.
|
|
@@ -363,7 +390,6 @@ def contours(self):
|
|
|
363
390
|
abort = False
|
|
364
391
|
|
|
365
392
|
# Construct the turning point determinant.
|
|
366
|
-
normal = self.normal_spline((0, 1)) # We only need the first two indices
|
|
367
393
|
turningPointDeterminant = normal.dot((cosTheta, sinTheta))
|
|
368
394
|
|
|
369
395
|
# Find intersections with u and v boundaries.
|
|
@@ -478,6 +504,20 @@ def contours(self):
|
|
|
478
504
|
# (3) Take all the points found in Step (1) and Step (2) and order them by distance in the theta direction from the origin.
|
|
479
505
|
points.sort()
|
|
480
506
|
|
|
507
|
+
# Extra step not in the paper: Add a panel between two consecutive turning points to uniquely determine contours between them.
|
|
508
|
+
if len(points) > 1:
|
|
509
|
+
i = 0
|
|
510
|
+
previousPoint = points[i]
|
|
511
|
+
while i < len(points) - 1:
|
|
512
|
+
i += 1
|
|
513
|
+
point = points[i]
|
|
514
|
+
if not previousPoint.onBoundary and not point.onBoundary and point.d - previousPoint.d > epsilon:
|
|
515
|
+
# We have two consecutive turning points on separate panels.
|
|
516
|
+
# Insert a panel in between them, with the uvw value of None, since there is no zero associated.
|
|
517
|
+
points.insert(i, Point(0.5 * (previousPoint.d + point.d), 0.0, False, None))
|
|
518
|
+
i += 1
|
|
519
|
+
previousPoint = point
|
|
520
|
+
|
|
481
521
|
# (4) Initialize an ordered list of contours. No contours will be on the list at first.
|
|
482
522
|
currentContourPoints = [] # Holds contours currently being identified
|
|
483
523
|
contourPoints = [] # Hold contours already identified
|
|
@@ -523,29 +563,34 @@ def contours(self):
|
|
|
523
563
|
# the list. Go back to Step (5).
|
|
524
564
|
# First, construct panel, whose zeros lie along the panel boundary, u * cosTheta + v * sinTheta - d = 0.
|
|
525
565
|
panel.coefs[self.nDep] -= point.d
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
566
|
+
|
|
567
|
+
if point.uvw is None:
|
|
568
|
+
# For an inserted panel between two consecutive turning points, just find zeros along the panel.
|
|
569
|
+
panelPoints = panel.zeros()
|
|
570
|
+
else:
|
|
571
|
+
# Split panel below and above the known zero point.
|
|
572
|
+
# This avoids extra computation and the high-zero at the known zero point.
|
|
573
|
+
panelPoints = [point.uvw]
|
|
574
|
+
# To split the panel, we need to determine the offset from the point.
|
|
575
|
+
# Since the objective function (self) is zero and its derivative is zero at the point,
|
|
576
|
+
# we use second derivatives to determine when the objective function will likely grow
|
|
577
|
+
# evaluationEpsilon above zero again.
|
|
578
|
+
wrt = [0] * self.nInd
|
|
579
|
+
wrt[0] = 2
|
|
580
|
+
selfUU = self.derivative(wrt, point.uvw)
|
|
581
|
+
wrt[0] = 1
|
|
582
|
+
wrt[1] = 1
|
|
583
|
+
selfUV = self.derivative(wrt, point.uvw)
|
|
584
|
+
wrt[0] = 0
|
|
585
|
+
wrt[1] = 2
|
|
586
|
+
selfVV = self.derivative(wrt, point.uvw)
|
|
587
|
+
offset = np.sqrt(2.0 * evaluationEpsilon / \
|
|
588
|
+
np.linalg.norm(selfUU * sinTheta * sinTheta - 2.0 * selfUV * sinTheta * cosTheta + selfVV * cosTheta * cosTheta))
|
|
589
|
+
# Now, we can find the zeros of the split panel, checking to ensure each panel is within bounds first.
|
|
590
|
+
if point.uvw[0] + sinTheta * offset < 1.0 - epsilon and epsilon < point.uvw[1] - cosTheta * offset:
|
|
591
|
+
panelPoints += panel.trim(((point.uvw[0] + sinTheta * offset, 1.0), (0.0, point.uvw[1] - cosTheta * offset)) + ((None, None),) * (self.nInd - 2)).zeros()
|
|
592
|
+
if epsilon < point.uvw[0] - sinTheta * offset and point.uvw[1] + cosTheta * offset < 1.0 - epsilon:
|
|
593
|
+
panelPoints += panel.trim(((0.0, point.uvw[0] - sinTheta * offset), (point.uvw[1] + cosTheta * offset, 1.0)) + ((None, None),) * (self.nInd - 2)).zeros()
|
|
549
594
|
# Sort zero points by their position along the panel boundary (using vector orthogonal to its normal).
|
|
550
595
|
panelPoints.sort(key=lambda uvw: uvw[1] * cosTheta - uvw[0] * sinTheta)
|
|
551
596
|
# Add d back to prepare for next turning point.
|
|
@@ -553,7 +598,7 @@ def contours(self):
|
|
|
553
598
|
# Go through panel points, adding them to existing contours, creating new ones, or closing old ones.
|
|
554
599
|
adjustment = 0 # Adjust index after a contour point is added or removed.
|
|
555
600
|
for i, uvw in zip(range(len(panelPoints)), panelPoints):
|
|
556
|
-
if np.allclose(point.uvw, uvw):
|
|
601
|
+
if point.uvw is not None and np.allclose(point.uvw, uvw):
|
|
557
602
|
if point.det > 0.0:
|
|
558
603
|
# Insert the turning point twice (second one appears before the first one in the points list).
|
|
559
604
|
currentContourPoints.insert(i, [True, point.uvw]) # True indicates end point
|
|
@@ -576,6 +621,9 @@ def contours(self):
|
|
|
576
621
|
# Now we just need to create splines for those contours using the Spline.contour method.
|
|
577
622
|
splineContours = []
|
|
578
623
|
for points in contourPoints:
|
|
579
|
-
|
|
624
|
+
contour = bspy.spline.Spline.contour(self, points[1:]) # Skip endPoint boolean at start of points list
|
|
625
|
+
# Transform the contour to self's original domain.
|
|
626
|
+
contour.coefs = (contour.coefs.T * (domain[1] - domain[0]) + domain[0]).T
|
|
627
|
+
splineContours.append(contour)
|
|
580
628
|
|
|
581
629
|
return splineContours
|
|
@@ -573,10 +573,10 @@ def normal_spline(self, indices=None):
|
|
|
573
573
|
ord += order - 1 if ccm[i, j] and index != j else 0
|
|
574
574
|
newOrd = max(newOrd, ord)
|
|
575
575
|
newOrder.append(newOrd)
|
|
576
|
-
uniqueKnots, counts = np.unique(knots, return_counts=True)
|
|
576
|
+
uniqueKnots, counts = np.unique(knots[order - 1:self.nCoef[i] + 1], return_counts=True)
|
|
577
577
|
counts += newOrd - order + 1 # Because we're multiplying all the tangents, the knot elevation is one more
|
|
578
|
-
counts[0]
|
|
579
|
-
counts[-1]
|
|
578
|
+
counts[0] = newOrd # But not at the endpoints, which are full order as usual
|
|
579
|
+
counts[-1] = newOrd # But not at the endpoints, which are full order as usual
|
|
580
580
|
newKnots.append(np.repeat(uniqueKnots, counts))
|
|
581
581
|
# Also calculate the total number of coefficients, capturing how it progressively increases, and
|
|
582
582
|
# using that calculation to span uvw from the starting knot to the end for each variable.
|
|
@@ -457,6 +457,22 @@ class Spline:
|
|
|
457
457
|
indMap = [(*(mapping if _isIterable(mapping) else (mapping, mapping)), True) for mapping in indMap]
|
|
458
458
|
return bspy._spline_operations.multiplyAndConvolve(self, other, indMap, productType)
|
|
459
459
|
|
|
460
|
+
def copy(self, metadata={}):
|
|
461
|
+
"""
|
|
462
|
+
Create a copy of a spline.
|
|
463
|
+
|
|
464
|
+
Parameters
|
|
465
|
+
----------
|
|
466
|
+
metadata : `dict`, optional
|
|
467
|
+
A dictionary of ancillary data to store with the spline. Default is {}.
|
|
468
|
+
|
|
469
|
+
Returns
|
|
470
|
+
-------
|
|
471
|
+
spline : `Spline`
|
|
472
|
+
The spline copy.
|
|
473
|
+
"""
|
|
474
|
+
return type(self)(self.nInd, self.nDep, self.order, self.nCoef, self.knots, self.coefs, self.accuracy, metadata)
|
|
475
|
+
|
|
460
476
|
def cross(self, vector):
|
|
461
477
|
"""
|
|
462
478
|
Cross product a spline with `nDep` of 2 or 3 by the given vector.
|
|
@@ -898,6 +914,30 @@ class Spline:
|
|
|
898
914
|
|
|
899
915
|
@staticmethod
|
|
900
916
|
def load(fileName, splineType=None):
|
|
917
|
+
"""
|
|
918
|
+
Load a spline from the specified filename (full path).
|
|
919
|
+
|
|
920
|
+
Parameters
|
|
921
|
+
----------
|
|
922
|
+
fileName : `string`
|
|
923
|
+
The full path to the file containing the spline. Can be a relative path.
|
|
924
|
+
|
|
925
|
+
splineType : `type`, optional
|
|
926
|
+
The class type that should be created. It must be an instance of Spline (the default).
|
|
927
|
+
|
|
928
|
+
Returns
|
|
929
|
+
-------
|
|
930
|
+
spline : `Spline`
|
|
931
|
+
The loaded spline.
|
|
932
|
+
|
|
933
|
+
See Also
|
|
934
|
+
--------
|
|
935
|
+
`save` : Save a spline to the specified filename (full path).
|
|
936
|
+
|
|
937
|
+
Notes
|
|
938
|
+
-----
|
|
939
|
+
Uses numpy's load function.
|
|
940
|
+
"""
|
|
901
941
|
kw = np.load(fileName)
|
|
902
942
|
order = kw["order"]
|
|
903
943
|
nInd = len(order)
|
|
@@ -1094,6 +1134,22 @@ class Spline:
|
|
|
1094
1134
|
return bspy._spline_domain.reparametrize(self, newDomain)
|
|
1095
1135
|
|
|
1096
1136
|
def save(self, fileName):
|
|
1137
|
+
"""
|
|
1138
|
+
Save a spline to the specified filename (full path).
|
|
1139
|
+
|
|
1140
|
+
Parameters
|
|
1141
|
+
----------
|
|
1142
|
+
fileName : `string`
|
|
1143
|
+
The full path to the file containing the spline. Can be a relative path.
|
|
1144
|
+
|
|
1145
|
+
See Also
|
|
1146
|
+
--------
|
|
1147
|
+
`load` : Load a spline from the specified filename (full path).
|
|
1148
|
+
|
|
1149
|
+
Notes
|
|
1150
|
+
-----
|
|
1151
|
+
Uses numpy's savez function. Accuracy and metadata are not saved.
|
|
1152
|
+
"""
|
|
1097
1153
|
kw = {}
|
|
1098
1154
|
kw["order"] = order=np.array(self.order, np.int32)
|
|
1099
1155
|
for i in range(len(self.knots)):
|
|
@@ -592,7 +592,7 @@ def test_blossom():
|
|
|
592
592
|
maxError = max(maxError, np.linalg.norm(myCurve.coefs[:,i] - coef))
|
|
593
593
|
assert maxError <= np.finfo(float).eps
|
|
594
594
|
|
|
595
|
-
def
|
|
595
|
+
def test_contours():
|
|
596
596
|
maxError = 0.0
|
|
597
597
|
F = lambda x: (x[0] - x[2], x[1] - x[3], x[0]*x[0] + x[1]*x[1]/9.0 - 1.0 + x[2]*x[2] + x[3]*x[3]/9.0 - 1.0)
|
|
598
598
|
spline = bspy.Spline.contour(F, ((1.0, 0.0, 1.0, 0.0), (0.0, 3.0, 0.0, 3.0)))
|
|
@@ -601,6 +601,41 @@ def test_contour():
|
|
|
601
601
|
maxError = max(maxError, np.linalg.norm(F(x)))
|
|
602
602
|
assert maxError <= 0.05
|
|
603
603
|
|
|
604
|
+
maxError = 0.0
|
|
605
|
+
order = 3
|
|
606
|
+
knots = [0.0] * order + [1.0] * order
|
|
607
|
+
nCoef = len(knots) - order
|
|
608
|
+
spline = bspy.Spline(2, 1, (order, order), (nCoef, nCoef), (knots, knots), \
|
|
609
|
+
(((1.0, 0.0, 1.0), (0.0, -5.0, 0.0), (1.0, 0.0, 1.0)),))
|
|
610
|
+
contours = spline.contours()
|
|
611
|
+
for contour in contours:
|
|
612
|
+
for t in np.linspace(0.0, 1.0, 11):
|
|
613
|
+
uvw = contour((t,))
|
|
614
|
+
maxError = max(maxError, np.linalg.norm(spline(uvw)))
|
|
615
|
+
assert maxError <= 0.05
|
|
616
|
+
|
|
617
|
+
return # Comment this line to run the following lengthy test
|
|
618
|
+
|
|
619
|
+
maxError = 0.0
|
|
620
|
+
F = lambda u , v : (u ** 2 + (v - 3/4) ** 2 - 1/25) * \
|
|
621
|
+
((u - 2/5) ** 2 + (v - 3/5) ** 2 - 1/25) * \
|
|
622
|
+
(u ** 2 + (v - 3/2) ** 2 - 25/16) * \
|
|
623
|
+
((u - 1) ** 2 + (v - 3/10) ** 2 - 1/25)
|
|
624
|
+
order = 9
|
|
625
|
+
knots = [0.0] * order + [1.0] * order
|
|
626
|
+
nCoef = order
|
|
627
|
+
points = []
|
|
628
|
+
for u in np.linspace(0.0, 1.0, nCoef):
|
|
629
|
+
for v in np.linspace(0.0, 1.0, nCoef):
|
|
630
|
+
points.append((u, v, F(u, v)))
|
|
631
|
+
spline = bspy.Spline.least_squares(2, 1, (order, order), points, (knots, knots))
|
|
632
|
+
contours = spline.contours()
|
|
633
|
+
for contour in contours:
|
|
634
|
+
for t in np.linspace(0.0, 1.0, 11):
|
|
635
|
+
uvw = contour((t,))
|
|
636
|
+
maxError = max(maxError, np.linalg.norm(spline(uvw)))
|
|
637
|
+
assert maxError <= 0.05
|
|
638
|
+
|
|
604
639
|
def test_contract():
|
|
605
640
|
maxError = 0.0
|
|
606
641
|
contracted = mySurface.contract([.25, None])
|
|
@@ -804,29 +839,6 @@ def test_integral():
|
|
|
804
839
|
assert maxError <= np.finfo(float).eps
|
|
805
840
|
|
|
806
841
|
def test_intersect():
|
|
807
|
-
maxError = 0.0
|
|
808
|
-
F = lambda u , v : (u ** 2 + (v - 3/4) ** 2 - 1/25) * \
|
|
809
|
-
((u - 2/5) ** 2 + (v - 3/5) ** 2 - 1/25) * \
|
|
810
|
-
(u ** 2 + (v - 3/2) ** 2 - 25/16) * \
|
|
811
|
-
((u - 1) ** 2 + (v - 3/10) ** 2 - 1/25)
|
|
812
|
-
|
|
813
|
-
order = 9
|
|
814
|
-
knots = [0.0] * order + [1.0] * order
|
|
815
|
-
nCoef = order
|
|
816
|
-
points = []
|
|
817
|
-
for u in np.linspace(0.0, 1.0, nCoef):
|
|
818
|
-
for v in np.linspace(0.0, 1.0, nCoef):
|
|
819
|
-
points.append((u, v, F(u, v)))
|
|
820
|
-
spline = bspy.Spline.least_squares(2, 1, (order, order), points, (knots, knots))
|
|
821
|
-
|
|
822
|
-
return # Comment this line to run the following lengthy test
|
|
823
|
-
|
|
824
|
-
contours = spline.contours()
|
|
825
|
-
for contour in contours:
|
|
826
|
-
for t in np.linspace(0.0, 1.0, 11):
|
|
827
|
-
uvw = contour((t,))
|
|
828
|
-
maxError = max(maxError, np.linalg.norm(spline(uvw)))
|
|
829
|
-
assert maxError <= np.finfo(float).eps ** 0.2
|
|
830
842
|
|
|
831
843
|
return # Comment this line to run the following additional really lengthy test
|
|
832
844
|
|
|
@@ -1064,6 +1076,11 @@ def test_zeros():
|
|
|
1064
1076
|
|
|
1065
1077
|
tolerance = 1.0e-14
|
|
1066
1078
|
|
|
1079
|
+
spline = bspy.Spline(1, 1, (3,), (3,), [[0., 0., 0., 1., 1., 1.]], [[-1.2, -0.2, 0.8]], 0.0, {})
|
|
1080
|
+
expectedRoots = (0.6,)
|
|
1081
|
+
roots = spline.zeros()
|
|
1082
|
+
check_1D_roots(expectedRoots, roots, tolerance)
|
|
1083
|
+
|
|
1067
1084
|
spline = bspy.Spline(1, 1, (4,), (4,), ((0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0),), ((1.0, -2.0, -2.0, 1.0),))
|
|
1068
1085
|
expectedRoots = (0.12732200375003502, 0.8726779962499649)
|
|
1069
1086
|
roots = spline.zeros()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|