refactor: Massive architectural changes.

See README.md for new, semi-finalized TODO list.
main
Sofus Albert Høgsbro Rose 2024-03-10 11:56:37 +01:00
parent 28e6760dfb
commit 4c207b96e0
125 changed files with 6282 additions and 3574 deletions

588
README.md
View File

@ -1,239 +1,397 @@
# Node Design
Now that we can do all the cool things ex. presets and such, it's time to think more design.
# Nodes
## Inputs
[x] Wave Constant
- [ ] Implement export of frequency / wavelength ranges.
[ ] Unit System
- [ ] Implement presets, including "Tidy3D" and "Blender", shown in the label row.
## Nodes
**NOTE**: Throughout, when an object can be selected (ex. for GeoNodes structure to affect), a button should be available to generate a new object for the occasion.
[ ] Constants / Blender Constant
[ ] Constants / Number Constant
[ ] Constants / Physical Constant
- [ ] Pol: Elliptical plot viz
- [ ] Pol: Poincare sphere viz
[ ] Constants / Scientific Constant
**NOTE**: Throughout, all nodes that output floats/vectors should have a sympy dimension. Any node that takes floats/vectors should either have a pre-defined unit (exposed as a string in the node UI), or a selectable unit (ex. for value inputs).
[ ] Web / Tidy3D Web Importer
- Inputs
- Scene
- Time
- Unit System
- Parameters: Sympy variables.
- *type* Parameter
- Constants: Typed numbers.
- Scientific Constant
- *type* Constant
- Lists
- *type* List Element
- File Data: Data from a file.
- Outputs
- Viewers
- Value Viewer: Live-monitoring.
- Console Viewer: w/Button to Print Types
- Exporters
- JSON File Exporter: Compatible with any socket implementing `.as_json()`.
- Plotters
- *various kinds of plotting? To Blender datablocks primarily, maybe*.
[ ] File Import / JSON File Import
- [ ] Dropdown to choose various supported JSON-sourced objects incl.
[ ] File Import / Tidy3D File Import
- [ ] Implement HDF-based import of Tidy3D-exported object (which includes ex. mesh data and such)
[ ] File Import / Array File Import
- [ ] Standardize 1D and 2D array loading/saving on numpy's savetxt with gzip enabled.
- [ ] Implement datatype dropdown to guide format from disk, prefilled to detected.
- [ ] Implement unit system input to guide conversion from numpy data type.
- [ ] Implement a LazyValue to provide a data path that avoids having to load massive arrays every time always.
- Sources
- **ALL**: Accept a Temporal Shape
## Outputs
[ ] Viewer
- [ ] A setting that live-previews just a value.
- [ ] Pop-up multiline string print as alternative to console print.
- [ ] Toggleable auto-plot, auto-3D-preview, auto-value-view, (?)auto-text-view.
[ ] File Export / JSON File Export
[ ] File Import / Tidy3D File Export
- [ ] Implement HDF-based export of Tidy3D-exported object (which includes ex. mesh data and such)
[ ] File Export / Array File Export
- [ ] Implement datatype dropdown to guide format on disk.
- [ ] Implement unit system input to guide conversion to numpy data type.
- [ ] Standardize 1D and 2D array loading/saving on numpy's savetxt with gzip enabled.
## Viz
[ ] Monitor Data Viz
- [ ] Implement dropdown to choose which monitor in the SimulationData should be visualized (based on which are available in the SimulationData), and implement visualization based on every kind of monitor-adjascent output data type (<https://docs.flexcompute.com/projects/tidy3d/en/latest/api/output_data.html>)
## Sources
[ ] Temporal Shapes / Gaussian Pulse Temporal Shape
[ ] Temporal Shapes / Continuous Wave Temporal Shape
[ ] Temporal Shapes / Symbolic Temporal Shape
- [ ] Specify a Sympy function to generate appropriate array based on
[ ] Temporal Shapes / Array Temporal Shape
[ ] Point Dipole Source
[ ] Plane Wave Source
- [ ] Implement an oriented vector input with 3D preview.
[ ] Uniform Current Source
[ ] TFSF Source
[ ] Gaussian Beam Source
[ ] Astigmatic Gaussian Beam Source
[ ] Mode Source
[ ] Array Source / EH Array Source
[ ] Array Source / EH Equivilance Array Source
## Mediums
[x] Library Medium
- [ ] Implement frequency range output
[ ] PEC Medium
[ ] Isotropic Medium
[ ] Anisotropic Medium
[ ] Sellmeier Medium
[ ] Drude Medium
[ ] Drude-Lorentz Medium
[ ] Debye Medium
[ ] Pole-Residue Medium
- Temporal Shapes
- Gaussian Pulse Temporal Shape
- Continuous Wave Temporal Shape
- Array Temporal Shape
- Point Dipole Source
- Uniform Current Source
- Plane Wave Source
- Mode Source
- Gaussian Beam Source
- Astigmatic Gaussian Beam Source
- TFSF Source
- E/H Equivalence Array Source
- E/H Array Source
[ ] Non-Linearity / `chi_3` Susceptibility Non-Linearity
[ ] Non-Linearity / Two-Photon Absorption Non-Linearity
[ ] Non-Linearity / Kerr Non-Linearity
[ ] Space/Time epsilon/mu Modulation
## Structures
[ ] BLObject Structure
[ ] GeoNodes Structure
- [ ] Use the modifier itself as memory, via the ManagedObj
- [?] When GeoNodes themselves declare panels, implement a grid-like tab system to select which sockets should be exposed in the node at a given point in time.
[ ] Primitive Structures / Plane
[ ] Primitive Structures / Box Structure
[ ] Primitive Structures / Sphere
[ ] Primitive Structures / Cylinder
[ ] Primitive Structures / Ring
[ ] Primitive Structures / Capsule
[ ] Primitive Structures / Cone
## Monitors
- **ALL**: "Steady-State" / "Time Domain" (only if relevant).
[ ] E/H Field Monitor
- [ ] Monitor Domain as dropdown with Frequency or Time
- [ ] Axis-aligned planar 2D (pixel) and coord-aligned box 3D (voxel).
[ ] Field Power Flux Monitor
- [ ] Monitor Domain as dropdown with Frequency or Time
- [ ] Axis-aligned planar 2D (pixel) and coord-aligned box 3D (voxel).
[ ] \epsilon Tensor Monitor
- [ ] Axis-aligned planar 2D (pixel) and coord-aligned box 3D (voxel).
[ ] Diffraction Monitor
- [ ] Axis-aligned planar 2D (pixel)
[ ] Projected E/H Field Monitor / Cartesian Projected E/H Field Monitor
- [ ] Use to implement the metalens: <https://docs.flexcompute.com/projects/tidy3d/en/latest/notebooks/Metalens.html>
[ ] Projected E/H Field Monitor / Angle Projected E/H Field Monitor
[ ] Projected E/H Field Monitor / K-Space Projected E/H Field Monitor
- **TODO**: "Modal" solver monitoring (seems to be some kind of spatial+frequency feature, which an EM field can be decomposed into using a specially configured solver, which can be used to look for very particular kinds of effects by constraining investigations of a solver result to filter out everything that isn't these particular modes aka. features. Kind of a fourier-based redimensionalization, almost).
## Simulations
[-] FDTDSim
[-] Sim Domain
- [ ] By-Medium batching of Structures when building the td.Simulation object, which can have significant performance implications.
[-] Boundary Conds
- [ ] Rename from Bounds / BoundBox
[ ] Boundary Cond / PML Bound Face
- [ ] Implement dropdown for "Normal" and "Stable"
[ ] Boundary Cond / PEC Bound Face
[ ] Boundary Cond / PMC Bound Face
[ ] Boundary Cond / Bloch Bound Face
[ ] Boundary Cond / Periodic Bound Face
[ ] Boundary Cond / Absorbing Bound Face
[ ] Sim Grid
[ ] Sim Grid Axes / Auto Sim Grid Axis
[ ] Sim Grid Axes / Manual Sim Grid Axis
[ ] Sim Grid Axes / Uniform Sim Grid Axis
[ ] Sim Grid Axes / Array Sim Grid Axis
## Converters
[ ] Math
- [ ] Implement common operations w/secondary choice of socket type based on a custom internal data structure
- [ ] Implement angfreq/frequency/vacwl conversion.
[ ] Separate
[ ] Combine
- [ ] Implement concatenation of sim-critical socket types into their multi-type
- Mediums
- **ALL**: Accept spatial field. Else, spatial uniformity.
- **ALL**: Accept non-linearity. Else, linear.
- **ALL**: Accept space-time modulation. Else, static.
- Library Medium
- **NOTE**: Should provide an EnumProperty of materials with its own categorizations. It should provide another EnumProperty to choose the experiment. It should also be filterable by wavelength range, maybe also model info. Finally, a reference should be generated on use as text.
- PEC Medium
- Isotropic Medium
- Anisotropic Medium
- 3-Sellmeier Medium
- Sellmeier Medium
- Pole-Residue Medium
- Drude Medium
- Drude-Lorentz Medium
- Debye Medium
- Non-Linearities
- Add Non-Linearity
- \chi_3 Susceptibility Non-Linearity
- Two-Photon Absorption Non-Linearity
- Kerr Non-Linearity
- Space/Time \epsilon/\mu Modulation
# GeoNodes
[ ] Tests / Monkey (suzanne deserves to be simulated, she may need manifolding up though :))
[ ] Tests / Wood Pile
- Structures
- Object Structure
- GeoNodes Structure
- Scripted Structure
- Primitives
- Box Structure
- Sphere Structure
- Cylinder Structure
[ ] Primitives / Plane
[ ] Primitives / Box
[ ] Primitives / Sphere
[ ] Primitives / Cylinder
[ ] Primitives / Ring
[ ] Primitives / Capsule
[ ] Primitives / Cone
[ ] Array / Square Array **NOTE: Ring and cylinder**
[ ] Array / Hex Array **NOTE: Ring and cylinder**
[ ] Hole Array / Square Hole Array: Takes a primitive hole shape.
[ ] Hole Array / Hex Hole Array: Takes a primitive hole shape.
[ ] Cavity Array / Hex Array w/ L-Cavity
[ ] Cavity Array / Hex Array w/ H-Cavity
[ ] Crystal Sphere Lattice / Sphere FCC Array
[ ] Crystal Sphere Lattice / Sphere BCC Array
- Bounds
- Bound Box
- Bound Faces
- PML Bound Face: "Normal"/"Stable"
- PEC Bound Face
- PMC Bound Face
- Bloch Bound Face
- Periodic Bound Face
- Absorbing Bound Face
- Monitors
- **ALL**: "Steady-State" / "Time Domain" (only if relevant).
- E/H Field Monitor: "Steady-State"
- Field Power Flux Monitor
- \epsilon Tensor Monitor
- Diffraction Monitor
- **TODO**: "Modal" solver monitoring (seems to be some kind of spatial+frequency feature, which an EM field can be decomposed into using a specially configured solver, which can be used to look for very particular kinds of effects by constraining investigations of a solver result to filter out everything that isn't these particular modes aka. features. Kind of a fourier-based redimensionalization, almost).
- **TODO**: Near-field projections like so:
- Cartesian Near-Field Projection Monitor
- Observation Angle Near-Field Projection Monitor
- K-Space Near-Field Projection Monitor
# Benchmark / Example Sims
- [ ] Tunable Chiral Metasurface <https://docs.flexcompute.com/projects/tidy3d/en/latest/notebooks/TunableChiralMetasurface.html>
- Simulations
- Sim Grid
- Sim Grid Axis
- Automatic Sim Grid Axis
- Manual Sim Grid Axis
- Uniform Sim Grid Axis
- Array Sim Grid Axis
- FDTD Sim
# Sockets
## Basic
[ ] Any
[ ] Bool
[ ] String
- [ ] Rename from "Text"
[ ] File Path
## Blender
[ ] Object
[ ] Collection
[ ] Image
[ ] GeoNodes
[ ] Text
## Maxwell
[ ] Bound Conds
[ ] Bound Cond
[ ] Medium
[ ] Medium Non-Linearity
[ ] Source
[ ] Temporal Shape
[ ] Structure
[ ] Monitor
[ ] FDTD Sim
[ ] Sim Domain
- [?] Toggleable option to sync the simulation time duration to the scene end time (how to handle FPS vs time-step? Should we adjust the FPS such that there is one time step per frame, while keeping the definition of "second" aligned to a unit system?)
[ ] Sim Grid
[ ] Sim Grid Axis
[ ] Simulation Data
## Tidy3D
[ ] Cloud Task
## Number
[ ] Integer
[ ] Rational
[ ] Real
[ ] Complex
## Physical
[ ] Unit System
- [ ] Implement more comprehensible UI; honestly, probably with the new panels (<https://developer.blender.org/docs/release_notes/4.1/python_api/>)
[ ] Time
[ ] Angle
[ ] Solid Angle (steradian)
[ ] Frequency (hertz)
[ ] Angular Frequency (`rad*hertz`)
### Cartesian
[ ] Length
[ ] Area
[ ] Volume
[ ] Point 1D
[ ] Point 2D
[ ] Point 3D
[ ] Size 2D
[ ] Size 3D
### Mechanical
[ ] Mass
[ ] Speed
[ ] Velocity 3D
[ ] Acceleration Scalar
[ ] Acceleration 3D
[ ] Force Scalar
[ ] Force 3D
[ ] Pressure
### Statistical
[ ] Energy (joule)
[ ] Power (watt)
[ ] Temperature
### Electrodynamical
[ ] Current (ampere)
[ ] Current Density 3D
[ ] Charge (coulomb)
[ ] Voltage (volts)
[ ] Capacitance (farad)
[ ] Resistance (ohm)
[ ] Electric Conductance (siemens)
[ ] Magnetic Flux (weber)
[ ] Magnetic Flux Density (tesla)
[ ] Inductance (henry)
[ ] Electric Field 3D (`volt*meter`)
[ ] Magnetic Field 3D (tesla)
### Luminal
[ ] Luminous Intensity (candela)
[ ] Luminous Flux (lumen)
[ ] Illuminance (lux)
### Optical
[ ] Jones Polarization
[ ] Polarization
- Utilities
- Math: Contains a dropdown for operation.
- *type* Math: **Be careful about units :)**
- Operations
- List Operation
## Sockets
- basic
- Any
- FilePath
- Text
- number
- IntegerNumber
- RationalNumber
- RealNumber
- ComplexNumber
- RealNumberField
- ComplexNumberField
- vector
- Real2DVector
- Complex2DVector
- Real2DVectorField
- Complex2DVectorField
- Real3DVector
- Complex3DVector
- Real3DVectorField
- Complex3DVectorField
- physics
- PhysicalTime
- PhysicalAngle
- PhysicalLength
- PhysicalArea
- PhysicalVolume
- PhysicalMass
- PhysicalLengthDensity
- PhysicalAreaDensity
- PhysicalVolumeDensity
- PhysicalSpeed
- PhysicalAcceleration
- PhysicalForce
- PhysicalPolarization
- PhysicalFrequency
- PhysicalSpectralDistribution
- blender
- BlenderObject
- BlenderCollection
- BlenderGeoNodes
- BlenderImage
- maxwell
- MaxwellMedium
- MaxwellMediumNonLinearity
- MaxwellStructure
- MaxwellBoundBox
- MaxwellBoundFace
- MaxwellMonitor
- MaxwellSimGrid
- FDTDSim
# Style
[ ] Rethink the meaning of color and shapes in node sockets, including whether dynamic functionality is needed when it comes to socket shape (ex. it might be nice to know whether a socket is array-like or uses units).
[ ] Rethink the meaning of color and shapes in node sockets, including whether dynamic functionality is needed when it comes to socket shape.
### GeoNode Trees
For ease of use, we can ship with premade node trees/groups for:
- Primitives
- Plane
- Box
- Sphere
- Cylinder
- Ring
- Capsule
- Cone
- Array
- Square Array: Takes a primitive shape.
- Hex Array: Takes a primitive shape.
- Hole Array
- Square Hole Array: Takes a primitive hole shape.
- Hex Hole Array: Takes a primitive hole shape.
- Cavities
- Hex Array w/ L-Cavity: Takes a primitive hole shape.
- Hex Array w/ H-Cavity: Takes a primitive hole shape.
- Crystal
- FCC Sphere Array: Takes a primitive spherical-like shape.
- BCC Sphere Array: Takes a primitive spherical-like shape.
- Wood Pile
# Architecture
## Registration and Contracts
[ ] Finish the contract code converting from Blender sockets to our sockets based on dimensionality and the property description.
[ ] Refactor the node category code; it's ugly as all fuck.
[?] Would be nice with some kind of indicator somewhere to help set good socket descriptions when using geonodes and wanting units.
When it comes to geometry, we do need to make sure
## Managed Objects
[ ] Implement modifier support on the managed BL object, with special attention paid to the needs of the GeoNodes socket.
- [ ] Implement preview toggling too, ex. using the relevant node tree collections
- Remember, the managed object is "dumb". It's the node's responsibility to react to any relevant `on_value_change`, and forward all state needed by the modifier to the managed obj. It's only the managed obj's responsibility to not update any modifier value that wouldn't change anything.
[ ] Implement loading the xarray-defined voxels into OpenVDB, saving it, and loading it as a managed BL object with the volume setting.
[ ] Implement basic jax-driven volume voxel processing, especially cube based slicing.
[ ] Implement jax-driven linear interpolation of volume voxels to an image texture, whose pixels are sized according to the dimensions of another managed plane object (perhaps a uniquely described Managed BL object itself).
### Notes
**NOTE**: When several geometries assigned to the same medium are assigned to the same `tidy3d.GeometryGroup`, there can apparently be "significant performance enhancement"s (<https://docs.flexcompute.com/projects/tidy3d/en/latest/_autosummary/tidy3d.GeometryGroup.html#tidy3d.GeometryGroup>).
- We can and should, in the Simulation builder (or just the structure concatenator), batch together structures with the same Medium.
## Node Base Class
[ ] Dedicated `draw_preview`-type draw functions for plot customizations.
- [ ] For now, previewing isn't something I think should be part of the node
[ ] Custom `@cache`/`@lru_cache`/`@cached_property` which caches by instance ID (possibly based on `beartype` or `pydantic`).
[ ] When presets are used, if a preset is selected and the user alters a preset setting, then dynamically switch the preset indicator back to "Custom" to indicate that there is no active preset
[ ] It seems that `node.inputs` and `node.outputs` allows the use of a `move` method, which may allow reordering sockets dynamically, which we should expose to the user as user-configurable ordering rules (maybe resolved with a constraint solver).
[?] Mechanism for dynamic names (ex. "Library Medium" becoming "Au Medium")
[ ] Mechanism for selecting a blender object managed by a particular node.
[ ] Mechanism for ex. specially coloring a node that is currently participating in the preview.
**NOTE**: Some symmetries can be greatly important for performance. <https://docs.flexcompute.com/projects/tidy3d/en/latest/notebooks/Symmetry.html>
## Socket Base Class
[ ] A feature `use_array` which allows a socket to declare that it can be both a single value and array-like (possibly constrained to a given shape). This should also allow the SocketDef to request that the input socket be initialised as a multi-input socket, once Blender updates to support those.
- [ ] Implement a shape-selector, with a dropdown for dimensionality and an appropriate `IntegerVectorProperty` for each kind of shape (supporting also straight-up inf), which is declared to the node that supports array-likeness so it can decide how exactly to expose properties in the array-like context of things.
[ ] Make `to_socket`s no-consent to new links from `from_socket`s of differing type (we'll see if this controls the typing story enough for now, and how much we'll need capabilities in the long run)
- [?] Alternatively, reject non matching link types, and red-mark non matching capabilities?
## Many Nodes
[ ] Implement LazyValue stuff, including LazyParamValue on a new class of constant-like input nodes that really just emit ex. sympy variables.
[?] Require a Unit System for nodes that construct Tidy3D objects
[ ] Medium Features
- [ ] Accept spatial field. Else, spatial uniformity.
- [ ] Accept non-linearity. Else, linear.
- [ ] Accept space-time modulation. Else, static.
[ ] Modal Features
- [ ] ModeSpec, for use by ModeSource, ModeMonitor, ModeSolverMonitor. Data includes ModeSolverData, ModeData, ScalarModeFieldDataArray, ModeAmpsDataArray, ModeIndexDataArray, ModeSolver.
## Development Tooling
[ ] Implement `rye` support
[ ] Setup neovim to be an ideal editor
## Version Churn
[ ] Implement real StrEnum sockets, since they appear in py3.11
[ ] Think about implementing new panels where appropriate (<https://developer.blender.org/docs/release_notes/4.1/python_api/>)
[ ] Think about using the new bl4.1 file handler API to enable drag and drop creation of appropriate nodes (for importing files without hassle).
[ ] Keep an eye on our manual `__annotations__` hacking; python 3.13 is apparently fucking with it.
[ ] Plan for multi-input sockets <https://projects.blender.org/blender/blender/commit/14106150797a6ce35e006ffde18e78ea7ae67598> (for now, just use the "Combine" node and have seperate socket types for both).
[ ] Keep an eye out for volume geonodes in 4.2 (July 16, 2024), which will better allow for more complicated volume processing (we might still want/need the jax based stuff after, but let's keep it minimal just in case)
## Packaging
[ ] Allow specifying custom dir for keeping pip dependencies, so we can unify prod and dev (currently we hard-code a dev dependency path).
[ ] Refactor top-level `__init__.py` to check dependencies first. If not everything is available, it should only register a minimal addon; specifically, a message telling the user that the addon requires additional dependencies (list which), and the button to install them. When the installation is done, re-check deps and register the rest of the addon.
[ ] Use a Modal and multiline-text-like construction to print `pip install` as we install dependencies, so that the user has an idea that something is happening.
[ ] Test on Windows
## Node Tree Cache Semantics
## Projects / Plugins
### Field Data
[ ] Directly dealing with field data, instead of having field manipulations be baked into viz node(s).
[ ] Yee Cell Data as Attributes on By-Cell Point Cloud w/GeoNodes Integrations
- In effect, when we have xarray data defined based on Yee Cells ex. Poynting vector coordinates, let's import this to Blender as a simple point cloud centered at each cell and grant each an attribute corresponding to the data.
- What we can then do is use vanilla GeoNodes to ex. read the vector attribute, and draw small arrow meshes (maybe resampled which auto-interpolates the field values) from each point, thus effectively visualizing . vector fields and many other fun things.
- Of course, this is no good for volume cell data - but we can just overlay the raw volume cell data as we please. We can also, if we're sneaky, deal with our volume data as points as far as we can, and then finally do a "points to volume" type deal to make it sufficiently "fluffy/cloudy".
- I wonder if we could use the Attribute node in the shader editor to project interpolated values from points, onto a ex. plane mesh, in a way that would also be visualizable in the viewport.
### Tidy3D Features
[ ] Symmetry for Performance
- [ ] Implement <https://docs.flexcompute.com/projects/tidy3d/en/latest/notebooks/Symmetry.html>
[ ] Dispersive Model Fitting
[ ] Scattering Matrix Calculator
[ ] Resonance Finder
[ ] Adjoint Optimization
[ ] Design Space Exploration / Parameterization
### Preview Semantics
[ ] Custom gizmos attached to preview toggles!
- There is a WIP for GN-driven gizmos: <https://projects.blender.org/blender/blender/pulls/112677>
- Probably best to wait for that, then just add gizmos to existing driven GN trees, as opposed to unholy OGL spaghetti.
[ ] Node-ManagedObj Selection binding
- BL to Node:
- Trigger: The post-depsgraph handler seems appropriate.
- Input: Read the object location (origin), using a unit system.
- Output: Write the input socket value.
- Condition: Input socket is unlinked. (If it's linked, then lock the object's position. Use sync_link_added() for that)
- Node to BL:
- Trigger: "Report" action on an input socket that the managed object declares reliance on.
- Input: The input socket value (linked or unlinked)
- Output: The object location (origin), using a unit system.
### Parametric Geometry UX
[ ] Consider allowing a mesh attribute (set in ex. geometry node) to specify the name of a medium.
- This allows assembling complex multi-medium structures in one geonodes tree.
- This should result in the spawning of several Medium input sockets in the GeoNodes structure node, named as the attributes are.
- The GeoNodes structure node should then output as array-like TriMeshes, for which mediums are correctly defined.
### Alternative Engines
[ ] MEEP integration (<https://meep.readthedocs.io/en/latest/>)
- The main boost would be if we could setup a MEEP simulation entirely from a td.Simulation object.

View File

@ -46,17 +46,39 @@ BL_REGISTER = [
*operators.BL_REGISTER,
*preferences.BL_REGISTER,
]
BL_KMI_REGISTER = [
*operators.BL_KMI_REGISTER,
]
BL_NODE_CATEGORIES = [
*node_trees.BL_NODE_CATEGORIES,
]
km = bpy.context.window_manager.keyconfigs.addon.keymaps.new(
name='Node Editor',
space_type="NODE_EDITOR",
)
REGISTERED_KEYMAPS = []
def register():
global REGISTERED_KEYMAPS
for cls in BL_REGISTER:
bpy.utils.register_class(cls)
for kmi_def in BL_KMI_REGISTER:
kmi = km.keymap_items.new(
*kmi_def["_"],
ctrl=kmi_def["ctrl"],
shift=kmi_def["shift"],
alt=kmi_def["alt"],
)
REGISTERED_KEYMAPS.append(kmi)
def unregister():
for cls in reversed(BL_REGISTER):
bpy.utils.unregister_class(cls)
for kmi in REGISTERED_KEYMAPS:
km.keymap_items.remove(kmi)
if __name__ == "__main__":
register()

View File

@ -1,3 +1,9 @@
import sympy as sp
sp.printing.str.StrPrinter._default_settings['abbrev'] = True
## In this tree, all Sympy unit printing must be abbreviated.
## By configuring this in __init__.py, we guarantee it for all subimports.
## (Unless, elsewhere, this setting is changed. Be careful!)
from . import sockets
from . import node_tree
from . import nodes

View File

@ -2,7 +2,7 @@
import bpy
import nodeitems_utils
from . import contracts
from . import contracts as ct
from .nodes import BL_NODES
DYNAMIC_SUBMENU_REGISTRATIONS = []
@ -15,7 +15,7 @@ def mk_node_categories(
items = []
# Add Node Items
base_category = contracts.NodeCategory["_".join(syllable_prefix)]
base_category = ct.NodeCategory["_".join(syllable_prefix)]
for node_type, node_category in BL_NODES.items():
if node_category == base_category:
items.append(nodeitems_utils.NodeItem(node_type.value))
@ -23,7 +23,7 @@ def mk_node_categories(
# Add Node Sub-Menus
for syllable, sub_tree in tree.items():
current_syllable_path = syllable_prefix + [syllable]
current_category = contracts.NodeCategory[
current_category = ct.NodeCategory[
"_".join(current_syllable_path)
]
@ -54,9 +54,9 @@ def mk_node_categories(
self.layout.menu(submenu_id)
return draw
menu_class = type(current_category.value, (bpy.types.Menu,), {
menu_class = type(str(current_category.value), (bpy.types.Menu,), {
'bl_idname': current_category.value,
'bl_label': contracts.NodeCategory_to_category_label[current_category],
'bl_label': ct.NODE_CAT_LABELS[current_category],
'draw': draw_factory(tuple(subitems)),
})
@ -72,7 +72,7 @@ def mk_node_categories(
# - Blender Registration
####################
BL_NODE_CATEGORIES = mk_node_categories(
contracts.NodeCategory.get_tree()["MAXWELLSIM"],
ct.NodeCategory.get_tree()["MAXWELLSIM"],
syllable_prefix = ["MAXWELLSIM"],
)
## TODO: refactor, this has a big code smell
@ -82,7 +82,7 @@ BL_REGISTER = [
## TEST - TODO this is a big code smell
def menu_draw(self, context):
if context.space_data.tree_type == contracts.TreeType.MaxwellSim.value:
if context.space_data.tree_type == ct.TreeType.MaxwellSim.value:
for nodeitem_or_submenu in BL_NODE_CATEGORIES:
if isinstance(nodeitem_or_submenu, str):
submenu_id = nodeitem_or_submenu

View File

@ -1,922 +0,0 @@
import typing as typ
import typing_extensions as pytypes_ext
import enum
import sympy as sp
sp.printing.str.StrPrinter._default_settings['abbrev'] = True
## When we str() a unit expression, use abbrevied units.
import sympy.physics.units as spu
import pydantic as pyd
import bpy
from ...utils.blender_type_enum import (
BlenderTypeEnum, append_cls_name_to_values, wrap_values_in_MT
)
from ...utils import extra_sympy_units as spuex
####################
# - String Types
####################
BlenderColorRGB = tuple[float, float, float, float]
BlenderID = pytypes_ext.Annotated[str, pyd.StringConstraints(
pattern=r'^[A-Z_]+$',
)]
# Socket ID
SocketName = pytypes_ext.Annotated[str, pyd.StringConstraints(
pattern=r'^[a-zA-Z0-9_]+$',
)]
BLSocketName = pytypes_ext.Annotated[str, pyd.StringConstraints(
pattern=r'^[a-zA-Z0-9_]+$',
)]
# Socket ID
PresetID = pytypes_ext.Annotated[str, pyd.StringConstraints(
pattern=r'^[A-Z_]+$',
)]
####################
# - Sympy Expression Typing
####################
ALL_UNIT_SYMBOLS = {
unit
for unit in spu.__dict__.values()
if isinstance(unit, spu.Quantity)
}
def has_units(expr: sp.Expr):
return any(
symbol in ALL_UNIT_SYMBOLS
for symbol in expr.atoms(sp.Symbol)
)
def is_exactly_expressed_as_unit(expr: sp.Expr, unit) -> bool:
#try:
converted_expr = expr / unit
return (
converted_expr.is_number
and not converted_expr.has(spu.Quantity)
)
####################
# - Icon Types
####################
class Icon(BlenderTypeEnum):
MaxwellSimTree = "MOD_SIMPLEDEFORM"
####################
# - Tree Types
####################
@append_cls_name_to_values
class TreeType(BlenderTypeEnum):
MaxwellSim = enum.auto()
####################
# - Socket Types
####################
@append_cls_name_to_values
class SocketType(BlenderTypeEnum):
# Base
Any = enum.auto()
Bool = enum.auto()
Text = enum.auto()
FilePath = enum.auto()
# Number
IntegerNumber = enum.auto()
RationalNumber = enum.auto()
RealNumber = enum.auto()
ComplexNumber = enum.auto()
# Vector
Real2DVector = enum.auto()
Complex2DVector = enum.auto()
Real3DVector = enum.auto()
Complex3DVector = enum.auto()
# Physical
PhysicalUnitSystem = enum.auto()
PhysicalTime = enum.auto()
PhysicalAngle = enum.auto()
PhysicalLength = enum.auto()
PhysicalArea = enum.auto()
PhysicalVolume = enum.auto()
PhysicalPoint2D = enum.auto()
PhysicalPoint3D = enum.auto()
PhysicalSize2D = enum.auto()
PhysicalSize3D = enum.auto()
PhysicalMass = enum.auto()
PhysicalSpeed = enum.auto()
PhysicalAccelScalar = enum.auto()
PhysicalForceScalar = enum.auto()
PhysicalAccel3DVector = enum.auto()
PhysicalForce3DVector = enum.auto()
PhysicalPol = enum.auto()
PhysicalFreq = enum.auto()
PhysicalVacWL = enum.auto()
PhysicalSpecPowerDist = enum.auto()
PhysicalSpecRelPermDist = enum.auto()
# Blender
BlenderObject = enum.auto()
BlenderCollection = enum.auto()
BlenderImage = enum.auto()
BlenderVolume = enum.auto()
BlenderGeoNodes = enum.auto()
BlenderText = enum.auto()
BlenderPreviewTarget = enum.auto()
# Maxwell
MaxwellSource = enum.auto()
MaxwellTemporalShape = enum.auto()
MaxwellMedium = enum.auto()
MaxwellMediumNonLinearity = enum.auto()
MaxwellStructure = enum.auto()
MaxwellBoundBox = enum.auto()
MaxwellBoundFace = enum.auto()
MaxwellMonitor = enum.auto()
MaxwellFDTDSim = enum.auto()
MaxwellSimGrid = enum.auto()
MaxwellSimGridAxis = enum.auto()
SocketType_to_units = {
SocketType.PhysicalTime: {
"default": "PS",
"values": {
"PS": spu.picosecond,
"NS": spu.nanosecond,
"MS": spu.microsecond,
"MLSEC": spu.millisecond,
"SEC": spu.second,
"MIN": spu.minute,
"HOUR": spu.hour,
"DAY": spu.day,
},
},
SocketType.PhysicalAngle: {
"default": "RADIAN",
"values": {
"RADIAN": spu.radian,
"DEGREE": spu.degree,
"STERAD": spu.steradian,
"ANGMIL": spu.angular_mil,
},
},
SocketType.PhysicalLength: {
"default": "UM",
"values": {
"PM": spu.picometer,
"A": spu.angstrom,
"NM": spu.nanometer,
"UM": spu.micrometer,
"MM": spu.millimeter,
"CM": spu.centimeter,
"M": spu.meter,
"INCH": spu.inch,
"FOOT": spu.foot,
"YARD": spu.yard,
"MILE": spu.mile,
},
},
SocketType.PhysicalArea: {
"default": "UM_SQ",
"values": {
"PM_SQ": spu.picometer**2,
"A_SQ": spu.angstrom**2,
"NM_SQ": spu.nanometer**2,
"UM_SQ": spu.micrometer**2,
"MM_SQ": spu.millimeter**2,
"CM_SQ": spu.centimeter**2,
"M_SQ": spu.meter**2,
"INCH_SQ": spu.inch**2,
"FOOT_SQ": spu.foot**2,
"YARD_SQ": spu.yard**2,
"MILE_SQ": spu.mile**2,
},
},
SocketType.PhysicalVolume: {
"default": "UM_CB",
"values": {
"PM_CB": spu.picometer**3,
"A_CB": spu.angstrom**3,
"NM_CB": spu.nanometer**3,
"UM_CB": spu.micrometer**3,
"MM_CB": spu.millimeter**3,
"CM_CB": spu.centimeter**3,
"M_CB": spu.meter**3,
"ML": spu.milliliter,
"L": spu.liter,
"INCH_CB": spu.inch**3,
"FOOT_CB": spu.foot**3,
"YARD_CB": spu.yard**3,
"MILE_CB": spu.mile**3,
},
},
SocketType.PhysicalPoint2D: {
"default": "UM",
"values": {
"PM": spu.picometer,
"A": spu.angstrom,
"NM": spu.nanometer,
"UM": spu.micrometer,
"MM": spu.millimeter,
"CM": spu.centimeter,
"M": spu.meter,
"INCH": spu.inch,
"FOOT": spu.foot,
"YARD": spu.yard,
"MILE": spu.mile,
},
},
SocketType.PhysicalPoint3D: {
"default": "UM",
"values": {
"PM": spu.picometer,
"A": spu.angstrom,
"NM": spu.nanometer,
"UM": spu.micrometer,
"MM": spu.millimeter,
"CM": spu.centimeter,
"M": spu.meter,
"INCH": spu.inch,
"FOOT": spu.foot,
"YARD": spu.yard,
"MILE": spu.mile,
},
},
SocketType.PhysicalSize2D: {
"default": "UM",
"values": {
"PM": spu.picometer,
"A": spu.angstrom,
"NM": spu.nanometer,
"UM": spu.micrometer,
"MM": spu.millimeter,
"CM": spu.centimeter,
"M": spu.meter,
"INCH": spu.inch,
"FOOT": spu.foot,
"YARD": spu.yard,
"MILE": spu.mile,
},
},
SocketType.PhysicalSize3D: {
"default": "UM",
"values": {
"PM": spu.picometer,
"A": spu.angstrom,
"NM": spu.nanometer,
"UM": spu.micrometer,
"MM": spu.millimeter,
"CM": spu.centimeter,
"M": spu.meter,
"INCH": spu.inch,
"FOOT": spu.foot,
"YARD": spu.yard,
"MILE": spu.mile,
},
},
SocketType.PhysicalMass: {
"default": "UG",
"values": {
"E_REST": spu.electron_rest_mass,
"DAL": spu.dalton,
"UG": spu.microgram,
"MG": spu.milligram,
"G": spu.gram,
"KG": spu.kilogram,
"TON": spu.metric_ton,
},
},
SocketType.PhysicalSpeed: {
"default": "UM_S",
"values": {
"PM_S": spu.picometer / spu.second,
"NM_S": spu.nanometer / spu.second,
"UM_S": spu.micrometer / spu.second,
"MM_S": spu.millimeter / spu.second,
"M_S": spu.meter / spu.second,
"KM_S": spu.kilometer / spu.second,
"KM_H": spu.kilometer / spu.hour,
"FT_S": spu.feet / spu.second,
"MI_H": spu.mile / spu.hour,
},
},
SocketType.PhysicalAccelScalar: {
"default": "UM_S_SQ",
"values": {
"PM_S_SQ": spu.picometer / spu.second**2,
"NM_S_SQ": spu.nanometer / spu.second**2,
"UM_S_SQ": spu.micrometer / spu.second**2,
"MM_S_SQ": spu.millimeter / spu.second**2,
"M_S_SQ": spu.meter / spu.second**2,
"KM_S_SQ": spu.kilometer / spu.second**2,
"FT_S_SQ": spu.feet / spu.second**2,
},
},
SocketType.PhysicalForceScalar: {
"default": "UNEWT",
"values": {
"KG_M_S_SQ": spu.kg * spu.m/spu.second**2,
"NNEWT": spuex.nanonewton,
"UNEWT": spuex.micronewton,
"MNEWT": spuex.millinewton,
"NEWT": spu.newton,
},
},
SocketType.PhysicalAccel3DVector: {
"default": "UM_S_SQ",
"values": {
"PM_S_SQ": spu.picometer / spu.second**2,
"NM_S_SQ": spu.nanometer / spu.second**2,
"UM_S_SQ": spu.micrometer / spu.second**2,
"MM_S_SQ": spu.millimeter / spu.second**2,
"M_S_SQ": spu.meter / spu.second**2,
"KM_S_SQ": spu.kilometer / spu.second**2,
"FT_S_SQ": spu.feet / spu.second**2,
},
},
SocketType.PhysicalForce3DVector: {
"default": "UNEWT",
"values": {
"KG_M_S_SQ": spu.kg * spu.m/spu.second**2,
"NNEWT": spuex.nanonewton,
"UNEWT": spuex.micronewton,
"MNEWT": spuex.millinewton,
"NEWT": spu.newton,
},
},
SocketType.PhysicalFreq: {
"default": "THZ",
"values": {
"HZ": spu.hertz,
"KHZ": spuex.kilohertz,
"MHZ": spuex.megahertz,
"GHZ": spuex.gigahertz,
"THZ": spuex.terahertz,
"PHZ": spuex.petahertz,
"EHZ": spuex.exahertz,
},
},
SocketType.PhysicalVacWL: {
"default": "NM",
"values": {
"PM": spu.picometer, ## c(vac) = wl*freq
"A": spu.angstrom,
"NM": spu.nanometer,
"UM": spu.micrometer,
"MM": spu.millimeter,
"CM": spu.centimeter,
"M": spu.meter,
},
},
}
SocketType_to_color = {
# Basic
SocketType.Any: (0.8, 0.8, 0.8, 1.0), # Light Grey
SocketType.Bool: (0.7, 0.7, 0.7, 1.0), # Medium Light Grey
SocketType.Text: (0.7, 0.7, 0.7, 1.0), # Medium Light Grey
SocketType.FilePath: (0.6, 0.6, 0.6, 1.0), # Medium Grey
# Number
SocketType.IntegerNumber: (0.5, 0.5, 1.0, 1.0), # Light Blue
SocketType.RationalNumber: (0.4, 0.4, 0.9, 1.0), # Medium Light Blue
SocketType.RealNumber: (0.3, 0.3, 0.8, 1.0), # Medium Blue
SocketType.ComplexNumber: (0.2, 0.2, 0.7, 1.0), # Dark Blue
# Vector
SocketType.Real2DVector: (0.5, 1.0, 0.5, 1.0), # Light Green
SocketType.Complex2DVector: (0.4, 0.9, 0.4, 1.0), # Medium Light Green
SocketType.Real3DVector: (0.3, 0.8, 0.3, 1.0), # Medium Green
SocketType.Complex3DVector: (0.2, 0.7, 0.2, 1.0), # Dark Green
# Physical
SocketType.PhysicalUnitSystem: (1.0, 0.5, 0.5, 1.0), # Light Red
SocketType.PhysicalTime: (1.0, 0.5, 0.5, 1.0), # Light Red
SocketType.PhysicalAngle: (0.9, 0.45, 0.45, 1.0), # Medium Light Red
SocketType.PhysicalLength: (0.8, 0.4, 0.4, 1.0), # Medium Red
SocketType.PhysicalArea: (0.7, 0.35, 0.35, 1.0), # Medium Dark Red
SocketType.PhysicalVolume: (0.6, 0.3, 0.3, 1.0), # Dark Red
SocketType.PhysicalPoint2D: (0.7, 0.35, 0.35, 1.0), # Medium Dark Red
SocketType.PhysicalPoint3D: (0.6, 0.3, 0.3, 1.0), # Dark Red
SocketType.PhysicalSize2D: (0.7, 0.35, 0.35, 1.0), # Medium Dark Red
SocketType.PhysicalSize3D: (0.6, 0.3, 0.3, 1.0), # Dark Red
SocketType.PhysicalMass: (0.9, 0.6, 0.4, 1.0), # Light Orange
SocketType.PhysicalSpeed: (0.8, 0.55, 0.35, 1.0), # Medium Light Orange
SocketType.PhysicalAccelScalar: (0.7, 0.5, 0.3, 1.0), # Medium Orange
SocketType.PhysicalForceScalar: (0.6, 0.45, 0.25, 1.0), # Medium Dark Orange
SocketType.PhysicalAccel3DVector: (0.7, 0.5, 0.3, 1.0), # Medium Orange
SocketType.PhysicalForce3DVector: (0.6, 0.45, 0.25, 1.0), # Medium Dark Orange
SocketType.PhysicalPol: (0.5, 0.4, 0.2, 1.0), # Dark Orange
SocketType.PhysicalFreq: (1.0, 0.7, 0.5, 1.0), # Light Peach
SocketType.PhysicalVacWL: (1.0, 0.7, 0.5, 1.0), # Light Peach
SocketType.PhysicalSpecPowerDist: (0.9, 0.65, 0.45, 1.0), # Medium Light Peach
SocketType.PhysicalSpecRelPermDist: (0.8, 0.6, 0.4, 1.0), # Medium Peach
# Blender
SocketType.BlenderObject: (0.7, 0.5, 1.0, 1.0), # Light Purple
SocketType.BlenderCollection: (0.6, 0.45, 0.9, 1.0), # Medium Light Purple
SocketType.BlenderImage: (0.5, 0.4, 0.8, 1.0), # Medium Purple
SocketType.BlenderVolume: (0.4, 0.35, 0.7, 1.0), # Medium Dark Purple
SocketType.BlenderGeoNodes: (0.3, 0.3, 0.6, 1.0), # Dark Purple
SocketType.BlenderText: (0.5, 0.5, 0.75, 1.0), # Light Lavender
SocketType.BlenderPreviewTarget: (0.5, 0.5, 0.75, 1.0), # Light Lavender
# Maxwell
SocketType.MaxwellSource: (1.0, 1.0, 0.5, 1.0), # Light Yellow
SocketType.MaxwellTemporalShape: (0.9, 0.9, 0.45, 1.0), # Medium Light Yellow
SocketType.MaxwellMedium: (0.8, 0.8, 0.4, 1.0), # Medium Yellow
SocketType.MaxwellMediumNonLinearity: (0.7, 0.7, 0.35, 1.0), # Medium Dark Yellow
SocketType.MaxwellStructure: (0.6, 0.6, 0.3, 1.0), # Dark Yellow
SocketType.MaxwellBoundBox: (0.9, 0.8, 0.5, 1.0), # Light Gold
SocketType.MaxwellBoundFace: (0.8, 0.7, 0.45, 1.0), # Medium Light Gold
SocketType.MaxwellMonitor: (0.7, 0.6, 0.4, 1.0), # Medium Gold
SocketType.MaxwellFDTDSim: (0.6, 0.5, 0.35, 1.0), # Medium Dark Gold
SocketType.MaxwellSimGrid: (0.5, 0.4, 0.3, 1.0), # Dark Gold
SocketType.MaxwellSimGridAxis: (0.4, 0.3, 0.25, 1.0), # Darkest Gold
}
BLNodeSocket_to_SocketType = {
1: {
"NodeSocketStandard": SocketType.Any,
"NodeSocketVirtual": SocketType.Any,
"NodeSocketGeometry": SocketType.Any,
"NodeSocketTexture": SocketType.Any,
"NodeSocketShader": SocketType.Any,
"NodeSocketMaterial": SocketType.Any,
"NodeSocketString": SocketType.Text,
"NodeSocketBool": SocketType.Bool,
"NodeSocketCollection": SocketType.BlenderCollection,
"NodeSocketImage": SocketType.BlenderImage,
"NodeSocketObject": SocketType.BlenderObject,
"NodeSocketFloat": SocketType.RealNumber,
"NodeSocketFloatAngle": SocketType.PhysicalAngle,
"NodeSocketFloatDistance": SocketType.PhysicalLength,
"NodeSocketFloatFactor": SocketType.RealNumber,
"NodeSocketFloatPercentage": SocketType.RealNumber,
"NodeSocketFloatTime": SocketType.PhysicalTime,
"NodeSocketFloatTimeAbsolute": SocketType.RealNumber,
"NodeSocketFloatUnsigned": SocketType.RealNumber,
"NodeSocketInt": SocketType.IntegerNumber,
"NodeSocketIntFactor": SocketType.IntegerNumber,
"NodeSocketIntPercentage": SocketType.IntegerNumber,
"NodeSocketIntUnsigned": SocketType.IntegerNumber,
},
2: {
"NodeSocketVector": SocketType.Real3DVector,
"NodeSocketVectorAcceleration": SocketType.Real3DVector,
"NodeSocketVectorDirection": SocketType.Real3DVector,
"NodeSocketVectorEuler": SocketType.Real3DVector,
"NodeSocketVectorTranslation": SocketType.Real3DVector,
"NodeSocketVectorVelocity": SocketType.Real3DVector,
"NodeSocketVectorXYZ": SocketType.Real3DVector,
#"NodeSocketVector": SocketType.Real2DVector,
#"NodeSocketVectorAcceleration": SocketType.PhysicalAccel2D,
#"NodeSocketVectorDirection": SocketType.PhysicalDir2D,
#"NodeSocketVectorEuler": SocketType.PhysicalEuler2D,
#"NodeSocketVectorTranslation": SocketType.PhysicalDispl2D,
#"NodeSocketVectorVelocity": SocketType.PhysicalVel2D,
#"NodeSocketVectorXYZ": SocketType.Real2DPoint,
},
3: {
"NodeSocketRotation": SocketType.Real3DVector,
"NodeSocketColor": SocketType.Any,
"NodeSocketVector": SocketType.Real3DVector,
#"NodeSocketVectorAcceleration": SocketType.PhysicalAccel3D,
#"NodeSocketVectorDirection": SocketType.PhysicalDir3D,
#"NodeSocketVectorEuler": SocketType.PhysicalEuler3D,
#"NodeSocketVectorTranslation": SocketType.PhysicalDispl3D,
"NodeSocketVectorTranslation": SocketType.PhysicalPoint3D,
#"NodeSocketVectorVelocity": SocketType.PhysicalVel3D,
"NodeSocketVectorXYZ": SocketType.PhysicalPoint3D,
},
}
BLNodeSocket_to_SocketType_by_desc = {
1: {
"Angle": SocketType.PhysicalAngle,
"Length": SocketType.PhysicalLength,
"Area": SocketType.PhysicalArea,
"Volume": SocketType.PhysicalVolume,
"Mass": SocketType.PhysicalMass,
"Speed": SocketType.PhysicalSpeed,
"Accel": SocketType.PhysicalAccelScalar,
"Force": SocketType.PhysicalForceScalar,
"Freq": SocketType.PhysicalFreq,
},
2: {
#"2DCount": SocketType.Int2DVector,
#"2DPoint": SocketType.PhysicalPoint2D,
#"2DSize": SocketType.PhysicalSize2D,
#"2DPol": SocketType.PhysicalPol,
"2DPoint": SocketType.PhysicalPoint3D,
"2DSize": SocketType.PhysicalSize3D,
},
3: {
#"Count": SocketType.Int3DVector,
"Point": SocketType.PhysicalPoint3D,
"Size": SocketType.PhysicalSize3D,
#"Force": SocketType.PhysicalForce3D,
"Freq": SocketType.PhysicalSize3D,
},
}
####################
# - Node Types
####################
@append_cls_name_to_values
class NodeType(BlenderTypeEnum):
KitchenSink = enum.auto()
# Inputs
UnitSystem = enum.auto()
## Inputs / Scene
Time = 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()
## Inputs /
InputFile = enum.auto()
# Outputs
## Outputs / Viewers
Viewer3D = enum.auto()
ValueViewer = enum.auto()
ConsoleViewer = enum.auto()
## Outputs / 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()
# 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()
## Mediums / Non-Linearities
AddNonLinearity = enum.auto()
ChiThreeSusceptibilityNonLinearity = enum.auto()
TwoPhotonAbsorptionNonLinearity = enum.auto()
KerrNonLinearity = enum.auto()
# Structures
ObjectStructure = enum.auto()
GeoNodesStructure = enum.auto()
ScriptedStructure = enum.auto()
## Structures / Primitives
BoxStructure = enum.auto()
SphereStructure = enum.auto()
CylinderStructure = enum.auto()
# Bounds
BoundBox = enum.auto()
## Bounds / Bound Faces
PMLBoundFace = enum.auto()
PECBoundFace = enum.auto()
PMCBoundFace = enum.auto()
BlochBoundFace = enum.auto()
PeriodicBoundFace = enum.auto()
AbsorbingBoundFace = 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()
# Sims
SimGrid = enum.auto()
## Sims / Sim Grid Axis
AutomaticSimGridAxis = enum.auto()
ManualSimGridAxis = enum.auto()
UniformSimGridAxis = enum.auto()
ArraySimGridAxis = enum.auto()
## Sim /
FDTDSim = enum.auto()
# Utilities
Combine = enum.auto()
Separate = enum.auto()
Math = enum.auto()
## Utilities / Converters
WaveConverter = enum.auto()
## Utilities / Operations
ArrayOperation = enum.auto()
####################
# - Node Category Types
####################
@wrap_values_in_MT
class NodeCategory(BlenderTypeEnum):
MAXWELLSIM = enum.auto()
# Inputs/
MAXWELLSIM_INPUTS = enum.auto()
MAXWELLSIM_INPUTS_SCENE = enum.auto()
MAXWELLSIM_INPUTS_PARAMETERS = enum.auto()
MAXWELLSIM_INPUTS_CONSTANTS = enum.auto()
MAXWELLSIM_INPUTS_LISTS = enum.auto()
# Outputs/
MAXWELLSIM_OUTPUTS = enum.auto()
MAXWELLSIM_OUTPUTS_VIEWERS = enum.auto()
MAXWELLSIM_OUTPUTS_EXPORTERS = enum.auto()
MAXWELLSIM_OUTPUTS_PLOTTERS = enum.auto()
# Sources/
MAXWELLSIM_SOURCES = enum.auto()
MAXWELLSIM_SOURCES_TEMPORALSHAPES = enum.auto()
# Mediums/
MAXWELLSIM_MEDIUMS = enum.auto()
MAXWELLSIM_MEDIUMS_NONLINEARITIES = enum.auto()
# Structures/
MAXWELLSIM_STRUCTURES = enum.auto()
MAXWELLSIM_STRUCTURES_PRIMITIVES = enum.auto()
# Bounds/
MAXWELLSIM_BOUNDS = enum.auto()
MAXWELLSIM_BOUNDS_BOUNDFACES = enum.auto()
# Monitors/
MAXWELLSIM_MONITORS = enum.auto()
MAXWELLSIM_MONITORS_NEARFIELDPROJECTIONS = enum.auto()
# Simulations/
MAXWELLSIM_SIMS = enum.auto()
MAXWELLSIM_SIMGRIDAXES = enum.auto()
# Utilities/
MAXWELLSIM_UTILITIES = enum.auto()
MAXWELLSIM_UTILITIES_CONVERTERS = enum.auto()
MAXWELLSIM_UTILITIES_OPERATIONS = enum.auto()
@classmethod
def get_tree(cls):
## TODO: Refactor
syllable_categories = [
node_category.value.split("_")
for node_category in cls
if node_category.value != "MAXWELLSIM"
]
category_tree = {}
for syllable_category in syllable_categories:
# Set Current Subtree to Root
current_category_subtree = category_tree
for i, syllable in enumerate(syllable_category):
# Create New Category Subtree and/or Step to Subtree
if syllable not in current_category_subtree:
current_category_subtree[syllable] = {}
current_category_subtree = current_category_subtree[syllable]
return category_tree
NodeCategory_to_category_label = {
# Inputs/
NodeCategory.MAXWELLSIM_INPUTS: "Inputs",
NodeCategory.MAXWELLSIM_INPUTS_SCENE: "Scene",
NodeCategory.MAXWELLSIM_INPUTS_PARAMETERS: "Parameters",
NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS: "Constants",
NodeCategory.MAXWELLSIM_INPUTS_LISTS: "Lists",
# Outputs/
NodeCategory.MAXWELLSIM_OUTPUTS: "Outputs",
NodeCategory.MAXWELLSIM_OUTPUTS_VIEWERS: "Viewers",
NodeCategory.MAXWELLSIM_OUTPUTS_EXPORTERS: "Exporters",
NodeCategory.MAXWELLSIM_OUTPUTS_PLOTTERS: "Plotters",
# Sources/
NodeCategory.MAXWELLSIM_SOURCES: "Sources",
NodeCategory.MAXWELLSIM_SOURCES_TEMPORALSHAPES: "Temporal Shapes",
# Mediums/
NodeCategory.MAXWELLSIM_MEDIUMS: "Mediums",
NodeCategory.MAXWELLSIM_MEDIUMS_NONLINEARITIES: "Non-Linearities",
# Structures/
NodeCategory.MAXWELLSIM_STRUCTURES: "Structures",
NodeCategory.MAXWELLSIM_STRUCTURES_PRIMITIVES: "Primitives",
# Bounds/
NodeCategory.MAXWELLSIM_BOUNDS: "Bounds",
NodeCategory.MAXWELLSIM_BOUNDS_BOUNDFACES: "Bound Faces",
# Monitors/
NodeCategory.MAXWELLSIM_MONITORS: "Monitors",
NodeCategory.MAXWELLSIM_MONITORS_NEARFIELDPROJECTIONS: "Near-Field Projections",
# Simulations/
NodeCategory.MAXWELLSIM_SIMS: "Simulations",
NodeCategory.MAXWELLSIM_SIMGRIDAXES: "Sim Grid Axes",
# Utilities/
NodeCategory.MAXWELLSIM_UTILITIES: "Utilities",
NodeCategory.MAXWELLSIM_UTILITIES_CONVERTERS: "Converters",
NodeCategory.MAXWELLSIM_UTILITIES_OPERATIONS: "Operations",
}
####################
# - Protocols
####################
class SocketDefProtocol(typ.Protocol):
socket_type: SocketType
label: str
def init(self, bl_socket: bpy.types.NodeSocket) -> None:
...
class PresetDef(pyd.BaseModel):
label: str
description: str
values: dict[SocketName, typ.Any]
SocketReturnType = typ.TypeVar('SocketReturnType', covariant=True)
## - Covariance: If B subtypes A, then Container[B] subtypes Container[A].
## - This is absolutely what we want here.
#@typ.runtime_checkable
#class BLSocketProtocol(typ.Protocol):
# socket_type: SocketType
# socket_color: BlenderColorRGB
#
# bl_label: str
#
# compatible_types: dict[typ.Type, set[typ.Callable[[typ.Any], bool]]]
#
# def draw(
# self,
# context: bpy.types.Context,
# layout: bpy.types.UILayout,
# node: bpy.types.Node,
# text: str,
# ) -> None:
# ...
#
# @property
# def default_value(self) -> typ.Any:
# ...
# @default_value.setter
# def default_value(self, value: typ.Any) -> typ.Any:
# ...
#
@typ.runtime_checkable
class NodeTypeProtocol(typ.Protocol):
node_type: NodeType
bl_label: str
input_sockets: dict[SocketName, SocketDefProtocol]
output_sockets: dict[SocketName, SocketDefProtocol]
presets: dict[PresetID, PresetDef] | None
# Built-In Blender Methods
def init(self, context: bpy.types.Context) -> None:
...
def draw_buttons(
self,
context: bpy.types.Context,
layout: bpy.types.UILayout,
) -> None:
...
@classmethod
def poll(cls, ntree: bpy.types.NodeTree) -> None:
...
# Socket Getters
def g_input_bl_socket(
self,
input_socket_name: SocketName,
) -> bpy.types.NodeSocket:
...
def g_output_bl_socket(
self,
output_socket_name: SocketName,
) -> bpy.types.NodeSocket:
...
# Socket Methods
def s_input_value(
self,
input_socket_name: SocketName,
value: typ.Any
) -> typ.Any:
...
# Data-Flow Methods
def compute_input(
self,
input_socket_name: SocketName,
) -> typ.Any:
...
def compute_output(
self,
output_socket_name: SocketName,
) -> typ.Any:
...

View File

@ -0,0 +1,52 @@
####################
# - String Types
####################
from .bl import SocketName
from .bl import PresetName
from .bl import ManagedObjName
from .bl import BLEnumID
from .bl import BLColorRGBA
####################
# - Icon Types
####################
from .icons import Icon
####################
# - Tree Types
####################
from .trees import TreeType
####################
# - Socket Types
####################
from .socket_types import SocketType
from .socket_units import SOCKET_UNITS
from .socket_colors import SOCKET_COLORS
from .socket_shapes import SOCKET_SHAPES
from .socket_bl_maps import BLSocketToSocket
####################
# - Node Types
####################
from .node_types import NodeType
from .node_cats import NodeCategory
from .node_cat_labels import NODE_CAT_LABELS
####################
# - Managed Obj Type
####################
from .managed_obj_type import ManagedObjType
####################
# - Data Flows
####################
from .data_flows import DataFlowKind
####################
# - Schemas
####################
from . import schemas

View File

@ -0,0 +1,26 @@
import typing as typ
import pydantic as pyd
import typing_extensions as pytypes_ext
import bpy
####################
# - Pure BL Types
####################
BLEnumID = pytypes_ext.Annotated[str, pyd.StringConstraints(
pattern=r'^[A-Z_]+$',
)]
SocketName = pytypes_ext.Annotated[str, pyd.StringConstraints(
pattern=r'^[a-zA-Z0-9_]+$',
)]
PresetName = pytypes_ext.Annotated[str, pyd.StringConstraints(
pattern=r'^[a-zA-Z0-9_]+$',
)]
BLColorRGBA = tuple[float, float, float, float]
####################
# - Shared-With-BL Types
####################
ManagedObjName = pytypes_ext.Annotated[str, pyd.StringConstraints(
pattern=r'^[a-z_]+$',
)]

View File

@ -0,0 +1,56 @@
import enum
from ....utils.blender_type_enum import BlenderTypeEnum
class DataFlowKind(BlenderTypeEnum):
"""Defines a shape/kind of data that may flow through a node tree.
Since a node socket may define one of each, we can support several related kinds of data flow through the same node-graph infrastructure.
Attributes:
Value: A value usable without new data.
- Basic types aka. float, int, list, string, etc. .
- Exotic (immutable-ish) types aka. numpy array, KDTree, etc. .
- A usable constructed object, ex. a `tidy3d.Box`.
- Expressions (`sp.Expr`) that don't have unknown variables.
- Lazy sequences aka. generators, with all data bound.
LazyValue: An object which, when given new data, can make many values.
- An `sp.Expr`, which might need `simplify`ing, `jax` JIT'ing, unit cancellations, variable substitutions, etc. before use.
- Lazy objects, for which all parameters aren't yet known.
- A computational graph aka. `aesara`, which may even need to be handled before
Capabilities: A `ValueCapability` object providing compatibility.
# Value Data Flow
Simply passing values is the simplest and easiest use case.
This doesn't mean it's "dumb" - ex. a `sp.Expr` might, before use, have `simplify`, rewriting, unit cancellation, etc. run.
All of this is okay, as long as there is no *introduction of new data* ex. variable substitutions.
# Lazy Value Data Flow
By passing (essentially) functions, one supports:
- **Lightness**: While lazy values can be made expensive to construct, they will generally not be nearly as heavy to handle when trying to work with ex. operations on voxel arrays.
- **Performance**: Parameterizing ex. `sp.Expr` with variables allows one to build very optimized functions, which can make ex. node graph updates very fast if the only operation run is the `jax` JIT'ed function (aka. GPU accelerated) generated from the final full expression.
- **Numerical Stability**: Libraries like `aesara` build a computational graph, which can be automatically rewritten to avoid many obvious conditioning / cancellation errors.
- **Lazy Output**: The goal of a node-graph may not be the definition of a single value, but rather, a parameterized expression for generating *many values* with known properties. This is especially interesting for use cases where one wishes to build an optimization step using nodes.
# Capability Passing
By being able to pass "capabilities" next to other kinds of values, nodes can quickly determine whether a given link is valid without having to actually compute it.
# Lazy Parameter Value
When using parameterized LazyValues, one may wish to independently pass parameter values through the graph, so they can be inserted into the final (cached) high-performance expression without.
The advantage of using a different data flow would be changing this kind of value would ONLY invalidate lazy parameter value caches, which would allow an incredibly fast path of getting the value into the lazy expression for high-performance computation.
Implementation TBD - though, ostensibly, one would have a "parameter" node which both would only provide a LazyValue (aka. a symbolic variable), but would also be able to provide a LazyParamValue, which would be a particular value of some kind (probably via the `value` of some other node socket).
"""
Value = enum.auto()
LazyValue = enum.auto()
Capabilities = enum.auto()
LazyParamValue = enum.auto()

View File

@ -0,0 +1,4 @@
from ....utils.blender_type_enum import BlenderTypeEnum
class Icon(BlenderTypeEnum):
SimNodeEditor = "MOD_SIMPLEDEFORM"

View File

@ -0,0 +1,9 @@
import enum
from ....utils.blender_type_enum import (
BlenderTypeEnum
)
class ManagedObjType(BlenderTypeEnum):
ManagedBLObject = enum.auto()
ManagedBLImage = enum.auto()

View File

@ -0,0 +1,46 @@
from .node_cats import NodeCategory as NC
NODE_CAT_LABELS = {
# 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",
# Outputs/
NC.MAXWELLSIM_OUTPUTS: "Outputs",
NC.MAXWELLSIM_OUTPUTS_VIEWERS: "Viewers",
NC.MAXWELLSIM_OUTPUTS_EXPORTERS: "Exporters",
NC.MAXWELLSIM_OUTPUTS_PLOTTERS: "Plotters",
# Sources/
NC.MAXWELLSIM_SOURCES: "Sources",
NC.MAXWELLSIM_SOURCES_TEMPORALSHAPES: "Temporal Shapes",
# Mediums/
NC.MAXWELLSIM_MEDIUMS: "Mediums",
NC.MAXWELLSIM_MEDIUMS_NONLINEARITIES: "Non-Linearities",
# Structures/
NC.MAXWELLSIM_STRUCTURES: "Structures",
NC.MAXWELLSIM_STRUCTURES_PRIMITIVES: "Primitives",
# Bounds/
NC.MAXWELLSIM_BOUNDS: "Bounds",
NC.MAXWELLSIM_BOUNDS_BOUNDFACES: "Bound Faces",
# Monitors/
NC.MAXWELLSIM_MONITORS: "Monitors",
NC.MAXWELLSIM_MONITORS_NEARFIELDPROJECTIONS: "Near-Field Projections",
# Simulations/
NC.MAXWELLSIM_SIMS: "Simulations",
NC.MAXWELLSIM_SIMGRIDAXES: "Sim Grid Axes",
# Utilities/
NC.MAXWELLSIM_UTILITIES: "Utilities",
NC.MAXWELLSIM_UTILITIES_CONVERTERS: "Converters",
NC.MAXWELLSIM_UTILITIES_OPERATIONS: "Operations",
}

View File

@ -0,0 +1,74 @@
import enum
from ....utils.blender_type_enum import (
BlenderTypeEnum, wrap_values_in_MT
)
@wrap_values_in_MT
class NodeCategory(BlenderTypeEnum):
MAXWELLSIM = 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()
# Outputs/
MAXWELLSIM_OUTPUTS = enum.auto()
MAXWELLSIM_OUTPUTS_VIEWERS = enum.auto()
MAXWELLSIM_OUTPUTS_EXPORTERS = enum.auto()
MAXWELLSIM_OUTPUTS_PLOTTERS = enum.auto()
# Sources/
MAXWELLSIM_SOURCES = enum.auto()
MAXWELLSIM_SOURCES_TEMPORALSHAPES = enum.auto()
# Mediums/
MAXWELLSIM_MEDIUMS = enum.auto()
MAXWELLSIM_MEDIUMS_NONLINEARITIES = enum.auto()
# Structures/
MAXWELLSIM_STRUCTURES = enum.auto()
MAXWELLSIM_STRUCTURES_PRIMITIVES = enum.auto()
# Bounds/
MAXWELLSIM_BOUNDS = enum.auto()
MAXWELLSIM_BOUNDS_BOUNDFACES = enum.auto()
# Monitors/
MAXWELLSIM_MONITORS = enum.auto()
MAXWELLSIM_MONITORS_NEARFIELDPROJECTIONS = enum.auto()
# Simulations/
MAXWELLSIM_SIMS = enum.auto()
MAXWELLSIM_SIMGRIDAXES = enum.auto()
# Utilities/
MAXWELLSIM_UTILITIES = enum.auto()
MAXWELLSIM_UTILITIES_CONVERTERS = enum.auto()
MAXWELLSIM_UTILITIES_OPERATIONS = enum.auto()
@classmethod
def get_tree(cls):
## TODO: Refactor
syllable_categories = [
str(node_category.value).split("_")
for node_category in cls
if node_category.value != "MAXWELLSIM"
]
category_tree = {}
for syllable_category in syllable_categories:
# Set Current Subtree to Root
current_category_subtree = category_tree
for i, syllable in enumerate(syllable_category):
# Create New Category Subtree and/or Step to Subtree
if syllable not in current_category_subtree:
current_category_subtree[syllable] = {}
current_category_subtree = current_category_subtree[syllable]
return category_tree

View File

@ -0,0 +1,147 @@
import enum
from ....utils.blender_type_enum import (
BlenderTypeEnum, append_cls_name_to_values
)
@append_cls_name_to_values
class NodeType(BlenderTypeEnum):
KitchenSink = enum.auto()
# Inputs
UnitSystem = enum.auto()
## Inputs / Scene
Time = enum.auto()
## Inputs / Importers
Tidy3DWebImporter = 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()
## Inputs /
InputFile = enum.auto()
# Outputs
## Outputs / Viewers
Viewer = enum.auto()
ValueViewer = enum.auto()
ConsoleViewer = enum.auto()
## Outputs / Exporters
JSONFileExporter = enum.auto()
Tidy3DWebExporter = 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()
# 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()
## Mediums / Non-Linearities
AddNonLinearity = enum.auto()
ChiThreeSusceptibilityNonLinearity = enum.auto()
TwoPhotonAbsorptionNonLinearity = enum.auto()
KerrNonLinearity = enum.auto()
# Structures
ObjectStructure = enum.auto()
GeoNodesStructure = enum.auto()
ScriptedStructure = enum.auto()
## Structures / Primitives
BoxStructure = enum.auto()
SphereStructure = enum.auto()
CylinderStructure = enum.auto()
# Bounds
BoundBox = enum.auto()
## Bounds / Bound Faces
PMLBoundFace = enum.auto()
PECBoundFace = enum.auto()
PMCBoundFace = enum.auto()
BlochBoundFace = enum.auto()
PeriodicBoundFace = enum.auto()
AbsorbingBoundFace = 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()
# Sims
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()
# Utilities
Combine = enum.auto()
Separate = enum.auto()
Math = enum.auto()
## Utilities / Converters
WaveConverter = enum.auto()
## Utilities / Operations
ArrayOperation = enum.auto()

View File

@ -0,0 +1,4 @@
from .preset_def import PresetDef
from .socket_def import SocketDef
from .managed_obj import ManagedObj
from .managed_obj_def import ManagedObjDef

View File

@ -0,0 +1,31 @@
import typing as typ
import typing as typx
import pydantic as pyd
from ..bl import ManagedObjName, SocketName
from ..managed_obj_type import ManagedObjType
class ManagedObj(typ.Protocol):
managed_obj_type: ManagedObjType
def __init__(
self,
name: ManagedObjName,
):
...
@property
def name(self) -> str: ...
@name.setter
def name(self, value: str): ...
def free(self):
...
def bl_select(self):
"""If this is a managed Blender object, and the operation "select this in Blender" makes sense, then do so.
Else, do nothing.
"""
pass

View File

@ -0,0 +1,11 @@
import typing as typ
from dataclasses import dataclass
import pydantic as pyd
from ..bl import PresetName, SocketName, BLEnumID
from .managed_obj import ManagedObj
class ManagedObjDef(pyd.BaseModel):
mk: typ.Callable[[str], ManagedObj]
name_prefix: str = ""

View File

@ -0,0 +1,9 @@
import typing as typ
import pydantic as pyd
from ..bl import ManagedObjName, SocketName
from ..managed_obj_type import ManagedObjType
class MaxwellSimNode(typ.Protocol):

View File

@ -0,0 +1,10 @@
import typing as typ
import pydantic as pyd
from ..bl import PresetName, SocketName, BLEnumID
class PresetDef(pyd.BaseModel):
label: PresetName
description: str
values: dict[SocketName, typ.Any]

View File

@ -0,0 +1,12 @@
import typing as typ
import bpy
from ..socket_types import SocketType
@typ.runtime_checkable
class SocketDef(typ.Protocol):
socket_type: SocketType
def init(self, bl_socket: bpy.types.NodeSocket) -> None:
...

View File

@ -0,0 +1,119 @@
import sympy.physics.units as spu
from ....utils import extra_sympy_units as spuex
from .socket_types import SocketType as ST
Dimensions = int ## Num. Elements in the Socket
BLSocketType = str ## A Blender-Defined Socket Type
class BLSocketToSocket:
"""Encodes ways of converting blender sockets of known dimensionality
to a corresponding SocketType.
"Dimensionality" is simply how many elements the blender socket has.
The user must explicitly specify this, as blender allows a variable
number of elements for some sockets, and we do not.
"""
####################
# - Direct BLSocketType -> SocketType
####################
by_bl_socket_type: dict[Dimensions, dict[BLSocketType, ST]] = {
1: {
"NodeSocketStandard": ST.Any,
"NodeSocketVirtual": ST.Any,
"NodeSocketGeometry": ST.Any,
"NodeSocketTexture": ST.Any,
"NodeSocketShader": ST.Any,
"NodeSocketMaterial": ST.Any,
"NodeSocketString": ST.Text,
"NodeSocketBool": ST.Bool,
"NodeSocketCollection": ST.BlenderCollection,
"NodeSocketImage": ST.BlenderImage,
"NodeSocketObject": ST.BlenderObject,
"NodeSocketFloat": ST.RealNumber,
"NodeSocketFloatAngle": ST.PhysicalAngle,
"NodeSocketFloatDistance": ST.PhysicalLength,
"NodeSocketFloatFactor": ST.RealNumber,
"NodeSocketFloatPercentage": ST.RealNumber,
"NodeSocketFloatTime": ST.PhysicalTime,
"NodeSocketFloatTimeAbsolute": ST.RealNumber,
"NodeSocketFloatUnsigned": ST.RealNumber,
"NodeSocketInt": ST.IntegerNumber,
"NodeSocketIntFactor": ST.IntegerNumber,
"NodeSocketIntPercentage": ST.IntegerNumber,
"NodeSocketIntUnsigned": ST.IntegerNumber,
},
2: {
"NodeSocketVector": ST.Real3DVector,
"NodeSocketVectorAcceleration": ST.Real3DVector,
"NodeSocketVectorDirection": ST.Real3DVector,
"NodeSocketVectorEuler": ST.Real3DVector,
"NodeSocketVectorTranslation": ST.Real3DVector,
"NodeSocketVectorVelocity": ST.Real3DVector,
"NodeSocketVectorXYZ": ST.Real3DVector,
#"NodeSocketVector": ST.Real2DVector,
#"NodeSocketVectorAcceleration": ST.PhysicalAccel2D,
#"NodeSocketVectorDirection": ST.PhysicalDir2D,
#"NodeSocketVectorEuler": ST.PhysicalEuler2D,
#"NodeSocketVectorTranslation": ST.PhysicalDispl2D,
#"NodeSocketVectorVelocity": ST.PhysicalVel2D,
#"NodeSocketVectorXYZ": ST.Real2DPoint,
},
3: {
"NodeSocketRotation": ST.Real3DVector,
"NodeSocketColor": ST.Any,
"NodeSocketVector": ST.Real3DVector,
#"NodeSocketVectorAcceleration": ST.PhysicalAccel3D,
#"NodeSocketVectorDirection": ST.PhysicalDir3D,
#"NodeSocketVectorEuler": ST.PhysicalEuler3D,
#"NodeSocketVectorTranslation": ST.PhysicalDispl3D,
"NodeSocketVectorTranslation": ST.PhysicalPoint3D,
#"NodeSocketVectorVelocity": ST.PhysicalVel3D,
"NodeSocketVectorXYZ": ST.PhysicalPoint3D,
},
}
####################
# - BLSocket Description-Driven SocketType Choice
####################
by_description = {
1: {
"Angle": ST.PhysicalAngle,
"Length": ST.PhysicalLength,
"Area": ST.PhysicalArea,
"Volume": ST.PhysicalVolume,
"Mass": ST.PhysicalMass,
"Speed": ST.PhysicalSpeed,
"Accel": ST.PhysicalAccelScalar,
"Force": ST.PhysicalForceScalar,
"Freq": ST.PhysicalFreq,
},
2: {
#"2DCount": ST.Int2DVector,
#"2DPoint": ST.PhysicalPoint2D,
#"2DSize": ST.PhysicalSize2D,
#"2DPol": ST.PhysicalPol,
"2DPoint": ST.PhysicalPoint3D,
"2DSize": ST.PhysicalSize3D,
},
3: {
#"Count": ST.Int3DVector,
"Point": ST.PhysicalPoint3D,
"Size": ST.PhysicalSize3D,
#"Force": ST.PhysicalForce3D,
"Freq": ST.PhysicalSize3D,
},
}

View File

@ -0,0 +1,73 @@
import sympy.physics.units as spu
from ....utils import extra_sympy_units as spuex
from .socket_types import SocketType as ST
## TODO: Don't just presume sRGB.
SOCKET_COLORS = {
# Basic
ST.Any: (0.8, 0.8, 0.8, 1.0), # Light Grey
ST.Bool: (0.7, 0.7, 0.7, 1.0), # Medium Light Grey
ST.Text: (0.7, 0.7, 0.7, 1.0), # Medium Light Grey
ST.FilePath: (0.6, 0.6, 0.6, 1.0), # Medium Grey
ST.Secret: (0.0, 0.0, 0.0, 1.0), # Black
# Number
ST.IntegerNumber: (0.5, 0.5, 1.0, 1.0), # Light Blue
ST.RationalNumber: (0.4, 0.4, 0.9, 1.0), # Medium Light Blue
ST.RealNumber: (0.3, 0.3, 0.8, 1.0), # Medium Blue
ST.ComplexNumber: (0.2, 0.2, 0.7, 1.0), # Dark Blue
# Vector
ST.Real2DVector: (0.5, 1.0, 0.5, 1.0), # Light Green
ST.Complex2DVector: (0.4, 0.9, 0.4, 1.0), # Medium Light Green
ST.Real3DVector: (0.3, 0.8, 0.3, 1.0), # Medium Green
ST.Complex3DVector: (0.2, 0.7, 0.2, 1.0), # Dark Green
# Physical
ST.PhysicalUnitSystem: (1.0, 0.5, 0.5, 1.0), # Light Red
ST.PhysicalTime: (1.0, 0.5, 0.5, 1.0), # Light Red
ST.PhysicalAngle: (0.9, 0.45, 0.45, 1.0), # Medium Light Red
ST.PhysicalLength: (0.8, 0.4, 0.4, 1.0), # Medium Red
ST.PhysicalArea: (0.7, 0.35, 0.35, 1.0), # Medium Dark Red
ST.PhysicalVolume: (0.6, 0.3, 0.3, 1.0), # Dark Red
ST.PhysicalPoint2D: (0.7, 0.35, 0.35, 1.0), # Medium Dark Red
ST.PhysicalPoint3D: (0.6, 0.3, 0.3, 1.0), # Dark Red
ST.PhysicalSize2D: (0.7, 0.35, 0.35, 1.0), # Medium Dark Red
ST.PhysicalSize3D: (0.6, 0.3, 0.3, 1.0), # Dark Red
ST.PhysicalMass: (0.9, 0.6, 0.4, 1.0), # Light Orange
ST.PhysicalSpeed: (0.8, 0.55, 0.35, 1.0), # Medium Light Orange
ST.PhysicalAccelScalar: (0.7, 0.5, 0.3, 1.0), # Medium Orange
ST.PhysicalForceScalar: (0.6, 0.45, 0.25, 1.0), # Medium Dark Orange
ST.PhysicalAccel3DVector: (0.7, 0.5, 0.3, 1.0), # Medium Orange
ST.PhysicalForce3DVector: (0.6, 0.45, 0.25, 1.0), # Medium Dark Orange
ST.PhysicalPol: (0.5, 0.4, 0.2, 1.0), # Dark Orange
ST.PhysicalFreq: (1.0, 0.7, 0.5, 1.0), # Light Peach
# Blender
ST.BlenderObject: (0.7, 0.5, 1.0, 1.0), # Light Purple
ST.BlenderCollection: (0.6, 0.45, 0.9, 1.0), # Medium Light Purple
ST.BlenderImage: (0.5, 0.4, 0.8, 1.0), # Medium Purple
ST.BlenderVolume: (0.4, 0.35, 0.7, 1.0), # Medium Dark Purple
ST.BlenderGeoNodes: (0.3, 0.3, 0.6, 1.0), # Dark Purple
ST.BlenderText: (0.5, 0.5, 0.75, 1.0), # Light Lavender
ST.BlenderPreviewTarget: (0.5, 0.5, 0.75, 1.0), # Light Lavender
# Maxwell
ST.MaxwellSource: (1.0, 1.0, 0.5, 1.0), # Light Yellow
ST.MaxwellTemporalShape: (0.9, 0.9, 0.45, 1.0), # Medium Light Yellow
ST.MaxwellMedium: (0.8, 0.8, 0.4, 1.0), # Medium Yellow
ST.MaxwellMediumNonLinearity: (0.7, 0.7, 0.35, 1.0), # Medium Dark Yellow
ST.MaxwellStructure: (0.6, 0.6, 0.3, 1.0), # Dark Yellow
ST.MaxwellBoundBox: (0.9, 0.8, 0.5, 1.0), # Light Gold
ST.MaxwellBoundFace: (0.8, 0.7, 0.45, 1.0), # Medium Light Gold
ST.MaxwellMonitor: (0.7, 0.6, 0.4, 1.0), # Medium Gold
ST.MaxwellFDTDSim: (0.6, 0.5, 0.35, 1.0), # Medium Dark Gold
ST.MaxwellSimGrid: (0.5, 0.4, 0.3, 1.0), # Dark Gold
ST.MaxwellSimGridAxis: (0.4, 0.3, 0.25, 1.0), # Darkest Gold
ST.MaxwellSimDomain: (0.4, 0.3, 0.25, 1.0), # Darkest Gold
# Tidy3D
ST.Tidy3DCloudTask: (0.4, 0.3, 0.25, 1.0), # Darkest Gold
}

View File

@ -0,0 +1,68 @@
from .socket_types import SocketType as ST
SOCKET_SHAPES = {
# Basic
ST.Any: "CIRCLE",
ST.Bool: "CIRCLE",
ST.Text: "SQUARE",
ST.FilePath: "SQUARE",
ST.Secret: "SQUARE",
# Number
ST.IntegerNumber: "CIRCLE",
ST.RationalNumber: "CIRCLE",
ST.RealNumber: "CIRCLE",
ST.ComplexNumber: "CIRCLE_DOT",
# Vector
ST.Real2DVector: "SQUARE_DOT",
ST.Complex2DVector: "DIAMOND_DOT",
ST.Real3DVector: "SQUARE_DOT",
ST.Complex3DVector: "DIAMOND_DOT",
# Physical
ST.PhysicalUnitSystem: "CIRCLE",
ST.PhysicalTime: "CIRCLE",
ST.PhysicalAngle: "DIAMOND",
ST.PhysicalLength: "SQUARE",
ST.PhysicalArea: "SQUARE",
ST.PhysicalVolume: "SQUARE",
ST.PhysicalPoint2D: "DIAMOND",
ST.PhysicalPoint3D: "DIAMOND",
ST.PhysicalSize2D: "SQUARE",
ST.PhysicalSize3D: "SQUARE",
ST.PhysicalMass: "CIRCLE",
ST.PhysicalSpeed: "CIRCLE",
ST.PhysicalAccelScalar: "CIRCLE",
ST.PhysicalForceScalar: "CIRCLE",
ST.PhysicalAccel3DVector: "SQUARE_DOT",
ST.PhysicalForce3DVector: "SQUARE_DOT",
ST.PhysicalPol: "DIAMOND",
ST.PhysicalFreq: "CIRCLE",
# Blender
ST.BlenderObject: "SQUARE",
ST.BlenderCollection: "SQUARE",
ST.BlenderImage: "DIAMOND",
ST.BlenderVolume: "DIAMOND",
ST.BlenderGeoNodes: "DIAMOND",
ST.BlenderText: "SQUARE",
ST.BlenderPreviewTarget: "SQUARE",
# Maxwell
ST.MaxwellSource: "CIRCLE",
ST.MaxwellTemporalShape: "CIRCLE",
ST.MaxwellMedium: "CIRCLE",
ST.MaxwellMediumNonLinearity: "CIRCLE",
ST.MaxwellStructure: "SQUARE",
ST.MaxwellBoundBox: "SQUARE",
ST.MaxwellBoundFace: "DIAMOND",
ST.MaxwellMonitor: "CIRCLE",
ST.MaxwellFDTDSim: "SQUARE",
ST.MaxwellSimGrid: "SQUARE",
ST.MaxwellSimGridAxis: "DIAMOND",
ST.MaxwellSimDomain: "SQUARE",
# Tidy3D
ST.Tidy3DCloudTask: "CIRCLE",
}

View File

@ -0,0 +1,89 @@
import enum
from ....utils.blender_type_enum import (
BlenderTypeEnum, append_cls_name_to_values, wrap_values_in_MT
)
@append_cls_name_to_values
class SocketType(BlenderTypeEnum):
# Base
Any = enum.auto()
Bool = enum.auto()
Text = enum.auto()
FilePath = enum.auto()
Secret = enum.auto()
# Number
IntegerNumber = enum.auto()
RationalNumber = enum.auto()
RealNumber = enum.auto()
ComplexNumber = enum.auto()
# Vector
Real2DVector = enum.auto()
Complex2DVector = enum.auto()
Real3DVector = enum.auto()
Complex3DVector = enum.auto()
# Physical
PhysicalUnitSystem = enum.auto()
PhysicalTime = enum.auto()
PhysicalAngle = enum.auto()
PhysicalLength = enum.auto()
PhysicalArea = enum.auto()
PhysicalVolume = enum.auto()
PhysicalPoint2D = enum.auto()
PhysicalPoint3D = enum.auto()
PhysicalSize2D = enum.auto()
PhysicalSize3D = enum.auto()
PhysicalMass = enum.auto()
PhysicalSpeed = enum.auto()
PhysicalAccelScalar = enum.auto()
PhysicalForceScalar = enum.auto()
PhysicalAccel3DVector = enum.auto()
PhysicalForce3DVector = enum.auto()
PhysicalPol = enum.auto()
PhysicalFreq = enum.auto()
# Blender
BlenderObject = enum.auto()
BlenderCollection = enum.auto()
BlenderImage = enum.auto()
BlenderVolume = enum.auto()
BlenderGeoNodes = enum.auto()
BlenderText = enum.auto()
BlenderPreviewTarget = enum.auto()
# Maxwell
MaxwellSource = enum.auto()
MaxwellTemporalShape = enum.auto()
MaxwellMedium = enum.auto()
MaxwellMediumNonLinearity = enum.auto()
MaxwellStructure = enum.auto()
MaxwellBoundBox = enum.auto()
MaxwellBoundFace = enum.auto()
MaxwellMonitor = enum.auto()
MaxwellFDTDSim = enum.auto()
MaxwellSimGrid = enum.auto()
MaxwellSimGridAxis = enum.auto()
MaxwellSimDomain = enum.auto()
# Tidy3D
Tidy3DCloudTask = enum.auto()

View File

@ -0,0 +1,265 @@
import sympy.physics.units as spu
from ....utils import extra_sympy_units as spuex
from .socket_types import SocketType as ST
SOCKET_UNITS = {
ST.PhysicalTime: {
"default": "PS",
"values": {
"PS": spu.picosecond,
"NS": spu.nanosecond,
"MS": spu.microsecond,
"MLSEC": spu.millisecond,
"SEC": spu.second,
"MIN": spu.minute,
"HOUR": spu.hour,
"DAY": spu.day,
},
},
ST.PhysicalAngle: {
"default": "RADIAN",
"values": {
"RADIAN": spu.radian,
"DEGREE": spu.degree,
"STERAD": spu.steradian,
"ANGMIL": spu.angular_mil,
},
},
ST.PhysicalLength: {
"default": "UM",
"values": {
"PM": spu.picometer,
"A": spu.angstrom,
"NM": spu.nanometer,
"UM": spu.micrometer,
"MM": spu.millimeter,
"CM": spu.centimeter,
"M": spu.meter,
"INCH": spu.inch,
"FOOT": spu.foot,
"YARD": spu.yard,
"MILE": spu.mile,
},
},
ST.PhysicalArea: {
"default": "UM_SQ",
"values": {
"PM_SQ": spu.picometer**2,
"A_SQ": spu.angstrom**2,
"NM_SQ": spu.nanometer**2,
"UM_SQ": spu.micrometer**2,
"MM_SQ": spu.millimeter**2,
"CM_SQ": spu.centimeter**2,
"M_SQ": spu.meter**2,
"INCH_SQ": spu.inch**2,
"FOOT_SQ": spu.foot**2,
"YARD_SQ": spu.yard**2,
"MILE_SQ": spu.mile**2,
},
},
ST.PhysicalVolume: {
"default": "UM_CB",
"values": {
"PM_CB": spu.picometer**3,
"A_CB": spu.angstrom**3,
"NM_CB": spu.nanometer**3,
"UM_CB": spu.micrometer**3,
"MM_CB": spu.millimeter**3,
"CM_CB": spu.centimeter**3,
"M_CB": spu.meter**3,
"ML": spu.milliliter,
"L": spu.liter,
"INCH_CB": spu.inch**3,
"FOOT_CB": spu.foot**3,
"YARD_CB": spu.yard**3,
"MILE_CB": spu.mile**3,
},
},
ST.PhysicalPoint2D: {
"default": "UM",
"values": {
"PM": spu.picometer,
"A": spu.angstrom,
"NM": spu.nanometer,
"UM": spu.micrometer,
"MM": spu.millimeter,
"CM": spu.centimeter,
"M": spu.meter,
"INCH": spu.inch,
"FOOT": spu.foot,
"YARD": spu.yard,
"MILE": spu.mile,
},
},
ST.PhysicalPoint3D: {
"default": "UM",
"values": {
"PM": spu.picometer,
"A": spu.angstrom,
"NM": spu.nanometer,
"UM": spu.micrometer,
"MM": spu.millimeter,
"CM": spu.centimeter,
"M": spu.meter,
"INCH": spu.inch,
"FOOT": spu.foot,
"YARD": spu.yard,
"MILE": spu.mile,
},
},
ST.PhysicalSize2D: {
"default": "UM",
"values": {
"PM": spu.picometer,
"A": spu.angstrom,
"NM": spu.nanometer,
"UM": spu.micrometer,
"MM": spu.millimeter,
"CM": spu.centimeter,
"M": spu.meter,
"INCH": spu.inch,
"FOOT": spu.foot,
"YARD": spu.yard,
"MILE": spu.mile,
},
},
ST.PhysicalSize3D: {
"default": "UM",
"values": {
"PM": spu.picometer,
"A": spu.angstrom,
"NM": spu.nanometer,
"UM": spu.micrometer,
"MM": spu.millimeter,
"CM": spu.centimeter,
"M": spu.meter,
"INCH": spu.inch,
"FOOT": spu.foot,
"YARD": spu.yard,
"MILE": spu.mile,
},
},
ST.PhysicalMass: {
"default": "UG",
"values": {
"E_REST": spu.electron_rest_mass,
"DAL": spu.dalton,
"UG": spu.microgram,
"MG": spu.milligram,
"G": spu.gram,
"KG": spu.kilogram,
"TON": spu.metric_ton,
},
},
ST.PhysicalSpeed: {
"default": "UM_S",
"values": {
"PM_S": spu.picometer / spu.second,
"NM_S": spu.nanometer / spu.second,
"UM_S": spu.micrometer / spu.second,
"MM_S": spu.millimeter / spu.second,
"M_S": spu.meter / spu.second,
"KM_S": spu.kilometer / spu.second,
"KM_H": spu.kilometer / spu.hour,
"FT_S": spu.feet / spu.second,
"MI_H": spu.mile / spu.hour,
},
},
ST.PhysicalAccelScalar: {
"default": "UM_S_SQ",
"values": {
"PM_S_SQ": spu.picometer / spu.second**2,
"NM_S_SQ": spu.nanometer / spu.second**2,
"UM_S_SQ": spu.micrometer / spu.second**2,
"MM_S_SQ": spu.millimeter / spu.second**2,
"M_S_SQ": spu.meter / spu.second**2,
"KM_S_SQ": spu.kilometer / spu.second**2,
"FT_S_SQ": spu.feet / spu.second**2,
},
},
ST.PhysicalForceScalar: {
"default": "UNEWT",
"values": {
"KG_M_S_SQ": spu.kg * spu.m/spu.second**2,
"NNEWT": spuex.nanonewton,
"UNEWT": spuex.micronewton,
"MNEWT": spuex.millinewton,
"NEWT": spu.newton,
},
},
ST.PhysicalAccel3DVector: {
"default": "UM_S_SQ",
"values": {
"PM_S_SQ": spu.picometer / spu.second**2,
"NM_S_SQ": spu.nanometer / spu.second**2,
"UM_S_SQ": spu.micrometer / spu.second**2,
"MM_S_SQ": spu.millimeter / spu.second**2,
"M_S_SQ": spu.meter / spu.second**2,
"KM_S_SQ": spu.kilometer / spu.second**2,
"FT_S_SQ": spu.feet / spu.second**2,
},
},
ST.PhysicalForce3DVector: {
"default": "UNEWT",
"values": {
"KG_M_S_SQ": spu.kg * spu.m/spu.second**2,
"NNEWT": spuex.nanonewton,
"UNEWT": spuex.micronewton,
"MNEWT": spuex.millinewton,
"NEWT": spu.newton,
},
},
ST.PhysicalFreq: {
"default": "THZ",
"values": {
"HZ": spu.hertz,
"KHZ": spuex.kilohertz,
"MHZ": spuex.megahertz,
"GHZ": spuex.gigahertz,
"THZ": spuex.terahertz,
"PHZ": spuex.petahertz,
"EHZ": spuex.exahertz,
},
},
ST.PhysicalPol: {
"default": "RADIAN",
"values": {
"RADIAN": spu.radian,
"DEGREE": spu.degree,
"STERAD": spu.steradian,
"ANGMIL": spu.angular_mil,
},
},
ST.MaxwellMedium: {
"default": "NM",
"values": {
"PM": spu.picometer, ## c(vac) = wl*freq
"A": spu.angstrom,
"NM": spu.nanometer,
"UM": spu.micrometer,
"MM": spu.millimeter,
"CM": spu.centimeter,
"M": spu.meter,
},
},
ST.MaxwellMonitor: {
"default": "NM",
"values": {
"PM": spu.picometer, ## c(vac) = wl*freq
"A": spu.angstrom,
"NM": spu.nanometer,
"UM": spu.micrometer,
"MM": spu.millimeter,
"CM": spu.centimeter,
"M": spu.meter,
},
},
}

View File

@ -0,0 +1,9 @@
import enum
from ....utils.blender_type_enum import (
BlenderTypeEnum, append_cls_name_to_values
)
@append_cls_name_to_values
class TreeType(BlenderTypeEnum):
MaxwellSim = enum.auto()

View File

@ -0,0 +1,2 @@
from .managed_bl_image import ManagedBLImage
from .managed_bl_object import ManagedBLObject

View File

@ -0,0 +1,190 @@
import typing as typ
import typing_extensions as typx
import io
import numpy as np
import pydantic as pyd
import matplotlib.axis as mpl_ax
import bpy
from .. import contracts as ct
AREA_TYPE = "IMAGE_EDITOR"
SPACE_TYPE = "IMAGE_EDITOR"
class ManagedBLImage(ct.schemas.ManagedObj):
managed_obj_type = ct.ManagedObjType.ManagedBLImage
_bl_image_name: str
def __init__(self, name: str):
## TODO: Check that blender doesn't have any other images by the same name.
self._bl_image_name = name
@property
def name(self):
return self._bl_image_name
@name.setter
def name(self, value: str):
## TODO: Check that blender doesn't have any other images by the same name.
if (bl_image := bpy.data.images.get(self.name)):
bl_image.name = value
self._bl_image_name = value
def free(self):
if (bl_image := bpy.data.images.get(self.name)):
bpy.data.images.remove(bl_image)
####################
# - Managed Object Management
####################
def bl_image(
self,
width_px: int,
height_px: int,
color_model: typx.Literal["RGB", "RGBA"],
dtype: typx.Literal["uint8", "float32"],
):
"""Returns the managed blender image.
If the requested image properties are different from the image's, then delete the old image make a new image with correct geometry.
"""
channels = 4 if color_model == "RGBA" else 3
# Remove Image (if mismatch)
if (
(bl_image := bpy.data.images.get(self.name))
and (
bl_image.size[0] != width_px
or bl_image.size[1] != height_px
or bl_image.channels != channels
or bl_image.is_float ^ (dtype == "float32")
)
):
self.free()
# Create Image w/Geometry (if none exists)
if not (bl_image := bpy.data.images.get(self.name)):
bl_image = bpy.data.images.new(
self.name,
width=width_px,
height=height_px,
)
return bl_image
####################
# - Editor UX Manipulation
####################
@property
def preview_area(self) -> bpy.types.Area:
"""Returns the visible preview area in the Blender UI.
If none are valid, return None.
"""
valid_areas = [
area
for area in bpy.context.screen.areas
if area.type == AREA_TYPE
]
if valid_areas:
return valid_areas[0]
@property
def preview_space(self) -> bpy.types.SpaceProperties:
"""Returns the visible preview space in the visible preview area of
the Blender UI
"""
if (preview_area := self.preview_area):
return next(
space
for space in preview_area.spaces
if space.type == SPACE_TYPE
)
####################
# - Actions
####################
def bl_select(self) -> None:
"""Synchronizes the managed object to the preview, by manipulating
relevant editors.
"""
if (bl_image := bpy.data.images.get(self.name)):
self.preview_space.image = bl_image
####################
# - Special Methods
####################
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,
):
import matplotlib.pyplot as plt
# Compute Image Geometry
if (preview_area := self.preview_area):
# Retrieve DPI from Blender Preferences
_dpi = bpy.context.preferences.system.dpi
# Retrieve Image Geometry from Area
width_px = preview_area.width
height_px = preview_area.height
# Compute Inches
_width_inches = width_px / _dpi
_height_inches = height_px / _dpi
elif width_inches and height_inches and dpi:
# Copy Parameters
_dpi = dpi
_width_inches = height_inches
_height_inches = height_inches
# Compute Pixel Geometry
width_px = int(_width_inches * _dpi)
height_px = int(_height_inches * _dpi)
else:
msg = f"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
# Create MPL Figure, Axes, and Compute Figure Geometry
fig, ax = plt.subplots(
figsize=[_width_inches, _height_inches],
dpi=_dpi,
)
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.
# Plot w/User Parameter
func_plotter(ax)
# Save Figure to BytesIO
with io.BytesIO() as buff:
fig.savefig(buff, format='raw', dpi=dpi)
buff.seek(0)
image_data = np.frombuffer(
buff.getvalue(),
dtype=np.uint8,
).reshape([cmp_height_px, cmp_width_px, -1])
image_data = np.flipud(image_data).astype(np.float32) / 255
plt.close(fig)
# Optimized Write to Blender Image
bl_image = self.bl_image(cmp_width_px, cmp_height_px, "RGBA", "uint8")
bl_image.pixels.foreach_set(image_data.ravel())
bl_image.update()
if bl_select:
self.bl_select()

View File

@ -0,0 +1,202 @@
import typing as typ
import typing_extensions as typx
import functools
import contextlib
import io
import numpy as np
import pydantic as pyd
import matplotlib.axis as mpl_ax
import bpy
import bmesh
from .. import contracts as ct
class ManagedBLObject(ct.schemas.ManagedObj):
managed_obj_type = ct.ManagedObjType.ManagedBLObject
_bl_object_name: str
def __init__(self, name: str):
## TODO: Check that blender doesn't have any other objects by the same name.
self._bl_object_name = name
# Object Name
@property
def bl_object_name(self):
return self._bl_object_name
@bl_object_name.setter
def set_bl_object_name(self, value: str):
## TODO: Check that blender doesn't have any other objects by the same name.
if (bl_object := bpy.data.objects.get(self.bl_object_name)):
bl_object.name = value
self._bl_object_name = value
# Object Datablock Name
@property
def bl_mesh_name(self):
return self.bl_object_name + "Mesh"
@property
def bl_volume_name(self):
return self.bl_object_name + "Volume"
# Deallocation
def free(self):
if (bl_object := bpy.data.objects.get(self.bl_object_name)):
# Delete the Underlying Datablock
if bl_object.type == "MESH":
bpy.data.meshes.remove(bl_object.data)
elif bl_object.type == "VOLUME":
bpy.data.volumes.remove(bl_object.data)
else:
msg = f"Type of to-delete `bl_object`, {bl_object.type}, is not valid"
raise ValueError(msg)
####################
# - Actions
####################
def trigger_action(
self,
action: typx.Literal["report", "enable_previews"],
):
if action == "report":
pass ## TODO: Cache invalidation.
if action == "enable_previews":
pass ## Image "previews" don't need enabling.
def bl_select(self) -> None:
"""Selects the managed Blender object globally, causing it to be ex.
outlined in the 3D viewport.
"""
bpy.ops.object.select_all(action='DESELECT')
bpy.data.objects['Suzanne'].select_set(True)
####################
# - Managed Object Management
####################
def bl_object(
self,
kind: typx.Literal["MESH", "VOLUME"],
):
"""Returns the managed blender object.
If the requested object data type is different, then delete the old
object and recreate.
"""
# Remove Object (if mismatch)
if (
(bl_object := bpy.data.images.get(self.bl_object_name))
and bl_object.type != kind
):
self.free()
# Create Object w/Appropriate Data Block
if not (bl_object := bpy.data.images.get(self.bl_object_name)):
if bl_object.type == "MESH":
bl_data = bpy.data.meshes.new(self.bl_mesh_name)
elif bl_object.type == "VOLUME":
raise NotImplementedError
else:
msg = f"Requested `bl_object` type {bl_object.type} is not valid"
raise ValueError(msg)
bl_object = bpy.data.objects.new(self.bl_object_name, bl_data)
return bl_object
####################
# - Data Properties
####################
@property
def raw_mesh(self) -> bpy.types.Mesh:
"""Returns the object's raw mesh data.
Raises an error if the object has no mesh data.
"""
if (
(bl_object := bpy.data.objects.get(self.bl_object_name))
and bl_object.type == "MESH"
):
return bl_object.data
msg = f"Requested MESH data from `bl_object` of type {bl_object.type}"
raise ValueError(msg)
@contextlib.contextmanager
def as_bmesh(
self,
evaluate: bool = True,
triangulate: bool = False,
) -> bpy.types.Mesh:
if (
(bl_object := bpy.data.objects.get(self.bl_object_name))
and bl_object.type == "MESH"
):
bmesh_mesh = None
try:
bmesh_mesh = bmesh.new()
if evaluate:
bmesh_mesh.from_object(
bl_object,
bpy.context.evaluated_depsgraph_get(),
)
else:
bmesh_mesh.from_object(bl_object)
if triangulate:
bmesh.ops.triangulate(bmesh_mesh, faces=bmesh_mesh.faces)
yield bmesh_mesh
finally:
if bmesh_mesh: bmesh_mesh.free()
msg = f"Requested BMesh from `bl_object` of type {bl_object.type}"
raise ValueError(msg)
@functools.cached_property
def as_arrays(self) -> dict:
# Compute Evaluted + Triangulated Mesh
_mesh = bpy.data.meshes.new(name="TemporaryMesh")
with self.as_bmesh(evaluate=True, triangulate=True) as bmesh_mesh:
bmesh_mesh.to_mesh(_mesh)
# Optimized Vertex Copy
## See <https://blog.michelanders.nl/2016/02/copying-vertices-to-numpy-arrays-in_4.html>
verts = np.zeros(3 * len(_mesh.vertices), dtype=np.float64)
_mesh.vertices.foreach_get('co', verts)
verts.shape = (-1, 3)
# Optimized Triangle Copy
## To understand, read it, **carefully**.
faces = np.zeros(3 * len(_mesh.polygons), dtype=np.uint64)
_mesh.polygons.foreach_get('vertices', faces)
faces.shape = (-1, 3)
# Remove Temporary Mesh
bpy.data.meshes.remove(_mesh)
return {
"verts": verts,
"faces": faces,
}
#@property
#def volume(self) -> bpy.types.Volume:
# """Returns the object's volume data.
#
# Raises an error if the object has no volume data.
# """
# if (
# (bl_object := bpy.data.objects.get(self.bl_object_name))
# and bl_object.type == "VOLUME"
# ):
# return bl_object.data
#
# msg = f"Requested VOLUME data from `bl_object` of type {bl_object.type}"
# raise ValueError(msg)

View File

@ -1,92 +1,184 @@
import typing as typ
import bpy
from . import contracts
from . import contracts as ct
ICON_SIM_TREE = 'MOD_SIMPLEDEFORM'
####################
# - Cache Management
####################
MemAddr = int
class DeltaNodeLinkCache(typ.TypedDict):
added: set[MemAddr]
removed: set[MemAddr]
class BLENDER_MAXWELL_PT_MaxwellSimTreePanel(bpy.types.Panel):
bl_label = "Node Tree Custom Prop"
bl_idname = "NODE_PT_custom_prop"
bl_space_type = 'NODE_EDITOR'
bl_region_type = 'UI'
bl_category = 'Item'
@classmethod
def poll(cls, context):
return context.space_data.tree_type == contracts.TreeType.MaxwellSim.value
def draw(self, context):
layout = self.layout
node_tree = context.space_data.node_tree
layout.prop(node_tree, "preview_collection")
layout.prop(node_tree, "non_preview_collection")
class NodeLinkCache:
def __init__(self, node_tree: bpy.types.NodeTree):
# Initialize Parameters
self._node_tree = node_tree
self.link_ptrs_to_links = {}
self.link_ptrs = set()
self.link_ptrs_from_sockets = {}
self.link_ptrs_to_sockets = {}
# Fill Cache
self.regenerate()
def remove(self, link_ptrs: set[MemAddr]) -> None:
for link_ptr in link_ptrs:
self.link_ptrs.remove(link_ptr)
self.link_ptrs_to_links.pop(link_ptr, None)
def regenerate(self) -> DeltaNodeLinkCache:
current_link_ptrs_to_links = {
link.as_pointer(): link for link in self._node_tree.links
}
current_link_ptrs = set(current_link_ptrs_to_links.keys())
# Compute Delta
added_link_ptrs = current_link_ptrs - self.link_ptrs
removed_link_ptrs = self.link_ptrs - current_link_ptrs
# Update Caches Incrementally
self.remove(removed_link_ptrs)
self.link_ptrs |= added_link_ptrs
for link_ptr in added_link_ptrs:
link = current_link_ptrs_to_links[link_ptr]
self.link_ptrs_to_links[link_ptr] = link
self.link_ptrs_from_sockets[link_ptr] = link.from_socket
self.link_ptrs_to_sockets[link_ptr] = link.to_socket
return {"added": added_link_ptrs, "removed": removed_link_ptrs}
####################
# - Node Tree Definition
####################
class MaxwellSimTree(bpy.types.NodeTree):
bl_idname = contracts.TreeType.MaxwellSim
bl_idname = ct.TreeType.MaxwellSim.value
bl_label = "Maxwell Sim Editor"
bl_icon = contracts.Icon.MaxwellSimTree
bl_icon = ct.Icon.SimNodeEditor.value
managed_collection: bpy.props.PointerProperty(
name="Managed Collection",
description="Collection of Blender objects managed by this tree",
type=bpy.types.Collection,
)
preview_collection: bpy.props.PointerProperty(
name="Preview Collection",
description="Collection of Blender objects that will be previewed",
type=bpy.types.Collection,
update=(lambda self, context: self.trigger_updates())
)
non_preview_collection: bpy.props.PointerProperty(
name="Non-Preview Collection",
description="Collection of Blender objects that will NOT be previewed",
type=bpy.types.Collection,
update=(lambda self, context: self.trigger_updates())
)
def trigger_updates(self):
pass
####################
# - Lock Methods
####################
def unlock_all(self):
for node in self.nodes:
node.locked = False
for bl_socket in [*node.inputs, *node.outputs]:
bl_socket.locked = False
####################
# - Update Methods
####################
def sync_node_removed(self, node: bpy.types.Node):
"""Run by `Node.free()` when a node is being removed.
Removes node input links from the internal cache (so we don't attempt to update non-existant sockets).
"""
for bl_socket in node.inputs.values():
# Retrieve Socket Links (if any)
self._node_link_cache.remove({
link.as_pointer()
for link in bl_socket.links
})
## ONLY Input Socket Links are Removed from the NodeLink Cache
## - update() handles link-removal from still-existing node just fine.
## - update() does NOT handle link-removal of non-existant nodes.
def update(self):
"""Run by Blender when 'something changes' in the node tree.
Updates an internal node link cache, then updates sockets that just lost/gained an input link.
"""
if not hasattr(self, "_node_link_cache"):
self._node_link_cache = NodeLinkCache(self)
## We presume update() is run before the first link is altered.
## - Else, the first link of the session will not update caches.
## - We remain slightly unsure of the semantics.
## - More testing needed to prevent this 'first-link bug'.
return
# Compute Changes to NodeLink Cache
delta_links = self._node_link_cache.regenerate()
link_alterations = {
"to_remove": [],
"to_add": [],
}
for link_ptr in delta_links["removed"]:
from_socket = self._node_link_cache.link_ptrs_from_sockets[link_ptr]
to_socket = self._node_link_cache.link_ptrs_to_sockets[link_ptr]
# Update Socket Caches
self._node_link_cache.link_ptrs_from_sockets.pop(link_ptr, None)
self._node_link_cache.link_ptrs_to_sockets.pop(link_ptr, None)
# Trigger Report Chain on Socket that Just Lost a Link
## Aka. Forward-Refresh Caches Relying on Linkage
if not (
consent_removal := to_socket.sync_link_removed(from_socket)
):
# Did Not Consent to Removal: Queue Add Link
link_alterations["to_add"].append((from_socket, to_socket))
for link_ptr in delta_links["added"]:
link = self._node_link_cache.link_ptrs_to_links.get(link_ptr)
if link is None: continue
# Trigger Report Chain on Socket that Just Gained a Link
## Aka. Forward-Refresh Caches Relying on Linkage
if not (
consent_added := link.to_socket.sync_link_added(link)
):
# Did Not Consent to Addition: Queue Remove Link
link_alterations["to_remove"].append(link)
# Execute Queued Operations
## - Especially undoing undesirable link changes.
## - This is important for locked graphs, whose links must not change.
for link in link_alterations["to_remove"]:
self.links.remove(link)
for from_socket, to_socket in link_alterations["to_add"]:
self.links.new(from_socket, to_socket)
# If Queued Operations: Regenerate Cache
## - This prevents the next update() from picking up on alterations.
if link_alterations["to_remove"] or link_alterations["to_add"]:
self._node_link_cache.regenerate()
####################
# - Post-Load Handler
####################
def initialize_sim_tree_node_link_cache(scene: bpy.types.Scene):
"""Whenever a file is loaded, create/regenerate the NodeLinkCache in all trees.
"""
for node_tree in bpy.data.node_groups:
if node_tree.bl_idname == "MaxwellSimTree":
if not hasattr(node_tree, "_node_link_cache"):
node_tree._node_link_cache = NodeLinkCache(node_tree)
else:
node_tree._node_link_cache.regenerate()
####################
# - Blender Registration
####################
bpy.app.handlers.load_post.append(initialize_sim_tree_node_link_cache)
BL_REGISTER = [
MaxwellSimTree,
]
####################
# - Red Edges on Error
####################
## TODO: Refactor
#def link_callback_new(context):
# print("A THING HAPPENED")
# node_tree_type = contracts.TreeType.MaxwellSim.value
# link = context.link
#
# if not (
# link.from_node.node_tree.bl_idname == node_tree_type
# and link.to_node.node_tree.bl_idname == node_tree_type
# ):
# return
#
# source_node = link.from_node
#
# source_socket_name = source_node.g_output_socket_name(
# link.from_socket.name
# )
# link_data = source_node.compute_output(source_socket_name)
#
# destination_socket = link.to_socket
# link.is_valid = destination_socket.is_compatible(link_data)
#
# print(source_node, destination_socket, link.is_valid)
#
#bpy.msgbus.subscribe_rna(
# key=("active", "node_tree"),
# owner=MaxwellSimTree,
# args=(bpy.context,),
# notify=link_callback_new,
# options={'PERSISTENT'}
#)

View File

@ -5,10 +5,10 @@ from . import outputs
from . import sources
from . import mediums
from . import structures
from . import bounds
from . import monitors
#from . import bounds
#from . import monitors
from . import simulations
from . import utilities
#from . import utilities
BL_REGISTER = [
*kitchen_sink.BL_REGISTER,
@ -17,10 +17,10 @@ BL_REGISTER = [
*sources.BL_REGISTER,
*mediums.BL_REGISTER,
*structures.BL_REGISTER,
*bounds.BL_REGISTER,
*monitors.BL_REGISTER,
# *bounds.BL_REGISTER,
# *monitors.BL_REGISTER,
*simulations.BL_REGISTER,
*utilities.BL_REGISTER,
# *utilities.BL_REGISTER,
]
BL_NODES = {
**kitchen_sink.BL_NODES,
@ -29,8 +29,8 @@ BL_NODES = {
**sources.BL_NODES,
**mediums.BL_NODES,
**structures.BL_NODES,
**bounds.BL_NODES,
**monitors.BL_NODES,
# **bounds.BL_NODES,
# **monitors.BL_NODES,
**simulations.BL_NODES,
**utilities.BL_NODES,
# **utilities.BL_NODES,
}

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,23 @@
from . import unit_system
#from . import unit_system
from . import importers
from . import constants
from . import lists
from . import scene
#from . import lists
#from . import scene
BL_REGISTER = [
*unit_system.BL_REGISTER,
*scene.BL_REGISTER,
*importers.BL_REGISTER,
# *unit_system.BL_REGISTER,
#
# *scene.BL_REGISTER,
*constants.BL_REGISTER,
*lists.BL_REGISTER,
#*lists.BL_REGISTER,
]
BL_NODES = {
**unit_system.BL_NODES,
**scene.BL_NODES,
**importers.BL_NODES,
# **unit_system.BL_NODES,
#
# **scene.BL_NODES,
**constants.BL_NODES,
**lists.BL_NODES,
# **lists.BL_NODES,
}

View File

@ -1,23 +1,23 @@
from . import wave_constant
from . import scientific_constant
from . import number_constant
from . import physical_constant
from . import blender_constant
#from . import scientific_constant
#
#from . import number_constant
#from . import physical_constant
#from . import blender_constant
BL_REGISTER = [
*wave_constant.BL_REGISTER,
*scientific_constant.BL_REGISTER,
*number_constant.BL_REGISTER,
*physical_constant.BL_REGISTER,
*blender_constant.BL_REGISTER,
# *scientific_constant.BL_REGISTER,
#
# *number_constant.BL_REGISTER,
# *physical_constant.BL_REGISTER,
# *blender_constant.BL_REGISTER,
]
BL_NODES = {
**wave_constant.BL_NODES,
**scientific_constant.BL_NODES,
**number_constant.BL_NODES,
**physical_constant.BL_NODES,
**blender_constant.BL_NODES,
# **scientific_constant.BL_NODES,
#
# **number_constant.BL_NODES,
# **physical_constant.BL_NODES,
# **blender_constant.BL_NODES,
}

View File

@ -3,75 +3,64 @@ import sympy as sp
import sympy.physics.units as spu
import scipy as sc
from .... import contracts
from .... import contracts as ct
from .... import sockets
from ... import base
vac_speed_of_light = (
VAC_SPEED_OF_LIGHT = (
sc.constants.speed_of_light
* spu.meter/spu.second
)
class WaveConstantNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.WaveConstant
class WaveConstantNode(base.MaxwellSimNode):
node_type = ct.NodeType.WaveConstant
bl_label = "Wave Constant"
input_sockets = {}
input_socket_sets = {
"vac_wl": {
"vac_wl": sockets.PhysicalVacWLSocketDef(
label="Vac WL",
),
"Vacuum WL": {
"WL": sockets.PhysicalLengthSocketDef(),
},
"freq": {
"freq": sockets.PhysicalFreqSocketDef(
label="Freq",
),
"Frequency": {
"Freq": sockets.PhysicalFreqSocketDef(),
},
}
output_sockets = {
"vac_wl": sockets.PhysicalVacWLSocketDef(
label="Vac WL",
),
"freq": sockets.PhysicalVacWLSocketDef(
label="Freq",
),
"WL": sockets.PhysicalLengthSocketDef(),
"Freq": sockets.PhysicalFreqSocketDef(),
}
output_socket_sets = {}
####################
# - Callbacks
####################
@base.computes_output_socket("vac_wl")
def compute_vac_wl(self: contracts.NodeTypeProtocol) -> sp.Expr:
if self.socket_set == "vac_wl":
return self.compute_input("vac_wl")
elif self.socket_set == "freq":
freq = self.compute_input("freq")
@base.computes_output_socket(
"WL",
kind=ct.DataFlowKind.Value,
input_sockets={"WL", "Freq"},
)
def compute_vac_wl(self, input_socket_values: dict) -> sp.Expr:
if (vac_wl := input_socket_values["WL"]):
return vac_wl
elif (freq := input_socket_values["Freq"]):
return spu.convert_to(
vac_speed_of_light / freq,
VAC_SPEED_OF_LIGHT / freq,
spu.meter,
)
raise ValueError("No valid socket set.")
raise RuntimeError("Vac WL and Freq are both non-truthy")
@base.computes_output_socket("freq")
def compute_freq(self: contracts.NodeTypeProtocol) -> sp.Expr:
if self.socket_set == "vac_wl":
vac_wl = self.compute_input("vac_wl")
@base.computes_output_socket(
"Freq",
input_sockets={"WL", "Freq"},
)
def compute_freq(self, input_sockets: dict) -> sp.Expr:
if (vac_wl := input_sockets["WL"]):
return spu.convert_to(
vac_speed_of_light / vac_wl,
VAC_SPEED_OF_LIGHT / vac_wl,
spu.hertz,
)
elif self.socket_set == "freq":
return self.compute_input("freq")
raise ValueError("No valid socket set.")
elif (freq := input_sockets["Freq"]):
return freq
####################
# - Blender Registration
@ -80,7 +69,7 @@ BL_REGISTER = [
WaveConstantNode,
]
BL_NODES = {
contracts.NodeType.WaveConstant: (
contracts.NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS
ct.NodeType.WaveConstant: (
ct.NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS
)
}

View File

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

View File

@ -0,0 +1,105 @@
import functools
import tempfile
from pathlib import Path
import typing as typ
from pathlib import Path
import bpy
import sympy as sp
import pydantic as pyd
import tidy3d as td
import tidy3d.web as _td_web
from ......utils.auth_td_web import g_td_web, is_td_web_authed
from .... import contracts as ct
from .... import sockets
from ... import base
@functools.cache
def task_status(task_id: str):
task = _td_web.api.webapi.get_info(task_id)
return task.status
####################
# - Node
####################
class Tidy3DWebImporterNode(base.MaxwellSimNode):
node_type = ct.NodeType.Tidy3DWebImporter
bl_label = "Tidy3DWebImporter"
input_sockets = {
"Cloud Task": sockets.Tidy3DCloudTaskSocketDef(
task_exists=True,
),
}
output_sockets = {}
####################
# - UI
####################
def draw_info(self, context, layout): pass
####################
# - Output Methods
####################
@base.computes_output_socket(
"FDTD Sim",
input_sockets={"Cloud Task"},
)
def compute_cloud_task(self, input_sockets: dict) -> str:
if not isinstance(task_id := input_sockets["Cloud Task"], str):
msg ="Input task does not exist"
raise ValueError(msg)
# Load the Simulation
td_web = g_td_web(None) ## Presume already auth'ed
with tempfile.NamedTemporaryFile(delete=False) as f:
_path_tmp = Path(f.name)
_path_tmp.rename(f.name + ".json")
path_tmp = Path(f.name + ".json")
cloud_sim = _td_web.api.webapi.load_simulation(
task_id,
path=str(path_tmp),
)
Path(path_tmp).unlink()
return cloud_sim
####################
# - Update
####################
@base.on_value_changed(
socket_name="Cloud Task",
input_sockets={"Cloud Task"}
)
def on_value_changed__cloud_task(self, input_sockets: dict):
task_status.cache_clear()
if (
(task_id := input_sockets["Cloud Task"]) is None
or isinstance(task_id, dict)
or task_status(task_id) != "success"
or not is_td_web_authed
):
if self.loose_output_sockets: self.loose_output_sockets = {}
return
td_web = g_td_web(None) ## Presume already auth'ed
self.loose_output_sockets = {
"FDTD Sim": sockets.MaxwellFDTDSimSocketDef(),
"FDTD Sim Data": sockets.AnySocketDef(),
}
####################
# - Blender Registration
####################
BL_REGISTER = [
Tidy3DWebImporterNode,
]
BL_NODES = {
ct.NodeType.Tidy3DWebImporter: (
ct.NodeCategory.MAXWELLSIM_INPUTS_IMPORTERS
)
}

View File

@ -1,13 +1,15 @@
from pathlib import Path
import tidy3d as td
import sympy as sp
import sympy.physics.units as spu
from .. import contracts
from .. import contracts as ct
from .. import sockets
from . import base
class KitchenSinkNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.KitchenSink
class KitchenSinkNode(base.MaxwellSimNode):
node_type = ct.NodeType.KitchenSink
bl_label = "Kitchen Sink"
#bl_icon = ...
@ -15,75 +17,75 @@ class KitchenSinkNode(base.MaxwellSimTreeNode):
####################
# - Sockets
####################
input_sockets = {}
input_sockets = {
"Static Data": sockets.AnySocketDef(),
}
input_socket_sets = {
"basic": {
"basic_any": sockets.AnySocketDef(label="Any"),
"basic_bool": sockets.BoolSocketDef(label="Bool"),
"basic_filepath": sockets.FilePathSocketDef(label="FilePath"),
"basic_text": sockets.TextSocketDef(label="Text"),
"Basic": {
"Any": sockets.AnySocketDef(),
"Bool": sockets.BoolSocketDef(),
"FilePath": sockets.FilePathSocketDef(),
"Text": sockets.TextSocketDef(),
},
"number": {
"number_integer": sockets.IntegerNumberSocketDef(label="IntegerNumber"),
"number_rational": sockets.RationalNumberSocketDef(label="RationalNumber"),
"number_real": sockets.RealNumberSocketDef(label="RealNumber"),
"number_complex": sockets.ComplexNumberSocketDef(label="ComplexNumber"),
"Number": {
"Integer": sockets.IntegerNumberSocketDef(),
"Rational": sockets.RationalNumberSocketDef(),
"Real": sockets.RealNumberSocketDef(),
"Complex": sockets.ComplexNumberSocketDef(),
},
"vector": {
"vector_real2dvector": sockets.Real2DVectorSocketDef(label="Real2DVector"),
"vector_complex2dvector": sockets.Complex2DVectorSocketDef(label="Complex2DVector"),
"vector_real3dvector": sockets.Real3DVectorSocketDef(label="Real3DVector"),
"vector_complex3dvector": sockets.Complex3DVectorSocketDef(label="Complex3DVector"),
"Vector": {
"Real 2D": sockets.Real2DVectorSocketDef(),
"Real 3D": sockets.Real3DVectorSocketDef(
default_value=sp.Matrix([0.0, 0.0, 0.0])
),
"Complex 2D": sockets.Complex2DVectorSocketDef(),
"Complex 3D": sockets.Complex3DVectorSocketDef(),
},
"physical": {
"physical_time": sockets.PhysicalTimeSocketDef(label="PhysicalTime"),
#"physical_point_2d": sockets.PhysicalPoint2DSocketDef(label="PhysicalPoint2D"),
"physical_angle": sockets.PhysicalAngleSocketDef(label="PhysicalAngle"),
"physical_length": sockets.PhysicalLengthSocketDef(label="PhysicalLength"),
"physical_area": sockets.PhysicalAreaSocketDef(label="PhysicalArea"),
"physical_volume": sockets.PhysicalVolumeSocketDef(label="PhysicalVolume"),
"physical_point_3d": sockets.PhysicalPoint3DSocketDef(label="PhysicalPoint3D"),
#"physical_size_2d": sockets.PhysicalSize2DSocketDef(label="PhysicalSize2D"),
"physical_size_3d": sockets.PhysicalSize3DSocketDef(label="PhysicalSize3D"),
"physical_mass": sockets.PhysicalMassSocketDef(label="PhysicalMass"),
"physical_speed": sockets.PhysicalSpeedSocketDef(label="PhysicalSpeed"),
"physical_accel_scalar": sockets.PhysicalAccelScalarSocketDef(label="PhysicalAccelScalar"),
"physical_force_scalar": sockets.PhysicalForceScalarSocketDef(label="PhysicalForceScalar"),
#"physical_accel_3dvector": sockets.PhysicalAccel3DVectorSocketDef(label="PhysicalAccel3DVector"),
#"physical_force_3dvector": sockets.PhysicalForce3DVectorSocketDef(label="PhysicalForce3DVector"),
"physical_pol": sockets.PhysicalPolSocketDef(label="PhysicalPol"),
"physical_freq": sockets.PhysicalFreqSocketDef(label="PhysicalFreq"),
"physical_spec_power_dist": sockets.PhysicalSpecPowerDistSocketDef(label="PhysicalSpecPowerDist"),
"physical_spec_rel_perm_dist": sockets.PhysicalSpecRelPermDistSocketDef(label="PhysicalSpecRelPermDist"),
"Physical": {
"Time": sockets.PhysicalTimeSocketDef(),
#"physical_point_2d": sockets.PhysicalPoint2DSocketDef(),
"Angle": sockets.PhysicalAngleSocketDef(),
"Length": sockets.PhysicalLengthSocketDef(),
"Area": sockets.PhysicalAreaSocketDef(),
"Volume": sockets.PhysicalVolumeSocketDef(),
"Point 3D": sockets.PhysicalPoint3DSocketDef(),
##"physical_size_2d": sockets.PhysicalSize2DSocketDef(),
"Size 3D": sockets.PhysicalSize3DSocketDef(),
"Mass": sockets.PhysicalMassSocketDef(),
"Speed": sockets.PhysicalSpeedSocketDef(),
"Accel Scalar": sockets.PhysicalAccelScalarSocketDef(),
"Force Scalar": sockets.PhysicalForceScalarSocketDef(),
#"physical_accel_3dvector": sockets.PhysicalAccel3DVectorSocketDef(),
##"physical_force_3dvector": sockets.PhysicalForce3DVectorSocketDef(),
"Pol": sockets.PhysicalPolSocketDef(),
"Freq": sockets.PhysicalFreqSocketDef(),
},
"blender": {
"blender_object": sockets.BlenderObjectSocketDef(label="BlenderObject"),
"blender_collection": sockets.BlenderCollectionSocketDef(label="BlenderCollection"),
"blender_image": sockets.BlenderImageSocketDef(label="BlenderImage"),
"blender_volume": sockets.BlenderVolumeSocketDef(label="BlenderVolume"),
"blender_geonodes": sockets.BlenderGeoNodesSocketDef(label="BlenderGeoNodes"),
"blender_text": sockets.BlenderTextSocketDef(label="BlenderText"),
"Blender": {
"Object": sockets.BlenderObjectSocketDef(),
"Collection": sockets.BlenderCollectionSocketDef(),
"Image": sockets.BlenderImageSocketDef(),
"GeoNodes": sockets.BlenderGeoNodesSocketDef(),
"Text": sockets.BlenderTextSocketDef(),
},
"maxwell": {
"maxwell_source": sockets.MaxwellSourceSocketDef(label="MaxwellSource"),
"maxwell_temporal_shape": sockets.MaxwellTemporalShapeSocketDef(label="MaxwellTemporalShape"),
"maxwell_medium": sockets.MaxwellMediumSocketDef(label="MaxwellMedium"),
#"maxwell_medium_nonlinearity": sockets.MaxwellMediumNonLinearitySocketDef(label="MaxwellMediumNonLinearity"),
"maxwell_structure": sockets.MaxwellStructureSocketDef(label="MaxwellMedium"),
"maxwell_bound_box": sockets.MaxwellBoundBoxSocketDef(label="MaxwellBoundBox"),
"maxwell_bound_face": sockets.MaxwellBoundFaceSocketDef(label="MaxwellBoundFace"),
"maxwell_monitor": sockets.MaxwellMonitorSocketDef(label="MaxwellMonitor"),
"maxwell_fdtd_sim": sockets.MaxwellFDTDSimSocketDef(label="MaxwellFDTDSim"),
"maxwell_sim_grid": sockets.MaxwellSimGridSocketDef(label="MaxwellSimGrid"),
"maxwell_sim_grid_axis": sockets.MaxwellSimGridAxisSocketDef(label="MaxwellSimGridAxis"),
"Maxwell": {
"Source": sockets.MaxwellSourceSocketDef(),
"Temporal Shape": sockets.MaxwellTemporalShapeSocketDef(),
"Medium": sockets.MaxwellMediumSocketDef(),
"Medium Non-Linearity": sockets.MaxwellMediumNonLinearitySocketDef(),
"Structure": sockets.MaxwellStructureSocketDef(),
"Bound Box": sockets.MaxwellBoundBoxSocketDef(),
"Bound Face": sockets.MaxwellBoundFaceSocketDef(),
"Monitor": sockets.MaxwellMonitorSocketDef(),
"FDTD Sim": sockets.MaxwellFDTDSimSocketDef(),
"Sim Grid": sockets.MaxwellSimGridSocketDef(),
"Sim Grid Axis": sockets.MaxwellSimGridAxisSocketDef(),
},
}
output_sockets = {}
output_socket_sets = {
k + " Output": v
for k, v in input_socket_sets.items()
output_sockets = {
"Static Data": sockets.AnySocketDef(),
}
output_socket_sets = input_socket_sets
@ -94,7 +96,7 @@ BL_REGISTER = [
KitchenSinkNode,
]
BL_NODES = {
contracts.NodeType.KitchenSink: (
contracts.NodeCategory.MAXWELLSIM_INPUTS
ct.NodeType.KitchenSink: (
ct.NodeCategory.MAXWELLSIM_INPUTS
)
}

View File

@ -1,47 +1,47 @@
from . import library_medium
from . import pec_medium
from . import isotropic_medium
from . import anisotropic_medium
from . import triple_sellmeier_medium
from . import sellmeier_medium
from . import pole_residue_medium
from . import drude_medium
from . import drude_lorentz_medium
from . import debye_medium
from . import non_linearities
#from . import pec_medium
#from . import isotropic_medium
#from . import anisotropic_medium
#
#from . import triple_sellmeier_medium
#from . import sellmeier_medium
#from . import pole_residue_medium
#from . import drude_medium
#from . import drude_lorentz_medium
#from . import debye_medium
#
#from . import non_linearities
BL_REGISTER = [
*library_medium.BL_REGISTER,
*pec_medium.BL_REGISTER,
*isotropic_medium.BL_REGISTER,
*anisotropic_medium.BL_REGISTER,
*triple_sellmeier_medium.BL_REGISTER,
*sellmeier_medium.BL_REGISTER,
*pole_residue_medium.BL_REGISTER,
*drude_medium.BL_REGISTER,
*drude_lorentz_medium.BL_REGISTER,
*debye_medium.BL_REGISTER,
*non_linearities.BL_REGISTER,
# *pec_medium.BL_REGISTER,
# *isotropic_medium.BL_REGISTER,
# *anisotropic_medium.BL_REGISTER,
#
# *triple_sellmeier_medium.BL_REGISTER,
# *sellmeier_medium.BL_REGISTER,
# *pole_residue_medium.BL_REGISTER,
# *drude_medium.BL_REGISTER,
# *drude_lorentz_medium.BL_REGISTER,
# *debye_medium.BL_REGISTER,
#
# *non_linearities.BL_REGISTER,
]
BL_NODES = {
**library_medium.BL_NODES,
**pec_medium.BL_NODES,
**isotropic_medium.BL_NODES,
**anisotropic_medium.BL_NODES,
**triple_sellmeier_medium.BL_NODES,
**sellmeier_medium.BL_NODES,
**pole_residue_medium.BL_NODES,
**drude_medium.BL_NODES,
**drude_lorentz_medium.BL_NODES,
**debye_medium.BL_NODES,
**non_linearities.BL_NODES,
# **pec_medium.BL_NODES,
# **isotropic_medium.BL_NODES,
# **anisotropic_medium.BL_NODES,
#
# **triple_sellmeier_medium.BL_NODES,
# **sellmeier_medium.BL_NODES,
# **pole_residue_medium.BL_NODES,
# **drude_medium.BL_NODES,
# **drude_lorentz_medium.BL_NODES,
# **debye_medium.BL_NODES,
#
# **non_linearities.BL_NODES,
}

View File

@ -1,3 +1,6 @@
import typing as typ
import functools
import bpy
import tidy3d as td
import sympy as sp
@ -6,37 +9,33 @@ import numpy as np
import scipy as sc
from .....utils import extra_sympy_units as spuex
from ... import contracts
from ... import contracts as ct
from ... import sockets
from ... import managed_objs
from .. import base
class ExperimentOperator00(bpy.types.Operator):
bl_idname = "blender_maxwell.experiment_operator_00"
bl_label = "exp"
VAC_SPEED_OF_LIGHT = (
sc.constants.speed_of_light
* spu.meter/spu.second
)
@classmethod
def poll(cls, context):
return True
def execute(self, context):
node = context.node
node.invoke_matplotlib_and_update_image()
return {'FINISHED'}
class LibraryMediumNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.LibraryMedium
class LibraryMediumNode(base.MaxwellSimNode):
node_type = ct.NodeType.LibraryMedium
bl_label = "Library Medium"
#bl_icon = ...
####################
# - Sockets
####################
input_sockets = {}
output_sockets = {
"medium": sockets.MaxwellMediumSocketDef(
label="Medium"
),
"Medium": sockets.MaxwellMediumSocketDef(),
}
managed_obj_defs = {
"nk_plot": ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLImage(name),
name_prefix="nkplot_",
)
}
####################
@ -61,19 +60,12 @@ class LibraryMediumNode(base.MaxwellSimTreeNode):
if mat_key != "graphene" ## For some reason, it's unique...
],
default="Au",
update=(lambda self,context: self.update()),
update=(lambda self, context: self.sync_prop("material", context)),
)
####################
# - UI
####################
def draw_props(self, context, layout):
layout.prop(self, "material", text="")
def draw_info(self, context, layout):
layout.operator(ExperimentOperator00.bl_idname, text="Experiment")
vac_speed_of_light = sc.constants.speed_of_light * spu.meter/spu.second
@property
def freq_range_str(self) -> tuple[sp.Expr, sp.Expr]:
## TODO: Cache (node instances don't seem able to keep data outside of properties, not even cached_property)
mat = td.material_library[self.material]
freq_range = [
spu.convert_to(
@ -82,80 +74,89 @@ class LibraryMediumNode(base.MaxwellSimTreeNode):
) / spuex.terahertz
for val in mat.medium.frequency_range
]
return sp.pretty(
[freq_range[0].n(4), freq_range[1].n(4)],
use_unicode=True
)
@property
def nm_range_str(self) -> str:
## TODO: Cache (node instances don't seem able to keep data outside of properties, not even cached_property)
mat = td.material_library[self.material]
nm_range = [
spu.convert_to(
vac_speed_of_light / (val * spu.hertz),
VAC_SPEED_OF_LIGHT / (val * spu.hertz),
spu.nanometer,
) / spu.nanometer
for val in mat.medium.frequency_range
for val in reversed(mat.medium.frequency_range)
]
layout.label(text=f"nm: [{nm_range[1].n(2)}, {nm_range[0].n(2)}]")
layout.label(text=f"THz: [{freq_range[0].n(2)}, {freq_range[1].n(2)}]")
return sp.pretty(
[nm_range[0].n(4), nm_range[1].n(4)],
use_unicode=True
)
####################
# - Output Socket Computation
# - UI
####################
@base.computes_output_socket("medium")
def compute_medium(self: contracts.NodeTypeProtocol) -> td.AbstractMedium:
def draw_props(self, context, layout):
layout.prop(self, "material", text="")
def draw_info(self, context, col):
# UI Drawing
split = col.split(factor=0.23, align=True)
_col = split.column(align=True)
_col.alignment = "LEFT"
_col.label(text="nm")
_col.label(text="THz")
_col = split.column(align=True)
_col.alignment = "RIGHT"
_col.label(text=self.nm_range_str)
_col.label(text=self.freq_range_str)
####################
# - Output Sockets
####################
@base.computes_output_socket("Medium")
def compute_vac_wl(self) -> sp.Expr:
return td.material_library[self.material].medium
####################
# - Experiment
# - Event Callbacks
####################
def invoke_matplotlib_and_update_image(self):
import matplotlib.pyplot as plt
mat = td.material_library[self.material]
@base.on_show_plot(
managed_objs={"nk_plot"},
props={"material"},
stop_propagation=True, ## Plot only the first plottable node
)
def on_show_plot(
self,
managed_objs: dict[str, ct.schemas.ManagedObj],
props: dict[str, typ.Any],
):
medium = td.material_library[props["material"]].medium
freq_range = [
spu.convert_to(
val * spu.hertz,
spuex.terahertz,
) / spu.hertz
for val in medium.frequency_range
]
aspect_ratio = 1.0
for area in bpy.context.screen.areas:
if area.type == 'IMAGE_EDITOR':
width = area.width
height = area.height
aspect_ratio = width / height
# Generate a plot with matplotlib
fig_width = 6
fig_height = fig_width / aspect_ratio
fig, ax = plt.subplots(figsize=(fig_width, fig_height))
ax.set_aspect(aspect_ratio)
mat.medium.plot(
np.linspace(*mat.medium.frequency_range[:2], 50),
ax=ax,
managed_objs["nk_plot"].mpl_plot_to_image(
lambda ax: medium.plot(medium.frequency_range, ax=ax),
bl_select=True,
)
# Save the plot to a temporary file
temp_plot_file = bpy.path.abspath('//temp_plot.png')
fig.savefig(temp_plot_file, bbox_inches='tight')
plt.close(fig) # Close the figure to free up memory
# Load or reload the image in Blender
if "matplotlib_plot" in bpy.data.images:
image = bpy.data.images["matplotlib_plot"]
image.reload()
else:
image = bpy.data.images.load(temp_plot_file)
image.name = "matplotlib_plot"
# Write the plot to an image datablock in Blender
for area in bpy.context.screen.areas:
if area.type == 'IMAGE_EDITOR':
for space in area.spaces:
if space.type == 'IMAGE_EDITOR':
space.image = image
return True
####################
# - Blender Registration
####################
BL_REGISTER = [
ExperimentOperator00,
LibraryMediumNode,
]
BL_NODES = {
contracts.NodeType.LibraryMedium: (
contracts.NodeCategory.MAXWELLSIM_MEDIUMS
ct.NodeType.LibraryMedium: (
ct.NodeCategory.MAXWELLSIM_MEDIUMS
)
}

View File

@ -1,10 +1,11 @@
from . import viewers
from . import viewer
from . import exporters
from . import plotters
BL_REGISTER = [
*viewer.BL_REGISTER,
*exporters.BL_REGISTER,
]
BL_NODES = {
**viewer.BL_NODES,
**exporters.BL_NODES,
}

View File

@ -1,8 +1,11 @@
from . import json_file_exporter
from . import 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

@ -7,39 +7,13 @@ import sympy as sp
import pydantic as pyd
import tidy3d as td
from .... import contracts
from .... import contracts as ct
from .... import sockets
from ... import base
####################
# - Operators
####################
class JSONFileExporterPrintJSON(bpy.types.Operator):
bl_idname = "blender_maxwell.json_file_exporter_print_json"
bl_label = "Print the JSON of what's linked into a JSONFileExporterNode."
@classmethod
def poll(cls, context):
return True
def execute(self, context):
node = context.node
print(node.linked_data_as_json())
return {'FINISHED'}
class JSONFileExporterMeshData(bpy.types.Operator):
bl_idname = "blender_maxwell.json_file_exporter_mesh_data"
bl_label = "Print any mesh data linked into a JSONFileExporterNode."
@classmethod
def poll(cls, context):
return True
def execute(self, context):
node = context.node
print(node.linked_mesh_data())
return {'FINISHED'}
class JSONFileExporterSaveJSON(bpy.types.Operator):
bl_idname = "blender_maxwell.json_file_exporter_save_json"
bl_label = "Save the JSON of what's linked into a JSONFileExporterNode."
@ -56,22 +30,24 @@ class JSONFileExporterSaveJSON(bpy.types.Operator):
####################
# - Node
####################
class JSONFileExporterNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.JSONFileExporter
class JSONFileExporterNode(base.MaxwellSimNode):
node_type = ct.NodeType.JSONFileExporter
bl_label = "JSON File Exporter"
#bl_icon = constants.ICON_SIM_INPUT
input_sockets = {
"json_path": sockets.FilePathSocketDef(
label="JSON Path",
default_path="simulation.json"
"Data": sockets.AnySocketDef(),
"JSON Path": sockets.FilePathSocketDef(
default_path=Path("simulation.json")
),
"data": sockets.AnySocketDef(
label="Data",
"JSON Indent": sockets.IntegerNumberSocketDef(
default_value=4,
),
}
output_sockets = {}
output_sockets = {
"JSON String": sockets.TextSocketDef(),
}
####################
# - UI Layout
@ -81,53 +57,50 @@ class JSONFileExporterNode(base.MaxwellSimTreeNode):
context: bpy.types.Context,
layout: bpy.types.UILayout,
) -> None:
layout.operator(JSONFileExporterPrintJSON.bl_idname, text="Print")
layout.operator(JSONFileExporterSaveJSON.bl_idname, text="Save")
layout.operator(JSONFileExporterMeshData.bl_idname, text="Mesh Info")
layout.operator(JSONFileExporterSaveJSON.bl_idname, text="Save JSON")
####################
# - Methods
####################
def linked_data_as_json(self) -> str | None:
if self.g_input_bl_socket("data").is_linked:
data: typ.Any = self.compute_input("data")
# Tidy3D Objects: Call .json()
if hasattr(data, "json"):
return data.json()
# Pydantic Models: Call .model_dump_json()
elif isinstance(data, pyd.BaseModel):
return data.model_dump_json()
else:
json.dumps(data)
def linked_mesh_data(self) -> str | None:
if self.g_input_bl_socket("data").is_linked:
data: typ.Any = self.compute_input("data")
if isinstance(data, td.Structure):
return data.geometry
def export_data_as_json(self) -> None:
if (data := self.linked_data_as_json()):
data_dict = json.loads(data)
with self.compute_input("json_path").open("w") as f:
json.dump(data_dict, f, ensure_ascii=False, indent=4)
if (json_str := self.compute_output("JSON String")):
data_dict = json.loads(json_str)
with self._compute_input("JSON Path").open("w") as f:
indent = self._compute_input("JSON Indent")
json.dump(data_dict, f, ensure_ascii=False, indent=indent)
####################
# - Output Sockets
####################
@base.computes_output_socket(
"JSON String",
input_sockets={"Data"},
)
def compute_json_string(self, input_sockets: dict[str, typ.Any]) -> str | None:
if not (data := input_sockets["Data"]):
return None
# Tidy3D Objects: Call .json()
if hasattr(data, "json"):
return data.json()
# Pydantic Models: Call .model_dump_json()
elif isinstance(data, pyd.BaseModel):
return data.model_dump_json()
else:
json.dumps(data)
####################
# - Blender Registration
####################
BL_REGISTER = [
JSONFileExporterPrintJSON,
JSONFileExporterMeshData,
JSONFileExporterSaveJSON,
JSONFileExporterNode,
]
BL_NODES = {
contracts.NodeType.JSONFileExporter: (
contracts.NodeCategory.MAXWELLSIM_OUTPUTS_EXPORTERS
ct.NodeType.JSONFileExporter: (
ct.NodeCategory.MAXWELLSIM_OUTPUTS_EXPORTERS
)
}

View File

@ -0,0 +1,393 @@
import json
import tempfile
import functools
import typing as typ
import json
from pathlib import Path
import bpy
import sympy as sp
import pydantic as pyd
import tidy3d as td
import tidy3d.web as _td_web
from ......utils.auth_td_web import g_td_web, is_td_web_authed
from .... import contracts as ct
from .... import sockets
from ... import base
####################
# - Task Getters
####################
## TODO: We should probably refactor this setup.
@functools.cache
def estimated_task_cost(task_id: str):
return _td_web.api.webapi.estimate_cost(task_id)
@functools.cache
def billed_task_cost(task_id: str):
return _td_web.api.webapi.real_cost(task_id)
@functools.cache
def task_status(task_id: str):
task = _td_web.api.webapi.get_info(task_id)
return task.status
####################
# - Progress Timer
####################
## TODO: We should probably refactor this too.
class Tidy3DTaskStatusModalOperator(bpy.types.Operator):
bl_idname = "blender_maxwell.tidy_3d_task_status_modal_operator"
bl_label = "Tidy3D Task Status Modal Operator"
_timer = None
_task_id = None
_node = None
_status = None
_reported_done = False
def modal(self, context, event):
# Retrieve New Status
task_status.cache_clear()
new_status = task_status(self._task_id)
if new_status != self._status:
task_status.cache_clear()
self._status = new_status
# Check Done Status
if self._status in {"success", "error"}:
# Report Done
if not self._reported_done:
self._node.trigger_action("value_changed")
self._reported_done = True
# Finish when Billing is Known
if not billed_task_cost(self._task_id):
billed_task_cost.cache_clear()
else:
return {'FINISHED'}
return {'PASS_THROUGH'}
def execute(self, context):
node = context.node
wm = context.window_manager
self._timer = wm.event_timer_add(0.25, window=context.window)
self._task_id = node.uploaded_task_id
self._node = node
self._status = task_status(self._task_id)
wm.modal_handler_add(self)
return {'RUNNING_MODAL'}
####################
# - Web Uploader / Loader / Runner / Releaser
####################
## TODO: We should probably refactor this too.
class Tidy3DWebUploadOperator(bpy.types.Operator):
bl_idname = "blender_maxwell.tidy_3d_web_upload_operator"
bl_label = "Tidy3D Web Upload Operator"
bl_description = "Upload the attached (locked) simulation, such that it is ready to run on the Tidy3D cloud"
@classmethod
def poll(cls, context):
space = context.space_data
return (
space.type == 'NODE_EDITOR'
and space.node_tree is not None
and space.node_tree.bl_idname == "MaxwellSimTreeType"
and is_td_web_authed()
and hasattr(context, "node")
and context.node.lock_tree
)
def execute(self, context):
node = context.node
node.web_upload()
return {'FINISHED'}
class Tidy3DLoadUploadedOperator(bpy.types.Operator):
bl_idname = "blender_maxwell.tidy_3d_load_uploaded_operator"
bl_label = "Tidy3D Load Uploaded Operator"
bl_description = "Load an already-uploaded simulation, as selected in the dropdown of the 'Cloud Task' socket"
@classmethod
def poll(cls, context):
space = context.space_data
return (
space.type == 'NODE_EDITOR'
and space.node_tree is not None
and space.node_tree.bl_idname == "MaxwellSimTreeType"
and is_td_web_authed()
and hasattr(context, "node")
and context.node.lock_tree
)
def execute(self, context):
node = context.node
node.load_uploaded_task()
# Load Simulation to Compare
## Load Local Sim
local_sim = node._compute_input("FDTD Sim")
## Load Cloud Sim
task_id = node.compute_output("Cloud Task")
with tempfile.NamedTemporaryFile(delete=False) as f:
_path_tmp = Path(f.name)
_path_tmp.rename(f.name + ".json")
path_tmp = Path(f.name + ".json")
cloud_sim = _td_web.api.webapi.load_simulation(task_id, path=str(path_tmp))
Path(path_tmp).unlink()
## Compare
if local_sim != cloud_sim:
node.release_uploaded_task()
msg = "Loaded simulation doesn't match input simulation"
raise ValueError(msg)
return {'FINISHED'}
class RunUploadedTidy3DSim(bpy.types.Operator):
bl_idname = "blender_maxwell.run_uploaded_tidy_3d_sim"
bl_label = "Run Uploaded Tidy3D Sim"
bl_description = "Run the currently uploaded (and loaded) simulation"
@classmethod
def poll(cls, context):
space = context.space_data
return (
space.type == 'NODE_EDITOR'
and space.node_tree is not None
and space.node_tree.bl_idname == "MaxwellSimTreeType"
and is_td_web_authed()
and hasattr(context, "node")
and context.node.lock_tree
and context.node.uploaded_task_id
and task_status(context.node.uploaded_task_id) == "draft"
)
def execute(self, context):
node = context.node
node.run_uploaded_task()
bpy.ops.blender_maxwell.tidy_3d_task_status_modal_operator()
return {'FINISHED'}
class ReleaseTidy3DExportOperator(bpy.types.Operator):
bl_idname = "blender_maxwell.release_tidy_3d_export_operator"
bl_label = "Release Tidy3D Export Operator"
@classmethod
def poll(cls, context):
space = context.space_data
return (
space.type == 'NODE_EDITOR'
and space.node_tree is not None
and space.node_tree.bl_idname == "MaxwellSimTreeType"
and is_td_web_authed()
and hasattr(context, "node")
and context.node.lock_tree
and context.node.uploaded_task_id
)
def execute(self, context):
node = context.node
node.release_uploaded_task()
return {'FINISHED'}
####################
# - Web Exporter Node
####################
class Tidy3DWebExporterNode(base.MaxwellSimNode):
node_type = ct.NodeType.Tidy3DWebExporter
bl_label = "Tidy3DWebExporter"
input_sockets = {
"FDTD Sim": sockets.MaxwellFDTDSimSocketDef(),
"Cloud Task": sockets.Tidy3DCloudTaskSocketDef(
task_exists=False,
),
}
output_sockets = {
"Cloud Task": sockets.Tidy3DCloudTaskSocketDef(
task_exists=True,
),
}
lock_tree: bpy.props.BoolProperty(
name="Whether to lock the attached tree",
description="Whether or not to lock the attached tree",
default=False,
update=(lambda self, context: self.sync_lock_tree(context)),
)
uploaded_task_id: bpy.props.StringProperty(
name="Uploaded Task ID",
description="The uploaded task ID",
default="",
)
####################
# - Sync Methods
####################
def sync_lock_tree(self, context):
node_tree = self.id_data
if self.lock_tree:
self.trigger_action("enable_lock")
self.locked = False
for bl_socket in self.inputs:
if bl_socket.name == "FDTD Sim": continue
bl_socket.locked = False
else:
self.trigger_action("disable_lock")
####################
# - Output Socket Callbacks
####################
def web_upload(self):
if not (sim := self._compute_input("FDTD Sim")):
raise ValueError("Must attach simulation")
if not (new_task_dict := self._compute_input("Cloud Task")):
raise ValueError("No valid cloud task defined")
td_web = g_td_web(None) ## Presume already auth'ed
self.uploaded_task_id = td_web.api.webapi.upload(
sim,
**new_task_dict,
verbose=True,
)
self.inputs["Cloud Task"].sync_task_loaded(self.uploaded_task_id)
def load_uploaded_task(self):
self.inputs["Cloud Task"].sync_task_loaded(None)
self.uploaded_task_id = self._compute_input("Cloud Task")
self.trigger_action("value_changed")
def run_uploaded_task(self):
td_web = g_td_web(None) ## Presume already auth'ed
td_web.api.webapi.start(self.uploaded_task_id)
self.trigger_action("value_changed")
def release_uploaded_task(self):
self.uploaded_task_id = ""
self.inputs["Cloud Task"].sync_task_released(specify_new_task=True)
self.trigger_action("value_changed")
####################
# - UI
####################
def draw_operators(self, context, layout):
is_authed = is_td_web_authed()
has_uploaded_task_id = bool(self.uploaded_task_id)
# Row: Run Simulation
row = layout.row(align=True)
if has_uploaded_task_id: row.enabled = False
row.operator(
Tidy3DWebUploadOperator.bl_idname,
text="Upload Sim",
)
tree_lock_icon = "LOCKED" if self.lock_tree else "UNLOCKED"
row.prop(self, "lock_tree", toggle=True, icon=tree_lock_icon, text="")
# Row: Run Simulation
row = layout.row(align=True)
if is_authed and has_uploaded_task_id:
run_sim_text = f"Run Sim (~{estimated_task_cost(self.uploaded_task_id):.3f} credits)"
else:
run_sim_text = f"Run Sim"
row.operator(
RunUploadedTidy3DSim.bl_idname,
text=run_sim_text,
)
if has_uploaded_task_id:
tree_lock_icon = "LOOP_BACK"
row.operator(
ReleaseTidy3DExportOperator.bl_idname,
icon="LOOP_BACK",
text="",
)
else:
row.operator(
Tidy3DLoadUploadedOperator.bl_idname,
icon="TRIA_UP_BAR",
text="",
)
# Row: Simulation Progress
if is_authed and has_uploaded_task_id:
progress = {
"draft": (0.0, "Waiting to Run..."),
"initialized": (0.0, "Initializing..."),
"queued": (0.0, "Queued..."),
"preprocessing": (0.05, "Pre-processing..."),
"running": (0.2, "Running..."),
"postprocessing": (0.85, "Post-processing..."),
"success": (1.0, f"Success (={billed_task_cost(self.uploaded_task_id)} credits)"),
"error": (1.0, f"Error (={billed_task_cost(self.uploaded_task_id)} credits)"),
}[task_status(self.uploaded_task_id)]
layout.separator()
row = layout.row(align=True)
row.progress(
factor=progress[0],
type="BAR",
text=progress[1],
)
####################
# - Output Methods
####################
@base.computes_output_socket(
"Cloud Task",
input_sockets={"Cloud Task"},
)
def compute_cloud_task(self, input_sockets: dict) -> str | None:
if self.uploaded_task_id: return self.uploaded_task_id
return None
####################
# - Update
####################
@base.on_value_changed(socket_name="FDTD Sim")
def on_value_changed__fdtd_sim(self):
estimated_task_cost.cache_clear()
task_status.cache_clear()
billed_task_cost.cache_clear()
@base.on_value_changed(socket_name="Cloud Task")
def on_value_changed__cloud_task(self):
estimated_task_cost.cache_clear()
task_status.cache_clear()
billed_task_cost.cache_clear()
####################
# - Blender Registration
####################
BL_REGISTER = [
Tidy3DWebUploadOperator,
Tidy3DTaskStatusModalOperator,
RunUploadedTidy3DSim,
Tidy3DLoadUploadedOperator,
ReleaseTidy3DExportOperator,
Tidy3DWebExporterNode,
]
BL_NODES = {
ct.NodeType.Tidy3DWebExporter: (
ct.NodeCategory.MAXWELLSIM_OUTPUTS_EXPORTERS
)
}

View File

@ -1,2 +0,0 @@
BL_REGISTER = []
BL_NODES = {}

View File

@ -0,0 +1,97 @@
import functools
import typing as typ
import json
from pathlib import Path
import bpy
import sympy as sp
import pydantic as pyd
import tidy3d as td
from ... import contracts as ct
from ... import sockets
from .. import base
class ConsoleViewOperator(bpy.types.Operator):
bl_idname = "blender_maxwell.console_view_operator"
bl_label = "View Plots"
@classmethod
def poll(cls, context):
return True
def execute(self, context):
node = context.node
node.print_data_to_console()
return {'FINISHED'}
class RefreshPlotViewOperator(bpy.types.Operator):
bl_idname = "blender_maxwell.refresh_plot_view_operator"
bl_label = "Refresh Plots"
@classmethod
def poll(cls, context):
return True
def execute(self, context):
node = context.node
node.trigger_action("value_changed", "Data")
return {'FINISHED'}
####################
# - Node
####################
class ViewerNode(base.MaxwellSimNode):
node_type = ct.NodeType.Viewer
bl_label = "Viewer"
input_sockets = {
"Data": sockets.AnySocketDef(),
}
####################
# - UI
####################
def draw_operators(self, context, layout):
row = layout.row(align=True)
row.label(text="Console")
row.operator(ConsoleViewOperator.bl_idname, text="Print")
row = layout.row(align=True)
row.label(text="Plot")
row.operator(RefreshPlotViewOperator.bl_idname, text="", icon="FILE_REFRESH")
####################
# - Methods
####################
def print_data_to_console(self):
if not (data := self._compute_input("Data")):
return
if isinstance(data, sp.Basic):
sp.pprint(data, use_unicode=True)
print(str(data))
####################
# - Update
####################
@base.on_value_changed(socket_name="Data")
def on_value_changed__data(self):
self.trigger_action("show_plot")
####################
# - Blender Registration
####################
BL_REGISTER = [
ConsoleViewOperator,
RefreshPlotViewOperator,
ViewerNode,
]
BL_NODES = {
ct.NodeType.Viewer: (
ct.NodeCategory.MAXWELLSIM_OUTPUTS
)
}

View File

@ -1,14 +0,0 @@
from . import viewer_3d
from . import value_viewer
from . import console_viewer
BL_REGISTER = [
*viewer_3d.BL_REGISTER,
*value_viewer.BL_REGISTER,
*console_viewer.BL_REGISTER,
]
BL_NODES = {
**viewer_3d.BL_NODES,
**value_viewer.BL_NODES,
**console_viewer.BL_NODES,
}

View File

@ -1,6 +0,0 @@
####################
# - Blender Registration
####################
BL_REGISTER = []
BL_NODES = {}

View File

@ -1,6 +0,0 @@
####################
# - Blender Registration
####################
BL_REGISTER = []
BL_NODES = {}

View File

@ -1,50 +0,0 @@
import typing as typ
import json
from pathlib import Path
import bpy
import sympy as sp
import pydantic as pyd
import tidy3d as td
from .... import contracts
from .... import sockets
from ... import base
INTERNAL_GEONODES = {
}
####################
# - Node
####################
class Viewer3DNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.Viewer3D
bl_label = "3D Viewer"
input_sockets = {
"data": sockets.AnySocketDef(
label="Data",
),
}
output_sockets = {}
####################
# - Update
####################
def update_cb(self):
pass
####################
# - Blender Registration
####################
BL_REGISTER = [
Viewer3DNode,
]
BL_NODES = {
contracts.NodeType.Viewer3D: (
contracts.NodeCategory.MAXWELLSIM_OUTPUTS_VIEWERS
)
}

View File

@ -1,15 +1,19 @@
from . import sim_grid
from . import sim_grid_axes
from . import sim_domain
#from . import sim_grid
#from . import sim_grid_axes
from . import fdtd_sim
BL_REGISTER = [
*sim_grid.BL_REGISTER,
*sim_grid_axes.BL_REGISTER,
*sim_domain.BL_REGISTER,
# *sim_grid.BL_REGISTER,
# *sim_grid_axes.BL_REGISTER,
*fdtd_sim.BL_REGISTER,
]
BL_NODES = {
**sim_grid.BL_NODES,
**sim_grid_axes.BL_NODES,
**sim_domain.BL_NODES,
# **sim_grid.BL_NODES,
# **sim_grid_axes.BL_NODES,
**fdtd_sim.BL_NODES,
}

View File

@ -2,71 +2,57 @@ import tidy3d as td
import sympy as sp
import sympy.physics.units as spu
from ... import contracts
from ... import contracts as ct
from ... import sockets
from .. import base
class FDTDSimNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.FDTDSim
class FDTDSimNode(base.MaxwellSimNode):
node_type = ct.NodeType.FDTDSim
bl_label = "FDTD Simulation"
#bl_icon = ...
####################
# - Sockets
####################
input_sockets = {
"run_time": sockets.PhysicalTimeSocketDef(
label="Run Time",
),
"domain": sockets.PhysicalSize3DSocketDef(
label="Domain",
),
"ambient_medium": sockets.MaxwellMediumSocketDef(
label="Ambient Medium",
),
"source": sockets.MaxwellSourceSocketDef(
label="Source",
),
"structure": sockets.MaxwellStructureSocketDef(
label="Structure",
),
"bound": sockets.MaxwellBoundBoxSocketDef(
label="Bound",
),
"Domain": sockets.MaxwellSimDomainSocketDef(),
"BCs": sockets.MaxwellBoundBoxSocketDef(),
"Sources": sockets.MaxwellSourceSocketDef(),
"Structures": sockets.MaxwellStructureSocketDef(),
"Monitors": sockets.MaxwellMonitorSocketDef(),
}
output_sockets = {
"fdtd_sim": sockets.MaxwellFDTDSimSocketDef(
label="FDTD Sim",
),
"FDTD Sim": sockets.MaxwellFDTDSimSocketDef(),
}
####################
# - Output Socket Computation
####################
@base.computes_output_socket("fdtd_sim")
def compute_simulation(self: contracts.NodeTypeProtocol) -> td.Simulation:
_run_time = self.compute_input("run_time")
_domain = self.compute_input("domain")
ambient_medium = self.compute_input("ambient_medium")
structures = [self.compute_input("structure")]
sources = [self.compute_input("source")]
bound = self.compute_input("bound")
@base.computes_output_socket(
"FDTD Sim",
kind=ct.DataFlowKind.Value,
input_sockets={
"Sources", "Structures", "Domain", "BCs", "Monitors"
},
)
def compute_fdtd_sim(self, input_sockets: dict) -> sp.Expr:
sim_domain = input_sockets["Domain"]
sources = input_sockets["Sources"]
structures = input_sockets["Structures"]
bounds = input_sockets["BCs"]
monitors = input_sockets["Monitors"]
run_time = spu.convert_to(_run_time, spu.second) / spu.second
domain = tuple(spu.convert_to(_domain, spu.um) / spu.um)
if not isinstance(sources, list):
sources = [sources]
if not isinstance(structures, list):
structures = [structures]
return td.Simulation(
size=domain,
medium=ambient_medium,
**sim_domain, ## run_time=, size=, grid=, medium=
structures=structures,
sources=sources,
boundary_spec=bound,
run_time=run_time,
boundary_spec=bounds,
)
####################
# - Blender Registration
####################
@ -74,7 +60,7 @@ BL_REGISTER = [
FDTDSimNode,
]
BL_NODES = {
contracts.NodeType.FDTDSim: (
contracts.NodeCategory.MAXWELLSIM_SIMS
ct.NodeType.FDTDSim: (
ct.NodeCategory.MAXWELLSIM_SIMS
)
}

View File

@ -0,0 +1,60 @@
import bpy
import sympy as sp
import sympy.physics.units as spu
import scipy as sc
from ... import contracts as ct
from ... import sockets
from .. import base
class SimDomainNode(base.MaxwellSimNode):
node_type = ct.NodeType.SimDomain
bl_label = "Sim Domain"
input_sockets = {
"Duration": sockets.PhysicalTimeSocketDef(
default_value = 5 * spu.ps,
default_unit = spu.ps,
),
"Size": sockets.PhysicalSize3DSocketDef(),
"Grid": sockets.MaxwellSimGridSocketDef(),
"Ambient Medium": sockets.MaxwellMediumSocketDef(),
}
output_sockets = {
"Domain": sockets.MaxwellSimDomainSocketDef(),
}
####################
# - Callbacks
####################
@base.computes_output_socket(
"Domain",
input_sockets={"Duration", "Size", "Grid", "Ambient Medium"},
)
def compute_sim_domain(self, input_sockets: dict) -> sp.Expr:
if all([
(_duration := input_sockets["Duration"]),
(_size := input_sockets["Size"]),
(grid := input_sockets["Grid"]),
(medium := input_sockets["Ambient Medium"]),
]):
duration = spu.convert_to(_duration, spu.second) / spu.second
size = tuple(spu.convert_to(_size, spu.um) / spu.um)
return dict(
run_time=duration,
size=size,
grid_spec=grid,
medium=medium,
)
####################
# - Blender Registration
####################
BL_REGISTER = [
SimDomainNode,
]
BL_NODES = {
ct.NodeType.SimDomain: (
ct.NodeCategory.MAXWELLSIM_SIMS
)
}

View File

@ -1,27 +1,27 @@
from . import temporal_shapes
from . import point_dipole_source
from . import uniform_current_source
#from . import uniform_current_source
from . import plane_wave_source
from . import gaussian_beam_source
from . import astigmatic_gaussian_beam_source
from . import tfsf_source
#from . import gaussian_beam_source
#from . import astigmatic_gaussian_beam_source
#from . import tfsf_source
BL_REGISTER = [
*temporal_shapes.BL_REGISTER,
*point_dipole_source.BL_REGISTER,
*uniform_current_source.BL_REGISTER,
# *uniform_current_source.BL_REGISTER,
*plane_wave_source.BL_REGISTER,
*gaussian_beam_source.BL_REGISTER,
*astigmatic_gaussian_beam_source.BL_REGISTER,
*tfsf_source.BL_REGISTER,
# *gaussian_beam_source.BL_REGISTER,
# *astigmatic_gaussian_beam_source.BL_REGISTER,
# *tfsf_source.BL_REGISTER,
]
BL_NODES = {
**temporal_shapes.BL_NODES,
**point_dipole_source.BL_NODES,
**uniform_current_source.BL_NODES,
# **uniform_current_source.BL_NODES,
**plane_wave_source.BL_NODES,
**gaussian_beam_source.BL_NODES,
**astigmatic_gaussian_beam_source.BL_NODES,
**tfsf_source.BL_NODES,
# **gaussian_beam_source.BL_NODES,
# **astigmatic_gaussian_beam_source.BL_NODES,
# **tfsf_source.BL_NODES,
}

View File

@ -4,79 +4,93 @@ import tidy3d as td
import sympy as sp
import sympy.physics.units as spu
from ... import contracts
import bpy
from ... import contracts as ct
from ... import sockets
from .. import base
class PlaneWaveSourceNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.PlaneWaveSource
class PlaneWaveSourceNode(base.MaxwellSimNode):
node_type = ct.NodeType.PlaneWaveSource
bl_label = "Plane Wave Source"
#bl_icon = ...
####################
# - Sockets
####################
input_sockets = {
"temporal_shape": sockets.MaxwellTemporalShapeSocketDef(
label="Temporal Shape",
),
"center": sockets.PhysicalPoint3DSocketDef(
label="Center",
),
"size": sockets.PhysicalSize3DSocketDef(
label="Size",
),
"direction": sockets.BoolSocketDef(
label="+ Direction?",
"Temporal Shape": sockets.MaxwellTemporalShapeSocketDef(),
"Center": sockets.PhysicalPoint3DSocketDef(),
"Direction": sockets.BoolSocketDef(
default_value=True,
),
"angle_theta": sockets.PhysicalAngleSocketDef(
label="θ",
),
"angle_phi": sockets.PhysicalAngleSocketDef(
label="φ",
),
"angle_pol": sockets.PhysicalAngleSocketDef(
label="Pol Angle",
),
"Pol": sockets.PhysicalPolSocketDef(),
}
output_sockets = {
"source": sockets.MaxwellSourceSocketDef(
label="Source",
),
"Source": sockets.MaxwellSourceSocketDef(),
}
####################
# - Properties
####################
inj_axis: bpy.props.EnumProperty(
name="Injection Axis",
description="Axis to inject plane wave along",
items=[
("X", "X", "X-Axis"),
("Y", "Y", "Y-Axis"),
("Z", "Z", "Z-Axis"),
],
default="Y",
update=(lambda self, context: self.sync_prop("inj_axis")),
)
####################
# - Output Socket Computation
####################
@base.computes_output_socket("source")
def compute_source(self: contracts.NodeTypeProtocol) -> td.PointDipole:
temporal_shape = self.compute_input("temporal_shape")
_center = self.compute_input("center")
_size = self.compute_input("size")
_direction = self.compute_input("direction")
_angle_theta = self.compute_input("angle_theta")
_angle_phi = self.compute_input("angle_phi")
_angle_pol = self.compute_input("angle_pol")
@base.computes_output_socket(
"Source",
input_sockets={"Temporal Shape", "Center", "Direction", "Pol"},
props={"inj_axis"},
)
def compute_source(self, input_sockets: dict, props: dict):
temporal_shape = input_sockets["Temporal Shape"]
_center = input_sockets["Center"]
_direction = input_sockets["Direction"]
_inj_axis = props["inj_axis"]
pol = input_sockets["Pol"]
direction = {
False: "-",
True: "+",
}[_direction]
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
size = tuple(
0 if val == 1.0 else math.inf
for val in spu.convert_to(_size, spu.um) / spu.um
)
angle_theta = spu.convert_to(_angle_theta, spu.rad) / spu.rad
angle_phi = spu.convert_to(_angle_phi, spu.rad) / spu.rad
angle_pol = spu.convert_to(_angle_pol, spu.rad) / spu.rad
size = {
"X": (0, math.inf, math.inf),
"Y": (math.inf, 0, math.inf),
"Z": (math.inf, math.inf, 0),
}[_inj_axis]
S0, S1, S2, S3 = tuple(pol)
chi = 0.5 * sp.atan2(S2, S1)
psi = 0.5 * sp.asin(S3/S0)
## chi: Pol angle
## psi: Ellipticity
## TODO: Something's wonky.
#angle_theta = chi
#angle_phi = psi
pol_angle = sp.pi/2 - chi
# Display the results
return td.PlaneWave(
center=center,
center=tuple(_center),
size=size,
source_time=temporal_shape,
direction="+" if _direction else "-",
angle_theta=angle_theta,
angle_phi=angle_phi,
pol_angle=angle_pol,
#angle_theta=angle_theta,
#angle_phi=angle_phi,
#pol_angle=pol_angle,
)
@ -88,7 +102,7 @@ BL_REGISTER = [
PlaneWaveSourceNode,
]
BL_NODES = {
contracts.NodeType.PlaneWaveSource: (
contracts.NodeCategory.MAXWELLSIM_SOURCES
ct.NodeType.PlaneWaveSource: (
ct.NodeCategory.MAXWELLSIM_SOURCES
)
}

View File

@ -1,59 +1,81 @@
import typing as typ
import tidy3d as td
import sympy as sp
import sympy.physics.units as spu
from ... import contracts
import bpy
from ... import contracts as ct
from ... import sockets
from .. import base
class PointDipoleSourceNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.PointDipoleSource
class PointDipoleSourceNode(base.MaxwellSimNode):
node_type = ct.NodeType.PointDipoleSource
bl_label = "Point Dipole Source"
#bl_icon = ...
####################
# - Sockets
####################
input_sockets = {
"polarization": sockets.PhysicalPolSocketDef(
label="Polarization",
),
"temporal_shape": sockets.MaxwellTemporalShapeSocketDef(
label="Temporal Shape",
),
"center": sockets.PhysicalPoint3DSocketDef(
label="Center",
),
"interpolate": sockets.BoolSocketDef(
label="Interpolate",
"Temporal Shape": sockets.MaxwellTemporalShapeSocketDef(),
"Center": sockets.PhysicalPoint3DSocketDef(),
"Interpolate": sockets.BoolSocketDef(
default_value=True,
),
}
output_sockets = {
"source": sockets.MaxwellSourceSocketDef(
label="Source",
),
"Source": sockets.MaxwellSourceSocketDef(),
}
####################
# - Properties
####################
pol_axis: bpy.props.EnumProperty(
name="Polarization Axis",
description="Polarization Axis",
items=[
("EX", "Ex", "Electric field in x-dir"),
("EY", "Ey", "Electric field in y-dir"),
("EZ", "Ez", "Electric field in z-dir"),
],
default="EX",
update=(lambda self, context: self.sync_prop("pol_axis")),
)
####################
# - UI
####################
def draw_props(self, context, layout):
layout.prop(self, "pol_axis", text="Pol Axis")
####################
# - Output Socket Computation
####################
@base.computes_output_socket("source")
def compute_source(self: contracts.NodeTypeProtocol) -> td.PointDipole:
polarization = self.compute_input("polarization")
temporal_shape = self.compute_input("temporal_shape")
_center = self.compute_input("center")
interpolate = self.compute_input("interpolate")
@base.computes_output_socket(
"Source",
input_sockets={"Temporal Shape", "Center", "Interpolate"},
props={"pol_axis"},
)
def compute_source(self, input_sockets: dict[str, typ.Any], props: dict[str, typ.Any]) -> td.PointDipole:
pol_axis = {
"EX": "Ex",
"EY": "Ey",
"EZ": "Ez",
}[props["pol_axis"]]
temporal_shape = input_sockets["Temporal Shape"]
_center = input_sockets["Center"]
interpolate = input_sockets["Interpolate"]
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
return td.PointDipole(
_res = td.PointDipole(
center=center,
source_time=temporal_shape,
interpolate=interpolate,
polarization=polarization,
polarization=pol_axis,
)
return _res
@ -64,7 +86,7 @@ BL_REGISTER = [
PointDipoleSourceNode,
]
BL_NODES = {
contracts.NodeType.PointDipoleSource: (
contracts.NodeCategory.MAXWELLSIM_SOURCES
ct.NodeType.PointDipoleSource: (
ct.NodeCategory.MAXWELLSIM_SOURCES
)
}

View File

@ -1,14 +1,14 @@
from . import gaussian_pulse_temporal_shape
from . import continuous_wave_temporal_shape
from . import array_temporal_shape
#from . import continuous_wave_temporal_shape
#from . import array_temporal_shape
BL_REGISTER = [
*gaussian_pulse_temporal_shape.BL_REGISTER,
*continuous_wave_temporal_shape.BL_REGISTER,
*array_temporal_shape.BL_REGISTER,
# *continuous_wave_temporal_shape.BL_REGISTER,
# *array_temporal_shape.BL_REGISTER,
]
BL_NODES = {
**gaussian_pulse_temporal_shape.BL_NODES,
**continuous_wave_temporal_shape.BL_NODES,
**array_temporal_shape.BL_NODES,
# **continuous_wave_temporal_shape.BL_NODES,
# **array_temporal_shape.BL_NODES,
}

View File

@ -1,13 +1,20 @@
import typing as typ
import tidy3d as td
import numpy as np
import sympy as sp
import sympy.physics.units as spu
from .... import contracts
import bpy
from ......utils import extra_sympy_units as spuex
from .... import contracts as ct
from .... import sockets
from .... import managed_objs
from ... import base
class GaussianPulseTemporalShapeNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.GaussianPulseTemporalShape
class GaussianPulseTemporalShapeNode(base.MaxwellSimNode):
node_type = ct.NodeType.GaussianPulseTemporalShape
bl_label = "Gaussian Pulse Temporal Shape"
#bl_icon = ...
@ -19,45 +26,85 @@ class GaussianPulseTemporalShapeNode(base.MaxwellSimTreeNode):
#"amplitude": sockets.RealNumberSocketDef(
# label="Temporal Shape",
#), ## Should have a unit of some kind...
"phase": sockets.PhysicalAngleSocketDef(
label="Phase",
"Freq Center": sockets.PhysicalFreqSocketDef(
default_value=500 * spuex.terahertz,
),
"freq_center": sockets.PhysicalFreqSocketDef(
label="Freq Center",
"Freq Std.": sockets.PhysicalFreqSocketDef(
default_value=200 * spuex.terahertz,
),
"freq_std": sockets.PhysicalFreqSocketDef(
label="Freq STD",
),
"time_delay_rel_ang_freq": sockets.RealNumberSocketDef(
label="Time Delay rel. Ang. Freq",
"Phase": sockets.PhysicalAngleSocketDef(),
"Delay rel. AngFreq": sockets.RealNumberSocketDef(
default_value=5.0,
),
"remove_dc_component": sockets.BoolSocketDef(
label="Remove DC",
"Remove DC": sockets.BoolSocketDef(
default_value=True,
),
}
output_sockets = {
"temporal_shape": sockets.MaxwellTemporalShapeSocketDef(
label="Temporal Shape",
),
"Temporal Shape": sockets.MaxwellTemporalShapeSocketDef(),
}
managed_obj_defs = {
"amp_time": ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLImage(name),
name_prefix="amp_time_",
)
}
####################
# - Properties
####################
plot_time_start: bpy.props.FloatProperty(
name="Plot Time Start (ps)",
description="The instance ID of a particular MaxwellSimNode instance, used to index caches",
default=0.0,
update=(lambda self, context: self.sync_prop("plot_time_start", context)),
)
plot_time_end: bpy.props.FloatProperty(
name="Plot Time End (ps)",
description="The instance ID of a particular MaxwellSimNode instance, used to index caches",
default=5,
update=(lambda self, context: self.sync_prop("plot_time_start", context)),
)
####################
# - UI
####################
def draw_props(self, context, layout):
layout.label(text="Plot Settings")
split = layout.split(factor=0.6)
col = split.column()
col.label(text="t-Range (ps)")
col = split.column()
col.prop(self, "plot_time_start", text="")
col.prop(self, "plot_time_end", text="")
####################
# - Output Socket Computation
####################
@base.computes_output_socket("temporal_shape")
def compute_source(self: contracts.NodeTypeProtocol) -> td.PointDipole:
_phase = self.compute_input("phase")
_freq_center = self.compute_input("freq_center")
_freq_std = self.compute_input("freq_std")
time_delay_rel_ang_freq = self.compute_input("time_delay_rel_ang_freq")
remove_dc_component = self.compute_input("remove_dc_component")
@base.computes_output_socket(
"Temporal Shape",
input_sockets={
"Freq Center", "Freq Std.", "Phase", "Delay rel. AngFreq",
"Remove DC",
}
)
def compute_source(self, input_sockets: dict) -> td.GaussianPulse:
if (
(_freq_center := input_sockets["Freq Center"]) is None
or (_freq_std := input_sockets["Freq Std."]) is None
or (_phase := input_sockets["Phase"]) is None
or (time_delay_rel_ang_freq := input_sockets["Delay rel. AngFreq"]) is None
or (remove_dc_component := input_sockets["Remove DC"]) is None
):
raise ValueError("Inputs not defined")
cheating_amplitude = 1.0
phase = spu.convert_to(_phase, spu.radian) / spu.radian
freq_center = spu.convert_to(_freq_center, spu.hertz) / spu.hertz
freq_std = spu.convert_to(_freq_std, spu.hertz) / spu.hertz
phase = spu.convert_to(_phase, spu.radian) / spu.radian
return td.GaussianPulse(
amplitude=cheating_amplitude,
@ -67,6 +114,29 @@ class GaussianPulseTemporalShapeNode(base.MaxwellSimTreeNode):
offset=time_delay_rel_ang_freq,
remove_dc_component=remove_dc_component,
)
@base.on_show_plot(
managed_objs={"amp_time"},
props={"plot_time_start", "plot_time_end"},
output_sockets={"Temporal Shape"},
stop_propagation=True,
)
def on_show_plot(
self,
managed_objs: dict[str, ct.schemas.ManagedObj],
output_sockets: dict[str, typ.Any],
props: dict[str, typ.Any],
):
temporal_shape = output_sockets["Temporal Shape"]
plot_time_start = props["plot_time_start"] * 1e-15
plot_time_end = props["plot_time_end"] * 1e-15
times = np.linspace(plot_time_start, plot_time_end)
managed_objs["amp_time"].mpl_plot_to_image(
lambda ax: temporal_shape.plot_spectrum(times, ax=ax),
bl_select=True,
)
@ -77,7 +147,7 @@ BL_REGISTER = [
GaussianPulseTemporalShapeNode,
]
BL_NODES = {
contracts.NodeType.GaussianPulseTemporalShape: (
contracts.NodeCategory.MAXWELLSIM_SOURCES_TEMPORALSHAPES
ct.NodeType.GaussianPulseTemporalShape: (
ct.NodeCategory.MAXWELLSIM_SOURCES_TEMPORALSHAPES
)
}

View File

@ -1,18 +1,18 @@
from . import object_structure
from . import geonodes_structure
from . import scripted_structure
#from . import object_structure
#from . import geonodes_structure
#from . import scripted_structure
from . import primitives
BL_REGISTER = [
*object_structure.BL_REGISTER,
*geonodes_structure.BL_REGISTER,
*scripted_structure.BL_REGISTER,
# *object_structure.BL_REGISTER,
# *geonodes_structure.BL_REGISTER,
# *scripted_structure.BL_REGISTER,
*primitives.BL_REGISTER,
]
BL_NODES = {
**object_structure.BL_NODES,
**geonodes_structure.BL_NODES,
**scripted_structure.BL_NODES,
# **object_structure.BL_NODES,
# **geonodes_structure.BL_NODES,
# **scripted_structure.BL_NODES,
**primitives.BL_NODES,
}

View File

@ -1,14 +1,14 @@
from . import box_structure
from . import cylinder_structure
from . import sphere_structure
#from . import cylinder_structure
#from . import sphere_structure
BL_REGISTER = [
*box_structure.BL_REGISTER,
*cylinder_structure.BL_REGISTER,
*sphere_structure.BL_REGISTER,
# *cylinder_structure.BL_REGISTER,
# *sphere_structure.BL_REGISTER,
]
BL_NODES = {
**box_structure.BL_NODES,
**cylinder_structure.BL_NODES,
**sphere_structure.BL_NODES,
# **cylinder_structure.BL_NODES,
# **sphere_structure.BL_NODES,
}

View File

@ -2,43 +2,37 @@ import tidy3d as td
import sympy as sp
import sympy.physics.units as spu
from .... import contracts
from .... import contracts as ct
from .... import sockets
from ... import base
class BoxStructureNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.BoxStructure
class BoxStructureNode(base.MaxwellSimNode):
node_type = ct.NodeType.BoxStructure
bl_label = "Box Structure"
#bl_icon = ...
####################
# - Sockets
####################
input_sockets = {
"medium": sockets.MaxwellMediumSocketDef(
label="Medium",
),
"center": sockets.PhysicalPoint3DSocketDef(
label="Center",
),
"size": sockets.PhysicalSize3DSocketDef(
label="Size",
),
"Medium": sockets.MaxwellMediumSocketDef(),
"Center": sockets.PhysicalPoint3DSocketDef(),
"Size": sockets.PhysicalSize3DSocketDef(),
}
output_sockets = {
"structure": sockets.MaxwellStructureSocketDef(
label="Structure",
),
"Structure": sockets.MaxwellStructureSocketDef(),
}
####################
# - Output Socket Computation
####################
@base.computes_output_socket("structure")
def compute_simulation(self: contracts.NodeTypeProtocol) -> td.Box:
medium = self.compute_input("medium")
_center = self.compute_input("center")
_size = self.compute_input("size")
@base.computes_output_socket(
"Structure",
input_sockets={"Medium", "Center", "Size"},
)
def compute_simulation(self, input_sockets: dict) -> td.Box:
medium = input_sockets["Medium"]
_center = input_sockets["Center"]
_size = input_sockets["Size"]
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
size = tuple(spu.convert_to(_size, spu.um) / spu.um)
@ -60,7 +54,7 @@ BL_REGISTER = [
BoxStructureNode,
]
BL_NODES = {
contracts.NodeType.BoxStructure: (
contracts.NodeCategory.MAXWELLSIM_STRUCTURES_PRIMITIVES
ct.NodeType.BoxStructure: (
ct.NodeCategory.MAXWELLSIM_STRUCTURES_PRIMITIVES
)
}

View File

@ -6,7 +6,7 @@ from ... import contracts
from ... import sockets
from .. import base
class CombineNode(base.MaxwellSimTreeNode):
class CombineNode(base.MaxwellSimNode):
node_type = contracts.NodeType.Combine
bl_label = "Combine"
#bl_icon = ...

View File

@ -31,23 +31,19 @@ PhysicalAccelScalarSocketDef = physical.PhysicalAccelScalarSocketDef
PhysicalForceScalarSocketDef = physical.PhysicalForceScalarSocketDef
PhysicalPolSocketDef = physical.PhysicalPolSocketDef
PhysicalFreqSocketDef = physical.PhysicalFreqSocketDef
PhysicalVacWLSocketDef = physical.PhysicalVacWLSocketDef
PhysicalSpecRelPermDistSocketDef = physical.PhysicalSpecRelPermDistSocketDef
PhysicalSpecPowerDistSocketDef = physical.PhysicalSpecPowerDistSocketDef
from . import blender
BlenderObjectSocketDef = blender.BlenderObjectSocketDef
BlenderCollectionSocketDef = blender.BlenderCollectionSocketDef
BlenderImageSocketDef = blender.BlenderImageSocketDef
BlenderVolumeSocketDef = blender.BlenderVolumeSocketDef
BlenderGeoNodesSocketDef = blender.BlenderGeoNodesSocketDef
BlenderTextSocketDef = blender.BlenderTextSocketDef
BlenderPreviewTargetSocketDef = blender.BlenderPreviewTargetSocketDef
from . import maxwell
MaxwellBoundBoxSocketDef = maxwell.MaxwellBoundBoxSocketDef
MaxwellBoundFaceSocketDef = maxwell.MaxwellBoundFaceSocketDef
MaxwellMediumSocketDef = maxwell.MaxwellMediumSocketDef
MaxwellMediumNonLinearitySocketDef = maxwell.MaxwellMediumNonLinearitySocketDef
MaxwellSourceSocketDef = maxwell.MaxwellSourceSocketDef
MaxwellTemporalShapeSocketDef = maxwell.MaxwellTemporalShapeSocketDef
MaxwellStructureSocketDef = maxwell.MaxwellStructureSocketDef
@ -55,6 +51,10 @@ MaxwellMonitorSocketDef = maxwell.MaxwellMonitorSocketDef
MaxwellFDTDSimSocketDef = maxwell.MaxwellFDTDSimSocketDef
MaxwellSimGridSocketDef = maxwell.MaxwellSimGridSocketDef
MaxwellSimGridAxisSocketDef = maxwell.MaxwellSimGridAxisSocketDef
MaxwellSimDomainSocketDef = maxwell.MaxwellSimDomainSocketDef
from . import tidy3d
Tidy3DCloudTaskSocketDef = tidy3d.Tidy3DCloudTaskSocketDef
BL_REGISTER = [
*basic.BL_REGISTER,
@ -63,4 +63,5 @@ BL_REGISTER = [
*physical.BL_REGISTER,
*blender.BL_REGISTER,
*maxwell.BL_REGISTER,
*tidy3d.BL_REGISTER,
]

View File

@ -1,175 +1,316 @@
import typing as typ
import typing_extensions as typx
import functools
import bpy
import pydantic as pyd
import sympy as sp
import sympy.physics.units as spu
from .. import contracts
from .. import contracts as ct
class BLSocket(bpy.types.NodeSocket):
"""A base type for nodes that greatly simplifies the implementation of
reliable, powerful nodes.
class MaxwellSimSocket(bpy.types.NodeSocket):
# Fundamentals
socket_type: ct.SocketType
bl_label: str
Should be used together with `contracts.BLSocketProtocol`.
"""
# Style
display_shape: typx.Literal[
"CIRCLE", "SQUARE", "DIAMOND", "CIRCLE_DOT", "SQUARE_DOT",
"DIAMOND_DOT",
]
socket_color: tuple
# Options
#link_limit: int = 0
use_units: bool = False
# Computed
bl_idname: str
####################
# - Initialization
####################
def __init_subclass__(cls, **kwargs: typ.Any):
super().__init_subclass__(**kwargs) ## Yucky superclass setup.
# Set bl_idname
cls.bl_idname = cls.socket_type.value
cls.socket_color = contracts.SocketType_to_color[
cls.socket_type.value
]
# Setup Blender ID for Node
if not hasattr(cls, "socket_type"):
msg = f"Socket class {cls} does not define 'socket_type'"
raise ValueError(msg)
cls.bl_idname = str(cls.socket_type.value)
# Setup Locked Property for Node
cls.__annotations__["locked"] = bpy.props.BoolProperty(
name="Locked State",
description="The lock-state of a particular socket, which determines the socket's user editability",
default=False,
)
# Setup Style
cls.socket_color = ct.SOCKET_COLORS[cls.socket_type]
cls.socket_shape = ct.SOCKET_SHAPES[cls.socket_type]
# Configure Use of Units
if (
hasattr(cls, "use_units")
and cls.socket_type in contracts.SocketType_to_units
):
# Set Unit Properties
cls.__annotations__["raw_unit"] = bpy.props.EnumProperty(
if cls.use_units:
if not (socket_units := ct.SOCKET_UNITS.get(cls.socket_type)):
msg = "Tried to `use_units` on {cls.bl_idname} socket, but `SocketType` has no units defined in `contracts.SOCKET_UNITS`"
raise RuntimeError(msg)
# Current Unit
cls.__annotations__["active_unit"] = bpy.props.EnumProperty(
name="Unit",
description="Choose a unit",
items=[
(unit_name, str(unit_value), str(unit_value))
for unit_name, unit_value in contracts.SocketType_to_units[
cls.socket_type
]["values"].items()
for unit_name, unit_value in socket_units["values"].items()
],
default=contracts.SocketType_to_units[
cls.socket_type
]["default"],
update=lambda self, context: self._update_unit(),
default=socket_units["default"],
update=lambda self, context: self.sync_unit_change(),
)
cls.__annotations__["raw_unit_previous"] = bpy.props.StringProperty(
default=contracts.SocketType_to_units[
cls.socket_type
]["default"]
)
# Declare Node Property: 'preset' EnumProperty
if hasattr(cls, "draw_preview"):
cls.__annotations__["preview_active"] = bpy.props.BoolProperty(
name="Preview",
description="Preview the socket value",
default=False,
# Previous Unit (for conversion)
cls.__annotations__["prev_active_unit"] = bpy.props.StringProperty(
default=socket_units["default"],
)
####################
# - Internal Methods
# - Action Chain
####################
def trigger_action(
self,
action: typx.Literal["enable_lock", "disable_lock", "value_changed", "show_preview", "show_plot"],
) -> None:
"""Called whenever the socket's output value has changed.
This also invalidates any of the socket's caches.
When called on an input node, the containing node's
`trigger_action` method will be called with this socket.
When called on a linked output node, the linked socket's
`trigger_action` method will be called.
"""
# Forwards Chains
if action in {"value_changed"}:
## Input Socket
if not self.is_output:
self.node.trigger_action(action, socket_name=self.name)
## Linked Output Socket
elif self.is_output and self.is_linked:
for link in self.links:
link.to_socket.trigger_action(action)
# Backwards Chains
elif action in {"enable_lock", "disable_lock", "show_preview", "show_plot"}:
if action == "enable_lock":
self.locked = True
if action == "disable_lock":
self.locked = False
## Output Socket
if self.is_output:
self.node.trigger_action(action, socket_name=self.name)
## Linked Input Socket
elif not self.is_output and self.is_linked:
for link in self.links:
link.from_socket.trigger_action(action)
####################
# - Action Chain: Event Handlers
####################
def sync_prop(self, prop_name: str, context: bpy.types.Context):
"""Called when a property has been updated.
"""
if not hasattr(self, prop_name):
msg = f"Property {prop_name} not defined on socket {self}"
raise RuntimeError(msg)
self.trigger_action("value_changed")
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.
"""
if self.locked: return False
if self.is_output:
msg = f"Tried to sync 'link add' on output socket"
raise RuntimeError(msg)
self.trigger_action("value_changed")
return True
def sync_link_removed(self, from_socket) -> bool:
"""Called when a link has been removed from this (input) socket.
Returns a bool, whether or not the socket consents to the link change.
"""
if self.locked: return False
if self.is_output:
msg = f"Tried to sync 'link add' on output socket"
raise RuntimeError(msg)
self.trigger_action("value_changed")
return True
####################
# - Data Chain
####################
@property
def units(self) -> dict[str, sp.Expr]:
return contracts.SocketType_to_units[
def value(self) -> typ.Any:
raise NotImplementedError
@value.setter
def value(self, value: typ.Any) -> None:
raise NotImplementedError
@property
def lazy_value(self) -> None:
raise NotImplementedError
@lazy_value.setter
def lazy_value(self, lazy_value: typ.Any) -> None:
raise NotImplementedError
@property
def capabilities(self) -> None:
raise NotImplementedError
def _compute_data(
self,
kind: ct.DataFlowKind = ct.DataFlowKind.Value,
) -> typ.Any:
"""Computes the internal data of this socket, ONLY.
**NOTE**: Low-level method. Use `compute_data` instead.
"""
if kind == ct.DataFlowKind.Value:
return self.value
if kind == ct.DataFlowKind.LazyValue:
return self.lazy_value
if kind == ct.DataFlowKind.Capabilities:
return self.capabilities
return None
def compute_data(
self,
kind: ct.DataFlowKind = ct.DataFlowKind.Value,
):
"""Computes the value of this socket, including all relevant factors:
- If input socket, and unlinked, compute internal data.
- If input socket, and linked, compute linked socket data.
- If output socket, ask node for data.
"""
# Compute Output Socket
if self.is_output:
return self.node.compute_output(self.name, kind=kind)
# Compute Input Socket
## Unlinked: Retrieve Socket Value
if not self.is_linked: return self._compute_data(kind)
## Linked: Compute Output of Linked Sockets
linked_values = [
link.from_socket.compute_data(kind)
for link in self.links
]
## Return Single Value / List of Values
if len(linked_values) == 1: return linked_values[0]
return linked_values
####################
# - Unit Properties
####################
@functools.cached_property
def possible_units(self) -> dict[str, sp.Expr]:
if not self.use_units:
msg = "Tried to get possible units for socket {self}, but socket doesn't `use_units`"
raise ValueError(msg)
return ct.SOCKET_UNITS[
self.socket_type
]["values"]
@property
def unit(self) -> sp.Expr:
return contracts.SocketType_to_units[
self.socket_type
]["values"][self.raw_unit]
@unit.setter
def unit(self, value) -> sp.Expr:
raw_unit_name = [
raw_unit_name
for raw_unit_name, unit_value in contracts.SocketType_to_units[
self.socket_type
]["values"].items()
if value == unit_value
][0]
self.raw_unit = raw_unit_name
return self.possible_units[self.active_unit]
@property
def _unit_previous(self) -> sp.Expr:
return contracts.SocketType_to_units[
self.socket_type
]["values"][self.raw_unit_previous]
def prev_unit(self) -> sp.Expr:
return self.possible_units[self.prev_active_unit]
@_unit_previous.setter
def _unit_previous(self, value) -> sp.Expr:
raw_unit_name = [
raw_unit_name
for raw_unit_name, unit_value in contracts.SocketType_to_units[
self.socket_type
]["values"].items()
if value == unit_value
][0]
@unit.setter
def unit(self, value: str | sp.Expr) -> None:
# Retrieve Unit by String
if isinstance(value, str) and value in self.possible_units:
self.active_unit = self.possible_units[value]
return
self.raw_unit_previous = raw_unit_name
# Retrieve =1 Matching Unit Name
matching_unit_names = [
unit_name
for unit_name, unit_sympy in self.possible_units.items()
if value == unit_sympy
]
if len(matching_unit_names) == 0:
msg = f"Tried to set unit for socket {self} with value {value}, but it is not one of possible units {''.join(possible.units.values())} for this socket (as defined in `contracts.SOCKET_UNITS`)"
raise ValueError(msg)
if len(matching_unit_names) > 1:
msg = f"Tried to set unit for socket {self} with value {value}, but multiple possible matching units {''.join(possible.units.values())} for this socket (as defined in `contracts.SOCKET_UNITS`); there may only be one"
raise RuntimeError(msg)
self.active_unit = matching_unit_names[0]
def value_as_unit(self, value) -> typ.Any:
"""Return the given value expresse as the current internal unit,
without the unit.
def sync_unit_change(self) -> None:
"""In unit-aware sockets, the internal `value()` property multiplies the Blender property value by the current active unit.
When the unit is changed, `value()` will display the old scalar with the new unit.
To fix this, we need to update the scalar to use the new unit.
Can be overridden if more specific logic is required.
"""
if hasattr(self, "raw_value") and hasattr(self, "unit"):
# (Guard) Value Compatibility
if not self.is_compatible(value):
msg = f"Tried setting socket ({self}) to incompatible value ({value}) of type {type(value)}"
raise ValueError(msg)
# Return Converted Unit
return spu.convert_to(
value, self.unit
) / self.unit
else:
raise ValueError("Tried to get 'raw_value_as_unit', but class has no 'raw_value'")
prev_value = self.value / self.unit * self.prev_unit
## After changing units, self.value is expressed in the wrong unit.
## - Therefore, we removing the new unit, and re-add the prev unit.
## - Using only self.value avoids implementation-specific details.
self.value = spu.convert_to(
prev_value,
self.unit
) ## Now, the unit conversion can be done correctly.
self.prev_active_unit = self.active_unit
def _update_unit(self) -> None:
"""Convert (if needed) the `raw_value` property, to use the unit
set in the `unit` property.
If the `raw_value` property isn't set, this only sets "unit_previous".
Run right after setting the `unit` property, in order to synchronize
the value with the new unit.
####################
# - Style
####################
def draw_color(
self,
context: bpy.types.Context,
node: bpy.types.Node,
) -> ct.BLColorRGBA:
"""Color of the socket icon, when embedded in a node.
"""
if hasattr(self, "raw_value") and hasattr(self, "unit"):
if hasattr(self.raw_value, "__getitem__"):
self.raw_value = tuple(spu.convert_to(
sp.Matrix(tuple(self.raw_value)) * self._unit_previous,
self.unit,
) / self.unit)
else:
self.raw_value = spu.convert_to(
self.raw_value * self._unit_previous,
self.unit,
) / self.unit
self._unit_previous = self.unit
return self.socket_color
####################
# - Callback Dispatcher
####################
def trigger_updates(self) -> None:
if not self.is_output:
self.node.update()
####################
# - Methods
####################
def is_compatible(self, value: typ.Any) -> bool:
if not hasattr(self, "compatible_types"):
return True
for compatible_type, checks in self.compatible_types.items():
if (
compatible_type is typ.Any or
isinstance(value, compatible_type)
):
return all(check(self, value) for check in checks)
return False
####################
# - UI
####################
@classmethod
def draw_color_simple(cls) -> contracts.BlenderColorRGB:
def draw_color_simple(cls) -> ct.BLColorRGBA:
"""Fallback color of the socket icon (ex.when not embedded in a node).
"""
return cls.socket_color
####################
# - UI Methods
####################
def draw(
self,
context: bpy.types.Context,
@ -177,6 +318,10 @@ class BLSocket(bpy.types.NodeSocket):
node: bpy.types.Node,
text: str,
) -> None:
"""Called by Blender to draw the socket UI.
"""
if self.locked: layout.enabled = False
if self.is_output:
self.draw_output(context, layout, node, text)
else:
@ -189,44 +334,31 @@ class BLSocket(bpy.types.NodeSocket):
node: bpy.types.Node,
text: str,
) -> None:
"""Draws the socket UI, when the socket is an input socket.
"""
# Draw Linked Input: Label Row
if self.is_linked:
layout.label(text=text)
return
# Column
col = layout.column(align=True)
# Parent Column
col = layout.column(align=False)
# Row: Label & Preview Toggle
label_col_row = col.row(align=True)
if hasattr(self, "draw_label_row"):
self.draw_label_row(label_col_row, text)
elif hasattr(self, "raw_unit"):
label_col_row.label(text=text)
label_col_row.prop(self, "raw_unit", text="")
# Draw Label Row
row = col.row(align=True)
if self.use_units:
split = row.split(factor=0.65, align=True)
_row = split.row(align=True)
self.draw_label_row(_row, text)
_col = split.column(align=True)
_col.prop(self, "active_unit", text="")
else:
label_col_row.label(text=text)
self.draw_label_row(row, text)
if hasattr(self, "draw_preview"):
label_col_row.prop(
self,
"preview_active",
toggle=True,
text="",
icon="SEQ_PREVIEW",
)
# Row: Preview (in Box)
if hasattr(self, "draw_preview"):
if self.preview_active:
col_box = col.box()
self.draw_preview(col_box)
# Row(s): Value
if hasattr(self, "draw_value"):
self.draw_value(col)
elif hasattr(self, "raw_value"):
#col_row = col.row(align=True)
col.prop(self, "raw_value", text="")
# Draw Value Row(s)
self.draw_value(col)
def draw_output(
self,
@ -235,23 +367,28 @@ class BLSocket(bpy.types.NodeSocket):
node: bpy.types.Node,
text: str,
) -> None:
col = layout.column()
row_col = col.row()
row_col.alignment = "RIGHT"
# Row: Label & Preview Toggle
if hasattr(self, "draw_preview"):
row_col.prop(
self,
"preview_active",
toggle=True,
text="",
icon="SEQ_PREVIEW",
)
"""Draws the socket UI, when the socket is an output socket.
"""
layout.label(text=text)
####################
# - UI Methods
####################
def draw_label_row(
self,
row: bpy.types.UILayout,
text: str,
) -> None:
"""Called to draw the label row (same height as socket shape).
row_col.label(text=text)
Can be overridden.
"""
row.label(text=text)
def draw_value(self, col: bpy.types.UILayout) -> None:
"""Called to draw the value column in unlinked input sockets.
# Row: Preview (in box)
if hasattr(self, "draw_preview"):
if self.preview_active:
col_box = col.box()
self.draw_preview(col_box)
Can be overridden.
"""
pass

View File

@ -5,46 +5,20 @@ import sympy as sp
import pydantic as pyd
from .. import base
from ... import contracts
from ... import contracts as ct
####################
# - Blender Socket
####################
class AnyBLSocket(base.BLSocket):
socket_type = contracts.SocketType.Any
socket_color = (0.0, 0.0, 0.0, 1.0)
class AnyBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.Any
bl_label = "Any"
compatible_types = {
typ.Any: {},
}
####################
# - Socket UI
####################
def draw_label_row(self, label_col_row: bpy.types.UILayout, text: str) -> None:
"""Draw the value of the real number.
"""
label_col_row.label(text=text)
####################
# - Computation of Default Value
####################
@property
def default_value(self) -> None:
return None
@default_value.setter
def default_value(self, value: typ.Any) -> None:
pass
####################
# - Socket Configuration
####################
class AnySocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.Any
label: str
socket_type: ct.SocketType = ct.SocketType.Any
def init(self, bl_socket: AnyBLSocket) -> None:
pass

View File

@ -5,27 +5,23 @@ import sympy as sp
import pydantic as pyd
from .. import base
from ... import contracts
from ... import contracts as ct
####################
# - Blender Socket
####################
class BoolBLSocket(base.BLSocket):
socket_type = contracts.SocketType.Bool
class BoolBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.Bool
bl_label = "Bool"
compatible_types = {
bool: {},
}
####################
# - Properties
####################
raw_value: bpy.props.BoolProperty(
name="Boolean",
description="Represents a boolean",
description="Represents a boolean value",
default=False,
update=(lambda self, context: self.trigger_updates()),
update=(lambda self, context: self.sync_prop("raw_value", context)),
)
####################
@ -35,36 +31,27 @@ class BoolBLSocket(base.BLSocket):
label_col_row.label(text=text)
label_col_row.prop(self, "raw_value", text="")
def draw_value(self, label_col_row: bpy.types.UILayout) -> None:
pass
####################
# - Computation of Default Value
####################
@property
def default_value(self) -> str:
def value(self) -> bool:
return self.raw_value
@default_value.setter
def default_value(self, value: typ.Any) -> None:
# (Guard) Value Compatibility
if not self.is_compatible(value):
msg = f"Tried setting socket ({self}) to incompatible value ({value}) of type {type(value)}"
raise ValueError(msg)
self.raw_value = bool(value)
@value.setter
def value(self, value: bool) -> None:
self.raw_value = value
####################
# - Socket Configuration
####################
class BoolSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.Bool
label: str
socket_type: ct.SocketType = ct.SocketType.Bool
default_value: bool = False
def init(self, bl_socket: BoolBLSocket) -> None:
bl_socket.raw_value = self.default_value
bl_socket.value = self.default_value
####################
# - Blender Registration

View File

@ -6,29 +6,25 @@ import sympy as sp
import pydantic as pyd
from .. import base
from ... import contracts
from ... import contracts as ct
####################
# - Blender Socket
####################
class FilePathBLSocket(base.BLSocket):
socket_type = contracts.SocketType.FilePath
class FilePathBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.FilePath
bl_label = "File Path"
compatible_types = {
Path: {},
}
####################
# - Properties
####################
raw_value: bpy.props.StringProperty(
name="File Path",
description="Represents the path to a file",
#default="",
subtype="FILE_PATH",
update=(lambda self, context: self.trigger_updates()),
update=(lambda self, context: self.sync_prop("raw_value", context)),
)
## TODO: Use bpy methods to constrain the path
####################
# - Socket UI
@ -41,39 +37,23 @@ class FilePathBLSocket(base.BLSocket):
# - Computation of Default Value
####################
@property
def default_value(self) -> Path:
"""Return the text.
Returns:
The text as a string.
"""
return Path(str(self.raw_value))
def value(self) -> Path:
return Path(bpy.path.abspath(self.raw_value))
@default_value.setter
def default_value(self, value: typ.Any) -> None:
"""Set the real number from some compatible type, namely
real sympy expressions with no symbols, or floats.
"""
# (Guard) Value Compatibility
if not self.is_compatible(value):
msg = f"Tried setting socket ({self}) to incompatible value ({value}) of type {type(value)}"
raise ValueError(msg)
self.raw_value = str(Path(value))
@value.setter
def value(self, value: Path) -> None:
self.raw_value = bpy.path.relpath(str(value))
####################
# - Socket Configuration
####################
class FilePathSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.FilePath
label: str
socket_type: ct.SocketType = ct.SocketType.FilePath
default_path: Path = Path("")
def init(self, bl_socket: FilePathBLSocket) -> None:
bl_socket.default_value = self.default_path
bl_socket.value = self.default_path
####################
# - Blender Registration

View File

@ -5,21 +5,15 @@ import sympy as sp
import pydantic as pyd
from .. import base
from ... import contracts
from ... import contracts as ct
####################
# - Blender Socket
####################
class TextBLSocket(base.BLSocket):
socket_type = contracts.SocketType.Text
socket_color = (0.2, 0.2, 0.2, 1.0)
class TextBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.Text
bl_label = "Text"
compatible_types = {
str: {},
}
####################
# - Properties
####################
@ -27,7 +21,7 @@ class TextBLSocket(base.BLSocket):
name="Text",
description="Represents some text",
default="",
update=(lambda self, context: self.trigger_updates()),
update=(lambda self, context: self.sync_prop("raw_value", context)),
)
####################
@ -38,44 +32,27 @@ class TextBLSocket(base.BLSocket):
"""
label_col_row.prop(self, "raw_value", text=text)
def draw_value(self, label_col_row: bpy.types.UILayout) -> None:
pass
####################
# - Computation of Default Value
####################
@property
def default_value(self) -> str:
"""Return the text.
Returns:
The text as a string.
"""
def value(self) -> str:
return self.raw_value
@default_value.setter
def default_value(self, value: typ.Any) -> None:
"""Set the real number from some compatible type, namely
real sympy expressions with no symbols, or floats.
"""
# (Guard) Value Compatibility
if not self.is_compatible(value):
msg = f"Tried setting socket ({self}) to incompatible value ({value}) of type {type(value)}"
raise ValueError(msg)
self.raw_value = str(value)
@value.setter
def value(self, value: str) -> None:
self.raw_value = value
####################
# - Socket Configuration
####################
class TextSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.Text
label: str
socket_type: ct.SocketType = ct.SocketType.Text
default_text: str = ""
def init(self, bl_socket: TextBLSocket) -> None:
pass
bl_socket.value = self.default_text
####################
# - Blender Registration

View File

@ -4,25 +4,18 @@ BlenderObjectSocketDef = object_socket.BlenderObjectSocketDef
BlenderCollectionSocketDef = collection_socket.BlenderCollectionSocketDef
from . import image_socket
from . import volume_socket
BlenderImageSocketDef = image_socket.BlenderImageSocketDef
BlenderVolumeSocketDef = volume_socket.BlenderVolumeSocketDef
from . import geonodes_socket
from . import text_socket
BlenderGeoNodesSocketDef = geonodes_socket.BlenderGeoNodesSocketDef
BlenderTextSocketDef = text_socket.BlenderTextSocketDef
from . import target_socket
BlenderPreviewTargetSocketDef = target_socket.BlenderPreviewTargetSocketDef
BL_REGISTER = [
*object_socket.BL_REGISTER,
*collection_socket.BL_REGISTER,
*image_socket.BL_REGISTER,
*volume_socket.BL_REGISTER,
*target_socket.BL_REGISTER,
*geonodes_socket.BL_REGISTER,
*text_socket.BL_REGISTER,

View File

@ -4,14 +4,14 @@ import bpy
import pydantic as pyd
from .. import base
from ... import contracts
from ... import contracts as ct
####################
# - Blender Socket
####################
class BlenderCollectionBLSocket(base.BLSocket):
socket_type = contracts.SocketType.BlenderCollection
bl_label = "BlenderCollection"
class BlenderCollectionBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.BlenderCollection
bl_label = "Blender Collection"
####################
# - Properties
@ -20,26 +20,31 @@ class BlenderCollectionBLSocket(base.BLSocket):
name="Blender Collection",
description="Represents a Blender collection",
type=bpy.types.Collection,
update=(lambda self, context: self.trigger_updates()),
update=(lambda self, context: self.sync_prop("raw_value", context)),
)
####################
# - UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, "raw_value", text="")
####################
# - Default Value
####################
@property
def default_value(self) -> bpy.types.Collection | None:
def value(self) -> bpy.types.Collection | None:
return self.raw_value
@default_value.setter
def default_value(self, value: bpy.types.Collection) -> None:
@value.setter
def value(self, value: bpy.types.Collection) -> None:
self.raw_value = value
####################
# - Socket Configuration
####################
class BlenderCollectionSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.BlenderCollection
label: str
socket_type: ct.SocketType = ct.SocketType.BlenderCollection
def init(self, bl_socket: BlenderCollectionBLSocket) -> None:
pass

View File

@ -4,7 +4,7 @@ import bpy
import pydantic as pyd
from .. import base
from ... import contracts
from ... import contracts as ct
####################
# - Operators
@ -22,28 +22,19 @@ class BlenderMaxwellResetGeoNodesSocket(bpy.types.Operator):
####################
# - Blender Socket
####################
class BlenderGeoNodesBLSocket(base.BLSocket):
socket_type = contracts.SocketType.BlenderGeoNodes
bl_label = "BlenderGeoNodes"
class BlenderGeoNodesBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.BlenderGeoNodes
bl_label = "Geometry Node Tree"
####################
# - Properties
####################
def update_geonodes_node(self):
if hasattr(self.node, "update_sockets_from_geonodes"):
self.node.update_sockets_from_geonodes()
else:
raise ValueError("Node doesn't have GeoNodes socket update method.")
# Run the Usual Updates
self.trigger_updates()
raw_value: bpy.props.PointerProperty(
name="Blender GeoNodes Tree",
description="Represents a Blender GeoNodes Tree",
type=bpy.types.NodeTree,
poll=(lambda self, obj: obj.bl_idname == 'GeometryNodeTree'),
update=(lambda self, context: self.update_geonodes_node()),
update=(lambda self, context: self.sync_prop("raw_value", context)),
)
####################
@ -58,23 +49,28 @@ class BlenderGeoNodesBLSocket(base.BLSocket):
icon="FILE_REFRESH",
)
####################
# - UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, "raw_value", text="")
####################
# - Default Value
####################
@property
def default_value(self) -> bpy.types.Object | None:
def value(self) -> bpy.types.NodeTree | None:
return self.raw_value
@default_value.setter
def default_value(self, value: bpy.types.Object) -> None:
@value.setter
def value(self, value: bpy.types.NodeTree) -> None:
self.raw_value = value
####################
# - Socket Configuration
####################
class BlenderGeoNodesSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.BlenderGeoNodes
label: str
socket_type: ct.SocketType = ct.SocketType.BlenderGeoNodes
def init(self, bl_socket: BlenderGeoNodesBLSocket) -> None:
pass

View File

@ -4,32 +4,47 @@ import bpy
import pydantic as pyd
from .. import base
from ... import contracts
from ... import contracts as ct
####################
# - Blender Socket
####################
class BlenderImageBLSocket(base.BLSocket):
socket_type = contracts.SocketType.BlenderImage
bl_label = "BlenderImage"
class BlenderImageBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.BlenderImage
bl_label = "Blender Image"
####################
# - Properties
####################
raw_value: bpy.props.PointerProperty(
name="Blender Image",
description="Represents a Blender Image",
type=bpy.types.Image,
update=(lambda self, context: self.sync_prop("raw_value", context)),
)
####################
# - UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, "raw_value", text="")
####################
# - Default Value
####################
@property
def default_value(self) -> None:
pass
def value(self) -> bpy.types.Image | None:
return self.raw_value
@default_value.setter
def default_value(self, value: typ.Any) -> None:
pass
@value.setter
def value(self, value: bpy.types.Image) -> None:
self.raw_value = value
####################
# - Socket Configuration
####################
class BlenderImageSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.BlenderImage
label: str
socket_type: ct.SocketType = ct.SocketType.BlenderImage
def init(self, bl_socket: BlenderImageBLSocket) -> None:
pass

View File

@ -4,7 +4,7 @@ import bpy
import pydantic as pyd
from .. import base
from ... import contracts
from ... import contracts as ct
####################
# - Blender Socket
@ -13,6 +13,7 @@ class BlenderMaxwellCreateAndAssignBLObject(bpy.types.Operator):
bl_idname = "blender_maxwell.create_and_assign_bl_object"
bl_label = "Create and Assign BL Object"
## TODO: Refactor
def execute(self, context):
mesh = bpy.data.meshes.new("GenMesh")
new_bl_object = bpy.data.objects.new("GenObj", mesh)
@ -32,9 +33,9 @@ class BlenderMaxwellCreateAndAssignBLObject(bpy.types.Operator):
####################
# - Blender Socket
####################
class BlenderObjectBLSocket(base.BLSocket):
socket_type = contracts.SocketType.BlenderObject
bl_label = "BlenderObject"
class BlenderObjectBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.BlenderObject
bl_label = "Blender Object"
####################
# - Properties
@ -43,7 +44,7 @@ class BlenderObjectBLSocket(base.BLSocket):
name="Blender Object",
description="Represents a Blender object",
type=bpy.types.Object,
update=(lambda self, context: self.trigger_updates()),
update=(lambda self, context: self.sync_prop("raw_value", context)),
)
####################
@ -57,23 +58,25 @@ class BlenderObjectBLSocket(base.BLSocket):
icon="ADD",
)
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, "raw_value", text="")
####################
# - Default Value
####################
@property
def default_value(self) -> bpy.types.Object | None:
def value(self) -> bpy.types.Object | None:
return self.raw_value
@default_value.setter
def default_value(self, value: bpy.types.Object) -> None:
@value.setter
def value(self, value: bpy.types.Object) -> None:
self.raw_value = value
####################
# - Socket Configuration
####################
class BlenderObjectSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.BlenderObject
label: str
socket_type: ct.SocketType = ct.SocketType.BlenderObject
def init(self, bl_socket: BlenderObjectBLSocket) -> None:
pass

View File

@ -9,27 +9,42 @@ from ... import contracts
####################
# - Blender Socket
####################
class BlenderTextBLSocket(base.BLSocket):
class BlenderTextBLSocket(base.MaxwellSimSocket):
socket_type = contracts.SocketType.BlenderText
bl_label = "BlenderText"
bl_label = "Blender Text"
####################
# - Properties
####################
raw_value: bpy.props.PointerProperty(
name="Blender Text",
description="Represents a Blender text datablock",
type=bpy.types.Text,
update=(lambda self, context: self.sync_prop("raw_value", context)),
)
####################
# - UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, "raw_value", text="")
####################
# - Default Value
####################
@property
def default_value(self) -> None:
pass
def value(self) -> bpy.types.Text:
return self.raw_value
@default_value.setter
def default_value(self, value: typ.Any) -> None:
pass
@value.setter
def value(self, value: bpy.types.Text) -> None:
self.raw_value = value
####################
# - Socket Configuration
####################
class BlenderTextSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.BlenderText
label: str
def init(self, bl_socket: BlenderTextBLSocket) -> None:
pass

View File

@ -1,42 +0,0 @@
import typing as typ
import bpy
import pydantic as pyd
from .. import base
from ... import contracts
####################
# - Blender Socket
####################
class BlenderVolumeBLSocket(base.BLSocket):
socket_type = contracts.SocketType.BlenderVolume
bl_label = "BlenderVolume"
####################
# - Default Value
####################
@property
def default_value(self) -> None:
pass
@default_value.setter
def default_value(self, value: typ.Any) -> None:
pass
####################
# - Socket Configuration
####################
class BlenderVolumeSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.BlenderVolume
label: str
def init(self, bl_socket: BlenderVolumeBLSocket) -> None:
pass
####################
# - Blender Registration
####################
BL_REGISTER = [
BlenderVolumeBLSocket
]

View File

@ -4,7 +4,9 @@ MaxwellBoundBoxSocketDef = bound_box_socket.MaxwellBoundBoxSocketDef
MaxwellBoundFaceSocketDef = bound_face_socket.MaxwellBoundFaceSocketDef
from . import medium_socket
from . import medium_non_linearity_socket
MaxwellMediumSocketDef = medium_socket.MaxwellMediumSocketDef
MaxwellMediumNonLinearitySocketDef = medium_non_linearity_socket.MaxwellMediumNonLinearitySocketDef
from . import source_socket
from . import temporal_shape_socket
@ -20,15 +22,18 @@ MaxwellMonitorSocketDef = monitor_socket.MaxwellMonitorSocketDef
from . import fdtd_sim_socket
from . import sim_grid_socket
from . import sim_grid_axis_socket
from . import sim_domain_socket
MaxwellFDTDSimSocketDef = fdtd_sim_socket.MaxwellFDTDSimSocketDef
MaxwellSimGridSocketDef = sim_grid_socket.MaxwellSimGridSocketDef
MaxwellSimGridAxisSocketDef = sim_grid_axis_socket.MaxwellSimGridAxisSocketDef
MaxwellSimDomainSocketDef = sim_domain_socket.MaxwellSimDomainSocketDef
BL_REGISTER = [
*bound_box_socket.BL_REGISTER,
*bound_face_socket.BL_REGISTER,
*medium_socket.BL_REGISTER,
*medium_non_linearity_socket.BL_REGISTER,
*source_socket.BL_REGISTER,
*temporal_shape_socket.BL_REGISTER,
*structure_socket.BL_REGISTER,
@ -36,4 +41,5 @@ BL_REGISTER = [
*fdtd_sim_socket.BL_REGISTER,
*sim_grid_socket.BL_REGISTER,
*sim_grid_axis_socket.BL_REGISTER,
*sim_domain_socket.BL_REGISTER,
]

View File

@ -5,7 +5,7 @@ import pydantic as pyd
import tidy3d as td
from .. import base
from ... import contracts
from ... import contracts as ct
BOUND_FACE_ITEMS = [
("PML", "PML", "Perfectly matched layer"),
@ -13,98 +13,121 @@ BOUND_FACE_ITEMS = [
("PMC", "PMC", "Perfect magnetic conductor"),
("PERIODIC", "Periodic", "Infinitely periodic layer"),
]
BOUND_MAP = {
"PML": td.PML(),
"PEC": td.PECBoundary(),
"PMC": td.PMCBoundary(),
"PERIODIC": td.Periodic(),
}
class MaxwellBoundBoxBLSocket(base.BLSocket):
socket_type = contracts.SocketType.MaxwellBoundBox
class MaxwellBoundBoxBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.MaxwellBoundBox
bl_label = "Maxwell Bound Box"
compatible_types = {
td.BoundarySpec: {}
}
####################
# - Properties
####################
show_definition: bpy.props.BoolProperty(
name="Show Bounds Definition",
description="Toggle to show bound faces",
default=False,
update=(lambda self, context: self.sync_prop("show_definition", context)),
)
x_pos: bpy.props.EnumProperty(
name="+x Bound Face",
description="+x choice of default boundary face",
items=BOUND_FACE_ITEMS,
default="PML",
update=(lambda self, context: self.trigger_updates()),
update=(lambda self, context: self.sync_prop("x_pos", context)),
)
x_neg: bpy.props.EnumProperty(
name="-x Bound Face",
description="-x choice of default boundary face",
items=BOUND_FACE_ITEMS,
default="PML",
update=(lambda self, context: self.trigger_updates()),
update=(lambda self, context: self.sync_prop("x_neg", context)),
)
y_pos: bpy.props.EnumProperty(
name="+y Bound Face",
description="+y choice of default boundary face",
items=BOUND_FACE_ITEMS,
default="PML",
update=(lambda self, context: self.trigger_updates()),
update=(lambda self, context: self.sync_prop("y_pos", context)),
)
y_neg: bpy.props.EnumProperty(
name="-y Bound Face",
description="-y choice of default boundary face",
items=BOUND_FACE_ITEMS,
default="PML",
update=(lambda self, context: self.trigger_updates()),
update=(lambda self, context: self.sync_prop("y_neg", context)),
)
z_pos: bpy.props.EnumProperty(
name="+z Bound Face",
description="+z choice of default boundary face",
items=BOUND_FACE_ITEMS,
default="PML",
update=(lambda self, context: self.trigger_updates()),
update=(lambda self, context: self.sync_prop("z_pos", context)),
)
z_neg: bpy.props.EnumProperty(
name="-z Bound Face",
description="-z choice of default boundary face",
items=BOUND_FACE_ITEMS,
default="PML",
update=(lambda self, context: self.trigger_updates()),
update=(lambda self, context: self.sync_prop("z_neg", context)),
)
####################
# - UI
####################
def draw_label_row(self, row: bpy.types.UILayout, text) -> None:
row.label(text=text)
row.prop(
self, "show_definition", toggle=True, text="", icon="MOD_LENGTH"
)
def draw_value(self, col: bpy.types.UILayout) -> None:
col.label(text="-/+ x")
col_row = col.row(align=True)
col_row.prop(self, "x_neg", text="")
col_row.prop(self, "x_pos", text="")
if not self.show_definition: return
col.label(text="-/+ y")
col_row = col.row(align=True)
col_row.prop(self, "y_neg", text="")
col_row.prop(self, "y_pos", text="")
col.label(text="-/+ z")
col_row = col.row(align=True)
col_row.prop(self, "z_neg", text="")
col_row.prop(self, "z_pos", text="")
for axis in ["x", "y", "z"]:
row = col.row(align=False)
split = row.split(factor=0.2, align=False)
_col = split.column(align=True)
_col.alignment = "RIGHT"
_col.label(text=axis + " -")
_col.label(text=" +")
_col = split.column(align=True)
_col.prop(self, axis + "_neg", text="")
_col.prop(self, axis + "_pos", text="")
####################
# - Computation of Default Value
####################
@property
def default_value(self) -> td.BoundarySpec:
return td.BoundarySpec()
@default_value.setter
def default_value(self, value: typ.Any) -> None:
return None
def value(self) -> td.BoundarySpec:
return td.BoundarySpec(
x=td.Boundary(
plus=BOUND_MAP[self.x_pos],
minus=BOUND_MAP[self.x_neg],
),
y=td.Boundary(
plus=BOUND_MAP[self.y_pos],
minus=BOUND_MAP[self.y_neg],
),
z=td.Boundary(
plus=BOUND_MAP[self.z_pos],
minus=BOUND_MAP[self.z_neg],
),
)
####################
# - Socket Configuration
####################
class MaxwellBoundBoxSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.MaxwellBoundBox
label: str
socket_type: ct.SocketType = ct.SocketType.MaxwellBoundBox
def init(self, bl_socket: MaxwellBoundBoxBLSocket) -> None:
pass

View File

@ -1,14 +1,15 @@
import typing as typ
import typing_extensions as typx
import bpy
import pydantic as pyd
import tidy3d as td
from .. import base
from ... import contracts
from ... import contracts as ct
class MaxwellBoundFaceBLSocket(base.BLSocket):
socket_type = contracts.SocketType.MaxwellBoundFace
class MaxwellBoundFaceBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.MaxwellBoundFace
bl_label = "Maxwell Bound Face"
####################
@ -24,21 +25,20 @@ class MaxwellBoundFaceBLSocket(base.BLSocket):
("PERIODIC", "Periodic", "Infinitely periodic layer"),
],
default="PML",
update=(lambda self, context: self.trigger_updates()),
update=(lambda self, context: self.sync_prop("default_choice", context)),
)
####################
# - UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col_row = col.row(align=True)
col_row.prop(self, "default_choice", text="")
col.prop(self, "default_choice", text="")
####################
# - Computation of Default Value
####################
@property
def default_value(self) -> td.BoundarySpec:
def value(self) -> td.BoundarySpec:
return {
"PML": td.PML(num_layers=12),
"PEC": td.PECBoundary(),
@ -46,21 +46,20 @@ class MaxwellBoundFaceBLSocket(base.BLSocket):
"PERIODIC": td.Periodic(),
}[self.default_choice]
@default_value.setter
def default_value(self, value: typ.Any) -> None:
return None
@value.setter
def value(self, value: typx.Literal["PML", "PEC", "PMC", "PERIODIC"]) -> None:
self.default_choice = value
####################
# - Socket Configuration
####################
class MaxwellBoundFaceSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.MaxwellBoundFace
label: str
socket_type: ct.SocketType = ct.SocketType.MaxwellBoundFace
default_choice: str = "PML"
default_choice: typx.Literal["PML", "PEC", "PMC", "PERIODIC"] = "PML"
def init(self, bl_socket: MaxwellBoundFaceBLSocket) -> None:
bl_socket.default_choice = self.default_choice
bl_socket.value = self.default_choice
####################
# - Blender Registration

View File

@ -5,33 +5,17 @@ import pydantic as pyd
import tidy3d as td
from .. import base
from ... import contracts
from ... import contracts as ct
class MaxwellFDTDSimBLSocket(base.BLSocket):
socket_type = contracts.SocketType.MaxwellFDTDSim
bl_label = "Maxwell Source"
compatible_types = {
td.Simulation: {},
}
####################
# - Computation of Default Value
####################
@property
def default_value(self) -> None:
return None
@default_value.setter
def default_value(self, value: typ.Any) -> None:
pass
class MaxwellFDTDSimBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.MaxwellFDTDSim
bl_label = "Maxwell FDTD Simulation"
####################
# - Socket Configuration
####################
class MaxwellFDTDSimSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.MaxwellFDTDSim
label: str
socket_type: ct.SocketType = ct.SocketType.MaxwellFDTDSim
def init(self, bl_socket: MaxwellFDTDSimBLSocket) -> None:
pass

View File

@ -0,0 +1,28 @@
import typing as typ
import bpy
import pydantic as pyd
import tidy3d as td
from .. import base
from ... import contracts as ct
class MaxwellMediumNonLinearityBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.MaxwellMediumNonLinearity
bl_label = "Medium Non-Linearity"
####################
# - Socket Configuration
####################
class MaxwellMediumNonLinearitySocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.MaxwellMediumNonLinearity
def init(self, bl_socket: MaxwellMediumNonLinearityBLSocket) -> None:
pass
####################
# - Blender Registration
####################
BL_REGISTER = [
MaxwellMediumNonLinearityBLSocket,
]

View File

@ -2,83 +2,114 @@ import typing as typ
import bpy
import pydantic as pyd
import sympy as sp
import sympy.physics.units as spu
import tidy3d as td
import scipy as sc
from .....utils.pydantic_sympy import ConstrSympyExpr, Complex
from .. import base
from ... import contracts
from ... import contracts as ct
class MaxwellMediumBLSocket(base.BLSocket):
socket_type = contracts.SocketType.MaxwellMedium
VAC_SPEED_OF_LIGHT = (
sc.constants.speed_of_light
* spu.meter/spu.second
)
class MaxwellMediumBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.MaxwellMedium
bl_label = "Maxwell Medium"
compatible_types = {
td.components.medium.AbstractMedium: {}
}
use_units = True
####################
# - Properties
####################
rel_permittivity: bpy.props.FloatProperty(
name="Permittivity",
description="Represents a simple, real permittivity.",
default=0.0,
wl: bpy.props.FloatProperty(
name="WL",
description="WL to evaluate conductivity at",
default=500.0,
precision=4,
update=(lambda self, context: self.trigger_updates()),
step=50,
update=(lambda self, context: self.sync_prop("wl", context)),
)
rel_permittivity: bpy.props.FloatVectorProperty(
name="Relative Permittivity",
description="Represents a simple, complex permittivity",
size=2,
default=(1.0, 0.0),
precision=2,
update=(lambda self, context: self.sync_prop("rel_permittivity", context)),
)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
"""Draw the value of the area, including a toggle for
specifying the active unit.
"""
col_row = col.row(align=True)
col_row.prop(self, "rel_permittivity", text="ϵr")
col.prop(self, "wl", text="λ")
col.separator(factor=1.0)
split = col.split(factor=0.35, align=False)
col = split.column(align=True)
col.label(text="ϵ_r ()")
col = split.column(align=True)
col.prop(self, "rel_permittivity", text="")
####################
# - Computation of Default Value
####################
@property
def default_value(self) -> td.Medium:
"""Return the built-in medium representation as a `tidy3d` object,
ready to use in the simulation.
Returns:
A completely normal medium with permittivity set.
"""
return td.Medium(
permittivity=self.rel_permittivity,
def value(self) -> td.Medium:
freq = spu.convert_to(
VAC_SPEED_OF_LIGHT / (self.wl*self.unit),
spu.hertz,
) / spu.hertz
return td.Medium.from_nk(
n=self.rel_permittivity[0],
k=self.rel_permittivity[1],
freq=freq,
)
@default_value.setter
def default_value(self, value: typ.Any) -> None:
"""Set the built-in medium representation by adjusting the
permittivity, ONLY.
@value.setter
def value(
self,
value: tuple[ConstrSympyExpr(allow_variables=False), complex]
) -> None:
_wl, rel_permittivity = value
Args:
value: Must be a tidy3d.Medium, or similar subclass.
"""
wl = float(
spu.convert_to(
_wl,
self.unit,
) / self.unit
)
self.wl = wl
self.rel_permittivity = (rel_permittivity.real, rel_permittivity.imag)
def sync_unit_change(self):
"""Override unit change to only alter frequency unit."""
# ONLY Allow td.Medium
if isinstance(value, td.Medium):
self.rel_permittivity = value.permittivity
msg = f"Tried setting MaxwellMedium socket ({self}) to something that isn't a simple `tidy3d.Medium`"
raise ValueError(msg)
self.value = (
self.wl * self.prev_unit,
complex(*self.rel_permittivity)
)
self.prev_active_unit = self.active_unit
####################
# - Socket Configuration
####################
class MaxwellMediumSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.MaxwellMedium
label: str
socket_type: ct.SocketType = ct.SocketType.MaxwellMedium
rel_permittivity: float = 1.0
default_permittivity_real: float = 1.0
default_permittivity_imag: float = 0.0
def init(self, bl_socket: MaxwellMediumBLSocket) -> None:
bl_socket.rel_permittivity = self.rel_permittivity
bl_socket.rel_permittivity = (
self.default_permittivity_real, self.default_permittivity_imag
)
####################
# - Blender Registration

View File

@ -1,37 +1,54 @@
import typing as typ
import bpy
import sympy.physics.units as spu
import pydantic as pyd
import tidy3d as td
import scipy as sc
from .. import base
from ... import contracts
from ... import contracts as ct
class MaxwellMonitorBLSocket(base.BLSocket):
socket_type = contracts.SocketType.MaxwellMonitor
bl_label = "Maxwell Bound Box"
compatible_types = {
td.BoundarySpec: {}
}
VAC_SPEED_OF_LIGHT = (
sc.constants.speed_of_light
* spu.meter/spu.second
)
class MaxwellMonitorBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.MaxwellMonitor
bl_label = "Maxwell Monitor"
use_units = True
####################
# - Computation of Default Value
# - Properties
####################
wl: bpy.props.FloatProperty(
name="WL",
description="WL to store in monitor",
default=500.0,
precision=4,
step=50,
update=(lambda self, context: self.sync_prop("wl", context)),
)
@property
def default_value(self) -> td.BoundarySpec:
return td.BoundarySpec()
@default_value.setter
def default_value(self, value: typ.Any) -> None:
return None
def value(self) -> td.Monitor:
freq = spu.convert_to(
VAC_SPEED_OF_LIGHT / (self.wl*self.unit),
spu.hertz,
) / spu.hertz
return td.FieldMonitor(
size=(td.inf, td.inf, 0),
freqs=[freq],
name="fields",
colocate=True,
)
####################
# - Socket Configuration
####################
class MaxwellMonitorSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.MaxwellMonitor
label: str
socket_type: ct.SocketType = ct.SocketType.MaxwellMonitor
def init(self, bl_socket: MaxwellMonitorBLSocket) -> None:
pass

View File

@ -0,0 +1,28 @@
import typing as typ
import bpy
import pydantic as pyd
import tidy3d as td
from .. import base
from ... import contracts as ct
class MaxwellSimDomainBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.MaxwellSimDomain
bl_label = "Sim Domain"
####################
# - Socket Configuration
####################
class MaxwellSimDomainSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.MaxwellSimDomain
def init(self, bl_socket: MaxwellSimDomainBLSocket) -> None:
pass
####################
# - Blender Registration
####################
BL_REGISTER = [
MaxwellSimDomainBLSocket,
]

View File

@ -5,33 +5,17 @@ import pydantic as pyd
import tidy3d as td
from .. import base
from ... import contracts
from ... import contracts as ct
class MaxwellSimGridAxisBLSocket(base.BLSocket):
socket_type = contracts.SocketType.MaxwellSimGridAxis
class MaxwellSimGridAxisBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.MaxwellSimGridAxis
bl_label = "Maxwell Bound Box"
compatible_types = {
td.BoundarySpec: {}
}
####################
# - Computation of Default Value
####################
@property
def default_value(self) -> td.BoundarySpec:
return td.BoundarySpec()
@default_value.setter
def default_value(self, value: typ.Any) -> None:
return None
####################
# - Socket Configuration
####################
class MaxwellSimGridAxisSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.MaxwellSimGridAxis
label: str
socket_type: ct.SocketType = ct.SocketType.MaxwellSimGridAxis
def init(self, bl_socket: MaxwellSimGridAxisBLSocket) -> None:
pass

View File

@ -5,36 +5,56 @@ import pydantic as pyd
import tidy3d as td
from .. import base
from ... import contracts
from ... import contracts as ct
class MaxwellSimGridBLSocket(base.BLSocket):
socket_type = contracts.SocketType.MaxwellSimGrid
bl_label = "Maxwell Bound Box"
class MaxwellSimGridBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.MaxwellSimGrid
bl_label = "Maxwell Sim Grid"
compatible_types = {
td.BoundarySpec: {}
}
####################
# - Properties
####################
min_steps_per_wl: bpy.props.FloatProperty(
name="Minimum Steps per Wavelength",
description="How many grid steps to ensure per wavelength",
default=10.0,
min=0.01,
#step=10,
precision=2,
update=(lambda self, context: self.sync_prop("min_steps_per_wl", context)),
)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
split = col.split(factor=0.5, align=False)
col = split.column(align=True)
col.label(text="min. stp/λ")
col = split.column(align=True)
col.prop(self, "min_steps_per_wl", text="")
####################
# - Computation of Default Value
####################
@property
def default_value(self) -> td.BoundarySpec:
return td.BoundarySpec()
@default_value.setter
def default_value(self, value: typ.Any) -> None:
return None
def value(self) -> td.GridSpec:
return td.GridSpec.auto(
min_steps_per_wvl=self.min_steps_per_wl,
)
####################
# - Socket Configuration
####################
class MaxwellSimGridSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.MaxwellSimGrid
label: str
socket_type: ct.SocketType = ct.SocketType.MaxwellSimGrid
min_steps_per_wl: float = 10.0
def init(self, bl_socket: MaxwellSimGridBLSocket) -> None:
pass
bl_socket.min_steps_per_wl = self.min_steps_per_wl
####################
# - Blender Registration

View File

@ -5,33 +5,17 @@ import pydantic as pyd
import tidy3d as td
from .. import base
from ... import contracts
from ... import contracts as ct
class MaxwellSourceBLSocket(base.BLSocket):
socket_type = contracts.SocketType.MaxwellSource
class MaxwellSourceBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.MaxwellSource
bl_label = "Maxwell Source"
compatible_types = {
td.components.base_sim.source.AbstractSource: {}
}
####################
# - Computation of Default Value
####################
@property
def default_value(self) -> td.Medium:
return None
@default_value.setter
def default_value(self, value: typ.Any) -> None:
pass
####################
# - Socket Configuration
####################
class MaxwellSourceSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.MaxwellSource
label: str
socket_type: ct.SocketType = ct.SocketType.MaxwellSource
def init(self, bl_socket: MaxwellSourceBLSocket) -> None:
pass

View File

@ -2,36 +2,19 @@ import typing as typ
import bpy
import pydantic as pyd
import tidy3d as td
from .. import base
from ... import contracts
from ... import contracts as ct
class MaxwellStructureBLSocket(base.BLSocket):
socket_type = contracts.SocketType.MaxwellStructure
class MaxwellStructureBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.MaxwellStructure
bl_label = "Maxwell Structure"
compatible_types = {
td.components.structure.AbstractStructure: {}
}
####################
# - Computation of Default Value
####################
@property
def default_value(self) -> None:
return None
@default_value.setter
def default_value(self, value: typ.Any) -> None:
pass
####################
# - Socket Configuration
####################
class MaxwellStructureSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.MaxwellStructure
label: str
socket_type: ct.SocketType = ct.SocketType.MaxwellStructure
def init(self, bl_socket: MaxwellStructureBLSocket) -> None:
pass

View File

@ -5,29 +5,17 @@ import pydantic as pyd
import tidy3d as td
from .. import base
from ... import contracts
from ... import contracts as ct
class MaxwellTemporalShapeBLSocket(base.BLSocket):
socket_type = contracts.SocketType.MaxwellTemporalShape
class MaxwellTemporalShapeBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.MaxwellTemporalShape
bl_label = "Maxwell Temporal Shape"
####################
# - Computation of Default Value
####################
@property
def default_value(self) -> td.Medium:
return None
@default_value.setter
def default_value(self, value: typ.Any) -> None:
pass
####################
# - Socket Configuration
####################
class MaxwellTemporalShapeSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.MaxwellTemporalShape
label: str
socket_type: ct.SocketType = ct.SocketType.MaxwellTemporalShape
def init(self, bl_socket: MaxwellTemporalShapeBLSocket) -> None:
pass

View File

@ -4,24 +4,17 @@ import bpy
import sympy as sp
import pydantic as pyd
from .....utils.pydantic_sympy import SympyExpr
from .. import base
from ... import contracts
from ... import contracts as ct
####################
# - Blender Socket
####################
class ComplexNumberBLSocket(base.BLSocket):
socket_type = contracts.SocketType.ComplexNumber
class ComplexNumberBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.ComplexNumber
bl_label = "Complex Number"
compatible_types = {
complex: {},
sp.Expr: {
lambda self, v: v.is_complex,
lambda self, v: len(v.free_symbols) == 0,
},
}
####################
# - Properties
####################
@ -31,7 +24,7 @@ class ComplexNumberBLSocket(base.BLSocket):
size=2,
default=(0.0, 0.0),
subtype='NONE',
update=(lambda self, context: self.trigger_updates()),
update=(lambda self, context: self.sync_prop("raw_value", context)),
)
coord_sys: bpy.props.EnumProperty(
name="Coordinate System",
@ -41,7 +34,7 @@ class ComplexNumberBLSocket(base.BLSocket):
("POLAR", "Polar", "Use Polar Coordinates", "DRIVER_ROTATIONAL_DIFFERENCE", 1),
],
default="CARTESIAN",
update=lambda self, context: self._update_coord_sys(),
update=lambda self, context: self._sync_coord_sys(context),
)
####################
@ -55,34 +48,11 @@ class ComplexNumberBLSocket(base.BLSocket):
col_row.prop(self, "raw_value", text="")
col.prop(self, "coord_sys", text="")
def draw_preview(self, col_box: bpy.types.UILayout) -> None:
"""Draw a live-preview value for the complex number, into the
given preview box.
- Cartesian: a,b -> a + ib
- Polar: r,t -> re^(it)
Returns:
The sympy expression representing the complex number.
"""
if self.coord_sys == "CARTESIAN":
text = f"= {self.default_value.n(2)}"
elif self.coord_sys == "POLAR":
r = sp.Abs(self.default_value).n(2)
theta_rad = sp.arg(self.default_value).n(2)
text = f"= {r*sp.exp(sp.I*theta_rad)}"
else:
raise RuntimeError("Invalid coordinate system for complex number")
col_box.label(text=text)
####################
# - Computation of Default Value
####################
@property
def default_value(self) -> sp.Expr:
def value(self) -> SympyExpr:
"""Return the complex number as a sympy expression, of a form
determined by the coordinate system.
@ -100,8 +70,8 @@ class ComplexNumberBLSocket(base.BLSocket):
"POLAR": v1 * sp.exp(sp.I*v2),
}[self.coord_sys]
@default_value.setter
def default_value(self, value: typ.Any) -> None:
@value.setter
def value(self, value: SympyExpr) -> None:
"""Set the complex number from a sympy expression, using an internal
representation determined by the coordinate system.
@ -122,7 +92,7 @@ class ComplexNumberBLSocket(base.BLSocket):
####################
# - Internal Update Methods
####################
def _update_coord_sys(self):
def _sync_coord_sys(self, context: bpy.types.Context):
if self.coord_sys == "CARTESIAN":
r, theta_rad = self.raw_value
self.raw_value = (
@ -137,20 +107,17 @@ class ComplexNumberBLSocket(base.BLSocket):
sp.arg(cart_value) if y != 0 else 0,
)
self.trigger_updates()
self.sync_prop("coord_sys", context)
####################
# - Socket Configuration
####################
class ComplexNumberSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.ComplexNumber
label: str
socket_type: ct.SocketType = ct.SocketType.ComplexNumber
preview: bool = False
coord_sys: typ.Literal["CARTESIAN", "POLAR"] = "CARTESIAN"
def init(self, bl_socket: ComplexNumberBLSocket) -> None:
bl_socket.preview_active = self.preview
bl_socket.coord_sys = self.coord_sys
####################

View File

@ -4,14 +4,14 @@ import bpy
import pydantic as pyd
from .. import base
from ... import contracts
from ... import contracts as ct
####################
# - Blender Socket
####################
class IntegerNumberBLSocket(base.BLSocket):
socket_type = contracts.SocketType.IntegerNumber
bl_label = "IntegerNumber"
class IntegerNumberBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.IntegerNumber
bl_label = "Integer Number"
####################
# - Properties
@ -20,31 +20,37 @@ class IntegerNumberBLSocket(base.BLSocket):
name="Integer",
description="Represents an integer",
default=0,
update=(lambda self, context: self.trigger_updates()),
update=(lambda self, context: self.sync_prop("raw_value", context)),
)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col_row = col.row()
col_row.prop(self, "raw_value", text="")
####################
# - Default Value
####################
@property
def default_value(self) -> None:
def value(self) -> int:
return self.raw_value
@default_value.setter
def default_value(self, value: typ.Any) -> None:
self.raw_value = int(value)
@value.setter
def value(self, value: int) -> None:
self.raw_value = value
####################
# - Socket Configuration
####################
class IntegerNumberSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.IntegerNumber
label: str
socket_type: ct.SocketType = ct.SocketType.IntegerNumber
default_value: int = 0
def init(self, bl_socket: IntegerNumberBLSocket) -> None:
bl_socket.raw_value = self.default_value
bl_socket.value = self.default_value
####################
# - Blender Registration

View File

@ -1,34 +1,62 @@
import typing as typ
import bpy
import sympy as sp
import pydantic as pyd
from .....utils.pydantic_sympy import SympyExpr
from .. import base
from ... import contracts
from ... import contracts as ct
####################
# - Blender Socket
####################
class RationalNumberBLSocket(base.BLSocket):
socket_type = contracts.SocketType.RationalNumber
class RationalNumberBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.RationalNumber
bl_label = "Rational Number"
####################
# - Properties
####################
raw_value: bpy.props.IntVectorProperty(
name="Rational Number",
description="Represents a rational number (int / int)",
size=2,
default=(1, 1),
subtype='NONE',
update=(lambda self, context: self.sync_prop("raw_value", context)),
)
####################
# - UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col_row = col.row(align=True)
col_row.prop(self, "raw_value", text="")
####################
# - Default Value
####################
@property
def default_value(self) -> None:
pass
def value(self) -> sp.Rational:
p, q = self.raw_value
return sp.Rational(p, q)
@default_value.setter
def default_value(self, value: typ.Any) -> None:
pass
@value.setter
def value(self, value: float | tuple[int, int] | SympyExpr) -> None:
if isinstance(value, float):
approx_rational = sp.nsimplify(value)
self.raw_value = (approx_rational.p, approx_rational.q)
elif isinstance(value, tuple):
self.raw_value = value
else:
self.raw_value = (value.p, value.q)
####################
# - Socket Configuration
####################
class RationalNumberSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.RationalNumber
label: str
socket_type: ct.SocketType = ct.SocketType.RationalNumber
def init(self, bl_socket: RationalNumberBLSocket) -> None:
pass

View File

@ -4,24 +4,17 @@ import bpy
import sympy as sp
import pydantic as pyd
from .....utils.pydantic_sympy import SympyExpr
from .. import base
from ... import contracts
from ... import contracts as ct
####################
# - Blender Socket
####################
class RealNumberBLSocket(base.BLSocket):
socket_type = contracts.SocketType.RealNumber
class RealNumberBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.RealNumber
bl_label = "Real Number"
compatible_types = {
float: {},
sp.Expr: {
lambda self, v: v.is_real,
lambda self, v: len(v.free_symbols) == 0,
},
}
####################
# - Properties
####################
@ -30,46 +23,40 @@ class RealNumberBLSocket(base.BLSocket):
description="Represents a real number",
default=0.0,
precision=6,
update=(lambda self, context: self.trigger_updates()),
update=(lambda self, context: self.sync_prop("raw_value", context)),
)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col_row = col.row()
col_row.prop(self, "raw_value", text="")
####################
# - Computation of Default Value
####################
@property
def default_value(self) -> float:
"""Return the real number.
Returns:
The real number as a float.
"""
def value(self) -> float:
return self.raw_value
@default_value.setter
def default_value(self, value: typ.Any) -> None:
"""Set the real number from some compatible type, namely
real sympy expressions with no symbols, or floats.
"""
# (Guard) Value Compatibility
if not self.is_compatible(value):
msg = f"Tried setting socket ({self}) to incompatible value ({value}) of type {type(value)}"
raise ValueError(msg)
self.raw_value = float(value)
@value.setter
def value(self, value: float | SympyExpr) -> None:
if isinstance(value, float):
self.raw_value = value
else:
float(value.n())
####################
# - Socket Configuration
####################
class RealNumberSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.RealNumber
label: str
socket_type: ct.SocketType = ct.SocketType.RealNumber
default_value: float = 0.0
def init(self, bl_socket: RealNumberBLSocket) -> None:
bl_socket.default_value = self.default_value
bl_socket.value = self.default_value
####################
# - Blender Registration

View File

@ -34,14 +34,7 @@ from . import pol_socket
PhysicalPolSocketDef = pol_socket.PhysicalPolSocketDef
from . import freq_socket
from . import vac_wl_socket
PhysicalFreqSocketDef = freq_socket.PhysicalFreqSocketDef
PhysicalVacWLSocketDef = vac_wl_socket.PhysicalVacWLSocketDef
from . import spec_rel_permit_dist_socket
from . import spec_power_dist_socket
PhysicalSpecRelPermDistSocketDef = spec_rel_permit_dist_socket.PhysicalSpecRelPermDistSocketDef
PhysicalSpecPowerDistSocketDef = spec_power_dist_socket.PhysicalSpecPowerDistSocketDef
BL_REGISTER = [
@ -68,7 +61,4 @@ BL_REGISTER = [
*pol_socket.BL_REGISTER,
*freq_socket.BL_REGISTER,
*vac_wl_socket.BL_REGISTER,
*spec_rel_permit_dist_socket.BL_REGISTER,
*spec_power_dist_socket.BL_REGISTER,
]

View File

@ -1,17 +1,19 @@
import typing as typ
import bpy
import sympy.physics.units as spu
import pydantic as pyd
from .....utils.pydantic_sympy import SympyExpr
from .. import base
from ... import contracts
from ... import contracts as ct
####################
# - Blender Socket
####################
class PhysicalAccelScalarBLSocket(base.BLSocket):
socket_type = contracts.SocketType.PhysicalAccelScalar
bl_label = "PhysicalAccel"
class PhysicalAccelScalarBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.PhysicalAccelScalar
bl_label = "Accel Scalar"
use_units = True
####################
@ -22,28 +24,33 @@ class PhysicalAccelScalarBLSocket(base.BLSocket):
description="Represents the unitless part of the acceleration",
default=0.0,
precision=6,
update=(lambda self, context: self.trigger_updates()),
update=(lambda self, context: self.sync_prop("raw_value", context)),
)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, "raw_value", text="")
####################
# - Default Value
####################
@property
def default_value(self) -> None:
def value(self) -> SympyExpr:
return self.raw_value * self.unit
@default_value.setter
def default_value(self, value: typ.Any) -> None:
self.raw_value = self.value_as_unit(value)
@value.setter
def value(self, value: SympyExpr) -> None:
self.raw_value = spu.convert_to(value, self.unit) / self.unit
####################
# - Socket Configuration
####################
class PhysicalAccelScalarSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.PhysicalAccelScalar
label: str
socket_type: ct.SocketType = ct.SocketType.PhysicalAccelScalar
default_unit: typ.Any | None = None
default_unit: SympyExpr | None = None
def init(self, bl_socket: PhysicalAccelScalarBLSocket) -> None:
if self.default_unit:

View File

@ -1,17 +1,19 @@
import typing as typ
import bpy
import sympy.physics.units as spu
import pydantic as pyd
from .....utils.pydantic_sympy import SympyExpr
from .. import base
from ... import contracts
from ... import contracts as ct
####################
# - Blender Socket
####################
class PhysicalAngleBLSocket(base.BLSocket):
socket_type = contracts.SocketType.PhysicalAngle
bl_label = "PhysicalAngle"
class PhysicalAngleBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.PhysicalAngle
bl_label = "Physical Angle"
use_units = True
####################
@ -22,28 +24,33 @@ class PhysicalAngleBLSocket(base.BLSocket):
description="Represents the unitless part of the acceleration",
default=0.0,
precision=4,
update=(lambda self, context: self.trigger_updates()),
update=(lambda self, context: self.sync_prop("raw_value", context)),
)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, "raw_value", text="")
####################
# - Default Value
####################
@property
def default_value(self) -> None:
def value(self) -> SympyExpr:
return self.raw_value * self.unit
@default_value.setter
def default_value(self, value: typ.Any) -> None:
self.raw_value = self.value_as_unit(value)
@value.setter
def value(self, value: SympyExpr) -> None:
self.raw_value = spu.convert_to(value, self.unit) / self.unit
####################
# - Socket Configuration
####################
class PhysicalAngleSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.PhysicalAngle
label: str
socket_type: ct.SocketType = ct.SocketType.PhysicalAngle
default_unit: typ.Any | None = None
default_unit: SympyExpr | None = None
def init(self, bl_socket: PhysicalAngleBLSocket) -> None:
if self.default_unit:

View File

@ -5,25 +5,15 @@ import sympy as sp
import sympy.physics.units as spu
import pydantic as pyd
from .....utils.pydantic_sympy import SympyExpr
from .. import base
from ... import contracts
from ... import contracts as ct
class PhysicalAreaBLSocket(base.BLSocket):
socket_type = contracts.SocketType.PhysicalArea
class PhysicalAreaBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.PhysicalArea
bl_label = "Physical Area"
use_units = True
compatible_types = {
sp.Expr: {
lambda self, v: v.is_real,
lambda self, v: len(v.free_symbols) == 0,
lambda self, v: any(
contracts.is_exactly_expressed_as_unit(v, unit)
for unit in self.units.values()
)
},
}
####################
# - Properties
####################
@ -32,19 +22,14 @@ class PhysicalAreaBLSocket(base.BLSocket):
description="Represents the unitless part of the area",
default=0.0,
precision=6,
update=(lambda self, context: self.trigger_updates()),
update=(lambda self, context: self.sync_prop("raw_value", context)),
)
####################
# - Socket UI
####################
def draw_label_row(self, label_col_row: bpy.types.UILayout, text: str) -> None:
label_col_row.label(text=text)
label_col_row.prop(self, "raw_unit", text="")
def draw_value(self, col: bpy.types.UILayout) -> None:
col_row = col.row(align=True)
col_row.prop(self, "raw_value", text="")
col.prop(self, "raw_value", text="")
####################
# - Computation of Default Value
@ -73,8 +58,7 @@ class PhysicalAreaBLSocket(base.BLSocket):
# - Socket Configuration
####################
class PhysicalAreaSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.PhysicalArea
label: str
socket_type: ct.SocketType = ct.SocketType.PhysicalArea
default_unit: typ.Any | None = None

View File

@ -1,17 +1,19 @@
import typing as typ
import bpy
import sympy.physics.units as spu
import pydantic as pyd
from .....utils.pydantic_sympy import SympyExpr
from .. import base
from ... import contracts
from ... import contracts as ct
####################
# - Blender Socket
####################
class PhysicalForceScalarBLSocket(base.BLSocket):
socket_type = contracts.SocketType.PhysicalForceScalar
bl_label = "PhysicalForceScalar"
class PhysicalForceScalarBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.PhysicalForceScalar
bl_label = "Force Scalar"
use_units = True
####################
@ -22,28 +24,33 @@ class PhysicalForceScalarBLSocket(base.BLSocket):
description="Represents the unitless part of the force",
default=0.0,
precision=6,
update=(lambda self, context: self.trigger_updates()),
update=(lambda self, context: self.sync_prop("raw_value", context)),
)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, "raw_value", text="")
####################
# - Default Value
####################
@property
def default_value(self) -> None:
def value(self) -> SympyExpr:
return self.raw_value * self.unit
@default_value.setter
def default_value(self, value: typ.Any) -> None:
self.raw_value = self.value_as_unit(value)
@value.setter
def value(self, value: SympyExpr) -> None:
self.raw_value = spu.convert_to(value, self.unit) / self.unit
####################
# - Socket Configuration
####################
class PhysicalForceScalarSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.PhysicalForceScalar
label: str
socket_type: ct.SocketType = ct.SocketType.PhysicalForceScalar
default_unit: typ.Any | None = None
default_unit: SympyExpr | None = None
def init(self, bl_socket: PhysicalForceScalarBLSocket) -> None:
if self.default_unit:

View File

@ -1,17 +1,19 @@
import typing as typ
import bpy
import sympy.physics.units as spu
import pydantic as pyd
from .....utils.pydantic_sympy import SympyExpr
from .. import base
from ... import contracts
from ... import contracts as ct
####################
# - Blender Socket
####################
class PhysicalFreqBLSocket(base.BLSocket):
socket_type = contracts.SocketType.PhysicalFreq
bl_label = "PhysicalFreq"
class PhysicalFreqBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.PhysicalFreq
bl_label = "Frequency"
use_units = True
####################
@ -22,29 +24,40 @@ class PhysicalFreqBLSocket(base.BLSocket):
description="Represents the unitless part of the frequency",
default=0.0,
precision=6,
update=(lambda self, context: self.trigger_updates()),
update=(lambda self, context: self.sync_prop("raw_value", context)),
)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, "raw_value", text="")
####################
# - Default Value
####################
@property
def default_value(self) -> None:
def value(self) -> SympyExpr:
return self.raw_value * self.unit
@default_value.setter
def default_value(self, value: typ.Any) -> None:
self.raw_value = self.value_as_unit(value)
@value.setter
def value(self, value: SympyExpr) -> None:
self.raw_value = spu.convert_to(value, self.unit) / self.unit
####################
# - Socket Configuration
####################
class PhysicalFreqSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.PhysicalFreq
label: str
socket_type: ct.SocketType = ct.SocketType.PhysicalFreq
default_value: SympyExpr | None = None
default_unit: SympyExpr | None = None
def init(self, bl_socket: PhysicalFreqBLSocket) -> None:
pass
if self.default_value:
bl_socket.value = self.default_value
if self.default_unit:
bl_socket.unit = self.default_unit
####################
# - Blender Registration

View File

@ -1,16 +1,18 @@
import typing as typ
import bpy
import sympy.physics.units as spu
import pydantic as pyd
from .....utils.pydantic_sympy import SympyExpr
from .. import base
from ... import contracts
from ... import contracts as ct
####################
# - Blender Socket
####################
class PhysicalLengthBLSocket(base.BLSocket):
socket_type = contracts.SocketType.PhysicalLength
class PhysicalLengthBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.PhysicalLength
bl_label = "PhysicalLength"
use_units = True
@ -22,28 +24,33 @@ class PhysicalLengthBLSocket(base.BLSocket):
description="Represents the unitless part of the force",
default=0.0,
precision=6,
update=(lambda self, context: self.trigger_updates()),
update=(lambda self, context: self.sync_prop("raw_value", context)),
)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, "raw_value", text="")
####################
# - Default Value
####################
@property
def default_value(self) -> None:
def value(self) -> SympyExpr:
return self.raw_value * self.unit
@default_value.setter
def default_value(self, value: typ.Any) -> None:
self.raw_value = self.value_as_unit(value)
@value.setter
def value(self, value: SympyExpr) -> None:
self.raw_value = spu.convert_to(value, self.unit) / self.unit
####################
# - Socket Configuration
####################
class PhysicalLengthSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.PhysicalLength
label: str
socket_type: ct.SocketType = ct.SocketType.PhysicalLength
default_unit: typ.Any | None = None
default_unit: SympyExpr | None = None
def init(self, bl_socket: PhysicalLengthBLSocket) -> None:
if self.default_unit:

View File

@ -1,34 +1,57 @@
import typing as typ
import bpy
import sympy.physics.units as spu
import pydantic as pyd
from .....utils.pydantic_sympy import SympyExpr
from .. import base
from ... import contracts
from ... import contracts as ct
####################
# - Blender Socket
####################
class PhysicalMassBLSocket(base.BLSocket):
socket_type = contracts.SocketType.PhysicalMass
bl_label = "PhysicalMass"
class PhysicalMassBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.PhysicalMass
bl_label = "Mass"
use_units = True
####################
# - Properties
####################
raw_value: bpy.props.FloatProperty(
name="Unitless Mass",
description="Represents the unitless part of mass",
default=0.0,
precision=6,
update=(lambda self, context: self.sync_prop("raw_value", context)),
)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, "raw_value", text="")
####################
# - Default Value
####################
@property
def default_value(self) -> None:
pass
def value(self) -> SympyExpr:
return self.raw_value * self.unit
@default_value.setter
def default_value(self, value: typ.Any) -> None:
pass
@value.setter
def value(self, value: SympyExpr) -> None:
self.raw_value = spu.convert_to(value, self.unit) / self.unit
####################
# - Socket Configuration
####################
class PhysicalMassSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.PhysicalMass
label: str
socket_type: ct.SocketType = ct.SocketType.PhysicalMass
default_unit: SympyExpr | None = None
def init(self, bl_socket: PhysicalMassBLSocket) -> None:
pass

View File

@ -5,25 +5,15 @@ import sympy as sp
import sympy.physics.units as spu
import pydantic as pyd
from .....utils.pydantic_sympy import SympyExpr
from .. import base
from ... import contracts
from ... import contracts as ct
class PhysicalPoint3DBLSocket(base.BLSocket):
socket_type = contracts.SocketType.PhysicalPoint3D
bl_label = "Physical Volume"
class PhysicalPoint3DBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.PhysicalPoint3D
bl_label = "Volume"
use_units = True
compatible_types = {
sp.Expr: {
lambda self, v: v.is_real,
lambda self, v: len(v.free_symbols) == 0,
lambda self, v: any(
contracts.is_exactly_expressed_as_unit(v, unit)
for unit in self.units.values()
)
},
}
####################
# - Properties
####################
@ -33,26 +23,31 @@ class PhysicalPoint3DBLSocket(base.BLSocket):
size=3,
default=(0.0, 0.0, 0.0),
precision=4,
update=(lambda self, context: self.trigger_updates()),
update=(lambda self, context: self.sync_prop("raw_value", context)),
)
####################
# - Computation of Default Value
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, "raw_value", text="")
####################
# - Default Value
####################
@property
def default_value(self) -> sp.Expr:
def value(self) -> sp.MatrixBase:
return sp.Matrix(tuple(self.raw_value)) * self.unit
@default_value.setter
def default_value(self, value: typ.Any) -> None:
self.raw_value = self.value_as_unit(value)
@value.setter
def value(self, value: SympyExpr) -> None:
self.raw_value = tuple(spu.convert_to(value, self.unit) / self.unit)
####################
# - Socket Configuration
####################
class PhysicalPoint3DSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.PhysicalPoint3D
label: str
socket_type: ct.SocketType = ct.SocketType.PhysicalPoint3D
default_unit: typ.Any | None = None

Some files were not shown because too many files have changed in this diff Show More