luminarycloud 0.15.5__py3-none-any.whl → 0.16.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.
Files changed (77) hide show
  1. luminarycloud/_client/client.py +5 -0
  2. luminarycloud/_helpers/__init__.py +1 -0
  3. luminarycloud/_helpers/_code_representation.py +21 -4
  4. luminarycloud/_helpers/download.py +67 -1
  5. luminarycloud/_proto/api/v0/luminarycloud/inference/inference_pb2.py +9 -9
  6. luminarycloud/_proto/api/v0/luminarycloud/inference/inference_pb2.pyi +7 -4
  7. luminarycloud/_proto/api/v0/luminarycloud/physics_ai/physics_ai_pb2.py +45 -21
  8. luminarycloud/_proto/api/v0/luminarycloud/physics_ai/physics_ai_pb2.pyi +65 -0
  9. luminarycloud/_proto/api/v0/luminarycloud/physics_ai/physics_ai_pb2_grpc.py +34 -0
  10. luminarycloud/_proto/api/v0/luminarycloud/physics_ai/physics_ai_pb2_grpc.pyi +12 -0
  11. luminarycloud/_proto/api/v0/luminarycloud/thirdpartyintegration/onshape/onshape_pb2.py +194 -7
  12. luminarycloud/_proto/api/v0/luminarycloud/thirdpartyintegration/onshape/onshape_pb2.pyi +407 -5
  13. luminarycloud/_proto/api/v0/luminarycloud/thirdpartyintegration/onshape/onshape_pb2_grpc.py +171 -0
  14. luminarycloud/_proto/api/v0/luminarycloud/thirdpartyintegration/onshape/onshape_pb2_grpc.pyi +64 -0
  15. luminarycloud/_proto/api/v0/luminarycloud/upload/upload_pb2.py +4 -2
  16. luminarycloud/_proto/api/v0/luminarycloud/upload/upload_pb2_grpc.py +34 -0
  17. luminarycloud/_proto/api/v0/luminarycloud/upload/upload_pb2_grpc.pyi +12 -0
  18. luminarycloud/_proto/api/v0/luminarycloud/vis/vis_pb2.py +128 -107
  19. luminarycloud/_proto/api/v0/luminarycloud/vis/vis_pb2.pyi +48 -3
  20. luminarycloud/_proto/assistant/assistant_pb2.py +82 -61
  21. luminarycloud/_proto/assistant/assistant_pb2.pyi +40 -0
  22. luminarycloud/_proto/assistant/assistant_pb2_grpc.py +34 -0
  23. luminarycloud/_proto/assistant/assistant_pb2_grpc.pyi +12 -0
  24. luminarycloud/_proto/base/base_pb2.py +7 -6
  25. luminarycloud/_proto/base/base_pb2.pyi +4 -0
  26. luminarycloud/_proto/client/simulation_pb2.py +351 -351
  27. luminarycloud/_proto/client/simulation_pb2.pyi +105 -97
  28. luminarycloud/_proto/geometry/geometry_pb2.py +68 -68
  29. luminarycloud/_proto/geometry/geometry_pb2.pyi +15 -7
  30. luminarycloud/_proto/hexmesh/hexmesh_pb2.py +40 -15
  31. luminarycloud/_proto/hexmesh/hexmesh_pb2.pyi +58 -1
  32. luminarycloud/_proto/inferenceservice/inferenceservice_pb2.py +11 -11
  33. luminarycloud/_proto/inferenceservice/inferenceservice_pb2.pyi +12 -4
  34. luminarycloud/_proto/lcstatus/codes_pb2.py +3 -2
  35. luminarycloud/_proto/lcstatus/codes_pb2.pyi +4 -0
  36. luminarycloud/_proto/quantity/quantity_pb2.py +11 -2
  37. luminarycloud/_proto/quantity/quantity_pb2.pyi +6 -0
  38. luminarycloud/_proto/table/table_pb2.pyi +4 -2
  39. luminarycloud/_proto/upload/upload_pb2.py +27 -7
  40. luminarycloud/_proto/upload/upload_pb2.pyi +31 -0
  41. luminarycloud/enum/quantity_type.py +19 -0
  42. luminarycloud/enum/tables.py +1 -0
  43. luminarycloud/enum/vis_enums.py +20 -0
  44. luminarycloud/feature_modification.py +6 -7
  45. luminarycloud/geometry.py +24 -0
  46. luminarycloud/geometry_version.py +23 -0
  47. luminarycloud/mesh.py +8 -1
  48. luminarycloud/params/simulation/adjoint_.py +4 -4
  49. luminarycloud/params/simulation/material/material_fluid_.py +1 -1
  50. luminarycloud/params/simulation/material/material_solid_.py +1 -1
  51. luminarycloud/params/simulation/output_.py +1 -1
  52. luminarycloud/params/simulation/physics/fluid/initialization/fluid_existing_solution_.py +28 -0
  53. luminarycloud/params/simulation/physics/fluid/solution_controls/fluid_relaxation_method/fluid_implicit_relaxation/robust_startup/__init__.py +1 -0
  54. luminarycloud/params/simulation/physics/fluid/solution_controls/fluid_relaxation_method/fluid_implicit_relaxation/robust_startup/robust_startup_auto_.py +30 -0
  55. luminarycloud/params/simulation/physics/fluid/solution_controls/fluid_relaxation_method/fluid_implicit_relaxation/robust_startup/robust_startup_on_.py +1 -1
  56. luminarycloud/params/simulation/physics/fluid/solution_controls/fluid_relaxation_method/fluid_implicit_relaxation_.py +6 -2
  57. luminarycloud/params/simulation/physics/fluid/solution_controls_fluid_.py +4 -0
  58. luminarycloud/params/simulation/simulation_param_.py +6 -0
  59. luminarycloud/physics_ai/__init__.py +4 -0
  60. luminarycloud/physics_ai/inference.py +140 -4
  61. luminarycloud/physics_ai/solution.py +60 -0
  62. luminarycloud/project.py +9 -7
  63. luminarycloud/simulation_param.py +29 -15
  64. luminarycloud/simulation_template.py +14 -10
  65. luminarycloud/tables.py +11 -12
  66. luminarycloud/thirdparty/__init__.py +12 -0
  67. luminarycloud/thirdparty/onshape.py +170 -0
  68. luminarycloud/vis/__init__.py +2 -0
  69. luminarycloud/vis/data_extraction.py +44 -6
  70. luminarycloud/vis/display.py +26 -11
  71. luminarycloud/vis/filters.py +226 -67
  72. luminarycloud/vis/primitives.py +3 -2
  73. luminarycloud/vis/visualization.py +198 -41
  74. luminarycloud/volume_selection.py +2 -2
  75. {luminarycloud-0.15.5.dist-info → luminarycloud-0.16.1.dist-info}/METADATA +6 -6
  76. {luminarycloud-0.15.5.dist-info → luminarycloud-0.16.1.dist-info}/RECORD +77 -73
  77. {luminarycloud-0.15.5.dist-info → luminarycloud-0.16.1.dist-info}/WHEEL +0 -0
