fix: Various critical fixes, field preview

main
Sofus Albert Høgsbro Rose 2024-04-10 10:46:55 +02:00
parent 1d90f9ca7c
commit dc76ab7688
Signed by: so-rose
GPG Key ID: AD901CB0F3701434
44 changed files with 822 additions and 881 deletions

26
TODO.md
View File

@ -27,10 +27,11 @@
- [ ] Implement caching, such that the file will only download if the file doesn't already exist.
- [ ] Have a visual indicator for the current download status, with a manual re-download button.
- [ ] File Import / Material Import
- [ ] Dropdown to choose import format
- [ ] File Import / Tidy3D File Import
- [ ] HDF and JSON file support, with appropriate choice of loose output socket.
- [x] File Import / Material Import
- [x] Dropdown to choose import format
- MERGED w/TIDY3D FILE IMPORT
- [x] File Import / Tidy3D File Import
- [x] HDF and JSON file support, with appropriate choice of loose output socket.
- [ ] File Import / Array File Import
- [ ] Standardize 1D and 2D array loading/saving on numpy's savetxt with gzip enabled.
- [ ] Implement unit system input to guide conversion from numpy data type.
@ -40,8 +41,7 @@
## Outputs
- [x] Viewer
- [ ] Remove image preview when disabling plots.
- [ ] BUG: CTRL+SHIFT+CLICK not on a node shows an error; should just do nothing.
- [ ] Auto-enable 3D preview when creating.
- [x] Auto-enable 3D preview when creating.
- [ ] Test/support multiple viewers at the same time.
- [ ] Pop-up w/multiline string as alternative to console print.
@ -349,6 +349,7 @@
- [ ] Document the node tree cache semantics thoroughly; it's a VERY nuanced piece of logic, and its invariants may not survive Blender versions / the author's working memory
- [ ] Start standardizing nodes/sockets w/individualized SemVer
- Perhaps keep node / socket versions in a property, so that trying to load an incompatible major version hop can error w/indicator of where to find a compatible `blender_maxwell` version.
- [ ] `log.error` should invoke `self.report` in some Blender operator - used for errors that are due to usage error (which can't simply be prevented with UX design, like text file formatting of import), not due to error in the program.
## Documentation
- [ ] Make all modules available
@ -456,18 +457,31 @@ We're trying to do our part by reporting bugs we find!
This is where we keep track of them for now.
## Blender Maxwell Bugs
- [ ] BUG: CTRL+SHIFT+CLICK not on a node shows an error; should just do nothing.
- [ ] Slow changing of socket sets / range on wave constant.
- [ ] API auth shouldn't show if everything is fine in Cloud Task socket
- [ ] Cloud task socket loads folders before its node shows, which can be slow (and error prone if offline)
- [ ] Dispersive fit is slow, which means lag on normal operations that rely on the fit result - fit computation should be integrated into the node, and the output socket should only appear when the fit is available.
- [ ] Numerical, Physical Constant is missing entries
BROKE NODES
- [ ] Numerical constant doesn't switch types
- [ ] Blender constant is inexplicably mega laggy
- [ ] Web importer is just wonky in general
- [ ] JSON File exporter is having trouble with generic types (is that bad?)
- [ ] Extact Data needs flux settings
- [ ] Point dipole still has no preview
- [ ] Plane wave math still doesn't work and it has no preview
- [ ] Monitors need a way of setting infinite dimensions
## Blender Bugs
Reported:
- (SOLVED) <https://projects.blender.org/blender/blender/issues/119664>
Unreported:
- The `__mp_main__` bug.
- Animated properties within custom node trees don't update with the frame. See: <https://projects.blender.org/blender/blender/issues/66392>
## Tidy3D bugs
Unreported:

View File

@ -22,6 +22,7 @@ dependencies = [
"idna==3.3",
"charset-normalizer==2.0.10",
"certifi==2021.10.8",
"jax[cpu]>=0.4.26",
]
readme = "README.md"
requires-python = "~= 3.11"

View File

@ -46,6 +46,9 @@ idna==3.3
importlib-metadata==6.11.0
# via dask
# via tidy3d
jax==0.4.26
jaxlib==0.4.26
# via jax
jmespath==1.0.1
# via boto3
# via botocore
@ -55,18 +58,27 @@ locket==1.0.0
# via partd
matplotlib==3.8.3
# via tidy3d
ml-dtypes==0.4.0
# via jax
# via jaxlib
mpmath==1.3.0
# via sympy
networkx==3.2
numpy==1.24.3
# via contourpy
# via h5py
# via jax
# via jaxlib
# via matplotlib
# via ml-dtypes
# via opt-einsum
# via scipy
# via shapely
# via tidy3d
# via trimesh
# via xarray
opt-einsum==3.3.0
# via jax
packaging==24.0
# via dask
# via h5netcdf
@ -112,6 +124,8 @@ ruff==0.3.2
s3transfer==0.5.2
# via boto3
scipy==1.12.0
# via jax
# via jaxlib
# via tidy3d
shapely==2.0.3
# via tidy3d

View File

@ -45,6 +45,9 @@ idna==3.3
importlib-metadata==6.11.0
# via dask
# via tidy3d
jax==0.4.26
jaxlib==0.4.26
# via jax
jmespath==1.0.1
# via boto3
# via botocore
@ -54,18 +57,27 @@ locket==1.0.0
# via partd
matplotlib==3.8.3
# via tidy3d
ml-dtypes==0.4.0
# via jax
# via jaxlib
mpmath==1.3.0
# via sympy
networkx==3.2
numpy==1.24.3
# via contourpy
# via h5py
# via jax
# via jaxlib
# via matplotlib
# via ml-dtypes
# via opt-einsum
# via scipy
# via shapely
# via tidy3d
# via trimesh
# via xarray
opt-einsum==3.3.0
# via jax
packaging==24.0
# via dask
# via h5netcdf
@ -110,6 +122,8 @@ rtree==1.2.0
s3transfer==0.5.2
# via boto3
scipy==1.12.0
# via jax
# via jaxlib
# via tidy3d
shapely==2.0.3
# via tidy3d

View File

@ -49,7 +49,7 @@ class GeoNodes(enum.StrEnum):
StructurePrimitiveCone = '_structure_primitive_cone'
## Monitor
MonitorEHField = '_monitor_eh_field'
MonitorFieldPowerFlux = '_monitor_field_power_flux'
MonitorPowerFlux = '_monitor_power_flux'
MonitorEpsTensor = '_monitor_eps_tensor'
MonitorDiffraction = '_monitor_diffraction'
MonitorProjCartEHField = '_monitor_proj_eh_field'
@ -114,7 +114,7 @@ GN_PARENT_PATHS: dict[GeoNodes, Path] = {
GeoNodes.StructurePrimitiveCone: GN_INTERNAL_STRUCTURES_PATH,
## Monitor
GeoNodes.MonitorEHField: GN_INTERNAL_STRUCTURES_PATH,
GeoNodes.MonitorFieldPowerFlux: GN_INTERNAL_STRUCTURES_PATH,
GeoNodes.MonitorPowerFlux: GN_INTERNAL_STRUCTURES_PATH,
GeoNodes.MonitorEpsTensor: GN_INTERNAL_STRUCTURES_PATH,
GeoNodes.MonitorDiffraction: GN_INTERNAL_STRUCTURES_PATH,
GeoNodes.MonitorProjCartEHField: GN_INTERNAL_STRUCTURES_PATH,

View File

@ -5,6 +5,7 @@ import typing as typ
from types import MappingProxyType
# import colour ## TODO
import jax
import numpy as np
import sympy as sp
import sympy.physics.units as spu
@ -105,7 +106,7 @@ class DataValueArray:
"""A simple, flat array of values with an optionally-attached unit.
Attributes:
values: A 1D array-like object of arbitrary numerical type.
values: An ND array-like object of arbitrary numerical type.
unit: A `sympy` unit.
None if unitless.
"""
@ -181,12 +182,14 @@ class LazyDataValueRange:
scaling: typx.Literal['lin', 'geom', 'log'] = 'lin'
has_unit: bool = False
unit: spu.Quantity = False
def rescale_to_unit(self, unit: spu.Quantity) -> typ.Self:
if self.has_unit:
return LazyDataValueRange(
symbols=self.symbols,
has_unit=self.has_unit,
unit=unit,
start=spu.convert_to(self.start, unit),
stop=spu.convert_to(self.stop, unit),
steps=self.steps,
@ -205,8 +208,13 @@ class LazyDataValueRange:
return LazyDataValueRange(
symbols=self.symbols,
has_unit=self.has_unit,
start=bound_cb(self.start if not reverse else self.stop),
stop=bound_cb(self.stop if not reverse else self.start),
unit=self.unit,
start=spu.convert_to(
bound_cb(self.start if not reverse else self.stop), self.unit
),
stop=spu.convert_to(
bound_cb(self.stop if not reverse else self.start), self.unit
),
steps=self.steps,
scaling=self.scaling,
)
@ -276,3 +284,37 @@ class LazyDataValueSpectrum:
values=self.as_func(*list(symbol_values.values())),
values_unit=self.value_unit,
)
#
#
#####################
## - Data Pipeline
#####################
# @dataclasses.dataclass(frozen=True, kw_only=True)
# class DataPipelineDim:
# unit: spu.Quantity | None
#
# class DataPipelineDimType(enum.StrEnum):
# # Map Inputs
# Time = enum.auto()
# Freq = enum.auto()
# Space3D = enum.auto()
# DiffOrder = enum.auto()
#
# # Map Inputs
# Power = enum.auto()
# EVec = enum.auto()
# HVec = enum.auto()
# RelPerm = enum.auto()
#
#
# @dataclasses.dataclass(frozen=True, kw_only=True)
# class LazyDataPipeline:
# dims: list[DataPipelineDim]
#
# def _callable(self):
# """JITs the current pipeline of functions with `jax`."""
#
# def __call__(self):
# pass

View File

@ -1,18 +1,19 @@
from .node_cats import NodeCategory as NC
NODE_CAT_LABELS = {
# Analysis/
NC.MAXWELLSIM_ANALYSIS: 'Analysis',
NC.MAXWELLSIM_ANALYSIS_MATH: 'Math',
# Inputs/
NC.MAXWELLSIM_INPUTS: 'Inputs',
NC.MAXWELLSIM_INPUTS_IMPORTERS: 'Importers',
NC.MAXWELLSIM_INPUTS_SCENE: 'Scene',
NC.MAXWELLSIM_INPUTS_PARAMETERS: 'Parameters',
NC.MAXWELLSIM_INPUTS_CONSTANTS: 'Constants',
NC.MAXWELLSIM_INPUTS_LISTS: 'Lists',
NC.MAXWELLSIM_INPUTS_FILEIMPORTERS: 'File Importers',
NC.MAXWELLSIM_INPUTS_WEBIMPORTERS: 'Web Importers',
# Outputs/
NC.MAXWELLSIM_OUTPUTS: 'Outputs',
NC.MAXWELLSIM_OUTPUTS_VIEWERS: 'Viewers',
NC.MAXWELLSIM_OUTPUTS_EXPORTERS: 'Exporters',
NC.MAXWELLSIM_OUTPUTS_PLOTTERS: 'Plotters',
NC.MAXWELLSIM_OUTPUTS_FILEEXPORTERS: 'File Exporters',
NC.MAXWELLSIM_OUTPUTS_WEBEXPORTERS: 'Web Exporters',
# Sources/
NC.MAXWELLSIM_SOURCES: 'Sources',
NC.MAXWELLSIM_SOURCES_TEMPORALSHAPES: 'Temporal Shapes',
@ -27,14 +28,10 @@ NODE_CAT_LABELS = {
NC.MAXWELLSIM_BOUNDS_BOUNDCONDS: 'Bound Conds',
# Monitors/
NC.MAXWELLSIM_MONITORS: 'Monitors',
NC.MAXWELLSIM_MONITORS_NEARFIELDPROJECTIONS: 'Near-Field Projections',
NC.MAXWELLSIM_MONITORS_PROJECTED: 'Projected',
# Simulations/
NC.MAXWELLSIM_SIMS: 'Simulations',
NC.MAXWELLSIM_SIMGRIDAXES: 'Sim Grid Axes',
NC.MAXWELLSIM_SIMS_SIMGRIDAXES: 'Sim Grid Axes',
# Utilities/
NC.MAXWELLSIM_UTILITIES: 'Utilities',
NC.MAXWELLSIM_UTILITIES_CONVERTERS: 'Converters',
NC.MAXWELLSIM_UTILITIES_OPERATIONS: 'Operations',
# Viz/
NC.MAXWELLSIM_VIZ: 'Viz',
}

View File

@ -7,19 +7,21 @@ from ....utils.blender_type_enum import BlenderTypeEnum, wrap_values_in_MT
class NodeCategory(BlenderTypeEnum):
MAXWELLSIM = enum.auto()
# Analysis/
MAXWELLSIM_ANALYSIS = enum.auto()
MAXWELLSIM_ANALYSIS_MATH = enum.auto()
# Inputs/
MAXWELLSIM_INPUTS = enum.auto()
MAXWELLSIM_INPUTS_IMPORTERS = enum.auto()
MAXWELLSIM_INPUTS_SCENE = enum.auto()
MAXWELLSIM_INPUTS_PARAMETERS = enum.auto()
MAXWELLSIM_INPUTS_CONSTANTS = enum.auto()
MAXWELLSIM_INPUTS_LISTS = enum.auto()
MAXWELLSIM_INPUTS_FILEIMPORTERS = enum.auto()
MAXWELLSIM_INPUTS_WEBIMPORTERS = enum.auto()
# Outputs/
MAXWELLSIM_OUTPUTS = enum.auto()
MAXWELLSIM_OUTPUTS_VIEWERS = enum.auto()
MAXWELLSIM_OUTPUTS_EXPORTERS = enum.auto()
MAXWELLSIM_OUTPUTS_PLOTTERS = enum.auto()
MAXWELLSIM_OUTPUTS_FILEEXPORTERS = enum.auto()
MAXWELLSIM_OUTPUTS_WEBEXPORTERS = enum.auto()
# Sources/
MAXWELLSIM_SOURCES = enum.auto()
@ -39,19 +41,14 @@ class NodeCategory(BlenderTypeEnum):
# Monitors/
MAXWELLSIM_MONITORS = enum.auto()
MAXWELLSIM_MONITORS_NEARFIELDPROJECTIONS = enum.auto()
MAXWELLSIM_MONITORS_PROJECTED = enum.auto()
# Simulations/
MAXWELLSIM_SIMS = enum.auto()
MAXWELLSIM_SIMGRIDAXES = enum.auto()
MAXWELLSIM_SIMS_SIMGRIDAXES = enum.auto()
# Utilities/
MAXWELLSIM_UTILITIES = enum.auto()
MAXWELLSIM_UTILITIES_CONVERTERS = enum.auto()
MAXWELLSIM_UTILITIES_OPERATIONS = enum.auto()
# Viz/
MAXWELLSIM_VIZ = enum.auto()
@classmethod
def get_tree(cls):

View File

@ -8,140 +8,114 @@ from ....utils.blender_type_enum import (
@append_cls_name_to_values
class NodeType(BlenderTypeEnum):
KitchenSink = enum.auto()
#KitchenSink = enum.auto()
# Analysis
Viz = enum.auto()
ExtractData = enum.auto()
## Analysis / Math
MapMath = enum.auto()
FilterMath = enum.auto()
ReduceMath = enum.auto()
OperateMath = enum.auto()
# Inputs
WaveConstant = enum.auto()
UnitSystem = enum.auto()
## Inputs / Scene
Time = enum.auto()
#Time = enum.auto()
## Inputs / Web Importers
Tidy3DWebImporter = enum.auto()
## Inputs / File Importers
Tidy3DFileImporter = enum.auto()
## Inputs / Parameters
NumberParameter = enum.auto()
PhysicalParameter = enum.auto()
## Inputs / Constants
WaveConstant = enum.auto()
ScientificConstant = enum.auto()
NumberConstant = enum.auto()
PhysicalConstant = enum.auto()
BlenderConstant = enum.auto()
## Inputs / Lists
RealList = enum.auto()
ComplexList = enum.auto()
# Outputs
## Outputs / Viewers
Viewer = enum.auto()
ValueViewer = enum.auto()
ConsoleViewer = enum.auto()
## Outputs / Exporters
JSONFileExporter = enum.auto()
## Outputs / File Exporters
Tidy3DWebExporter = enum.auto()
## Outputs / Web Exporters
JSONFileExporter = enum.auto()
# Sources
## Sources / Temporal Shapes
GaussianPulseTemporalShape = enum.auto()
ContinuousWaveTemporalShape = enum.auto()
ListTemporalShape = enum.auto()
## Sources /
PointDipoleSource = enum.auto()
UniformCurrentSource = enum.auto()
PlaneWaveSource = enum.auto()
ModeSource = enum.auto()
GaussianBeamSource = enum.auto()
AstigmaticGaussianBeamSource = enum.auto()
TFSFSource = enum.auto()
EHEquivalenceSource = enum.auto()
EHSource = enum.auto()
UniformCurrentSource = enum.auto()
#ModeSource = enum.auto()
#GaussianBeamSource = enum.auto()
#AstigmaticGaussianBeamSource = enum.auto()
#TFSFSource = enum.auto()
#EHEquivalenceSource = enum.auto()
#EHSource = enum.auto()
## Sources / Temporal Shapes
GaussianPulseTemporalShape = enum.auto()
#ContinuousWaveTemporalShape = enum.auto()
#ArrayTemporalShape = enum.auto()
# Mediums
LibraryMedium = enum.auto()
PECMedium = enum.auto()
IsotropicMedium = enum.auto()
AnisotropicMedium = enum.auto()
TripleSellmeierMedium = enum.auto()
SellmeierMedium = enum.auto()
PoleResidueMedium = enum.auto()
DrudeMedium = enum.auto()
DrudeLorentzMedium = enum.auto()
DebyeMedium = enum.auto()
#PECMedium = enum.auto()
#IsotropicMedium = enum.auto()
#AnisotropicMedium = enum.auto()
#TripleSellmeierMedium = enum.auto()
#SellmeierMedium = enum.auto()
#PoleResidueMedium = enum.auto()
#DrudeMedium = enum.auto()
#DrudeLorentzMedium = enum.auto()
#DebyeMedium = enum.auto()
## Mediums / Non-Linearities
AddNonLinearity = enum.auto()
ChiThreeSusceptibilityNonLinearity = enum.auto()
TwoPhotonAbsorptionNonLinearity = enum.auto()
KerrNonLinearity = enum.auto()
#AddNonLinearity = enum.auto()
#ChiThreeSusceptibilityNonLinearity = enum.auto()
#TwoPhotonAbsorptionNonLinearity = enum.auto()
#KerrNonLinearity = enum.auto()
# Structures
ObjectStructure = enum.auto()
#ObjectStructure = enum.auto()
GeoNodesStructure = enum.auto()
ScriptedStructure = enum.auto()
#ScriptedStructure = enum.auto()
## Structures / Primitives
BoxStructure = enum.auto()
SphereStructure = enum.auto()
CylinderStructure = enum.auto()
#CylinderStructure = enum.auto()
# Bounds
BoundConds = enum.auto()
## Bounds / Bound Faces
## Bounds / Bound Conds
PMLBoundCond = enum.auto()
PECBoundCond = enum.auto()
PMCBoundCond = enum.auto()
BlochBoundCond = enum.auto()
PeriodicBoundCond = enum.auto()
AbsorbingBoundCond = enum.auto()
# Monitors
EHFieldMonitor = enum.auto()
FieldPowerFluxMonitor = enum.auto()
EpsilonTensorMonitor = enum.auto()
DiffractionMonitor = enum.auto()
## Monitors / Near-Field Projections
CartesianNearFieldProjectionMonitor = enum.auto()
ObservationAngleNearFieldProjectionMonitor = enum.auto()
KSpaceNearFieldProjectionMonitor = enum.auto()
PowerFluxMonitor = enum.auto()
#EpsilonTensorMonitor = enum.auto()
#DiffractionMonitor = enum.auto()
## Monitors / Projected
#CartesianNearFieldProjectionMonitor = enum.auto()
#ObservationAngleNearFieldProjectionMonitor = enum.auto()
#KSpaceNearFieldProjectionMonitor = enum.auto()
# Sims
FDTDSim = enum.auto()
SimDomain = enum.auto()
SimGrid = enum.auto()
## Sims / Sim Grid Axis
AutomaticSimGridAxis = enum.auto()
ManualSimGridAxis = enum.auto()
UniformSimGridAxis = enum.auto()
ArraySimGridAxis = enum.auto()
## Sim /
FDTDSim = enum.auto()
#AutomaticSimGridAxis = enum.auto()
#ManualSimGridAxis = enum.auto()
#UniformSimGridAxis = enum.auto()
#ArraySimGridAxis = enum.auto()
# Utilities
Combine = enum.auto()
Separate = enum.auto()
Math = enum.auto()
## Utilities / Converters
WaveConverter = enum.auto()
## Utilities / Operations
ArrayOperation = enum.auto()
# Viz
FDTDSimDataViz = enum.auto()

View File

@ -1,7 +1,11 @@
import io
import time
import typing as typ
import bpy
import jax
import jax.numpy as jnp
import matplotlib
import matplotlib.axis as mpl_ax
import numpy as np
import typing_extensions as typx
@ -14,6 +18,63 @@ log = logger.get(__name__)
AREA_TYPE = 'IMAGE_EDITOR'
SPACE_TYPE = 'IMAGE_EDITOR'
# Colormap
_MPL_CM = matplotlib.cm.get_cmap('viridis', 512)
VIRIDIS_COLORMAP = jnp.array([_MPL_CM(i)[:3] for i in range(512)])
def apply_colormap(normalized_data, colormap):
# Linear interpolation between colormap points
n_colors = colormap.shape[0]
indices = normalized_data * (n_colors - 1)
lower_idx = jnp.floor(indices).astype(jnp.int32)
upper_idx = jnp.ceil(indices).astype(jnp.int32)
alpha = indices - lower_idx
lower_colors = jax.vmap(lambda i: colormap[i])(lower_idx)
upper_colors = jax.vmap(lambda i: colormap[i])(upper_idx)
return (1 - alpha)[..., None] * lower_colors + alpha[..., None] * upper_colors
@jax.jit
def rgba_image_from_xyzf__viridis(xyz_freq):
amplitude = jnp.abs(jnp.squeeze(xyz_freq))
amplitude_normalized = (amplitude - amplitude.min()) / (
amplitude.max() - amplitude.min()
)
rgb_array = apply_colormap(amplitude_normalized, VIRIDIS_COLORMAP)
alpha_channel = jnp.ones_like(amplitude_normalized)
return jnp.dstack((rgb_array, alpha_channel))
@jax.jit
def rgba_image_from_xyzf__grayscale(xyz_freq):
amplitude = jnp.abs(jnp.squeeze(xyz_freq))
amplitude_normalized = (amplitude - amplitude.min()) / (
amplitude.max() - amplitude.min()
)
rgb_array = jnp.stack([amplitude_normalized] * 3, axis=-1)
alpha_channel = jnp.ones_like(amplitude_normalized)
return jnp.dstack((rgb_array, alpha_channel))
def rgba_image_from_xyzf(xyz_freq, colormap: str | None = None):
"""RGBA Image from Squeezable XYZ-Freq w/fixed freq.
Parameters:
xyz_freq: Shape (xlen, ylen, zlen), one dimension has length 1.
width_px: Pixel width to resize the image to.
height: Pixel height to resize the image to.
Returns:
Image as a JAX array of shape (height, width, 3)
"""
if colormap == 'VIRIDIS':
return rgba_image_from_xyzf__viridis(xyz_freq)
if colormap == 'GRAYSCALE':
return rgba_image_from_xyzf__grayscale(xyz_freq)
class ManagedBLImage(ct.schemas.ManagedObj):
managed_obj_type = ct.ManagedObjType.ManagedBLImage
@ -81,6 +142,7 @@ class ManagedBLImage(ct.schemas.ManagedObj):
self.name,
width=width_px,
height=height_px,
float_buffer=dtype == 'float32',
)
return bl_image
@ -120,18 +182,14 @@ class ManagedBLImage(ct.schemas.ManagedObj):
self.preview_space.image = bl_image
####################
# - Special Methods
# - Image Geometry
####################
def mpl_plot_to_image(
def gen_image_geometry(
self,
func_plotter: typ.Callable[[mpl_ax.Axis], None],
width_inches: float | None = None,
height_inches: float | None = None,
dpi: int | None = None,
bl_select: bool = False,
):
import matplotlib.pyplot as plt
# Compute Image Geometry
if preview_area := self.preview_area:
# Retrieve DPI from Blender Preferences
@ -159,44 +217,103 @@ class ManagedBLImage(ct.schemas.ManagedObj):
msg = 'There must either be a preview area, or defined `width_inches`, `height_inches`, and `dpi`'
raise ValueError(msg)
# Compute Plot Dimensions
aspect_ratio = _width_inches / _height_inches
log.debug(
'Create MPL Axes (aspect=%d, width=%d, height=%d)',
aspect_ratio,
_width_inches,
_height_inches,
return aspect_ratio, _dpi, _width_inches, _height_inches, width_px, height_px
####################
# - Special Methods
####################
def xyzf_to_image(
self, xyz_freq, colormap: str | None = 'VIRIDIS', bl_select: bool = False
):
self.data_to_image(
lambda _: rgba_image_from_xyzf(xyz_freq, colormap=colormap),
bl_select=bl_select,
)
def data_to_image(
self,
func_image_data: typ.Callable[[int], np.array],
bl_select: bool = False,
):
# time_start = time.perf_counter()
image_data = func_image_data(4)
width_px = image_data.shape[1]
height_px = image_data.shape[0]
# log.debug('Computed Image Data (%f)', time.perf_counter() - time_start)
bl_image = self.bl_image(width_px, height_px, 'RGBA', 'float32')
bl_image.pixels.foreach_set(np.float32(image_data).ravel())
bl_image.update()
# log.debug('Set BL Image (%f)', time.perf_counter() - time_start)
if bl_select:
self.bl_select()
def mpl_plot_to_image(
self,
func_plotter: typ.Callable[[mpl_ax.Axis], None],
width_inches: float | None = None,
height_inches: float | None = None,
dpi: int | None = None,
bl_select: bool = False,
):
# time_start = time.perf_counter()
import matplotlib.pyplot as plt
# log.debug('Imported PyPlot (%f)', time.perf_counter() - time_start)
# Compute Plot Dimensions
aspect_ratio, _dpi, _width_inches, _height_inches, width_px, height_px = (
self.gen_image_geometry(width_inches, height_inches, dpi)
)
# log.debug('Computed MPL Geometry (%f)', time.perf_counter() - time_start)
#log.debug(
# 'Creating MPL Axes (aspect=%f, width=%f, height=%f)',
# aspect_ratio,
# _width_inches,
# _height_inches,
#)
# Create MPL Figure, Axes, and Compute Figure Geometry
fig, ax = plt.subplots(
figsize=[_width_inches, _height_inches],
dpi=_dpi,
)
# log.debug('Created MPL Axes (%f)', time.perf_counter() - time_start)
ax.set_aspect(aspect_ratio)
cmp_width_px, cmp_height_px = fig.canvas.get_width_height()
## Use computed pixel w/h to preempt off-by-one size errors.
ax.set_aspect('auto') ## Workaround aspect-ratio bugs
# log.debug('Set MPL Aspect (%f)', time.perf_counter() - time_start)
# Plot w/User Parameter
func_plotter(ax)
# log.debug('User Plot Function (%f)', time.perf_counter() - time_start)
# Save Figure to BytesIO
with io.BytesIO() as buff:
# log.debug('Made BytesIO (%f)', time.perf_counter() - time_start)
fig.savefig(buff, format='raw', dpi=dpi)
# log.debug('Saved Figure to BytesIO (%f)', time.perf_counter() - time_start)
buff.seek(0)
image_data = np.frombuffer(
buff.getvalue(),
dtype=np.uint8,
).reshape([cmp_height_px, cmp_width_px, -1])
# log.debug('Set Image Data (%f)', time.perf_counter() - time_start)
image_data = np.flipud(image_data).astype(np.float32) / 255
# log.debug('Flipped Image Data (%f)', time.perf_counter() - time_start)
plt.close(fig)
# Optimized Write to Blender Image
bl_image = self.bl_image(cmp_width_px, cmp_height_px, 'RGBA', 'uint8')
# log.debug('Made BL Image (%f)', time.perf_counter() - time_start)
bl_image.pixels.foreach_set(image_data.ravel())
# log.debug('Set BL Image Pixels (%f)', time.perf_counter() - time_start)
bl_image.update()
# log.debug('Updated BL Image (%f)', time.perf_counter() - time_start)
if bl_select:
self.bl_select()

View File

@ -1,7 +1,7 @@
# from . import kitchen_sink
# from . import bounds
from . import (
analysis,
inputs,
mediums,
monitors,
@ -10,11 +10,11 @@ from . import (
sources,
structures,
utilities,
viz,
)
BL_REGISTER = [
# *kitchen_sink.BL_REGISTER,
*analysis.BL_REGISTER,
*inputs.BL_REGISTER,
*outputs.BL_REGISTER,
*sources.BL_REGISTER,
@ -24,10 +24,10 @@ BL_REGISTER = [
*monitors.BL_REGISTER,
*simulations.BL_REGISTER,
*utilities.BL_REGISTER,
*viz.BL_REGISTER,
]
BL_NODES = {
# **kitchen_sink.BL_NODES,
**analysis.BL_NODES,
**inputs.BL_NODES,
**outputs.BL_NODES,
**sources.BL_NODES,
@ -37,5 +37,4 @@ BL_NODES = {
**monitors.BL_NODES,
**simulations.BL_NODES,
**utilities.BL_NODES,
**viz.BL_NODES,
}

View File

@ -0,0 +1,10 @@
from . import extract_data, viz
BL_REGISTER = [
*extract_data.BL_REGISTER,
*viz.BL_REGISTER,
]
BL_NODES = {
**extract_data.BL_NODES,
**viz.BL_NODES,
}

View File

@ -0,0 +1,239 @@
import typing as typ
import bpy
from .....utils import logger
from ... import contracts as ct
from ... import managed_objs, sockets
from .. import base, events
log = logger.get(__name__)
CACHE_SIM_DATA = {}
class ExtractDataNode(base.MaxwellSimNode):
"""Node for visualizing simulation data, by querying its monitors."""
node_type = ct.NodeType.ExtractData
bl_label = 'Extract Data'
input_socket_sets: typ.ClassVar = {
'Sim Data': {'Sim Data': sockets.MaxwellFDTDSimDataSocketDef()},
'Field Data': {'Field Data': sockets.AnySocketDef()},
}
output_sockets: typ.ClassVar = {
'Data': sockets.AnySocketDef(),
}
####################
# - Properties: Sim Data
####################
sim_data__monitor_name: bpy.props.EnumProperty(
name='Sim Data Monitor Name',
description='Monitor to extract from the attached SimData',
items=lambda self, context: self.search_monitors(context),
update=lambda self, context: self.sync_prop('sim_data__monitor_name', context),
)
cache__num_monitors: bpy.props.StringProperty(default='')
cache__monitor_names: bpy.props.StringProperty(default='')
cache__monitor_types: bpy.props.StringProperty(default='')
def search_monitors(self, _: bpy.types.Context) -> list[tuple[str, str, str]]:
"""Search the linked simulation data for monitors."""
# No Linked Sim Data: Return 'None'
if not self.inputs.get('Sim Data') or not self.inputs['Sim Data'].is_linked:
return [('NONE', 'None', 'No monitors')]
# Return Monitor Names
## Special Case for No Monitors
monitor_names = (
self.cache__monitor_names.split(',') if self.cache__monitor_names else []
)
monitor_types = (
self.cache__monitor_types.split(',') if self.cache__monitor_types else []
)
if len(monitor_names) == 0:
return [('NONE', 'None', 'No monitors')]
return [
(
monitor_name,
f'{monitor_name}',
f'Monitor "{monitor_name}" ({monitor_type}) recorded by the Sim',
)
for monitor_name, monitor_type in zip(
monitor_names, monitor_types, strict=False
)
]
def draw_props__sim_data(
self, _: bpy.types.Context, col: bpy.types.UILayout
) -> None:
col.prop(self, 'sim_data__monitor_name', text='')
def draw_info__sim_data(
self, _: bpy.types.Context, col: bpy.types.UILayout
) -> None:
if self.sim_data__monitor_name != 'NONE':
# Header
row = col.row()
row.alignment = 'CENTER'
row.label(text=f'{self.cache__num_monitors} Monitors')
# Monitor Info
if int(self.cache__num_monitors) > 0:
for monitor_name, monitor_type in zip(
self.cache__monitor_names.split(','),
self.cache__monitor_types.split(','),
strict=False,
):
col.label(text=f'{monitor_name}: {monitor_type}')
####################
# - Events: Sim Data
####################
@events.on_value_changed(
socket_name='Sim Data',
)
def on_sim_data_changed(self):
# SimData Cache Hit and SimData Input Unlinked
## Delete Cache Entry
if (
CACHE_SIM_DATA.get(self.instance_id) is not None
and not self.inputs['Sim Data'].is_linked
):
CACHE_SIM_DATA.pop(self.instance_id, None) ## Both member-check
self.cache__num_monitors = ''
self.cache__monitor_names = ''
self.cache__monitor_types = ''
# SimData Cache Miss and Linked SimData
if (
CACHE_SIM_DATA.get(self.instance_id) is None
and self.inputs['Sim Data'].is_linked
):
sim_data = self._compute_input('Sim Data')
## Create Cache Entry
CACHE_SIM_DATA[self.instance_id] = {
'sim_data': sim_data,
'monitor_names': list(sim_data.monitor_data.keys()),
'monitor_types': [
monitor_data.type for monitor_data in sim_data.monitor_data.values()
],
}
cache = CACHE_SIM_DATA[self.instance_id]
self.cache__num_monitors = str(len(cache['monitor_names']))
self.cache__monitor_names = ','.join(cache['monitor_names'])
self.cache__monitor_types = ','.join(cache['monitor_types'])
####################
# - Properties: Field Data
####################
field_data__component: bpy.props.EnumProperty(
name='Field Data Component',
description='Field monitor component to extract from the attached Field Data',
items=lambda self, context: self.search_field_data_components(context),
update=lambda self, context: self.sync_prop('field_data__component', context),
)
cache__components: bpy.props.StringProperty(default='')
def search_field_data_components(
self, _: bpy.types.Context
) -> list[tuple[str, str, str]]:
if not self.inputs.get('Field Data') or not self.inputs['Field Data'].is_linked:
return [('NONE', 'None', 'No data')]
if not self.cache__components:
return [('NONE', 'Loading...', 'Loading data...')]
components = [
tuple(component_str.split(','))
for component_str in self.cache__components.split('|')
]
if len(components) == 0:
return [('NONE', 'None', 'No components')]
return components
def draw_props__field_data(
self, _: bpy.types.Context, col: bpy.types.UILayout
) -> None:
col.prop(self, 'field_data__component', text='')
def draw_info__field_data(
self, _: bpy.types.Context, col: bpy.types.UILayout
) -> None:
pass
####################
# - Events: Field Data
####################
@events.on_value_changed(
socket_name='Field Data',
)
def on_field_data_changed(self):
if self.inputs['Field Data'].is_linked and not self.cache__components:
field_data = self._compute_input('Field Data')
components = [
*([('Ex', 'Ex', 'Ex')] if field_data.Ex is not None else []),
*([('Ey', 'Ey', 'Ey')] if field_data.Ey is not None else []),
*([('Ez', 'Ez', 'Ez')] if field_data.Ez is not None else []),
*([('Hx', 'Hx', 'Hx')] if field_data.Hx is not None else []),
*([('Hy', 'Hy', 'Hy')] if field_data.Hy is not None else []),
*([('Hz', 'Hz', 'Hz')] if field_data.Hz is not None else []),
]
self.cache__components = '|'.join(
[','.join(component) for component in components]
)
elif not self.inputs['Field Data'].is_linked and self.cache__components:
self.cache__components = ''
####################
# - Global
####################
def draw_props(self, context: bpy.types.Context, col: bpy.types.UILayout) -> None:
if self.active_socket_set == 'Sim Data':
self.draw_props__sim_data(context, col)
if self.active_socket_set == 'Field Data':
self.draw_props__field_data(context, col)
def draw_info(self, context: bpy.types.Context, col: bpy.types.UILayout) -> None:
if self.active_socket_set == 'Sim Data':
self.draw_info__sim_data(context, col)
if self.active_socket_set == 'Field Data':
self.draw_info__field_data(context, col)
@events.computes_output_socket(
'Data',
props={'sim_data__monitor_name', 'field_data__component'},
)
def compute_extracted_data(self, props: dict):
if self.active_socket_set == 'Sim Data':
if (
CACHE_SIM_DATA.get(self.instance_id) is None
and self.inputs['Sim Data'].is_linked
):
self.on_sim_data_changed()
sim_data = CACHE_SIM_DATA[self.instance_id]['sim_data']
return sim_data.monitor_data[props['sim_data__monitor_name']]
elif self.active_socket_set == 'Field Data': # noqa: RET505
field_data = self._compute_input('Field Data')
return getattr(field_data, props['field_data__component'])
msg = f'Tried to get data from unknown output socket in "{self.bl_label}"'
raise RuntimeError(msg)
####################
# - Blender Registration
####################
BL_REGISTER = [
ExtractDataNode,
]
BL_NODES = {ct.NodeType.ExtractData: (ct.NodeCategory.MAXWELLSIM_ANALYSIS)}

View File

@ -0,0 +1,107 @@
import typing as typ
import bpy
import jax.numpy as jnp
from .....utils import logger
from ... import contracts as ct
from ... import managed_objs, sockets
from .. import base, events
log = logger.get(__name__)
class VizNode(base.MaxwellSimNode):
"""Node for visualizing simulation data, by querying its monitors."""
node_type = ct.NodeType.Viz
bl_label = 'Viz'
####################
# - Sockets
####################
input_sockets = {
'Data': sockets.AnySocketDef(),
'Freq': sockets.PhysicalFreqSocketDef(),
}
# input_sockets_sets: typ.ClassVar = {
# '2D Freq': {
# 'Data': sockets.AnySocketDef(),
# 'Freq': sockets.PhysicalFreqSocketDef(),
# },
# }
output_sockets: typ.ClassVar = {
'Preview': sockets.AnySocketDef(),
}
managed_obj_defs: typ.ClassVar = {
'plot': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLImage(name),
),
#'empty': ct.schemas.ManagedObjDef(
# mk=lambda name: managed_objs.ManagedBLEmpty(name),
# ),
}
#####################
## - Properties
#####################
colormap: bpy.props.EnumProperty(
name='Colormap',
description='Colormap to apply to grayscale output',
items=[
('VIRIDIS', 'Viridis', 'Good default colormap'),
('GRAYSCALE', 'Grayscale', 'Barebones'),
],
default='VIRIDIS',
update=lambda self, context: self.sync_prop('colormap', context),
)
#####################
## - UI
#####################
def draw_props(self, _: bpy.types.Context, col: bpy.types.UILayout):
col.prop(self, 'colormap')
#####################
## - Plotting
#####################
@events.on_show_plot(
managed_objs={'plot'},
input_sockets={'Data', 'Freq'},
props={'colormap'},
unit_systems={'Tidy3DUnits': ct.UNITS_TIDY3D},
scale_input_sockets={
'Freq': 'Tidy3DUnits',
},
stop_propagation=True,
)
def on_show_plot(
self,
managed_objs: dict[str, ct.schemas.ManagedObj],
input_sockets: dict,
props: dict,
unit_systems: dict,
):
selected_data = jnp.array(
input_sockets['Data'].sel(f=input_sockets['Freq'], method='nearest')
)
managed_objs['plot'].xyzf_to_image(
selected_data,
colormap=props['colormap'],
bl_select=True,
)
# @events.on_init()
# def on_init(self):
# self.on_changed_inputs()
####################
# - Blender Registration
####################
BL_REGISTER = [
VizNode,
]
BL_NODES = {ct.NodeType.Viz: (ct.NodeCategory.MAXWELLSIM_ANALYSIS)}

View File

@ -105,12 +105,10 @@ class MaxwellSimNode(bpy.types.Node):
# Setup Callback Methods
cls._output_socket_methods = {
(method.extra_data['output_socket_name'], method.extra_data['kind']): method
method
for attr_name in dir(cls)
if hasattr(method := getattr(cls, attr_name), 'action_type')
and method.action_type == 'computes_output_socket'
and hasattr(method, 'extra_data')
and method.extra_data
}
cls._on_value_changed_methods = {
method
@ -553,10 +551,21 @@ class MaxwellSimNode(bpy.types.Node):
The value of the output socket, as computed by the dedicated method
registered using the `@computes_output_socket` decorator.
"""
if output_socket_method := self._output_socket_methods.get(
(output_socket_name, kind)
):
return output_socket_method(self)
possible_output_socket_methods = [
output_socket_method
for output_socket_method in self._output_socket_methods
if kind == output_socket_method.extra_data['kind']
and (
output_socket_name
== output_socket_method.extra_data['output_socket_name']
or (
output_socket_method.extra_data['any_loose_output_socket']
and output_socket_name in self.loose_output_sockets
)
)
]
if len(possible_output_socket_methods) == 1:
return possible_output_socket_methods[0](self)
msg = f'No output method for ({output_socket_name}, {kind.value!s}'
raise ValueError(msg)

View File

@ -30,6 +30,7 @@ class EventCallbackData_ComputesOutputSocket(typ.TypedDict): # noqa: N801
"""Extra data used to select a method to compute output sockets."""
output_socket_name: ct.SocketName
any_loose_output_socket: bool
kind: ct.DataFlowKind
@ -194,8 +195,8 @@ def event_decorator(
_output_sockets = {
output_socket_name: node.compute_output(
output_socket_name,
kind=input_socket_kinds.get(
input_socket_name, ct.DataFlowKind.Value
kind=output_socket_kinds.get(
output_socket_name, ct.DataFlowKind.Value
),
)
for output_socket_name in output_sockets
@ -231,7 +232,10 @@ def event_decorator(
## Compute All Loose Input Sockets
if all_loose_input_sockets:
_loose_input_sockets = {
input_socket_name: node._compute_input(input_socket_name, kind=node.inputs[input_socket_name].active_kind)
input_socket_name: node._compute_input(
input_socket_name,
kind=node.inputs[input_socket_name].active_kind,
)
for input_socket_name in node.loose_input_sockets
}
method_kw_args |= {'loose_input_sockets': _loose_input_sockets}
@ -239,7 +243,10 @@ def event_decorator(
## Compute All Loose Output Sockets
if all_loose_output_sockets:
_loose_output_sockets = {
output_socket_name: node.compute_output(output_socket_name, kind=node.outputs[output_socket_name].active_kind)
output_socket_name: node.compute_output(
output_socket_name,
kind=node.outputs[output_socket_name].active_kind,
)
for output_socket_name in node.loose_output_sockets
}
method_kw_args |= {'loose_output_sockets': _loose_output_sockets}
@ -274,7 +281,8 @@ def event_decorator(
# - Simplified Event Callbacks
####################
def computes_output_socket(
output_socket_name: ct.SocketName,
output_socket_name: ct.SocketName | None,
any_loose_output_socket: bool = False,
kind: ct.DataFlowKind = ct.DataFlowKind.Value,
**kwargs,
):
@ -282,6 +290,7 @@ def computes_output_socket(
action_type='computes_output_socket',
extra_data={
'output_socket_name': output_socket_name,
'any_loose_output_socket': any_loose_output_socket,
'kind': kind,
},
**kwargs,

View File

@ -196,9 +196,9 @@ class Tidy3DFileImporterNode(base.MaxwellSimNode):
else:
self.loose_output_sockets = {
'SIMULATION_DATA': {
'Sim Data': sockets.MaxwellFDTDSimSocketDef(),
'Sim Data': sockets.MaxwellFDTDSimDataSocketDef(),
},
'SIMULATION': {'Sim': sockets.MaxwellFDTDSimDataSocketDef()},
'SIMULATION': {'Sim': sockets.MaxwellFDTDSimSocketDef()},
'MEDIUM': {'Medium': sockets.MaxwellMediumSocketDef()},
'EXPERIM_DISP_MEDIUM': {
'Experim Disp Medium': sockets.MaxwellMediumSocketDef()
@ -245,5 +245,5 @@ BL_REGISTER = [
Tidy3DFileImporterNode,
]
BL_NODES = {
ct.NodeType.Tidy3DFileImporter: (ct.NodeCategory.MAXWELLSIM_INPUTS_IMPORTERS)
ct.NodeType.Tidy3DFileImporter: (ct.NodeCategory.MAXWELLSIM_INPUTS_FILEIMPORTERS)
}

View File

@ -35,9 +35,34 @@ class WaveConstantNode(base.MaxwellSimNode):
####################
@events.computes_output_socket(
'WL',
kind=ct.DataFlowKind.Value,
all_loose_input_sockets=True,
)
def compute_wl(self, loose_input_sockets: dict) -> sp.Expr:
def compute_wl_value(self, loose_input_sockets: dict) -> sp.Expr:
if (wl := loose_input_sockets.get('WL')) is not None:
return wl
freq = loose_input_sockets.get('Freq')
return constants.vac_speed_of_light / freq
@events.computes_output_socket(
'Freq',
kind=ct.DataFlowKind.Value,
all_loose_input_sockets=True,
)
def compute_freq_value(self, loose_input_sockets: dict) -> sp.Expr:
if (freq := loose_input_sockets.get('Freq')) is not None:
return freq
wl = loose_input_sockets.get('WL')
return constants.vac_speed_of_light / wl
@events.computes_output_socket(
'WL',
kind=ct.DataFlowKind.LazyValueRange,
all_loose_input_sockets=True,
)
def compute_wl_lazyvaluerange(self, loose_input_sockets: dict) -> sp.Expr:
if (wl := loose_input_sockets.get('WL')) is not None:
return wl
@ -52,9 +77,10 @@ class WaveConstantNode(base.MaxwellSimNode):
@events.computes_output_socket(
'Freq',
kind=ct.DataFlowKind.LazyValueRange,
all_loose_input_sockets=True,
)
def compute_freq(self, loose_input_sockets: dict) -> sp.Expr:
def compute_freq_lazyvaluerange(self, loose_input_sockets: dict) -> sp.Expr:
if (freq := loose_input_sockets.get('Freq')) is not None:
return freq

View File

@ -102,5 +102,5 @@ BL_REGISTER = [
Tidy3DWebImporterNode,
]
BL_NODES = {
ct.NodeType.Tidy3DWebImporter: (ct.NodeCategory.MAXWELLSIM_INPUTS_IMPORTERS)
ct.NodeType.Tidy3DWebImporter: (ct.NodeCategory.MAXWELLSIM_INPUTS_WEBIMPORTERS)
}

View File

@ -18,7 +18,7 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
"""Node providing for the monitoring of electromagnetic fields within a given planar region or volume."""
node_type = ct.NodeType.EHFieldMonitor
bl_label = 'E/H Field Monitor'
bl_label = 'EH Field Monitor'
use_sim_node_name = True
####################
@ -45,8 +45,9 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
),
},
}
output_sockets: typ.ClassVar = {
'Monitor': sockets.MaxwellMonitorSocketDef(),
output_socket_sets: typ.ClassVar = {
'Freq Domain': {'Freq Monitor': sockets.MaxwellMonitorSocketDef()},
'Time Domain': {'Time Monitor': sockets.MaxwellMonitorSocketDef()},
}
managed_obj_defs: typ.ClassVar = {
@ -62,63 +63,45 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
# - Output Sockets
####################
@events.computes_output_socket(
'Monitor',
props={'active_socket_set', 'sim_node_name'},
'Freq Monitor',
props={'sim_node_name'},
input_sockets={
'Rec Start',
'Rec Stop',
'Center',
'Size',
'Samples/Space',
'Samples/Time',
'Freqs',
},
input_socket_kinds={
'Freqs': ct.DataFlowKind.LazyValueRange,
},
unit_systems={'Tidy3DUnits': ct.UNITS_TIDY3D},
scale_input_sockets={
'Center': 'Tidy3DUnits',
'Size': 'Tidy3DUnits',
'Samples/Space': 'Tidy3DUnits',
'Rec Start': 'Tidy3DUnits',
'Rec Stop': 'Tidy3DUnits',
'Samples/Time': 'Tidy3DUnits',
'Freqs': 'Tidy3DUnits',
},
)
def compute_monitor(
self, input_sockets: dict, props: dict, unit_systems: dict,
) -> td.FieldMonitor | td.FieldTimeMonitor:
if props['active_socket_set'] == 'Freq Domain':
freqs = input_sockets['Freqs']
log.info(
'Computing FieldMonitor (name="%s") with center="%s", size="%s"',
props['sim_node_name'],
input_sockets['Center'],
input_sockets['Size'],
)
return td.FieldMonitor(
center=input_sockets['Center'],
size=input_sockets['Size'],
name=props['sim_node_name'],
interval_space=input_sockets['Samples/Space'],
freqs=[
float(spu.convert_to(freq, spu.hertz) / spu.hertz) for freq in freqs
],
)
## Time Domain
def compute_freq_monitor(
self,
input_sockets: dict,
props: dict,
unit_systems: dict,
) -> td.FieldMonitor:
log.info(
'Computing FieldTimeMonitor (name=%s) with center=%s, size=%s',
'Computing FieldMonitor (name="%s") with center="%s", size="%s"',
props['sim_node_name'],
input_sockets['Center'],
input_sockets['Size'],
)
return td.FieldTimeMonitor(
return td.FieldMonitor(
center=input_sockets['Center'],
size=input_sockets['Size'],
name=props['sim_node_name'],
start=input_sockets['Rec Start'],
stop=input_sockets['Rec Stop'],
interval=input_sockets['Samples/Time'],
interval_space=input_sockets['Samples/Space'],
interval_space=tuple(input_sockets['Samples/Space']),
freqs=input_sockets['Freqs'].realize().values,
#freqs=[
# float(spu.convert_to(freq, spu.hertz) / spu.hertz) for freq in freqs
#],
)
####################

View File

@ -15,9 +15,9 @@ from .. import base, events
log = logger.get(__name__)
class FieldPowerFluxMonitorNode(base.MaxwellSimNode):
node_type = ct.NodeType.FieldPowerFluxMonitor
bl_label = 'Field Power Flux Monitor'
class PowerFluxMonitorNode(base.MaxwellSimNode):
node_type = ct.NodeType.PowerFluxMonitor
bl_label = 'Power Flux Monitor'
use_sim_node_name = True
####################
@ -160,6 +160,6 @@ class FieldPowerFluxMonitorNode(base.MaxwellSimNode):
# - Blender Registration
####################
BL_REGISTER = [
FieldPowerFluxMonitorNode,
PowerFluxMonitorNode,
]
BL_NODES = {ct.NodeType.FieldPowerFluxMonitor: (ct.NodeCategory.MAXWELLSIM_MONITORS)}
BL_NODES = {ct.NodeType.PowerFluxMonitor: (ct.NodeCategory.MAXWELLSIM_MONITORS)}

View File

@ -1,10 +1,12 @@
from . import exporters, viewer
from . import file_exporters, viewer, web_exporters
BL_REGISTER = [
*viewer.BL_REGISTER,
*exporters.BL_REGISTER,
*file_exporters.BL_REGISTER,
*web_exporters.BL_REGISTER,
]
BL_NODES = {
**viewer.BL_NODES,
**exporters.BL_NODES,
**file_exporters.BL_NODES,
**web_exporters.BL_NODES,
}

View File

@ -1,10 +0,0 @@
from . import json_file_exporter, tidy3d_web_exporter
BL_REGISTER = [
*json_file_exporter.BL_REGISTER,
*tidy3d_web_exporter.BL_REGISTER,
]
BL_NODES = {
**json_file_exporter.BL_NODES,
**tidy3d_web_exporter.BL_NODES,
}

View File

@ -0,0 +1,8 @@
from . import json_file_exporter
BL_REGISTER = [
*json_file_exporter.BL_REGISTER,
]
BL_NODES = {
**json_file_exporter.BL_NODES,
}

View File

@ -98,5 +98,5 @@ BL_REGISTER = [
JSONFileExporterNode,
]
BL_NODES = {
ct.NodeType.JSONFileExporter: (ct.NodeCategory.MAXWELLSIM_OUTPUTS_EXPORTERS)
ct.NodeType.JSONFileExporter: (ct.NodeCategory.MAXWELLSIM_OUTPUTS_FILEEXPORTERS)
}

View File

@ -130,7 +130,7 @@ class ViewerNode(base.MaxwellSimNode):
)
def on_changed_plot_preview(self, props):
if self.inputs['Data'].is_linked and props['auto_plot']:
log.info('Enabling 2D Plot from "%s"', self.name)
# log.debug('Enabling 2D Plot from "%s"', self.name)
self.trigger_action(ct.DataFlowAction.ShowPlot)
@events.on_value_changed(
@ -139,22 +139,26 @@ class ViewerNode(base.MaxwellSimNode):
)
def on_changed_3d_preview(self, props):
# Unpreview Everything
node_tree = self.id_data
node_tree.unpreview_all()
if props['auto_3d_preview']:
node_tree = self.id_data
node_tree.unpreview_all()
# Trigger Preview Action
if self.inputs['Data'].is_linked and props['auto_3d_preview']:
log.info('Enabling 3D Previews from "%s"', self.name)
# log.debug('Enabling 3D Previews from "%s"', self.name)
self.trigger_action(ct.DataFlowAction.ShowPreview)
@events.on_value_changed(
socket_name='Data',
)
def on_changed_3d_data(self):
# Just Linked / Just Unlinked: Preview/Unpreview
if self.inputs['Data'].is_linked ^ self.cache__data_socket_linked:
# Is Linked: Re-Preview
if self.inputs['Data'].is_linked:
self.on_changed_3d_preview()
self.on_changed_plot_preview()
# Just Linked / Just Unlinked: Preview/Unpreview All
if self.inputs['Data'].is_linked ^ self.cache__data_socket_linked:
self.cache__data_socket_linked = self.inputs['Data'].is_linked

View File

@ -0,0 +1,8 @@
from . import tidy3d_web_exporter
BL_REGISTER = [
*tidy3d_web_exporter.BL_REGISTER,
]
BL_NODES = {
**tidy3d_web_exporter.BL_NODES,
}

View File

@ -243,7 +243,6 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
task_name=new_task[0],
cloud_folder=new_task[1],
sim=sim,
upload_progress_cb=lambda uploaded_bytes: None, ## TODO: Use!
verbose=True,
)
@ -428,5 +427,5 @@ BL_REGISTER = [
Tidy3DWebExporterNode,
]
BL_NODES = {
ct.NodeType.Tidy3DWebExporter: (ct.NodeCategory.MAXWELLSIM_OUTPUTS_EXPORTERS)
ct.NodeType.Tidy3DWebExporter: (ct.NodeCategory.MAXWELLSIM_OUTPUTS_WEBEXPORTERS)
}

View File

@ -51,7 +51,7 @@ class SimDomainNode(base.MaxwellSimNode):
'Size': 'Tidy3DUnits',
},
)
def compute_domain(self, input_sockets: dict) -> sp.Expr:
def compute_domain(self, input_sockets: dict, unit_systems) -> sp.Expr:
return {
'run_time': input_sockets['Duration'],
'center': input_sockets['Center'],
@ -96,7 +96,7 @@ class SimDomainNode(base.MaxwellSimNode):
@events.on_init()
def on_init(self):
self.on_input_change()
self.on_input_changed()
####################

View File

@ -74,7 +74,10 @@ class PointDipoleSourceNode(base.MaxwellSimNode):
},
)
def compute_source(
self, input_sockets: dict[str, typ.Any], props: dict[str, typ.Any]
self,
input_sockets: dict[str, typ.Any],
props: dict[str, typ.Any],
unit_systems: dict,
) -> td.PointDipole:
pol_axis = {
'EX': 'Ex',

View File

@ -67,7 +67,7 @@ class GaussianPulseTemporalShapeNode(base.MaxwellSimNode):
####################
# - UI
####################
def draw_props(self, context, layout):
def draw_props(self, _, layout):
layout.label(text='Plot Settings')
split = layout.split(factor=0.6)

View File

@ -76,6 +76,9 @@ class GeoNodesStructureNode(base.MaxwellSimNode):
input_sockets={'Center', 'GeoNodes'},
all_loose_input_sockets=True,
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
scale_input_sockets={
'Center': 'BlenderUnits'
}
)
def on_input_changed(
self,

View File

@ -90,27 +90,27 @@ class CombineNode(base.MaxwellSimNode):
@events.computes_output_socket(
'Sources',
input_sockets={f'Source #{i}' for i in range(MAX_AMOUNT)},
all_loose_input_sockets=True,
props={'amount'},
)
def compute_sources(self, input_sockets, props) -> sp.Expr:
return [input_sockets[f'Source #{i}'] for i in range(props['amount'])]
def compute_sources(self, loose_input_sockets, props) -> sp.Expr:
return [loose_input_sockets[f'Source #{i}'] for i in range(props['amount'])]
@events.computes_output_socket(
'Structures',
input_sockets={f'Structure #{i}' for i in range(MAX_AMOUNT)},
all_loose_input_sockets=True,
props={'amount'},
)
def compute_structures(self, input_sockets, props) -> sp.Expr:
return [input_sockets[f'Structure #{i}'] for i in range(props['amount'])]
def compute_structures(self, loose_input_sockets, props) -> sp.Expr:
return [loose_input_sockets[f'Structure #{i}'] for i in range(props['amount'])]
@events.computes_output_socket(
'Monitors',
input_sockets={f'Monitor #{i}' for i in range(MAX_AMOUNT)},
all_loose_input_sockets=True,
props={'amount'},
)
def compute_monitors(self, input_sockets, props) -> sp.Expr:
return [input_sockets[f'Monitor #{i}'] for i in range(props['amount'])]
def compute_monitors(self, loose_input_sockets, props) -> sp.Expr:
return [loose_input_sockets[f'Monitor #{i}'] for i in range(props['amount'])]
####################
# - Input Socket Compilation

View File

@ -1,8 +0,0 @@
from . import sim_data_viz
BL_REGISTER = [
*sim_data_viz.BL_REGISTER,
]
BL_NODES = {
**sim_data_viz.BL_NODES,
}

View File

@ -1,306 +0,0 @@
import typing as typ
import bpy
from ... import contracts as ct
from ... import managed_objs, sockets
from .. import base, events
CACHE = {}
class FDTDSimDataVizNode(base.MaxwellSimNode):
node_type = ct.NodeType.FDTDSimDataViz
bl_label = 'FDTD Sim Data Viz'
####################
# - Sockets
####################
input_sockets: typ.ClassVar = {
'FDTD Sim Data': sockets.MaxwellFDTDSimDataSocketDef(),
}
output_sockets: typ.ClassVar = {'Preview': sockets.AnySocketDef()}
managed_obj_defs: typ.ClassVar = {
'viz_plot': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLImage(name),
name_prefix='',
),
'viz_object': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLObject(name),
name_prefix='',
),
}
####################
# - Properties
####################
viz_monitor_name: bpy.props.EnumProperty(
name='Viz Monitor Name',
description='Monitor to visualize within the attached SimData',
items=lambda self, context: self.retrieve_monitors(context),
update=(lambda self, context: self.sync_viz_monitor_name(context)),
)
cache_viz_monitor_type: bpy.props.StringProperty(
name='Viz Monitor Type',
description='Type of the viz monitor',
default='',
)
# Field Monitor Type
field_viz_component: bpy.props.EnumProperty(
name='Field Component',
description='Field component to visualize',
items=[
('E', 'E', 'Electric'),
# ("H", "H", "Magnetic"),
# ("S", "S", "Poynting"),
('Ex', 'Ex', 'Ex'),
('Ey', 'Ey', 'Ey'),
('Ez', 'Ez', 'Ez'),
# ("Hx", "Hx", "Hx"),
# ("Hy", "Hy", "Hy"),
# ("Hz", "Hz", "Hz"),
],
default='E',
update=lambda self, context: self.sync_prop('field_viz_component', context),
)
field_viz_part: bpy.props.EnumProperty(
name='Field Part',
description='Field part to visualize',
items=[
('real', 'Real', 'Electric'),
('imag', 'Imaginary', 'Imaginary'),
('abs', 'Abs', 'Abs'),
('abs^2', 'Squared Abs', 'Square Abs'),
('phase', 'Phase', 'Phase'),
],
default='real',
update=lambda self, context: self.sync_prop('field_viz_part', context),
)
field_viz_scale: bpy.props.EnumProperty(
name='Field Scale',
description='Field scale to visualize in, Linear or Log',
items=[
('lin', 'Linear', 'Linear Scale'),
('dB', 'Log (dB)', 'Logarithmic (dB) Scale'),
],
default='lin',
update=lambda self, context: self.sync_prop('field_viz_scale', context),
)
field_viz_structure_visibility: bpy.props.FloatProperty(
name='Field Viz Plot: Structure Visibility',
description='Visibility of structes',
default=0.2,
min=0.0,
max=1.0,
update=lambda self, context: self.sync_prop('field_viz_plot_fixed_f', context),
)
field_viz_plot_fix_x: bpy.props.BoolProperty(
name='Field Viz Plot: Fix X',
description='Fix the x-coordinate on the plot',
default=False,
update=lambda self, context: self.sync_prop('field_viz_plot_fix_x', context),
)
field_viz_plot_fix_y: bpy.props.BoolProperty(
name='Field Viz Plot: Fix Y',
description='Fix the y coordinate on the plot',
default=False,
update=lambda self, context: self.sync_prop('field_viz_plot_fix_y', context),
)
field_viz_plot_fix_z: bpy.props.BoolProperty(
name='Field Viz Plot: Fix Z',
description='Fix the z coordinate on the plot',
default=False,
update=lambda self, context: self.sync_prop('field_viz_plot_fix_z', context),
)
field_viz_plot_fix_f: bpy.props.BoolProperty(
name='Field Viz Plot: Fix Freq',
description='Fix the frequency coordinate on the plot',
default=False,
update=lambda self, context: self.sync_prop('field_viz_plot_fix_f', context),
)
field_viz_plot_fixed_x: bpy.props.FloatProperty(
name='Field Viz Plot: Fix X',
description='Fix the x-coordinate on the plot',
default=0.0,
update=lambda self, context: self.sync_prop('field_viz_plot_fixed_x', context),
)
field_viz_plot_fixed_y: bpy.props.FloatProperty(
name='Field Viz Plot: Fixed Y',
description='Fix the y coordinate on the plot',
default=0.0,
update=lambda self, context: self.sync_prop('field_viz_plot_fixed_y', context),
)
field_viz_plot_fixed_z: bpy.props.FloatProperty(
name='Field Viz Plot: Fixed Z',
description='Fix the z coordinate on the plot',
default=0.0,
update=lambda self, context: self.sync_prop('field_viz_plot_fixed_z', context),
)
field_viz_plot_fixed_f: bpy.props.FloatProperty(
name='Field Viz Plot: Fixed Freq (Thz)',
description='Fix the frequency coordinate on the plot',
default=0.0,
update=lambda self, context: self.sync_prop('field_viz_plot_fixed_f', context),
)
####################
# - Derived Properties
####################
def sync_viz_monitor_name(self, context):
if (sim_data := self._compute_input('FDTD Sim Data')) is None:
return
self.cache_viz_monitor_type = sim_data.monitor_data[self.viz_monitor_name].type
self.sync_prop('viz_monitor_name', context)
def retrieve_monitors(self, context) -> list[tuple]:
global CACHE
if not CACHE.get(self.instance_id):
sim_data = self._compute_input('FDTD Sim Data')
if sim_data is not None:
CACHE[self.instance_id] = {
'monitors': list(sim_data.monitor_data.keys())
}
else:
return [('NONE', 'None', 'No monitors')]
monitor_names = CACHE[self.instance_id]['monitors']
# Check for No Monitors
if not monitor_names:
return [('NONE', 'None', 'No monitors')]
return [
(
monitor_name,
monitor_name,
f"Monitor '{monitor_name}' recorded by the FDTD Sim",
)
for monitor_name in monitor_names
]
####################
# - UI
####################
def draw_props(self, context, layout):
row = layout.row()
row.prop(self, 'viz_monitor_name', text='')
if self.cache_viz_monitor_type == 'FieldData':
# Array Selection
split = layout.split(factor=0.45)
col = split.column(align=False)
col.label(text='Component')
col.label(text='Part')
col.label(text='Scale')
col = split.column(align=False)
col.prop(self, 'field_viz_component', text='')
col.prop(self, 'field_viz_part', text='')
col.prop(self, 'field_viz_scale', text='')
# Coordinate Fixing
split = layout.split(factor=0.45)
col = split.column(align=False)
col.prop(self, 'field_viz_plot_fix_x', text='Fix x (um)')
col.prop(self, 'field_viz_plot_fix_y', text='Fix y (um)')
col.prop(self, 'field_viz_plot_fix_z', text='Fix z (um)')
col.prop(self, 'field_viz_plot_fix_f', text='Fix f (THz)')
col = split.column(align=False)
col.prop(self, 'field_viz_plot_fixed_x', text='')
col.prop(self, 'field_viz_plot_fixed_y', text='')
col.prop(self, 'field_viz_plot_fixed_z', text='')
col.prop(self, 'field_viz_plot_fixed_f', text='')
####################
# - On Value Changed Methods
####################
@events.on_value_changed(
socket_name='FDTD Sim Data',
managed_objs={'viz_object'},
input_sockets={'FDTD Sim Data'},
)
def on_value_changed__fdtd_sim_data(
self,
managed_objs: dict[str, ct.schemas.ManagedObj],
input_sockets: dict[str, typ.Any],
) -> None:
global CACHE
if (sim_data := input_sockets['FDTD Sim Data']) is None:
CACHE.pop(self.instance_id, None)
return
CACHE[self.instance_id] = {'monitors': list(sim_data.monitor_data.keys())}
####################
# - Plotting
####################
@events.on_show_plot(
managed_objs={'viz_plot'},
props={
'viz_monitor_name',
'field_viz_component',
'field_viz_part',
'field_viz_scale',
'field_viz_structure_visibility',
'field_viz_plot_fix_x',
'field_viz_plot_fix_y',
'field_viz_plot_fix_z',
'field_viz_plot_fix_f',
'field_viz_plot_fixed_x',
'field_viz_plot_fixed_y',
'field_viz_plot_fixed_z',
'field_viz_plot_fixed_f',
},
input_sockets={'FDTD Sim Data'},
stop_propagation=True,
)
def on_show_plot(
self,
managed_objs: dict[str, ct.schemas.ManagedObj],
input_sockets: dict[str, typ.Any],
props: dict[str, typ.Any],
):
if (sim_data := input_sockets['FDTD Sim Data']) is None or (
monitor_name := props['viz_monitor_name']
) == 'NONE':
return
coord_fix = {}
for coord in ['x', 'y', 'z', 'f']:
if props[f'field_viz_plot_fix_{coord}']:
coord_fix |= {
coord: props[f'field_viz_plot_fixed_{coord}'],
}
if 'f' in coord_fix:
coord_fix['f'] *= 1e12
managed_objs['viz_plot'].mpl_plot_to_image(
lambda ax: sim_data.plot_field(
monitor_name,
props['field_viz_component'],
val=props['field_viz_part'],
scale=props['field_viz_scale'],
eps_alpha=props['field_viz_structure_visibility'],
phase=0,
**coord_fix,
ax=ax,
),
bl_select=True,
)
####################
# - Blender Registration
####################
BL_REGISTER = [
FDTDSimDataVizNode,
]
BL_NODES = {ct.NodeType.FDTDSimDataViz: (ct.NodeCategory.MAXWELLSIM_VIZ)}

View File

@ -1,319 +0,0 @@
import typing as typ
import bpy
from ... import contracts as ct
from ... import managed_objs, sockets
from .. import base, events
CACHE = {}
class FDTDSimDataVizNode(base.MaxwellSimNode):
node_type = ct.NodeType.FDTDSimDataViz
bl_label = 'FDTD Sim Data Viz'
####################
# - Sockets
####################
input_sockets: typ.ClassVar = {
'FDTD Sim Data': sockets.MaxwellFDTDSimDataSocketDef(),
}
output_sockets: typ.ClassVar = {'Preview': sockets.AnySocketDef()}
managed_obj_defs: typ.ClassVar = {
'viz_plot': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLImage(name),
name_prefix='',
),
'viz_object': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLObject(name),
name_prefix='',
),
}
####################
# - Properties
####################
viz_monitor_name: bpy.props.EnumProperty(
name='Viz Monitor Name',
description='Monitor to visualize within the attached SimData',
items=lambda self, context: self.retrieve_monitors(context),
update=(lambda self, context: self.sync_viz_monitor_name(context)),
)
cache_viz_monitor_type: bpy.props.StringProperty(
name='Viz Monitor Type',
description='Type of the viz monitor',
default='',
)
# Field Monitor Type
field_viz_component: bpy.props.EnumProperty(
name='Field Component',
description='Field component to visualize',
items=[
('E', 'E', 'Electric'),
# ("H", "H", "Magnetic"),
# ("S", "S", "Poynting"),
('Ex', 'Ex', 'Ex'),
('Ey', 'Ey', 'Ey'),
('Ez', 'Ez', 'Ez'),
# ("Hx", "Hx", "Hx"),
# ("Hy", "Hy", "Hy"),
# ("Hz", "Hz", "Hz"),
],
default='E',
update=lambda self, context: self.sync_prop('field_viz_component', context),
)
field_viz_part: bpy.props.EnumProperty(
name='Field Part',
description='Field part to visualize',
items=[
('real', 'Real', 'Electric'),
('imag', 'Imaginary', 'Imaginary'),
('abs', 'Abs', 'Abs'),
('abs^2', 'Squared Abs', 'Square Abs'),
('phase', 'Phase', 'Phase'),
],
default='real',
update=lambda self, context: self.sync_prop('field_viz_part', context),
)
field_viz_scale: bpy.props.EnumProperty(
name='Field Scale',
description='Field scale to visualize in, Linear or Log',
items=[
('lin', 'Linear', 'Linear Scale'),
('dB', 'Log (dB)', 'Logarithmic (dB) Scale'),
],
default='lin',
update=lambda self, context: self.sync_prop('field_viz_scale', context),
)
field_viz_structure_visibility: bpy.props.FloatProperty(
name='Field Viz Plot: Structure Visibility',
description='Visibility of structes',
default=0.2,
min=0.0,
max=1.0,
update=lambda self, context: self.sync_prop('field_viz_plot_fixed_f', context),
)
field_viz_plot_fix_x: bpy.props.BoolProperty(
name='Field Viz Plot: Fix X',
description='Fix the x-coordinate on the plot',
default=False,
update=lambda self, context: self.sync_prop('field_viz_plot_fix_x', context),
)
field_viz_plot_fix_y: bpy.props.BoolProperty(
name='Field Viz Plot: Fix Y',
description='Fix the y coordinate on the plot',
default=False,
update=lambda self, context: self.sync_prop('field_viz_plot_fix_y', context),
)
field_viz_plot_fix_z: bpy.props.BoolProperty(
name='Field Viz Plot: Fix Z',
description='Fix the z coordinate on the plot',
default=False,
update=lambda self, context: self.sync_prop('field_viz_plot_fix_z', context),
)
field_viz_plot_fix_f: bpy.props.BoolProperty(
name='Field Viz Plot: Fix Freq',
description='Fix the frequency coordinate on the plot',
default=False,
update=lambda self, context: self.sync_prop('field_viz_plot_fix_f', context),
)
field_viz_plot_fixed_x: bpy.props.FloatProperty(
name='Field Viz Plot: Fix X',
description='Fix the x-coordinate on the plot',
default=0.0,
update=lambda self, context: self.sync_prop('field_viz_plot_fixed_x', context),
)
field_viz_plot_fixed_y: bpy.props.FloatProperty(
name='Field Viz Plot: Fixed Y',
description='Fix the y coordinate on the plot',
default=0.0,
update=lambda self, context: self.sync_prop('field_viz_plot_fixed_y', context),
)
field_viz_plot_fixed_z: bpy.props.FloatProperty(
name='Field Viz Plot: Fixed Z',
description='Fix the z coordinate on the plot',
default=0.0,
update=lambda self, context: self.sync_prop('field_viz_plot_fixed_z', context),
)
field_viz_plot_fixed_f: bpy.props.FloatProperty(
name='Field Viz Plot: Fixed Freq (Thz)',
description='Fix the frequency coordinate on the plot',
default=0.0,
update=lambda self, context: self.sync_prop('field_viz_plot_fixed_f', context),
)
####################
# - Derived Properties
####################
def sync_viz_monitor_name(self, context):
if (sim_data := self._compute_input('FDTD Sim Data')) is None:
return
self.cache_viz_monitor_type = sim_data.monitor_data[self.viz_monitor_name].type
self.sync_prop('viz_monitor_name', context)
def retrieve_monitors(self, context) -> list[tuple]:
global CACHE
if not CACHE.get(self.instance_id):
sim_data = self._compute_input('FDTD Sim Data')
if sim_data is not None:
CACHE[self.instance_id] = {
'monitors': list(sim_data.monitor_data.keys())
}
else:
return [('NONE', 'None', 'No monitors')]
monitor_names = CACHE[self.instance_id]['monitors']
# Check for No Monitors
if not monitor_names:
return [('NONE', 'None', 'No monitors')]
return [
(
monitor_name,
monitor_name,
f"Monitor '{monitor_name}' recorded by the FDTD Sim",
)
for monitor_name in monitor_names
]
####################
# - UI
####################
def draw_props(self, context, layout):
row = layout.row()
row.prop(self, 'viz_monitor_name', text='')
if self.cache_viz_monitor_type == 'FieldData':
# Array Selection
split = layout.split(factor=0.45)
col = split.column(align=False)
col.label(text='Component')
col.label(text='Part')
col.label(text='Scale')
col = split.column(align=False)
col.prop(self, 'field_viz_component', text='')
col.prop(self, 'field_viz_part', text='')
col.prop(self, 'field_viz_scale', text='')
# Coordinate Fixing
split = layout.split(factor=0.45)
col = split.column(align=False)
col.prop(self, 'field_viz_plot_fix_x', text='Fix x (um)')
col.prop(self, 'field_viz_plot_fix_y', text='Fix y (um)')
col.prop(self, 'field_viz_plot_fix_z', text='Fix z (um)')
col.prop(self, 'field_viz_plot_fix_f', text='Fix f (THz)')
col = split.column(align=False)
col.prop(self, 'field_viz_plot_fixed_x', text='')
col.prop(self, 'field_viz_plot_fixed_y', text='')
col.prop(self, 'field_viz_plot_fixed_z', text='')
col.prop(self, 'field_viz_plot_fixed_f', text='')
####################
# - On Value Changed Methods
####################
@events.on_value_changed(
socket_name='FDTD Sim Data',
managed_objs={'viz_object'},
input_sockets={'FDTD Sim Data'},
)
def on_value_changed__fdtd_sim_data(
self,
managed_objs: dict[str, ct.schemas.ManagedObj],
input_sockets: dict[str, typ.Any],
) -> None:
global CACHE
if (sim_data := input_sockets['FDTD Sim Data']) is None:
CACHE.pop(self.instance_id, None)
return
CACHE[self.instance_id] = {'monitors': list(sim_data.monitor_data.keys())}
####################
# - Plotting
####################
@events.on_show_plot(
managed_objs={'viz_plot'},
props={
'viz_monitor_name',
'field_viz_component',
'field_viz_part',
'field_viz_scale',
'field_viz_structure_visibility',
'field_viz_plot_fix_x',
'field_viz_plot_fix_y',
'field_viz_plot_fix_z',
'field_viz_plot_fix_f',
'field_viz_plot_fixed_x',
'field_viz_plot_fixed_y',
'field_viz_plot_fixed_z',
'field_viz_plot_fixed_f',
},
input_sockets={'FDTD Sim Data'},
stop_propagation=True,
)
def on_show_plot(
self,
managed_objs: dict[str, ct.schemas.ManagedObj],
input_sockets: dict[str, typ.Any],
props: dict[str, typ.Any],
):
if (sim_data := input_sockets['FDTD Sim Data']) is None or (
monitor_name := props['viz_monitor_name']
) == 'NONE':
return
coord_fix = {}
for coord in ['x', 'y', 'z', 'f']:
if props[f'field_viz_plot_fix_{coord}']:
coord_fix |= {
coord: props[f'field_viz_plot_fixed_{coord}'],
}
if 'f' in coord_fix:
coord_fix['f'] *= 1e12
managed_objs['viz_plot'].mpl_plot_to_image(
lambda ax: sim_data.plot_field(
monitor_name,
props['field_viz_component'],
val=props['field_viz_part'],
scale=props['field_viz_scale'],
eps_alpha=props['field_viz_structure_visibility'],
phase=0,
**coord_fix,
ax=ax,
),
bl_select=True,
)
# @events.on_show_preview(
# managed_objs={"viz_object"},
# )
# def on_show_preview(
# self,
# managed_objs: dict[str, ct.schemas.ManagedObj],
# ):
# """Called whenever a Loose Input Socket is altered.
#
# Synchronizes the change to the actual GeoNodes modifier, so that the change is immediately visible.
# """
# managed_objs["viz_object"].show_preview("MESH")
####################
# - Blender Registration
####################
BL_REGISTER = [
FDTDSimDataVizNode,
]
BL_NODES = {ct.NodeType.FDTDSimDataViz: (ct.NodeCategory.MAXWELLSIM_VIZ)}

View File

@ -176,11 +176,24 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
raise RuntimeError(msg)
def sync_link_added(self, link) -> bool:
"""Called when a link has been added to this (input) socket.
Returns a bool, whether or not the socket consents to the link change.
"""
"""Called when a link has been added to this (input) socket."""
if self.locked:
log.error(
'Attempted to link output socket "%s" (%s) to input socket "%s" (%s), but input socket is locked',
link.from_socket.bl_label,
link.from_socket.capabilities,
self.bl_label,
self.capabilities,
)
return False
if not link.from_socket.capabilities.is_compatible_with(self.capabilities):
log.error(
'Attempted to link output socket "%s" (%s) to input socket "%s" (%s), but capabilities are invalid',
link.from_socket.bl_label,
link.from_socket.capabilities,
self.bl_label,
self.capabilities,
)
return False
if self.is_output:
msg = "Tried to sync 'link add' on output socket"

View File

@ -19,7 +19,7 @@ class MaxwellMonitorSocketDef(pyd.BaseModel):
def init(self, bl_socket: MaxwellMonitorBLSocket) -> None:
if self.is_list:
bl_socket.active_kind = ct.DataValueArray
bl_socket.active_kind = ct.DataFlowKind.ValueArray
####################

View File

@ -19,7 +19,7 @@ class MaxwellSourceSocketDef(pyd.BaseModel):
def init(self, bl_socket: MaxwellSourceBLSocket) -> None:
if self.is_list:
bl_socket.active_kind = ct.DataValueArray
bl_socket.active_kind = ct.DataFlowKind.ValueArray
####################

View File

@ -19,7 +19,7 @@ class MaxwellStructureSocketDef(pyd.BaseModel):
def init(self, bl_socket: MaxwellStructureBLSocket) -> None:
if self.is_list:
bl_socket.active_kind = ct.DataValueArray
bl_socket.active_kind = ct.DataFlowKind.ValueArray
####################

View File

@ -79,6 +79,7 @@ class PhysicalFreqBLSocket(base.MaxwellSimSocket):
return ct.LazyDataValueRange(
symbols=set(),
has_unit=True,
unit=self.unit,
start=sp.S(self.min_freq) * self.unit,
stop=sp.S(self.max_freq) * self.unit,
steps=self.steps,

View File

@ -79,6 +79,7 @@ class PhysicalLengthBLSocket(base.MaxwellSimSocket):
return ct.LazyDataValueRange(
symbols=set(),
has_unit=True,
unit=self.unit,
start=sp.S(self.min_len) * self.unit,
stop=sp.S(self.max_len) * self.unit,
steps=self.steps,
@ -116,7 +117,6 @@ class PhysicalLengthSocketDef(pyd.BaseModel):
bl_socket.lazy_value_range = (self.min_len, self.max_len, self.steps)
####################
# - Blender Registration
####################

View File

@ -66,15 +66,6 @@ class InstallPyDeps(bpy.types.Operator):
'Running pip w/cmdline: %s',
' '.join(cmdline),
)
print("TRYING CRASH")
import sys
for module_name, module in sys.modules.copy().items():
if module_name == '__mp_main__':
print('Problematic Module Entry', module_name)
print(module)
#print('MODULE REPR', module)
continue
print("NO CRASH")
subprocess.check_call(cmdline)
except subprocess.CalledProcessError:
log.exception('Failed to install PyDeps')