pyvale 2025.7.2__cp311-cp311-macosx_14_0_arm64.whl → 2025.8.1__cp311-cp311-macosx_14_0_arm64.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 pyvale might be problematic. Click here for more details.
- pyvale/__init__.py +12 -92
- pyvale/blender/__init__.py +23 -0
- pyvale/{pyvaleexceptions.py → blender/blenderexceptions.py} +0 -3
- pyvale/{blenderlightdata.py → blender/blenderlightdata.py} +3 -3
- pyvale/{blendermaterialdata.py → blender/blendermaterialdata.py} +1 -1
- pyvale/{blenderrenderdata.py → blender/blenderrenderdata.py} +5 -3
- pyvale/{blenderscene.py → blender/blenderscene.py} +33 -30
- pyvale/{blendertools.py → blender/blendertools.py} +14 -10
- pyvale/dataset/__init__.py +7 -0
- pyvale/dataset/dataset.py +443 -0
- pyvale/dic/__init__.py +20 -0
- pyvale/{dic2d.py → dic/dic2d.py} +31 -36
- pyvale/dic/dic2dconv.py +6 -0
- pyvale/{dic2dcpp.cpython-311-darwin.so → dic/dic2dcpp.cpython-311-darwin.so} +0 -0
- pyvale/{dicdataimport.py → dic/dicdataimport.py} +8 -8
- pyvale/{dicregionofinterest.py → dic/dicregionofinterest.py} +1 -1
- pyvale/{dicresults.py → dic/dicresults.py} +1 -1
- pyvale/{dicstrain.py → dic/dicstrain.py} +9 -9
- pyvale/examples/basics/{ex1_1_basicscalars_therm2d.py → ex1a_basicscalars_therm2d.py} +12 -9
- pyvale/examples/basics/{ex1_2_sensormodel_therm2d.py → ex1b_sensormodel_therm2d.py} +17 -14
- pyvale/examples/basics/{ex1_3_customsens_therm3d.py → ex1c_customsens_therm3d.py} +27 -24
- pyvale/examples/basics/{ex1_4_basicerrors_therm3d.py → ex1d_basicerrors_therm3d.py} +32 -29
- pyvale/examples/basics/{ex1_5_fielderrs_therm3d.py → ex1e_fielderrs_therm3d.py} +19 -15
- pyvale/examples/basics/{ex1_6_caliberrs_therm2d.py → ex1f_caliberrs_therm2d.py} +20 -16
- pyvale/examples/basics/{ex1_7_spatavg_therm2d.py → ex1g_spatavg_therm2d.py} +19 -16
- pyvale/examples/basics/{ex2_1_basicvectors_disp2d.py → ex2a_basicvectors_disp2d.py} +13 -10
- pyvale/examples/basics/{ex2_2_vectorsens_disp2d.py → ex2b_vectorsens_disp2d.py} +19 -15
- pyvale/examples/basics/{ex2_3_sensangle_disp2d.py → ex2c_sensangle_disp2d.py} +21 -18
- pyvale/examples/basics/{ex2_4_chainfielderrs_disp2d.py → ex2d_chainfielderrs_disp2d.py} +31 -29
- pyvale/examples/basics/{ex2_5_vectorfields3d_disp3d.py → ex2e_vectorfields3d_disp3d.py} +21 -18
- pyvale/examples/basics/{ex3_1_basictensors_strain2d.py → ex3a_basictensors_strain2d.py} +16 -14
- pyvale/examples/basics/{ex3_2_tensorsens2d_strain2d.py → ex3b_tensorsens2d_strain2d.py} +17 -14
- pyvale/examples/basics/{ex3_3_tensorsens3d_strain3d.py → ex3c_tensorsens3d_strain3d.py} +25 -22
- pyvale/examples/basics/{ex4_1_expsim2d_thermmech2d.py → ex4a_expsim2d_thermmech2d.py} +17 -14
- pyvale/examples/basics/{ex4_2_expsim3d_thermmech3d.py → ex4b_expsim3d_thermmech3d.py} +37 -34
- pyvale/examples/basics/ex5_nomesh.py +24 -0
- pyvale/examples/dic/ex1_2_blenderdeformed.py +174 -0
- pyvale/examples/dic/ex1_region_of_interest.py +6 -3
- pyvale/examples/dic/ex2_plate_with_hole.py +21 -18
- pyvale/examples/dic/ex3_plate_with_hole_strain.py +8 -6
- pyvale/examples/dic/ex4_dic_blender.py +17 -15
- pyvale/examples/dic/ex5_dic_challenge.py +19 -14
- pyvale/examples/genanalyticdata/ex1_1_scalarvisualisation.py +16 -10
- pyvale/examples/genanalyticdata/ex1_2_scalarcasebuild.py +3 -3
- pyvale/examples/genanalyticdata/ex2_1_analyticsensors.py +29 -23
- pyvale/examples/genanalyticdata/ex2_2_analyticsensors_nomesh.py +67 -0
- pyvale/examples/imagedef2d/ex_imagedef2d_todisk.py +12 -9
- pyvale/examples/mooseherder/ex0_create_moose_config.py +65 -0
- pyvale/examples/mooseherder/ex1a_modify_moose_input.py +71 -0
- pyvale/examples/mooseherder/ex1b_modify_gmsh_input.py +69 -0
- pyvale/examples/mooseherder/ex2a_run_moose_once.py +80 -0
- pyvale/examples/mooseherder/ex2b_run_gmsh_once.py +64 -0
- pyvale/examples/mooseherder/ex2c_run_both_once.py +114 -0
- pyvale/examples/mooseherder/ex3_run_moose_seq_para.py +157 -0
- pyvale/examples/mooseherder/ex4_run_gmsh-moose_seq_para.py +176 -0
- pyvale/examples/mooseherder/ex5_run_moose_paramulti.py +136 -0
- pyvale/examples/mooseherder/ex6_read_moose_exodus.py +163 -0
- pyvale/examples/mooseherder/ex7a_read_moose_herd_results.py +153 -0
- pyvale/examples/mooseherder/ex7b_read_multi_herd_results.py +116 -0
- pyvale/examples/mooseherder/ex7c_read_multi_gmshmoose_results.py +127 -0
- pyvale/examples/mooseherder/ex7d_readconfig_multi_gmshmoose_results.py +143 -0
- pyvale/examples/mooseherder/ex8_read_existing_sweep_output.py +72 -0
- pyvale/examples/renderblender/ex1_1_blenderscene.py +24 -20
- pyvale/examples/renderblender/ex1_2_blenderdeformed.py +22 -18
- pyvale/examples/renderblender/ex2_1_stereoscene.py +36 -29
- pyvale/examples/renderblender/ex2_2_stereodeformed.py +26 -20
- pyvale/examples/renderblender/ex3_1_blendercalibration.py +24 -17
- pyvale/examples/renderrasterisation/ex_rastenp.py +14 -12
- pyvale/examples/renderrasterisation/ex_rastercyth_oneframe.py +14 -15
- pyvale/examples/renderrasterisation/ex_rastercyth_static_cypara.py +13 -11
- pyvale/examples/renderrasterisation/ex_rastercyth_static_pypara.py +13 -11
- pyvale/mooseherder/__init__.py +32 -0
- pyvale/mooseherder/directorymanager.py +416 -0
- pyvale/mooseherder/exodusreader.py +763 -0
- pyvale/mooseherder/gmshrunner.py +163 -0
- pyvale/mooseherder/inputmodifier.py +236 -0
- pyvale/mooseherder/mooseconfig.py +226 -0
- pyvale/mooseherder/mooseherd.py +527 -0
- pyvale/mooseherder/mooserunner.py +303 -0
- pyvale/mooseherder/outputreader.py +22 -0
- pyvale/mooseherder/simdata.py +92 -0
- pyvale/mooseherder/simrunner.py +31 -0
- pyvale/mooseherder/sweepreader.py +356 -0
- pyvale/mooseherder/sweeptools.py +76 -0
- pyvale/sensorsim/__init__.py +82 -0
- pyvale/{camera.py → sensorsim/camera.py} +7 -7
- pyvale/{camerasensor.py → sensorsim/camerasensor.py} +7 -7
- pyvale/{camerastereo.py → sensorsim/camerastereo.py} +2 -2
- pyvale/{cameratools.py → sensorsim/cameratools.py} +4 -4
- pyvale/{cython → sensorsim/cython}/rastercyth.c +596 -596
- pyvale/{cython → sensorsim/cython}/rastercyth.cpython-311-darwin.so +0 -0
- pyvale/{cython → sensorsim/cython}/rastercyth.py +16 -17
- pyvale/{errorcalculator.py → sensorsim/errorcalculator.py} +1 -1
- pyvale/{errorintegrator.py → sensorsim/errorintegrator.py} +2 -2
- pyvale/{errorrand.py → sensorsim/errorrand.py} +4 -4
- pyvale/{errorsyscalib.py → sensorsim/errorsyscalib.py} +2 -2
- pyvale/{errorsysdep.py → sensorsim/errorsysdep.py} +2 -2
- pyvale/{errorsysfield.py → sensorsim/errorsysfield.py} +8 -8
- pyvale/{errorsysindep.py → sensorsim/errorsysindep.py} +3 -3
- pyvale/sensorsim/exceptions.py +8 -0
- pyvale/{experimentsimulator.py → sensorsim/experimentsimulator.py} +23 -3
- pyvale/{field.py → sensorsim/field.py} +1 -1
- pyvale/{fieldconverter.py → sensorsim/fieldconverter.py} +72 -19
- pyvale/sensorsim/fieldinterp.py +37 -0
- pyvale/sensorsim/fieldinterpmesh.py +124 -0
- pyvale/sensorsim/fieldinterppoints.py +55 -0
- pyvale/{fieldsampler.py → sensorsim/fieldsampler.py} +4 -4
- pyvale/{fieldscalar.py → sensorsim/fieldscalar.py} +28 -24
- pyvale/{fieldtensor.py → sensorsim/fieldtensor.py} +33 -31
- pyvale/{fieldvector.py → sensorsim/fieldvector.py} +33 -31
- pyvale/{imagedef2d.py → sensorsim/imagedef2d.py} +9 -5
- pyvale/{integratorfactory.py → sensorsim/integratorfactory.py} +6 -6
- pyvale/{integratorquadrature.py → sensorsim/integratorquadrature.py} +3 -3
- pyvale/{integratorrectangle.py → sensorsim/integratorrectangle.py} +3 -3
- pyvale/{integratorspatial.py → sensorsim/integratorspatial.py} +1 -1
- pyvale/{rastercy.py → sensorsim/rastercy.py} +5 -5
- pyvale/{rasternp.py → sensorsim/rasternp.py} +9 -9
- pyvale/{rasteropts.py → sensorsim/rasteropts.py} +1 -1
- pyvale/{renderer.py → sensorsim/renderer.py} +1 -1
- pyvale/{rendermesh.py → sensorsim/rendermesh.py} +5 -5
- pyvale/{renderscene.py → sensorsim/renderscene.py} +2 -2
- pyvale/{sensorarray.py → sensorsim/sensorarray.py} +1 -1
- pyvale/{sensorarrayfactory.py → sensorsim/sensorarrayfactory.py} +12 -12
- pyvale/{sensorarraypoint.py → sensorsim/sensorarraypoint.py} +10 -8
- pyvale/{sensordata.py → sensorsim/sensordata.py} +1 -1
- pyvale/{sensortools.py → sensorsim/sensortools.py} +2 -20
- pyvale/sensorsim/simtools.py +174 -0
- pyvale/{visualexpplotter.py → sensorsim/visualexpplotter.py} +3 -3
- pyvale/{visualimages.py → sensorsim/visualimages.py} +2 -2
- pyvale/{visualsimanimator.py → sensorsim/visualsimanimator.py} +4 -4
- pyvale/{visualsimplotter.py → sensorsim/visualsimplotter.py} +5 -5
- pyvale/{visualsimsensors.py → sensorsim/visualsimsensors.py} +12 -12
- pyvale/{visualtools.py → sensorsim/visualtools.py} +1 -1
- pyvale/{visualtraceplotter.py → sensorsim/visualtraceplotter.py} +2 -2
- pyvale/simcases/case17.geo +3 -0
- pyvale/simcases/case17.i +4 -4
- pyvale/simcases/run_1case.py +1 -9
- pyvale/simcases/run_all_cases.py +1 -1
- pyvale/simcases/run_build_case.py +1 -1
- pyvale/simcases/run_example_cases.py +1 -1
- pyvale/verif/__init__.py +12 -0
- pyvale/{analyticsimdatafactory.py → verif/analyticsimdatafactory.py} +2 -2
- pyvale/{analyticsimdatagenerator.py → verif/analyticsimdatagenerator.py} +2 -2
- pyvale/verif/psens.py +125 -0
- pyvale/verif/psensconst.py +18 -0
- pyvale/verif/psensmech.py +227 -0
- pyvale/verif/psensmultiphys.py +187 -0
- pyvale/verif/psensscalar.py +347 -0
- pyvale/verif/psenstensor.py +123 -0
- pyvale/verif/psensvector.py +116 -0
- {pyvale-2025.7.2.dist-info → pyvale-2025.8.1.dist-info}/METADATA +6 -7
- pyvale-2025.8.1.dist-info/RECORD +262 -0
- pyvale/dataset.py +0 -415
- pyvale/simtools.py +0 -67
- pyvale-2025.7.2.dist-info/RECORD +0 -214
- /pyvale/{blendercalibrationdata.py → blender/blendercalibrationdata.py} +0 -0
- /pyvale/{dicchecks.py → dic/dicchecks.py} +0 -0
- /pyvale/{dicspecklegenerator.py → dic/dicspecklegenerator.py} +0 -0
- /pyvale/{dicspecklequality.py → dic/dicspecklequality.py} +0 -0
- /pyvale/{dicstrainresults.py → dic/dicstrainresults.py} +0 -0
- /pyvale/{cameradata.py → sensorsim/cameradata.py} +0 -0
- /pyvale/{cameradata2d.py → sensorsim/cameradata2d.py} +0 -0
- /pyvale/{errordriftcalc.py → sensorsim/errordriftcalc.py} +0 -0
- /pyvale/{fieldtransform.py → sensorsim/fieldtransform.py} +0 -0
- /pyvale/{generatorsrandom.py → sensorsim/generatorsrandom.py} +0 -0
- /pyvale/{imagetools.py → sensorsim/imagetools.py} +0 -0
- /pyvale/{integratortype.py → sensorsim/integratortype.py} +0 -0
- /pyvale/{output.py → sensorsim/output.py} +0 -0
- /pyvale/{raster.py → sensorsim/raster.py} +0 -0
- /pyvale/{sensordescriptor.py → sensorsim/sensordescriptor.py} +0 -0
- /pyvale/{visualimagedef.py → sensorsim/visualimagedef.py} +0 -0
- /pyvale/{visualopts.py → sensorsim/visualopts.py} +0 -0
- /pyvale/{analyticmeshgen.py → verif/analyticmeshgen.py} +0 -0
- {pyvale-2025.7.2.dist-info → pyvale-2025.8.1.dist-info}/WHEEL +0 -0
- {pyvale-2025.7.2.dist-info → pyvale-2025.8.1.dist-info}/licenses/LICENSE +0 -0
- {pyvale-2025.7.2.dist-info → pyvale-2025.8.1.dist-info}/top_level.txt +0 -0
|
@@ -8,15 +8,17 @@ import time
|
|
|
8
8
|
import numpy as np
|
|
9
9
|
from scipy.spatial.transform import Rotation
|
|
10
10
|
import matplotlib.pyplot as plt
|
|
11
|
-
import mooseherder as mh
|
|
12
|
-
import pyvale as pyv
|
|
13
11
|
import imagebenchmarks as ib
|
|
14
12
|
|
|
13
|
+
# Pyvale imports
|
|
14
|
+
import pyvale.sensorsim as sens
|
|
15
|
+
import pyvale.mooseherder as mh
|
|
16
|
+
|
|
15
17
|
def main() -> None:
|
|
16
18
|
print()
|
|
17
19
|
print(80*"=")
|
|
18
20
|
print("RASTER CYTHON FILE (should be *.so on Linux):")
|
|
19
|
-
print(
|
|
21
|
+
print(sens.rastercyth.__file__)
|
|
20
22
|
print(80*"=")
|
|
21
23
|
print()
|
|
22
24
|
|
|
@@ -26,14 +28,14 @@ def main() -> None:
|
|
|
26
28
|
# replaced with a path to your own simulation file
|
|
27
29
|
#sim_path = Path.home()/"pyvale"/"src"/"pyvale"/"simcases"/"case26_out.e"
|
|
28
30
|
|
|
29
|
-
sim_path =
|
|
30
|
-
#sim_path =
|
|
31
|
+
sim_path = sens.DataSet.render_simple_block_path()
|
|
32
|
+
#sim_path = sens.DataSet.render_mechanical_3d_path()
|
|
31
33
|
sim_data = mh.ExodusReader(sim_path).read_all_sim_data()
|
|
32
34
|
|
|
33
35
|
disp_comps = ("disp_x","disp_y","disp_z")
|
|
34
36
|
|
|
35
37
|
# Scale m -> mm
|
|
36
|
-
sim_data =
|
|
38
|
+
sim_data = sens.scale_length_units(1000.0,sim_data,disp_comps)
|
|
37
39
|
|
|
38
40
|
print()
|
|
39
41
|
print(f"{np.max(np.abs(sim_data.node_vars['disp_x']))=}")
|
|
@@ -42,15 +44,12 @@ def main() -> None:
|
|
|
42
44
|
print()
|
|
43
45
|
|
|
44
46
|
# Extracts the surface mesh from a full 3d simulation for rendering
|
|
45
|
-
render_mesh =
|
|
47
|
+
render_mesh = sens.create_render_mesh(sim_data,
|
|
46
48
|
("disp_y","disp_x","disp_z"),
|
|
47
49
|
sim_spat_dim=3,
|
|
48
50
|
field_disp_keys=disp_comps)
|
|
49
51
|
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
53
|
pixel_num = np.array((960,1280),dtype=np.int32)
|
|
55
54
|
pixel_size = np.array((5.3e-3,5.3e-3),dtype=np.float64)
|
|
56
55
|
focal_leng: float = 50.0
|
|
@@ -58,7 +57,7 @@ def main() -> None:
|
|
|
58
57
|
fov_scale_factor: float = 1.1
|
|
59
58
|
|
|
60
59
|
(roi_pos_world,
|
|
61
|
-
cam_pos_world) =
|
|
60
|
+
cam_pos_world) = sens.CameraTools.pos_fill_frame(
|
|
62
61
|
coords_world=render_mesh.coords,
|
|
63
62
|
pixel_num=pixel_num,
|
|
64
63
|
pixel_size=pixel_size,
|
|
@@ -67,7 +66,7 @@ def main() -> None:
|
|
|
67
66
|
frame_fill=fov_scale_factor,
|
|
68
67
|
)
|
|
69
68
|
|
|
70
|
-
cam_data =
|
|
69
|
+
cam_data = sens.CameraData(
|
|
71
70
|
pixels_num=pixel_num,
|
|
72
71
|
pixels_size=pixel_size,
|
|
73
72
|
pos_world=cam_pos_world,
|
|
@@ -144,7 +143,7 @@ def main() -> None:
|
|
|
144
143
|
|
|
145
144
|
(image_buffer,
|
|
146
145
|
depth_buffer,
|
|
147
|
-
elems_in_image) =
|
|
146
|
+
elems_in_image) = sens.rastercyth.raster_static_frame(
|
|
148
147
|
render_mesh.coords,
|
|
149
148
|
render_mesh.connectivity,
|
|
150
149
|
fields_render,
|
|
@@ -164,7 +163,7 @@ def main() -> None:
|
|
|
164
163
|
|
|
165
164
|
#===========================================================================
|
|
166
165
|
# PLOTTING
|
|
167
|
-
plot_on =
|
|
166
|
+
plot_on = True
|
|
168
167
|
plot_field = 0
|
|
169
168
|
|
|
170
169
|
# depth_to_plot = np.copy(np.asarray(depth_buffer[:,:,plot_frame]))
|
|
@@ -173,7 +172,7 @@ def main() -> None:
|
|
|
173
172
|
# image_to_plot[depth_buffer[:,:,plot_frame] > 10*cam_data.image_dist] = np.nan
|
|
174
173
|
|
|
175
174
|
if plot_on:
|
|
176
|
-
plot_opts =
|
|
175
|
+
plot_opts = sens.PlotOptsGeneral()
|
|
177
176
|
|
|
178
177
|
|
|
179
178
|
(fig, ax) = plt.subplots(figsize=plot_opts.single_fig_size_square,
|
|
@@ -9,26 +9,28 @@ import time
|
|
|
9
9
|
import numpy as np
|
|
10
10
|
from scipy.spatial.transform import Rotation
|
|
11
11
|
import matplotlib.pyplot as plt
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
|
|
13
|
+
# Pyvale imports
|
|
14
|
+
import pyvale.sensorsim as sens
|
|
15
|
+
import pyvale.mooseherder as mh
|
|
14
16
|
|
|
15
17
|
def main() -> None:
|
|
16
18
|
print()
|
|
17
19
|
print(80*"=")
|
|
18
20
|
print("RASTER CYTHON FILE (should be *.so on Linux):")
|
|
19
|
-
print(
|
|
21
|
+
print(sens.rastercyth.__file__)
|
|
20
22
|
print(80*"=")
|
|
21
23
|
print()
|
|
22
24
|
|
|
23
|
-
sim_path =
|
|
24
|
-
#sim_path =
|
|
25
|
+
sim_path = sens.DataSet.render_mechanical_3d_path()
|
|
26
|
+
#sim_path = sens.DataSet.render_simple_block_path()
|
|
25
27
|
#sim_path = Path.home()/"pyvale"/"src"/"pyvale"/"simcases"/"case26_out.e"
|
|
26
28
|
sim_data = mh.ExodusReader(sim_path).read_all_sim_data()
|
|
27
29
|
|
|
28
30
|
disp_comps = ("disp_x","disp_y","disp_z")
|
|
29
31
|
|
|
30
32
|
# Scale m -> mm
|
|
31
|
-
sim_data =
|
|
33
|
+
sim_data = sens.scale_length_units(1000.0,sim_data,disp_comps)
|
|
32
34
|
|
|
33
35
|
print()
|
|
34
36
|
print(f"{np.max(np.abs(sim_data.node_vars['disp_x']))=}")
|
|
@@ -37,7 +39,7 @@ def main() -> None:
|
|
|
37
39
|
print()
|
|
38
40
|
|
|
39
41
|
# Extracts the surface mesh from a full 3d simulation for rendering
|
|
40
|
-
render_mesh =
|
|
42
|
+
render_mesh = sens.create_render_mesh(sim_data,
|
|
41
43
|
("disp_y","disp_x"),
|
|
42
44
|
sim_spat_dim=3,
|
|
43
45
|
field_disp_keys=disp_comps)
|
|
@@ -66,7 +68,7 @@ def main() -> None:
|
|
|
66
68
|
fov_scale_factor: float = 1.1
|
|
67
69
|
|
|
68
70
|
(roi_pos_world,
|
|
69
|
-
cam_pos_world) =
|
|
71
|
+
cam_pos_world) = sens.CameraTools.pos_fill_frame(
|
|
70
72
|
coords_world=render_mesh.coords,
|
|
71
73
|
pixel_num=pixel_num,
|
|
72
74
|
pixel_size=pixel_size,
|
|
@@ -75,7 +77,7 @@ def main() -> None:
|
|
|
75
77
|
frame_fill=fov_scale_factor,
|
|
76
78
|
)
|
|
77
79
|
|
|
78
|
-
cam_data =
|
|
80
|
+
cam_data = sens.CameraData(
|
|
79
81
|
pixels_num=pixel_num,
|
|
80
82
|
pixels_size=pixel_size,
|
|
81
83
|
pos_world=cam_pos_world,
|
|
@@ -120,7 +122,7 @@ def main() -> None:
|
|
|
120
122
|
|
|
121
123
|
(image_buffer,
|
|
122
124
|
depth_buffer,
|
|
123
|
-
elems_in_image) =
|
|
125
|
+
elems_in_image) = sens.rastercyth.raster_static_mesh(
|
|
124
126
|
render_mesh,
|
|
125
127
|
cam_data,
|
|
126
128
|
0)
|
|
@@ -149,7 +151,7 @@ def main() -> None:
|
|
|
149
151
|
# image_to_plot[depth_buffer[:,:,plot_frame] > 10*cam_data.image_dist] = np.nan
|
|
150
152
|
|
|
151
153
|
if plot_on:
|
|
152
|
-
plot_opts =
|
|
154
|
+
plot_opts = sens.PlotOptsGeneral()
|
|
153
155
|
|
|
154
156
|
for ff in plot_frames:
|
|
155
157
|
(fig, ax) = plt.subplots(figsize=plot_opts.single_fig_size_square,
|
|
@@ -8,29 +8,31 @@ import time
|
|
|
8
8
|
import numpy as np
|
|
9
9
|
from scipy.spatial.transform import Rotation
|
|
10
10
|
import matplotlib.pyplot as plt
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
|
|
12
|
+
# Pyvale imports
|
|
13
|
+
import pyvale.sensorsim as sens
|
|
14
|
+
import pyvale.mooseherder as mh
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
def main() -> None:
|
|
16
18
|
print()
|
|
17
19
|
print(80*"=")
|
|
18
20
|
print("RASTER CYTHON FILE (should be *.so on Linux):")
|
|
19
|
-
print(
|
|
21
|
+
print(sens.rastercyth.__file__)
|
|
20
22
|
print(80*"=")
|
|
21
23
|
print()
|
|
22
24
|
|
|
23
25
|
# This a path to an exodus *.e output file from MOOSE, this can be
|
|
24
26
|
# replaced with a path to your own simulation file
|
|
25
|
-
sim_path =
|
|
26
|
-
#sim_path =
|
|
27
|
+
sim_path = sens.DataSet.render_mechanical_3d_path()
|
|
28
|
+
#sim_path = sens.DataSet.render_simple_block_path()
|
|
27
29
|
#sim_path = Path.home()/"pyvale"/"src"/"pyvale"/"simcases"/"case26_out.e"
|
|
28
30
|
sim_data = mh.ExodusReader(sim_path).read_all_sim_data()
|
|
29
31
|
|
|
30
32
|
disp_comps = ("disp_x","disp_y","disp_z")
|
|
31
33
|
|
|
32
34
|
# Scale m -> mm
|
|
33
|
-
sim_data =
|
|
35
|
+
sim_data = sens.scale_length_units(1000.0,sim_data,disp_comps)
|
|
34
36
|
|
|
35
37
|
print()
|
|
36
38
|
print(f"{np.max(np.abs(sim_data.node_vars['disp_x']))=}")
|
|
@@ -39,7 +41,7 @@ def main() -> None:
|
|
|
39
41
|
print()
|
|
40
42
|
|
|
41
43
|
# Extracts the surface mesh from a full 3d simulation for rendering
|
|
42
|
-
render_mesh =
|
|
44
|
+
render_mesh = sens.create_render_mesh(sim_data,
|
|
43
45
|
("disp_y","disp_x"),
|
|
44
46
|
sim_spat_dim=3,
|
|
45
47
|
field_disp_keys=disp_comps)
|
|
@@ -68,7 +70,7 @@ def main() -> None:
|
|
|
68
70
|
fov_scale_factor: float = 1.1
|
|
69
71
|
|
|
70
72
|
(roi_pos_world,
|
|
71
|
-
cam_pos_world) =
|
|
73
|
+
cam_pos_world) = sens.CameraTools.pos_fill_frame(
|
|
72
74
|
coords_world=render_mesh.coords,
|
|
73
75
|
pixel_num=pixel_num,
|
|
74
76
|
pixel_size=pixel_size,
|
|
@@ -77,7 +79,7 @@ def main() -> None:
|
|
|
77
79
|
frame_fill=fov_scale_factor,
|
|
78
80
|
)
|
|
79
81
|
|
|
80
|
-
cam_data =
|
|
82
|
+
cam_data = sens.CameraData(
|
|
81
83
|
pixels_num=pixel_num,
|
|
82
84
|
pixels_size=pixel_size,
|
|
83
85
|
pos_world=cam_pos_world,
|
|
@@ -122,7 +124,7 @@ def main() -> None:
|
|
|
122
124
|
|
|
123
125
|
(image_buffer,
|
|
124
126
|
depth_buffer,
|
|
125
|
-
elems_in_images) =
|
|
127
|
+
elems_in_images) = sens.RasterCY.raster_static_mesh(cam_data,
|
|
126
128
|
render_mesh,
|
|
127
129
|
16)
|
|
128
130
|
|
|
@@ -150,7 +152,7 @@ def main() -> None:
|
|
|
150
152
|
# image_to_plot[depth_buffer[:,:,plot_frame] > 10*cam_data.image_dist] = np.nan
|
|
151
153
|
|
|
152
154
|
if plot_on:
|
|
153
|
-
plot_opts =
|
|
155
|
+
plot_opts = sens.PlotOptsGeneral()
|
|
154
156
|
|
|
155
157
|
for ff in plot_frames:
|
|
156
158
|
(fig, ax) = plt.subplots(figsize=plot_opts.single_fig_size_square,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#===============================================================================
|
|
2
|
+
# pyvale: the python validation engine
|
|
3
|
+
# License: MIT
|
|
4
|
+
# Copyright (C) 2025 The Computer Aided Validation Team
|
|
5
|
+
#===============================================================================
|
|
6
|
+
|
|
7
|
+
from .inputmodifier import InputModifier
|
|
8
|
+
from .simrunner import SimRunner
|
|
9
|
+
from .mooserunner import MooseRunner
|
|
10
|
+
from .gmshrunner import GmshRunner
|
|
11
|
+
from .exodusreader import ExodusReader
|
|
12
|
+
from .mooseherd import MooseHerd
|
|
13
|
+
from .directorymanager import DirectoryManager
|
|
14
|
+
from .sweepreader import SweepReader
|
|
15
|
+
from .simdata import SimData
|
|
16
|
+
from .simdata import SimReadConfig
|
|
17
|
+
from .mooseconfig import MooseConfig
|
|
18
|
+
from .sweeptools import sweep_param_grid
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
__all__ = ["InputModifier",
|
|
22
|
+
"SimRunner",
|
|
23
|
+
"MooseRunner",
|
|
24
|
+
"GmshRunner",
|
|
25
|
+
"ExodusReader",
|
|
26
|
+
"mooseherd",
|
|
27
|
+
"DirectoryManager",
|
|
28
|
+
"SweepReader",
|
|
29
|
+
"SimData",
|
|
30
|
+
"SimReadConfig",
|
|
31
|
+
"MooseConfig",
|
|
32
|
+
"sweep_param_grid"]
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# pyvale: the python validation engine
|
|
3
|
+
# License: MIT
|
|
4
|
+
# Copyright (C) 2025 The Computer Aided Validation Team
|
|
5
|
+
# ==============================================================================
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import shutil
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
class DirectoryManager:
|
|
13
|
+
"""Manages directories for running simulations in parallel with the
|
|
14
|
+
mooseherd.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
|
|
19
|
+
Returns
|
|
20
|
+
-------
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
def __init__(self, n_dirs: int = 1) -> None:
|
|
24
|
+
"""__init__
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
n_dirs (int, optional): number of directories to be created.
|
|
28
|
+
Defaults to 1.
|
|
29
|
+
"""
|
|
30
|
+
self._n_dirs = n_dirs
|
|
31
|
+
self._sub_dir = 'sim-workdir'
|
|
32
|
+
self._base_dir = Path().cwd()
|
|
33
|
+
self._run_dirs = self._set_run_dirs()
|
|
34
|
+
self._output_paths = list([])
|
|
35
|
+
self._output_key_tag = 'output-key'
|
|
36
|
+
self._sweep_var_tag = 'sweep-vars'
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _set_run_dirs(self) -> list[Path]:
|
|
40
|
+
"""_set_run_dirs: helper function that populates the list of
|
|
41
|
+
directories that will be created by the manager. Uses the base directory
|
|
42
|
+
at the start of the path and then creates numbered sub-directory paths
|
|
43
|
+
based on the sub directory name and number of directories specified.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
list[Path]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
"""
|
|
54
|
+
run_dirs = list([])
|
|
55
|
+
for nn in range(self._n_dirs): # type: ignore
|
|
56
|
+
run_dirs.append(self._base_dir / (self._sub_dir + '-' + str(nn+1)))
|
|
57
|
+
|
|
58
|
+
return run_dirs
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def set_sub_dir_name(self, sub_dir_name: str) -> None:
|
|
62
|
+
"""set_sub_dir_name: used to set the string used at the start of the
|
|
63
|
+
created sub-directores. default on creation is 'sim-workdir'. Populates
|
|
64
|
+
the list of run directories using the new sub directory name.
|
|
65
|
+
|
|
66
|
+
Parameters
|
|
67
|
+
----------
|
|
68
|
+
sub_dir_name : str
|
|
69
|
+
string to be used to name the created
|
|
70
|
+
sub-directories within the base directory.
|
|
71
|
+
sub_dir_name: str :
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
|
|
77
|
+
"""
|
|
78
|
+
self._sub_dir = sub_dir_name
|
|
79
|
+
self._run_dirs = self._set_run_dirs()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def set_base_dir(self, base_dir: Path, clear_old_dirs = False) -> None:
|
|
83
|
+
"""set_base_dir: sets the base directory to create sub-directors for
|
|
84
|
+
running the simulations. The base directory must exist.
|
|
85
|
+
|
|
86
|
+
Parameters
|
|
87
|
+
----------
|
|
88
|
+
base_dir : Path
|
|
89
|
+
directory in which the new working directories will
|
|
90
|
+
be created.
|
|
91
|
+
clear_old_dirs : bool
|
|
92
|
+
deletes previous directories in
|
|
93
|
+
the base directory and their contents if they exist. Defaults
|
|
94
|
+
to False.
|
|
95
|
+
|
|
96
|
+
Returns
|
|
97
|
+
-------
|
|
98
|
+
|
|
99
|
+
Raises
|
|
100
|
+
------
|
|
101
|
+
FileExistsError
|
|
102
|
+
the selected base directory does no exist.
|
|
103
|
+
|
|
104
|
+
"""
|
|
105
|
+
if not base_dir.is_dir():
|
|
106
|
+
raise FileExistsError("Specified base directory does not exist.")
|
|
107
|
+
|
|
108
|
+
if clear_old_dirs:
|
|
109
|
+
self.clear_dirs()
|
|
110
|
+
|
|
111
|
+
self._base_dir = base_dir
|
|
112
|
+
self._run_dirs = self._set_run_dirs()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def get_output_key_tag(self) -> str:
|
|
116
|
+
"""get_output_key_tag: returns the string used to name the output
|
|
117
|
+
key files that map the simulation outputs to the various directories
|
|
118
|
+
that are being managed.
|
|
119
|
+
|
|
120
|
+
Parameters
|
|
121
|
+
----------
|
|
122
|
+
|
|
123
|
+
Returns
|
|
124
|
+
-------
|
|
125
|
+
str
|
|
126
|
+
common string used to name the output key json files.
|
|
127
|
+
|
|
128
|
+
"""
|
|
129
|
+
return self._output_key_tag
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def get_sweep_var_tag(self) -> str:
|
|
133
|
+
"""get_sweep_var_tag: returns ths string used to name the sweep
|
|
134
|
+
variable json file that contains a copy of the dictionary the user
|
|
135
|
+
provided as part of the parameter sweep.
|
|
136
|
+
|
|
137
|
+
Parameters
|
|
138
|
+
----------
|
|
139
|
+
|
|
140
|
+
Returns
|
|
141
|
+
-------
|
|
142
|
+
str
|
|
143
|
+
common string used to name the sweep variable json files.
|
|
144
|
+
|
|
145
|
+
"""
|
|
146
|
+
return self._sweep_var_tag
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def create_dirs(self) -> list[Path]:
|
|
150
|
+
"""Creates the specified number of directories based on
|
|
151
|
+
the sub_dir name within the base directory.
|
|
152
|
+
|
|
153
|
+
Parameters
|
|
154
|
+
----------
|
|
155
|
+
|
|
156
|
+
Returns
|
|
157
|
+
-------
|
|
158
|
+
list[Path]
|
|
159
|
+
list of paths to the directories to create.
|
|
160
|
+
|
|
161
|
+
"""
|
|
162
|
+
for rr in self._run_dirs:
|
|
163
|
+
if not rr.is_dir():
|
|
164
|
+
rr.mkdir()
|
|
165
|
+
|
|
166
|
+
return self._run_dirs
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def clear_dirs(self) -> None:
|
|
170
|
+
"""Deletes all working directories in the base directory
|
|
171
|
+
that have the corresponding sub-directory name and their contents.
|
|
172
|
+
"""
|
|
173
|
+
all_dirs = os.listdir(self._base_dir)
|
|
174
|
+
for dd in all_dirs:
|
|
175
|
+
if os.path.isdir(self._base_dir / dd):
|
|
176
|
+
if self._sub_dir in dd:
|
|
177
|
+
shutil.rmtree(self._base_dir / dd)
|
|
178
|
+
|
|
179
|
+
def reset_dirs(self) -> list[Path]:
|
|
180
|
+
"""Helper function that first clears the working directories if they
|
|
181
|
+
exist and then creates new working directories ready for a simulation
|
|
182
|
+
sweep.
|
|
183
|
+
|
|
184
|
+
Returns
|
|
185
|
+
-------
|
|
186
|
+
list[Path]
|
|
187
|
+
List of paths to the created working directories
|
|
188
|
+
"""
|
|
189
|
+
self.clear_dirs()
|
|
190
|
+
return self.create_dirs()
|
|
191
|
+
|
|
192
|
+
def get_all_run_dirs(self) -> list[Path]:
|
|
193
|
+
"""Returns the list of paths to all the directories
|
|
194
|
+
that can be used to run simulations.
|
|
195
|
+
|
|
196
|
+
Parameters
|
|
197
|
+
----------
|
|
198
|
+
|
|
199
|
+
Returns
|
|
200
|
+
-------
|
|
201
|
+
list[Path]
|
|
202
|
+
paths to all created directories.
|
|
203
|
+
|
|
204
|
+
"""
|
|
205
|
+
return self._run_dirs
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def get_run_dir(self, dir_num: int) -> Path:
|
|
209
|
+
"""get_run_dir: returns the path to the run directory based on the
|
|
210
|
+
input directory number. The directory number can be greater than the
|
|
211
|
+
number of managed directories in the run_dirs list in this case the
|
|
212
|
+
directory number will wrap and point at an existing run directory
|
|
213
|
+
allowing multiple simulations to be run in the same directory.
|
|
214
|
+
|
|
215
|
+
Parameters
|
|
216
|
+
----------
|
|
217
|
+
dir_num : int
|
|
218
|
+
number of the directory path to be retrieved. Can be
|
|
219
|
+
greater than the specified number of directories and will wrap
|
|
220
|
+
appropriately
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
Returns
|
|
224
|
+
-------
|
|
225
|
+
Path
|
|
226
|
+
path to the directory
|
|
227
|
+
|
|
228
|
+
"""
|
|
229
|
+
if dir_num < 0:
|
|
230
|
+
dir_num = 0
|
|
231
|
+
elif dir_num >= self._n_dirs:
|
|
232
|
+
dir_num = dir_num % self._n_dirs
|
|
233
|
+
|
|
234
|
+
return self._run_dirs[dir_num]
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def set_output_paths(self, output_paths: list[list[Path | None]]) -> None:
|
|
238
|
+
"""set_output_paths: sets the list of lists to the simulation output
|
|
239
|
+
based on herder input.
|
|
240
|
+
|
|
241
|
+
Parameters
|
|
242
|
+
----------
|
|
243
|
+
output_paths : list[list[Path | None]]
|
|
244
|
+
paths to all outputs from the
|
|
245
|
+
variable sweep. Outer list is the variable combination run
|
|
246
|
+
inner list is based on the order the simulations were called.
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
Returns
|
|
250
|
+
-------
|
|
251
|
+
|
|
252
|
+
"""
|
|
253
|
+
self._output_paths = output_paths
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def get_output_paths(self) -> list[list[Path | None]]:
|
|
257
|
+
"""get_output_paths: returns the list of lists to the simulation output
|
|
258
|
+
files.
|
|
259
|
+
|
|
260
|
+
Parameters
|
|
261
|
+
----------
|
|
262
|
+
|
|
263
|
+
Returns
|
|
264
|
+
-------
|
|
265
|
+
list[list[Path | None]]
|
|
266
|
+
paths to all outputs from the variable sweep.
|
|
267
|
+
Outer list is the variable combination run inner list is based
|
|
268
|
+
on the order the simulations were called.
|
|
269
|
+
|
|
270
|
+
"""
|
|
271
|
+
return self._output_paths
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def get_output_key_file(self, sweep_iter: int = 1) -> Path:
|
|
275
|
+
"""get_output_key_file: gets the path to the output key file created
|
|
276
|
+
during the variable sweep mapping directories to given combinations
|
|
277
|
+
of variables that were run.
|
|
278
|
+
|
|
279
|
+
Parameters
|
|
280
|
+
----------
|
|
281
|
+
sweep_iter : int
|
|
282
|
+
number corresponding to the sweep iteration to
|
|
283
|
+
retrieve. Defaults to 1.
|
|
284
|
+
|
|
285
|
+
Returns
|
|
286
|
+
-------
|
|
287
|
+
Path
|
|
288
|
+
path to the output key file that maps output paths to the
|
|
289
|
+
combinations of variables in the sweep.
|
|
290
|
+
|
|
291
|
+
"""
|
|
292
|
+
return self._run_dirs[0] / f'{self._output_key_tag}-{sweep_iter:d}.json'
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def write_output_key(self, sweep_iter: int) -> None:
|
|
296
|
+
"""write_output_key: converts the output paths to strings and saves
|
|
297
|
+
them in json format.
|
|
298
|
+
|
|
299
|
+
Parameters
|
|
300
|
+
----------
|
|
301
|
+
sweep_iter : int
|
|
302
|
+
number corresponing to the sweep iteration to
|
|
303
|
+
write. The sweep iteration is used to number the output key
|
|
304
|
+
files.
|
|
305
|
+
|
|
306
|
+
Returns
|
|
307
|
+
-------
|
|
308
|
+
|
|
309
|
+
"""
|
|
310
|
+
str_output = output_paths_to_str(self._output_paths)
|
|
311
|
+
|
|
312
|
+
with open(self.get_output_key_file(sweep_iter), "w", encoding='utf-8') as okf:
|
|
313
|
+
json.dump(str_output, okf, indent=4)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def get_sweep_var_file(self, sweep_iter: int = 1) -> Path:
|
|
317
|
+
"""get_sweep_var_file: path to the json file which contains the
|
|
318
|
+
dictionary of variables that were analysed at the given sweep iteration
|
|
319
|
+
|
|
320
|
+
Parameters
|
|
321
|
+
----------
|
|
322
|
+
sweep_iter : int
|
|
323
|
+
Sweep iteration (call number to herd
|
|
324
|
+
run_para). Defaults to 1.
|
|
325
|
+
|
|
326
|
+
Returns
|
|
327
|
+
-------
|
|
328
|
+
Path
|
|
329
|
+
path to the json sweep variable file.
|
|
330
|
+
|
|
331
|
+
"""
|
|
332
|
+
return self._run_dirs[0] / f'{self._sweep_var_tag}-{sweep_iter:d}.json'
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def write_sweep_vars(self,
|
|
336
|
+
sweep_vars: list[list[dict | None]],
|
|
337
|
+
sweep_iter: int = 1) -> None:
|
|
338
|
+
"""write_sweep_vars: writes the sweep variable dictionary to a json
|
|
339
|
+
file to log the variables used for each simulation.
|
|
340
|
+
|
|
341
|
+
Parameters
|
|
342
|
+
----------
|
|
343
|
+
sweep_vars : list[list[dict | None]]
|
|
344
|
+
sweep variables as passed to the herd to run.
|
|
345
|
+
sweep_iter : int
|
|
346
|
+
iteration number for number of calls to the
|
|
347
|
+
herd. Defaults to 1.
|
|
348
|
+
|
|
349
|
+
Returns
|
|
350
|
+
-------
|
|
351
|
+
|
|
352
|
+
"""
|
|
353
|
+
with open(self.get_sweep_var_file(sweep_iter), "w", encoding='utf-8') as okf:
|
|
354
|
+
json.dump(sweep_vars, okf, indent=4)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def output_paths_to_str(output_files: list[list[Path | None]]
|
|
359
|
+
) -> list[list[str | None]]:
|
|
360
|
+
"""output_paths_to_str: helper function for converting the output paths
|
|
361
|
+
to strings to allow them to be saved as json.
|
|
362
|
+
|
|
363
|
+
Parameters
|
|
364
|
+
----------
|
|
365
|
+
output_files : list[list[Path | None]]
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
Returns
|
|
369
|
+
-------
|
|
370
|
+
list[list[str]]
|
|
371
|
+
as input with Path converted to str
|
|
372
|
+
|
|
373
|
+
"""
|
|
374
|
+
str_output = list([])
|
|
375
|
+
for sim_iter in output_files:
|
|
376
|
+
iter_output = list([])
|
|
377
|
+
for output_path in sim_iter:
|
|
378
|
+
if output_path is None:
|
|
379
|
+
iter_output.append(None)
|
|
380
|
+
else:
|
|
381
|
+
iter_output.append(str(output_path))
|
|
382
|
+
|
|
383
|
+
str_output.append(iter_output)
|
|
384
|
+
|
|
385
|
+
return str_output
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def output_str_to_paths(output_files: list[list[str | None]]
|
|
389
|
+
) -> list[list[Path | None]]:
|
|
390
|
+
"""output_str_to_paths: helper function to convert strings read from output
|
|
391
|
+
key json to paths.
|
|
392
|
+
|
|
393
|
+
Parameters
|
|
394
|
+
----------
|
|
395
|
+
output_files : list[list[str]]
|
|
396
|
+
output file list of path strings as in the output key file.
|
|
397
|
+
|
|
398
|
+
Returns
|
|
399
|
+
-------
|
|
400
|
+
list[list[Path]]
|
|
401
|
+
as input with str converted to Path.
|
|
402
|
+
|
|
403
|
+
"""
|
|
404
|
+
str_output = list([])
|
|
405
|
+
|
|
406
|
+
for sim_iter in output_files:
|
|
407
|
+
iter_output = list([])
|
|
408
|
+
for output_path in sim_iter:
|
|
409
|
+
if output_path is None:
|
|
410
|
+
iter_output.append(None)
|
|
411
|
+
else:
|
|
412
|
+
iter_output.append(Path(output_path))
|
|
413
|
+
|
|
414
|
+
str_output.append(iter_output)
|
|
415
|
+
|
|
416
|
+
return str_output
|