luminarycloud 0.16.2__py3-none-any.whl → 0.18.0__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/_auth/auth.py +23 -34
- luminarycloud/_client/client.py +23 -4
- luminarycloud/_client/retry_interceptor.py +7 -0
- luminarycloud/_helpers/_create_geometry.py +134 -34
- luminarycloud/_helpers/_wait_for_mesh.py +14 -4
- luminarycloud/_helpers/cond.py +0 -1
- luminarycloud/_proto/api/v0/luminarycloud/geometry/geometry_pb2.py +146 -123
- luminarycloud/_proto/api/v0/luminarycloud/geometry/geometry_pb2.pyi +82 -15
- luminarycloud/_proto/api/v0/luminarycloud/geometry/geometry_pb2_grpc.py +34 -0
- luminarycloud/_proto/api/v0/luminarycloud/geometry/geometry_pb2_grpc.pyi +12 -0
- luminarycloud/_proto/api/v0/luminarycloud/inference/inference_pb2.py +8 -8
- luminarycloud/_proto/api/v0/luminarycloud/inference/inference_pb2.pyi +12 -7
- luminarycloud/_proto/api/v0/luminarycloud/mesh/mesh_pb2.py +25 -3
- luminarycloud/_proto/api/v0/luminarycloud/mesh/mesh_pb2.pyi +30 -0
- luminarycloud/_proto/api/v0/luminarycloud/mesh/mesh_pb2_grpc.py +34 -0
- luminarycloud/_proto/api/v0/luminarycloud/mesh/mesh_pb2_grpc.pyi +12 -0
- luminarycloud/_proto/api/v0/luminarycloud/pipelines/pipelines_pb2.py +246 -0
- luminarycloud/_proto/api/v0/luminarycloud/pipelines/pipelines_pb2.pyi +420 -0
- luminarycloud/_proto/api/v0/luminarycloud/pipelines/pipelines_pb2_grpc.py +240 -0
- luminarycloud/_proto/api/v0/luminarycloud/pipelines/pipelines_pb2_grpc.pyi +90 -0
- luminarycloud/_proto/api/v0/luminarycloud/project/project_pb2.py +54 -3
- luminarycloud/_proto/api/v0/luminarycloud/project/project_pb2.pyi +92 -1
- luminarycloud/_proto/api/v0/luminarycloud/project/project_pb2_grpc.py +132 -0
- luminarycloud/_proto/api/v0/luminarycloud/project/project_pb2_grpc.pyi +40 -0
- luminarycloud/_proto/api/v0/luminarycloud/project_ui_state/project_ui_state_pb2.py +97 -0
- luminarycloud/_proto/api/v0/luminarycloud/project_ui_state/project_ui_state_pb2.pyi +93 -0
- luminarycloud/_proto/api/v0/luminarycloud/project_ui_state/project_ui_state_pb2_grpc.py +132 -0
- luminarycloud/_proto/api/v0/luminarycloud/project_ui_state/project_ui_state_pb2_grpc.pyi +44 -0
- luminarycloud/_proto/api/v0/luminarycloud/simulation_template/simulation_template_pb2.py +48 -26
- luminarycloud/_proto/api/v0/luminarycloud/simulation_template/simulation_template_pb2.pyi +30 -2
- luminarycloud/_proto/api/v0/luminarycloud/simulation_template/simulation_template_pb2_grpc.py +36 -0
- luminarycloud/_proto/api/v0/luminarycloud/simulation_template/simulation_template_pb2_grpc.pyi +18 -0
- luminarycloud/_proto/api/v0/luminarycloud/vis/vis_pb2.py +153 -133
- luminarycloud/_proto/api/v0/luminarycloud/vis/vis_pb2.pyi +51 -3
- luminarycloud/_proto/client/simulation_pb2.py +261 -251
- luminarycloud/_proto/client/simulation_pb2.pyi +35 -2
- luminarycloud/_proto/frontend/output/output_pb2.py +24 -24
- luminarycloud/_proto/frontend/output/output_pb2.pyi +6 -3
- luminarycloud/_proto/geometry/geometry_pb2.py +63 -63
- luminarycloud/_proto/geometry/geometry_pb2.pyi +16 -8
- luminarycloud/_proto/hexmesh/hexmesh_pb2.py +17 -4
- luminarycloud/_proto/hexmesh/hexmesh_pb2.pyi +22 -1
- luminarycloud/_proto/inferenceservice/inferenceservice_pb2.py +10 -10
- luminarycloud/_proto/inferenceservice/inferenceservice_pb2.pyi +12 -7
- luminarycloud/_proto/quantity/quantity_pb2.py +19 -19
- luminarycloud/enum/geometry_status.py +15 -8
- luminarycloud/enum/pipeline_job_status.py +23 -0
- luminarycloud/feature_modification.py +3 -6
- luminarycloud/geometry.py +25 -0
- luminarycloud/geometry_version.py +23 -0
- luminarycloud/mesh.py +16 -0
- luminarycloud/params/enum/_enum_wrappers.py +29 -0
- luminarycloud/params/simulation/physics/fluid/boundary_conditions/inlet/fan_curve_inlet_.py +1 -1
- luminarycloud/params/simulation/physics/fluid/boundary_conditions/inlet/mach_inlet_.py +5 -1
- luminarycloud/params/simulation/physics/fluid/boundary_conditions/inlet/mass_flow_inlet_.py +5 -1
- luminarycloud/params/simulation/physics/fluid/boundary_conditions/inlet/total_pressure_inlet_.py +5 -1
- luminarycloud/params/simulation/physics/fluid/boundary_conditions/inlet/velocity_magnitude_inlet_.py +5 -1
- luminarycloud/physics_ai/inference.py +46 -30
- luminarycloud/pipelines/__init__.py +7 -0
- luminarycloud/pipelines/api.py +213 -0
- luminarycloud/project.py +98 -11
- luminarycloud/simulation_template.py +15 -6
- luminarycloud/vis/__init__.py +6 -0
- luminarycloud/vis/data_extraction.py +201 -31
- luminarycloud/vis/filters.py +94 -35
- luminarycloud/vis/interactive_inference.py +153 -0
- luminarycloud/vis/interactive_scene.py +35 -16
- luminarycloud/vis/primitives.py +87 -1
- luminarycloud/vis/visualization.py +50 -6
- luminarycloud/volume_selection.py +3 -6
- {luminarycloud-0.16.2.dist-info → luminarycloud-0.18.0.dist-info}/METADATA +18 -18
- {luminarycloud-0.16.2.dist-info → luminarycloud-0.18.0.dist-info}/RECORD +73 -62
- {luminarycloud-0.16.2.dist-info → luminarycloud-0.18.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Copyright 2025 Luminary Cloud, Inc. All Rights Reserved.
|
|
2
|
+
import json
|
|
3
|
+
from .._proto.api.v0.luminarycloud.vis import vis_pb2
|
|
4
|
+
from .display import DisplayAttributes
|
|
5
|
+
from luminarycloud.enum.vis_enums import SceneMode, VisQuantity, Representation, FieldComponent
|
|
6
|
+
from luminarycloud.vis import Field
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
import luminarycloud.enum.quantity_type as quantity_type
|
|
9
|
+
from .interactive_scene import InteractiveScene
|
|
10
|
+
|
|
11
|
+
from .._proto.api.v0.luminarycloud.vis import vis_pb2
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from .visualization import Scene, LookAtCamera, DirectionalCamera, ColorMap
|
|
15
|
+
from ..geometry import Geometry
|
|
16
|
+
from ..mesh import Mesh
|
|
17
|
+
from ..solution import Solution
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
import luminarycloud_jupyter as lcj
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from luminarycloud_jupyter import InteractiveLCVisWidget, LCVisPlaneWidget
|
|
24
|
+
except ImportError:
|
|
25
|
+
lcj = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
_SOURCE_FILTER_ID = "___LC_SOURCE_FILTER___"
|
|
29
|
+
|
|
30
|
+
# Global workspace state configuration
|
|
31
|
+
# We use this to provide a constructed context to the LCVis widget so that we can visualize
|
|
32
|
+
# the surface inference results.
|
|
33
|
+
WORKSPACE_STATE_CONFIG = {
|
|
34
|
+
"connections": {_SOURCE_FILTER_ID: []},
|
|
35
|
+
"filters": [
|
|
36
|
+
{
|
|
37
|
+
"id": _SOURCE_FILTER_ID,
|
|
38
|
+
"name": "LcMeshSource",
|
|
39
|
+
"params": {
|
|
40
|
+
"fvm_params": "",
|
|
41
|
+
"url": "dummy",
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"workspace_params": {"edge_mode": "boundary"},
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class InteractiveInference(InteractiveScene):
|
|
50
|
+
"""
|
|
51
|
+
The InteractiveInference acts as the bridge between the RenderData and
|
|
52
|
+
the Jupyter widget, handling checking if we have the widget package
|
|
53
|
+
before passing calls to the widget to handle it being an optional
|
|
54
|
+
dependency
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(self, signed_url: str, mode: SceneMode = SceneMode.INLINE) -> None:
|
|
58
|
+
# Initialize the widget directly without calling parent constructor
|
|
59
|
+
# since we don't have a real Scene object and override set_scene anyway
|
|
60
|
+
if not lcj:
|
|
61
|
+
raise ImportError(
|
|
62
|
+
"Interactive visualization requires luminarycloud[jupyter] to be installed"
|
|
63
|
+
)
|
|
64
|
+
self.widget = lcj.LCVisWidget(scene_mode=mode)
|
|
65
|
+
self._scene = None # Not used in InteractiveInference
|
|
66
|
+
|
|
67
|
+
# Do inference-specific setup
|
|
68
|
+
self.set_signed_url(signed_url, False)
|
|
69
|
+
# Known quantities written by inference
|
|
70
|
+
self._valid_quantities = [
|
|
71
|
+
VisQuantity.NONE,
|
|
72
|
+
VisQuantity.PRESSURE,
|
|
73
|
+
VisQuantity.WALL_SHEAR_STRESS,
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
def set_signed_url(self, signed_url: str, isComparator: bool) -> None:
|
|
77
|
+
# TODO(matt): we could make isCompartor an index so we could compare
|
|
78
|
+
# more than two scenes at once.
|
|
79
|
+
|
|
80
|
+
# Import here to avoid circular import issue
|
|
81
|
+
from .visualization import LookAtCamera
|
|
82
|
+
|
|
83
|
+
WORKSPACE_STATE_CONFIG["filters"][0]["params"]["url"] = signed_url
|
|
84
|
+
resp = vis_pb2.GetRenderDataUrlsResponse()
|
|
85
|
+
resp.urls.filter_ids.append(_SOURCE_FILTER_ID)
|
|
86
|
+
file = resp.urls.data_files.add()
|
|
87
|
+
file.signed_url = signed_url
|
|
88
|
+
resp.urls.data_files.append(file)
|
|
89
|
+
resp.workspace_state = json.dumps(WORKSPACE_STATE_CONFIG)
|
|
90
|
+
|
|
91
|
+
self.widget.set_workspace_state(resp, isComparator)
|
|
92
|
+
|
|
93
|
+
self.reset_camera()
|
|
94
|
+
self.widget.set_flat_shading(True)
|
|
95
|
+
|
|
96
|
+
def get_field_range(self, quantity: VisQuantity) -> list[float]:
|
|
97
|
+
"""
|
|
98
|
+
Get the field range for a given quantity.
|
|
99
|
+
"""
|
|
100
|
+
if quantity not in self._valid_quantities:
|
|
101
|
+
raise ValueError(
|
|
102
|
+
f"Invalid field quantity: {quantity}. Valid quantities are: {self._valid_quantities}"
|
|
103
|
+
)
|
|
104
|
+
return self.widget.get_field_range(quantity)
|
|
105
|
+
|
|
106
|
+
def set_display_field(self, field: Field) -> None:
|
|
107
|
+
if field.quantity not in self._valid_quantities:
|
|
108
|
+
raise ValueError(
|
|
109
|
+
f"Invalid field quantity: {field.quantity}. Valid quantities are: {self._valid_quantities}"
|
|
110
|
+
)
|
|
111
|
+
if not quantity_type._is_vector(field.quantity):
|
|
112
|
+
# We normally handle this on the backend, but we are sending this directly
|
|
113
|
+
# to lcvis, so we need to make sure scalars are set to the X component
|
|
114
|
+
field.component = FieldComponent.X
|
|
115
|
+
attrs = DisplayAttributes()
|
|
116
|
+
attrs.field = field
|
|
117
|
+
attrs.visible = True
|
|
118
|
+
attrs.representation = Representation.SURFACE
|
|
119
|
+
self.widget.set_display_attributes(_SOURCE_FILTER_ID, attrs)
|
|
120
|
+
|
|
121
|
+
def get_field_ranges(self) -> dict[str, list[float]]:
|
|
122
|
+
"""
|
|
123
|
+
Get the field ranges from the widget.
|
|
124
|
+
"""
|
|
125
|
+
return self.widget.field_data_map
|
|
126
|
+
|
|
127
|
+
def set_color_map(self, color_map: "ColorMap") -> None:
|
|
128
|
+
if color_map.field.quantity not in self._valid_quantities:
|
|
129
|
+
raise ValueError(
|
|
130
|
+
f"Invalid field quantity: {color_map.field.quantity}. Valid quantities are: {self._valid_quantities}"
|
|
131
|
+
)
|
|
132
|
+
if not quantity_type._is_vector(color_map.field.quantity):
|
|
133
|
+
# We normally handle this on the backend, but we are sending this directly
|
|
134
|
+
# to lcvis, so we need to make sure scalars are set to the X component
|
|
135
|
+
color_map.field.component = FieldComponent.X
|
|
136
|
+
super().set_color_map(color_map)
|
|
137
|
+
|
|
138
|
+
def set_scene(self, scene: "Scene", isComparator: bool = False) -> None:
|
|
139
|
+
"""
|
|
140
|
+
InteractiveInference does not support setting scenes.
|
|
141
|
+
Use set_signed_url() instead to load inference data.
|
|
142
|
+
"""
|
|
143
|
+
raise NotImplementedError(
|
|
144
|
+
"InteractiveInference does not support set_scene(). Use set_signed_url() to load inference data."
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def compare(self, entity) -> None:
|
|
148
|
+
"""
|
|
149
|
+
InteractiveInference does not support scene comparison.
|
|
150
|
+
"""
|
|
151
|
+
raise NotImplementedError(
|
|
152
|
+
"InteractiveInference does not support compare(). This feature is only available for InteractiveScene."
|
|
153
|
+
)
|
|
@@ -8,13 +8,16 @@ from typing import TYPE_CHECKING, cast, Union
|
|
|
8
8
|
from .._proto.api.v0.luminarycloud.vis import vis_pb2
|
|
9
9
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
11
|
-
from .visualization import Scene, LookAtCamera, ColorMap
|
|
11
|
+
from .visualization import Scene, LookAtCamera, DirectionalCamera, ColorMap
|
|
12
12
|
from ..geometry import Geometry
|
|
13
13
|
from ..mesh import Mesh
|
|
14
14
|
from ..solution import Solution
|
|
15
15
|
|
|
16
16
|
try:
|
|
17
17
|
import luminarycloud_jupyter as lcj
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from luminarycloud_jupyter import InteractiveLCVisWidget, LCVisPlaneWidget
|
|
18
21
|
except ImportError:
|
|
19
22
|
lcj = None
|
|
20
23
|
|
|
@@ -105,7 +108,7 @@ class InteractiveScene:
|
|
|
105
108
|
# is moved there. Matt: this would be a lot of work. The current vis service
|
|
106
109
|
# call used in the backend doesn't use the streaming version. Further, there is
|
|
107
110
|
# a lot of extra code to manage the streaming callbacks.
|
|
108
|
-
self.widget.set_workspace_state(
|
|
111
|
+
self.widget.set_workspace_state(resp, isComparator)
|
|
109
112
|
|
|
110
113
|
# Sync display attributes and visibilities for surfaces
|
|
111
114
|
self.set_display_attributes(_SOURCE_FILTER_ID, scene.global_display_attrs)
|
|
@@ -115,21 +118,16 @@ class InteractiveScene:
|
|
|
115
118
|
for f in scene._filters:
|
|
116
119
|
self.set_display_attributes(f.id, f.display_attrs)
|
|
117
120
|
|
|
118
|
-
# Find and apply the first look at camera, if any, in the scene
|
|
119
|
-
set_camera = False
|
|
120
|
-
for c in scene._cameras:
|
|
121
|
-
if not isinstance(c, LookAtCamera):
|
|
122
|
-
continue
|
|
123
|
-
set_camera = True
|
|
124
|
-
self.set_camera(c)
|
|
125
|
-
|
|
126
121
|
# Set any color maps we have. TODO(matt): only a few attributes are connected atm.
|
|
127
122
|
for color_map in scene._color_maps:
|
|
128
123
|
self.set_color_map(color_map)
|
|
129
124
|
|
|
125
|
+
# Apply the first camera, if any, in the scene
|
|
130
126
|
# If we don't have an initial camera to use, reset the camera after loading
|
|
131
127
|
# the workspace state
|
|
132
|
-
if
|
|
128
|
+
if len(scene._cameras) > 0:
|
|
129
|
+
self.set_camera(scene._cameras[0])
|
|
130
|
+
else:
|
|
133
131
|
self.reset_camera()
|
|
134
132
|
|
|
135
133
|
def set_surface_visibility(self, surface_id: str, visible: bool) -> None:
|
|
@@ -144,11 +142,26 @@ class InteractiveScene:
|
|
|
144
142
|
def reset_camera(self) -> None:
|
|
145
143
|
self.widget.reset_camera()
|
|
146
144
|
|
|
147
|
-
def set_camera(self, camera: "LookAtCamera") -> None:
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
145
|
+
def set_camera(self, camera: "LookAtCamera | DirectionalCamera") -> None:
|
|
146
|
+
# Import here to avoid circular import issue
|
|
147
|
+
from .visualization import LookAtCamera
|
|
148
|
+
|
|
149
|
+
# Clear any prev camera state
|
|
150
|
+
self.widget.camera_position = []
|
|
151
|
+
self.widget.camera_look_at = []
|
|
152
|
+
self.widget.camera_up = []
|
|
153
|
+
self.widget.camera_pan = []
|
|
154
|
+
if isinstance(camera, LookAtCamera):
|
|
155
|
+
self.widget.camera_position = [
|
|
156
|
+
camera.position[0],
|
|
157
|
+
camera.position[1],
|
|
158
|
+
camera.position[2],
|
|
159
|
+
]
|
|
160
|
+
self.widget.camera_look_at = [camera.look_at[0], camera.look_at[1], camera.look_at[2]]
|
|
161
|
+
self.widget.camera_up = [camera.up[0], camera.up[1], camera.up[2]]
|
|
162
|
+
self.widget.camera_pan = [camera.pan_x, camera.pan_y, 0]
|
|
163
|
+
else:
|
|
164
|
+
self.widget.set_camera_orientation(camera.direction)
|
|
152
165
|
|
|
153
166
|
def set_color_map(self, color_map: "ColorMap") -> None:
|
|
154
167
|
self.widget.set_color_map(color_map)
|
|
@@ -170,6 +183,12 @@ class InteractiveScene:
|
|
|
170
183
|
def set_triad_visible(self, visible: bool) -> None:
|
|
171
184
|
self.widget.set_triad_visible(visible)
|
|
172
185
|
|
|
186
|
+
def add_plane_widget(self) -> "LCVisPlaneWidget":
|
|
187
|
+
return self.widget.add_plane_widget()
|
|
188
|
+
|
|
189
|
+
def delete_widget(self, widget: "InteractiveLCVisWidget") -> None:
|
|
190
|
+
self.widget.delete_widget(widget)
|
|
191
|
+
|
|
173
192
|
def compare(self, entity: Union["Geometry", "Mesh", "Solution"]) -> None:
|
|
174
193
|
# The entity can be a Geometry, Mesh, or Solution and is checked by the
|
|
175
194
|
# clone method. This can raise error if the scenes are incompatiable.
|
luminarycloud/vis/primitives.py
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
import dataclasses as dc
|
|
3
3
|
from luminarycloud.types import Vector3, Vector3Like
|
|
4
4
|
from .._helpers._code_representation import CodeRepr
|
|
5
|
+
from .._proto.api.v0.luminarycloud.vis import vis_pb2
|
|
6
|
+
from ..types.vector3 import _to_vector3
|
|
7
|
+
import math
|
|
5
8
|
|
|
6
9
|
|
|
7
10
|
@dc.dataclass
|
|
@@ -18,11 +21,26 @@ class Plane(CodeRepr):
|
|
|
18
21
|
normal: Vector3Like = dc.field(default_factory=lambda: Vector3(x=1, y=0, z=0))
|
|
19
22
|
"""The vector orthogonal to the plane. Default: [0,1,0]"""
|
|
20
23
|
|
|
24
|
+
def _to_proto(self) -> vis_pb2.Plane:
|
|
25
|
+
plane = vis_pb2.Plane()
|
|
26
|
+
plane.origin.CopyFrom(_to_vector3(self.origin)._to_proto())
|
|
27
|
+
plane.normal.CopyFrom(_to_vector3(self.normal)._to_proto())
|
|
28
|
+
return plane
|
|
29
|
+
|
|
30
|
+
def _from_proto(self, proto: vis_pb2.Plane) -> None:
|
|
31
|
+
self.origin = Vector3()
|
|
32
|
+
self.origin._from_proto(proto.origin)
|
|
33
|
+
self.normal = Vector3()
|
|
34
|
+
self.normal._from_proto(proto.normal)
|
|
35
|
+
|
|
36
|
+
def __repr__(self) -> str:
|
|
37
|
+
return self._to_code_helper(obj_name="plane")
|
|
38
|
+
|
|
21
39
|
|
|
22
40
|
@dc.dataclass
|
|
23
41
|
class Box(CodeRepr):
|
|
24
42
|
"""
|
|
25
|
-
This class defines a box used for
|
|
43
|
+
This class defines a box used for filters such as box clip.
|
|
26
44
|
|
|
27
45
|
.. warning:: This feature is experimental and may change or be removed in the future.
|
|
28
46
|
|
|
@@ -37,3 +55,71 @@ class Box(CodeRepr):
|
|
|
37
55
|
The rotation of the box specified in Euler angles (degrees) and applied
|
|
38
56
|
in XYZ ordering. Default: [0,0,0]
|
|
39
57
|
"""
|
|
58
|
+
|
|
59
|
+
def _to_proto(self) -> vis_pb2.Box:
|
|
60
|
+
box = vis_pb2.Box()
|
|
61
|
+
box.center.CopyFrom(_to_vector3(self.center)._to_proto())
|
|
62
|
+
box.lengths.CopyFrom(_to_vector3(self.lengths)._to_proto())
|
|
63
|
+
# The api interface is in degrees but the backend needs radians
|
|
64
|
+
radians = _to_vector3(self.angles)
|
|
65
|
+
radians.x = radians.x * (math.pi / 180)
|
|
66
|
+
radians.y = radians.y * (math.pi / 180)
|
|
67
|
+
radians.z = radians.z * (math.pi / 180)
|
|
68
|
+
box.angles.CopyFrom(radians._to_proto())
|
|
69
|
+
return box
|
|
70
|
+
|
|
71
|
+
def _from_proto(self, proto: vis_pb2.Box) -> None:
|
|
72
|
+
center = Vector3()
|
|
73
|
+
center._from_proto(proto.center)
|
|
74
|
+
self.center = center
|
|
75
|
+
lengths = Vector3()
|
|
76
|
+
lengths._from_proto(proto.lengths)
|
|
77
|
+
self.lengths = lengths
|
|
78
|
+
self.angles = Vector3()
|
|
79
|
+
# Backend units are radians, convert back to degrees
|
|
80
|
+
self.angles.x = proto.angles.x * (180 / math.pi)
|
|
81
|
+
self.angles.y = proto.angles.y * (180 / math.pi)
|
|
82
|
+
self.angles.z = proto.angles.z * (180 / math.pi)
|
|
83
|
+
|
|
84
|
+
def __repr__(self) -> str:
|
|
85
|
+
return self._to_code_helper(obj_name="box")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dc.dataclass
|
|
89
|
+
class AABB(CodeRepr):
|
|
90
|
+
"""
|
|
91
|
+
This class defines an axis-aligned bounding box used for filters such as SurfaceLICPlane.
|
|
92
|
+
|
|
93
|
+
.. warning:: This feature is experimental and may change or be removed in the future.
|
|
94
|
+
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
min: Vector3Like = dc.field(default_factory=lambda: Vector3(x=0, y=0, z=0))
|
|
98
|
+
"""The min point of the axis-aligned box. Default: [0,0,0]."""
|
|
99
|
+
max: Vector3Like = dc.field(default_factory=lambda: Vector3(x=1, y=1, z=1))
|
|
100
|
+
"""The max point of the axis-aligned box. Default: [1,1,1]."""
|
|
101
|
+
|
|
102
|
+
def is_valid(self) -> bool:
|
|
103
|
+
"""
|
|
104
|
+
Check if the AABB is valid. An AABB is valid if min is less than max in all dimensions.
|
|
105
|
+
"""
|
|
106
|
+
min = _to_vector3(self.min)
|
|
107
|
+
max = _to_vector3(self.max)
|
|
108
|
+
return min.x <= max.x and min.y <= max.y and min.z <= max.z
|
|
109
|
+
|
|
110
|
+
def _to_proto(self) -> vis_pb2.AABB:
|
|
111
|
+
aabb = vis_pb2.AABB()
|
|
112
|
+
aabb.min.CopyFrom(_to_vector3(self.min)._to_proto())
|
|
113
|
+
aabb.max.CopyFrom(_to_vector3(self.max)._to_proto())
|
|
114
|
+
return aabb
|
|
115
|
+
|
|
116
|
+
def _from_proto(self, proto: vis_pb2.AABB) -> None:
|
|
117
|
+
min_point = Vector3()
|
|
118
|
+
min_point._from_proto(proto.min)
|
|
119
|
+
self.min = min_point
|
|
120
|
+
max_point = Vector3()
|
|
121
|
+
max_point._from_proto(proto.max)
|
|
122
|
+
self.max = max_point
|
|
123
|
+
|
|
124
|
+
def __repr__(self) -> str:
|
|
125
|
+
return self._to_code_helper(obj_name="aabb")
|
|
@@ -99,6 +99,9 @@ class DirectionalCamera(CodeRepr):
|
|
|
99
99
|
self.height = static_ani.resolution.height
|
|
100
100
|
self.projection = CameraProjection(static_ani.camera.projection)
|
|
101
101
|
|
|
102
|
+
def __repr__(self) -> str:
|
|
103
|
+
return self._to_code_helper(obj_name="camera")
|
|
104
|
+
|
|
102
105
|
|
|
103
106
|
@dc.dataclass
|
|
104
107
|
class LookAtCamera(CodeRepr):
|
|
@@ -158,6 +161,9 @@ class LookAtCamera(CodeRepr):
|
|
|
158
161
|
self.height = static_ani.resolution.height
|
|
159
162
|
self.projection = CameraProjection(static_ani.camera.projection)
|
|
160
163
|
|
|
164
|
+
def __repr__(self) -> str:
|
|
165
|
+
return self._to_code_helper(obj_name="camera")
|
|
166
|
+
|
|
161
167
|
|
|
162
168
|
class RenderOutput:
|
|
163
169
|
"""
|
|
@@ -855,7 +861,9 @@ class Scene(CodeRepr):
|
|
|
855
861
|
raise ValueError(f"Scene.clone: id {id} not present in tags or surface ids")
|
|
856
862
|
return cloned
|
|
857
863
|
|
|
858
|
-
def to_code(
|
|
864
|
+
def to_code(
|
|
865
|
+
self, obj_name: str, hide_defaults: bool = True, clean_color_maps: bool = True
|
|
866
|
+
) -> str:
|
|
859
867
|
"""
|
|
860
868
|
This function will produce a code string that reproduces the scene
|
|
861
869
|
in its current state.
|
|
@@ -865,7 +873,12 @@ class Scene(CodeRepr):
|
|
|
865
873
|
obj_name: str
|
|
866
874
|
the object name of the scene.
|
|
867
875
|
hide_defaults: bool, optional
|
|
868
|
-
If True, the code will make a best effort not include default values
|
|
876
|
+
If True, the code will make a best effort not to include default values
|
|
877
|
+
for attributes. Default: True
|
|
878
|
+
clean_color_maps: bool, optional
|
|
879
|
+
If True, the code will not include color maps that are not used in
|
|
880
|
+
the scene. Additionally, this will remove the appearance so they are
|
|
881
|
+
automatically placed in the image. Default: True.
|
|
869
882
|
"""
|
|
870
883
|
imports = "import luminarycloud as lc\n"
|
|
871
884
|
imports += "import luminarycloud.vis as vis\n"
|
|
@@ -876,6 +889,7 @@ class Scene(CodeRepr):
|
|
|
876
889
|
imports += " CameraProjection,\n"
|
|
877
890
|
imports += " CameraDirection,\n"
|
|
878
891
|
imports += " RenderStatusType,\n"
|
|
892
|
+
imports += " ExtractStatusType,\n"
|
|
879
893
|
imports += " Representation,\n"
|
|
880
894
|
imports += " VisQuantity,\n"
|
|
881
895
|
imports += " StreamlineDirection,\n"
|
|
@@ -967,15 +981,28 @@ class Scene(CodeRepr):
|
|
|
967
981
|
if filter._parent_id != "":
|
|
968
982
|
code += f"{ids_to_obj_name[filter.id]}.set_parent({ids_to_obj_name[filter._parent_id]})\n"
|
|
969
983
|
code += "\n"
|
|
984
|
+
cmap_code = ""
|
|
970
985
|
if len(self._color_maps) != 0:
|
|
971
986
|
cmap_count = 0
|
|
972
|
-
|
|
987
|
+
cmap_code += "# Add color maps\n"
|
|
973
988
|
for color_map in self._color_maps:
|
|
989
|
+
if clean_color_maps and color_map.appearance is not None:
|
|
990
|
+
# When using UI code gen, the UI state contains color maps
|
|
991
|
+
# for every variable including ones that are not visible. We
|
|
992
|
+
# optionally clean them up here.
|
|
993
|
+
if not color_map.appearance.visible:
|
|
994
|
+
# If cleaning up color maps, don't include ones that are not visible.
|
|
995
|
+
continue
|
|
996
|
+
# Remove the appearance so the auto-placement logic takes over.
|
|
997
|
+
color_map.appearance = None
|
|
998
|
+
|
|
974
999
|
cmap_name = f"color_map{cmap_count}"
|
|
975
|
-
|
|
976
|
-
|
|
1000
|
+
cmap_code += color_map._to_code_helper(cmap_name, hide_defaults=hide_defaults)
|
|
1001
|
+
cmap_code += f"scene.add_color_map({cmap_name})\n"
|
|
977
1002
|
cmap_count += 1
|
|
978
|
-
|
|
1003
|
+
cmap_code += "\n"
|
|
1004
|
+
if cmap_count > 0:
|
|
1005
|
+
code += cmap_code
|
|
979
1006
|
|
|
980
1007
|
imports += "\n"
|
|
981
1008
|
# The code gen is very verbose, so we can do some string replacements
|
|
@@ -1005,6 +1032,15 @@ class Scene(CodeRepr):
|
|
|
1005
1032
|
]
|
|
1006
1033
|
code = "\n".join(filtered_lines)
|
|
1007
1034
|
|
|
1035
|
+
# Now add the methods to render and save the images.
|
|
1036
|
+
code += "\n"
|
|
1037
|
+
code += "render_output = scene.render_images(name='my image', description='Longer description')\n"
|
|
1038
|
+
code += "status = render_output.wait()\n"
|
|
1039
|
+
code += "if status == RenderStatusType.COMPLETED:\n"
|
|
1040
|
+
code += " render_output.save_images('image_prefix')\n"
|
|
1041
|
+
code += "else:\n"
|
|
1042
|
+
code += " print('Rendering failed', status)\n"
|
|
1043
|
+
|
|
1008
1044
|
return imports + code
|
|
1009
1045
|
|
|
1010
1046
|
|
|
@@ -1114,6 +1150,11 @@ def _spec_to_scene(spec: vis_pb2.ExtractSpec) -> Scene:
|
|
|
1114
1150
|
When we get the extract back, we don't know if it was auto-generated or not.
|
|
1115
1151
|
Thus, the resulting color maps will be much more verbose than the original.
|
|
1116
1152
|
"""
|
|
1153
|
+
|
|
1154
|
+
# SDK code gen from the UI will not have this set and could be a mix of data
|
|
1155
|
+
# extracts and scene filters. We will skip processing data extracts in this
|
|
1156
|
+
# code and do a best effort. In the UI, they still have a scene and we need to
|
|
1157
|
+
# produce it.
|
|
1117
1158
|
if spec.data_only:
|
|
1118
1159
|
raise ValueError("Error: cannot reconstruct a scene from a data only extract")
|
|
1119
1160
|
|
|
@@ -1180,6 +1221,9 @@ def _spec_to_scene(spec: vis_pb2.ExtractSpec) -> Scene:
|
|
|
1180
1221
|
pfilter = Threshold("")
|
|
1181
1222
|
elif typ == "contour":
|
|
1182
1223
|
pfilter = Isosurface("")
|
|
1224
|
+
elif typ == "line_sample" or typ == "intersection_curve":
|
|
1225
|
+
# Theses are data extracts and will be handled separately.
|
|
1226
|
+
continue
|
|
1183
1227
|
else:
|
|
1184
1228
|
raise ValueError(f"Error: unknown filter type {typ}")
|
|
1185
1229
|
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
|
|
4
4
|
import logging
|
|
5
5
|
from os import PathLike
|
|
6
|
-
from typing import Iterable, Iterator, Optional
|
|
6
|
+
from typing import TYPE_CHECKING, Iterable, Iterator, Optional
|
|
7
7
|
from uuid import uuid4
|
|
8
8
|
|
|
9
9
|
from luminarycloud._helpers import util
|
|
@@ -12,6 +12,7 @@ from luminarycloud._proto.api.v0.luminarycloud.geometry import (
|
|
|
12
12
|
geometry_pb2 as geometrypb,
|
|
13
13
|
)
|
|
14
14
|
from luminarycloud._proto.upload import upload_pb2 as uploadpb
|
|
15
|
+
from luminarycloud.types.adfloat import _from_ad_proto, _to_ad_proto
|
|
15
16
|
|
|
16
17
|
from ._client import get_default_client
|
|
17
18
|
from ._proto.base import base_pb2 as basepb
|
|
@@ -124,7 +125,6 @@ class VolumeSelection:
|
|
|
124
125
|
cad_file_path: PathLike,
|
|
125
126
|
*,
|
|
126
127
|
scaling: float = 1.0,
|
|
127
|
-
force_discrete: bool = False,
|
|
128
128
|
feature_name: str = "Import CAD",
|
|
129
129
|
) -> None:
|
|
130
130
|
"""
|
|
@@ -136,8 +136,6 @@ class VolumeSelection:
|
|
|
136
136
|
The path to the CAD file to import.
|
|
137
137
|
scaling : float
|
|
138
138
|
The scaling factor to apply to the CAD file.
|
|
139
|
-
force_discrete : bool
|
|
140
|
-
Whether to force the CAD file to be imported as a discrete mesh.
|
|
141
139
|
feature_name : str
|
|
142
140
|
The name of the feature. This is not a tag name.
|
|
143
141
|
"""
|
|
@@ -160,8 +158,7 @@ class VolumeSelection:
|
|
|
160
158
|
feature.import_geometry.CopyFrom(
|
|
161
159
|
gpb.ImportGeometry(
|
|
162
160
|
geometry_url=cad_url,
|
|
163
|
-
scaling=scaling,
|
|
164
|
-
force_discrete=force_discrete,
|
|
161
|
+
scaling=_to_ad_proto(scaling),
|
|
165
162
|
)
|
|
166
163
|
)
|
|
167
164
|
self.__create_feature(feature)
|
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: luminarycloud
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.18.0
|
|
4
4
|
Summary: Luminary Cloud SDK
|
|
5
5
|
Project-URL: Homepage, https://www.luminarycloud.com/
|
|
6
6
|
Project-URL: Documentation, https://app.luminarycloud.com/docs/api/
|
|
7
7
|
Author-email: "Luminary Cloud Inc." <support@luminarycloud.com>
|
|
8
8
|
Keywords: Luminary Cloud,SDK
|
|
9
9
|
Requires-Python: >=3.10
|
|
10
|
-
Requires-Dist: google-crc32c~=1.
|
|
11
|
-
Requires-Dist: googleapis-common-protos~=1.
|
|
12
|
-
Requires-Dist: grpcio-status~=1.
|
|
13
|
-
Requires-Dist: grpcio-tools~=1.
|
|
14
|
-
Requires-Dist: grpcio~=1.
|
|
15
|
-
Requires-Dist: importlib-metadata~=
|
|
10
|
+
Requires-Dist: google-crc32c~=1.7
|
|
11
|
+
Requires-Dist: googleapis-common-protos~=1.70
|
|
12
|
+
Requires-Dist: grpcio-status~=1.65
|
|
13
|
+
Requires-Dist: grpcio-tools~=1.65
|
|
14
|
+
Requires-Dist: grpcio~=1.65
|
|
15
|
+
Requires-Dist: importlib-metadata~=8.7
|
|
16
16
|
Requires-Dist: opentelemetry-api~=1.25
|
|
17
17
|
Requires-Dist: opentelemetry-exporter-otlp-proto-common~=1.25
|
|
18
18
|
Requires-Dist: opentelemetry-exporter-otlp-proto-http~=1.25
|
|
19
|
-
Requires-Dist: opentelemetry-instrumentation-grpc~=0.
|
|
20
|
-
Requires-Dist: opentelemetry-instrumentation~=0.
|
|
19
|
+
Requires-Dist: opentelemetry-instrumentation-grpc~=0.55b1
|
|
20
|
+
Requires-Dist: opentelemetry-instrumentation~=0.55b1
|
|
21
21
|
Requires-Dist: opentelemetry-proto~=1.25
|
|
22
22
|
Requires-Dist: opentelemetry-sdk~=1.25
|
|
23
|
-
Requires-Dist: opentelemetry-semantic-conventions~=0.
|
|
24
|
-
Requires-Dist: protobuf~=
|
|
25
|
-
Requires-Dist: pyjwt~=2.
|
|
26
|
-
Requires-Dist: python-dotenv~=1.
|
|
27
|
-
Requires-Dist: pyyaml~=6.0
|
|
28
|
-
Requires-Dist: requests~=2.
|
|
29
|
-
Requires-Dist: waitress~=3.0
|
|
30
|
-
Requires-Dist: werkzeug~=3.
|
|
31
|
-
Requires-Dist: zstandard
|
|
23
|
+
Requires-Dist: opentelemetry-semantic-conventions~=0.55b1
|
|
24
|
+
Requires-Dist: protobuf~=5.29
|
|
25
|
+
Requires-Dist: pyjwt~=2.10
|
|
26
|
+
Requires-Dist: python-dotenv~=1.1
|
|
27
|
+
Requires-Dist: pyyaml~=6.0
|
|
28
|
+
Requires-Dist: requests~=2.32
|
|
29
|
+
Requires-Dist: waitress~=3.0
|
|
30
|
+
Requires-Dist: werkzeug~=3.1
|
|
31
|
+
Requires-Dist: zstandard~=0.23
|
|
32
32
|
Provides-Extra: jupyter
|
|
33
33
|
Requires-Dist: luminarycloud-jupyter; extra == 'jupyter'
|
|
34
34
|
Description-Content-Type: text/markdown
|