luminarycloud 0.21.2__py3-none-any.whl → 0.22.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- luminarycloud/__init__.py +3 -1
- luminarycloud/_client/authentication_plugin.py +49 -0
- luminarycloud/_client/client.py +38 -8
- luminarycloud/_client/http_client.py +1 -1
- luminarycloud/_client/retry_interceptor.py +64 -2
- luminarycloud/_feature_flag.py +22 -0
- luminarycloud/_helpers/_create_simulation.py +7 -2
- luminarycloud/_helpers/download.py +11 -0
- luminarycloud/_helpers/proto_decorator.py +13 -5
- luminarycloud/_proto/api/v0/luminarycloud/feature_flag/feature_flag_pb2.py +55 -0
- luminarycloud/_proto/api/v0/luminarycloud/feature_flag/feature_flag_pb2.pyi +52 -0
- luminarycloud/_proto/api/v0/luminarycloud/feature_flag/feature_flag_pb2_grpc.py +72 -0
- luminarycloud/_proto/api/v0/luminarycloud/feature_flag/feature_flag_pb2_grpc.pyi +35 -0
- luminarycloud/_proto/api/v0/luminarycloud/geometry/geometry_pb2.py +132 -132
- luminarycloud/_proto/api/v0/luminarycloud/geometry/geometry_pb2.pyi +36 -8
- luminarycloud/_proto/api/v0/luminarycloud/mesh/mesh_pb2.py +74 -73
- luminarycloud/_proto/api/v0/luminarycloud/mesh/mesh_pb2.pyi +8 -1
- luminarycloud/_proto/api/v0/luminarycloud/physics_ai/physics_ai_pb2.py +53 -23
- luminarycloud/_proto/api/v0/luminarycloud/physics_ai/physics_ai_pb2.pyi +54 -1
- luminarycloud/_proto/api/v0/luminarycloud/physicsaiinference/physicsaiinference_pb2.py +195 -0
- luminarycloud/_proto/api/v0/luminarycloud/physicsaiinference/physicsaiinference_pb2.pyi +361 -0
- luminarycloud/_proto/api/v0/luminarycloud/physicsaiinference/physicsaiinference_pb2_grpc.py +172 -0
- luminarycloud/_proto/api/v0/luminarycloud/physicsaiinference/physicsaiinference_pb2_grpc.pyi +66 -0
- luminarycloud/_proto/api/v0/luminarycloud/simulation/simulation_pb2.py +97 -61
- luminarycloud/_proto/api/v0/luminarycloud/simulation/simulation_pb2.pyi +68 -3
- luminarycloud/_proto/api/v0/luminarycloud/simulation/simulation_pb2_grpc.py +34 -0
- luminarycloud/_proto/api/v0/luminarycloud/simulation/simulation_pb2_grpc.pyi +12 -0
- luminarycloud/_proto/api/v0/luminarycloud/simulation_template/simulation_template_pb2.py +33 -31
- luminarycloud/_proto/api/v0/luminarycloud/simulation_template/simulation_template_pb2.pyi +23 -2
- luminarycloud/_proto/api/v0/luminarycloud/thirdpartyintegration/onshape/onshape_pb2.py +88 -65
- luminarycloud/_proto/api/v0/luminarycloud/thirdpartyintegration/onshape/onshape_pb2.pyi +42 -0
- luminarycloud/_proto/api/v0/luminarycloud/thirdpartyintegration/onshape/onshape_pb2_grpc.py +34 -0
- luminarycloud/_proto/api/v0/luminarycloud/thirdpartyintegration/onshape/onshape_pb2_grpc.pyi +12 -0
- luminarycloud/_proto/base/base_pb2.py +7 -6
- luminarycloud/_proto/base/base_pb2.pyi +4 -0
- luminarycloud/_proto/cad/shape_pb2.py +39 -19
- luminarycloud/_proto/cad/shape_pb2.pyi +86 -34
- luminarycloud/_proto/client/simulation_pb2.py +3 -3
- luminarycloud/_proto/geometry/geometry_pb2.py +77 -63
- luminarycloud/_proto/geometry/geometry_pb2.pyi +42 -3
- luminarycloud/_proto/hexmesh/hexmesh_pb2.py +22 -18
- luminarycloud/_proto/hexmesh/hexmesh_pb2.pyi +18 -2
- luminarycloud/_proto/physicsaiinferenceservice/physicsaiinferenceservice_pb2.py +30 -0
- luminarycloud/_proto/physicsaiinferenceservice/physicsaiinferenceservice_pb2.pyi +7 -0
- luminarycloud/_proto/physicsaitrainingservice/physicsaitrainingservice_pb2.py +2 -2
- luminarycloud/_proto/physicsaitrainingservice/physicsaitrainingservice_pb2_grpc.py +34 -0
- luminarycloud/_proto/physicsaitrainingservice/physicsaitrainingservice_pb2_grpc.pyi +12 -0
- luminarycloud/enum/vis_enums.py +6 -0
- luminarycloud/feature_modification.py +32 -1
- luminarycloud/geometry.py +67 -7
- luminarycloud/geometry_version.py +4 -0
- luminarycloud/mesh.py +4 -0
- luminarycloud/meshing/mesh_generation_params.py +13 -14
- luminarycloud/meshing/sizing_strategy/sizing_strategies.py +1 -2
- luminarycloud/physics_ai/solution.py +4 -0
- luminarycloud/pipelines/api.py +99 -8
- luminarycloud/pipelines/core.py +12 -2
- luminarycloud/pipelines/stages.py +22 -9
- luminarycloud/project.py +5 -8
- luminarycloud/simulation.py +57 -0
- luminarycloud/types/vector3.py +1 -2
- luminarycloud/vis/data_extraction.py +7 -7
- luminarycloud/vis/interactive_report.py +163 -7
- luminarycloud/vis/report.py +113 -1
- luminarycloud/volume_selection.py +71 -7
- {luminarycloud-0.21.2.dist-info → luminarycloud-0.22.1.dist-info}/METADATA +1 -1
- {luminarycloud-0.21.2.dist-info → luminarycloud-0.22.1.dist-info}/RECORD +68 -58
- {luminarycloud-0.21.2.dist-info → luminarycloud-0.22.1.dist-info}/WHEEL +1 -1
- luminarycloud/pipeline_util/dictable.py +0 -27
luminarycloud/enum/vis_enums.py
CHANGED
|
@@ -73,6 +73,12 @@ class VisQuantity(IntEnum):
|
|
|
73
73
|
Q_CRITERION_TIME_AVERAGE = quantitypb.Q_CRITERION_TIME_AVERAGE
|
|
74
74
|
HEAT_FLUX_TIME_AVERAGE = quantitypb.HEAT_FLUX_TIME_AVERAGE
|
|
75
75
|
DEBUG_QUANTITY = quantitypb.DEBUG_QUANTITY
|
|
76
|
+
# Actuator disk quanties
|
|
77
|
+
THRUST_PER_UNIT_AREA = quantitypb.THRUST_PER_UNIT_AREA
|
|
78
|
+
TORQUE_PER_UNIT_AREA = quantitypb.TORQUE_PER_UNIT_AREA
|
|
79
|
+
BLADE_LOCAL_ANGLE_OF_ATTACK = quantitypb.BLADE_LOCAL_ANGLE_OF_ATTACK
|
|
80
|
+
BLADE_SECTIONAL_DRAG_COEFFICIENT = quantitypb.BLADE_SECTIONAL_DRAG_COEFFICIENT
|
|
81
|
+
BLADE_SECTIONAL_LIFT_COEFFICIENT = quantitypb.BLADE_SECTIONAL_LIFT_COEFFICIENT
|
|
76
82
|
|
|
77
83
|
|
|
78
84
|
# Return the text name for the VisQuantity including the units, as it appears in the UI
|
|
@@ -265,6 +265,7 @@ def modify_delete(
|
|
|
265
265
|
def modify_union(
|
|
266
266
|
feature: gpb.Feature,
|
|
267
267
|
volumes: Optional[List[Volume | int]] = None,
|
|
268
|
+
keep: Optional[bool] = None,
|
|
268
269
|
) -> gpb.Modification:
|
|
269
270
|
"""
|
|
270
271
|
Modify a boolean union feature with optional new body IDs.
|
|
@@ -273,6 +274,7 @@ def modify_union(
|
|
|
273
274
|
Args:
|
|
274
275
|
feature: A gpb.Feature object
|
|
275
276
|
volumes: List of volumes or volume IDs to union
|
|
277
|
+
keep: Whether to keep the original bodies
|
|
276
278
|
|
|
277
279
|
Returns:
|
|
278
280
|
A gpb.Modification object
|
|
@@ -287,6 +289,9 @@ def modify_union(
|
|
|
287
289
|
vol_ids = _volumes_to_int_list(volumes)
|
|
288
290
|
_update_repeated_field(boolean_op.reg_union.bodies, vol_ids)
|
|
289
291
|
|
|
292
|
+
if keep is not None:
|
|
293
|
+
boolean_op.reg_union.keep_source_bodies = keep
|
|
294
|
+
|
|
290
295
|
return gpb.Modification(
|
|
291
296
|
mod_type=gpb.Modification.ModificationType.MODIFICATION_TYPE_UPDATE_FEATURE,
|
|
292
297
|
feature=feature_copy,
|
|
@@ -297,6 +302,8 @@ def modify_subtraction(
|
|
|
297
302
|
feature: gpb.Feature,
|
|
298
303
|
volumes: Optional[List[Volume | int]] = None,
|
|
299
304
|
tool_volumes: Optional[List[Volume | int]] = None,
|
|
305
|
+
keep_source_bodies: Optional[bool] = None,
|
|
306
|
+
keep_tool_bodies: Optional[bool] = None,
|
|
300
307
|
propagate_tool_tags: Optional[bool] = None,
|
|
301
308
|
) -> gpb.Modification:
|
|
302
309
|
"""
|
|
@@ -307,6 +314,8 @@ def modify_subtraction(
|
|
|
307
314
|
feature: A gpb.Feature object
|
|
308
315
|
volumes: List of volumes or volume IDs to subtract from
|
|
309
316
|
tool_volumes: List of volumes or volume IDs to use for subtraction
|
|
317
|
+
keep_source_bodies: Whether to keep the original bodies
|
|
318
|
+
keep_tool_bodies: Whether to keep the tool bodies
|
|
310
319
|
propagate_tool_tags: Whether to propagate tool tags
|
|
311
320
|
|
|
312
321
|
Returns:
|
|
@@ -325,6 +334,12 @@ def modify_subtraction(
|
|
|
325
334
|
tool_ids = _volumes_to_int_list(tool_volumes)
|
|
326
335
|
_update_repeated_field(boolean_op.reg_subtraction.tools, tool_ids)
|
|
327
336
|
|
|
337
|
+
if keep_source_bodies is not None:
|
|
338
|
+
boolean_op.reg_subtraction.keep_source_bodies = keep_source_bodies
|
|
339
|
+
|
|
340
|
+
if keep_tool_bodies is not None:
|
|
341
|
+
boolean_op.reg_subtraction.keep_tool_bodies = keep_tool_bodies
|
|
342
|
+
|
|
328
343
|
if propagate_tool_tags is not None:
|
|
329
344
|
boolean_op.reg_subtraction.propagate_tool_tags = propagate_tool_tags
|
|
330
345
|
|
|
@@ -335,7 +350,9 @@ def modify_subtraction(
|
|
|
335
350
|
|
|
336
351
|
|
|
337
352
|
def modify_intersection(
|
|
338
|
-
feature: gpb.Feature,
|
|
353
|
+
feature: gpb.Feature,
|
|
354
|
+
volumes: Optional[List[Volume | int]] = None,
|
|
355
|
+
keep: Optional[bool] = None,
|
|
339
356
|
) -> gpb.Modification:
|
|
340
357
|
"""
|
|
341
358
|
Modify a boolean intersection feature with optional new volumes.
|
|
@@ -344,6 +361,7 @@ def modify_intersection(
|
|
|
344
361
|
Args:
|
|
345
362
|
feature: A gpb.Feature object
|
|
346
363
|
volumes: List of volumes or volume IDs to intersect
|
|
364
|
+
keep: Whether to keep the original bodies
|
|
347
365
|
|
|
348
366
|
Returns:
|
|
349
367
|
A gpb.Modification object
|
|
@@ -358,6 +376,9 @@ def modify_intersection(
|
|
|
358
376
|
vol_ids = _volumes_to_int_list(volumes)
|
|
359
377
|
_update_repeated_field(boolean_op.reg_intersection.bodies, vol_ids)
|
|
360
378
|
|
|
379
|
+
if keep is not None:
|
|
380
|
+
boolean_op.reg_intersection.keep_source_bodies = keep
|
|
381
|
+
|
|
361
382
|
return gpb.Modification(
|
|
362
383
|
mod_type=gpb.Modification.ModificationType.MODIFICATION_TYPE_UPDATE_FEATURE,
|
|
363
384
|
feature=feature_copy,
|
|
@@ -368,6 +389,8 @@ def modify_chop(
|
|
|
368
389
|
feature: gpb.Feature,
|
|
369
390
|
volumes: Optional[List[Volume | int]] = None,
|
|
370
391
|
tool_volumes: Optional[List[Volume | int]] = None,
|
|
392
|
+
keep_source_bodies: Optional[bool] = None,
|
|
393
|
+
keep_tool_bodies: Optional[bool] = None,
|
|
371
394
|
propagate_tool_tags: Optional[bool] = None,
|
|
372
395
|
) -> gpb.Modification:
|
|
373
396
|
"""
|
|
@@ -378,6 +401,8 @@ def modify_chop(
|
|
|
378
401
|
feature: A gpb.Feature object
|
|
379
402
|
volumes: List of volumes or volume IDs to chop
|
|
380
403
|
tool_volumes: List of volumes or volume IDs to use for chopping
|
|
404
|
+
keep_source_bodies: Whether to keep the original bodies
|
|
405
|
+
keep_tool_bodies: Whether to keep the tool bodies
|
|
381
406
|
propagate_tool_tags: Whether to propagate tool tags
|
|
382
407
|
|
|
383
408
|
Returns:
|
|
@@ -397,6 +422,12 @@ def modify_chop(
|
|
|
397
422
|
tool_ids = _volumes_to_int_list(tool_volumes)
|
|
398
423
|
_update_repeated_field(boolean_op.reg_chop.tools, tool_ids)
|
|
399
424
|
|
|
425
|
+
if keep_source_bodies is not None:
|
|
426
|
+
boolean_op.reg_chop.keep_source_bodies = keep_source_bodies
|
|
427
|
+
|
|
428
|
+
if keep_tool_bodies is not None:
|
|
429
|
+
boolean_op.reg_chop.keep_tool_bodies = keep_tool_bodies
|
|
430
|
+
|
|
400
431
|
if propagate_tool_tags is not None:
|
|
401
432
|
boolean_op.reg_chop.propagate_tool_tags = propagate_tool_tags
|
|
402
433
|
|
luminarycloud/geometry.py
CHANGED
|
@@ -61,6 +61,10 @@ class Geometry(ProtoWrapperBase):
|
|
|
61
61
|
"""
|
|
62
62
|
return timestamp_to_datetime(self._proto.update_time)
|
|
63
63
|
|
|
64
|
+
@property
|
|
65
|
+
def url(self) -> str:
|
|
66
|
+
return f"{self.project().url}/geometry/{self.id}"
|
|
67
|
+
|
|
64
68
|
def project(self) -> "Project":
|
|
65
69
|
"""
|
|
66
70
|
Get the project this geometry belongs to.
|
|
@@ -84,6 +88,18 @@ class Geometry(ProtoWrapperBase):
|
|
|
84
88
|
res = get_default_client().UpdateGeometry(req)
|
|
85
89
|
self._proto = res.geometry
|
|
86
90
|
|
|
91
|
+
def delete(self) -> None:
|
|
92
|
+
"""
|
|
93
|
+
Delete the geometry.
|
|
94
|
+
|
|
95
|
+
This operation cannot be reverted and all the geometry data will be deleted as part of
|
|
96
|
+
this request.
|
|
97
|
+
"""
|
|
98
|
+
req = geometrypb.DeleteGeometryRequest(
|
|
99
|
+
geometry_id=self.id,
|
|
100
|
+
)
|
|
101
|
+
get_default_client().DeleteGeometry(req)
|
|
102
|
+
|
|
87
103
|
def copy(self, name: str = "") -> "Geometry":
|
|
88
104
|
"""
|
|
89
105
|
Copy the geometry.
|
|
@@ -386,7 +402,7 @@ class Geometry(ProtoWrapperBase):
|
|
|
386
402
|
jitter = random.uniform(0.5, 1.5)
|
|
387
403
|
time.sleep(2 + jitter)
|
|
388
404
|
|
|
389
|
-
def create_tag(self, name: str, entities: Sequence[Volume | Surface]) ->
|
|
405
|
+
def create_tag(self, name: str, entities: Sequence[Volume | Surface]) -> Tag:
|
|
390
406
|
"""
|
|
391
407
|
Create a tag in the geometry.
|
|
392
408
|
|
|
@@ -396,6 +412,11 @@ class Geometry(ProtoWrapperBase):
|
|
|
396
412
|
The name of the tag to create.
|
|
397
413
|
entities : list of Volumes or Surfaces
|
|
398
414
|
The Volumes and Surfaces to tag.
|
|
415
|
+
|
|
416
|
+
Returns
|
|
417
|
+
-------
|
|
418
|
+
Tag
|
|
419
|
+
The tag that was created.
|
|
399
420
|
"""
|
|
400
421
|
volume_ids = []
|
|
401
422
|
surface_ids = []
|
|
@@ -407,8 +428,7 @@ class Geometry(ProtoWrapperBase):
|
|
|
407
428
|
else:
|
|
408
429
|
raise TypeError("entities must be of type Volume or Surface")
|
|
409
430
|
|
|
410
|
-
|
|
411
|
-
geometry_id=self.id,
|
|
431
|
+
self._modify(
|
|
412
432
|
modification=gpb.Modification(
|
|
413
433
|
mod_type=gpb.Modification.MODIFICATION_TYPE_CREATE_TAG,
|
|
414
434
|
create_or_update_tag=gpb.CreateOrUpdateTag(
|
|
@@ -418,9 +438,9 @@ class Geometry(ProtoWrapperBase):
|
|
|
418
438
|
),
|
|
419
439
|
),
|
|
420
440
|
)
|
|
421
|
-
|
|
441
|
+
return self._get_tag_by_name(name)
|
|
422
442
|
|
|
423
|
-
def rename_tag(self, old_name: str, new_name: str) ->
|
|
443
|
+
def rename_tag(self, old_name: str, new_name: str) -> Tag:
|
|
424
444
|
"""
|
|
425
445
|
Rename a tag in the geometry.
|
|
426
446
|
|
|
@@ -430,6 +450,11 @@ class Geometry(ProtoWrapperBase):
|
|
|
430
450
|
The name of the tag to rename.
|
|
431
451
|
new_name : str
|
|
432
452
|
The new name for the tag.
|
|
453
|
+
|
|
454
|
+
Returns
|
|
455
|
+
-------
|
|
456
|
+
Tag
|
|
457
|
+
The updated tag.
|
|
433
458
|
"""
|
|
434
459
|
self._modify(
|
|
435
460
|
modification=gpb.Modification(
|
|
@@ -440,8 +465,9 @@ class Geometry(ProtoWrapperBase):
|
|
|
440
465
|
),
|
|
441
466
|
),
|
|
442
467
|
)
|
|
468
|
+
return self._get_tag_by_name(new_name)
|
|
443
469
|
|
|
444
|
-
def untag_entities(self, name: str, entities: Sequence[Volume | Surface]) -> None:
|
|
470
|
+
def untag_entities(self, name: str, entities: Sequence[Volume | Surface]) -> Tag | None:
|
|
445
471
|
"""
|
|
446
472
|
Untag entities from a tag in the geometry.
|
|
447
473
|
|
|
@@ -452,6 +478,11 @@ class Geometry(ProtoWrapperBase):
|
|
|
452
478
|
entities : list of Volumes or Surfaces
|
|
453
479
|
The Volumes and Surfaces to untag. If empty, all entities with the
|
|
454
480
|
tag will be untagged.
|
|
481
|
+
|
|
482
|
+
Returns
|
|
483
|
+
-------
|
|
484
|
+
Tag
|
|
485
|
+
The updated tag.
|
|
455
486
|
"""
|
|
456
487
|
volume_ids = []
|
|
457
488
|
surface_ids = []
|
|
@@ -473,8 +504,12 @@ class Geometry(ProtoWrapperBase):
|
|
|
473
504
|
),
|
|
474
505
|
),
|
|
475
506
|
)
|
|
507
|
+
try:
|
|
508
|
+
return self._get_tag_by_name(name)
|
|
509
|
+
except ValueError:
|
|
510
|
+
return None
|
|
476
511
|
|
|
477
|
-
def update_tag(self, name: str, entities: Sequence[Volume | Surface]) ->
|
|
512
|
+
def update_tag(self, name: str, entities: Sequence[Volume | Surface]) -> Tag:
|
|
478
513
|
"""
|
|
479
514
|
Adds entities to a tag in the geometry.
|
|
480
515
|
|
|
@@ -483,6 +518,11 @@ class Geometry(ProtoWrapperBase):
|
|
|
483
518
|
name : str
|
|
484
519
|
The name of the tag to update.
|
|
485
520
|
entities : list of Volumes or Surfaces
|
|
521
|
+
|
|
522
|
+
Returns
|
|
523
|
+
-------
|
|
524
|
+
Tag
|
|
525
|
+
The updated tag.
|
|
486
526
|
"""
|
|
487
527
|
volume_ids = []
|
|
488
528
|
surface_ids = []
|
|
@@ -504,6 +544,7 @@ class Geometry(ProtoWrapperBase):
|
|
|
504
544
|
),
|
|
505
545
|
),
|
|
506
546
|
)
|
|
547
|
+
return self._get_tag_by_name(name)
|
|
507
548
|
|
|
508
549
|
def list_tags(self) -> list[Tag]:
|
|
509
550
|
"""
|
|
@@ -519,6 +560,25 @@ class Geometry(ProtoWrapperBase):
|
|
|
519
560
|
res: geometrypb.ListTagsResponse = get_default_client().ListTags(req)
|
|
520
561
|
return [Tag(t) for t in res.tags]
|
|
521
562
|
|
|
563
|
+
def _get_tag_by_name(self, name: str) -> Tag:
|
|
564
|
+
"""
|
|
565
|
+
Get a specific tag from the geometry.
|
|
566
|
+
|
|
567
|
+
Parameters
|
|
568
|
+
----------
|
|
569
|
+
name : str
|
|
570
|
+
The name of the tag.
|
|
571
|
+
|
|
572
|
+
Returns
|
|
573
|
+
-------
|
|
574
|
+
Tag
|
|
575
|
+
The tag with the specified name.
|
|
576
|
+
"""
|
|
577
|
+
try:
|
|
578
|
+
return next(filter(lambda t: t.name == name, self.list_tags()))
|
|
579
|
+
except StopIteration:
|
|
580
|
+
raise ValueError(f"Tag '{name}' not found in geometry {self.id}")
|
|
581
|
+
|
|
522
582
|
def list_entities(self) -> tuple[list[Surface], list[Volume]]:
|
|
523
583
|
"""
|
|
524
584
|
List all the entities in the geometry.
|
|
@@ -31,6 +31,10 @@ class GeometryVersion(ProtoWrapperBase):
|
|
|
31
31
|
"""
|
|
32
32
|
return timestamp_to_datetime(self._proto.create_time)
|
|
33
33
|
|
|
34
|
+
@property
|
|
35
|
+
def url(self) -> str:
|
|
36
|
+
return f"{self.geometry().url}/version/{self.id}"
|
|
37
|
+
|
|
34
38
|
def geometry(self) -> Geometry:
|
|
35
39
|
"""
|
|
36
40
|
Get the parent geometry.
|
luminarycloud/mesh.py
CHANGED
|
@@ -38,6 +38,10 @@ class Mesh(ProtoWrapperBase):
|
|
|
38
38
|
def create_time(self) -> datetime:
|
|
39
39
|
return timestamp_to_datetime(self._proto.create_time)
|
|
40
40
|
|
|
41
|
+
@property
|
|
42
|
+
def url(self) -> str:
|
|
43
|
+
return f"{self.project().url}/mesh/{self.id}"
|
|
44
|
+
|
|
41
45
|
def project(self) -> "Project":
|
|
42
46
|
"""
|
|
43
47
|
Get the project this mesh belongs to.
|
|
@@ -16,11 +16,10 @@ from ..params.geometry import (
|
|
|
16
16
|
)
|
|
17
17
|
from ..types import Vector3
|
|
18
18
|
from .sizing_strategy import MaxCount, Minimal, MinimalCount, SizingStrategy, TargetCount
|
|
19
|
-
from ..pipeline_util.dictable import PipelineDictable
|
|
20
19
|
|
|
21
20
|
|
|
22
21
|
@dataclass(kw_only=True)
|
|
23
|
-
class VolumeMeshingParams
|
|
22
|
+
class VolumeMeshingParams:
|
|
24
23
|
"""Volume meshing parameters."""
|
|
25
24
|
|
|
26
25
|
volumes: list[Volume]
|
|
@@ -39,7 +38,7 @@ class VolumeMeshingParams(PipelineDictable):
|
|
|
39
38
|
|
|
40
39
|
|
|
41
40
|
@dataclass(kw_only=True)
|
|
42
|
-
class ModelMeshingParams
|
|
41
|
+
class ModelMeshingParams:
|
|
43
42
|
"""Model meshing parameters."""
|
|
44
43
|
|
|
45
44
|
surfaces: Sequence[Surface | str]
|
|
@@ -62,7 +61,7 @@ class ModelMeshingParams(PipelineDictable):
|
|
|
62
61
|
|
|
63
62
|
|
|
64
63
|
@dataclass(kw_only=True)
|
|
65
|
-
class BoundaryLayerParams
|
|
64
|
+
class BoundaryLayerParams:
|
|
66
65
|
"""Boundary layer meshing parameters."""
|
|
67
66
|
|
|
68
67
|
surfaces: Sequence[Surface | str]
|
|
@@ -88,7 +87,7 @@ class BoundaryLayerParams(PipelineDictable):
|
|
|
88
87
|
|
|
89
88
|
|
|
90
89
|
@dataclass(kw_only=True)
|
|
91
|
-
class RefinementRegion
|
|
90
|
+
class RefinementRegion:
|
|
92
91
|
"""Refinement region parameters."""
|
|
93
92
|
|
|
94
93
|
name: str
|
|
@@ -108,15 +107,15 @@ class RefinementRegion(PipelineDictable):
|
|
|
108
107
|
proto.name = self.name
|
|
109
108
|
proto.h_limit = self.h_limit
|
|
110
109
|
if isinstance(self.shape, Sphere):
|
|
111
|
-
proto.sphere.
|
|
112
|
-
proto.sphere.
|
|
110
|
+
proto.sphere.center_float.CopyFrom(self.shape.center._to_base_proto())
|
|
111
|
+
proto.sphere.radius_float = self.shape.radius
|
|
113
112
|
elif isinstance(self.shape, SphereShell):
|
|
114
113
|
proto.sphere_shell.center.CopyFrom(self.shape.center._to_base_proto())
|
|
115
114
|
proto.sphere_shell.radius = self.shape.radius
|
|
116
115
|
proto.sphere_shell.radius_inner = self.shape.radius_inner
|
|
117
116
|
elif isinstance(self.shape, Cube):
|
|
118
|
-
proto.cube.
|
|
119
|
-
proto.cube.
|
|
117
|
+
proto.cube.min_float.CopyFrom(self.shape.min._to_base_proto())
|
|
118
|
+
proto.cube.max_float.CopyFrom(self.shape.max._to_base_proto())
|
|
120
119
|
elif isinstance(self.shape, OrientedCube):
|
|
121
120
|
proto.oriented_cube.min.CopyFrom(self.shape.min._to_base_proto())
|
|
122
121
|
proto.oriented_cube.max.CopyFrom(self.shape.max._to_base_proto())
|
|
@@ -124,9 +123,9 @@ class RefinementRegion(PipelineDictable):
|
|
|
124
123
|
proto.oriented_cube.x_axis.CopyFrom(self.shape.x_axis._to_base_proto())
|
|
125
124
|
proto.oriented_cube.y_axis.CopyFrom(self.shape.y_axis._to_base_proto())
|
|
126
125
|
elif isinstance(self.shape, Cylinder):
|
|
127
|
-
proto.cylinder.
|
|
128
|
-
proto.cylinder.
|
|
129
|
-
proto.cylinder.
|
|
126
|
+
proto.cylinder.start_float.CopyFrom(self.shape.start._to_base_proto())
|
|
127
|
+
proto.cylinder.end_float.CopyFrom(self.shape.end._to_base_proto())
|
|
128
|
+
proto.cylinder.radius_float = self.shape.radius
|
|
130
129
|
elif isinstance(self.shape, AnnularCylinder):
|
|
131
130
|
proto.annular_cylinder.start.CopyFrom(self.shape.start._to_base_proto())
|
|
132
131
|
proto.annular_cylinder.end.CopyFrom(self.shape.end._to_base_proto())
|
|
@@ -138,12 +137,12 @@ class RefinementRegion(PipelineDictable):
|
|
|
138
137
|
|
|
139
138
|
|
|
140
139
|
@dataclass(kw_only=True)
|
|
141
|
-
class MeshGenerationParams
|
|
140
|
+
class MeshGenerationParams:
|
|
142
141
|
"""Mesh generation parameters."""
|
|
143
142
|
|
|
144
143
|
geometry_id: str
|
|
145
144
|
"The ID of the geometry to generate a mesh for"
|
|
146
|
-
sizing_strategy: SizingStrategy = field(default_factory=
|
|
145
|
+
sizing_strategy: SizingStrategy = field(default_factory=Minimal)
|
|
147
146
|
"The sizing strategy to use"
|
|
148
147
|
|
|
149
148
|
# Defaults copied from ts/frontend/src/lib/paramDefaults/meshGenerationState.ts
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
|
|
3
3
|
from luminarycloud._helpers.warnings.deprecated import deprecated
|
|
4
|
-
from ...pipeline_util.dictable import PipelineDictable
|
|
5
4
|
|
|
6
5
|
|
|
7
6
|
@dataclass
|
|
8
|
-
class SizingStrategy
|
|
7
|
+
class SizingStrategy:
|
|
9
8
|
"""Sizing strategy parameters."""
|
|
10
9
|
|
|
11
10
|
pass
|
|
@@ -17,6 +17,7 @@ def _download_processed_solution_physics_ai( # noqa: F841
|
|
|
17
17
|
process_volume: bool = False,
|
|
18
18
|
single_precision: bool = True,
|
|
19
19
|
internal_options: Optional[Dict[str, str]] = None,
|
|
20
|
+
export_surface_groups: Optional[Dict[str, List[str]]] = None,
|
|
20
21
|
) -> tarfile.TarFile:
|
|
21
22
|
"""
|
|
22
23
|
Download solution data with physics AI processing applied.
|
|
@@ -37,6 +38,8 @@ def _download_processed_solution_physics_ai( # noqa: F841
|
|
|
37
38
|
If None, all available volume fields are included.
|
|
38
39
|
process_volume: Whether to process volume data
|
|
39
40
|
single_precision: Whether to use single precision for floating point fields
|
|
41
|
+
export_surface_groups: Dictionary mapping group names to lists of surface names.
|
|
42
|
+
Each group will be exported as an individual STL file.
|
|
40
43
|
|
|
41
44
|
Raises:
|
|
42
45
|
ValueError: If invalid field names are provided
|
|
@@ -46,6 +49,7 @@ def _download_processed_solution_physics_ai( # noqa: F841
|
|
|
46
49
|
get_default_client(),
|
|
47
50
|
solution_id,
|
|
48
51
|
exclude_surfaces=exclude_surfaces,
|
|
52
|
+
export_surface_groups=export_surface_groups,
|
|
49
53
|
fill_holes=fill_holes,
|
|
50
54
|
surface_fields_to_keep=surface_fields_to_keep,
|
|
51
55
|
volume_fields_to_keep=volume_fields_to_keep,
|
luminarycloud/pipelines/api.py
CHANGED
|
@@ -7,6 +7,7 @@ from time import time, sleep
|
|
|
7
7
|
import logging
|
|
8
8
|
|
|
9
9
|
from .arguments import PipelineArgValueType
|
|
10
|
+
from .core import Stage
|
|
10
11
|
from ..pipelines import Pipeline, PipelineArgs
|
|
11
12
|
from .._client import get_default_client
|
|
12
13
|
|
|
@@ -69,6 +70,25 @@ class PipelineRecord:
|
|
|
69
70
|
res = get_default_client().http.get(f"/rest/v0/pipelines/{self.id}/pipeline_jobs")
|
|
70
71
|
return [PipelineJobRecord.from_json(p) for p in res["data"]]
|
|
71
72
|
|
|
73
|
+
def delete(self) -> None:
|
|
74
|
+
"""
|
|
75
|
+
Delete this pipeline.
|
|
76
|
+
|
|
77
|
+
This will permanently delete the pipeline and all associated pipeline jobs.
|
|
78
|
+
This operation cannot be undone.
|
|
79
|
+
|
|
80
|
+
Raises
|
|
81
|
+
------
|
|
82
|
+
HTTPException
|
|
83
|
+
If the pipeline does not exist or if you do not have permission to delete it.
|
|
84
|
+
|
|
85
|
+
Examples
|
|
86
|
+
--------
|
|
87
|
+
>>> pipeline = pipelines.get_pipeline("pipeline-123")
|
|
88
|
+
>>> pipeline.delete()
|
|
89
|
+
"""
|
|
90
|
+
get_default_client().http.delete(f"/rest/v0/pipelines/{self.id}")
|
|
91
|
+
|
|
72
92
|
|
|
73
93
|
@dataclass
|
|
74
94
|
class PipelineJobRecord:
|
|
@@ -80,7 +100,7 @@ class PipelineJobRecord:
|
|
|
80
100
|
pipeline_id: str
|
|
81
101
|
name: str
|
|
82
102
|
description: str | None
|
|
83
|
-
status: Literal["pending", "running", "completed", "failed"]
|
|
103
|
+
status: Literal["pending", "running", "completed", "failed", "cancelled"]
|
|
84
104
|
created_at: datetime
|
|
85
105
|
updated_at: datetime
|
|
86
106
|
started_at: datetime | None
|
|
@@ -156,18 +176,37 @@ class PipelineJobRecord:
|
|
|
156
176
|
res = get_default_client().http.get(f"/rest/v0/pipeline_jobs/{self.id}/artifacts")
|
|
157
177
|
return res["data"]
|
|
158
178
|
|
|
179
|
+
def delete(self) -> None:
|
|
180
|
+
"""
|
|
181
|
+
Delete this pipeline job.
|
|
182
|
+
|
|
183
|
+
This will permanently delete the pipeline job and all associated runs and tasks.
|
|
184
|
+
This operation cannot be undone.
|
|
185
|
+
|
|
186
|
+
Raises
|
|
187
|
+
------
|
|
188
|
+
HTTPException
|
|
189
|
+
If the pipeline job does not exist or if you do not have permission to delete it.
|
|
190
|
+
|
|
191
|
+
Examples
|
|
192
|
+
--------
|
|
193
|
+
>>> pipeline_job = pipelines.get_pipeline_job("pipelinejob-123")
|
|
194
|
+
>>> pipeline_job.delete()
|
|
195
|
+
"""
|
|
196
|
+
get_default_client().http.delete(f"/rest/v0/pipeline_jobs/{self.id}")
|
|
197
|
+
|
|
159
198
|
def wait(
|
|
160
199
|
self,
|
|
161
200
|
*,
|
|
162
201
|
interval_seconds: float = 5,
|
|
163
202
|
timeout_seconds: float = float("inf"),
|
|
164
203
|
print_logs: bool = False,
|
|
165
|
-
) -> Literal["completed", "failed"]:
|
|
204
|
+
) -> Literal["completed", "failed", "cancelled"]:
|
|
166
205
|
"""
|
|
167
|
-
Wait for the pipeline job to complete or
|
|
206
|
+
Wait for the pipeline job to complete, fail, or be cancelled.
|
|
168
207
|
|
|
169
208
|
This method polls the pipeline job status at regular intervals until it reaches
|
|
170
|
-
a terminal state (completed or
|
|
209
|
+
a terminal state (completed, failed, or cancelled).
|
|
171
210
|
|
|
172
211
|
Parameters
|
|
173
212
|
----------
|
|
@@ -180,7 +219,7 @@ class PipelineJobRecord:
|
|
|
180
219
|
|
|
181
220
|
Returns
|
|
182
221
|
-------
|
|
183
|
-
Literal["completed", "failed"]
|
|
222
|
+
Literal["completed", "failed", "cancelled"]
|
|
184
223
|
The final status of the pipeline job.
|
|
185
224
|
|
|
186
225
|
Raises
|
|
@@ -216,6 +255,9 @@ class PipelineJobRecord:
|
|
|
216
255
|
elif updated_job.status == "failed":
|
|
217
256
|
logger.warning(f"Pipeline job {self.id} failed")
|
|
218
257
|
return "failed"
|
|
258
|
+
elif updated_job.status == "cancelled":
|
|
259
|
+
logger.info(f"Pipeline job {self.id} was cancelled")
|
|
260
|
+
return "cancelled"
|
|
219
261
|
|
|
220
262
|
# Check timeout
|
|
221
263
|
if time() >= deadline:
|
|
@@ -233,13 +275,53 @@ class PipelineJobRecord:
|
|
|
233
275
|
self.started_at = updated_job.started_at
|
|
234
276
|
self.completed_at = updated_job.completed_at
|
|
235
277
|
|
|
278
|
+
def get_concurrency_limits(self) -> dict[str, int]:
|
|
279
|
+
"""
|
|
280
|
+
Returns the concurrency limits for this pipeline job.
|
|
281
|
+
|
|
282
|
+
Returns
|
|
283
|
+
-------
|
|
284
|
+
dict[str, int]
|
|
285
|
+
A dictionary mapping stage IDs to their concurrency limits.
|
|
286
|
+
"""
|
|
287
|
+
res = get_default_client().http.get(f"/rest/v0/pipeline_jobs/{self.id}/concurrency_limits")
|
|
288
|
+
return {k: v["limit"] for k, v in res["data"].items()}
|
|
289
|
+
|
|
290
|
+
def set_concurrency_limits(self, limits: dict[str, int]) -> None:
|
|
291
|
+
"""
|
|
292
|
+
Sets the concurrency limits for this pipeline job.
|
|
293
|
+
|
|
294
|
+
Parameters
|
|
295
|
+
----------
|
|
296
|
+
limits : dict[str, int]
|
|
297
|
+
A dictionary mapping stage IDs to their concurrency limits.
|
|
298
|
+
"""
|
|
299
|
+
body = {k: {"limit": v} for k, v in limits.items()}
|
|
300
|
+
get_default_client().http.put(f"/rest/v0/pipeline_jobs/{self.id}/concurrency_limits", body)
|
|
301
|
+
|
|
302
|
+
def cancel(self) -> None:
|
|
303
|
+
"""Cancel this running pipeline job.
|
|
304
|
+
|
|
305
|
+
This will request cancellation of the underlying Prefect flow run. The
|
|
306
|
+
job should eventually transition to a cancelled terminal state once
|
|
307
|
+
the backend processes the cancellation.
|
|
308
|
+
|
|
309
|
+
Raises
|
|
310
|
+
------
|
|
311
|
+
HTTPError
|
|
312
|
+
If the pipeline job cannot be cancelled (e.g., not found, not
|
|
313
|
+
running, or lacks the necessary Prefect flow run ID).
|
|
314
|
+
"""
|
|
315
|
+
get_default_client().http.post(f"/rest/v0/pipeline_jobs/{self.id}/cancel", {})
|
|
316
|
+
logger.info(f"Cancelled pipeline job {self.id}")
|
|
317
|
+
|
|
236
318
|
|
|
237
319
|
@dataclass
|
|
238
320
|
class PipelineJobRunRecord:
|
|
239
321
|
pipeline_job_id: str
|
|
240
322
|
idx: int
|
|
241
323
|
arguments: list[PipelineArgValueType]
|
|
242
|
-
status: Literal["pending", "running", "completed", "failed"]
|
|
324
|
+
status: Literal["pending", "running", "completed", "failed", "cancelled"]
|
|
243
325
|
|
|
244
326
|
@classmethod
|
|
245
327
|
def from_json(cls, json: dict) -> "PipelineJobRunRecord":
|
|
@@ -347,7 +429,11 @@ def get_pipeline(id: str) -> PipelineRecord:
|
|
|
347
429
|
|
|
348
430
|
|
|
349
431
|
def create_pipeline_job(
|
|
350
|
-
pipeline_id: str,
|
|
432
|
+
pipeline_id: str,
|
|
433
|
+
args: PipelineArgs,
|
|
434
|
+
name: str,
|
|
435
|
+
description: str | None = None,
|
|
436
|
+
concurrency_limits: dict[str, int] | None = None,
|
|
351
437
|
) -> PipelineJobRecord:
|
|
352
438
|
"""
|
|
353
439
|
Create a new pipeline job.
|
|
@@ -362,6 +448,8 @@ def create_pipeline_job(
|
|
|
362
448
|
Name of the pipeline job.
|
|
363
449
|
description : str, optional
|
|
364
450
|
Description of the pipeline job.
|
|
451
|
+
concurrency_limits : dict[str, int], optional
|
|
452
|
+
A dictionary mapping stage IDs to their concurrency limits.
|
|
365
453
|
"""
|
|
366
454
|
|
|
367
455
|
arg_rows = [row.row_values for row in args.rows]
|
|
@@ -373,7 +461,10 @@ def create_pipeline_job(
|
|
|
373
461
|
}
|
|
374
462
|
|
|
375
463
|
res = get_default_client().http.post(f"/rest/v0/pipelines/{pipeline_id}/pipeline_jobs", body)
|
|
376
|
-
|
|
464
|
+
pjr = PipelineJobRecord.from_json(res["data"])
|
|
465
|
+
if concurrency_limits is not None:
|
|
466
|
+
pjr.set_concurrency_limits(concurrency_limits)
|
|
467
|
+
return pjr
|
|
377
468
|
|
|
378
469
|
|
|
379
470
|
def get_pipeline_job(id: str) -> PipelineJobRecord:
|
luminarycloud/pipelines/core.py
CHANGED
|
@@ -249,9 +249,16 @@ class Stage(Generic[TOutputs], ABC):
|
|
|
249
249
|
for name, value in self._params.items():
|
|
250
250
|
if hasattr(value, "_to_pipeline_dict"):
|
|
251
251
|
d[name], downstream_params = value._to_pipeline_dict()
|
|
252
|
+
for param in downstream_params:
|
|
253
|
+
if not isinstance(param, PipelineParameter):
|
|
254
|
+
raise ValueError(
|
|
255
|
+
f"Expected `_to_pipeline_dict()` to only return PipelineParameters, but got a {type(param)}: {param}"
|
|
256
|
+
)
|
|
252
257
|
pipeline_params.update(downstream_params)
|
|
253
258
|
else:
|
|
254
259
|
d[name] = value
|
|
260
|
+
# Strip None values. We treat absence of a param value in the YAML the same as a present null value.
|
|
261
|
+
d = {k: v for k, v in d.items() if v is not None}
|
|
255
262
|
return d, pipeline_params
|
|
256
263
|
|
|
257
264
|
def __str__(self) -> str:
|
|
@@ -285,7 +292,7 @@ class Pipeline:
|
|
|
285
292
|
def pipeline_params(self) -> set[PipelineParameter]:
|
|
286
293
|
return self._stages_dict_and_params()[1]
|
|
287
294
|
|
|
288
|
-
def
|
|
295
|
+
def get_stage_id(self, stage: Stage) -> str:
|
|
289
296
|
return self._stage_ids[stage]
|
|
290
297
|
|
|
291
298
|
def _stages_dict_and_params(self) -> tuple[dict, set[PipelineParameter]]:
|
|
@@ -361,7 +368,10 @@ class Pipeline:
|
|
|
361
368
|
for stage_id in d["tasks"]:
|
|
362
369
|
_parse_stage(d, stage_id, parsed_stages)
|
|
363
370
|
|
|
364
|
-
|
|
371
|
+
pipe = cls(list(parsed_stages.values()))
|
|
372
|
+
# Preserve the stage IDs from the YAML definition by overwriting the auto-assigned ones
|
|
373
|
+
pipe._stage_ids = {stage: stage_id for stage_id, stage in parsed_stages.items()}
|
|
374
|
+
return pipe
|
|
365
375
|
|
|
366
376
|
|
|
367
377
|
def _recursive_replace_pipeline_params(d: Any, parsed_params: dict) -> Any:
|