plot3d 1.8.1__tar.gz → 1.8.2__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.
Files changed (28) hide show
  1. {plot3d-1.8.1 → plot3d-1.8.2}/PKG-INFO +1 -1
  2. {plot3d-1.8.1 → plot3d-1.8.2}/plot3d/__init__.py +11 -4
  3. {plot3d-1.8.1 → plot3d-1.8.2}/plot3d/blockfunctions.py +134 -55
  4. plot3d-1.8.2/plot3d/connectivity.py +1255 -0
  5. {plot3d-1.8.1 → plot3d-1.8.2}/plot3d/differencing.py +9 -9
  6. {plot3d-1.8.1 → plot3d-1.8.2}/plot3d/face.py +3 -7
  7. {plot3d-1.8.1 → plot3d-1.8.2}/plot3d/facefunctions.py +64 -82
  8. {plot3d-1.8.1 → plot3d-1.8.2}/plot3d/glennht/export_functions.py +26 -22
  9. {plot3d-1.8.1 → plot3d-1.8.2}/plot3d/glennht/import_functions.py +13 -13
  10. {plot3d-1.8.1 → plot3d-1.8.2}/plot3d/graph.py +5 -5
  11. {plot3d-1.8.1 → plot3d-1.8.2}/plot3d/gridpro/import_functions.py +3 -3
  12. plot3d-1.8.2/plot3d/normals.py +391 -0
  13. {plot3d-1.8.1 → plot3d-1.8.2}/plot3d/periodicity.py +287 -411
  14. {plot3d-1.8.1 → plot3d-1.8.2}/plot3d/pointwise/import_functions.py +5 -9
  15. {plot3d-1.8.1 → plot3d-1.8.2}/plot3d/read.py +18 -24
  16. {plot3d-1.8.1 → plot3d-1.8.2}/plot3d/split_block.py +1 -1
  17. plot3d-1.8.2/plot3d/verify.py +411 -0
  18. {plot3d-1.8.1 → plot3d-1.8.2}/plot3d/write.py +14 -44
  19. {plot3d-1.8.1 → plot3d-1.8.2}/pyproject.toml +1 -1
  20. plot3d-1.8.1/plot3d/connectivity.py +0 -723
  21. {plot3d-1.8.1 → plot3d-1.8.2}/plot3d/block.py +0 -0
  22. {plot3d-1.8.1 → plot3d-1.8.2}/plot3d/block_merging_mixed_facepairs.py +0 -0
  23. {plot3d-1.8.1 → plot3d-1.8.2}/plot3d/glennht/__init__.py +0 -0
  24. {plot3d-1.8.1 → plot3d-1.8.2}/plot3d/glennht/class_definitions.py +0 -0
  25. {plot3d-1.8.1 → plot3d-1.8.2}/plot3d/gridpro/__init__.py +0 -0
  26. {plot3d-1.8.1 → plot3d-1.8.2}/plot3d/listfunctions.py +0 -0
  27. {plot3d-1.8.1 → plot3d-1.8.2}/plot3d/point_match.py +0 -0
  28. {plot3d-1.8.1 → plot3d-1.8.2}/plot3d/pointwise/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plot3d
3
- Version: 1.8.1
3
+ Version: 1.8.2
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
@@ -3,17 +3,24 @@ from importlib import import_module
3
3
  import os, warnings
4
4
 
5
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
6
+ from .blockfunctions import rotate_block, get_outer_bounds, block_connection_matrix,split_blocks, plot_blocks, reduce_blocks, find_matching_faces, compute_min_gcd, scale_face_bounds, constant_axis
7
7
  from .block_merging_mixed_facepairs import combine_nxnxn_cubes_mixed_pairs
8
- from .connectivity import find_matching_blocks, get_face_intersection, connectivity_fast, face_matches_to_dict, verify_connectivity
8
+ from .connectivity import find_matching_blocks, get_face_intersection, connectivity_fast, face_matches_to_dict, PERMUTATION_MATRICES
9
9
  from .face import Face
