fix: fixed bugs preventing end-to-end sim

Several bugs/gotchas/papercuts have been fixed, which together made it really hard

Closes #47, #48, #49, #50, #51, #52.
Sofus Albert Høgsbro Rose 2024-05-05 14:26:09 +02:00
parent 999d601c49
commit 20c9e6f902
Signed by: so-rose
GPG Key ID: AD901CB0F3701434
23 changed files with 148 additions and 48 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -48,10 +48,10 @@ ORIGINAL_SYS_PATH = sys.path.copy()
#################### ####################
# - Local Addon Cache # - Local Addon Cache
#################### ####################
ADDON_CACHE = PATH_ADDON_ROOT / '.addon_cache' DEFAULT_ADDON_CACHE = PATH_ADDON_ROOT / '.addon_cache'
ADDON_CACHE.mkdir(exist_ok=True) DEFAULT_ADDON_CACHE.mkdir(exist_ok=True)
PIP_INSTALL_LOG = ADDON_CACHE / 'pip_install.log' PIP_INSTALL_LOG = DEFAULT_ADDON_CACHE / 'pip_install.log'
#################### ####################

View File

@ -133,7 +133,7 @@ class BLSocketType(enum.StrEnum):
return { return {
# Blender # Blender
# Basic # Basic
BLST.Bool: ST.String, BLST.Bool: ST.Bool,
# Float # Float
# Array-Like # Array-Like
BLST.Color: ST.Color, BLST.Color: ST.Color,

View File

@ -69,7 +69,8 @@ class FlowKind(enum.StrEnum):
flow_obj, flow_obj,
unit_system: spux.UnitSystem, unit_system: spux.UnitSystem,
): ):
log.debug('%s: Scaling "%s" to Unit System', kind, str(flow_obj)) # log.debug('%s: Scaling "%s" to Unit System', kind, str(flow_obj))
## TODO: Use a hot-path logger.
if kind == FlowKind.Value: if kind == FlowKind.Value:
return spux.scale_to_unit_system( return spux.scale_to_unit_system(
flow_obj, flow_obj,

View File

@ -352,6 +352,22 @@ class LazyArrayRangeFlow:
#################### ####################
# - Realization # - Realization
#################### ####################
def realize_start(
self,
symbol_values: dict[spux.Symbol, typ.Any] = MappingProxyType({}),
) -> ArrayFlow | LazyValueFuncFlow:
return spux.sympy_to_python(
self.start.subs({sym: symbol_values[sym.name] for sym in self.symbols})
)
def realize_stop(
self,
symbol_values: dict[spux.Symbol, typ.Any] = MappingProxyType({}),
) -> ArrayFlow | LazyValueFuncFlow:
return spux.sympy_to_python(
self.stop.subs({sym: symbol_values[sym.name] for sym in self.symbols})
)
def realize( def realize(
self, self,
symbol_values: dict[spux.Symbol, typ.Any] = MappingProxyType({}), symbol_values: dict[spux.Symbol, typ.Any] = MappingProxyType({}),
@ -377,12 +393,8 @@ class LazyArrayRangeFlow:
raise ValueError(msg) raise ValueError(msg)
# Realize Symbols # Realize Symbols
realized_start = spux.sympy_to_python( realized_start = self.realize_start(symbol_values)
self.start.subs({sym: symbol_values[sym.name] for sym in self.symbols}) realized_stop = self.realize_stop(symbol_values)
)
realized_stop = spux.sympy_to_python(
self.stop.subs({sym: symbol_values[sym.name] for sym in self.symbols})
)
# Return Linspace / Logspace # Return Linspace / Logspace
def gen_array() -> jtyp.Inexact[jtyp.Array, ' steps']: def gen_array() -> jtyp.Inexact[jtyp.Array, ' steps']:

View File

@ -110,9 +110,15 @@ def write_modifier_geonodes(
for socket_name in modifier_attrs['inputs']: for socket_name in modifier_attrs['inputs']:
iface_id = socket_infos[socket_name].bl_isocket_identifier iface_id = socket_infos[socket_name].bl_isocket_identifier
bl_modifier[iface_id] = spux.scale_to_unit_system( input_value = modifier_attrs['inputs'][socket_name]
modifier_attrs['inputs'][socket_name], modifier_attrs['unit_system']
) if isinstance(input_value, spux.SympyType):
bl_modifier[iface_id] = spux.scale_to_unit_system(
input_value, modifier_attrs['unit_system']
)
else:
bl_modifier[iface_id] = input_value
modifier_altered = True modifier_altered = True
## TODO: More fine-grained alterations ## TODO: More fine-grained alterations

View File

@ -164,7 +164,6 @@ class VizTarget(enum.StrEnum):
@staticmethod @staticmethod
def valid_targets_for(viz_mode: VizMode) -> list[typ.Self] | None: def valid_targets_for(viz_mode: VizMode) -> list[typ.Self] | None:
return { return {
None: [],
VizMode.Hist1D: [VizTarget.Plot2D], VizMode.Hist1D: [VizTarget.Plot2D],
VizMode.BoxPlot1D: [VizTarget.Plot2D], VizMode.BoxPlot1D: [VizTarget.Plot2D],
VizMode.Curve2D: [VizTarget.Plot2D], VizMode.Curve2D: [VizTarget.Plot2D],
@ -227,7 +226,7 @@ class VizNode(base.MaxwellSimNode):
## - Properties ## - Properties
##################### #####################
viz_mode: enum.Enum = bl_cache.BLField( viz_mode: enum.Enum = bl_cache.BLField(
prop_ui=True, enum_cb=lambda self, _: self.search_modes() prop_ui=True, enum_cb=lambda self, _: self.search_viz_modes()
) )
viz_target: enum.Enum = bl_cache.BLField( viz_target: enum.Enum = bl_cache.BLField(
prop_ui=True, enum_cb=lambda self, _: self.search_targets() prop_ui=True, enum_cb=lambda self, _: self.search_targets()
@ -249,8 +248,8 @@ class VizNode(base.MaxwellSimNode):
return None return None
def search_modes(self) -> list[ct.BLEnumElement]: def search_viz_modes(self) -> list[ct.BLEnumElement]:
if not ct.FlowSignal.check(self.data_info): if self.data_info is not None:
return [ return [
( (
viz_mode, viz_mode,

View File

@ -261,17 +261,24 @@ class MaxwellSimNode(bpy.types.Node):
#################### ####################
@events.on_value_changed( @events.on_value_changed(
prop_name='sim_node_name', prop_name='sim_node_name',
props={'sim_node_name'},
stop_propagation=True, stop_propagation=True,
) )
def _on_sim_node_name_changed(self): def _on_sim_node_name_changed(self, props):
log.info( log.info(
'Changed Sim Node Name of a "%s" to "%s" (self=%s)', 'Changed Sim Node Name of a "%s" to "%s" (self=%s)',
self.bl_idname, self.bl_idname,
self.sim_node_name, props['sim_node_name'],
str(self), str(self),
) )
# Set Name of Managed Objects # Set Name of Managed Objects
for mobj in self.managed_objs.values():
mobj.name = props['sim_node_name']
## Invalidate Cache
## -> Persistance doesn't happen if we simply mutate.
## -> This ensures that the name change is picked up.
self.managed_objs = bl_cache.Signal.InvalidateCache self.managed_objs = bl_cache.Signal.InvalidateCache
@events.on_value_changed(prop_name='active_socket_set') @events.on_value_changed(prop_name='active_socket_set')

View File

@ -80,7 +80,6 @@ class ReloadTrackedTask(bpy.types.Operator):
def execute(self, context): def execute(self, context):
node = context.node node = context.node
tdcloud.TidyCloudTasks.update_task(node.cloud_task) tdcloud.TidyCloudTasks.update_task(node.cloud_task)
node.sim_data = bl_cache.Signal.InvalidateCache
return {'FINISHED'} return {'FINISHED'}
@ -137,7 +136,7 @@ class Tidy3DWebRunnerNode(base.MaxwellSimNode):
return task_info return task_info
@bl_cache.cached_bl_property(persist=False) @property
def sim_data(self) -> td.Simulation | None: def sim_data(self) -> td.Simulation | None:
"""Retrieve the simulation data of the current cloud task from the input socket. """Retrieve the simulation data of the current cloud task from the input socket.
@ -155,7 +154,7 @@ class Tidy3DWebRunnerNode(base.MaxwellSimNode):
self.cloud_task, self.cloud_task,
tdcloud.TidyCloudTasks.task_info( tdcloud.TidyCloudTasks.task_info(
self.cloud_task.task_id self.cloud_task.task_id
).disk_cache_path(ct.addon.ADDON_CACHE), ).disk_cache_path(ct.addon.prefs().addon_cache_path),
) )
if sim_data is None: if sim_data is None:
return None return None
@ -250,12 +249,6 @@ class Tidy3DWebRunnerNode(base.MaxwellSimNode):
#################### ####################
# - Output Methods # - Output Methods
#################### ####################
@events.on_value_changed(
socket_name='Cloud Task',
)
def compute_cloud_task(self) -> None:
self.sim_data = bl_cache.Signal.InvalidateCache
@events.computes_output_socket( @events.computes_output_socket(
'Sim Data', 'Sim Data',
props={'sim_data'}, props={'sim_data'},

View File

@ -135,6 +135,48 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
freqs=input_sockets['Freqs'].realize().values, freqs=input_sockets['Freqs'].realize().values,
) )
@events.computes_output_socket(
'Time Monitor',
props={'sim_node_name'},
input_sockets={
'Center',
'Size',
'Spatial Subdivs',
'Time Range',
'Temporal Subdivs',
},
input_socket_kinds={
'Time Range': ct.FlowKind.LazyArrayRange,
},
unit_systems={'Tidy3DUnits': ct.UNITS_TIDY3D},
scale_input_sockets={
'Center': 'Tidy3DUnits',
'Size': 'Tidy3DUnits',
'Time Range': 'Tidy3DUnits',
},
)
def compute_time_monitor(
self,
input_sockets: dict,
props: dict,
unit_systems: dict,
) -> td.FieldMonitor:
log.info(
'Computing FieldMonitor (name="%s") with center="%s", size="%s"',
props['sim_node_name'],
input_sockets['Center'],
input_sockets['Size'],
)
return td.FieldTimeMonitor(
center=input_sockets['Center'],
size=input_sockets['Size'],
name=props['sim_node_name'],
interval_space=tuple(input_sockets['Spatial Subdivs']),
start=input_sockets['Time Range'].realize_start(),
stop=input_sockets['Time Range'].realize_stop(),
interval=input_sockets['Temporal Subdivs'],
)
#################### ####################
# - Preview # - Preview
#################### ####################

View File

@ -132,7 +132,7 @@ class PowerFluxMonitorNode(base.MaxwellSimNode):
size=input_sockets['Size'], size=input_sockets['Size'],
name=props['sim_node_name'], name=props['sim_node_name'],
interval_space=(1, 1, 1), interval_space=(1, 1, 1),
freqs=input_sockets['Freqs'].realize_array, freqs=input_sockets['Freqs'].realize_array.values,
normal_dir='+' if input_sockets['Direction'] else '-', normal_dir='+' if input_sockets['Direction'] else '-',
) )

View File

@ -190,7 +190,9 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
has_uploaded_task = self.uploaded_task_id != '' has_uploaded_task = self.uploaded_task_id != ''
if has_uploaded_task: if has_uploaded_task:
return tdcloud.TidyCloudTasks.task(self.uploaded_task_id) return tdcloud.TidyCloudTasks.tasks(self.new_cloud_task.cloud_folder).get(
self.uploaded_task_id
)
return None return None
@property @property
@ -199,9 +201,8 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
If one can't be loaded, return None. If one can't be loaded, return None.
""" """
has_uploaded_task = self.uploaded_task_id != '' uploaded_task = self.uploaded_task
if uploaded_task is not None:
if has_uploaded_task:
return tdcloud.TidyCloudTasks.task_info(self.uploaded_task_id) return tdcloud.TidyCloudTasks.task_info(self.uploaded_task_id)
return None return None

View File

@ -979,6 +979,16 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
col: Target for defining UI elements. col: Target for defining UI elements.
""" """
def draw_lazy_value_func(self, col: bpy.types.UILayout) -> None:
"""Draws the socket lazy value function UI on its own line.
Notes:
Should be overriden by individual socket classes, if they have an editable `FlowKind.LazyValueFunc`.
Parameters:
col: Target for defining UI elements.
"""
#################### ####################
# - UI Methods: Auxilliary # - UI Methods: Auxilliary
#################### ####################

View File

@ -225,7 +225,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
# Workaround: Manually Jiggle FlowKind Invalidation # Workaround: Manually Jiggle FlowKind Invalidation
self.value = self.value self.value = self.value
self.lazy_value_range = self.lazy_value_range self.lazy_array_range = self.lazy_array_range
#################### ####################
# - Property Callback # - Property Callback

View File

@ -46,6 +46,23 @@ class BLMaxwellAddonPrefs(bpy.types.AddonPreferences):
#################### ####################
# - Properties # - Properties
#################### ####################
# Addon Cache Path
bl__addon_cache_path: bpy.props.StringProperty(
name='Addon Cache Path',
description='Path to Addon Cache',
subtype='FILE_PATH',
default=str(ct.addon.DEFAULT_ADDON_CACHE),
update=lambda self, _: self.on_addon_pydeps_changed(),
)
@property
def addon_cache_path(self) -> Path:
return Path(bpy.path.abspath(self.bl__addon_cache_path))
@addon_cache_path.setter
def addon_cache_path(self, path: Path) -> None:
self.bl__addon_cache_path = str(path.resolve())
# PyDeps Default Path # PyDeps Default Path
use_default_pydeps_path: bpy.props.BoolProperty( use_default_pydeps_path: bpy.props.BoolProperty(
name='Use Default PyDeps Path', name='Use Default PyDeps Path',

View File

@ -68,12 +68,18 @@ def install_and_enable_addon(addon_name: str, addon_zip: Path) -> None:
bpy.ops.wm.save_userpref() bpy.ops.wm.save_userpref()
def setup_for_development(addon_name: str, path_addon_dev_deps: Path) -> None: def setup_for_development(
addon_name: str,
path_addon_dev_deps: Path,
path_addon_cache_path: Path | None = None,
) -> None:
addon_prefs = bpy.context.preferences.addons[addon_name].preferences addon_prefs = bpy.context.preferences.addons[addon_name].preferences
# PyDeps Path # PyDeps Path
addon_prefs.use_default_pydeps_path = False addon_prefs.use_default_pydeps_path = False
addon_prefs.pydeps_path = path_addon_dev_deps addon_prefs.pydeps_path = path_addon_dev_deps
if path_addon_cache_path is not None:
addon_prefs.addon_cache_path = path_addon_cache_path
# Save User Preferences # Save User Preferences
bpy.ops.wm.save_userpref() bpy.ops.wm.save_userpref()
@ -92,7 +98,9 @@ if __name__ == '__main__':
) as path_zipped: ) as path_zipped:
install_and_enable_addon(info.ADDON_NAME, path_zipped) install_and_enable_addon(info.ADDON_NAME, path_zipped)
setup_for_development(info.ADDON_NAME, info.PATH_ADDON_DEV_DEPS) setup_for_development(
info.ADDON_NAME, info.PATH_ADDON_DEV_DEPS, info.PATH_ADDON_DEV_CACHE
)
bpy.ops.wm.quit_blender() bpy.ops.wm.quit_blender()
sys.exit(info.STATUS_INSTALLED_ADDON) sys.exit(info.STATUS_INSTALLED_ADDON)

View File

@ -77,4 +77,5 @@ BOOTSTRAP_LOG_LEVEL_FILENAME = '.bootstrap_log_level'
PATH_ADDON_DEV_BLEND = PATH_DEV / 'demo.blend' PATH_ADDON_DEV_BLEND = PATH_DEV / 'demo.blend'
PATH_ADDON_DEV_DEPS = PATH_DEV / '.cached-dev-dependencies' PATH_ADDON_DEV_DEPS = PATH_DEV / '.cached-dev-dependencies'
PATH_ADDON_DEV_CACHE = PATH_DEV / '.dev-addon-cache'
PATH_ADDON_DEV_DEPS.mkdir(exist_ok=True) PATH_ADDON_DEV_DEPS.mkdir(exist_ok=True)