plot3d 1.6.8__tar.gz → 1.7.0__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.
- {plot3d-1.6.8 → plot3d-1.7.0}/PKG-INFO +3 -3
- {plot3d-1.6.8 → plot3d-1.7.0}/plot3d/__init__.py +11 -7
- {plot3d-1.6.8 → plot3d-1.7.0}/plot3d/block.py +28 -75
- plot3d-1.7.0/plot3d/block_merging_mixed_facepairs.py +301 -0
- plot3d-1.7.0/plot3d/blockfunctions.py +361 -0
- {plot3d-1.6.8 → plot3d-1.7.0}/plot3d/connectivity.py +3 -3
- {plot3d-1.6.8 → plot3d-1.7.0}/plot3d/face.py +13 -11
- {plot3d-1.6.8 → plot3d-1.7.0}/plot3d/facefunctions.py +51 -3
- {plot3d-1.6.8 → plot3d-1.7.0}/pyproject.toml +4 -4
- plot3d-1.6.8/plot3d/blockfunctions.py +0 -179
- {plot3d-1.6.8 → plot3d-1.7.0}/plot3d/differencing.py +0 -0
- {plot3d-1.6.8 → plot3d-1.7.0}/plot3d/graph.py +0 -0
- {plot3d-1.6.8 → plot3d-1.7.0}/plot3d/listfunctions.py +0 -0
- {plot3d-1.6.8 → plot3d-1.7.0}/plot3d/periodicity.py +0 -0
- {plot3d-1.6.8 → plot3d-1.7.0}/plot3d/point_match.py +0 -0
- {plot3d-1.6.8 → plot3d-1.7.0}/plot3d/read.py +0 -0
- {plot3d-1.6.8 → plot3d-1.7.0}/plot3d/split_block.py +0 -0
- {plot3d-1.6.8 → plot3d-1.7.0}/plot3d/write.py +0 -0
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: plot3d
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7.0
|
|
4
4
|
Summary: Plot3D python utilities for reading and writing and also finding connectivity between blocks
|
|
5
5
|
Author: Paht Juangphanich
|
|
6
6
|
Author-email: paht.juangphanich@nasa.gov
|
|
7
|
-
Requires-Python: >=3.10.
|
|
7
|
+
Requires-Python: >=3.10.12,<4.0.0
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: Programming Language :: Python :: 3.11
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.12
|
|
11
11
|
Requires-Dist: networkx
|
|
12
|
-
Requires-Dist: numpy
|
|
12
|
+
Requires-Dist: numpy
|
|
13
13
|
Requires-Dist: pandas
|
|
14
14
|
Requires-Dist: scipy
|
|
15
15
|
Requires-Dist: tqdm
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
from __future__ import absolute_import
|
|
2
|
+
from importlib import import_module
|
|
3
|
+
import os, warnings
|
|
2
4
|
|
|
3
|
-
from .block import Block
|
|
4
|
-
from .blockfunctions import rotate_block,get_outer_bounds,block_connection_matrix
|
|
5
|
+
from .block import Block
|
|
6
|
+
from .blockfunctions import rotate_block, get_outer_bounds, block_connection_matrix,split_blocks, plot_blocks, reduce_blocks, find_matching_faces
|
|
7
|
+
from .block_merging_mixed_facepairs import combine_nxnxn_cubes_mixed_pairs
|
|
5
8
|
from .connectivity import find_matching_blocks, get_face_intersection, connectivity_fast, face_matches_to_dict
|
|
6
9
|
from .face import Face
|
|
7
10
|
from .facefunctions import create_face_from_diagonals, get_outer_faces, find_connected_faces, find_bounding_faces,split_face,find_face_nearest_point,match_faces_dict_to_list,outer_face_dict_to_list,find_closest_block
|
|
@@ -14,8 +17,9 @@ from .split_block import split_blocks, Direction
|
|
|
14
17
|
from .listfunctions import unique_pairs
|
|
15
18
|
|
|
16
19
|
# Try importing metis
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
if os.getenv('METIS_DLL') is not None:
|
|
21
|
+
if import_module('metis') is not None:
|
|
22
|
+
import metis
|
|
23
|
+
from .graph import block_to_graph,get_face_vertex_indices,get_starting_vertex,add_connectivity_to_graph, block_connectivity_to_graph
|
|
24
|
+
else:
|
|
25
|
+
print("METIS_DLL is not set. metis may not be configured. plot3D will function without metis")
|
|
@@ -2,17 +2,25 @@ import numpy as np
|
|
|
2
2
|
import math
|
|
3
3
|
from tqdm import trange
|
|
4
4
|
from typing import List
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
import numpy.typing as npt
|
|
5
7
|
|
|
6
8
|
class Block:
|
|
7
9
|
"""Plot3D Block definition
|
|
8
10
|
"""
|
|
9
|
-
|
|
11
|
+
X: npt.NDArray
|
|
12
|
+
Y: npt.NDArray
|
|
13
|
+
Z: npt.NDArray
|
|
14
|
+
IMAX:int
|
|
15
|
+
JMAX:int
|
|
16
|
+
KMAX:int
|
|
17
|
+
def __init__(self, X:npt.NDArray,Y:npt.NDArray,Z:npt.NDArray):
|
|
10
18
|
"""Initializes the block using all the X,Y,Z coordinates of the block
|
|
11
19
|
|
|
12
20
|
Args:
|
|
13
|
-
X (
|
|
14
|
-
Y (
|
|
15
|
-
Z (
|
|
21
|
+
X (npt.NDArray): All the X coordinates (i,j,k)
|
|
22
|
+
Y (npt.NDArray): All the Y coordinates (i,j,k)
|
|
23
|
+
Z (npt.NDArray): All the Z coordinates (i,j,k)
|
|
16
24
|
|
|
17
25
|
"""
|
|
18
26
|
self.IMAX,self.JMAX,self.KMAX = X.shape;
|
|
@@ -24,6 +32,8 @@ class Block:
|
|
|
24
32
|
self.cy = np.mean(Y)
|
|
25
33
|
self.cz = np.mean(Z)
|
|
26
34
|
|
|
35
|
+
def __repr__(self):
|
|
36
|
+
return f"({self.IMAX},{self.JMAX},{self.KMAX})"
|
|
27
37
|
|
|
28
38
|
def scale(self,factor:float):
|
|
29
39
|
"""Scales a mesh by a certain factor
|
|
@@ -164,6 +174,20 @@ class Block:
|
|
|
164
174
|
v[i,j,k]= vol12/12
|
|
165
175
|
return v
|
|
166
176
|
|
|
177
|
+
def get_faces(self):
|
|
178
|
+
"""
|
|
179
|
+
Returns a dictionary of the six faces of the block.
|
|
180
|
+
Each face is a tuple of (X_face, Y_face, Z_face).
|
|
181
|
+
"""
|
|
182
|
+
return {
|
|
183
|
+
'imin': (self.X[0,:,:], self.Y[0,:,:], self.Z[0,:,:]),
|
|
184
|
+
'imax': (self.X[-1,:,:], self.Y[-1,:,:], self.Z[-1,:,:]),
|
|
185
|
+
'jmin': (self.X[:,0,:], self.Y[:,0,:], self.Z[:,0,:]),
|
|
186
|
+
'jmax': (self.X[:,-1,:], self.Y[:,-1,:], self.Z[:,-1,:]),
|
|
187
|
+
'kmin': (self.X[:,:,0], self.Y[:,:,0], self.Z[:,:,0]),
|
|
188
|
+
'kmax': (self.X[:,:,-1], self.Y[:,:,-1], self.Z[:,:,-1]),
|
|
189
|
+
}
|
|
190
|
+
|
|
167
191
|
@property
|
|
168
192
|
def size(self)->int:
|
|
169
193
|
"""returns the total number of nodes
|
|
@@ -173,77 +197,6 @@ class Block:
|
|
|
173
197
|
"""
|
|
174
198
|
return self.IMAX*self.JMAX*self.KMAX
|
|
175
199
|
|
|
176
|
-
def checkCollinearity(v1:np.ndarray, v2:np.ndarray):
|
|
177
|
-
# Calculate their cross product
|
|
178
|
-
cross_P = np.cross(v1,v2)
|
|
179
|
-
|
|
180
|
-
# Check if their cross product
|
|
181
|
-
# is a NULL Vector or not
|
|
182
|
-
if (cross_P[0] == 0 and
|
|
183
|
-
cross_P[1] == 0 and
|
|
184
|
-
cross_P[2] == 0):
|
|
185
|
-
return True
|
|
186
|
-
else:
|
|
187
|
-
return False
|
|
188
|
-
|
|
189
|
-
def calculate_outward_normals(block:Block):
|
|
190
|
-
# Calculate Normals
|
|
191
|
-
X = block.X
|
|
192
|
-
Y = block.Y
|
|
193
|
-
Z = block.Z
|
|
194
|
-
imax = block.IMAX
|
|
195
|
-
jmax = block.JMAX
|
|
196
|
-
kmax = block.KMAX
|
|
197
|
-
# IMAX - Normal should be out of the page
|
|
198
|
-
# Normals I direction: IMIN https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal
|
|
199
|
-
x = [X[0,0,0],X[0,jmax,0],X[0,0,kmax]]
|
|
200
|
-
y = [Y[0,0,0],Y[0,jmax,0],Y[0,0,kmax]]
|
|
201
|
-
z = [Z[0,0,0],Z[0,jmax,0],Z[0,0,kmax]]
|
|
202
|
-
u = np.array([x[1]-x[0],y[1]-y[0],z[1]-z[0]])
|
|
203
|
-
v = np.array([x[2]-x[0],y[2]-y[0],z[2]-z[0]])
|
|
204
|
-
n_imin = np.cross(v1,v2)
|
|
205
|
-
|
|
206
|
-
# Normals I direction: IMAX
|
|
207
|
-
x = [X[imax,0,0],X[imax,jmax,0],X[imax,0,kmax]]
|
|
208
|
-
y = [Y[imax,0,0],Y[imax,jmax,0],Y[imax,0,kmax]]
|
|
209
|
-
z = [Z[imax,0,0],Z[imax,jmax,0],Z[imax,0,kmax]]
|
|
210
|
-
v1 = np.array([x[1]-x[0],y[1]-y[0],z[1]-z[0]])
|
|
211
|
-
v2 = np.array([x[2]-x[0],y[2]-y[0],z[2]-z[0]])
|
|
212
|
-
n_imax = np.cross(v1,v2)
|
|
213
|
-
|
|
214
|
-
# Normals J direction: JMIN
|
|
215
|
-
x = [X[0,0,0],X[imax,0,0],X[0,0,kmax]]
|
|
216
|
-
y = [Y[0,0,0],Y[imax,0,0],Y[0,0,kmax]]
|
|
217
|
-
z = [Z[0,0,0],Z[imax,0,0],Z[0,0,kmax]]
|
|
218
|
-
v1 = np.array([x[1]-x[0],y[1]-y[0],z[1]-z[0]])
|
|
219
|
-
v2 = np.array([x[2]-x[0],y[2]-y[0],z[2]-z[0]])
|
|
220
|
-
n_jmin = np.cross(v1,v2)
|
|
221
|
-
|
|
222
|
-
# Normals J direction: JMAX
|
|
223
|
-
x = [X[0,jmax,0],X[imax,jmax,0],X[0,jmax,kmax]]
|
|
224
|
-
y = [Y[0,jmax,0],Y[imax,jmax,0],Y[0,jmax,kmax]]
|
|
225
|
-
z = [Z[0,jmax,0],Z[imax,jmax,0],Z[0,jmax,kmax]]
|
|
226
|
-
v1 = np.array([x[1]-x[0],y[1]-y[0],z[1]-z[0]])
|
|
227
|
-
v2 = np.array([x[2]-x[0],y[2]-y[0],z[2]-z[0]])
|
|
228
|
-
n_jmax = np.cross(v1,v2)
|
|
229
|
-
|
|
230
|
-
# Normals K direction: KMIN
|
|
231
|
-
x = [X[imax,0,0],X[0,jmax,0],X[0,0,0]]
|
|
232
|
-
y = [Y[imax,0,0],Y[0,jmax,0],Y[0,0,0]]
|
|
233
|
-
z = [Z[imax,0,0],Z[0,jmax,0],Z[0,0,0]]
|
|
234
|
-
v1 = np.array([x[1]-x[0],y[1]-y[0],z[1]-z[0]])
|
|
235
|
-
v2 = np.array([x[2]-x[0],y[2]-y[0],z[2]-z[0]])
|
|
236
|
-
n_kmin = np.cross(v1,v2)
|
|
237
|
-
|
|
238
|
-
# Normals K direction: KMAX
|
|
239
|
-
x = [X[imax,0,kmax],X[0,jmax,kmax],X[0,0,kmax]]
|
|
240
|
-
y = [Y[imax,0,kmax],Y[0,jmax,kmax],Y[0,0,kmax]]
|
|
241
|
-
z = [Z[imax,0,kmax],Z[0,jmax,kmax],Z[0,0,kmax]]
|
|
242
|
-
v1 = np.array([x[1]-x[0],y[1]-y[0],z[1]-z[0]])
|
|
243
|
-
v2 = np.array([x[2]-x[0],y[2]-y[0],z[2]-z[0]])
|
|
244
|
-
n_kmax = np.cross(v1,v2)
|
|
245
|
-
|
|
246
|
-
return n_imin,n_jmin,n_kmin,n_imax,n_jmax,n_kmax
|
|
247
200
|
|
|
248
201
|
def reduce_blocks(blocks:List[Block],factor:int):
|
|
249
202
|
"""reduce the blocks by a factor of (factor)
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
from typing import Dict, List, Set, Tuple
|
|
2
|
+
import numpy as np
|
|
3
|
+
from .block import Block
|
|
4
|
+
from .blockfunctions import find_matching_faces, build_connectivity_graph, standardize_block_orientation
|
|
5
|
+
from .write import write_plot3D
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def rotate_block_to_align_faces(X, Y, Z, face_from: str, face_to: str):
|
|
9
|
+
"""
|
|
10
|
+
Rotate block2 geometry so that face_from aligns with face_to.
|
|
11
|
+
Currently supports: jmax → imin.
|
|
12
|
+
"""
|
|
13
|
+
if (face_from, face_to) == ('jmax', 'imin'):
|
|
14
|
+
Xr = np.transpose(X, (1, 0, 2))
|
|
15
|
+
Yr = np.transpose(Y, (1, 0, 2))
|
|
16
|
+
Zr = np.transpose(Z, (1, 0, 2))
|
|
17
|
+
Xr = np.flip(Xr, axis=0)
|
|
18
|
+
Yr = np.flip(Yr, axis=0)
|
|
19
|
+
Zr = np.flip(Zr, axis=0)
|
|
20
|
+
return Xr, Yr, Zr
|
|
21
|
+
raise NotImplementedError(f"Rotation from {face_from} to {face_to} not implemented.")
|
|
22
|
+
|
|
23
|
+
def combine_2_blocks_mixed_pairing(block1, block2, tol=1e-8):
|
|
24
|
+
"""
|
|
25
|
+
Combine block1 and block2 by matching and aligning any pair of faces, including cross-axis combinations.
|
|
26
|
+
Automatically transposes and flips block2, and flips block1 if necessary to maintain monotonic physical direction.
|
|
27
|
+
|
|
28
|
+
This version generalizes the direction check to detect which of X, Y, or Z is varying most along the stacking axis,
|
|
29
|
+
and uses that to determine whether block1 needs to be flipped before concatenation.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Block: The merged block with physically consistent orientation.
|
|
33
|
+
"""
|
|
34
|
+
face1, face2, flip_flags = find_matching_faces(block1, block2, tol=tol)
|
|
35
|
+
if face1 is None or flip_flags is None:
|
|
36
|
+
print("No matching faces or incompatible orientation.")
|
|
37
|
+
return block1
|
|
38
|
+
|
|
39
|
+
flip_ud, flip_lr = flip_flags
|
|
40
|
+
|
|
41
|
+
face_axis_normal = {
|
|
42
|
+
'imin': (0, -1), 'imax': (0, 1),
|
|
43
|
+
'jmin': (1, -1), 'jmax': (1, 1),
|
|
44
|
+
'kmin': (2, -1), 'kmax': (2, 1),
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
axis1, dir1 = face_axis_normal[face1]
|
|
48
|
+
axis2, dir2 = face_axis_normal[face2] # type: ignore
|
|
49
|
+
|
|
50
|
+
# Transpose block2 to align its face axis with block1's
|
|
51
|
+
transpose = None
|
|
52
|
+
if axis1 != axis2:
|
|
53
|
+
if (axis1, axis2) in [(0, 1), (1, 0)]:
|
|
54
|
+
transpose = (1, 0, 2)
|
|
55
|
+
elif (axis1, axis2) in [(0, 2), (2, 0)]:
|
|
56
|
+
transpose = (2, 1, 0)
|
|
57
|
+
elif (axis1, axis2) in [(1, 2), (2, 1)]:
|
|
58
|
+
transpose = (0, 2, 1)
|
|
59
|
+
|
|
60
|
+
X2, Y2, Z2 = block2.X.copy(), block2.Y.copy(), block2.Z.copy()
|
|
61
|
+
if transpose:
|
|
62
|
+
X2, Y2, Z2 = np.transpose(X2, transpose), np.transpose(Y2, transpose), np.transpose(Z2, transpose)
|
|
63
|
+
|
|
64
|
+
# Apply local flips from face alignment
|
|
65
|
+
if face2 in ['imin', 'imax']:
|
|
66
|
+
if flip_ud: X2, Y2, Z2 = np.flip(X2, 1), np.flip(Y2, 1), np.flip(Z2, 1)
|
|
67
|
+
if flip_lr: X2, Y2, Z2 = np.flip(X2, 2), np.flip(Y2, 2), np.flip(Z2, 2)
|
|
68
|
+
elif face2 in ['jmin', 'jmax']:
|
|
69
|
+
if flip_ud: X2, Y2, Z2 = np.flip(X2, 0), np.flip(Y2, 0), np.flip(Z2, 0)
|
|
70
|
+
if flip_lr: X2, Y2, Z2 = np.flip(X2, 2), np.flip(Y2, 2), np.flip(Z2, 2)
|
|
71
|
+
elif face2 in ['kmin', 'kmax']:
|
|
72
|
+
if flip_ud: X2, Y2, Z2 = np.flip(X2, 0), np.flip(Y2, 0), np.flip(Z2, 0)
|
|
73
|
+
if flip_lr: X2, Y2, Z2 = np.flip(X2, 1), np.flip(Y2, 1), np.flip(Z2, 1)
|
|
74
|
+
|
|
75
|
+
# Determine stacking axis
|
|
76
|
+
stack_axis = axis1
|
|
77
|
+
|
|
78
|
+
# Identify which physical axis (X/Y/Z) changes most along the stacking axis
|
|
79
|
+
def get_dominant_step(A, axis):
|
|
80
|
+
n = A.shape[axis]
|
|
81
|
+
center = [s // 2 for s in A.shape]
|
|
82
|
+
center[axis] = slice(None)
|
|
83
|
+
values = A[tuple(center)]
|
|
84
|
+
return np.nanmean(values[-1] - values[0])
|
|
85
|
+
|
|
86
|
+
# Step values in block1
|
|
87
|
+
stepX1 = get_dominant_step(block1.X, stack_axis)
|
|
88
|
+
stepY1 = get_dominant_step(block1.Y, stack_axis)
|
|
89
|
+
stepZ1 = get_dominant_step(block1.Z, stack_axis)
|
|
90
|
+
dominant_axis = np.argmax(np.abs([stepX1, stepY1, stepZ1]))
|
|
91
|
+
step1 = [stepX1, stepY1, stepZ1][dominant_axis]
|
|
92
|
+
|
|
93
|
+
# Step values in block2 (transformed and flipped)
|
|
94
|
+
stepX2 = get_dominant_step(X2, stack_axis)
|
|
95
|
+
stepY2 = get_dominant_step(Y2, stack_axis)
|
|
96
|
+
stepZ2 = get_dominant_step(Z2, stack_axis)
|
|
97
|
+
step2 = [stepX2, stepY2, stepZ2][dominant_axis]
|
|
98
|
+
|
|
99
|
+
# Flip block1 if physical step directions are inconsistent
|
|
100
|
+
if np.sign(step1) != np.sign(step2):
|
|
101
|
+
block1.X = np.flip(block1.X, axis=stack_axis)
|
|
102
|
+
block1.Y = np.flip(block1.Y, axis=stack_axis)
|
|
103
|
+
block1.Z = np.flip(block1.Z, axis=stack_axis)
|
|
104
|
+
|
|
105
|
+
# Slice off overlapping face from block2
|
|
106
|
+
slicer = [slice(None)] * 3
|
|
107
|
+
slicer[stack_axis] = slice(1, None) if face2.endswith('min') else slice(0, -1) # type: ignore
|
|
108
|
+
X2s, Y2s, Z2s = X2[tuple(slicer)], Y2[tuple(slicer)], Z2[tuple(slicer)]
|
|
109
|
+
|
|
110
|
+
# Concatenate along stack axis
|
|
111
|
+
if face2.endswith('min'): # type: ignore
|
|
112
|
+
X = np.concatenate([block1.X, X2s], axis=stack_axis)
|
|
113
|
+
Y = np.concatenate([block1.Y, Y2s], axis=stack_axis)
|
|
114
|
+
Z = np.concatenate([block1.Z, Z2s], axis=stack_axis)
|
|
115
|
+
else:
|
|
116
|
+
X = np.concatenate([X2s, block1.X], axis=stack_axis)
|
|
117
|
+
Y = np.concatenate([Y2s, block1.Y], axis=stack_axis)
|
|
118
|
+
Z = np.concatenate([Z2s, block1.Z], axis=stack_axis)
|
|
119
|
+
return standardize_block_orientation(Block(X, Y, Z))
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def combine_blocks_mixed_pairs(blocks: List[Block], tol: float = 1e-8, max_tries: int = 4) -> Tuple[List[Block], List[int]]:
|
|
124
|
+
"""
|
|
125
|
+
Combine as many blocks as possible from a group of up to 8 blocks via face matching.
|
|
126
|
+
|
|
127
|
+
Parameters
|
|
128
|
+
----------
|
|
129
|
+
blocks : List[Block]
|
|
130
|
+
List of up to 8 Block objects.
|
|
131
|
+
tol : float
|
|
132
|
+
Tolerance for face matching.
|
|
133
|
+
max_tries : int
|
|
134
|
+
Number of passes to try merging the blocks further.
|
|
135
|
+
|
|
136
|
+
Returns
|
|
137
|
+
-------
|
|
138
|
+
merged_blocks : List[Block]
|
|
139
|
+
List of successfully merged Block objects (1 or more).
|
|
140
|
+
used_indices : List[int]
|
|
141
|
+
Indices of blocks that were used in any successful merge.
|
|
142
|
+
"""
|
|
143
|
+
from itertools import combinations
|
|
144
|
+
|
|
145
|
+
remaining = list(enumerate(blocks)) # [(index, Block)]
|
|
146
|
+
used_indices = set()
|
|
147
|
+
|
|
148
|
+
# Initial merged_blocks are just the input blocks
|
|
149
|
+
merged_blocks = [blk for _, blk in remaining]
|
|
150
|
+
index_lookup = {id(blk): idx for idx, blk in remaining}
|
|
151
|
+
|
|
152
|
+
tries = 0
|
|
153
|
+
while len(merged_blocks) > 1 and tries < max_tries:
|
|
154
|
+
new_merged = []
|
|
155
|
+
merged_flags = [False] * len(merged_blocks)
|
|
156
|
+
used_this_pass = set()
|
|
157
|
+
skip = set()
|
|
158
|
+
|
|
159
|
+
i = 0
|
|
160
|
+
while i < len(merged_blocks):
|
|
161
|
+
if i in skip:
|
|
162
|
+
i += 1
|
|
163
|
+
continue
|
|
164
|
+
|
|
165
|
+
blk_a = merged_blocks[i]
|
|
166
|
+
merged = None
|
|
167
|
+
found = False
|
|
168
|
+
|
|
169
|
+
for j in range(i + 1, len(merged_blocks)):
|
|
170
|
+
if j in skip:
|
|
171
|
+
continue
|
|
172
|
+
blk_b = merged_blocks[j]
|
|
173
|
+
face1, face2,_ = find_matching_faces(blk_a, blk_b, tol=tol)
|
|
174
|
+
if face1 is not None:
|
|
175
|
+
try:
|
|
176
|
+
merged = combine_2_blocks_mixed_pairing(blk_a, blk_b, tol=tol)
|
|
177
|
+
found = True
|
|
178
|
+
break
|
|
179
|
+
except Exception as e:
|
|
180
|
+
print(f"⚠️ Failed to merge blocks {i} and {j}: {e}")
|
|
181
|
+
|
|
182
|
+
if found:
|
|
183
|
+
new_merged.append(merged)
|
|
184
|
+
skip.update([i, j]) # type: ignore
|
|
185
|
+
else:
|
|
186
|
+
new_merged.append(blk_a)
|
|
187
|
+
skip.add(i)
|
|
188
|
+
|
|
189
|
+
i += 1
|
|
190
|
+
|
|
191
|
+
# Add any unmerged blocks at the end
|
|
192
|
+
for k in range(len(merged_blocks)):
|
|
193
|
+
if k not in skip:
|
|
194
|
+
new_merged.append(merged_blocks[k])
|
|
195
|
+
|
|
196
|
+
merged_blocks = new_merged
|
|
197
|
+
tries += 1
|
|
198
|
+
|
|
199
|
+
# Recover used indices (conservatively: all blocks involved in merging)
|
|
200
|
+
used_indices = list(range(len(blocks))) # all blocks are assumed used here
|
|
201
|
+
|
|
202
|
+
return merged_blocks, used_indices
|
|
203
|
+
|
|
204
|
+
def combine_nxnxn_cubes_mixed_pairs(
|
|
205
|
+
blocks: List[Block],
|
|
206
|
+
connectivities: List[List[Dict]],
|
|
207
|
+
cube_size: int = 2,
|
|
208
|
+
tol: float = 1e-8
|
|
209
|
+
) -> List[Tuple[Block, Set[int]]]:
|
|
210
|
+
"""
|
|
211
|
+
Find and combine all non-overlapping nxnxn cube groups of blocks
|
|
212
|
+
using face connectivity data and return the merged components.
|
|
213
|
+
|
|
214
|
+
Parameters
|
|
215
|
+
----------
|
|
216
|
+
blocks : List[Block]
|
|
217
|
+
All input block objects.
|
|
218
|
+
connectivities : List of face match metadata pairs
|
|
219
|
+
Face connectivity between blocks.
|
|
220
|
+
cube_size : int
|
|
221
|
+
Size of the cube (e.g. 2 for 2x2x2, 4 for 4x4x4).
|
|
222
|
+
tol : float
|
|
223
|
+
Face match tolerance.
|
|
224
|
+
|
|
225
|
+
Returns
|
|
226
|
+
-------
|
|
227
|
+
List[Tuple[Block, Set[int]]]
|
|
228
|
+
A list of merged Block objects and their source block indices.
|
|
229
|
+
"""
|
|
230
|
+
from itertools import product
|
|
231
|
+
|
|
232
|
+
used = set()
|
|
233
|
+
merged_groups = []
|
|
234
|
+
G = build_connectivity_graph(connectivities)
|
|
235
|
+
|
|
236
|
+
remaining_indices = list(range(len(blocks)))
|
|
237
|
+
|
|
238
|
+
def find_nxnxn_group(seed_index):
|
|
239
|
+
from collections import deque
|
|
240
|
+
visited = set()
|
|
241
|
+
queue = deque([seed_index])
|
|
242
|
+
group = set()
|
|
243
|
+
|
|
244
|
+
while queue and len(group) < cube_size ** 3:
|
|
245
|
+
idx = queue.popleft()
|
|
246
|
+
if idx in visited or idx in used:
|
|
247
|
+
continue
|
|
248
|
+
visited.add(idx)
|
|
249
|
+
group.add(idx)
|
|
250
|
+
for nbr in G.neighbors(idx):
|
|
251
|
+
if nbr not in visited and nbr not in used:
|
|
252
|
+
queue.append(nbr)
|
|
253
|
+
|
|
254
|
+
return group if len(group) == cube_size ** 3 else None
|
|
255
|
+
|
|
256
|
+
while True:
|
|
257
|
+
before_len = len(remaining_indices)
|
|
258
|
+
merged_this_round = False
|
|
259
|
+
new_used = set()
|
|
260
|
+
|
|
261
|
+
i = 0
|
|
262
|
+
while i < len(remaining_indices):
|
|
263
|
+
seed_index = remaining_indices[i]
|
|
264
|
+
if seed_index in used:
|
|
265
|
+
i += 1
|
|
266
|
+
continue
|
|
267
|
+
|
|
268
|
+
group_indices = find_nxnxn_group(seed_index)
|
|
269
|
+
if not group_indices or group_indices & new_used:
|
|
270
|
+
i += 1
|
|
271
|
+
continue
|
|
272
|
+
|
|
273
|
+
group_block_list = [blocks[k] for k in sorted(group_indices)]
|
|
274
|
+
index_mapping = {i: orig_idx for i, orig_idx in enumerate(sorted(group_indices))}
|
|
275
|
+
|
|
276
|
+
try:
|
|
277
|
+
partial_merges, local_indices = combine_blocks_mixed_pairs(group_block_list, tol=tol)
|
|
278
|
+
for merged_block in partial_merges:
|
|
279
|
+
merged_group = {index_mapping[i] for i in local_indices}
|
|
280
|
+
merged_groups.append((merged_block, merged_group))
|
|
281
|
+
# write_plot3D('block1.xyz',[merged_block])
|
|
282
|
+
new_used.update(merged_group)
|
|
283
|
+
merged_this_round = True
|
|
284
|
+
except Exception as e:
|
|
285
|
+
print(f"⚠️ Skipping group {group_indices} due to error: {e}")
|
|
286
|
+
i += 1
|
|
287
|
+
continue
|
|
288
|
+
|
|
289
|
+
# Update remaining_indices and restart inner loop
|
|
290
|
+
remaining_indices = [idx for idx in remaining_indices if idx not in new_used]
|
|
291
|
+
i = 0
|
|
292
|
+
|
|
293
|
+
used.update(new_used)
|
|
294
|
+
|
|
295
|
+
if not merged_this_round or len(remaining_indices) == before_len:
|
|
296
|
+
print("✅ No further merges possible. Appending unmerged blocks.")
|
|
297
|
+
for idx in remaining_indices:
|
|
298
|
+
merged_groups.append((blocks[idx], {idx}))
|
|
299
|
+
break
|
|
300
|
+
|
|
301
|
+
return merged_groups
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
from itertools import combinations, product
|
|
3
|
+
import math
|
|
4
|
+
import numpy as np
|
|
5
|
+
from typing import Dict, List, Optional, Set, Tuple
|
|
6
|
+
from tqdm import trange
|
|
7
|
+
from .facefunctions import create_face_from_diagonals, get_outer_faces, find_matching_faces,faces_match
|
|
8
|
+
from .block import Block, reduce_blocks
|
|
9
|
+
from .write import write_plot3D
|
|
10
|
+
from .face import Face
|
|
11
|
+
import tqdm
|
|
12
|
+
import networkx as nx
|
|
13
|
+
import matplotlib.pyplot as plt
|
|
14
|
+
from mpl_toolkits.mplot3d import Axes3D
|
|
15
|
+
import numpy.typing as npt
|
|
16
|
+
|
|
17
|
+
def rotate_block(block,rotation_matrix:np.ndarray) -> Block:
|
|
18
|
+
"""Rotates a block by a rotation matrix
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
rotation_matrix (np.ndarray): 3x3 rotation matrix
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Block: returns a new rotated block
|
|
25
|
+
"""
|
|
26
|
+
X = block.X.copy()
|
|
27
|
+
Y = block.Y.copy()
|
|
28
|
+
Z = block.Z.copy()
|
|
29
|
+
points = np.zeros(shape=(3,block.IMAX*block.JMAX*block.KMAX))
|
|
30
|
+
indx = 0
|
|
31
|
+
for i in range(block.IMAX):
|
|
32
|
+
for j in range(block.JMAX):
|
|
33
|
+
for k in range(block.KMAX):
|
|
34
|
+
points[0,indx] = block.X[i,j,k]
|
|
35
|
+
points[1,indx] = block.Y[i,j,k]
|
|
36
|
+
points[2,indx] = block.Z[i,j,k]
|
|
37
|
+
indx+=1
|
|
38
|
+
points_rotated = np.matmul(rotation_matrix,points)
|
|
39
|
+
indx=0
|
|
40
|
+
for i in range(block.IMAX):
|
|
41
|
+
for j in range(block.JMAX):
|
|
42
|
+
for k in range(block.KMAX):
|
|
43
|
+
X[i,j,k] = points_rotated[0,indx]
|
|
44
|
+
Y[i,j,k] = points_rotated[1,indx]
|
|
45
|
+
Z[i,j,k] = points_rotated[2,indx]
|
|
46
|
+
indx+=1
|
|
47
|
+
|
|
48
|
+
return Block(X,Y,Z)
|
|
49
|
+
|
|
50
|
+
def get_outer_bounds(blocks:List[Block]):
|
|
51
|
+
"""Get outer bounds for a set of blocks
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
blocks (List[Block]): Blocks defining your shape
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
(Tuple) containing:
|
|
58
|
+
|
|
59
|
+
**xbounds** (Tuple[float,float]): xmin,xmax
|
|
60
|
+
**ybounds** (Tuple[float,float]): ymin,ymax
|
|
61
|
+
**zbounds** (Tuple[float,float]): zmin,zmax
|
|
62
|
+
"""
|
|
63
|
+
xbounds = [blocks[0].X.min(),blocks[0].X.max()]
|
|
64
|
+
ybounds = [blocks[0].Y.min(),blocks[0].Y.max()]
|
|
65
|
+
zbounds = [blocks[0].Z.min(),blocks[0].Z.max()]
|
|
66
|
+
|
|
67
|
+
for i in range(1,len(blocks)):
|
|
68
|
+
xmin = blocks[i].X.min()
|
|
69
|
+
xmax = blocks[i].X.max()
|
|
70
|
+
|
|
71
|
+
ymin = blocks[i].Y.min()
|
|
72
|
+
ymax = blocks[i].Y.max()
|
|
73
|
+
|
|
74
|
+
zmin = blocks[i].Z.min()
|
|
75
|
+
zmax = blocks[i].Z.max()
|
|
76
|
+
|
|
77
|
+
if xmin<xbounds[0]:
|
|
78
|
+
xbounds[0] = xmin
|
|
79
|
+
elif xmax>xbounds[1]:
|
|
80
|
+
xbounds[1] = xmax
|
|
81
|
+
|
|
82
|
+
if ymin<ybounds[0]:
|
|
83
|
+
ybounds[0] = ymin
|
|
84
|
+
elif ymax>ybounds[1]:
|
|
85
|
+
ybounds[1] = ymax
|
|
86
|
+
|
|
87
|
+
if zmin<zbounds[0]:
|
|
88
|
+
zbounds[0] = zmin
|
|
89
|
+
elif zmax>zbounds[1]:
|
|
90
|
+
zbounds[1] = zmax
|
|
91
|
+
|
|
92
|
+
return tuple(xbounds),tuple(ybounds),tuple(zbounds)
|
|
93
|
+
|
|
94
|
+
def block_connection_matrix(blocks:List[Block],outer_faces:List[Dict[str,int]]=[],tol:float=1E-8):
|
|
95
|
+
"""Creates a matrix representing how block edges are connected to each other
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
blocks (List[Block]): List of blocks that describe the Plot3D mesh
|
|
99
|
+
outer_faces (List[Dict[str,int]], optional): List of outer faces remaining from connectivity. Useful if you are interested in finding faces that are exterior to the block. Also useful if you combine outerfaces with match faces, this will help identify connections by looking at split faces. Defaults to [].
|
|
100
|
+
tol (float, optional): Matching tolerance to look for when comparing face centroids.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
(Tuple): containing
|
|
104
|
+
|
|
105
|
+
*connectivity* (np.ndarray): integer matrix defining how the blocks are connected to each other
|
|
106
|
+
*connectivity_i* (np.ndarray): integer matrix defining connectivity of all blocks where IMAX=IMIN
|
|
107
|
+
*connectivity_j* (np.ndarray): integer matrix defining connectivity of all blocks where JMAX=JMIN
|
|
108
|
+
*connectivity_k* (np.ndarray): integer matrix defining connectivity of all blocks where KMAX=KMIN
|
|
109
|
+
|
|
110
|
+
"""
|
|
111
|
+
# Reduce the size of the blocks by the GCD
|
|
112
|
+
gcd_array = list()
|
|
113
|
+
for block_indx in range(len(blocks)):
|
|
114
|
+
block = blocks[block_indx]
|
|
115
|
+
gcd_array.append(math.gcd(block.IMAX-1, math.gcd(block.JMAX-1, block.KMAX-1)))
|
|
116
|
+
gcd_to_use = min(gcd_array) # You need to use the minimum gcd otherwise 1 block may not exactly match the next block. They all have to be scaled the same way.
|
|
117
|
+
blocks = reduce_blocks(deepcopy(blocks),gcd_to_use)
|
|
118
|
+
|
|
119
|
+
# Face to List
|
|
120
|
+
outer_faces_all = list()
|
|
121
|
+
for o in outer_faces:
|
|
122
|
+
face = create_face_from_diagonals(blocks[o['block_index']], int(o['IMIN']/gcd_to_use), int(o['JMIN']/gcd_to_use),
|
|
123
|
+
int(o['KMIN']/gcd_to_use), int(o['IMAX']/gcd_to_use), int(o['JMAX']/gcd_to_use), int(o['KMAX']/gcd_to_use))
|
|
124
|
+
face.set_block_index(o['block_index'])
|
|
125
|
+
if "id" in o:
|
|
126
|
+
face.id = o['id']
|
|
127
|
+
outer_faces_all.append(face)
|
|
128
|
+
|
|
129
|
+
outer_faces = outer_faces_all
|
|
130
|
+
|
|
131
|
+
n = len(blocks)
|
|
132
|
+
connectivity = np.eye(n,dtype=np.int8)
|
|
133
|
+
combos = list(combinations(range(n),2))
|
|
134
|
+
for indx in (pbar:=trange(len(combos))):
|
|
135
|
+
i,j = combos[indx]
|
|
136
|
+
pbar.set_description(f"Building block to block connectivity matrix: checking {i}")
|
|
137
|
+
b1 = blocks[i]
|
|
138
|
+
|
|
139
|
+
if len(outer_faces)==0: # Get the outerfaces to search
|
|
140
|
+
b1_outer_faces,_ = get_outer_faces(b1)
|
|
141
|
+
else:
|
|
142
|
+
b1_outer_faces = [o for o in outer_faces if o.BlockIndex == i] # type: ignore
|
|
143
|
+
|
|
144
|
+
if i != j and connectivity[i,j]!=-1:
|
|
145
|
+
b2 = blocks[j]
|
|
146
|
+
|
|
147
|
+
if len(outer_faces)==0: # Get the outerfaces to search
|
|
148
|
+
b2_outer_faces,_ = get_outer_faces(b2)
|
|
149
|
+
else:
|
|
150
|
+
b2_outer_faces = [o for o in outer_faces if o.BlockIndex == j] # type: ignore
|
|
151
|
+
|
|
152
|
+
# Check to see if any of the outer faces of the blocks match
|
|
153
|
+
connection_found=False
|
|
154
|
+
for f1 in b1_outer_faces:
|
|
155
|
+
for f2 in b2_outer_faces:
|
|
156
|
+
if (f1.is_connected(f2,tol)): # type: ignore # Check if face centroid is the same
|
|
157
|
+
connectivity[i,j] = 1 # Default block to block connection matrix
|
|
158
|
+
connectivity[j,i] = 1
|
|
159
|
+
connection_found=True
|
|
160
|
+
# c = np.sum(connectivity[i,:]==1)
|
|
161
|
+
# print(f"block {i} connections {c}")
|
|
162
|
+
break
|
|
163
|
+
if connection_found:
|
|
164
|
+
break
|
|
165
|
+
if not connection_found:
|
|
166
|
+
connectivity[i,j] = -1
|
|
167
|
+
connectivity[j,i] = -1
|
|
168
|
+
return connectivity
|
|
169
|
+
|
|
170
|
+
def plot_blocks(blocks):
|
|
171
|
+
gcd_array = list()
|
|
172
|
+
for block_indx in range(len(blocks)):
|
|
173
|
+
block = blocks[block_indx]
|
|
174
|
+
gcd_array.append(math.gcd(block.IMAX-1, math.gcd(block.JMAX-1, block.KMAX-1)))
|
|
175
|
+
gcd_to_use = min(gcd_array) # You need to use the minimum gcd otherwise 1 block may not exactly match the next block. They all have to be scaled the same way.
|
|
176
|
+
blocks = reduce_blocks(deepcopy(blocks),4)
|
|
177
|
+
|
|
178
|
+
fig = plt.figure(figsize=(10, 8))
|
|
179
|
+
ax = fig.add_subplot(111, projection='3d')
|
|
180
|
+
markers = ['o', 's'] # alternate between circle and square
|
|
181
|
+
|
|
182
|
+
for i, b in enumerate(blocks):
|
|
183
|
+
color = f"C{i % 10}"
|
|
184
|
+
X, Y, Z = b.X, b.Y, b.Z
|
|
185
|
+
ax.scatter(X.ravel(), Y.ravel(), Z.ravel(), s=20, alpha=0.4, # type: ignore
|
|
186
|
+
marker=markers[i % len(markers)], label=f'Block {i}', color=color)
|
|
187
|
+
|
|
188
|
+
# Draw lines along i-direction (axis 0)
|
|
189
|
+
for j in range(X.shape[1]):
|
|
190
|
+
for k in range(X.shape[2]):
|
|
191
|
+
ax.plot(X[:, j, k], Y[:, j, k], Z[:, j, k], color=color, linewidth=0.8, alpha=0.6)
|
|
192
|
+
|
|
193
|
+
# Draw lines along j-direction (axis 1)
|
|
194
|
+
for i_ in range(X.shape[0]):
|
|
195
|
+
for k in range(X.shape[2]):
|
|
196
|
+
ax.plot(X[i_, :, k], Y[i_, :, k], Z[i_, :, k], color=color, linewidth=0.8, alpha=0.6)
|
|
197
|
+
|
|
198
|
+
# Draw lines along k-direction (axis 2)
|
|
199
|
+
for i_ in range(X.shape[0]):
|
|
200
|
+
for j_ in range(X.shape[1]):
|
|
201
|
+
ax.plot(X[i_, j_, :], Y[i_, j_, :], Z[i_, j_, :], color=color, linewidth=0.8, alpha=0.6)
|
|
202
|
+
|
|
203
|
+
ax.set_xlabel('X')
|
|
204
|
+
ax.set_ylabel('Y')
|
|
205
|
+
ax.set_zlabel('Z') # type: ignore
|
|
206
|
+
ax.set_title('3D Block Grid with Connected Lines')
|
|
207
|
+
ax.legend()
|
|
208
|
+
plt.tight_layout()
|
|
209
|
+
plt.show()
|
|
210
|
+
|
|
211
|
+
def standardize_block_orientation(block:Block):
|
|
212
|
+
"""Standardizes the orientation of a block so that its physical coordinates increase
|
|
213
|
+
consistently along each of the indexing axes:
|
|
214
|
+
|
|
215
|
+
- X increases along the i-axis
|
|
216
|
+
- Y increases along the j-axis
|
|
217
|
+
- Z increases along the k-axis
|
|
218
|
+
|
|
219
|
+
This ensures consistent face orientation and alignment across multiple blocks,
|
|
220
|
+
especially useful when merging, visualizing, or exporting grids. The function
|
|
221
|
+
checks the dominant physical component (X, Y, or Z) along each axis, and flips
|
|
222
|
+
the block along that axis if the component decreases.
|
|
223
|
+
|
|
224
|
+
Parameters:
|
|
225
|
+
block (Block): The input block to be standardized.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Block: A new block instance with all three axes oriented consistently.
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
X, Y, Z = block.X.copy(), block.Y.copy(), block.Z.copy()
|
|
232
|
+
i_center = X.shape[0] // 2
|
|
233
|
+
j_center = X.shape[1] // 2
|
|
234
|
+
k_center = X.shape[2] // 2
|
|
235
|
+
|
|
236
|
+
# Check i-direction
|
|
237
|
+
dx_i = X[-1, j_center, k_center] - X[0, j_center, k_center]
|
|
238
|
+
if dx_i < 0:
|
|
239
|
+
X = np.flip(X, axis=0)
|
|
240
|
+
Y = np.flip(Y, axis=0)
|
|
241
|
+
Z = np.flip(Z, axis=0)
|
|
242
|
+
|
|
243
|
+
# Check j-direction
|
|
244
|
+
dy_j = Y[i_center, -1, k_center] - Y[i_center, 0, k_center]
|
|
245
|
+
if dy_j < 0:
|
|
246
|
+
X = np.flip(X, axis=1)
|
|
247
|
+
Y = np.flip(Y, axis=1)
|
|
248
|
+
Z = np.flip(Z, axis=1)
|
|
249
|
+
|
|
250
|
+
# Check k-direction
|
|
251
|
+
dz_k = Z[i_center, j_center, -1] - Z[i_center, j_center, 0]
|
|
252
|
+
if dz_k < 0:
|
|
253
|
+
X = np.flip(X, axis=2)
|
|
254
|
+
Y = np.flip(Y, axis=2)
|
|
255
|
+
Z = np.flip(Z, axis=2)
|
|
256
|
+
|
|
257
|
+
return Block(X, Y, Z)
|
|
258
|
+
|
|
259
|
+
def checkCollinearity(v1:npt.NDArray, v2:npt.NDArray):
|
|
260
|
+
# Calculate their cross product
|
|
261
|
+
cross_P = np.cross(v1,v2)
|
|
262
|
+
|
|
263
|
+
# Check if their cross product
|
|
264
|
+
# is a NULL Vector or not
|
|
265
|
+
if (cross_P[0] == 0 and
|
|
266
|
+
cross_P[1] == 0 and
|
|
267
|
+
cross_P[2] == 0):
|
|
268
|
+
return True
|
|
269
|
+
else:
|
|
270
|
+
return False
|
|
271
|
+
|
|
272
|
+
def calculate_outward_normals(block:Block):
|
|
273
|
+
# Calculate Normals
|
|
274
|
+
X = block.X
|
|
275
|
+
Y = block.Y
|
|
276
|
+
Z = block.Z
|
|
277
|
+
imax = block.IMAX
|
|
278
|
+
jmax = block.JMAX
|
|
279
|
+
kmax = block.KMAX
|
|
280
|
+
# IMAX - Normal should be out of the page
|
|
281
|
+
# Normals I direction: IMIN https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal
|
|
282
|
+
x = [X[0,0,0],X[0,jmax,0],X[0,0,kmax]]
|
|
283
|
+
y = [Y[0,0,0],Y[0,jmax,0],Y[0,0,kmax]]
|
|
284
|
+
z = [Z[0,0,0],Z[0,jmax,0],Z[0,0,kmax]]
|
|
285
|
+
u = np.array([x[1]-x[0],y[1]-y[0],z[1]-z[0]])
|
|
286
|
+
v = np.array([x[2]-x[0],y[2]-y[0],z[2]-z[0]])
|
|
287
|
+
n_imin = np.cross(v1,v2) # type: ignore
|
|
288
|
+
|
|
289
|
+
# Normals I direction: IMAX
|
|
290
|
+
x = [X[imax,0,0],X[imax,jmax,0],X[imax,0,kmax]]
|
|
291
|
+
y = [Y[imax,0,0],Y[imax,jmax,0],Y[imax,0,kmax]]
|
|
292
|
+
z = [Z[imax,0,0],Z[imax,jmax,0],Z[imax,0,kmax]]
|
|
293
|
+
v1 = np.array([x[1]-x[0],y[1]-y[0],z[1]-z[0]])
|
|
294
|
+
v2 = np.array([x[2]-x[0],y[2]-y[0],z[2]-z[0]])
|
|
295
|
+
n_imax = np.cross(v1,v2)
|
|
296
|
+
|
|
297
|
+
# Normals J direction: JMIN
|
|
298
|
+
x = [X[0,0,0],X[imax,0,0],X[0,0,kmax]]
|
|
299
|
+
y = [Y[0,0,0],Y[imax,0,0],Y[0,0,kmax]]
|
|
300
|
+
z = [Z[0,0,0],Z[imax,0,0],Z[0,0,kmax]]
|
|
301
|
+
v1 = np.array([x[1]-x[0],y[1]-y[0],z[1]-z[0]])
|
|
302
|
+
v2 = np.array([x[2]-x[0],y[2]-y[0],z[2]-z[0]])
|
|
303
|
+
n_jmin = np.cross(v1,v2)
|
|
304
|
+
|
|
305
|
+
# Normals J direction: JMAX
|
|
306
|
+
x = [X[0,jmax,0],X[imax,jmax,0],X[0,jmax,kmax]]
|
|
307
|
+
y = [Y[0,jmax,0],Y[imax,jmax,0],Y[0,jmax,kmax]]
|
|
308
|
+
z = [Z[0,jmax,0],Z[imax,jmax,0],Z[0,jmax,kmax]]
|
|
309
|
+
v1 = np.array([x[1]-x[0],y[1]-y[0],z[1]-z[0]])
|
|
310
|
+
v2 = np.array([x[2]-x[0],y[2]-y[0],z[2]-z[0]])
|
|
311
|
+
n_jmax = np.cross(v1,v2)
|
|
312
|
+
|
|
313
|
+
# Normals K direction: KMIN
|
|
314
|
+
x = [X[imax,0,0],X[0,jmax,0],X[0,0,0]]
|
|
315
|
+
y = [Y[imax,0,0],Y[0,jmax,0],Y[0,0,0]]
|
|
316
|
+
z = [Z[imax,0,0],Z[0,jmax,0],Z[0,0,0]]
|
|
317
|
+
v1 = np.array([x[1]-x[0],y[1]-y[0],z[1]-z[0]])
|
|
318
|
+
v2 = np.array([x[2]-x[0],y[2]-y[0],z[2]-z[0]])
|
|
319
|
+
n_kmin = np.cross(v1,v2)
|
|
320
|
+
|
|
321
|
+
# Normals K direction: KMAX
|
|
322
|
+
x = [X[imax,0,kmax],X[0,jmax,kmax],X[0,0,kmax]]
|
|
323
|
+
y = [Y[imax,0,kmax],Y[0,jmax,kmax],Y[0,0,kmax]]
|
|
324
|
+
z = [Z[imax,0,kmax],Z[0,jmax,kmax],Z[0,0,kmax]]
|
|
325
|
+
v1 = np.array([x[1]-x[0],y[1]-y[0],z[1]-z[0]])
|
|
326
|
+
v2 = np.array([x[2]-x[0],y[2]-y[0],z[2]-z[0]])
|
|
327
|
+
n_kmax = np.cross(v1,v2)
|
|
328
|
+
|
|
329
|
+
return n_imin,n_jmin,n_kmin,n_imax,n_jmax,n_kmax
|
|
330
|
+
|
|
331
|
+
def split_blocks(blocks:List[Block],gcd:int=4):
|
|
332
|
+
"""Split blocks but also keep greatest common divisor
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
blocks (List[]): _description_
|
|
336
|
+
gcd (int, optional): _description_. Defaults to 4.
|
|
337
|
+
"""
|
|
338
|
+
pass
|
|
339
|
+
|
|
340
|
+
def common_neighbor(G: nx.Graph, a: int, b: int, exclude: Set[int]) -> Optional[int]:
|
|
341
|
+
"""
|
|
342
|
+
Return a node that is connected to both `a` and `b` and not in `exclude`.
|
|
343
|
+
"""
|
|
344
|
+
for n in G.neighbors(a):
|
|
345
|
+
if n in exclude:
|
|
346
|
+
continue
|
|
347
|
+
if G.has_edge(n, b):
|
|
348
|
+
return n
|
|
349
|
+
return None
|
|
350
|
+
|
|
351
|
+
def build_connectivity_graph(connectivities: List[List[Dict]]) -> nx.Graph:
|
|
352
|
+
"""
|
|
353
|
+
Build an undirected graph from a list of face-to-face block connectivities.
|
|
354
|
+
Each edge connects two block indices.
|
|
355
|
+
"""
|
|
356
|
+
G = nx.Graph()
|
|
357
|
+
for pair in connectivities:
|
|
358
|
+
block1 = pair['block1']['block_index'] # type: ignore
|
|
359
|
+
block2 = pair['block2']['block_index'] # type: ignore
|
|
360
|
+
G.add_edge(block1, block2)
|
|
361
|
+
return G
|
|
@@ -58,8 +58,8 @@ def find_matching_blocks(block1:Block,block2:Block,block1_outer:List[Face], bloc
|
|
|
58
58
|
if match:
|
|
59
59
|
break
|
|
60
60
|
if match:
|
|
61
|
-
block1_outer.pop(p)
|
|
62
|
-
block2_outer.pop(q)
|
|
61
|
+
block1_outer.pop(p) # type: ignore
|
|
62
|
+
block2_outer.pop(q) # type: ignore
|
|
63
63
|
block1_outer.extend(block1_split_faces)
|
|
64
64
|
block2_outer.extend(block2_split_faces)
|
|
65
65
|
block1_split_faces.clear()
|
|
@@ -395,10 +395,10 @@ def connectivity(blocks:List[Block]):
|
|
|
395
395
|
outer_faces = list()
|
|
396
396
|
face_matches = list()
|
|
397
397
|
matches_to_remove = list()
|
|
398
|
-
# df_matches, blocki_outerfaces, blockj_outerfaces = find_matching_blocks(blocks[4],blocks[7],1E-12) # This function finds partial matches between blocks
|
|
399
398
|
temp = [get_outer_faces(b) for b in blocks]
|
|
400
399
|
block_outer_faces = [t[0] for t in temp]
|
|
401
400
|
combos = combinations_of_nearest_blocks(blocks,6) # Find the 6 nearest Blocks and search through all that.
|
|
401
|
+
# df_matches, blocki_outerfaces, blockj_outerfaces = find_matching_blocks(blocks[0],blocks[1],[block_outer_faces[0][0],block_outer_faces[0][1]],[block_outer_faces[1][0],block_outer_faces[1][1]],1E-12) # This function finds partial matches between blocks
|
|
402
402
|
|
|
403
403
|
t = trange(len(combos))
|
|
404
404
|
for indx in t: # block i
|
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
from typing import Dict, List, Tuple
|
|
2
2
|
import numpy as np
|
|
3
|
+
import numpy.typing as npt
|
|
3
4
|
import math
|
|
4
5
|
|
|
5
6
|
class Face:
|
|
7
|
+
x:npt.NDArray
|
|
8
|
+
y:npt.NDArray
|
|
9
|
+
z:npt.NDArray
|
|
10
|
+
I:npt.NDArray
|
|
11
|
+
J:npt.NDArray
|
|
12
|
+
K:npt.NDArray
|
|
13
|
+
cx:float = 0
|
|
14
|
+
cy:float = 0
|
|
15
|
+
cz:float = 0
|
|
16
|
+
nvertex:int = 0
|
|
17
|
+
blockIndex:int = 0 # not really needed except in periodicity
|
|
18
|
+
id:int = 0
|
|
6
19
|
"""Defines a Face of a block for example IMIN,JMIN,JMIN to IMAX,JMIN,JMIN
|
|
7
20
|
"""
|
|
8
21
|
def __init__(self,nvertex:int=4):
|
|
@@ -18,12 +31,6 @@ class Face:
|
|
|
18
31
|
self.I = np.zeros(4,dtype=np.int64)
|
|
19
32
|
self.J = np.zeros(4,dtype=np.int64)
|
|
20
33
|
self.K = np.zeros(4,dtype=np.int64)
|
|
21
|
-
self.cx = 0 # centroid
|
|
22
|
-
self.cy = 0
|
|
23
|
-
self.cz = 0
|
|
24
|
-
self.nvertex=0
|
|
25
|
-
self.blockIndex = 0 # not really needed except in periodicity
|
|
26
|
-
self.id = 0
|
|
27
34
|
|
|
28
35
|
def to_dict(self):
|
|
29
36
|
"""Returns a dictionary representaon of a face
|
|
@@ -116,11 +123,6 @@ class Face:
|
|
|
116
123
|
indx = list(set([indx_i]))[0] # Get the common one through a union
|
|
117
124
|
return self.x[indx], self.y[indx], self.z[indx]
|
|
118
125
|
|
|
119
|
-
def scale(self,gcd:int):
|
|
120
|
-
self.I = int(self.I*gcd)
|
|
121
|
-
self.J = int(self.J*gcd)
|
|
122
|
-
self.K = int(self.K*gcd)
|
|
123
|
-
|
|
124
126
|
def add_vertex(self, x:float,y:float,z:float, i:int, j:int, k:int):
|
|
125
127
|
"""Add vertex to define a face
|
|
126
128
|
|
|
@@ -1,10 +1,59 @@
|
|
|
1
|
-
from typing import Dict, List
|
|
1
|
+
from typing import Dict, List, Optional, Tuple
|
|
2
2
|
from .listfunctions import unique_pairs
|
|
3
3
|
from .block import Block, reduce_blocks
|
|
4
4
|
from .face import Face
|
|
5
5
|
from copy import deepcopy
|
|
6
|
-
import
|
|
6
|
+
import numpy.typing as npt
|
|
7
7
|
import numpy as np
|
|
8
|
+
import math
|
|
9
|
+
|
|
10
|
+
def faces_match(face1: Tuple[npt.NDArray, npt.NDArray, npt.NDArray],face2: Tuple[npt.NDArray, npt.NDArray, npt.NDArray],tol: float = 1e-12) -> Tuple[bool, Optional[Tuple[bool, bool]]]:
|
|
11
|
+
"""
|
|
12
|
+
Compare two block faces and return whether they match and the flip required on face2 to match face1.
|
|
13
|
+
Returns (True, (flip_ud, flip_lr)) if matching, otherwise (False, None).
|
|
14
|
+
"""
|
|
15
|
+
def get_corners(X, Y, Z):
|
|
16
|
+
return np.array([
|
|
17
|
+
[X[0, 0], Y[0, 0], Z[0, 0]],
|
|
18
|
+
[X[0, -1], Y[0, -1], Z[0, -1]],
|
|
19
|
+
[X[-1, 0], Y[-1, 0], Z[-1, 0]],
|
|
20
|
+
[X[-1, -1], Y[-1, -1], Z[-1, -1]],
|
|
21
|
+
])
|
|
22
|
+
|
|
23
|
+
X1, Y1, Z1 = face1
|
|
24
|
+
X2, Y2, Z2 = face2
|
|
25
|
+
|
|
26
|
+
if X1.shape != X2.shape:
|
|
27
|
+
return False, None
|
|
28
|
+
|
|
29
|
+
corners1 = get_corners(X1, Y1, Z1)
|
|
30
|
+
for flip_ud in [False, True]:
|
|
31
|
+
for flip_lr in [False, True]:
|
|
32
|
+
X2f, Y2f, Z2f = X2.copy(), Y2.copy(), Z2.copy()
|
|
33
|
+
if flip_ud:
|
|
34
|
+
X2f, Y2f, Z2f = np.flip(X2f, axis=0), np.flip(Y2f, axis=0), np.flip(Z2f, axis=0)
|
|
35
|
+
if flip_lr:
|
|
36
|
+
X2f, Y2f, Z2f = np.flip(X2f, axis=1), np.flip(Y2f, axis=1), np.flip(Z2f, axis=1)
|
|
37
|
+
|
|
38
|
+
corners2 = get_corners(X2f, Y2f, Z2f)
|
|
39
|
+
diffs = np.linalg.norm(corners1 - corners2, axis=1)
|
|
40
|
+
if np.all(diffs <= tol):
|
|
41
|
+
return True, (flip_ud, flip_lr)
|
|
42
|
+
|
|
43
|
+
return False, None
|
|
44
|
+
def find_matching_faces(block1, block2, tol=1e-8):
|
|
45
|
+
"""
|
|
46
|
+
Returns a tuple (face1_name, face2_name, flip_flags) if a matching face is found.
|
|
47
|
+
Otherwise returns (None, None, None).
|
|
48
|
+
"""
|
|
49
|
+
faces1 = block1.get_faces()
|
|
50
|
+
faces2 = block2.get_faces()
|
|
51
|
+
for face1_name, face1_data in faces1.items():
|
|
52
|
+
for face2_name, face2_data in faces2.items():
|
|
53
|
+
match, flip_flags = faces_match(face1_data, face2_data, tol=tol)
|
|
54
|
+
if match:
|
|
55
|
+
return face1_name, face2_name, flip_flags
|
|
56
|
+
return None, None, None
|
|
8
57
|
|
|
9
58
|
|
|
10
59
|
def get_outer_faces(block1:Block):
|
|
@@ -169,7 +218,6 @@ def find_connected_faces(face_to_search:Face,outer_faces:List[Face],connectivity
|
|
|
169
218
|
all_matching_faces = list(set(all_matching_faces))
|
|
170
219
|
return all_matching_faces
|
|
171
220
|
|
|
172
|
-
|
|
173
221
|
def find_closest_block(blocks:List[Block],x:np.ndarray,y:np.ndarray,z:np.ndarray,centroid:np.ndarray,translational_direction:str="x",minvalue:bool=True):
|
|
174
222
|
"""Find the closest block to an extreme in the x,y, or z direction and returns the targetting point.
|
|
175
223
|
Target point is the reference point where we want the closest block and the closest face
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "plot3d"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.7.0"
|
|
4
4
|
description = "Plot3D python utilities for reading and writing and also finding connectivity between blocks"
|
|
5
5
|
authors = ["Paht Juangphanich <paht.juangphanich@nasa.gov>"]
|
|
6
6
|
|
|
7
7
|
[tool.poetry.dependencies]
|
|
8
|
-
python = "^3.10.
|
|
9
|
-
numpy = "
|
|
8
|
+
python = "^3.10.12"
|
|
9
|
+
numpy = "*"
|
|
10
10
|
scipy = "*"
|
|
11
11
|
pandas = "*"
|
|
12
12
|
tqdm = "*"
|
|
13
13
|
networkx = "*"
|
|
14
14
|
|
|
15
|
-
[tool.poetry.
|
|
15
|
+
[tool.poetry.group.deve.dependencies]
|
|
16
16
|
|
|
17
17
|
[build-system]
|
|
18
18
|
requires = ["poetry>=1.1.2"]
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
from copy import deepcopy
|
|
2
|
-
from itertools import combinations
|
|
3
|
-
import math
|
|
4
|
-
import numpy as np
|
|
5
|
-
from typing import Dict, List
|
|
6
|
-
from tqdm import trange
|
|
7
|
-
from .facefunctions import create_face_from_diagonals, get_outer_faces
|
|
8
|
-
from .block import Block
|
|
9
|
-
from .face import Face
|
|
10
|
-
|
|
11
|
-
def rotate_block(block,rotation_matrix:np.ndarray) -> Block:
|
|
12
|
-
"""Rotates a block by a rotation matrix
|
|
13
|
-
|
|
14
|
-
Args:
|
|
15
|
-
rotation_matrix (np.ndarray): 3x3 rotation matrix
|
|
16
|
-
|
|
17
|
-
Returns:
|
|
18
|
-
Block: returns a new rotated block
|
|
19
|
-
"""
|
|
20
|
-
X = block.X.copy()
|
|
21
|
-
Y = block.Y.copy()
|
|
22
|
-
Z = block.Z.copy()
|
|
23
|
-
points = np.zeros(shape=(3,block.IMAX*block.JMAX*block.KMAX))
|
|
24
|
-
indx = 0
|
|
25
|
-
for i in range(block.IMAX):
|
|
26
|
-
for j in range(block.JMAX):
|
|
27
|
-
for k in range(block.KMAX):
|
|
28
|
-
points[0,indx] = block.X[i,j,k]
|
|
29
|
-
points[1,indx] = block.Y[i,j,k]
|
|
30
|
-
points[2,indx] = block.Z[i,j,k]
|
|
31
|
-
indx+=1
|
|
32
|
-
points_rotated = np.matmul(rotation_matrix,points)
|
|
33
|
-
indx=0
|
|
34
|
-
for i in range(block.IMAX):
|
|
35
|
-
for j in range(block.JMAX):
|
|
36
|
-
for k in range(block.KMAX):
|
|
37
|
-
X[i,j,k] = points_rotated[0,indx]
|
|
38
|
-
Y[i,j,k] = points_rotated[1,indx]
|
|
39
|
-
Z[i,j,k] = points_rotated[2,indx]
|
|
40
|
-
indx+=1
|
|
41
|
-
|
|
42
|
-
return Block(X,Y,Z)
|
|
43
|
-
|
|
44
|
-
def reduce_blocks(blocks:List[Block],factor:int):
|
|
45
|
-
"""reduce the blocks by a factor of (factor)
|
|
46
|
-
|
|
47
|
-
Args:
|
|
48
|
-
blocks (List[Block]): list of blocks to reduce in size
|
|
49
|
-
factor (int, optional): Number of indicies to skip . Defaults to 2.
|
|
50
|
-
|
|
51
|
-
Returns:
|
|
52
|
-
[type]: [description]
|
|
53
|
-
"""
|
|
54
|
-
for i in range(len(blocks)):
|
|
55
|
-
blocks[i].X = blocks[i].X[::factor,::factor,::factor]
|
|
56
|
-
blocks[i].Y = blocks[i].Y[::factor,::factor,::factor]
|
|
57
|
-
blocks[i].Z = blocks[i].Z[::factor,::factor,::factor]
|
|
58
|
-
blocks[i].IMAX,blocks[i].JMAX,blocks[i].KMAX = blocks[i].X.shape
|
|
59
|
-
return blocks
|
|
60
|
-
|
|
61
|
-
def get_outer_bounds(blocks:List[Block]):
|
|
62
|
-
"""Get outer bounds for a set of blocks
|
|
63
|
-
|
|
64
|
-
Args:
|
|
65
|
-
blocks (List[Block]): Blocks defining your shape
|
|
66
|
-
|
|
67
|
-
Returns:
|
|
68
|
-
(Tuple) containing:
|
|
69
|
-
|
|
70
|
-
**xbounds** (Tuple[float,float]): xmin,xmax
|
|
71
|
-
**ybounds** (Tuple[float,float]): ymin,ymax
|
|
72
|
-
**zbounds** (Tuple[float,float]): zmin,zmax
|
|
73
|
-
"""
|
|
74
|
-
xbounds = [blocks[0].X.min(),blocks[0].X.max()]
|
|
75
|
-
ybounds = [blocks[0].Y.min(),blocks[0].Y.max()]
|
|
76
|
-
zbounds = [blocks[0].Z.min(),blocks[0].Z.max()]
|
|
77
|
-
|
|
78
|
-
for i in range(1,len(blocks)):
|
|
79
|
-
xmin = blocks[i].X.min()
|
|
80
|
-
xmax = blocks[i].X.max()
|
|
81
|
-
|
|
82
|
-
ymin = blocks[i].Y.min()
|
|
83
|
-
ymax = blocks[i].Y.max()
|
|
84
|
-
|
|
85
|
-
zmin = blocks[i].Z.min()
|
|
86
|
-
zmax = blocks[i].Z.max()
|
|
87
|
-
|
|
88
|
-
if xmin<xbounds[0]:
|
|
89
|
-
xbounds[0] = xmin
|
|
90
|
-
elif xmax>xbounds[1]:
|
|
91
|
-
xbounds[1] = xmax
|
|
92
|
-
|
|
93
|
-
if ymin<ybounds[0]:
|
|
94
|
-
ybounds[0] = ymin
|
|
95
|
-
elif ymax>ybounds[1]:
|
|
96
|
-
ybounds[1] = ymax
|
|
97
|
-
|
|
98
|
-
if zmin<zbounds[0]:
|
|
99
|
-
zbounds[0] = zmin
|
|
100
|
-
elif zmax>zbounds[1]:
|
|
101
|
-
zbounds[1] = zmax
|
|
102
|
-
|
|
103
|
-
return tuple(xbounds),tuple(ybounds),tuple(zbounds)
|
|
104
|
-
|
|
105
|
-
def block_connection_matrix(blocks:List[Block],outer_faces:List[Dict[str,int]]=[],tol:float=1E-8):
|
|
106
|
-
"""Creates a matrix representing how block edges are connected to each other
|
|
107
|
-
|
|
108
|
-
Args:
|
|
109
|
-
blocks (List[Block]): List of blocks that describe the Plot3D mesh
|
|
110
|
-
outer_faces (List[Dict[str,int]], optional): List of outer faces remaining from connectivity. Useful if you are interested in finding faces that are exterior to the block. Also useful if you combine outerfaces with match faces, this will help identify connections by looking at split faces. Defaults to [].
|
|
111
|
-
tol (float, optional): Matching tolerance to look for when comparing face centroids.
|
|
112
|
-
|
|
113
|
-
Returns:
|
|
114
|
-
(Tuple): containing
|
|
115
|
-
|
|
116
|
-
*connectivity* (np.ndarray): integer matrix defining how the blocks are connected to each other
|
|
117
|
-
*connectivity_i* (np.ndarray): integer matrix defining connectivity of all blocks where IMAX=IMIN
|
|
118
|
-
*connectivity_j* (np.ndarray): integer matrix defining connectivity of all blocks where JMAX=JMIN
|
|
119
|
-
*connectivity_k* (np.ndarray): integer matrix defining connectivity of all blocks where KMAX=KMIN
|
|
120
|
-
|
|
121
|
-
"""
|
|
122
|
-
# Reduce the size of the blocks by the GCD
|
|
123
|
-
gcd_array = list()
|
|
124
|
-
for block_indx in range(len(blocks)):
|
|
125
|
-
block = blocks[block_indx]
|
|
126
|
-
gcd_array.append(math.gcd(block.IMAX-1, math.gcd(block.JMAX-1, block.KMAX-1)))
|
|
127
|
-
gcd_to_use = min(gcd_array) # You need to use the minimum gcd otherwise 1 block may not exactly match the next block. They all have to be scaled the same way.
|
|
128
|
-
blocks = reduce_blocks(deepcopy(blocks),gcd_to_use)
|
|
129
|
-
|
|
130
|
-
# Face to List
|
|
131
|
-
outer_faces_all = list()
|
|
132
|
-
for o in outer_faces:
|
|
133
|
-
face = create_face_from_diagonals(blocks[o['block_index']], int(o['IMIN']/gcd_to_use), int(o['JMIN']/gcd_to_use),
|
|
134
|
-
int(o['KMIN']/gcd_to_use), int(o['IMAX']/gcd_to_use), int(o['JMAX']/gcd_to_use), int(o['KMAX']/gcd_to_use))
|
|
135
|
-
face.set_block_index(o['block_index'])
|
|
136
|
-
if "id" in o:
|
|
137
|
-
face.id = o['id']
|
|
138
|
-
outer_faces_all.append(face)
|
|
139
|
-
|
|
140
|
-
outer_faces = outer_faces_all
|
|
141
|
-
|
|
142
|
-
n = len(blocks)
|
|
143
|
-
connectivity = np.eye(n,dtype=np.int8)
|
|
144
|
-
combos = list(combinations(range(n),2))
|
|
145
|
-
for indx in (pbar:=trange(len(combos))):
|
|
146
|
-
i,j = combos[indx]
|
|
147
|
-
pbar.set_description(f"Building block to block connectivity matrix: checking {i}")
|
|
148
|
-
b1 = blocks[i]
|
|
149
|
-
|
|
150
|
-
if len(outer_faces)==0: # Get the outerfaces to search
|
|
151
|
-
b1_outer_faces,_ = get_outer_faces(b1)
|
|
152
|
-
else:
|
|
153
|
-
b1_outer_faces = [o for o in outer_faces if o.BlockIndex == i]
|
|
154
|
-
|
|
155
|
-
if i != j and connectivity[i,j]!=-1:
|
|
156
|
-
b2 = blocks[j]
|
|
157
|
-
|
|
158
|
-
if len(outer_faces)==0: # Get the outerfaces to search
|
|
159
|
-
b2_outer_faces,_ = get_outer_faces(b2)
|
|
160
|
-
else:
|
|
161
|
-
b2_outer_faces = [o for o in outer_faces if o.BlockIndex == j]
|
|
162
|
-
|
|
163
|
-
# Check to see if any of the outer faces of the blocks match
|
|
164
|
-
connection_found=False
|
|
165
|
-
for f1 in b1_outer_faces:
|
|
166
|
-
for f2 in b2_outer_faces:
|
|
167
|
-
if (f1.is_connected(f2,tol)): # Check if face centroid is the same
|
|
168
|
-
connectivity[i,j] = 1 # Default block to block connection matrix
|
|
169
|
-
connectivity[j,i] = 1
|
|
170
|
-
connection_found=True
|
|
171
|
-
# c = np.sum(connectivity[i,:]==1)
|
|
172
|
-
# print(f"block {i} connections {c}")
|
|
173
|
-
break
|
|
174
|
-
if connection_found:
|
|
175
|
-
break
|
|
176
|
-
if not connection_found:
|
|
177
|
-
connectivity[i,j] = -1
|
|
178
|
-
connectivity[j,i] = -1
|
|
179
|
-
return connectivity
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|