10
10
  from .facefunctions import create_face_from_diagonals, get_outer_faces, find_bounding_faces,split_face,find_face_nearest_point,match_faces_dict_to_list,outer_face_dict_to_list,find_closest_block
11
11
  from .read import read_plot3D, read_ap_nasa
12
12
  from .write import write_plot3D
13
13
  from .differencing import find_edges, find_face_edges
14
- from .periodicity import periodicity, periodicity_fast, create_rotation_matrix, rotated_periodicity, translational_periodicity, verify_periodicity
14
+ from .periodicity import periodicity, periodicity_fast, create_rotation_matrix, rotated_periodicity, translational_periodicity
15
+ from .verify import (verify_connectivity, verify_periodicity,
16
+ extract_canonical_grid, apply_permutation, verify_match,
17
+ verify_partial_match, try_all_permutations, get_bounds,
18
+ determine_plane)
15
19
  from .point_match import point_match
16
20
  from .split_block import split_blocks, Direction
17
21
  from .listfunctions import unique_pairs
18
22
 
19
- from .graph import write_ddcmp, build_weighted_graph_from_face_matches,csr_from_adj_and_weights,partition_from_face_matches
23
+ from .graph import write_ddcmp, build_weighted_graph_from_face_matches,csr_from_adj_and_weights,partition_from_face_matches
24
+ from .normals import (index_space_normal, compute_permutation_matrix,
25
+ validate_connectivity, compute_all_normals,
26
+ export_normals_json, import_normals_json, plot_face_normals)
@@ -14,38 +14,79 @@ import matplotlib.pyplot as plt
14
14
  from mpl_toolkits.mplot3d import Axes3D
15
15
  import numpy.typing as npt
16
16
 