@@ -6,7 +6,6 @@ from os import PathLike
6
6
  from pprint import pformat
7
7
  from typing import Optional, TypeVar, cast
8
8
 
9
- import luminarycloud.params.enum._enum_wrappers as enum
10
9
  from luminarycloud._helpers._simulation_params_from_json import (
11
10
  simulation_params_from_json_path,
12
11
  simulation_params_from_json_dict,
@@ -38,7 +37,7 @@ from luminarycloud.params.simulation.volume_entity_ import (
38
37
  from luminarycloud.params.simulation import (
39
38
  SimulationParam as _SimulationParam,
40
39
  )
41
- from luminarycloud.reference_values import ReferenceValues, ReferenceValuesType
40
+ from luminarycloud.reference_values import ReferenceValues
42
41
  from luminarycloud.types import Vector3Like
43
42
  from luminarycloud.types.vector3 import _to_vector3_ad_proto
44
43
 
@@ -152,7 +151,10 @@ class SimulationParam(_SimulationParam):
152
151
  _list=volume_material_pairs,
153
152
  _accessor=lambda v: get_id(v.volume_identifier),
154
153
  _to_remove=volume_identifier.id,
155
- _warning_message=lambda v: f"Volume {_stringify_identifier(volume_identifier)} has already been assigned material {_stringify_identifier(v.material_identifier)}. Overwriting...",
154
+ _warning_message=lambda v: (
155
+ f"Volume {_stringify_identifier(volume_identifier)} has already been assigned "
156
+ f"material {_stringify_identifier(v.material_identifier)}. Overwriting..."
157
+ ),
156
158
  )
157
159
 
158
160
  if volume_identifier.id not in (get_id(v.volume_identifier) for v in self.volume_entity):
@@ -226,13 +228,19 @@ class SimulationParam(_SimulationParam):
226
228
  _list=volume_physics_pairs,
227
229
  _accessor=lambda v: get_id(v.volume_identifier),
228
230
  _to_remove=volume_identifier.id,
229
- _warning_message=lambda v: f"Volume {_stringify_identifier(volume_identifier)} has already been assigned physics {_stringify_identifier(v.physics_identifier)}. Overwriting...",
231
+ _warning_message=lambda v: (
232
+ f"Volume {_stringify_identifier(volume_identifier)} has already been assigned "
233
+ f"physics {_stringify_identifier(v.physics_identifier)}. Overwriting..."
234
+ ),
230
235
  )
