fix: Various critical fixes, field preview
parent
1d90f9ca7c
commit
dc76ab7688
26
TODO.md
26
TODO.md
|
@ -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:
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
}
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
|
@ -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)}
|
|
@ -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)}
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
#],
|
||||
)
|
||||
|
||||
####################
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
from . import json_file_exporter
|
||||
|
||||
BL_REGISTER = [
|
||||
*json_file_exporter.BL_REGISTER,
|
||||
]
|
||||
BL_NODES = {
|
||||
**json_file_exporter.BL_NODES,
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
from . import tidy3d_web_exporter
|
||||
|
||||
BL_REGISTER = [
|
||||
*tidy3d_web_exporter.BL_REGISTER,
|
||||
]
|
||||
BL_NODES = {
|
||||
**tidy3d_web_exporter.BL_NODES,
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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()
|
||||
|
||||
|
||||
####################
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
from . import sim_data_viz
|
||||
|
||||
BL_REGISTER = [
|
||||
*sim_data_viz.BL_REGISTER,
|
||||
]
|
||||
BL_NODES = {
|
||||
**sim_data_viz.BL_NODES,
|
||||
}
|
|
@ -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)}
|
|
@ -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)}
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
####################
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
####################
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
####################
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
####################
|
||||
|
|
|
@ -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')
|
||||
|
|
Loading…
Reference in New Issue