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.
Files changed (73) hide show
  1. luminarycloud/_auth/auth.py +23 -34
  2. luminarycloud/_client/client.py +23 -4
  3. luminarycloud/_client/retry_interceptor.py +7 -0
  4. luminarycloud/_helpers/_create_geometry.py +134 -34
  5. luminarycloud/_helpers/_wait_for_mesh.py +14 -4
  6. luminarycloud/_helpers/cond.py +0 -1
  7. luminarycloud/_proto/api/v0/luminarycloud/geometry/geometry_pb2.py +146 -123
  8. luminarycloud/_proto/api/v0/luminarycloud/geometry/geometry_pb2.pyi +82 -15
  9. luminarycloud/_proto/api/v0/luminarycloud/geometry/geometry_pb2_grpc.py +34 -0
  10. luminarycloud/_proto/api/v0/luminarycloud/geometry/geometry_pb2_grpc.pyi +12 -0
  11. luminarycloud/_proto/api/v0/luminarycloud/inference/inference_pb2.py +8 -8
  12. luminarycloud/_proto/api/v0/luminarycloud/inference/inference_pb2.pyi +12 -7
  13. luminarycloud/_proto/api/v0/luminarycloud/mesh/mesh_pb2.py +25 -3
  14. luminarycloud/_proto/api/v0/luminarycloud/mesh/mesh_pb2.pyi +30 -0
  15. luminarycloud/_proto/api/v0/luminarycloud/mesh/mesh_pb2_grpc.py +34 -0
  16. luminarycloud/_proto/api/v0/luminarycloud/mesh/mesh_pb2_grpc.pyi +12 -0
  17. luminarycloud/_proto/api/v0/luminarycloud/pipelines/pipelines_pb2.py +246 -0
  18. luminarycloud/_proto/api/v0/luminarycloud/pipelines/pipelines_pb2.pyi +420 -0
  19. luminarycloud/_proto/api/v0/luminarycloud/pipelines/pipelines_pb2_grpc.py +240 -0
  20. luminarycloud/_proto/api/v0/luminarycloud/pipelines/pipelines_pb2_grpc.pyi +90 -0
  21. luminarycloud/_proto/api/v0/luminarycloud/project/project_pb2.py +54 -3
  22. luminarycloud/_proto/api/v0/luminarycloud/project/project_pb2.pyi +92 -1
  23. luminarycloud/_proto/api/v0/luminarycloud/project/project_pb2_grpc.py +132 -0
  24. luminarycloud/_proto/api/v0/luminarycloud/project/project_pb2_grpc.pyi +40 -0
  25. luminarycloud/_proto/api/v0/luminarycloud/project_ui_state/project_ui_state_pb2.py +97 -0
  26. luminarycloud/_proto/api/v0/luminarycloud/project_ui_state/project_ui_state_pb2.pyi +93 -0
  27. luminarycloud/_proto/api/v0/luminarycloud/project_ui_state/project_ui_state_pb2_grpc.py +132 -0
  28. luminarycloud/_proto/api/v0/luminarycloud/project_ui_state/project_ui_state_pb2_grpc.pyi +44 -0
  29. luminarycloud/_proto/api/v0/luminarycloud/simulation_template/simulation_template_pb2.py +48 -26
  30. luminarycloud/_proto/api/v0/luminarycloud/simulation_template/simulation_template_pb2.pyi +30 -2
  31. luminarycloud/_proto/api/v0/luminarycloud/simulation_template/simulation_template_pb2_grpc.py +36 -0
  32. luminarycloud/_proto/api/v0/luminarycloud/simulation_template/simulation_template_pb2_grpc.pyi +18 -0
  33. luminarycloud/_proto/api/v0/luminarycloud/vis/vis_pb2.py +153 -133
  34. luminarycloud/_proto/api/v0/luminarycloud/vis/vis_pb2.pyi +51 -3
  35. luminarycloud/_proto/client/simulation_pb2.py +261 -251
  36. luminarycloud/_proto/client/simulation_pb2.pyi +35 -2
  37. luminarycloud/_proto/frontend/output/output_pb2.py +24 -24
  38. luminarycloud/_proto/frontend/output/output_pb2.pyi +6 -3
  39. luminarycloud/_proto/geometry/geometry_pb2.py +63 -63
  40. luminarycloud/_proto/geometry/geometry_pb2.pyi +16 -8
  41. luminarycloud/_proto/hexmesh/hexmesh_pb2.py +17 -4
  42. luminarycloud/_proto/hexmesh/hexmesh_pb2.pyi +22 -1
  43. luminarycloud/_proto/inferenceservice/inferenceservice_pb2.py +10 -10
  44. luminarycloud/_proto/inferenceservice/inferenceservice_pb2.pyi +12 -7
  45. luminarycloud/_proto/quantity/quantity_pb2.py +19 -19
  46. luminarycloud/enum/geometry_status.py +15 -8
  47. luminarycloud/enum/pipeline_job_status.py +23 -0
  48. luminarycloud/feature_modification.py +3 -6
  49. luminarycloud/geometry.py +25 -0
  50. luminarycloud/geometry_version.py +23 -0
  51. luminarycloud/mesh.py +16 -0
  52. luminarycloud/params/enum/_enum_wrappers.py +29 -0
  53. luminarycloud/params/simulation/physics/fluid/boundary_conditions/inlet/fan_curve_inlet_.py +1 -1
  54. luminarycloud/params/simulation/physics/fluid/boundary_conditions/inlet/mach_inlet_.py +5 -1
  55. luminarycloud/params/simulation/physics/fluid/boundary_conditions/inlet/mass_flow_inlet_.py +5 -1
  56. luminarycloud/params/simulation/physics/fluid/boundary_conditions/inlet/total_pressure_inlet_.py +5 -1
  57. luminarycloud/params/simulation/physics/fluid/boundary_conditions/inlet/velocity_magnitude_inlet_.py +5 -1
  58. luminarycloud/physics_ai/inference.py +46 -30
  59. luminarycloud/pipelines/__init__.py +7 -0
  60. luminarycloud/pipelines/api.py +213 -0
  61. luminarycloud/project.py +98 -11
  62. luminarycloud/simulation_template.py +15 -6
  63. luminarycloud/vis/__init__.py +6 -0
  64. luminarycloud/vis/data_extraction.py +201 -31
  65. luminarycloud/vis/filters.py +94 -35
  66. luminarycloud/vis/interactive_inference.py +153 -0
  67. luminarycloud/vis/interactive_scene.py +35 -16
  68. luminarycloud/vis/primitives.py +87 -1
  69. luminarycloud/vis/visualization.py +50 -6
  70. luminarycloud/volume_selection.py +3 -6
  71. {luminarycloud-0.16.2.dist-info → luminarycloud-0.18.0.dist-info}/METADATA +18 -18
  72. {luminarycloud-0.16.2.dist-info → luminarycloud-0.18.0.dist-info}/RECORD +73 -62
  73. {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(scene, resp, isComparator)
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 not set_camera:
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
- self.widget.camera_pan = [camera.pan_x, camera.pan_y]
149
- self.widget.camera_position = [camera.position[0], camera.position[1], camera.position[2]]
150
- self.widget.camera_look_at = [camera.look_at[0], camera.look_at[1], camera.look_at[2]]
151
- self.widget.camera_up = [camera.up[0], camera.up[1], camera.up[2]]
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.
@@ -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 filter such as box clip.
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(self, obj_name: str, hide_defaults: bool = True) -> str:
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 for attributes.
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
- code += "# Add color maps\n"
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
- code += color_map._to_code_helper(cmap_name, hide_defaults=hide_defaults)
976
- code += f"scene.add_color_map({cmap_name})\n"
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
- code += "\n"
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, TYPE_CHECKING
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.16.2
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.6.0
11
- Requires-Dist: googleapis-common-protos~=1.58
12
- Requires-Dist: grpcio-status~=1.51
13
- Requires-Dist: grpcio-tools~=1.51
14
- Requires-Dist: grpcio~=1.51
15
- Requires-Dist: importlib-metadata~=6.0.0
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.46b0
20
- Requires-Dist: opentelemetry-instrumentation~=0.46b0
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.46b0
24
- Requires-Dist: protobuf~=4.21
25
- Requires-Dist: pyjwt~=2.6
26
- Requires-Dist: python-dotenv~=1.0.0
27
- Requires-Dist: pyyaml~=6.0.2
28
- Requires-Dist: requests~=2.28
29
- Requires-Dist: waitress~=3.0.1
30
- Requires-Dist: werkzeug~=3.0.1
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