231
236
  _remove_from_list_with_warning(
232
237
  _list=volume_physics_pairs,
233
238
  _accessor=lambda v: get_id(v.physics_identifier),
234
239
  _to_remove=get_id(physics.physics_identifier),
235
- _warning_message=lambda v: f"Physics {_stringify_identifier(physics.physics_identifier)} has already been assigned to volume {_stringify_identifier(v.volume_identifier)}. Overwriting...",
240
+ _warning_message=lambda v: (
241
+ f"Physics {_stringify_identifier(physics.physics_identifier)} has already been "
242
+ f"assigned to volume {_stringify_identifier(v.volume_identifier)}. Overwriting..."
243
+ ),
236
244
  )
237
245
 
238
246
  if volume_identifier.id not in (get_id(v.volume_identifier) for v in self.volume_entity):
@@ -268,25 +276,25 @@ class SimulationParam(_SimulationParam):
268
276
  .. warning:: This feature is experimental and may change or be removed without notice.
269
277
  """
270
278
  self.adjoint = self.adjoint or Adjoint()
271
- self.adjoint.output = outputpb.Output()
272
- self.adjoint.output.quantity = quantity_type.value
273
- self.adjoint.output.in_surfaces.extend(surface_ids)
274
- self.adjoint.output.frame_id = frame_id
279
+ self.adjoint._output = outputpb.Output()
280
+ self.adjoint._output.quantity = quantity_type.value
281
+ self.adjoint._output.in_surfaces.extend(surface_ids)
282
+ self.adjoint._output.frame_id = frame_id
275
283
  if QuantityType._is_average(quantity_type):
276
284
  if averaging_type == AveragingType.UNSPECIFIED:
277
- self.adjoint.output.surface_average_properties.averaging_type = (
285
+ self.adjoint._output.surface_average_properties.averaging_type = (
278
286
  SpaceAveragingType.NO_AVERAGING.value
279
287
  )
280
288
  elif averaging_type == AveragingType.AREA:
281
- self.adjoint.output.surface_average_properties.averaging_type = (
289
+ self.adjoint._output.surface_average_properties.averaging_type = (
282
290
  SpaceAveragingType.AREA.value
283
291
  )
284
292
  elif averaging_type == AveragingType.MASS_FLOW:
285
- self.adjoint.output.surface_average_properties.averaging_type = (
293
+ self.adjoint._output.surface_average_properties.averaging_type = (
286
294
  SpaceAveragingType.MASS_FLOW.value
287
295
  )
288
296
  elif QuantityType._is_force(quantity_type):
289
- self.adjoint.output.force_properties.CopyFrom(
297
+ self.adjoint._output.force_properties.CopyFrom(
290
298
  outputpb.ForceProperties(
291
299
  force_dir_type=(
292
300
  outputpb.FORCE_DIRECTION_BODY_ORIENTATION_AND_FLOW_DIR
@@ -332,9 +340,15 @@ from luminarycloud import outputs, SimulationParam, EntityIdentifier
332
340
 
333
341
 
334
342
  """