17
+ def compute_min_gcd(blocks: List[Block]) -> int:
18
+ """Compute the minimum GCD across all block dimensions.
19
+
20
+ Args:
21
+ blocks: List of blocks.
22
+
23
+ Returns:
24
+ The minimum GCD value to use for uniform reduction.
25
+ """
26
+ return min(
27
+ math.gcd(b.IMAX - 1, math.gcd(b.JMAX - 1, b.KMAX - 1))
28
+ for b in blocks
29
+ )
30
+
31
+
32
+ def scale_face_bounds(face_dicts: list, factor: int, divide: bool = False):
33
+ """Scale lb/ub bounds in face-match or outer-face dicts by a factor.
34
+
35
+ For face-match dicts (with 'block1'/'block2' sub-dicts) both sides
36
+ are scaled. For outer-face dicts (flat dict with 'lb'/'ub') the
37
+ bounds are scaled directly.
38
+
39
+ Modifies *face_dicts* in place.
40
+
41
+ Args:
42
+ face_dicts: List of face-match or outer-face dicts.
43
+ factor: Scale factor.
44
+ divide: If True, divide by *factor*; otherwise multiply.
45
+ """
46
+ op = (lambda x: x // factor) if divide else (lambda x: x * factor)
47
+ for d in face_dicts:
48
+ if 'block1' in d:
49
+ for side in ('block1', 'block2'):
50
+ d[side]['lb'] = [op(x) for x in d[side]['lb']]
51
+ d[side]['ub'] = [op(x) for x in d[side]['ub']]
52
+ else:
53
+ d['lb'] = [op(x) for x in d['lb']]
54
+ d['ub'] = [op(x) for x in d['ub']]
55
+
56
+
57
+ def constant_axis(lb: list, ub: list) -> int:
58
+ """Return the index (0, 1, or 2) of the constant axis on a face, or -1.
59
+
60
+ Args:
61
+ lb: Lower bound [i, j, k].
62
+ ub: Upper bound [i, j, k].
63
+
64
+ Returns:
65
+ Axis index where lb[d] == ub[d], or -1 if none found.
66
+ """
67
+ for d in range(3):
68
+ if lb[d] == ub[d]:
69
+ return d
70
+ return -1
71
+
72
+
17
73
  def rotate_block(block,rotation_matrix:np.ndarray) -> Block:
18
- """Rotates a block by a rotation matrix
74
+ """Rotates a block by a rotation matrix
19
75
 
20
76
  Args:
21
- rotation_matrix (np.ndarray): 3x3 rotation matrix
77
+ block (Block): Block to rotate
78
+ rotation_matrix (np.ndarray): 3x3 rotation matrix
22
79
 
23
80
  Returns:
24
- Block: returns a new rotated block
81
+ Block: returns a new rotated block
25
82
  """
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)
83
+ shape = block.X.shape
84
+ pts = np.stack([block.X.ravel(), block.Y.ravel(), block.Z.ravel()], axis=0) # (3, N)
85
+ rotated = rotation_matrix @ pts # (3, N)
86
+ X = rotated[0].reshape(shape)
87
+ Y = rotated[1].reshape(shape)
88
+ Z = rotated[2].reshape(shape)
89
+ return Block(X, Y, Z)
49
90
 
50
91
  def get_outer_bounds(blocks:List[Block]):
51
92
  """Get outer bounds for a set of blocks
@@ -105,21 +146,31 @@ def block_connection_matrix(
105
146
  use_area_fallback: bool = True,
106
147
  area_min_overlap_frac: float = 0.01
107
148
  ) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
108
- """
109
- Creates matrices representing how blocks are connected.
149
+ """Create matrices representing how blocks are connected.
150
+
151
+ GCD-reduces blocks for speed, then checks every block pair for shared
152
+ nodes (primary) or overlapping face area (fallback).
153
+
154
+ Args:
155
+ blocks: List of all blocks.
156
+ outer_faces: Pre-computed outer faces as dicts with 'block_index', 'lb', 'ub'.
157
+ If empty, outer faces are computed automatically.
158
+ tol: General tolerance (unused directly; kept for API compat).
159
+ node_tol_xyz: Tolerance for node-sharing check.
160
+ min_shared_frac: Minimum fraction of shared nodes to count as connected.
161
+ min_shared_abs: Minimum absolute number of shared nodes.
162
+ stride_u: Sampling stride along u-axis for node check.
163
+ stride_v: Sampling stride along v-axis for node check.
164
+ use_area_fallback: If True, fall back to polygon-overlap check.
165
+ area_min_overlap_frac: Minimum overlap fraction for area fallback.
110
166
 
111
167
  Returns:
112
- connectivity : (n,n) overall connectivity (1 = connected, -1 = not)
113
- connectivity_i : (n,n) connections where both faces are I-constant
114
- connectivity_j : (n,n) connections where both faces are J-constant
115
- connectivity_k : (n,n) connections where both faces are K-constant
168
+ (connectivity, connectivity_i, connectivity_j, connectivity_k):
169
+ Four (n, n) matrices where 1 = connected, -1 = not connected.
170
+ The last three are axis-specific (both faces I/J/K-constant).
116
171
  """
117
172
  # Reduce the size of the blocks by the minimum GCD so index grids line up
118
- gcd_array = []
119
- for block_indx in range(len(blocks)):
120
- block = blocks[block_indx]
121
- gcd_array.append(math.gcd(block.IMAX - 1, math.gcd(block.JMAX - 1, block.KMAX - 1)))
122
- gcd_to_use = min(gcd_array)
173
+ gcd_to_use = compute_min_gcd(blocks)
123
174
  blocks = reduce_blocks(deepcopy(blocks), gcd_to_use)
124
175
 
125
176
  # Convert dict outer faces (if provided) to Face objects at the reduced resolution
@@ -127,8 +178,8 @@ def block_connection_matrix(
127
178
  for o in outer_faces:
128
179
  face = create_face_from_diagonals(
129
180
  blocks[o["block_index"]],
130
- int(o["IMIN"] / gcd_to_use), int(o["JMIN"] / gcd_to_use), int(o["KMIN"] / gcd_to_use),
131
- int(o["IMAX"] / gcd_to_use), int(o["JMAX"] / gcd_to_use), int(o["KMAX"] / gcd_to_use)
181
+ [int(o["lb"][0] / gcd_to_use), int(o["lb"][1] / gcd_to_use), int(o["lb"][2] / gcd_to_use)],
182
+ [int(o["ub"][0] / gcd_to_use), int(o["ub"][1] / gcd_to_use), int(o["ub"][2] / gcd_to_use)]
132
183
  )
133
184
  face.set_block_index(o["block_index"])
134
185
  if "id" in o:
@@ -211,7 +262,15 @@ def block_connection_matrix(
211
262
  return connectivity, connectivity_i, connectivity_j, connectivity_k
212
263
 
213
264
  def plot_blocks(blocks):
214
- gcd_array = list()
265
+ """Plot all blocks as a 3D wireframe grid using matplotlib.
266
+
267
+ GCD-reduces blocks for faster rendering, then draws grid lines
268
+ along each axis for every block with alternating markers.
269
+
270
+ Args:
271
+ blocks: List of Block objects to plot.
272
+ """
273
+ gcd_array = list()
215
274
  for block_indx in range(len(blocks)):
216
275
  block = blocks[block_indx]
217
276
  gcd_array.append(math.gcd(block.IMAX-1, math.gcd(block.JMAX-1, block.KMAX-1)))
@@ -300,6 +359,15 @@ def standardize_block_orientation(block:Block):
300
359
  return Block(X, Y, Z)
301
360
 
302
361
  def checkCollinearity(v1:npt.NDArray, v2:npt.NDArray):
362
+ """Check if two 3D vectors are collinear (parallel or anti-parallel).
363
+
364
+ Args:
365
+ v1: First 3D vector.
366
+ v2: Second 3D vector.
367
+
368
+ Returns:
369
+ True if the cross product is the zero vector, False otherwise.
370
+ """
303
371
  # Calculate their cross product
304
372
  cross_P = np.cross(v1,v2)
305
373
 
@@ -313,24 +381,35 @@ def checkCollinearity(v1:npt.NDArray, v2:npt.NDArray):
313
381
  return False
314
382
 
315
383
  def calculate_outward_normals(block:Block):
384
+ """Compute outward-facing normal vectors for all six faces of a block.
385
+
386
+ Uses corner points of each face to compute cross-product normals.
387
+
388
+ Args:
389
+ block: Block to compute normals for.
390
+
391
+ Returns:
392
+ (n_imin, n_jmin, n_kmin, n_imax, n_jmax, n_kmax): Six 3D normal vectors.
393
+ """
316
394
  # Calculate Normals
317
395
  X = block.X
318
396
  Y = block.Y
319
397
  Z = block.Z
320
- imax = block.IMAX
321
- jmax = block.JMAX
322
- kmax = block.KMAX
323
- # IMAX - Normal should be out of the page
398
+ # IMAX/JMAX/KMAX are shapes (e.g. 41); last valid index is shape-1
399
+ imax = block.IMAX - 1
400
+ jmax = block.JMAX - 1
401
+ kmax = block.KMAX - 1
402
+ # IMAX - Normal should be out of the page
324
403
  # Normals I direction: IMIN https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal
325
- x = [X[0,0,0],X[0,jmax,0],X[0,0,kmax]]
404
+ x = [X[0,0,0],X[0,jmax,0],X[0,0,kmax]]
326
405
  y = [Y[0,0,0],Y[0,jmax,0],Y[0,0,kmax]]
327
406
  z = [Z[0,0,0],Z[0,jmax,0],Z[0,0,kmax]]
328
- u = np.array([x[1]-x[0],y[1]-y[0],z[1]-z[0]])
407
+ u = np.array([x[1]-x[0],y[1]-y[0],z[1]-z[0]])
329
408
  v = np.array([x[2]-x[0],y[2]-y[0],z[2]-z[0]])
330
409
  n_imin = np.cross(u,v)
331
-
410
+
332
411
  # Normals I direction: IMAX
333
- x = [X[imax,0,0],X[imax,jmax,0],X[imax,0,kmax]]
412
+ x = [X[imax,0,0],X[imax,jmax,0],X[imax,0,kmax]]
334
413
  y = [Y[imax,0,0],Y[imax,jmax,0],Y[imax,0,kmax]]
335
414
  z = [Z[imax,0,0],Z[imax,jmax,0],Z[imax,0,kmax]]
336
415
  v1 = np.array([x[1]-x[0],y[1]-y[0],z[1]-z[0]])
@@ -338,7 +417,7 @@ def calculate_outward_normals(block:Block):
338
417
  n_imax = np.cross(v1,v2)
339
418
 
340
419
  # Normals J direction: JMIN
341
- x = [X[0,0,0],X[imax,0,0],X[0,0,kmax]]
420
+ x = [X[0,0,0],X[imax,0,0],X[0,0,kmax]]
342
421
  y = [Y[0,0,0],Y[imax,0,0],Y[0,0,kmax]]
343
422
  z = [Z[0,0,0],Z[imax,0,0],Z[0,0,kmax]]
344
423
  v1 = np.array([x[1]-x[0],y[1]-y[0],z[1]-z[0]])
@@ -346,7 +425,7 @@ def calculate_outward_normals(block:Block):
346
425
  n_jmin = np.cross(v1,v2)
347
426
 
348
427
  # Normals J direction: JMAX
349
- x = [X[0,jmax,0],X[imax,jmax,0],X[0,jmax,kmax]]
428
+ x = [X[0,jmax,0],X[imax,jmax,0],X[0,jmax,kmax]]
350
429
  y = [Y[0,jmax,0],Y[imax,jmax,0],Y[0,jmax,kmax]]
351
430
  z = [Z[0,jmax,0],Z[imax,jmax,0],Z[0,jmax,kmax]]
352
431
  v1 = np.array([x[1]-x[0],y[1]-y[0],z[1]-z[0]])
@@ -354,7 +433,7 @@ def calculate_outward_normals(block:Block):
354
433
  n_jmax = np.cross(v1,v2)
355
434
 
356
435
  # Normals K direction: KMIN
357
- x = [X[imax,0,0],X[0,jmax,0],X[0,0,0]]
436
+ x = [X[imax,0,0],X[0,jmax,0],X[0,0,0]]
358
437
  y = [Y[imax,0,0],Y[0,jmax,0],Y[0,0,0]]
359
438
  z = [Z[imax,0,0],Z[0,jmax,0],Z[0,0,0]]
360
439
  v1 = np.array([x[1]-x[0],y[1]-y[0],z[1]-z[0]])
@@ -362,7 +441,7 @@ def calculate_outward_normals(block:Block):
362
441
  n_kmin = np.cross(v1,v2)
363
442
 
364
443
  # Normals K direction: KMAX
365
- x = [X[imax,0,kmax],X[0,jmax,kmax],X[0,0,kmax]]
444
+ x = [X[imax,0,kmax],X[0,jmax,kmax],X[0,0,kmax]]
366
445
  y = [Y[imax,0,kmax],Y[0,jmax,kmax],Y[0,0,kmax]]
367
446
  z = [Z[imax,0,kmax],Z[0,jmax,kmax],Z[0,0,kmax]]
368
447
  v1 = np.array([x[1]-x[0],y[1]-y[0],z[1]-z[0]])
@@ -372,11 +451,11 @@ def calculate_outward_normals(block:Block):
372
451
  return n_imin,n_jmin,n_kmin,n_imax,n_jmax,n_kmax
373
452
 
374
453
  def split_blocks(blocks:List[Block],gcd:int=4):
375
- """Split blocks but also keep greatest common divisor
454
+ """Split blocks into smaller sub-blocks while preserving GCD divisibility.
376
455
 
377
456
  Args:
378
- blocks (List[]): _description_
379
- gcd (int, optional): _description_. Defaults to 4.
457
+ blocks (List[Block]): Blocks to split.
458
+ gcd (int): Target greatest common divisor for sub-block dimensions.
380
459
  """
381
460
  pass
382
461