morphopt 3.1.1__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.
Potentially problematic release.
This version of morphopt might be problematic. Click here for more details.
- morphopt/__init__.py +32 -0
- morphopt/opt_runner.py +130 -0
- morphopt/optcore/baseobject.py +48 -0
- morphopt/optcore/controller.py +404 -0
- morphopt/optcore/history.py +440 -0
- morphopt/optcore/modelparams/__init__.py +4 -0
- morphopt/optcore/modelparams/base_params.py +97 -0
- morphopt/optcore/modelparams/feamodel/__init__.py +0 -0
- morphopt/optcore/modelparams/feamodel/feainterface/__init__.py +10 -0
- morphopt/optcore/modelparams/feamodel/feainterface/basefeainterface.py +50 -0
- morphopt/optcore/modelparams/feamodel/feainterface/bodyforceinterface.py +71 -0
- morphopt/optcore/modelparams/feamodel/feainterface/boundaryconditioninterface.py +59 -0
- morphopt/optcore/modelparams/feamodel/feainterface/contactinterface.py +113 -0
- morphopt/optcore/modelparams/feamodel/feainterface/coupleinterface.py +31 -0
- morphopt/optcore/modelparams/feamodel/feainterface/pointinterface.py +79 -0
- morphopt/optcore/modelparams/feamodel/feainterface/pressureinterface.py +68 -0
- morphopt/optcore/modelparams/feamodel/feainterface/referencepointinterface.py +29 -0
- morphopt/optcore/modelparams/feamodel/feainterface/springinterface.py +104 -0
- morphopt/optcore/modelparams/feamodel/feaparams.py +264 -0
- morphopt/optcore/modelparams/geometry/__init__.py +3 -0
- morphopt/optcore/modelparams/geometry/geometryinterfaces/__init__.py +1 -0
- morphopt/optcore/modelparams/geometry/geometryinterfaces/basesurfaceinterface.py +763 -0
- morphopt/optcore/modelparams/geometry/geometryinterfaces/bspsurfaceinterface.py +723 -0
- morphopt/optcore/modelparams/geometry/geometryinterfaces/cpgeosurfaceinterface.py +374 -0
- morphopt/optcore/modelparams/geometry/geometryparams.py +715 -0
- morphopt/optcore/modelparams/materials/__init__.py +1 -0
- morphopt/optcore/modelparams/materials/materialparams.py +110 -0
- morphopt/optcore/modelparams/params.py +88 -0
- morphopt/optcore/objfunc.py +248 -0
- morphopt/optcore/solver.py +159 -0
- morphopt/optcore/updaters/__init__.py +2 -0
- morphopt/optcore/updaters/base_updater.py +110 -0
- morphopt/optcore/updaters/geometry/__init__.py +2 -0
- morphopt/optcore/updaters/geometry/objectivefuncs/__init__.py +5 -0
- morphopt/optcore/updaters/geometry/objectivefuncs/basefuncs.py +117 -0
- morphopt/optcore/updaters/geometry/objectivefuncs/boundarys.py +91 -0
- morphopt/optcore/updaters/geometry/objectivefuncs/distancesurface.py +123 -0
- morphopt/optcore/updaters/geometry/objectivefuncs/shapederivative.py +179 -0
- morphopt/optcore/updaters/geometry/objectivefuncs/surfacefairness.py +40 -0
- morphopt/optcore/updaters/geometry/update_geometry.py +347 -0
- morphopt/optcore/updaters/optimizer.py +177 -0
- morphopt/optcore/updaters/updaters.py +82 -0
- morphopt/optcore/utils/plot_history_surface.py +239 -0
- morphopt/taskoptmization.py +75 -0
- morphopt/taskui.py +458 -0
- morphopt-3.1.1.dist-info/METADATA +242 -0
- morphopt-3.1.1.dist-info/RECORD +49 -0
- morphopt-3.1.1.dist-info/WHEEL +5 -0
- morphopt-3.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import numpy as np
|
|
5
|
+
import torch
|
|
6
|
+
import gmsh
|
|
7
|
+
|
|
8
|
+
import morphopt
|
|
9
|
+
|
|
10
|
+
from .basesurfaceinterface import CpBasedInterface
|
|
11
|
+
import cpgeo
|
|
12
|
+
|
|
13
|
+
class CPGEOInterface(CpBasedInterface):
|
|
14
|
+
"""
|
|
15
|
+
Class to handle the CPGEO surface interface.
|
|
16
|
+
|
|
17
|
+
CPGEO uses triangular meshes with control points for morphology optimization,
|
|
18
|
+
providing a flexible alternative to B-spline surfaces for complex geometries.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, surface: cpgeo.CPGEO, init_size: float, symmetric = [0], MaxC = 1.0):
|
|
22
|
+
super().__init__(surface, symmetric)
|
|
23
|
+
|
|
24
|
+
self.model = surface
|
|
25
|
+
"""The CPGEO surface model."""
|
|
26
|
+
|
|
27
|
+
self._cps = torch.from_numpy(self.model.control_points).to(torch.get_default_device())
|
|
28
|
+
|
|
29
|
+
self.MaxC = MaxC
|
|
30
|
+
|
|
31
|
+
self.init_size = init_size
|
|
32
|
+
|
|
33
|
+
self._num_knots: int
|
|
34
|
+
"""The number of knot points for the CPGEO model."""
|
|
35
|
+
|
|
36
|
+
def initialize(self):
|
|
37
|
+
"""Initialize the CPGEO model and preload knot points for evaluation."""
|
|
38
|
+
|
|
39
|
+
# Initialize CPGEO knots and thresholds
|
|
40
|
+
self.model.initialize()
|
|
41
|
+
|
|
42
|
+
# Load control points into torch tensor
|
|
43
|
+
self._cps = torch.from_numpy(self.model.control_points).to(torch.get_default_device())
|
|
44
|
+
|
|
45
|
+
# Get knot points from the CPGEO model
|
|
46
|
+
# For CPGEO, we use knot points as evaluation points (analogous to UV grid for BSP)
|
|
47
|
+
self._num_knots = self.model._knots.shape[0]
|
|
48
|
+
self._preload_uv = torch.from_numpy(self.model._knots).to(torch.get_default_device())
|
|
49
|
+
|
|
50
|
+
# Precompute weights for knot points (derivative 0, 1, 2)
|
|
51
|
+
# get_weights3 returns: (indices_cps, indices_pts, w) for derivative=0
|
|
52
|
+
# (indices_cps, indices_pts, w), (indices_cps, indices_pts, wdu) for derivative=1
|
|
53
|
+
# (indices_cps, indices_pts, w), (indices_cps, indices_pts, wdu), (indices_cps, indices_pts, wdu2) for derivative=2
|
|
54
|
+
result = self.model.get_weights3(self.model._knots, derivative=2)
|
|
55
|
+
indices_cps, indices_pts, w, wdu, wdu2 = result
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
indices_cps_t = torch.from_numpy(indices_cps).to(torch.get_default_device())
|
|
59
|
+
indices_pts_t = torch.from_numpy(indices_pts).to(torch.get_default_device())
|
|
60
|
+
|
|
61
|
+
weights_per_query = indices_pts_t[1:] - indices_pts_t[:-1]
|
|
62
|
+
# 使用repeat_interleave创建查询点索引
|
|
63
|
+
indices_pts_t = torch.repeat_interleave(torch.arange(self._preload_uv.shape[0], dtype=torch.long), weights_per_query)
|
|
64
|
+
|
|
65
|
+
# Convert to torch tensors and flatten for compatibility with base class _map method
|
|
66
|
+
self._indices = torch.stack([indices_pts_t, indices_cps_t], dim=0).reshape(2, -1)
|
|
67
|
+
|
|
68
|
+
self._weights = torch.from_numpy(w).to(torch.get_default_device()).flatten()
|
|
69
|
+
|
|
70
|
+
self._weights_du = torch.from_numpy(wdu[0]).to(torch.get_default_device()).flatten()
|
|
71
|
+
self._weights_dv = torch.from_numpy(wdu[1]).to(torch.get_default_device()).flatten()
|
|
72
|
+
self._weights_du2 = torch.from_numpy(wdu2[0, 0]).to(torch.get_default_device()).flatten()
|
|
73
|
+
self._weights_dv2 = torch.from_numpy(wdu2[1, 1]).to(torch.get_default_device()).flatten()
|
|
74
|
+
self._weights_dudv = torch.from_numpy(wdu2[0, 1]).to(torch.get_default_device()).flatten()
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def output_stl_file(vertices, faces, path_output, name_output):
|
|
78
|
+
"""Output CPGEO mesh as STL file.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
vertices (np.ndarray): Vertex coordinates, shape (N, 3)
|
|
82
|
+
faces (np.ndarray): Face connectivity, shape (F, 3)
|
|
83
|
+
path_output (str): Output directory path
|
|
84
|
+
name_output (str): Output file name (without extension)
|
|
85
|
+
"""
|
|
86
|
+
import pyvista as pv
|
|
87
|
+
|
|
88
|
+
# Create PyVista mesh
|
|
89
|
+
faces_with_count = np.hstack([np.full((faces.shape[0], 1), 3), faces])
|
|
90
|
+
mesh = pv.PolyData(vertices, faces_with_count)
|
|
91
|
+
|
|
92
|
+
# Output STL file
|
|
93
|
+
output_file = path_output + name_output + '.stl'
|
|
94
|
+
mesh.save(output_file)
|
|
95
|
+
|
|
96
|
+
return output_file
|
|
97
|
+
|
|
98
|
+
def output_data(self, path_output, name_output, seed_size=-1, flip=False):
|
|
99
|
+
"""Output CPGEO mesh data to STL file.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
path_output (str): Output directory path
|
|
103
|
+
name_output (str): Output file name
|
|
104
|
+
seed_size (float): Optional seed size (unused for CPGEO)
|
|
105
|
+
flip (bool): Optional flip flag (unused for CPGEO)
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
str: Output filename with extension
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
pools = morphopt.controller.pools
|
|
112
|
+
r = self.get_r().detach().cpu().numpy()
|
|
113
|
+
result = pools.apply_async(self.output_stl_file, args=(
|
|
114
|
+
r,
|
|
115
|
+
self.model._cp_faces,
|
|
116
|
+
path_output,
|
|
117
|
+
name_output))
|
|
118
|
+
result.get()
|
|
119
|
+
|
|
120
|
+
return name_output + '.stl'
|
|
121
|
+
|
|
122
|
+
def _geofair_data(self, r: torch.Tensor, rdu: torch.Tensor, rdu2: torch.Tensor):
|
|
123
|
+
"""
|
|
124
|
+
To compute the geometric fairness data.
|
|
125
|
+
|
|
126
|
+
Parameters:
|
|
127
|
+
r (torch.Tensor): The surface points.
|
|
128
|
+
rdu (torch.Tensor): The first derivatives of the surface points.
|
|
129
|
+
rdu2 (torch.Tensor): The second derivatives of the surface points.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
C0 (torch.Tensor): The curvature-based fairness measure.
|
|
133
|
+
FF0 (torch.Tensor): The first fundamental form-based fairness measure.
|
|
134
|
+
RRuu0 (torch.Tensor): The second derivative in u direction-based fairness measure.
|
|
135
|
+
RRuv0 (torch.Tensor): The mixed second derivative-based fairness measure.
|
|
136
|
+
RRvu0 (torch.Tensor): The mixed second derivative-based fairness measure.
|
|
137
|
+
RRvv0 (torch.Tensor): The second derivative in v direction-based fairness measure.
|
|
138
|
+
"""
|
|
139
|
+
Normal0 = torch.cross(rdu[:, :, 1], rdu[:, :, 0], dim=1)
|
|
140
|
+
Normal = Normal0 / torch.sqrt(torch.sum(Normal0**2, dim=1, keepdim=True))
|
|
141
|
+
|
|
142
|
+
I = torch.einsum('pim, pin->pmn', rdu, rdu)
|
|
143
|
+
invI = I.inverse()
|
|
144
|
+
II = torch.einsum('pimn, pi->pmn', rdu2, Normal)
|
|
145
|
+
|
|
146
|
+
detI = I[:, 0, 0] * I[:, 1, 1] - I[:, 0, 1] * I[:, 1, 0]
|
|
147
|
+
detII = II[:, 0, 0] * II[:, 1, 1] - II[:, 0, 1] * II[:, 1, 0]
|
|
148
|
+
|
|
149
|
+
H = 0.5 * (invI * II).sum([1, 2])
|
|
150
|
+
K = detII / detI
|
|
151
|
+
|
|
152
|
+
C0 = 4 * H**2 - 2 * K
|
|
153
|
+
|
|
154
|
+
return C0
|
|
155
|
+
|
|
156
|
+
def get_penalty_fairness(self, weight: torch.Tensor, r: torch.Tensor, rdu: torch.Tensor, rdu2: torch.Tensor) -> torch.Tensor:
|
|
157
|
+
|
|
158
|
+
C0 = self._geofair_data(r, rdu, rdu2)
|
|
159
|
+
|
|
160
|
+
indexC, C = self.barrier_function(C0, self.MaxC, 0.8,
|
|
161
|
+
3)
|
|
162
|
+
|
|
163
|
+
return (weight[indexC] * C).sum()
|
|
164
|
+
|
|
165
|
+
def get_points_weight(self):
|
|
166
|
+
"""Compute area-based weights for each knot point in CPGEO mesh.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
torch.Tensor: Weights based on local surface area around each knot
|
|
170
|
+
"""
|
|
171
|
+
R0 = self.get_r().detach()
|
|
172
|
+
|
|
173
|
+
# Get face connectivity
|
|
174
|
+
faces = torch.from_numpy(self.model._cp_faces).to(R0.device)
|
|
175
|
+
|
|
176
|
+
# Compute area for each triangle
|
|
177
|
+
v0 = R0[faces[:, 0]]
|
|
178
|
+
v1 = R0[faces[:, 1]]
|
|
179
|
+
v2 = R0[faces[:, 2]]
|
|
180
|
+
|
|
181
|
+
# Triangle area = 0.5 * ||cross product||
|
|
182
|
+
areas = torch.cross(v1 - v0, v2 - v0, dim=1).norm(dim=1) / 2.0
|
|
183
|
+
|
|
184
|
+
# Distribute area to vertices (each vertex gets 1/3 of incident triangle areas)
|
|
185
|
+
weights = torch.zeros(R0.shape[0], device=R0.device)
|
|
186
|
+
for i in range(3):
|
|
187
|
+
weights.scatter_add_(0, faces[:, i], areas / 3.0)
|
|
188
|
+
|
|
189
|
+
return weights
|
|
190
|
+
|
|
191
|
+
def save(self, filename):
|
|
192
|
+
"""Save CPGEO model to file."""
|
|
193
|
+
self.model.control_points = self._cps.detach().cpu().numpy()
|
|
194
|
+
self.model.save(filename)
|
|
195
|
+
|
|
196
|
+
def load(self, filename):
|
|
197
|
+
"""Load CPGEO model from file."""
|
|
198
|
+
self.model = cpgeo.CPGEO.load(filename + '.npz')
|
|
199
|
+
self._cps = torch.from_numpy(self.model.control_points).to(torch.get_default_device())
|
|
200
|
+
self.model.refine_surface(seed_size=self.init_size, max_iterations=4)
|
|
201
|
+
self.initialize()
|
|
202
|
+
|
|
203
|
+
1
|
|
204
|
+
|
|
205
|
+
def get_mesh(self):
|
|
206
|
+
"""Get PyVista mesh from CPGEO model.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
pv.PolyData: PyVista mesh with current control point positions
|
|
210
|
+
"""
|
|
211
|
+
import pyvista as pv
|
|
212
|
+
|
|
213
|
+
# Get current control point positions
|
|
214
|
+
vertices = self.get_r().detach().cpu().numpy()
|
|
215
|
+
faces = self.model._cp_faces
|
|
216
|
+
|
|
217
|
+
# Create PyVista mesh (prepend face count for each triangle)
|
|
218
|
+
faces_with_count = np.hstack([np.full((faces.shape[0], 1), 3), faces])
|
|
219
|
+
mesh = pv.PolyData(vertices, faces_with_count)
|
|
220
|
+
mesh.compute_normals(inplace=True)
|
|
221
|
+
|
|
222
|
+
return mesh
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@classmethod
|
|
226
|
+
def initialize_cylinder(cls, r0: float, length: float, seed_size: float, flip: bool,
|
|
227
|
+
num_U_ratio: int = 1, num_V_ratio: int = 1, symmetric: list[int] = [0],
|
|
228
|
+
degree = 3, init_location = [0.,0.,0.], maxR = 0.2, maxC = 1., maxFF = 0.2,
|
|
229
|
+
perturbation_L = -1.):
|
|
230
|
+
"""
|
|
231
|
+
Initialize the CPGEO surface for cylinder shape optimization.
|
|
232
|
+
|
|
233
|
+
Parameters:
|
|
234
|
+
r0 (float): The radius of the cylinder.
|
|
235
|
+
length (float): The length of the cylinder.
|
|
236
|
+
seed_size (float): The size of the mesh seed.
|
|
237
|
+
flip (bool): Whether to flip the surface normal or not.
|
|
238
|
+
num_U_ratio (int, optional): The ratio for mesh density in circumferential direction. Default is 1.
|
|
239
|
+
num_V_ratio (int, optional): The ratio for mesh density in axial direction. Default is 1.
|
|
240
|
+
symmetric (list[int]): The symmetry of the surface.
|
|
241
|
+
degree (int, optional): Unused for CPGEO. Default is 3.
|
|
242
|
+
init_location (list[float], optional): The initial location of the surface. Default is [0., 0., 0.].
|
|
243
|
+
maxR (float, optional): The maximum curvature derivative constraint. Default is 0.2.
|
|
244
|
+
maxC (float, optional): The maximum curvature constraint. Default is 1.0.
|
|
245
|
+
maxFF (float, optional): The maximum fairness factor. Default is 0.2.
|
|
246
|
+
perturbation_L (float, optional): The perturbation length for initial shape. Default is -1. If < 0, no perturbation is applied.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
CPGEOInterface: The initialized CPGEO surface object.
|
|
250
|
+
"""
|
|
251
|
+
import trimesh
|
|
252
|
+
|
|
253
|
+
# Create cylindrical mesh using trimesh
|
|
254
|
+
# Estimate number of segments based on seed size
|
|
255
|
+
num_segments_circ = max(12, round(r0 * 2 * np.pi / seed_size))
|
|
256
|
+
num_segments_circ = round(num_segments_circ / 12) * 12 # Make it divisible by 12
|
|
257
|
+
num_segments_circ = round(num_segments_circ / num_U_ratio) * num_U_ratio
|
|
258
|
+
|
|
259
|
+
num_segments_axial = max(2, round(length / seed_size))
|
|
260
|
+
num_segments_axial = round(num_segments_axial / num_V_ratio) * num_V_ratio
|
|
261
|
+
|
|
262
|
+
# Generate cylinder vertices
|
|
263
|
+
vertices = []
|
|
264
|
+
for i in range(num_segments_axial + 1):
|
|
265
|
+
z = length * i / num_segments_axial
|
|
266
|
+
for j in range(num_segments_circ):
|
|
267
|
+
theta = 2 * np.pi * j / num_segments_circ
|
|
268
|
+
if flip:
|
|
269
|
+
theta = -theta
|
|
270
|
+
|
|
271
|
+
# Base radius
|
|
272
|
+
r_val = r0
|
|
273
|
+
|
|
274
|
+
# Apply perturbation if specified
|
|
275
|
+
if perturbation_L > 0:
|
|
276
|
+
r_val = r0 * (1 + 0.04 * np.cos(2 * (z / length) * np.pi * (length / perturbation_L)))
|
|
277
|
+
|
|
278
|
+
x = r_val * np.cos(theta) + init_location[0]
|
|
279
|
+
y = r_val * np.sin(theta) + init_location[1]
|
|
280
|
+
z_final = z + init_location[2]
|
|
281
|
+
|
|
282
|
+
vertices.append([x, y, z_final])
|
|
283
|
+
|
|
284
|
+
vertices = np.array(vertices)
|
|
285
|
+
|
|
286
|
+
# Generate faces (triangulate the cylinder surface)
|
|
287
|
+
faces = []
|
|
288
|
+
for i in range(num_segments_axial):
|
|
289
|
+
for j in range(num_segments_circ):
|
|
290
|
+
# Current ring
|
|
291
|
+
v00 = i * num_segments_circ + j
|
|
292
|
+
v01 = i * num_segments_circ + (j + 1) % num_segments_circ
|
|
293
|
+
# Next ring
|
|
294
|
+
v10 = (i + 1) * num_segments_circ + j
|
|
295
|
+
v11 = (i + 1) * num_segments_circ + (j + 1) % num_segments_circ
|
|
296
|
+
|
|
297
|
+
# Two triangles per quad
|
|
298
|
+
if flip:
|
|
299
|
+
faces.append([v00, v10, v01])
|
|
300
|
+
faces.append([v01, v10, v11])
|
|
301
|
+
else:
|
|
302
|
+
faces.append([v00, v01, v10])
|
|
303
|
+
faces.append([v01, v11, v10])
|
|
304
|
+
|
|
305
|
+
faces = np.array(faces)
|
|
306
|
+
|
|
307
|
+
# Create CPGEO model
|
|
308
|
+
cpgeo_model = cpgeo.CPGEO(
|
|
309
|
+
control_points=vertices,
|
|
310
|
+
cp_faces=faces,
|
|
311
|
+
knot_influence_num=20
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
# Create interface
|
|
315
|
+
output = cls(cpgeo_model, init_size=seed_size, symmetric=symmetric,
|
|
316
|
+
MaxR=maxR, MaxC=maxC, MaxFF=maxFF)
|
|
317
|
+
output.flip = flip
|
|
318
|
+
|
|
319
|
+
return output
|
|
320
|
+
|
|
321
|
+
@classmethod
|
|
322
|
+
def initialize_Sphere(cls, r0: float, seed_size: float, flip: bool, symmetric: list[int] = [0], init_location = [0.,0.,0.],
|
|
323
|
+
MaxC = 1.0):
|
|
324
|
+
"""
|
|
325
|
+
Initialize the CPGEO surface for sphere shape optimization.
|
|
326
|
+
|
|
327
|
+
Parameters:
|
|
328
|
+
r0 (float): The radius of the sphere.
|
|
329
|
+
seed_size (float): The size of the mesh seed.
|
|
330
|
+
flip (bool): Whether to flip the surface normal or not.
|
|
331
|
+
symmetric (list[int]): The symmetry of the surface.
|
|
332
|
+
init_location (list[float], optional): The initial location of the surface. Default is [0., 0., 0.].
|
|
333
|
+
MaxC (float, optional): The maximum curvature constraint. Default is 1.0
|
|
334
|
+
Returns:
|
|
335
|
+
CPGEOInterface: The initialized CPGEO surface object.
|
|
336
|
+
|
|
337
|
+
"""
|
|
338
|
+
import cpgeo.capi
|
|
339
|
+
|
|
340
|
+
# Estimate number of points based on surface area and seed size
|
|
341
|
+
area = 4 * np.pi * r0**2
|
|
342
|
+
num_points = int(area / (seed_size**2))
|
|
343
|
+
|
|
344
|
+
# Generate points using Fibonacci Lattice for uniform spherical distribution
|
|
345
|
+
indices = np.arange(0, num_points, dtype=float) + 0.5
|
|
346
|
+
phi = np.arccos(1 - 2*indices/num_points) + 1e-9 # small offset to avoid north pole singularity
|
|
347
|
+
theta = np.pi * (1 + 5**0.5) * indices
|
|
348
|
+
|
|
349
|
+
x = r0 * np.cos(theta) * np.sin(phi) + init_location[0]
|
|
350
|
+
y = r0 * np.sin(theta) * np.sin(phi) + init_location[1]
|
|
351
|
+
z = r0 * np.cos(phi) + init_location[2]
|
|
352
|
+
|
|
353
|
+
vertices = np.stack((x, y, z), axis=-1)
|
|
354
|
+
|
|
355
|
+
# Get triangulation from CAPI
|
|
356
|
+
# We pass coordinates relative to the center, as spherical triangulation
|
|
357
|
+
# typically operates on directions or convex hull of points
|
|
358
|
+
vertices_relative = (vertices - np.array(init_location)) / r0
|
|
359
|
+
faces = cpgeo.capi.get_sphere_triangulation(vertices_relative.flatten())
|
|
360
|
+
|
|
361
|
+
# Create CPGEO model
|
|
362
|
+
cpgeo_model = cpgeo.CPGEO(
|
|
363
|
+
control_points=vertices,
|
|
364
|
+
cp_faces=faces,
|
|
365
|
+
knot_influence_num=20
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
# Create interface
|
|
369
|
+
output = cls(cpgeo_model, init_size=seed_size, symmetric=symmetric, MaxC=MaxC)
|
|
370
|
+
output.flip = flip
|
|
371
|
+
|
|
372
|
+
return output
|
|
373
|
+
|
|
374
|
+
|