335
- return code + self._to_code_helper("obj", hide_defaults, use_tmp_objs).replace(
336
- "luminarycloud.simulation_param.SimulationParam()", "SimulationParam()", 1
343
+ code += self._to_code_helper("obj", hide_defaults, use_tmp_objs).replace(
344
+ "luminarycloud.simulation_param.SimulationParam()", "luminarycloud.SimulationParam()", 1
337
345
  )
346
+ if self.adjoint.primal_simulation_id != "":
347
+ code += """
348
+ # TODO(USER): Select appropriate parameters to configure the adjoint output.
349
+ obj.configure_adjoint_output("...")
350
+ """
351
+ return code
338
352
 
339
353
  def find_parameter(self, parameter: str) -> None:
340
354
  """
@@ -119,14 +119,6 @@ class SimulationTemplate(ProtoWrapperBase):
119
119
  param_proto.CopyFrom(parameters)
120
120
  else:
121
121
  param_proto = simulation_params_from_json_path(parameters)
122
-
123
- if isinstance(parameters, (SimulationParam, clientpb.SimulationParam)):
124
- # Table references are manipulated via the simulation template, hence we need to persist
125
- # them when we update the parameters.
126
- param_proto.table_references.clear()
127
- for k, v in self._proto.parameters.table_references.items():
128
- param_proto.table_references[k].CopyFrom(v)
129
-
130
122
  req.parameters.CopyFrom(param_proto)
131
123
 
132
124
  if copy_from is not None:
@@ -482,7 +474,8 @@ class SimulationTemplate(ProtoWrapperBase):
482
474
  By default parameters with default values are omitted, this change be changed with
483
475
  hide_defaults=False.
484
476
  """
485
- param_code = self.get_parameters().to_code(hide_defaults)
477
+ parameters = self.get_parameters()
478
+ param_code = parameters.to_code(hide_defaults)
486
479
  code, param_code = param_code.split("\n\n\n")
487
480
 
488
481
  # Modify the header note included by SimulationParam.to_code.
@@ -490,10 +483,21 @@ class SimulationTemplate(ProtoWrapperBase):
490
483
  code += "\n\n\n"
491
484
  code += '# Create a new simulation template or modify the one that is synced with the UI "Setup" tab.\n'
492
485
  code += "# TODO(USER): Replace ... with project ID, or create a project.\n"
493
- code += 'project = get_project("...")\n'
486
+ code += 'project = luminarycloud.get_project("...")\n'
494
487
  code += f"# template = project.create_simulation_template(name={self.name})\n"
495
488
  code += "template = project.list_simulation_templates()[0] # Setup template\n\n"
496
489
 
490
+ if parameters._table_references:
491
+ code += "# Upload tabular data.\n"
492
+ code += (
493
+ "# TODO(USER): Provide the local file paths (they are not stored by Luminary).\n"
494
+ )
495
+ for name, table in sorted(parameters._table_references.items()):
496
+ table_type = TableType(table.table_type).__repr__().split(": ")[0][1:]
497
+ code += f'project.create_table({table_type}, "path/to/{table.uploaded_filename}", template)\n'
498
+ code += "# This shows the available tables:\n"
499
+ code += "# print(template.list_tables())\n\n"
500
+
497
501
  code += "# Define the simulation parameters.\n"
498
502
  code += param_code
499
503
  code += "\n# Update the simulation template with the parameters.\n"
luminarycloud/tables.py CHANGED
@@ -8,6 +8,7 @@ import csv
8
8
  from typing import Union
9
9
 
10
10
  from .enum import TableType, QuantityType
11
+ from ._helpers import CodeRepr
11
12
  from ._proto.table import table_pb2 as tablepb
12
13
  from ._proto.quantity import quantity_pb2 as quantitypb
13
14
 
@@ -121,11 +122,11 @@ def create_rectilinear_table(
121
122
  table.header.record_label[-1].name = label
122
123
  else:
123
124
  table.header.record_label[-1].quantity = QuantityType(label).value
124
- table.record.append(tablepb.Record())
125
125
 
126
126
  types = data_types(table_type, len(header))
127
127
 
128
128
  for row in rows:
129
+ record = tablepb.Record()
129
130
  for i, val in enumerate(row):
130
131
  # Axis coordinates are always adfloats and cannot be missing.
131
132
  if i == 0 and has_axis(table_type):
@@ -133,36 +134,34 @@ def create_rectilinear_table(
133
134
  table.axis[0].coordinate[-1].adfloat.value = float(val)
134
135
  continue
135
136
 
136
- j = i - has_axis(table_type)
137
137
  if val == "":
138
138
  if allow_missing_entries(table_type):
139
- table.record[j].entry.append(
140
- tablepb.Record.Entry(empty=tablepb.Record.Entry.Empty())
141
- )
139
+ record.entry.append(tablepb.Record.Entry(empty=tablepb.Record.Entry.Empty()))
142
140
  pass
143
141
  else:
144
142
  raise ValueError(f"Entry {i} in row {row} is missing.")
145
143
  continue
146
144
 
147
- table.record[j].entry.append(tablepb.Record.Entry())
145
+ record.entry.append(tablepb.Record.Entry())
148
146
  try:
149
147
  if types[i] == float:
150
- table.record[j].entry[-1].adfloat.value = float(val)
148
+ record.entry[-1].adfloat.value = float(val)
151
149
  else:
152
- table.record[j].entry[-1].string = val
150
+ record.entry[-1].string = val
153
151
  except ValueError:
154
152
  raise ValueError(f"Expected type {types[i]} for entry {i} in row {row}.")
153
+ table.record.append(record)
155
154
 
156
155
  return table
157
156
 
158
157
 
159
158
  @dataclass(kw_only=True)
160
- class RectilinearTable:
159
+ class RectilinearTable(CodeRepr):
161
160
  """Represents an uploaded table."""
162
161
 
163
- id: str
164
- name: str
165
- table_type: TableType
162
+ id: str = ""
163
+ name: str = ""
164
+ table_type: TableType = TableType.INVALID
166
165
 
167
166
 
168
167
  def _param_name_to_table_type(name: str) -> TableType:
@@ -0,0 +1,12 @@
1
+ # Copyright 2025 Luminary Cloud, Inc. All Rights Reserved.
2
+ """
3
+ Third-party integration functionality for the Luminary SDK.
4
+
5
+ This package provides modules for interacting with various third-party services.
6
+ Each service has its own submodule with specific functionality.
7
+
8
+ Available integrations:
9
+ - onshape: Onshape CAD platform integration
10
+ """
11
+
12
+ from . import onshape as onshape
@@ -0,0 +1,170 @@
1
+ # Copyright 2025 Luminary Cloud, Inc. All Rights Reserved.
2
+ """
3
+ Onshape integration functionality for the Luminary SDK.
4
+
5
+ This module provides functions for interacting with Onshape CAD platform,
6
+ allowing users to check authentication status and fetch variables using
7
+ native Python types. This is in addition to our more integrated uses of
8
+ Onshape within the SDK, such as allowing Onshape URLs in create_geometry.
9
+ """
10
+
11
+ from typing import NamedTuple
12
+ from dataclasses import dataclass
13
+ import re
14
+
15
+ from .._client import get_default_client
16
+ from .._proto.api.v0.luminarycloud.thirdpartyintegration.onshape import onshape_pb2 as onshapepb
17
+
18
+
19
+ @dataclass
20
+ class OnshapeVariable:
21
+ """
22
+ Represents a variable from Onshape according to its variable studio definition.
23
+
24
+ Attributes
25
+ ----------
26
+ type : str
27
+ The type of the variable (e.g., "LENGTH", "ANGLE", "NUMBER", "ANY")
28
+ name : str
29
+ The name of the variable
30
+ description : str
31
+ A description of the variable
32
+ value : str
33
+ The current value of the variable as a string
34
+ expression : str
35
+ The expression defining the variable
36
+ """
37
+
38
+ type: str
39
+ name: str
40
+ description: str
41
+ value: str
42
+ expression: str
43
+
44
+
45
+ class AuthenticationStatus(NamedTuple):
46
+ """
47
+ Authentication status for Onshape.
48
+
49
+ Attributes
50
+ ----------
51
+ is_authenticated : bool
52
+ Whether the use has active authentication with Onshape.
53
+ """
54
+
55
+ is_authenticated: bool
56
+
57
+
58
+ def _parse_onshape_url(url: str) -> onshapepb.OnshapePath:
59
+ """
60
+ Parse an Onshape URL to extract the necessary components and return an OnshapePath.
61
+
62
+ Parameters
63
+ ----------
64
+ url : str
65
+ The Onshape URL to parse
66
+
67
+ Returns
68
+ -------
69
+ OnshapePath
70
+ A protobuf OnshapePath object with all fields populated
71
+
72
+ Raises
73
+ ------
74
+ ValueError
75
+ If the URL format is invalid or cannot be parsed
76
+ """
77
+
78
+ # Expected format: https://{company_prefix}.onshape.com/documents/{document_id}/{w_or_v}/{wv_id}/e/{element_id}
79
+ url_pattern = (
80
+ r"https://([^.\s]+)\.onshape\.com/documents/([^/\s]+)/([wv])/([^/\s]+)/e/([^/\s?#]+)$"
81
+ )
82
+ match = re.match(url_pattern, url)
83
+
84
+ if not match:
85
+ raise ValueError(f"Invalid Onshape URL format: {url}")
86
+
87
+ company_prefix, document_id, w_or_v, wv_id, element_id = match.groups()
88
+
89
+ return onshapepb.OnshapePath(
90
+ company_prefix=company_prefix,
91
+ document_id=document_id,
92
+ w_or_v=w_or_v,
93
+ wv_id=wv_id,
94
+ element_id=element_id,
95
+ )
96
+
97
+
98
+ def get_authentication_status() -> AuthenticationStatus:
99
+ """
100
+ Check whether the current user has valid authentication with Onshape.
101
+
102
+ Returns
103
+ -------
104
+ AuthenticationStatus
105
+ The authentication status containing whether the user is authenticated
106
+
107
+ Examples
108
+ --------
109
+ >>> import luminarycloud.thirdparty.onshape as onshape
110
+ >>> status = onshape.get_authentication_status()
111
+ >>> if status.is_authenticated:
112
+ ... print("User is authenticated with Onshape")
113
+ ... else:
114
+ ... print("User needs to authenticate with Onshape")
115
+ """
116
+ req = onshapepb.GetAuthenticationStatusRequest()
117
+ res = get_default_client().GetAuthenticationStatus(req)
118
+ return AuthenticationStatus(is_authenticated=res.auth_active)
119
+
120
+
121
+ def fetch_variables(onshape_url: str) -> list[OnshapeVariable]:
122
+ """
123
+ Fetch variables from an Onshape document using an Onshape URL.
124
+
125
+ Parameters
126
+ ----------
127
+ onshape_url : str
128
+ The Onshape URL pointing to the document element.
129
+ Expected format: https://{company}.onshape.com/documents/{doc_id}/{w_or_v}/{workspace_or_version_id}/e/{element_id}
130
+
131
+ Returns
132
+ -------
133
+ list[OnshapeVariable]
134
+ A list of variables available in the specified Onshape element
135
+
136
+ Raises
137
+ ------
138
+ ValueError
139
+ If the URL format is invalid or cannot be parsed
140
+
141
+ Examples
142
+ --------
143
+ >>> import luminarycloud.thirdparty.onshape as onshape
144
+ >>> # Fetch variables from a workspace URL
145
+ >>> fake_url = "https://cad.onshape.com/documents/abc123/w/def456/e/ghi789"
146
+ >>> variables = onshape.fetch_variables(fake_url)
147
+ >>> for var in variables:
148
+ ... print(f"{var.name}: {var.value} ({var.type})")
149
+
150
+ >>> # Fetch variables from a version URL
151
+ >>> fake_url = "https://cad.onshape.com/documents/abc123/v/xyz999/e/ghi789"
152
+ >>> variables = onshape.fetch_variables(fake_url)
153
+ """
154
+
155
+ path = _parse_onshape_url(onshape_url)
156
+ req = onshapepb.FetchVariablesRequest(path=path)
157
+ res = get_default_client().FetchVariables(req)
158
+ variables = []
159
+ for proto_var in res.variables:
160
+ variables.append(
161
+ OnshapeVariable(
162
+ type=proto_var.type,
163
+ name=proto_var.name,
164
+ value=proto_var.value,
165
+ expression=proto_var.expression,
166
+ description=proto_var.description,
167
+ )
168
+ )
169
+
170
+ return variables
@@ -20,6 +20,7 @@ from .filters import (
20
20
  FixedSizeVectorGlyphs as FixedSizeVectorGlyphs,
21
21
  ScaledVectorGlyphs as ScaledVectorGlyphs,
22
22
  RakeStreamlines as RakeStreamlines,
23
+ GridStreamlines as GridStreamlines,
23
24
  SurfaceStreamlines as SurfaceStreamlines,
24
25
  SurfaceLIC as SurfaceLIC,
25
26
  Threshold as Threshold,
@@ -30,6 +31,7 @@ from .data_extraction import (
30
31
  IntersectionCurve as IntersectionCurve,
31
32
  DataExtractor as DataExtractor,
32
33
  ExtractOutput as ExtractOutput,
34
+ LineSample as LineSample,
33
35
  list_data_extracts as list_data_extracts,
34
36
  )
35
37
 
@@ -8,7 +8,7 @@ from typing import List, Tuple, cast, Union
8
8
  from abc import ABC, abstractmethod
9
9
  from .._proto.api.v0.luminarycloud.vis import vis_pb2
10
10
  from .primitives import Plane
11
- from ..types.vector3 import _to_vector3
11
+ from ..types.vector3 import _to_vector3, Vector3Like, Vector3
12
12
  from .._client import get_default_client
13
13
  import logging
14
14
  from ..solution import Solution
@@ -122,6 +122,45 @@ class IntersectionCurve(DataExtract):
122
122
  return vis_filter
123
123
 
124
124
 
125
+ class LineSample(DataExtract):
126
+ """
127
+
128
+ Generate line data by computing intersections between volumetric data and a line.
129
+
130
+ Extracts a 1D curve where the line intersects cell faces with solution field
131
+ values at intersection points.
132
+
133
+ .. warning:: This feature is experimental and may change or be removed in the future.
134
+
135
+ Attributes:
136
+ -----------
137
+ start: Vector3Like
138
+ The start of the line segment. Defaults to (0, 0, 0).
139
+ end: Vector3Like
140
+ The end of the line segment. Defaults to (1, 0, 0).
141
+ label: str
142
+ A user provided label for the line sample.
143
+ name : str
144
+ A user provided name for the filter.
145
+ """
146
+
147
+ def __init__(self, name: str) -> None:
148
+ super().__init__(generate_id("line-sample"))
149
+ self.name = name
150
+ self.start: Vector3Like = Vector3(x=0, y=0, z=0)
151
+ self.end: Vector3Like = Vector3(x=1, y=0, z=0)
152
+ self.label: str = ""
153
+
154
+ def _to_proto(self) -> vis_pb2.Filter:
155
+ vis_filter = vis_pb2.Filter()
156
+ vis_filter.id = self.id
157
+ vis_filter.name = self.name
158
+ vis_filter.line_sample.label = self.label
159
+ vis_filter.line_sample.start.CopyFrom(_to_vector3(self.start)._to_proto())
160
+ vis_filter.line_sample.end.CopyFrom(_to_vector3(self.end)._to_proto())
161
+ return vis_filter
162
+
163
+
125
164
  class ExtractOutput:
126
165
  """
127
166
  The extract output represents the request to extract data from a solution,
@@ -381,12 +420,11 @@ class DataExtractor:
381
420
  simulation = get_simulation(self._solution.simulation_id)
382
421
  mesh_meta = get_mesh_metadata(simulation.mesh_id)
383
422
  mesh = get_mesh(simulation.mesh_id)
384
- try:
385
- geom = mesh.geometry_version().geometry()
386
- except NotFoundError:
423
+ geo_ver = mesh.geometry_version()
424
+ if geo_ver is None:
387
425
  self._has_tags = False
388
- except Exception as e:
389
- raise ValueError("An unknow error occurred retrieving tags")
426
+ else:
427
+ geom = geo_ver.geometry()
390
428
 
391
429
  self._surface_ids: List[str] = []
392
430
  for zone in mesh_meta.zones:
@@ -4,10 +4,11 @@ from luminarycloud.enum import Representation, ColorMapPreset, FieldComponent, V
4
4
  from .._proto.api.v0.luminarycloud.vis import vis_pb2
5
5
  from typing import Optional
6
6
  import luminarycloud.enum.quantity_type as quantity_type
7
+ from .._helpers._code_representation import CodeRepr
7
8
 
8
9
 
9
10
  @dc.dataclass
10
- class Field:
11
+ class Field(CodeRepr):
11
12
  """
12
13
  The field controls the field displayed on the object. If the field doesn't
13
14
  exist, we show a solid color.
@@ -16,8 +17,8 @@ class Field:
16
17
 
17
18
  """
18
19
 
19
- quantity: VisQuantity = VisQuantity.ABSOLUTE_PRESSURE
20
- """The quantity to color by."""
20
+ quantity: VisQuantity = VisQuantity.NONE
21
+ """The quantity to color by. Default: NONE."""
21
22
  component: FieldComponent = FieldComponent.MAGNITUDE
22
23
  """
23
24
  The component of the field to use, applicable to vector fields. If the field is a
@@ -45,9 +46,25 @@ class Field:
45
46
  self.component = FieldComponent(field.component)
46
47
  # If its a scalar, just ignore the component.
47
48
 
49
+ def _to_code(self, hide_defaults: bool = True, use_tmp_objs: bool = True) -> str:
50
+ # We have to handle this case specially because we need to omit the field
51
+ # component if the quantity is a scalar. Otherwise, it could confuse the user.
52
+ # Also we omit the instantiation line because all classes that use a Field already
53
+ # instantiate a default one in their own constructor. (Hopefully that invariant continues to
54
+ # hold)
55
+ def enum_to_string(val: VisQuantity | FieldComponent) -> str:
56
+ str_val = val.__repr__()
57
+ return str_val.split(": ")[0][1:]
58
+
59
+ code = ""
60
+ code += f".quantity = {enum_to_string(self.quantity)}\n"
61
+ if quantity_type._is_vector(self.quantity):
62
+ code += f".component = {enum_to_string(self.component)}\n"
63
+ return code
64
+
48
65
 
49
66
  @dc.dataclass
50
- class DisplayAttributes:
67
+ class DisplayAttributes(CodeRepr):
51
68
  """
52
69
  Display attributes specify how objects such as meshes, geometries, and
53
70
  filters appear in the scene.
@@ -86,7 +103,7 @@ class DisplayAttributes:
86
103
 
87
104
 
88
105
  @dc.dataclass
89
- class DataRange:
106
+ class DataRange(CodeRepr):
90
107
  """
91
108
  The data range represents a range of values. Ranges are only valid if the
92
109
  max value is greater than the or equal to the min_value. The default is
@@ -106,7 +123,7 @@ class DataRange:
106
123
 
107
124
 
108
125
  @dc.dataclass
109
- class ColorMapAppearance:
126
+ class ColorMapAppearance(CodeRepr):
110
127
  """
111
128
  ColorMapAppearance controls how the color maps appear in the image, including
112
129
  visibility, position and size.
@@ -141,7 +158,7 @@ class ColorMapAppearance:
141
158
 
142
159
 
143
160
  @dc.dataclass
144
- class ColorMap:
161
+ class ColorMap(CodeRepr):
145
162
  """
146
163
  The color map allows user control over how field values are mapped to
147
164
  colors. Color maps are assigned to fields (e.g., the quantity and component)
@@ -213,10 +230,8 @@ class ColorMap:
213
230
  return res
214
231
 
215
232
  def _from_proto(self, color_map: vis_pb2.ColorMap) -> None:
216
- self.field = Field(
217
- component=FieldComponent(color_map.field.component),
218
- quantity=VisQuantity(color_map.field.quantity_typ),
219
- )
233
+ self.field._from_proto(color_map.field)
234
+
220
235
  if color_map.HasField("range"):
221
236
  self.data_range = DataRange(
222
237
  min_value=color_map.range.min,