refactor: Continuing large-scale alterations.

The big news is that GeoNodes Structure is now implemented,
under the new and vastly more robust chaining system.

Upload to Tidy3D cloud is tested. Next is Monitors!
blender-plugin-mvp
Sofus Albert Høgsbro Rose 2024-03-11 16:35:41 +01:00
parent 1ebb57cff7
commit 134bf0c358
Signed by: so-rose
GPG Key ID: AD901CB0F3701434
98 changed files with 1665 additions and 1320 deletions

52
code/FUTURE.md 100644
View File

@ -0,0 +1,52 @@
# Projects / Plugins
## Larger Architectural Changes
[ ] Dedicated way of generating properties for sockets and nodes, incl. helping make better (less boilerplatey) use of callbacks
- Perhaps, we should also go 100% custom `PropertyGroup`, to the point that we have `nodes`, `sockets` and `props`.
- This would allow far simplified sockets (the more complex kinds), and help standardize use of ex. units in node properties.
- Having a dedicated base class for custom props would help avoid issues like forgetting to run `self.sync_prop` on every goddamn update method in every goddamn socket.
[ ] Dedicated way of handling node-specific operators without all the boilerplate.
## 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
[ ] Node tree header toggle that toggles a modal operator on and off, which constantly checks the context of the selected nodes, and tries to `bl_select` them (which in turn, should cause the node base class to `bl_select` shit inside).
- Shouldn't survive a file save; always startup with this thing off.
[ ] 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
[ ] Heat Solver
[ ] 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

@ -1,18 +1,24 @@
# Nodes # Nodes
**LEGEND**:
- [-] Exists but doesn't quite work good enough.
- [x] Done to working degree (the standard is "good enough for the demo").
- See check marks underneath
- [?] Unsure whether we should do this.
## Inputs ## Inputs
[x] Wave Constant [x] Wave Constant
- [ ] Implement export of frequency / wavelength ranges. - [ ] Implement export of frequency / wavelength array/range.
[ ] Unit System [-] Unit System
- [ ] Implement presets, including "Tidy3D" and "Blender", shown in the label row. - [ ] Implement presets, including "Tidy3D" and "Blender", shown in the label row.
[ ] Constants / Blender Constant [ ] Constants / Scientific Constant
[ ] Constants / Number Constant [x] Constants / Number Constant
[ ] Constants / Physical Constant [ ] Constants / Physical Constant
- [ ] Pol: Elliptical plot viz - [ ] Pol: Elliptical plot viz
- [ ] Pol: Poincare sphere viz - [ ] Pol: Poincare sphere viz
[ ] Constants / Scientific Constant [x] Constants / Blender Constant
[ ] Web / Tidy3D Web Importer [x] Web / Tidy3D Web Importer
[ ] File Import / JSON File Import [ ] File Import / JSON File Import
- [ ] Dropdown to choose various supported JSON-sourced objects incl. - [ ] Dropdown to choose various supported JSON-sourced objects incl.
@ -25,12 +31,14 @@
- [ ] Implement a LazyValue to provide a data path that avoids having to load massive arrays every time always. - [ ] Implement a LazyValue to provide a data path that avoids having to load massive arrays every time always.
## Outputs ## Outputs
[ ] Viewer [x] Viewer
- [ ] **BIG ONE**: Remove image preview when disabling plots.
- [ ] Either enforce singleton, or find a way to have several viewers at the same time.
- [ ] A setting that live-previews just a value. - [ ] A setting that live-previews just a value.
- [ ] Pop-up multiline string print as alternative to console print. - [ ] Pop-up multiline string print as alternative to console print.
- [ ] Toggleable auto-plot, auto-3D-preview, auto-value-view, (?)auto-text-view. - [x] Toggleable auto-plot, auto-3D-preview, auto-value-view, (?)auto-text-view.
[ ] File Export / JSON File Export [x] File Export / JSON File Export
[ ] File Import / Tidy3D File Export [ ] File Import / Tidy3D File Export
- [ ] Implement HDF-based export of Tidy3D-exported object (which includes ex. mesh data and such) - [ ] Implement HDF-based export of Tidy3D-exported object (which includes ex. mesh data and such)
[ ] File Export / Array File Export [ ] File Export / Array File Export
@ -41,16 +49,18 @@
## Viz ## Viz
[ ] Monitor Data 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>) - [ ] 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>)
- [ ] Project field values onto a plane object (managed)
## Sources ## Sources
[ ] Temporal Shapes / Gaussian Pulse Temporal Shape [x] Temporal Shapes / Gaussian Pulse Temporal Shape
[ ] Temporal Shapes / Continuous Wave Temporal Shape [x] Temporal Shapes / Continuous Wave Temporal Shape
[ ] Temporal Shapes / Symbolic Temporal Shape [ ] Temporal Shapes / Symbolic Temporal Shape
- [ ] Specify a Sympy function to generate appropriate array based on - [ ] Specify a Sympy function to generate appropriate array based on
[ ] Temporal Shapes / Array Temporal Shape [ ] Temporal Shapes / Array Temporal Shape
[ ] Point Dipole Source [x] Point Dipole Source
[ ] Plane Wave Source - [ ] Consider a "real" mesh - the empty kind of gets stuck inside of the sim domain.
[-] Plane Wave Source
- [ ] Implement an oriented vector input with 3D preview. - [ ] Implement an oriented vector input with 3D preview.
[ ] Uniform Current Source [ ] Uniform Current Source
[ ] TFSF Source [ ] TFSF Source
@ -84,8 +94,9 @@
## Structures ## Structures
[ ] BLObject Structure [ ] BLObject Structure
[ ] GeoNodes Structure [x] GeoNodes Structure
- [ ] Use the modifier itself as memory, via the ManagedObj - [x] Rewrite the `bl_socket_map.py`
- [x] 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. - [?] 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 / Plane
@ -120,11 +131,11 @@
## Simulations ## Simulations
[-] FDTDSim [-] FDTDSim
[-] Sim Domain [x] Sim Domain
- [ ] By-Medium batching of Structures when building the td.Simulation object, which can have significant performance implications. - [ ] By-Medium batching of Structures when building the td.Simulation object, which can have significant performance implications.
[-] Boundary Conds [x] Boundary Conds
- [ ] Rename from Bounds / BoundBox - [x] Rename from Bounds / BoundBox
[ ] Boundary Cond / PML Bound Face [ ] Boundary Cond / PML Bound Face
- [ ] Implement dropdown for "Normal" and "Stable" - [ ] Implement dropdown for "Normal" and "Stable"
[ ] Boundary Cond / PEC Bound Face [ ] Boundary Cond / PEC Bound Face
@ -174,36 +185,59 @@
# Benchmark / Example Sims # Benchmark / Example Sims
- [ ] Tunable Chiral Metasurface <https://docs.flexcompute.com/projects/tidy3d/en/latest/notebooks/TunableChiralMetasurface.html> [ ] Research-Grade Experiment
- Membrane 15nm thickness suspended in air
- Square lattice of holes period 900nm (900nm between each hole, air inside holes)
- Holes square radius 100nm
- Square lattice
- Analysis of transmission
- Guided mode resonance
[ ] Tunable Chiral Metasurface <https://docs.flexcompute.com/projects/tidy3d/en/latest/notebooks/TunableChiralMetasurface.html>
# Sockets # Sockets
## Basic ## Basic
[ ] Any [x] Any
[ ] Bool [x] Bool
[ ] String [x] String
- [ ] Rename from "Text" - [ ] Rename from "Text"
[ ] File Path [x] File Path
[x] Color
## Number
[x] Integer
[x] Rational
- [ ] Implement constrained SympyExpr check for Rational.
[x] Real
- [ ] Implement min/max for ex. 0..1 factor support.
- [ ] Implement constrained SympyExpr check for Rational.
[x] Complex
## Blender ## Blender
[ ] Object [x] Object
[ ] Collection - [ ] Implement default SocketDef object name
[x] Collection
- [ ] Implement default SocketDef collection name
[ ] Image [x] Image
- [ ] Implement default SocketDef image name
[ ] GeoNodes [x] GeoNodes
[ ] Text - [ ] Implement default SocketDef geonodes name
[x] Text
- [ ] Implement default SocketDef object name
## Maxwell ## Maxwell
[ ] Bound Conds [x] Bound Conds
[ ] Bound Cond [ ] Bound Cond
[ ] Medium [x] Medium
[ ] Medium Non-Linearity [ ] Medium Non-Linearity
[ ] Source [x] Source
[ ] Temporal Shape [ ] Temporal Shape
- [ ] Sane-default pulses for easy access.
[ ] Structure [ ] Structure
[ ] Monitor [ ] Monitor
@ -217,47 +251,46 @@
[ ] Simulation Data [ ] Simulation Data
## Tidy3D ## Tidy3D
[ ] Cloud Task [x] Cloud Task
- [ ] Implement switcher for API-key-having config filconfig file vs. direct entry of API key. It should be auto-filled with the config file when such a thing exists.
## Number
[ ] Integer
[ ] Rational
[ ] Real
[ ] Complex
## Physical ## Physical
[ ] Unit System [x] Unit System
- [ ] Implement more comprehensible UI; honestly, probably with the new panels (<https://developer.blender.org/docs/release_notes/4.1/python_api/>) - [ ] Implement more comprehensible UI; honestly, probably with the new panels (<https://developer.blender.org/docs/release_notes/4.1/python_api/>)
[ ] Time [x] Time
[ ] Angle [x] Angle
[ ] Solid Angle (steradian) [ ] Solid Angle (steradian)
[ ] Frequency (hertz) [x] Frequency (hertz)
[ ] Angular Frequency (`rad*hertz`) [ ] Angular Frequency (`rad*hertz`)
### Cartesian ### Cartesian
[ ] Length [x] Length
[ ] Area [x] Area
[ ] Volume [x] Volume
[ ] Point 1D [ ] Point 1D
[ ] Point 2D [ ] Point 2D
[ ] Point 3D [x] Point 3D
[ ] Size 2D [ ] Size 2D
[ ] Size 3D [x] Size 3D
[ ] Rotation 3D
- [ ] Implement Euler methods
- [ ] Implement Quaternion methods
### Mechanical ### Mechanical
[ ] Mass [ ] Mass
[ ] Speed [x] Speed
[ ] Velocity 3D [ ] Velocity 3D
[ ] Acceleration Scalar [x] Acceleration Scalar
[ ] Acceleration 3D [ ] Acceleration 3D
[ ] Force Scalar [x] Force Scalar
[ ] Force 3D [ ] Force 3D
[ ] Pressure [ ] Pressure
### Statistical ### Energy
[ ] Energy (joule) [ ] Energy (joule)
[ ] Power (watt) [ ] Power (watt)
[ ] Temperature [ ] Temperature
@ -283,7 +316,7 @@
[ ] Illuminance (lux) [ ] Illuminance (lux)
### Optical ### Optical
[ ] Jones Polarization [ ] Jones Polarization
[ ] Polarization [ ] Polarization (Stokes)
@ -295,18 +328,21 @@
# Architecture # Architecture
## Registration and Contracts ## Registration and Contracts
[ ] Finish the contract code converting from Blender sockets to our sockets based on dimensionality and the property description. [x] 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. [ ] Refactor the node category code; it's ugly.
[?] Would be nice with some kind of indicator somewhere to help set good socket descriptions when using geonodes and wanting units. [?] Would be nice with some kind of indicator somewhere to help set good socket descriptions when using geonodes and wanting units.
## Managed Objects ## Managed Objects
[ ] Implement modifier support on the managed BL object, with special attention paid to the needs of the GeoNodes socket. [x] 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 - [x] 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. - 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 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 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). [ ] 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).
## Utils or Services
[ ] Dedicated module for managing the interaction with the tidy3d cloud, to help nuke all the random caches out of existance.
## Node Base Class ## Node Base Class
[ ] Dedicated `draw_preview`-type draw functions for plot customizations. [ ] Dedicated `draw_preview`-type draw functions for plot customizations.
- [ ] For now, previewing isn't something I think should be part of the node - [ ] For now, previewing isn't something I think should be part of the node
@ -314,8 +350,9 @@
[ ] 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 [ ] 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). [ ] 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 dynamic names (ex. "Library Medium" becoming "Au Medium")
[ ] Mechanism for selecting a blender object managed by a particular node. [-] 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. [ ] Mechanism for ex. specially coloring a node that is currently participating in the preview.
[ ] Custom callbacks when deleting a node (in `free()`), to ex. delete all previews with the viewer node.
## Socket Base Class ## 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. [ ] 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.
@ -352,46 +389,3 @@
[ ] Test on Windows [ ] Test on Windows
## Node Tree Cache Semantics ## 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

@ -0,0 +1,251 @@
import typing as typ
import typing_extensions as typx
import pydantic as pyd
import sympy as sp
import sympy.physics.units as spu
import bpy
from ...utils import extra_sympy_units as spuex
from . import contracts as ct
from .contracts import SocketType as ST
from . import sockets as sck
# TODO: Caching?
# TODO: Move the manual labor stuff to contracts
BLSocketType = str ## A Blender-Defined Socket Type
BLSocketSize = int
DescType = str
Unit = typ.Any ## Type of a valid unit
####################
# - Socket to SocketDef
####################
SOCKET_DEFS = {
socket_type: getattr(
sck,
socket_type.value.removesuffix("SocketType") + "SocketDef",
)
for socket_type in ST
if hasattr(
sck,
socket_type.value.removesuffix("SocketType") + "SocketDef"
)
}
## TODO: Bit of a hack. Is it robust enough?
for socket_type in ST:
if not hasattr(
sck,
socket_type.value.removesuffix("SocketType") + "SocketDef",
):
print("Missing SocketDef for", socket_type.value)
####################
# - BL Socket Size Parser
####################
BL_SOCKET_3D_TYPE_PREFIXES = {
"NodeSocketVector",
"NodeSocketRotation",
}
BL_SOCKET_4D_TYPE_PREFIXES = {
"NodeSocketColor",
}
def size_from_bl_interface_socket(
bl_interface_socket: bpy.types.NodeTreeInterfaceSocket
) -> typx.Literal[1, 2, 3, 4]:
"""Parses the `size`, aka. number of elements, contained within the `default_value` of a Blender interface socket.
Since there are no 2D sockets in Blender, the user can specify "2D" in the Blender socket's description to "promise" that only the first two values will be used.
When this is done, the third value is left entirely untouched by this entire system.
A hard-coded set of NodeSocket<Type> prefixes are used to determine which interface sockets are, in fact, 3D.
- For 3D sockets, a hard-coded list of Blender node socket types is used.
- Else, it is a 1D socket type.
"""
if bl_interface_socket.description.startswith("2D"): return 2
if any(
bl_interface_socket.socket_type.startswith(bl_socket_3d_type_prefix)
for bl_socket_3d_type_prefix in BL_SOCKET_3D_TYPE_PREFIXES
):
return 3
if any(
bl_interface_socket.socket_type.startswith(bl_socket_4d_type_prefix)
for bl_socket_4d_type_prefix in BL_SOCKET_4D_TYPE_PREFIXES
):
return 4
return 1
####################
# - BL Socket Type / Unit Parser
####################
def parse_bl_interface_socket(
bl_interface_socket: bpy.types.NodeTreeInterfaceSocket,
) -> tuple[ST, sp.Expr | None]:
"""Parse a Blender interface socket by parsing its description, falling back to any direct type links.
Arguments:
bl_interface_socket: An interface socket associated with the global input to a node tree.
Returns:
The type of a corresponding MaxwellSimSocket, as well as a unit (if a particular unit was requested by the Blender interface socket).
"""
size = size_from_bl_interface_socket(bl_interface_socket)
# Determine Direct Socket Type
if (
direct_socket_type := ct.BL_SOCKET_DIRECT_TYPE_MAP.get(
(bl_interface_socket.socket_type, size)
)
) is None:
msg = "Blender interface socket has no mapping among 'MaxwellSimSocket's."
raise ValueError(msg)
# (Maybe) Return Direct Socket Type
## When there's no description, that's it; return.
if not ct.BL_SOCKET_DESCR_ANNOT_STRING in bl_interface_socket.description:
return (direct_socket_type, None)
# Parse Description for Socket Type
tokens = (
_tokens
if (_tokens := bl_interface_socket.description.split(" "))[0] != "2D"
else _tokens[1:]
) ## Don't include the "2D" token, if defined.
if (
socket_type := ct.BL_SOCKET_DESCR_TYPE_MAP.get(
(tokens[0], bl_interface_socket.socket_type, size)
)
) is None:
return (direct_socket_type, None) ## Description doesn't map to anything
# Determine Socket Unit (to use instead of "unit system")
## This is entirely OPTIONAL
socket_unit = None
if socket_type in ct.SOCKET_UNITS:
## Case: Unit is User-Defined
if len(tokens) > 1 and "(" in tokens[1] and ")" in tokens[1]:
# Compute (<unit_str>) as Unit Token
unit_token = tokens[1].removeprefix("(").removesuffix(")")
# Compare Unit Token to Valid Sympy-Printed Units
socket_unit = _socket_unit if (_socket_unit := [
unit
for unit in ct.SOCKET_UNITS[socket_type]["values"].values()
if str(unit) == unit_token
]) else ct.SOCKET_UNITS[socket_type]["values"][
ct.SOCKET_UNITS[socket_type]["default"]
]
## TODO: Enforce abbreviated sympy printing here, not globally
return (socket_type, socket_unit)
####################
# - BL Socket Interface Definition
####################
def socket_def_from_bl_interface_socket(
bl_interface_socket: bpy.types.NodeTreeInterfaceSocket,
):
"""Computes an appropriate (no-arg) SocketDef from the given `bl_interface_socket`, by parsing it.
"""
return SOCKET_DEFS[
parse_bl_interface_socket(bl_interface_socket)[0]
]
####################
# - Extract Default Interface Socket Value
####################
def value_from_bl(
bl_interface_socket: bpy.types.NodeTreeInterfaceSocket,
unit_system: dict | None = None,
) -> typ.Any:
"""Reads the value of any Blender socket, and writes its `default_value` to the `value` of any `MaxwellSimSocket`.
- If the size of the Blender socket is >1, then `value` is written to as a `sympy.Matrix`.
- If a unit system is given, then the Blender socket is matched to a `MaxwellSimSocket`, which is used to lookup an appropriate unit in the given `unit_system`.
"""
## TODO: Consider sympy.S()'ing the default_value
parsed_bl_socket_value = {
1: lambda: bl_interface_socket.default_value,
2: lambda: sp.Matrix(tuple(bl_interface_socket.default_value)[:2]),
3: lambda: sp.Matrix(tuple(bl_interface_socket.default_value)),
4: lambda: sp.Matrix(tuple(bl_interface_socket.default_value)),
}[size_from_bl_interface_socket(bl_interface_socket)]()
## The 'lambda' delays construction until size is determined
socket_type, unit = parse_bl_interface_socket(bl_interface_socket)
# Add Unit to Parsed (if relevant)
if unit is not None:
parsed_bl_socket_value *= unit
elif unit_system is not None:
parsed_bl_socket_value *= unit_system[socket_type]
return parsed_bl_socket_value
####################
# - Convert to Blender-Compatible Value
####################
def make_scalar_bl_compat(scalar: typ.Any) -> typ.Any:
"""Blender doesn't accept ex. Sympy numbers as values.
Therefore, we need to do some conforming.
Currently hard-coded; this is probably best.
"""
if isinstance(scalar, sp.Integer):
return int(scalar)
elif isinstance(scalar, sp.Float):
return float(scalar)
elif isinstance(scalar, sp.Rational):
return float(scalar)
elif isinstance(scalar, sp.Expr):
return float(scalar.n())
## TODO: More?
return scalar
def value_to_bl(
bl_interface_socket: bpy.types.NodeSocket,
value: typ.Any,
unit_system: dict | None = None,
) -> typ.Any:
socket_type, unit = parse_bl_interface_socket(bl_interface_socket)
# Set Socket
if unit is not None:
bl_socket_value = spu.convert_to(value, unit) / unit
elif (
unit_system is not None
and socket_type in unit_system
):
bl_socket_value = spu.convert_to(
value, unit_system[socket_type]
) / unit_system[socket_type]
else:
bl_socket_value = value
return {
1: lambda: make_scalar_bl_compat(bl_socket_value),
2: lambda: tuple([
make_scalar_bl_compat(bl_socket_value[0]),
make_scalar_bl_compat(bl_socket_value[1]),
bl_interface_socket.default_value[2]
## Don't touch (unused) 3rd bl_socket coordinate
]),
3: lambda: tuple([
make_scalar_bl_compat(el)
for el in bl_socket_value
]),
4: lambda: tuple([
make_scalar_bl_compat(el)
for el in bl_socket_value
]),
}[size_from_bl_interface_socket(bl_interface_socket)]()
## The 'lambda' delays construction until size is determined

View File

@ -26,7 +26,11 @@ from .socket_types import SocketType
from .socket_units import SOCKET_UNITS from .socket_units import SOCKET_UNITS
from .socket_colors import SOCKET_COLORS from .socket_colors import SOCKET_COLORS
from .socket_shapes import SOCKET_SHAPES from .socket_shapes import SOCKET_SHAPES
from .socket_bl_maps import BLSocketToSocket
from .socket_from_bl_desc import BL_SOCKET_DESCR_TYPE_MAP
from .socket_from_bl_direct import BL_SOCKET_DIRECT_TYPE_MAP
from .socket_from_bl_desc import BL_SOCKET_DESCR_ANNOT_STRING
#################### ####################
# - Node Types # - Node Types

View File

@ -29,7 +29,7 @@ NODE_CAT_LABELS = {
# Bounds/ # Bounds/
NC.MAXWELLSIM_BOUNDS: "Bounds", NC.MAXWELLSIM_BOUNDS: "Bounds",
NC.MAXWELLSIM_BOUNDS_BOUNDFACES: "Bound Faces", NC.MAXWELLSIM_BOUNDS_BOUNDCONDS: "Bound Conds",
# Monitors/ # Monitors/
NC.MAXWELLSIM_MONITORS: "Monitors", NC.MAXWELLSIM_MONITORS: "Monitors",

View File

@ -36,7 +36,7 @@ class NodeCategory(BlenderTypeEnum):
# Bounds/ # Bounds/
MAXWELLSIM_BOUNDS = enum.auto() MAXWELLSIM_BOUNDS = enum.auto()
MAXWELLSIM_BOUNDS_BOUNDFACES = enum.auto() MAXWELLSIM_BOUNDS_BOUNDCONDS = enum.auto()
# Monitors/ # Monitors/
MAXWELLSIM_MONITORS = enum.auto() MAXWELLSIM_MONITORS = enum.auto()

View File

@ -97,16 +97,16 @@ class NodeType(BlenderTypeEnum):
# Bounds # Bounds
BoundBox = enum.auto() BoundConds = enum.auto()
## Bounds / Bound Faces ## Bounds / Bound Faces
PMLBoundFace = enum.auto() PMLBoundCond = enum.auto()
PECBoundFace = enum.auto() PECBoundCond = enum.auto()
PMCBoundFace = enum.auto() PMCBoundCond = enum.auto()
BlochBoundFace = enum.auto() BlochBoundCond = enum.auto()
PeriodicBoundFace = enum.auto() PeriodicBoundCond = enum.auto()
AbsorbingBoundFace = enum.auto() AbsorbingBoundCond = enum.auto()
# Monitors # Monitors

View File

@ -3,6 +3,8 @@ import typing as typx
import pydantic as pyd import pydantic as pyd
import bpy
from ..bl import ManagedObjName, SocketName from ..bl import ManagedObjName, SocketName
from ..managed_obj_type import ManagedObjType from ..managed_obj_type import ManagedObjType

View File

@ -1,119 +0,0 @@
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

@ -8,9 +8,8 @@ SOCKET_COLORS = {
# Basic # Basic
ST.Any: (0.8, 0.8, 0.8, 1.0), # Light Grey 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.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.String: (0.7, 0.7, 0.7, 1.0), # Medium Light Grey
ST.FilePath: (0.6, 0.6, 0.6, 1.0), # Medium 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 # Number
ST.IntegerNumber: (0.5, 0.5, 1.0, 1.0), # Light Blue ST.IntegerNumber: (0.5, 0.5, 1.0, 1.0), # Light Blue
@ -39,8 +38,8 @@ SOCKET_COLORS = {
ST.PhysicalSpeed: (0.8, 0.55, 0.35, 1.0), # Medium 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.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.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.PhysicalAccel3D: (0.7, 0.5, 0.3, 1.0), # Medium Orange
ST.PhysicalForce3DVector: (0.6, 0.45, 0.25, 1.0), # Medium Dark Orange ST.PhysicalForce3D: (0.6, 0.45, 0.25, 1.0), # Medium Dark Orange
ST.PhysicalPol: (0.5, 0.4, 0.2, 1.0), # 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 ST.PhysicalFreq: (1.0, 0.7, 0.5, 1.0), # Light Peach
@ -48,10 +47,8 @@ SOCKET_COLORS = {
ST.BlenderObject: (0.7, 0.5, 1.0, 1.0), # Light Purple 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.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.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.BlenderGeoNodes: (0.3, 0.3, 0.6, 1.0), # Dark Purple
ST.BlenderText: (0.5, 0.5, 0.75, 1.0), # Light Lavender 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 # Maxwell
ST.MaxwellSource: (1.0, 1.0, 0.5, 1.0), # Light Yellow ST.MaxwellSource: (1.0, 1.0, 0.5, 1.0), # Light Yellow
@ -59,8 +56,8 @@ SOCKET_COLORS = {
ST.MaxwellMedium: (0.8, 0.8, 0.4, 1.0), # Medium 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.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.MaxwellStructure: (0.6, 0.6, 0.3, 1.0), # Dark Yellow
ST.MaxwellBoundBox: (0.9, 0.8, 0.5, 1.0), # Light Gold ST.MaxwellBoundConds: (0.9, 0.8, 0.5, 1.0), # Light Gold
ST.MaxwellBoundFace: (0.8, 0.7, 0.45, 1.0), # Medium Light Gold ST.MaxwellBoundCond: (0.8, 0.7, 0.45, 1.0), # Medium Light Gold
ST.MaxwellMonitor: (0.7, 0.6, 0.4, 1.0), # Medium 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.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.MaxwellSimGrid: (0.5, 0.4, 0.3, 1.0), # Dark Gold

View File

@ -0,0 +1,78 @@
from .socket_types import SocketType as ST
BL_SOCKET_DESCR_ANNOT_STRING = ":: "
BL_SOCKET_DESCR_TYPE_MAP = {
("Time", "NodeSocketFloat", 1): ST.PhysicalTime,
("Angle", "NodeSocketFloat", 1): ST.PhysicalAngle,
("SolidAngle", "NodeSocketFloat", 1): ST.PhysicalSolidAngle,
("Rotation", "NodeSocketVector", 2): ST.PhysicalRot2D,
("Rotation", "NodeSocketVector", 3): ST.PhysicalRot3D,
("Freq", "NodeSocketFloat", 1): ST.PhysicalFreq,
("AngFreq", "NodeSocketFloat", 1): ST.PhysicalAngFreq,
## Cartesian
("Length", "NodeSocketFloat", 1): ST.PhysicalLength,
("Area", "NodeSocketFloat", 1): ST.PhysicalArea,
("Volume", "NodeSocketFloat", 1): ST.PhysicalVolume,
("Disp", "NodeSocketVector", 2): ST.PhysicalDisp2D,
("Disp", "NodeSocketVector", 3): ST.PhysicalDisp3D,
("Point", "NodeSocketFloat", 1): ST.PhysicalPoint1D,
("Point", "NodeSocketVector", 2): ST.PhysicalPoint2D,
("Point", "NodeSocketVector", 3): ST.PhysicalPoint3D,
("Size", "NodeSocketVector", 2): ST.PhysicalSize2D,
("Size", "NodeSocketVector", 3): ST.PhysicalSize3D,
## Mechanical
("Mass", "NodeSocketFloat", 1): ST.PhysicalMass,
("Speed", "NodeSocketFloat", 1): ST.PhysicalSpeed,
("Vel", "NodeSocketVector", 2): ST.PhysicalVel2D,
("Vel", "NodeSocketVector", 3): ST.PhysicalVel3D,
("Accel", "NodeSocketFloat", 1): ST.PhysicalAccelScalar,
("Accel", "NodeSocketVector", 2): ST.PhysicalAccel2D,
("Accel", "NodeSocketVector", 3): ST.PhysicalAccel3D,
("Force", "NodeSocketFloat", 1): ST.PhysicalForceScalar,
("Force", "NodeSocketVector", 2): ST.PhysicalForce2D,
("Force", "NodeSocketVector", 3): ST.PhysicalForce3D,
("Pressure", "NodeSocketFloat", 1): ST.PhysicalPressure,
## Energetic
("Energy", "NodeSocketFloat", 1): ST.PhysicalEnergy,
("Power", "NodeSocketFloat", 1): ST.PhysicalPower,
("Temp", "NodeSocketFloat", 1): ST.PhysicalTemp,
## ELectrodynamical
("Curr", "NodeSocketFloat", 1): ST.PhysicalCurr,
("CurrDens", "NodeSocketVector", 2): ST.PhysicalCurrDens2D,
("CurrDens", "NodeSocketVector", 3): ST.PhysicalCurrDens3D,
("Charge", "NodeSocketFloat", 1): ST.PhysicalCharge,
("Voltage", "NodeSocketFloat", 1): ST.PhysicalVoltage,
("Capacitance", "NodeSocketFloat", 1): ST.PhysicalCapacitance,
("Resistance", "NodeSocketFloat", 1): ST.PhysicalResistance,
("Conductance", "NodeSocketFloat", 1): ST.PhysicalConductance,
("MagFlux", "NodeSocketFloat", 1): ST.PhysicalMagFlux,
("MagFluxDens", "NodeSocketFloat", 1): ST.PhysicalMagFluxDens,
("Inductance", "NodeSocketFloat", 1): ST.PhysicalInductance,
("EField", "NodeSocketFloat", 2): ST.PhysicalEField3D,
("EField", "NodeSocketFloat", 3): ST.PhysicalEField2D,
("HField", "NodeSocketFloat", 2): ST.PhysicalHField3D,
("HField", "NodeSocketFloat", 3): ST.PhysicalHField2D,
## Luminal
("LumIntensity", "NodeSocketFloat", 1): ST.PhysicalLumIntensity,
("LumFlux", "NodeSocketFloat", 1): ST.PhysicalLumFlux,
("Illuminance", "NodeSocketFloat", 1): ST.PhysicalIlluminance,
## Optical
("PolJones", "NodeSocketFloat", 2): ST.PhysicalPolJones,
("Pol", "NodeSocketFloat", 4): ST.PhysicalPol,
}

View File

@ -0,0 +1,36 @@
from .socket_types import SocketType as ST
BL_SOCKET_DIRECT_TYPE_MAP = {
("NodeSocketString", 1): ST.String,
("NodeSocketBool", 1): ST.Bool,
("NodeSocketCollection", 1): ST.BlenderCollection,
("NodeSocketImage", 1): ST.BlenderImage,
("NodeSocketObject", 1): ST.BlenderObject,
("NodeSocketFloat", 1): ST.RealNumber,
#("NodeSocketFloatAngle", 1): ST.PhysicalAngle,
#("NodeSocketFloatDistance", 1): ST.PhysicalLength,
("NodeSocketFloatFactor", 1): ST.RealNumber,
("NodeSocketFloatPercentage", 1): ST.RealNumber,
#("NodeSocketFloatTime", 1): ST.PhysicalTime,
#("NodeSocketFloatTimeAbsolute", 1): ST.PhysicalTime,
("NodeSocketInt", 1): ST.IntegerNumber,
("NodeSocketIntFactor", 1): ST.IntegerNumber,
("NodeSocketIntPercentage", 1): ST.IntegerNumber,
("NodeSocketIntUnsigned", 1): ST.IntegerNumber,
("NodeSocketRotation", 2): ST.PhysicalRot2D,
("NodeSocketColor", 3): ST.Color,
("NodeSocketVector", 2): ST.Real2DVector,
("NodeSocketVector", 3): ST.Real3DVector,
#("NodeSocketVectorAcceleration", 2): ST.PhysicalAccel2D,
#("NodeSocketVectorAcceleration", 3): ST.PhysicalAccel3D,
#("NodeSocketVectorDirection", 2): ST.Real2DVectorDir,
#("NodeSocketVectorDirection", 3): ST.Real3DVectorDir,
("NodeSocketVectorEuler", 2): ST.PhysicalRot2D,
("NodeSocketVectorEuler", 3): ST.PhysicalRot3D,
#("NodeSocketVectorTranslation", 3): ST.PhysicalDisp3D,
#("NodeSocketVectorVelocity", 3): ST.PhysicalVel3D,
#("NodeSocketVectorXYZ", 3): ST.PhysicalPoint3D,
}

View File

@ -4,9 +4,8 @@ SOCKET_SHAPES = {
# Basic # Basic
ST.Any: "CIRCLE", ST.Any: "CIRCLE",
ST.Bool: "CIRCLE", ST.Bool: "CIRCLE",
ST.Text: "SQUARE", ST.String: "SQUARE",
ST.FilePath: "SQUARE", ST.FilePath: "SQUARE",
ST.Secret: "SQUARE",
# Number # Number
ST.IntegerNumber: "CIRCLE", ST.IntegerNumber: "CIRCLE",
@ -35,8 +34,8 @@ SOCKET_SHAPES = {
ST.PhysicalSpeed: "CIRCLE", ST.PhysicalSpeed: "CIRCLE",
ST.PhysicalAccelScalar: "CIRCLE", ST.PhysicalAccelScalar: "CIRCLE",
ST.PhysicalForceScalar: "CIRCLE", ST.PhysicalForceScalar: "CIRCLE",
ST.PhysicalAccel3DVector: "SQUARE_DOT", ST.PhysicalAccel3D: "SQUARE_DOT",
ST.PhysicalForce3DVector: "SQUARE_DOT", ST.PhysicalForce3D: "SQUARE_DOT",
ST.PhysicalPol: "DIAMOND", ST.PhysicalPol: "DIAMOND",
ST.PhysicalFreq: "CIRCLE", ST.PhysicalFreq: "CIRCLE",
@ -44,10 +43,8 @@ SOCKET_SHAPES = {
ST.BlenderObject: "SQUARE", ST.BlenderObject: "SQUARE",
ST.BlenderCollection: "SQUARE", ST.BlenderCollection: "SQUARE",
ST.BlenderImage: "DIAMOND", ST.BlenderImage: "DIAMOND",
ST.BlenderVolume: "DIAMOND",
ST.BlenderGeoNodes: "DIAMOND", ST.BlenderGeoNodes: "DIAMOND",
ST.BlenderText: "SQUARE", ST.BlenderText: "SQUARE",
ST.BlenderPreviewTarget: "SQUARE",
# Maxwell # Maxwell
ST.MaxwellSource: "CIRCLE", ST.MaxwellSource: "CIRCLE",
@ -55,8 +52,8 @@ SOCKET_SHAPES = {
ST.MaxwellMedium: "CIRCLE", ST.MaxwellMedium: "CIRCLE",
ST.MaxwellMediumNonLinearity: "CIRCLE", ST.MaxwellMediumNonLinearity: "CIRCLE",
ST.MaxwellStructure: "SQUARE", ST.MaxwellStructure: "SQUARE",
ST.MaxwellBoundBox: "SQUARE", ST.MaxwellBoundConds: "SQUARE",
ST.MaxwellBoundFace: "DIAMOND", ST.MaxwellBoundCond: "DIAMOND",
ST.MaxwellMonitor: "CIRCLE", ST.MaxwellMonitor: "CIRCLE",
ST.MaxwellFDTDSim: "SQUARE", ST.MaxwellFDTDSim: "SQUARE",
ST.MaxwellSimGrid: "SQUARE", ST.MaxwellSimGrid: "SQUARE",

View File

@ -9,9 +9,9 @@ class SocketType(BlenderTypeEnum):
# Base # Base
Any = enum.auto() Any = enum.auto()
Bool = enum.auto() Bool = enum.auto()
Text = enum.auto() String = enum.auto()
FilePath = enum.auto() FilePath = enum.auto()
Secret = enum.auto() Color = enum.auto()
# Number # Number
IntegerNumber = enum.auto() IntegerNumber = enum.auto()
@ -21,69 +21,116 @@ class SocketType(BlenderTypeEnum):
# Vector # Vector
Real2DVector = enum.auto() Real2DVector = enum.auto()
Real2DVectorDir = enum.auto()
Complex2DVector = enum.auto() Complex2DVector = enum.auto()
Real3DVector = enum.auto() Real3DVector = enum.auto()
Real3DVectorDir = enum.auto()
Complex3DVector = 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 # Blender
BlenderObject = enum.auto() BlenderObject = enum.auto()
BlenderCollection = enum.auto() BlenderCollection = enum.auto()
BlenderImage = enum.auto() BlenderImage = enum.auto()
BlenderVolume = enum.auto()
BlenderGeoNodes = enum.auto() BlenderGeoNodes = enum.auto()
BlenderText = enum.auto() BlenderText = enum.auto()
BlenderPreviewTarget = enum.auto()
# Maxwell # Maxwell
MaxwellSource = enum.auto() MaxwellBoundConds = enum.auto()
MaxwellTemporalShape = enum.auto() MaxwellBoundCond = enum.auto()
MaxwellMedium = enum.auto() MaxwellMedium = enum.auto()
MaxwellMediumNonLinearity = enum.auto() MaxwellMediumNonLinearity = enum.auto()
MaxwellSource = enum.auto()
MaxwellTemporalShape = enum.auto()
MaxwellStructure = enum.auto() MaxwellStructure = enum.auto()
MaxwellBoundBox = enum.auto()
MaxwellBoundFace = enum.auto()
MaxwellMonitor = enum.auto() MaxwellMonitor = enum.auto()
MaxwellFDTDSim = enum.auto() MaxwellFDTDSim = enum.auto()
MaxwellSimDomain = enum.auto()
MaxwellSimGrid = enum.auto() MaxwellSimGrid = enum.auto()
MaxwellSimGridAxis = enum.auto() MaxwellSimGridAxis = enum.auto()
MaxwellSimDomain = enum.auto()
# Tidy3D # Tidy3D
Tidy3DCloudTask = enum.auto() Tidy3DCloudTask = enum.auto()
# Physical
PhysicalUnitSystem = enum.auto()
PhysicalTime = enum.auto()
PhysicalAngle = enum.auto()
PhysicalSolidAngle = enum.auto()
PhysicalRot2D = enum.auto()
PhysicalRot3D = enum.auto()
PhysicalFreq = enum.auto()
PhysicalAngFreq = enum.auto()
## Cartesian
PhysicalLength = enum.auto()
PhysicalArea = enum.auto()
PhysicalVolume = enum.auto()
PhysicalDisp2D = enum.auto()
PhysicalDisp3D = enum.auto()
PhysicalPoint1D = enum.auto()
PhysicalPoint2D = enum.auto()
PhysicalPoint3D = enum.auto()
PhysicalSize2D = enum.auto()
PhysicalSize3D = enum.auto()
## Mechanical
PhysicalMass = enum.auto()
PhysicalSpeed = enum.auto()
PhysicalVel2D = enum.auto()
PhysicalVel3D = enum.auto()
PhysicalAccelScalar = enum.auto()
PhysicalAccel2D = enum.auto()
PhysicalAccel3D = enum.auto()
PhysicalForceScalar = enum.auto()
PhysicalForce2D = enum.auto()
PhysicalForce3D = enum.auto()
PhysicalPressure = enum.auto()
## Energetic
PhysicalEnergy = enum.auto()
PhysicalPower = enum.auto()
PhysicalTemp = enum.auto()
## Electrodynamical
PhysicalCurr = enum.auto()
PhysicalCurrDens2D = enum.auto()
PhysicalCurrDens3D = enum.auto()
PhysicalCharge = enum.auto()
PhysicalVoltage = enum.auto()
PhysicalCapacitance = enum.auto()
PhysicalResistance = enum.auto()
PhysicalConductance = enum.auto()
PhysicalMagFlux = enum.auto()
PhysicalMagFluxDens = enum.auto()
PhysicalInductance = enum.auto()
PhysicalEField2D = enum.auto()
PhysicalEField3D = enum.auto()
PhysicalHField2D = enum.auto()
PhysicalHField3D = enum.auto()
## Luminal
PhysicalLumIntensity = enum.auto()
PhysicalLumFlux = enum.auto()
PhysicalIlluminance = enum.auto()
## Optical
PhysicalPolJones = enum.auto()
PhysicalPol = enum.auto()

View File

@ -194,7 +194,7 @@ SOCKET_UNITS = {
"NEWT": spu.newton, "NEWT": spu.newton,
}, },
}, },
ST.PhysicalAccel3DVector: { ST.PhysicalAccel3D: {
"default": "UM_S_SQ", "default": "UM_S_SQ",
"values": { "values": {
"PM_S_SQ": spu.picometer / spu.second**2, "PM_S_SQ": spu.picometer / spu.second**2,
@ -206,7 +206,7 @@ SOCKET_UNITS = {
"FT_S_SQ": spu.feet / spu.second**2, "FT_S_SQ": spu.feet / spu.second**2,
}, },
}, },
ST.PhysicalForce3DVector: { ST.PhysicalForce3D: {
"default": "UNEWT", "default": "UNEWT",
"values": { "values": {
"KG_M_S_SQ": spu.kg * spu.m/spu.second**2, "KG_M_S_SQ": spu.kg * spu.m/spu.second**2,

View File

@ -27,15 +27,30 @@ class ManagedBLImage(ct.schemas.ManagedObj):
@name.setter @name.setter
def name(self, value: str): def name(self, value: str):
## TODO: Check that blender doesn't have any other images by the same name. # Image Doesn't Exist
if (bl_image := bpy.data.images.get(self.name)): if not (bl_image := bpy.data.images.get(self._bl_image_name)):
bl_image.name = value # ...AND Desired Image Name is Not Taken
if not bpy.data.objects.get(value):
self._bl_image_name = value
return
self._bl_image_name = value # ...AND Desired Image Name is Taken
else:
msg = f"Desired name {value} for BL image is taken"
raise ValueError(msg)
# Object DOES Exist
bl_image.name = value
self._bl_image_name = bl_image.name
## - When name exists, Blender adds .### to prevent overlap.
## - `set_name` is allowed to change the name; nodes account for this.
def free(self): def free(self):
if (bl_image := bpy.data.images.get(self.name)): if not (bl_image := bpy.data.images.get(self.name)):
bpy.data.images.remove(bl_image) msg = "Can't free BL image that doesn't exist"
raise ValueError(msg)
bpy.data.images.remove(bl_image)
#################### ####################
# - Managed Object Management # - Managed Object Management

View File

@ -13,75 +13,146 @@ import bmesh
from .. import contracts as ct from .. import contracts as ct
ModifierType = typx.Literal["NODES", "ARRAY"]
MODIFIER_NAMES = {
"NODES": "BLMaxwell_GeoNodes",
"ARRAY": "BLMaxwell_Array",
}
MANAGED_COLLECTION_NAME = "BLMaxwell"
PREVIEW_COLLECTION_NAME = "BLMaxwell Visible"
def bl_collection(
collection_name: str, view_layer_exclude: bool
) -> bpy.types.Collection:
# Init the "Managed Collection"
# Ensure Collection exists (and is in the Scene collection)
if collection_name not in bpy.data.collections:
collection = bpy.data.collections.new(collection_name)
bpy.context.scene.collection.children.link(collection)
else:
collection = bpy.data.collections[collection_name]
## Ensure synced View Layer exclusion
if (layer_collection := bpy.context.view_layer.layer_collection.children[
collection_name
]).exclude != view_layer_exclude:
layer_collection.exclude = view_layer_exclude
return collection
class ManagedBLObject(ct.schemas.ManagedObj): class ManagedBLObject(ct.schemas.ManagedObj):
managed_obj_type = ct.ManagedObjType.ManagedBLObject managed_obj_type = ct.ManagedObjType.ManagedBLObject
_bl_object_name: str _bl_object_name: str
def __init__(self, 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 self._bl_object_name = name
# Object Name # Object Name
@property @property
def bl_object_name(self): def name(self):
return self._bl_object_name return self._bl_object_name
@bl_object_name.setter @name.setter
def set_bl_object_name(self, value: str): def set_name(self, value: str) -> None:
## TODO: Check that blender doesn't have any other objects by the same name. # Object Doesn't Exist
if (bl_object := bpy.data.objects.get(self.bl_object_name)): if not (bl_object := bpy.data.objects.get(self._bl_object_name)):
bl_object.name = value # ...AND Desired Object Name is Not Taken
if not bpy.data.objects.get(value):
self._bl_object_name = value
return
self._bl_object_name = value # ...AND Desired Object Name is Taken
else:
msg = f"Desired name {value} for BL object is taken"
raise ValueError(msg)
# Object DOES Exist
bl_object.name = value
self._bl_object_name = bl_object.name
## - When name exists, Blender adds .### to prevent overlap.
## - `set_name` is allowed to change the name; nodes account for this.
# Object Datablock Name # Object Datablock Name
@property @property
def bl_mesh_name(self): def bl_mesh_name(self):
return self.bl_object_name + "Mesh" return self.name
@property @property
def bl_volume_name(self): def bl_volume_name(self):
return self.bl_object_name + "Volume" return self.name
# Deallocation # Deallocation
def free(self): def free(self):
if (bl_object := bpy.data.objects.get(self.bl_object_name)): if not (bl_object := bpy.data.objects.get(self.name)):
# Delete the Underlying Datablock return ## Nothing to do
if bl_object.type == "MESH":
bpy.data.meshes.remove(bl_object.data) # Delete the Underlying Datablock
elif bl_object.type == "VOLUME": ## This automatically deletes the object too
bpy.data.volumes.remove(bl_object.data) if bl_object.type == "MESH":
else: bpy.data.meshes.remove(bl_object.data)
msg = f"Type of to-delete `bl_object`, {bl_object.type}, is not valid" elif bl_object.type == "EMPTY":
raise ValueError(msg) 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 # - Actions
#################### ####################
def trigger_action( def show_preview(
self, self,
action: typx.Literal["report", "enable_previews"], kind: typx.Literal["MESH", "EMPTY", "VOLUME"],
): empty_display_type: typx.Literal[
if action == "report": "PLAIN_AXES", "ARROWS", "SINGLE_ARROW", "CIRCLE", "CUBE",
pass ## TODO: Cache invalidation. "SPHERE", "CONE", "IMAGE",
] | None = None,
) -> None:
"""Moves the managed Blender object to the preview collection.
if action == "enable_previews": If it's already included, do nothing.
"""
bl_object = self.bl_object(kind)
if bl_object.name not in (preview_collection := bl_collection(
PREVIEW_COLLECTION_NAME, view_layer_exclude=False
)).objects:
preview_collection.objects.link(bl_object)
pass ## Image "previews" don't need enabling. if kind == "EMPTY" and empty_display_type is not None:
bl_object.empty_display_type = empty_display_type
def hide_preview(
self,
kind: typx.Literal["MESH", "EMPTY", "VOLUME"],
) -> None:
"""Removes the managed Blender object from the preview collection.
If it's already removed, do nothing.
"""
bl_object = self.bl_object(kind)
if bl_object.name not in (preview_collection := bl_collection(
PREVIEW_COLLECTION_NAME, view_layer_exclude=False
)).objects:
preview_collection.objects.unlink(bl_object)
def bl_select(self) -> None: def bl_select(self) -> None:
"""Selects the managed Blender object globally, causing it to be ex. """Selects the managed Blender object globally, causing it to be ex.
outlined in the 3D viewport. outlined in the 3D viewport.
""" """
if not (bl_object := bpy.data.objects.get(self.name)):
msg = "Managed BLObject does not exist"
raise ValueError(msg)
bpy.ops.object.select_all(action='DESELECT') bpy.ops.object.select_all(action='DESELECT')
bpy.data.objects['Suzanne'].select_set(True) bl_object.select_set(True)
#################### ####################
# - Managed Object Management # - Managed Object Management
#################### ####################
def bl_object( def bl_object(
self, self,
kind: typx.Literal["MESH", "VOLUME"], kind: typx.Literal["MESH", "EMPTY", "VOLUME"],
): ):
"""Returns the managed blender object. """Returns the managed blender object.
@ -90,27 +161,32 @@ class ManagedBLObject(ct.schemas.ManagedObj):
""" """
# Remove Object (if mismatch) # Remove Object (if mismatch)
if ( if (
(bl_object := bpy.data.images.get(self.bl_object_name)) (bl_object := bpy.data.objects.get(self.name))
and bl_object.type != kind and bl_object.type != kind
): ):
self.free() self.free()
# Create Object w/Appropriate Data Block # Create Object w/Appropriate Data Block
if not (bl_object := bpy.data.images.get(self.bl_object_name)): if not (bl_object := bpy.data.objects.get(self.name)):
if bl_object.type == "MESH": if kind == "MESH":
bl_data = bpy.data.meshes.new(self.bl_mesh_name) bl_data = bpy.data.meshes.new(self.bl_mesh_name)
elif bl_object.type == "VOLUME": elif kind == "EMPTY":
bl_data = None
elif kind == "VOLUME":
raise NotImplementedError raise NotImplementedError
else: else:
msg = f"Requested `bl_object` type {bl_object.type} is not valid" msg = f"Requested `bl_object` type {bl_object.type} is not valid"
raise ValueError(msg) raise ValueError(msg)
bl_object = bpy.data.objects.new(self.bl_object_name, bl_data) bl_object = bpy.data.objects.new(self.name, bl_data)
bl_collection(
MANAGED_COLLECTION_NAME, view_layer_exclude=True
).objects.link(bl_object)
return bl_object return bl_object
#################### ####################
# - Data Properties # - Mesh Data Properties
#################### ####################
@property @property
def raw_mesh(self) -> bpy.types.Mesh: def raw_mesh(self) -> bpy.types.Mesh:
@ -119,7 +195,7 @@ class ManagedBLObject(ct.schemas.ManagedObj):
Raises an error if the object has no mesh data. Raises an error if the object has no mesh data.
""" """
if ( if (
(bl_object := bpy.data.objects.get(self.bl_object_name)) (bl_object := bpy.data.objects.get(self.name))
and bl_object.type == "MESH" and bl_object.type == "MESH"
): ):
return bl_object.data return bl_object.data
@ -128,13 +204,13 @@ class ManagedBLObject(ct.schemas.ManagedObj):
raise ValueError(msg) raise ValueError(msg)
@contextlib.contextmanager @contextlib.contextmanager
def as_bmesh( def mesh_as_bmesh(
self, self,
evaluate: bool = True, evaluate: bool = True,
triangulate: bool = False, triangulate: bool = False,
) -> bpy.types.Mesh: ) -> bpy.types.Mesh:
if ( if (
(bl_object := bpy.data.objects.get(self.bl_object_name)) (bl_object := bpy.data.objects.get(self.name))
and bl_object.type == "MESH" and bl_object.type == "MESH"
): ):
bmesh_mesh = None bmesh_mesh = None
@ -156,14 +232,21 @@ class ManagedBLObject(ct.schemas.ManagedObj):
finally: finally:
if bmesh_mesh: bmesh_mesh.free() if bmesh_mesh: bmesh_mesh.free()
msg = f"Requested BMesh from `bl_object` of type {bl_object.type}" else:
raise ValueError(msg) msg = f"Requested BMesh from `bl_object` of type {bl_object.type}"
raise ValueError(msg)
@property
def mesh_as_arrays(self) -> dict:
## TODO: Cached
# Ensure Updated Geometry
bpy.context.view_layer.update()
## TODO: Must we?
@functools.cached_property
def as_arrays(self) -> dict:
# Compute Evaluted + Triangulated Mesh # Compute Evaluted + Triangulated Mesh
_mesh = bpy.data.meshes.new(name="TemporaryMesh") _mesh = bpy.data.meshes.new(name="TemporaryMesh")
with self.as_bmesh(evaluate=True, triangulate=True) as bmesh_mesh: with self.mesh_as_bmesh(evaluate=True, triangulate=True) as bmesh_mesh:
bmesh_mesh.to_mesh(_mesh) bmesh_mesh.to_mesh(_mesh)
# Optimized Vertex Copy # Optimized Vertex Copy
@ -186,6 +269,113 @@ class ManagedBLObject(ct.schemas.ManagedObj):
"faces": faces, "faces": faces,
} }
####################
# - Modifier Methods
####################
def bl_modifier(
self,
modifier_type: ModifierType,
):
"""Creates a new modifier for the current `bl_object`.
For all Blender modifier type names, see: <https://docs.blender.org/api/current/bpy_types_enum_items/object_modifier_type_items.html#rna-enum-object-modifier-type-items>
"""
if not (bl_object := bpy.data.objects.get(self.name)):
msg = "Can't add modifier to BL object that doesn't exist"
raise ValueError(msg)
# (Create and) Return Modifier
bl_modifier_name = MODIFIER_NAMES[modifier_type]
if bl_modifier_name not in bl_object.modifiers:
return bl_object.modifiers.new(
name=bl_modifier_name,
type=modifier_type,
)
return bl_object.modifiers[bl_modifier_name]
def modifier_attrs(self, modifier_type: ModifierType) -> dict:
"""Based on the modifier type, retrieve a representative dictionary of modifier attributes.
The attributes can then easily be set using `setattr`.
"""
bl_modifier = self.bl_modifier(modifier_type)
if modifier_type == "NODES":
return {
"node_group": bl_modifier.node_group,
}
elif modifier_type == "ARRAY":
raise NotImplementedError
def s_modifier_attrs(
self,
modifier_type: ModifierType,
modifier_attrs: dict,
):
bl_modifier = self.bl_modifier(modifier_type)
if modifier_type == "NODES":
if bl_modifier.node_group != modifier_attrs["node_group"]:
bl_modifier.node_group = modifier_attrs["node_group"]
elif modifier_type == "ARRAY":
raise NotImplementedError
####################
# - GeoNodes Modifier
####################
def sync_geonodes_modifier(
self,
geonodes_node_group,
geonodes_identifier_to_value: dict,
):
"""Push the given GeoNodes Interface values to a GeoNodes modifier attached to a managed MESH object.
The values must be compatible with the `default_value`s of the interface sockets.
If there is no object, it is created.
If the object isn't a MESH object, it is made so.
If the GeoNodes modifier doesn't exist, it is created.
If the GeoNodes node group doesn't match, it is changed.
Only differing interface values are actually changed.
"""
bl_object = self.bl_object("MESH")
# Get (/make) a GeoModes Modifier
bl_modifier = self.bl_modifier("NODES")
# Set GeoNodes Modifier Attributes (specifically, the 'node_group')
self.s_modifier_attrs("NODES", {"node_group": geonodes_node_group})
# Set GeoNodes Values
modifier_altered = False
for interface_identifier, value in (
geonodes_identifier_to_value.items()
):
if bl_modifier[interface_identifier] != value:
# Quickly Determine if IDPropertyArray is Equal
if hasattr(
bl_modifier[interface_identifier],
"to_list"
) and tuple(
bl_modifier[interface_identifier].to_list()
) == value:
continue
# Quickly Determine int/float Mismatch
if isinstance(
bl_modifier[interface_identifier],
float,
) and isinstance(value, int):
value = float(value)
bl_modifier[interface_identifier] = value
modifier_altered = True
# Update DepGraph (if anything changed)
if modifier_altered:
bl_object.data.update()
#@property #@property
#def volume(self) -> bpy.types.Volume: #def volume(self) -> bpy.types.Volume:
# """Returns the object's volume data. # """Returns the object's volume data.

View File

@ -61,17 +61,6 @@ class MaxwellSimTree(bpy.types.NodeTree):
bl_label = "Maxwell Sim Editor" bl_label = "Maxwell Sim Editor"
bl_icon = ct.Icon.SimNodeEditor.value 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,
)
#################### ####################
# - Lock Methods # - Lock Methods
#################### ####################
@ -81,6 +70,18 @@ class MaxwellSimTree(bpy.types.NodeTree):
for bl_socket in [*node.inputs, *node.outputs]: for bl_socket in [*node.inputs, *node.outputs]:
bl_socket.locked = False bl_socket.locked = False
####################
# - Init Methods
####################
def on_load(self):
"""Run by Blender when loading the NodeSimTree, ex. on file load, on creation, etc. .
It's a bit of a "fake" function - in practicality, it's triggered on the first update() function.
"""
## TODO: Consider tying this to an "on_load" handler
self._node_link_cache = NodeLinkCache(self)
#################### ####################
# - Update Methods # - Update Methods
#################### ####################
@ -105,7 +106,7 @@ class MaxwellSimTree(bpy.types.NodeTree):
Updates an internal node link cache, then updates sockets that just lost/gained an input link. Updates an internal node link cache, then updates sockets that just lost/gained an input link.
""" """
if not hasattr(self, "_node_link_cache"): if not hasattr(self, "_node_link_cache"):
self._node_link_cache = NodeLinkCache(self) self.on_load()
## We presume update() is run before the first link is altered. ## We presume update() is run before the first link is altered.
## - Else, the first link of the session will not update caches. ## - Else, the first link of the session will not update caches.
## - We remain slightly unsure of the semantics. ## - We remain slightly unsure of the semantics.

View File

@ -1,4 +1,4 @@
from . import kitchen_sink #from . import kitchen_sink
from . import inputs from . import inputs
from . import outputs from . import outputs
@ -11,7 +11,7 @@ from . import simulations
#from . import utilities #from . import utilities
BL_REGISTER = [ BL_REGISTER = [
*kitchen_sink.BL_REGISTER, #*kitchen_sink.BL_REGISTER,
*inputs.BL_REGISTER, *inputs.BL_REGISTER,
*outputs.BL_REGISTER, *outputs.BL_REGISTER,
*sources.BL_REGISTER, *sources.BL_REGISTER,
@ -23,7 +23,7 @@ BL_REGISTER = [
# *utilities.BL_REGISTER, # *utilities.BL_REGISTER,
] ]
BL_NODES = { BL_NODES = {
**kitchen_sink.BL_NODES, #**kitchen_sink.BL_NODES,
**inputs.BL_NODES, **inputs.BL_NODES,
**outputs.BL_NODES, **outputs.BL_NODES,
**sources.BL_NODES, **sources.BL_NODES,

View File

@ -67,7 +67,7 @@ class MaxwellSimNode(bpy.types.Node):
name="Sim Node Name", name="Sim Node Name",
description="The name of a particular MaxwellSimNode node, which can be used to help identify data managed by the node", description="The name of a particular MaxwellSimNode node, which can be used to help identify data managed by the node",
default="", default="",
update=(lambda self, context: self._sync_sim_node_name(context)) update=(lambda self, context: self.sync_sim_node_name(context))
) )
# Setup Locked Property for Node # Setup Locked Property for Node
@ -151,7 +151,7 @@ class MaxwellSimNode(bpy.types.Node):
) )
], ],
default=socket_set_names[0], default=socket_set_names[0],
update=(lambda self, _: self._sync_sockets()), update=(lambda self, _: self.sync_sockets()),
) )
# Setup Preset Dropdown # Setup Preset Dropdown
@ -172,24 +172,27 @@ class MaxwellSimNode(bpy.types.Node):
], ],
default=list(cls.presets.keys())[0], default=list(cls.presets.keys())[0],
update=lambda self, context: ( update=lambda self, context: (
self._sync_active_preset()() self.sync_active_preset()()
), ),
) )
#################### ####################
# - Generic Properties # - Generic Properties
#################### ####################
def _sync_sim_node_name(self, context): def sync_sim_node_name(self, context):
for managed_obj in self.managed_objs.values(): if (mobjs := CACHE[self.instance_id].get("managed_objs")) is None:
managed_obj.name = self.sim_node_name return
# Recurse Until Equal for mobj_id, mobj in mobjs.items():
if managed_obj.name != self.sim_node_name: # Retrieve Managed Obj Definition
self.sim_node_name = managed_obj.name mobj_def = self.managed_obj_defs[mobj_id]
## ManagedObj is allowed to alter the name when setting it.
## - This will happen whenever the name is taken. # Set Managed Obj Name
## - If altered, set the 'sim_node_name' to the altered name. mobj.name = mobj_def.name_prefix + self.sim_node_name
## - This will cause recursion, but only once. ## ManagedObj is allowed to alter the name when setting it.
## - This will happen whenever the name is taken.
## - If altered, set the 'sim_node_name' to the altered name.
## - This will cause recursion, but only once.
#################### ####################
# - Managed Object Properties # - Managed Object Properties
@ -205,7 +208,7 @@ class MaxwellSimNode(bpy.types.Node):
## - ManagedObjects MUST the same object by name. ## - ManagedObjects MUST the same object by name.
## - We sync our 'sim_node_name' with all managed objects. ## - We sync our 'sim_node_name' with all managed objects.
## - (There is also a class-defined 'name_prefix' to differentiate) ## - (There is also a class-defined 'name_prefix' to differentiate)
## - See the 'sim_node_name' w/its _sync function. ## - See the 'sim_node_name' w/its sync function.
if CACHE[self.instance_id].get("managed_objs") is None: if CACHE[self.instance_id].get("managed_objs") is None:
# Initialize the Managed Object Instance Cache # Initialize the Managed Object Instance Cache
CACHE[self.instance_id]["managed_objs"] = {} CACHE[self.instance_id]["managed_objs"] = {}
@ -304,7 +307,7 @@ class MaxwellSimNode(bpy.types.Node):
for model in deser.values() for model in deser.values()
], ],
"models": [ "models": [
dict(model) model.model_dump()
for model in deser.values() for model in deser.values()
if isinstance(model, pyd.BaseModel) if isinstance(model, pyd.BaseModel)
], ],
@ -336,7 +339,7 @@ class MaxwellSimNode(bpy.types.Node):
self.ser_loose_input_sockets = self._ser_loose_sockets(value) self.ser_loose_input_sockets = self._ser_loose_sockets(value)
# Synchronize Sockets # Synchronize Sockets
self._sync_sockets() self.sync_sockets()
## TODO: Perhaps re-init() all loose sockets anyway? ## TODO: Perhaps re-init() all loose sockets anyway?
@loose_output_sockets.setter @loose_output_sockets.setter
@ -346,7 +349,7 @@ class MaxwellSimNode(bpy.types.Node):
self.ser_loose_output_sockets = self._ser_loose_sockets(value) self.ser_loose_output_sockets = self._ser_loose_sockets(value)
# Synchronize Sockets # Synchronize Sockets
self._sync_sockets() self.sync_sockets()
## TODO: Perhaps re-init() all loose sockets anyway? ## TODO: Perhaps re-init() all loose sockets anyway?
#################### ####################
@ -402,7 +405,7 @@ class MaxwellSimNode(bpy.types.Node):
for socket_name, socket_def in created_sockets.items(): for socket_name, socket_def in created_sockets.items():
socket_def.init(bl_sockets[socket_name]) socket_def.init(bl_sockets[socket_name])
def _sync_sockets(self) -> None: def sync_sockets(self) -> None:
"""Synchronize the node's sockets with the active sockets. """Synchronize the node's sockets with the active sockets.
- Any non-existing active socket will be added and initialized. - Any non-existing active socket will be added and initialized.
@ -418,7 +421,7 @@ class MaxwellSimNode(bpy.types.Node):
#################### ####################
# - Preset Management # - Preset Management
#################### ####################
def _sync_active_preset(self) -> None: def sync_active_preset(self) -> None:
"""Applies the active preset by overwriting the value of """Applies the active preset by overwriting the value of
preset-defined input sockets. preset-defined input sockets.
""" """
@ -555,7 +558,11 @@ class MaxwellSimNode(bpy.types.Node):
and socket_name == method._extra_data.get("changed_socket") and socket_name == method._extra_data.get("changed_socket")
) or ( ) or (
prop_name prop_name
and socket_name == method._extra_data.get("changed_prop") and prop_name == method._extra_data.get("changed_prop")
) or (
socket_name
and method._extra_data.get("changed_loose_input")
and socket_name in self.loose_input_sockets
): ):
method(self) method(self)
@ -626,11 +633,11 @@ class MaxwellSimNode(bpy.types.Node):
## Only shown in draw_buttons if 'self.use_sim_node_name' ## Only shown in draw_buttons if 'self.use_sim_node_name'
# Initialize Sockets # Initialize Sockets
self._sync_sockets() self.sync_sockets()
# Apply Default Preset # Apply Default Preset
if self.active_preset: if self.active_preset:
self._sync_active_preset() self.sync_active_preset()
def update(self) -> None: def update(self) -> None:
pass pass
@ -672,6 +679,8 @@ def chain_event_decorator(
kind: ct.DataFlowKind = ct.DataFlowKind.Value, kind: ct.DataFlowKind = ct.DataFlowKind.Value,
input_sockets: set[str] = set(), ## For now, presume input_sockets: set[str] = set(), ## For now, presume
output_sockets: set[str] = set(), ## For now, presume output_sockets: set[str] = set(), ## For now, presume
loose_input_sockets: bool = False,
loose_output_sockets: bool = False,
props: set[str] = set(), props: set[str] = set(),
managed_objs: set[str] = set(), managed_objs: set[str] = set(),
@ -715,6 +724,24 @@ def chain_event_decorator(
} }
method_kw_args |= dict(output_sockets=_output_sockets) method_kw_args |= dict(output_sockets=_output_sockets)
## Add Loose Sockets
if loose_input_sockets:
_loose_input_sockets = {
input_socket_name: node._compute_input(input_socket_name, kind)
for input_socket_name in node.loose_input_sockets
}
method_kw_args |= dict(
loose_input_sockets=_loose_input_sockets
)
if loose_output_sockets:
_loose_output_sockets = {
output_socket_name: node.compute_output(output_socket_name, kind)
for output_socket_name in node.loose_output_sockets
}
method_kw_args |= dict(
loose_output_sockets=_loose_output_sockets
)
## Add Props ## Add Props
if props: if props:
_props = { _props = {
@ -808,17 +835,25 @@ def computes_output_socket(
def on_value_changed( def on_value_changed(
socket_name: ct.SocketName | None = None, socket_name: ct.SocketName | None = None,
prop_name: str | None = None, prop_name: str | None = None,
any_loose_input_socket: bool = False,
kind: ct.DataFlowKind = ct.DataFlowKind.Value, kind: ct.DataFlowKind = ct.DataFlowKind.Value,
input_sockets: set[str] = set(), input_sockets: set[str] = set(),
props: set[str] = set(), props: set[str] = set(),
managed_objs: set[str] = set(), managed_objs: set[str] = set(),
): ):
if socket_name is not None and prop_name is not None: if sum([
msg = "Either socket_name or prop_name, not both" int(socket_name is not None),
int(prop_name is not None),
int(any_loose_input_socket),
]) > 1:
msg = "Define only one of socket_name, prop_name or any_loose_input_socket"
raise ValueError(msg) raise ValueError(msg)
req_params = {"self"} | ( req_params = {"self"} | (
{"input_sockets"} if input_sockets else set() {"input_sockets"} if input_sockets else set()
) | (
{"loose_input_sockets"} if any_loose_input_socket else set()
) | ( ) | (
{"props"} if props else set() {"props"} if props else set()
) | ( ) | (
@ -827,13 +862,14 @@ def on_value_changed(
return chain_event_decorator( return chain_event_decorator(
callback_type="on_value_changed", callback_type="on_value_changed",
index_by=(socket_name, prop_name),
extra_data={ extra_data={
"changed_socket": socket_name, "changed_socket": socket_name,
"changed_prop": prop_name, "changed_prop": prop_name,
"changed_loose_input": any_loose_input_socket,
}, },
kind=kind, kind=kind,
input_sockets=input_sockets, input_sockets=input_sockets,
loose_input_sockets=any_loose_input_socket,
props=props, props=props,
managed_objs=managed_objs, managed_objs=managed_objs,
req_params=req_params, req_params=req_params,
@ -841,8 +877,8 @@ def on_value_changed(
def on_show_preview( def on_show_preview(
kind: ct.DataFlowKind = ct.DataFlowKind.Value, kind: ct.DataFlowKind = ct.DataFlowKind.Value,
input_sockets: set[str] = set(), ## For now, presume input_sockets: set[str] = set(), ## For now, presume only same kind
output_sockets: set[str] = set(), ## For now, presume output_sockets: set[str] = set(), ## For now, presume only same kind
props: set[str] = set(), props: set[str] = set(),
managed_objs: set[str] = set(), managed_objs: set[str] = set(),
): ):

View File

@ -2,12 +2,12 @@ import tidy3d as td
import sympy as sp import sympy as sp
import sympy.physics.units as spu import sympy.physics.units as spu
from ... import contracts from ... import contracts as ct
from ... import sockets from ... import sockets
from .. import base from .. import base
class BoundBoxNode(base.MaxwellSimTreeNode): class BoundCondsNode(base.MaxwellSimNode):
node_type = contracts.NodeType.BoundBox node_type = ct.NodeType.BoundConds
bl_label = "Bound Box" bl_label = "Bound Box"
#bl_icon = ... #bl_icon = ...
@ -15,42 +15,31 @@ class BoundBoxNode(base.MaxwellSimTreeNode):
# - Sockets # - Sockets
#################### ####################
input_sockets = { input_sockets = {
"x_pos": sockets.MaxwellBoundFaceSocketDef( "+X": sockets.MaxwellBoundCondSocketDef(),
label="+x", "-X": sockets.MaxwellBoundCondSocketDef(),
), "+Y": sockets.MaxwellBoundCondSocketDef(),
"x_neg": sockets.MaxwellBoundFaceSocketDef( "-Y": sockets.MaxwellBoundCondSocketDef(),
label="-x", "+Z": sockets.MaxwellBoundCondSocketDef(),
), "-Z": sockets.MaxwellBoundCondSocketDef(),
"y_pos": sockets.MaxwellBoundFaceSocketDef(
label="+y",
),
"y_neg": sockets.MaxwellBoundFaceSocketDef(
label="-y",
),
"z_pos": sockets.MaxwellBoundFaceSocketDef(
label="+z",
),
"z_neg": sockets.MaxwellBoundFaceSocketDef(
label="-z",
),
} }
output_sockets = { output_sockets = {
"bound": sockets.MaxwellBoundBoxSocketDef( "BCs": sockets.MaxwellBoundCondsSocketDef(),
label="Bound",
),
} }
#################### ####################
# - Output Socket Computation # - Output Socket Computation
#################### ####################
@base.computes_output_socket("bound") @base.computes_output_socket(
def compute_simulation(self: contracts.NodeTypeProtocol) -> td.BoundarySpec: "BCs",
x_pos = self.compute_input("x_pos") input_sockets={"+X", "-X", "+Y", "-Y", "+Z", "-Z"}
x_neg = self.compute_input("x_neg") )
y_pos = self.compute_input("x_pos") def compute_simulation(self, input_sockets) -> td.BoundarySpec:
y_neg = self.compute_input("x_neg") x_pos = input_sockets["+X"]
z_pos = self.compute_input("x_pos") x_neg = input_sockets["-X"]
z_neg = self.compute_input("x_neg") y_pos = input_sockets["+Y"]
y_neg = input_sockets["-Y"]
z_pos = input_sockets["+Z"]
z_neg = input_sockets["-Z"]
return td.BoundarySpec( return td.BoundarySpec(
x=td.Boundary( x=td.Boundary(
@ -73,10 +62,10 @@ class BoundBoxNode(base.MaxwellSimTreeNode):
# - Blender Registration # - Blender Registration
#################### ####################
BL_REGISTER = [ BL_REGISTER = [
BoundBoxNode, BoundCondsNode,
] ]
BL_NODES = { BL_NODES = {
contracts.NodeType.BoundBox: ( ct.NodeType.BoundConds: (
contracts.NodeCategory.MAXWELLSIM_BOUNDS ct.NodeCategory.MAXWELLSIM_BOUNDS
) )
} }

View File

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

View File

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

View File

@ -1,57 +1,41 @@
import typing as typ import typing as typ
from .... import contracts from .... import contracts as ct
from .... import sockets from .... import sockets
from ... import base from ... import base
class BlenderConstantNode(base.MaxwellSimTreeNode): class BlenderConstantNode(base.MaxwellSimNode):
node_type = contracts.NodeType.BlenderConstant node_type = ct.NodeType.BlenderConstant
bl_label = "Blender Constant" bl_label = "Blender Constant"
#bl_icon = constants.ICON_SIM_INPUT
input_sockets = {}
input_socket_sets = { input_socket_sets = {
"object": { "Object": {
"value": sockets.BlenderObjectSocketDef( "Value": sockets.BlenderObjectSocketDef(),
label="Object",
),
}, },
"collection": { "Collection": {
"value": sockets.BlenderCollectionSocketDef( "Value": sockets.BlenderCollectionSocketDef(),
label="Collection",
),
}, },
"image": { "Text": {
"value": sockets.BlenderImageSocketDef( "Value": sockets.BlenderTextSocketDef(),
label="Image",
),
}, },
"volume": { "Image": {
"value": sockets.BlenderVolumeSocketDef( "Value": sockets.BlenderImageSocketDef(),
label="Volume",
),
}, },
"text": { "GeoNode Tree": {
"value": sockets.BlenderTextSocketDef( "Value": sockets.BlenderGeoNodesSocketDef(),
label="Text",
),
},
"geonodes": {
"value": sockets.BlenderGeoNodesSocketDef(
label="GeoNodes",
),
}, },
} }
output_sockets = {}
output_socket_sets = input_socket_sets output_socket_sets = input_socket_sets
#################### ####################
# - Callbacks # - Callbacks
#################### ####################
@base.computes_output_socket("value") @base.computes_output_socket(
def compute_value(self: contracts.NodeTypeProtocol) -> typ.Any: "Value",
return self.compute_input("value") input_sockets={"Value"}
)
def compute_value(self, input_sockets) -> typ.Any:
return input_sockets["Value"]
@ -62,7 +46,7 @@ BL_REGISTER = [
BlenderConstantNode, BlenderConstantNode,
] ]
BL_NODES = { BL_NODES = {
contracts.NodeType.BlenderConstant: ( ct.NodeType.BlenderConstant: (
contracts.NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS ct.NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS
) )
} }

View File

@ -1,43 +1,41 @@
import typing as typ
import bpy import bpy
import sympy as sp import sympy as sp
from .... import contracts from .... import contracts as ct
from .... import sockets from .... import sockets
from ... import base from ... import base
class NumberConstantNode(base.MaxwellSimTreeNode): class NumberConstantNode(base.MaxwellSimNode):
node_type = contracts.NodeType.NumberConstant node_type = ct.NodeType.NumberConstant
bl_label = "Numerical Constant" bl_label = "Numerical Constant"
#bl_icon = constants.ICON_SIM_INPUT
input_sockets = {}
input_socket_sets = { input_socket_sets = {
"integer": { "Integer": {
"value": sockets.IntegerNumberSocketDef( "Value": sockets.IntegerNumberSocketDef(),
label="Integer",
),
}, },
"real": { "Rational": {
"value": sockets.RealNumberSocketDef( "Value": sockets.RationalNumberSocketDef(),
label="Real",
),
}, },
"complex": { "Real": {
"value": sockets.ComplexNumberSocketDef( "Value": sockets.RealNumberSocketDef(),
label="Complex", },
), "Complex": {
"Value": sockets.ComplexNumberSocketDef(),
}, },
} }
output_sockets = {}
output_socket_sets = input_socket_sets output_socket_sets = input_socket_sets
#################### ####################
# - Callbacks # - Callbacks
#################### ####################
@base.computes_output_socket("value") @base.computes_output_socket(
def compute_value(self: contracts.NodeTypeProtocol) -> sp.Expr: "Value",
return self.compute_input("value") input_sockets={"Value"}
)
def compute_value(self, input_sockets) -> typ.Any:
return input_sockets["Value"]
@ -48,7 +46,7 @@ BL_REGISTER = [
NumberConstantNode, NumberConstantNode,
] ]
BL_NODES = { BL_NODES = {
contracts.NodeType.NumberConstant: ( ct.NodeType.NumberConstant: (
contracts.NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS ct.NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS
) )
} }

View File

@ -1,11 +0,0 @@
from . import number_list
from . import physical_list
BL_REGISTER = [
*number_list.BL_REGISTER,
*physical_list.BL_REGISTER,
]
BL_NODES = {
**number_list.BL_NODES,
**physical_list.BL_NODES,
}

View File

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

View File

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

View File

@ -1,11 +0,0 @@
from . import time
from . import unit_system
BL_REGISTER = [
*time.BL_REGISTER,
*unit_system.BL_REGISTER,
]
BL_NODES = {
**time.BL_NODES,
**unit_system.BL_NODES,
}

View File

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

View File

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

View File

@ -1,33 +1,32 @@
import bpy import bpy
import sympy as sp import sympy as sp
from ... import contracts from ... import contracts as ct
from ... import sockets from ... import sockets
from .. import base from .. import base
class PhysicalUnitSystemNode(base.MaxwellSimTreeNode): class PhysicalUnitSystemNode(base.MaxwellSimNode):
node_type = contracts.NodeType.UnitSystem node_type = ct.NodeType.UnitSystem
bl_label = "Unit System"
bl_label = "Unit System Constant"
input_sockets = { input_sockets = {
"unit_system": sockets.PhysicalUnitSystemSocketDef( "Unit System": sockets.PhysicalUnitSystemSocketDef(
label="Unit System",
show_by_default=True, show_by_default=True,
), ),
} }
output_sockets = { output_sockets = {
"unit_system": sockets.PhysicalUnitSystemSocketDef( "Unit System": sockets.PhysicalUnitSystemSocketDef(),
label="Unit System",
),
} }
#################### ####################
# - Callbacks # - Callbacks
#################### ####################
@base.computes_output_socket("unit_system") @base.computes_output_socket(
def compute_value(self: contracts.NodeTypeProtocol) -> sp.Expr: "Unit System",
return self.compute_input("unit_system") input_sockets = {"Unit System"},
)
def compute_value(self, input_sockets) -> dict:
return input_sockets["Unit System"]
@ -38,7 +37,7 @@ BL_REGISTER = [
PhysicalUnitSystemNode, PhysicalUnitSystemNode,
] ]
BL_NODES = { BL_NODES = {
contracts.NodeType.UnitSystem: ( ct.NodeType.UnitSystem: (
contracts.NodeCategory.MAXWELLSIM_INPUTS ct.NodeCategory.MAXWELLSIM_INPUTS
) )
} }

View File

@ -3,9 +3,9 @@ import sympy as sp
import sympy.physics.units as spu import sympy.physics.units as spu
import scipy as sc import scipy as sc
from .... import contracts as ct from ... import contracts as ct
from .... import sockets from ... import sockets
from ... import base from .. import base
VAC_SPEED_OF_LIGHT = ( VAC_SPEED_OF_LIGHT = (
sc.constants.speed_of_light sc.constants.speed_of_light

View File

@ -46,7 +46,7 @@ class JSONFileExporterNode(base.MaxwellSimNode):
), ),
} }
output_sockets = { output_sockets = {
"JSON String": sockets.TextSocketDef(), "JSON String": sockets.StringSocketDef(),
} }
#################### ####################

View File

@ -11,6 +11,7 @@ import tidy3d as td
from ... import contracts as ct from ... import contracts as ct
from ... import sockets from ... import sockets
from .. import base from .. import base
from ...managed_objs import managed_bl_object
class ConsoleViewOperator(bpy.types.Operator): class ConsoleViewOperator(bpy.types.Operator):
@ -50,17 +51,53 @@ class ViewerNode(base.MaxwellSimNode):
"Data": sockets.AnySocketDef(), "Data": sockets.AnySocketDef(),
} }
####################
# - Properties
####################
auto_plot: bpy.props.BoolProperty(
name="Auto-Plot",
description="Whether to auto-plot anything plugged into the viewer node",
default=False,
update=lambda self, context: self.sync_prop("auto_plot", context),
)
auto_3d_preview: bpy.props.BoolProperty(
name="Auto 3D Preview",
description="Whether to auto-preview anything 3D, that's plugged into the viewer node",
default=False,
update=lambda self, context: self.sync_prop("auto_3d_preview", context),
)
#################### ####################
# - UI # - UI
#################### ####################
def draw_operators(self, context, layout): def draw_operators(self, context, layout):
row = layout.row(align=True) split = layout.split(factor=0.4)
row.label(text="Console")
row.operator(ConsoleViewOperator.bl_idname, text="Print")
row = layout.row(align=True) # Split LHS
row.label(text="Plot") col = split.column(align=False)
row.operator(RefreshPlotViewOperator.bl_idname, text="", icon="FILE_REFRESH") col.label(text="Console")
col.label(text="Plot")
col.label(text="3D")
# Split RHS
col = split.column(align=False)
## Console Options
col.operator(ConsoleViewOperator.bl_idname, text="Print")
## Plot Options
row = col.row(align=True)
row.prop(self, "auto_plot", text="Plot", toggle=True)
row.operator(
RefreshPlotViewOperator.bl_idname,
text="",
icon="FILE_REFRESH",
)
## 3D Preview Options
row = col.row(align=True)
row.prop(self, "auto_3d_preview", text="3D Preview", toggle=True)
#################### ####################
# - Methods # - Methods
@ -75,11 +112,46 @@ class ViewerNode(base.MaxwellSimNode):
print(str(data)) print(str(data))
#################### ####################
# - Update # - Updates
#################### ####################
@base.on_value_changed(socket_name="Data") @base.on_value_changed(
def on_value_changed__data(self): socket_name="Data",
self.trigger_action("show_plot") props={"auto_3d_preview"},
)
def on_value_changed__data(self, props):
# Show Plot
## Don't have to un-show other plots.
if self.auto_plot:
self.trigger_action("show_plot")
# Remove Anything Previewed
preview_collection = managed_bl_object.bl_collection(
managed_bl_object.PREVIEW_COLLECTION_NAME,
view_layer_exclude=False,
)
for bl_object in preview_collection.objects.values():
preview_collection.objects.unlink(bl_object)
# Preview Anything that Should be Previewed (maybe)
if props["auto_3d_preview"]:
self.trigger_action("show_preview")
@base.on_value_changed(
prop_name="auto_3d_preview",
props={"auto_3d_preview"},
)
def on_value_changed__auto_3d_preview(self, props):
# Remove Anything Previewed
preview_collection = managed_bl_object.bl_collection(
managed_bl_object.PREVIEW_COLLECTION_NAME,
view_layer_exclude=False,
)
for bl_object in preview_collection.objects.values():
preview_collection.objects.unlink(bl_object)
# Preview Anything that Should be Previewed (maybe)
if props["auto_3d_preview"]:
self.trigger_action("show_preview")
#################### ####################

View File

@ -15,7 +15,7 @@ class FDTDSimNode(base.MaxwellSimNode):
#################### ####################
input_sockets = { input_sockets = {
"Domain": sockets.MaxwellSimDomainSocketDef(), "Domain": sockets.MaxwellSimDomainSocketDef(),
"BCs": sockets.MaxwellBoundBoxSocketDef(), "BCs": sockets.MaxwellBoundCondsSocketDef(),
"Sources": sockets.MaxwellSourceSocketDef(), "Sources": sockets.MaxwellSourceSocketDef(),
"Structures": sockets.MaxwellStructureSocketDef(), "Structures": sockets.MaxwellStructureSocketDef(),
"Monitors": sockets.MaxwellMonitorSocketDef(), "Monitors": sockets.MaxwellMonitorSocketDef(),

View File

@ -3,9 +3,13 @@ import sympy as sp
import sympy.physics.units as spu import sympy.physics.units as spu
import scipy as sc import scipy as sc
from .....utils import analyze_geonodes
from ... import contracts as ct from ... import contracts as ct
from ... import sockets from ... import sockets
from .. import base from .. import base
from ... import managed_objs
GEONODES_DOMAIN_BOX = "domain_box"
class SimDomainNode(base.MaxwellSimNode): class SimDomainNode(base.MaxwellSimNode):
node_type = ct.NodeType.SimDomain node_type = ct.NodeType.SimDomain
@ -24,6 +28,13 @@ class SimDomainNode(base.MaxwellSimNode):
"Domain": sockets.MaxwellSimDomainSocketDef(), "Domain": sockets.MaxwellSimDomainSocketDef(),
} }
managed_obj_defs = {
"domain_box": ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLObject(name),
name_prefix="domain_box_",
)
}
#################### ####################
# - Callbacks # - Callbacks
#################### ####################
@ -47,6 +58,54 @@ class SimDomainNode(base.MaxwellSimNode):
medium=medium, medium=medium,
) )
####################
# - Preview
####################
@base.on_value_changed(
socket_name="Size",
input_sockets={"Size"},
managed_objs={"domain_box"},
)
def on_value_changed__center(
self,
input_sockets: dict,
managed_objs: dict[str, ct.schemas.ManagedObj],
):
_size = input_sockets["Size"]
size = tuple([
float(el)
for el in spu.convert_to(_size, spu.um) / spu.um
])
## TODO: Preview unit system?? Presume um for now
# Retrieve Hard-Coded GeoNodes and Analyze Input
geo_nodes = bpy.data.node_groups[GEONODES_DOMAIN_BOX]
geonodes_interface = analyze_geonodes.interface(
geo_nodes, direc="INPUT"
)
# Sync Modifier Inputs
managed_objs["domain_box"].sync_geonodes_modifier(
geonodes_node_group=geo_nodes,
geonodes_identifier_to_value={
geonodes_interface["Size"].identifier: size
## TODO: Use 'bl_socket_map.value_to_bl`!
## - This accounts for auto-conversion, unit systems, etc. .
## - We could keep it in the node base class...
## - ...But it needs aligning with Blender, too. Hmm.
}
)
@base.on_show_preview(
managed_objs={"domain_box"},
)
def on_show_preview(
self,
managed_objs: dict[str, ct.schemas.ManagedObj],
):
managed_objs["domain_box"].show_preview("MESH")
self.on_value_changed__center()
#################### ####################
# - Blender Registration # - Blender Registration
#################### ####################

View File

@ -8,6 +8,7 @@ import bpy
from ... import contracts as ct from ... import contracts as ct
from ... import sockets from ... import sockets
from .. import base from .. import base
from ... import managed_objs
class PointDipoleSourceNode(base.MaxwellSimNode): class PointDipoleSourceNode(base.MaxwellSimNode):
node_type = ct.NodeType.PointDipoleSource node_type = ct.NodeType.PointDipoleSource
@ -27,6 +28,13 @@ class PointDipoleSourceNode(base.MaxwellSimNode):
"Source": sockets.MaxwellSourceSocketDef(), "Source": sockets.MaxwellSourceSocketDef(),
} }
managed_obj_defs = {
"sphere_empty": ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLObject(name),
name_prefix="point_dipole_",
)
}
#################### ####################
# - Properties # - Properties
#################### ####################
@ -77,6 +85,39 @@ class PointDipoleSourceNode(base.MaxwellSimNode):
) )
return _res return _res
####################
# - Preview
####################
@base.on_value_changed(
socket_name="Center",
input_sockets={"Center"},
managed_objs={"sphere_empty"},
)
def on_value_changed__center(
self,
input_sockets: dict,
managed_objs: dict[str, ct.schemas.ManagedObj],
):
_center = input_sockets["Center"]
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
## TODO: Preview unit system?? Presume um for now
mobj = managed_objs["sphere_empty"]
bl_object = mobj.bl_object("EMPTY")
bl_object.location = center #tuple([float(el) for el in center])
@base.on_show_preview(
managed_objs={"sphere_empty"},
)
def on_show_preview(
self,
managed_objs: dict[str, ct.schemas.ManagedObj],
):
managed_objs["sphere_empty"].show_preview(
"EMPTY",
empty_display_type="SPHERE",
)
#################### ####################

View File

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

View File

@ -1,3 +1,5 @@
import typing as typ
import tidy3d as td import tidy3d as td
import numpy as np import numpy as np
import sympy as sp import sympy as sp
@ -7,290 +9,174 @@ import bpy
from bpy_types import bpy_types from bpy_types import bpy_types
import bmesh import bmesh
from ... import contracts from .....utils import analyze_geonodes
from ... import bl_socket_map
from ... import contracts as ct
from ... import sockets from ... import sockets
from .. import base from .. import base
from ... import managed_objs
GEONODES_MODIFIER_NAME = "BLMaxwell_GeoNodes" class GeoNodesStructureNode(base.MaxwellSimNode):
node_type = ct.NodeType.GeoNodesStructure
# Monkey-Patch Sympy Types
## TODO: This needs to be a more generic thing, this isn't the only place we're setting blender interface values.
def parse_scalar(scalar):
if isinstance(scalar, sp.Integer):
return int(scalar)
elif isinstance(scalar, sp.Float):
return float(scalar)
elif isinstance(scalar, sp.Rational):
return float(scalar)
elif isinstance(scalar, sp.Expr):
return float(scalar.n())
return scalar
def parse_bl_to_sp(scalar):
if isinstance(scalar, bpy_types.bpy_prop_array):
return sp.Matrix(tuple(scalar))
return scalar
class GeoNodesStructureNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.GeoNodesStructure
bl_label = "GeoNodes Structure" bl_label = "GeoNodes Structure"
#bl_icon = ...
#################### ####################
# - Sockets # - Sockets
#################### ####################
input_sockets = { input_sockets = {
"preview_target": sockets.BlenderPreviewTargetSocketDef( "Unit System": sockets.PhysicalUnitSystemSocketDef(),
label="Preview Target", "Medium": sockets.MaxwellMediumSocketDef(),
), "GeoNodes": sockets.BlenderGeoNodesSocketDef(),
"blender_unit_system": sockets.PhysicalUnitSystemSocketDef(
label="Blender Units",
),
"medium": sockets.MaxwellMediumSocketDef(
label="Medium",
),
"geo_nodes": sockets.BlenderGeoNodesSocketDef(
label="GeoNodes",
),
} }
output_sockets = { output_sockets = {
"structure": sockets.MaxwellStructureSocketDef( "Structure": sockets.MaxwellStructureSocketDef(),
label="Structure", }
),
managed_obj_defs = {
"geometry": ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLObject(name),
name_prefix="geonodes_",
)
} }
#################### ####################
# - Output Socket Computation # - Output Socket Computation
#################### ####################
@base.computes_output_socket("structure") @base.computes_output_socket(
def compute_simulation(self: contracts.NodeTypeProtocol) -> td.TriangleMesh: "Structure",
# Extract the Blender Object input_sockets={"Medium"},
bl_object = self.compute_input("object") managed_objs={"geometry"},
)
def compute_structure(
self,
input_sockets: dict[str, typ.Any],
managed_objs: dict[str, ct.schemas.ManagedObj],
) -> td.Structure:
# Extract the Managed Blender Object
mobj = managed_objs["geometry"]
# Ensure Updated Geometry # Extract Geometry as Arrays
bpy.context.view_layer.update() geometry_as_arrays = mobj.mesh_as_arrays
# Triangulate Object Mesh
bmesh_mesh = bmesh.new()
bmesh_mesh.from_object(
bl_object,
bpy.context.evaluated_depsgraph_get(),
)
bmesh.ops.triangulate(bmesh_mesh, faces=bmesh_mesh.faces)
mesh = bpy.data.meshes.new(name="TriangulatedMesh")
bmesh_mesh.to_mesh(mesh)
bmesh_mesh.free()
# Extract Vertices and Faces
vertices = np.array([vert.co for vert in mesh.vertices])
faces = np.array([
[vert for vert in poly.vertices]
for poly in mesh.polygons
])
# Remove Temporary Mesh
bpy.data.meshes.remove(mesh)
# Return TriMesh Structure
return td.Structure( return td.Structure(
geometry=td.TriangleMesh.from_vertices_faces(vertices, faces), geometry=td.TriangleMesh.from_vertices_faces(
medium=self.compute_input("medium") geometry_as_arrays["verts"],
geometry_as_arrays["faces"],
),
medium=input_sockets["Medium"],
) )
#################### ####################
# - Update Function # - Event Methods
#################### ####################
def free(self) -> None: @base.on_value_changed(
bl_socket = self.g_input_bl_socket("preview_target") socket_name="GeoNodes",
bl_socket.free() managed_objs={"geometry"},
input_sockets={"GeoNodes"},
)
def on_value_changed__geonodes(
self,
managed_objs: dict[str, ct.schemas.ManagedObj],
input_sockets: dict[str, typ.Any],
) -> None:
"""Called whenever the GeoNodes socket is changed.
def update_cb(self) -> None: Refreshes the Loose Input Sockets, which map directly to the GeoNodes tree input sockets.
bl_object = self.compute_input("preview_target") """
if bl_object is None: return if not (geo_nodes := input_sockets["GeoNodes"]):
managed_objs["geometry"].free()
self.loose_input_sockets = {}
return
geo_nodes = self.compute_input("geo_nodes") # Analyze GeoNodes
if geo_nodes is None: return ## Extract Valid Inputs (via GeoNodes Tree "Interface")
geonodes_interface = analyze_geonodes.interface(
geo_nodes, direc="INPUT"
)
bl_modifier = bl_object.modifiers.get(GEONODES_MODIFIER_NAME) # Set Loose Input Sockets
if bl_modifier is None: return ## Retrieve the appropriate SocketDef for the Blender Interface Socket
self.loose_input_sockets = {
# Set GeoNodes Modifier Attributes socket_name: bl_socket_map.socket_def_from_bl_interface_socket(
for idx, interface_item in enumerate( bl_interface_socket
geo_nodes.interface.items_tree.values() )() ## === <SocketType>SocketDef(), but with dynamic SocketDef
): for socket_name, bl_interface_socket in geonodes_interface.items()
if idx == 0: continue ## Always-on "Geometry" Input (from Object)
# Retrieve Input Socket
bl_socket = self.inputs[
interface_item.name
]
# Retrieve Linked/Unlinked Input Socket Value
if bl_socket.is_linked:
linked_bl_socket = bl_socket.links[0].from_socket
linked_bl_node = bl_socket.links[0].from_node
val = linked_bl_node.compute_output(
linked_bl_node.g_output_socket_name(
linked_bl_socket.name
)
) ## What a bunch of spaghetti
else:
val = bl_socket.default_value
# Retrieve Unit-System Corrected Modifier Value
bl_unit_system = self.compute_input("blender_unit_system")
socket_type = contracts.SocketType[
bl_socket.bl_idname.removesuffix("SocketType")
]
if socket_type in bl_unit_system:
unitless_val = spu.convert_to(
val,
bl_unit_system[socket_type],
) / bl_unit_system[socket_type]
else:
unitless_val = val
if isinstance(unitless_val, sp.matrices.MatrixBase):
unitless_val = tuple(
parse_scalar(scalar)
for scalar in unitless_val
)
else:
unitless_val = parse_scalar(unitless_val)
# Conservatively Set Differing Values
if bl_modifier[interface_item.identifier] != unitless_val:
bl_modifier[interface_item.identifier] = unitless_val
# Update DepGraph
bl_object.data.update()
def update_sockets_from_geonodes(self) -> None:
# Remove All "Loose" Sockets
socket_labels = {
socket_def.label
for socket_def in self.input_sockets.values()
} | {
socket_def.label
for socket_set_name, socket_set in self.input_socket_sets.items()
for socket_name, socket_def in socket_set.items()
}
bl_sockets_to_remove = {
bl_socket
for bl_socket_name, bl_socket in self.inputs.items()
if bl_socket_name not in socket_labels
} }
for bl_socket in bl_sockets_to_remove: ## Set Loose `socket.value` from Interface `default_value`
self.inputs.remove(bl_socket) for socket_name in self.loose_input_sockets:
socket = self.inputs[socket_name]
bl_interface_socket = geonodes_interface[socket_name]
# Query for Blender Object / Geo Nodes socket.value = bl_socket_map.value_from_bl(bl_interface_socket)
bl_object = self.compute_input("preview_target")
if bl_object is None: return
# Remove Existing GeoNodes Modifier ## Implicitly triggers the loose-input `on_value_changed` for each.
if GEONODES_MODIFIER_NAME in bl_object.modifiers:
modifier_to_remove = bl_object.modifiers[GEONODES_MODIFIER_NAME]
bl_object.modifiers.remove(modifier_to_remove)
# Retrieve GeoNodes Tree @base.on_value_changed(
geo_nodes = self.compute_input("geo_nodes") any_loose_input_socket=True,
if geo_nodes is None: return
# Add Non-Static Sockets from GeoNodes managed_objs={"geometry"},
for bl_socket_name, bl_socket in geo_nodes.interface.items_tree.items(): input_sockets={"Unit System", "GeoNodes"},
# For now, don't allow Geometry inputs. )
if bl_socket.socket_type == "NodeSocketGeometry": continue def on_value_changed__loose_inputs(
self,
managed_objs: dict[str, ct.schemas.ManagedObj],
input_sockets: dict[str, typ.Any],
loose_input_sockets: dict[str, typ.Any],
):
"""Called whenever a Loose Input Socket is altered.
# Establish Dimensions of GeoNodes Input Sockets Synchronizes the change to the actual GeoNodes modifier, so that the change is immediately visible.
if ( """
bl_socket.description.startswith("2D") # Retrieve Data
): unit_system = input_sockets["Unit System"]
dimensions = 2 mobj = managed_objs["geometry"]
elif (
bl_socket.socket_type.startswith("NodeSocketVector")
or bl_socket.socket_type.startswith("NodeSocketColor")
or bl_socket.socket_type.startswith("NodeSocketRotation")
):
dimensions = 3
else:
dimensions = 1
# Choose Socket via. Description Hint (if exists) if not (geo_nodes := input_sockets["GeoNodes"]): return
if (
":" in bl_socket.description
and "(" in (desc_hint := bl_socket.description.split(":")[0])
and ")" in desc_hint
):
for tag in contracts.BLNodeSocket_to_SocketType_by_desc[
dimensions
]:
if desc_hint.startswith(tag):
self.inputs.new(
contracts.BLNodeSocket_to_SocketType_by_desc[
dimensions
][tag],
bl_socket_name,
)
if len([ # Analyze GeoNodes Interface (input direction)
(unit := _unit) ## This retrieves NodeTreeSocketInterface elements
for _unit in contracts.SocketType_to_units[ geonodes_interface = analyze_geonodes.interface(
contracts.SocketType[ geo_nodes, direc="INPUT"
self.inputs[bl_socket_name].bl_idname.removesuffix("SocketType") )
]
]["values"].values()
if desc_hint[
desc_hint.find("(")+1 : desc_hint.find(")")
] == str(_unit)
]) > 0:
self.inputs[bl_socket_name].unit = unit
elif bl_socket.socket_type in contracts.BLNodeSocket_to_SocketType[ ## TODO: Check that Loose Sockets matches the Interface
dimensions ## - If the user deletes an interface socket, bad things will happen.
]: ## - We will try to set an identifier that doesn't exist!
self.inputs.new( ## - Instead, this should update the loose input sockets.
contracts.BLNodeSocket_to_SocketType[
dimensions ## Push Values to the GeoNodes Modifier
][bl_socket.socket_type], mobj.sync_geonodes_modifier(
bl_socket_name, geonodes_node_group=geo_nodes,
geonodes_identifier_to_value={
bl_interface_socket.identifier: bl_socket_map.value_to_bl(
bl_interface_socket,
loose_input_sockets[socket_name],
unit_system,
) )
for socket_name, bl_interface_socket in (
geonodes_interface.items()
)
}
)
####################
# - Event Methods
####################
@base.on_show_preview(
managed_objs={"geometry"},
)
def on_show_preview(
self,
managed_objs: dict[str, ct.schemas.ManagedObj],
):
"""Called whenever a Loose Input Socket is altered.
# Create New GeoNodes Modifier Synchronizes the change to the actual GeoNodes modifier, so that the change is immediately visible.
if GEONODES_MODIFIER_NAME not in bl_object.modifiers: """
modifier = bl_object.modifiers.new( managed_objs["geometry"].show_preview("MESH")
name=GEONODES_MODIFIER_NAME,
type="NODES",
)
modifier.node_group = geo_nodes
# Set Default Values
for interface_item in geo_nodes.interface.items_tree.values():
if (
interface_item.name in self.inputs
and hasattr(interface_item, "default_value")
):
bl_socket = self.inputs[
interface_item.name
]
if hasattr(bl_socket, "use_units"):
bl_unit_system = self.compute_input("blender_unit_system")
socket_type = contracts.SocketType[
bl_socket.bl_idname.removesuffix("SocketType")
]
bl_socket.default_value = (
parse_bl_to_sp(interface_item.default_value)
* bl_unit_system[socket_type]
)
else:
bl_socket.default_value = parse_bl_to_sp(interface_item.default_value)
#################### ####################
@ -300,7 +186,7 @@ BL_REGISTER = [
GeoNodesStructureNode, GeoNodesStructureNode,
] ]
BL_NODES = { BL_NODES = {
contracts.NodeType.GeoNodesStructure: ( ct.NodeType.GeoNodesStructure: (
contracts.NodeCategory.MAXWELLSIM_STRUCTURES ct.NodeCategory.MAXWELLSIM_STRUCTURES
) )
} }

View File

@ -1,7 +1,9 @@
from . import base
from . import basic from . import basic
AnySocketDef = basic.AnySocketDef AnySocketDef = basic.AnySocketDef
BoolSocketDef = basic.BoolSocketDef BoolSocketDef = basic.BoolSocketDef
TextSocketDef = basic.TextSocketDef StringSocketDef = basic.StringSocketDef
FilePathSocketDef = basic.FilePathSocketDef FilePathSocketDef = basic.FilePathSocketDef
from . import number from . import number
@ -40,8 +42,8 @@ BlenderGeoNodesSocketDef = blender.BlenderGeoNodesSocketDef
BlenderTextSocketDef = blender.BlenderTextSocketDef BlenderTextSocketDef = blender.BlenderTextSocketDef
from . import maxwell from . import maxwell
MaxwellBoundBoxSocketDef = maxwell.MaxwellBoundBoxSocketDef MaxwellBoundCondSocketDef = maxwell.MaxwellBoundCondSocketDef
MaxwellBoundFaceSocketDef = maxwell.MaxwellBoundFaceSocketDef MaxwellBoundCondsSocketDef = maxwell.MaxwellBoundCondsSocketDef
MaxwellMediumSocketDef = maxwell.MaxwellMediumSocketDef MaxwellMediumSocketDef = maxwell.MaxwellMediumSocketDef
MaxwellMediumNonLinearitySocketDef = maxwell.MaxwellMediumNonLinearitySocketDef MaxwellMediumNonLinearitySocketDef = maxwell.MaxwellMediumNonLinearitySocketDef
MaxwellSourceSocketDef = maxwell.MaxwellSourceSocketDef MaxwellSourceSocketDef = maxwell.MaxwellSourceSocketDef

View File

@ -170,6 +170,19 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
def value(self, value: typ.Any) -> None: def value(self, value: typ.Any) -> None:
raise NotImplementedError raise NotImplementedError
def value_as_unit_system(
self,
unit_system: dict,
dimensionless: bool = True
) -> typ.Any:
## TODO: Caching could speed this boi up quite a bit
unit_system_unit = unit_system[self.socket_type]
return spu.convert_to(
self.value,
unit_system_unit,
) / unit_system_unit
@property @property
def lazy_value(self) -> None: def lazy_value(self) -> None:
raise NotImplementedError raise NotImplementedError

View File

@ -1,19 +1,19 @@
from . import any_socket from . import any as any_socket
AnySocketDef = any_socket.AnySocketDef AnySocketDef = any_socket.AnySocketDef
from . import bool_socket from . import bool as bool_socket
BoolSocketDef = bool_socket.BoolSocketDef BoolSocketDef = bool_socket.BoolSocketDef
from . import text_socket from . import string
TextSocketDef = text_socket.TextSocketDef StringSocketDef = string.StringSocketDef
from . import file_path_socket from . import file_path
FilePathSocketDef = file_path_socket.FilePathSocketDef FilePathSocketDef = file_path.FilePathSocketDef
BL_REGISTER = [ BL_REGISTER = [
*any_socket.BL_REGISTER, *any_socket.BL_REGISTER,
*bool_socket.BL_REGISTER, *bool_socket.BL_REGISTER,
*text_socket.BL_REGISTER, *string.BL_REGISTER,
*file_path_socket.BL_REGISTER, *file_path.BL_REGISTER,
] ]

View File

@ -24,7 +24,6 @@ class FilePathBLSocket(base.MaxwellSimSocket):
subtype="FILE_PATH", subtype="FILE_PATH",
update=(lambda self, context: self.sync_prop("raw_value", context)), update=(lambda self, context: self.sync_prop("raw_value", context)),
) )
## TODO: Use bpy methods to constrain the path
#################### ####################
# - Socket UI # - Socket UI

View File

@ -10,16 +10,16 @@ from ... import contracts as ct
#################### ####################
# - Blender Socket # - Blender Socket
#################### ####################
class TextBLSocket(base.MaxwellSimSocket): class StringBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.Text socket_type = ct.SocketType.String
bl_label = "Text" bl_label = "String"
#################### ####################
# - Properties # - Properties
#################### ####################
raw_value: bpy.props.StringProperty( raw_value: bpy.props.StringProperty(
name="Text", name="String",
description="Represents some text", description="Represents a string",
default="", default="",
update=(lambda self, context: self.sync_prop("raw_value", context)), update=(lambda self, context: self.sync_prop("raw_value", context)),
) )
@ -28,8 +28,6 @@ class TextBLSocket(base.MaxwellSimSocket):
# - Socket UI # - Socket UI
#################### ####################
def draw_label_row(self, label_col_row: bpy.types.UILayout, text: str) -> None: def draw_label_row(self, label_col_row: bpy.types.UILayout, text: str) -> None:
"""Draw the value of the real number.
"""
label_col_row.prop(self, "raw_value", text=text) label_col_row.prop(self, "raw_value", text=text)
#################### ####################
@ -46,17 +44,17 @@ class TextBLSocket(base.MaxwellSimSocket):
#################### ####################
# - Socket Configuration # - Socket Configuration
#################### ####################
class TextSocketDef(pyd.BaseModel): class StringSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.Text socket_type: ct.SocketType = ct.SocketType.String
default_text: str = "" default_text: str = ""
def init(self, bl_socket: TextBLSocket) -> None: def init(self, bl_socket: StringBLSocket) -> None:
bl_socket.value = self.default_text bl_socket.value = self.default_text
#################### ####################
# - Blender Registration # - Blender Registration
#################### ####################
BL_REGISTER = [ BL_REGISTER = [
TextBLSocket, StringBLSocket,
] ]

View File

@ -1,22 +1,21 @@
from . import object_socket from . import object as object_socket
from . import collection_socket from . import collection
BlenderObjectSocketDef = object_socket.BlenderObjectSocketDef BlenderObjectSocketDef = object_socket.BlenderObjectSocketDef
BlenderCollectionSocketDef = collection_socket.BlenderCollectionSocketDef BlenderCollectionSocketDef = collection.BlenderCollectionSocketDef
from . import image_socket from . import image
BlenderImageSocketDef = image_socket.BlenderImageSocketDef BlenderImageSocketDef = image.BlenderImageSocketDef
from . import geonodes_socket from . import geonodes
from . import text_socket from . import text
BlenderGeoNodesSocketDef = geonodes_socket.BlenderGeoNodesSocketDef BlenderGeoNodesSocketDef = geonodes.BlenderGeoNodesSocketDef
BlenderTextSocketDef = text_socket.BlenderTextSocketDef BlenderTextSocketDef = text.BlenderTextSocketDef
BL_REGISTER = [ BL_REGISTER = [
*object_socket.BL_REGISTER, *object_socket.BL_REGISTER,
*collection_socket.BL_REGISTER, *collection.BL_REGISTER,
*image_socket.BL_REGISTER, *text.BL_REGISTER,
*image.BL_REGISTER,
*geonodes_socket.BL_REGISTER, *geonodes.BL_REGISTER,
*text_socket.BL_REGISTER,
] ]

View File

@ -18,7 +18,7 @@ class BlenderCollectionBLSocket(base.MaxwellSimSocket):
#################### ####################
raw_value: bpy.props.PointerProperty( raw_value: bpy.props.PointerProperty(
name="Blender Collection", name="Blender Collection",
description="Represents a Blender collection", description="A Blender collection",
type=bpy.types.Collection, type=bpy.types.Collection,
update=(lambda self, context: self.sync_prop("raw_value", context)), update=(lambda self, context: self.sync_prop("raw_value", context)),
) )

View File

@ -13,8 +13,17 @@ class BlenderMaxwellResetGeoNodesSocket(bpy.types.Operator):
bl_idname = "blender_maxwell.reset_geo_nodes_socket" bl_idname = "blender_maxwell.reset_geo_nodes_socket"
bl_label = "Reset GeoNodes Socket" bl_label = "Reset GeoNodes Socket"
node_tree_name: bpy.props.StringProperty(name="Node Tree Name")
node_name: bpy.props.StringProperty(name="Node Name")
socket_name: bpy.props.StringProperty(name="Socket Name")
def execute(self, context): def execute(self, context):
context.socket.update_geonodes_node() node_tree = bpy.data.node_groups[self.node_tree_name]
node = node_tree.nodes[self.node_name]
socket = node.inputs[self.socket_name]
# Report as though the GeoNodes Tree Changed
socket.sync_prop("raw_value", context)
return {'FINISHED'} return {'FINISHED'}
@ -33,7 +42,7 @@ class BlenderGeoNodesBLSocket(base.MaxwellSimSocket):
name="Blender GeoNodes Tree", name="Blender GeoNodes Tree",
description="Represents a Blender GeoNodes Tree", description="Represents a Blender GeoNodes Tree",
type=bpy.types.NodeTree, type=bpy.types.NodeTree,
poll=(lambda self, obj: obj.bl_idname == 'GeometryNodeTree'), poll=(lambda self, obj: obj.bl_idname == "GeometryNodeTree"),
update=(lambda self, context: self.sync_prop("raw_value", context)), update=(lambda self, context: self.sync_prop("raw_value", context)),
) )
@ -42,12 +51,16 @@ class BlenderGeoNodesBLSocket(base.MaxwellSimSocket):
#################### ####################
def draw_label_row(self, label_col_row, text): def draw_label_row(self, label_col_row, text):
label_col_row.label(text=text) label_col_row.label(text=text)
if self.raw_value: if not self.raw_value: return
label_col_row.operator(
"blender_maxwell.reset_geo_nodes_socket", op = label_col_row.operator(
text="", BlenderMaxwellResetGeoNodesSocket.bl_idname,
icon="FILE_REFRESH", text="",
) icon="FILE_REFRESH",
)
op.socket_name = self.name
op.node_name = self.node.name
op.node_tree_name = self.node.id_data.name
#################### ####################
# - UI # - UI

View File

@ -7,26 +7,22 @@ from .. import base
from ... import contracts as ct from ... import contracts as ct
#################### ####################
# - Blender Socket # - Create and Assign BL Object
#################### ####################
class BlenderMaxwellCreateAndAssignBLObject(bpy.types.Operator): class BlenderMaxwellCreateAndAssignBLObject(bpy.types.Operator):
bl_idname = "blender_maxwell.create_and_assign_bl_object" bl_idname = "blender_maxwell.create_and_assign_bl_object"
bl_label = "Create and Assign BL Object" bl_label = "Create and Assign BL Object"
## TODO: Refactor node_tree_name = bpy.props.StringProperty(name="Node Tree Name")
node_name = bpy.props.StringProperty(name="Node Name")
socket_name = bpy.props.StringProperty(name="Socket Name")
def execute(self, context): def execute(self, context):
mesh = bpy.data.meshes.new("GenMesh") node_tree = bpy.data.node_groups[self.node_tree_name]
new_bl_object = bpy.data.objects.new("GenObj", mesh) node = node_tree.nodes[self.node_name]
socket = node.inputs[self.socket_name]
context.collection.objects.link(new_bl_object) socket.create_and_assign_bl_object()
node = context.node
for bl_socket_name, bl_socket in node.inputs.items():
if isinstance(bl_socket, BlenderObjectBLSocket):
bl_socket.default_value = new_bl_object
if hasattr(node, "update_sockets_from_geonodes"):
node.update_sockets_from_geonodes()
return {'FINISHED'} return {'FINISHED'}
@ -52,15 +48,31 @@ class BlenderObjectBLSocket(base.MaxwellSimSocket):
#################### ####################
def draw_label_row(self, label_col_row, text): def draw_label_row(self, label_col_row, text):
label_col_row.label(text=text) label_col_row.label(text=text)
label_col_row.operator(
op = label_col_row.operator(
"blender_maxwell.create_and_assign_bl_object", "blender_maxwell.create_and_assign_bl_object",
text="", text="",
icon="ADD", icon="ADD",
) )
op.socket_name = self.name
op.node_name = self.node.name
op.node_tree_name = self.node.id_data.name
def draw_value(self, col: bpy.types.UILayout) -> None: def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, "raw_value", text="") col.prop(self, "raw_value", text="")
####################
# - Methods
####################
def create_and_assign_bl_object(self):
node_tree = self.node.id_data
mesh = bpy.data.meshes.new("MaxwellMesh")
new_bl_object = bpy.data.objects.new("MaxwellObject", mesh)
bpy.context.collection.objects.link(new_bl_object)
self.value = new_bl_object
#################### ####################
# - Default Value # - Default Value
#################### ####################

View File

@ -1,268 +0,0 @@
import typing as typ
import bpy
import sympy as sp
import pydantic as pyd
from .. import base
from ... import contracts
def mk_and_assign_target_bl_obj(bl_socket, node, node_tree):
# Create Mesh and Object
mesh = bpy.data.meshes.new("Mesh" + bl_socket.node.name)
new_bl_object = bpy.data.objects.new(bl_socket.node.name, mesh)
# Create Preview Collection and Object
if bl_socket.show_preview:
#if not node_tree.preview_collection:
# new_collection = bpy.data.collections.new("BLMaxwellPreview")
# node_tree.preview_collection = new_collection
#
# bpy.context.scene.collection.children.link(new_collection)
node_tree.preview_collection.objects.link(new_bl_object)
# Create Non-Preview Collection and Object
else:
#if not node_tree.non_preview_collection:
# new_collection = bpy.data.collections.new("BLMaxwellNonPreview")
# node_tree.non_preview_collection = new_collection
#
# bpy.context.scene.collection.children.link(new_collection)
node_tree.non_preview_collection.objects.link(new_bl_object)
bl_socket.local_target_object = new_bl_object
if hasattr(node, "update_sockets_from_geonodes"):
node.update_sockets_from_geonodes()
class BlenderMaxwellCreateAndAssignTargetBLObject(bpy.types.Operator):
bl_idname = "blender_maxwell.create_and_assign_target_bl_object"
bl_label = "Create and Assign Target BL Object"
def execute(self, context):
bl_socket = context.socket
node = bl_socket.node
node_tree = node.id_data
mk_and_assign_target_bl_obj(bl_socket, node, node_tree)
return {'FINISHED'}
####################
# - Blender Socket
####################
class BlenderPreviewTargetBLSocket(base.BLSocket):
socket_type = contracts.SocketType.BlenderPreviewTarget
bl_label = "BlenderPreviewTarget"
####################
# - Properties
####################
show_preview: bpy.props.BoolProperty(
name="Target Object Included in Preview",
description="Whether or not Blender will preview the target object",
default=True,
update=(lambda self, context: self.update_preview()),
)
show_definition: bpy.props.BoolProperty(
name="Show Unit System Definition",
description="Toggle to show unit system definition",
default=False,
update=(lambda self, context: self.trigger_updates()),
)
target_object_pinned: bpy.props.BoolProperty(
name="Target Object Pinned",
description="Whether or not Blender will manage the target object",
default=True,
)
preview_collection_pinned: bpy.props.BoolProperty(
name="Global Preview Collection Pinned",
description="Whether or not Blender will use the global preview collection",
default=True,
)
non_preview_collection_pinned: bpy.props.BoolProperty(
name="Global Non-Preview Collection Pinned",
description="Whether or not Blender will use the global non-preview collection",
default=True,
)
local_target_object: bpy.props.PointerProperty(
name="Local Target Blender Object",
description="Represents a Blender object to apply a preview to",
type=bpy.types.Object,
update=(lambda self, context: self.trigger_updates()),
)
local_preview_collection: bpy.props.PointerProperty(
name="Local Preview Collection",
description="Collection of Blender objects that will be previewed",
type=bpy.types.Collection,
update=(lambda self, context: self.trigger_updates())
)
local_non_preview_collection: bpy.props.PointerProperty(
name="Local Non-Preview Collection",
description="Collection of Blender objects that will NOT be previewed",
type=bpy.types.Collection,
update=(lambda self, context: self.trigger_updates())
)
####################
# - Methods
####################
def update_preview(self):
node_tree = self.node.id_data
# Target Object Pinned
if (
self.show_preview
and self.local_target_object
and self.target_object_pinned
):
node_tree.non_preview_collection.objects.unlink(self.local_target_object)
node_tree.preview_collection.objects.link(self.local_target_object)
elif (
not self.show_preview
and self.local_target_object
and self.target_object_pinned
):
node_tree.preview_collection.objects.unlink(self.local_target_object)
node_tree.non_preview_collection.objects.link(self.local_target_object)
# Target Object Not Pinned
if (
self.show_preview
and self.local_target_object
and not self.target_object_pinned
and self.local_target_object.name in (
node_tree.non_preview_collection.objects.keys()
)
):
node_tree.non_preview_collection.objects.unlink(self.local_target_object)
node_tree.preview_collection.objects.link(self.local_target_object)
elif (
not self.show_preview
and self.local_target_object
and not self.target_object_pinned
and self.local_target_object.name in (
node_tree.preview_collection.objects.keys()
)
):
node_tree.preview_collection.objects.unlink(self.local_target_object)
node_tree.non_preview_collection.objects.link(self.local_target_object)
self.trigger_updates()
####################
# - UI
####################
def draw_label_row(self, label_col_row: bpy.types.UILayout, text) -> None:
label_col_row.label(text=text)
label_col_row.prop(self, "show_preview", toggle=True, text="", icon="SEQ_PREVIEW")
label_col_row.prop(self, "show_definition", toggle=True, text="", icon="MOD_LENGTH")
def draw_value(self, col: bpy.types.UILayout) -> None:
node_tree = self.node.id_data
if self.show_definition:
col_row = col.row(align=True)
col_row.alignment = "EXPAND"
col_row.label(text="Target", icon="OBJECT_DATA")
col_row.prop(
self,
"target_object_pinned",
toggle=True,
icon="EVENT_A",
icon_only=True,
)
#col_row.operator(
# "blender_maxwell.create_and_assign_target_bl_object",
# text="",
# icon="ADD",
#)
col_row=col.row(align=True)
col_row.alignment = "EXPAND"
if not self.target_object_pinned:
col_row.prop(self, "local_target_object", text="")
# Non-Preview Collection
col_row=col.row(align=True)
col_row.alignment = "EXPAND"
col_row.label(text="Enabled", icon="COLLECTION_COLOR_04")
col_row.prop(
self,
"preview_collection_pinned",
toggle=True,
icon="PINNED",
icon_only=True,
)
col_row=col.row(align=True)
col_row.alignment = "EXPAND"
if not self.preview_collection_pinned:
col_row.prop(self, "local_preview_collection", text="")
# Non-Preview Collection
col_row=col.row(align=True)
col_row.alignment = "EXPAND"
col_row.label(text="Disabled", icon="COLLECTION_COLOR_01")
col_row.prop(
self,
"non_preview_collection_pinned",
toggle=True,
icon="PINNED",
icon_only=True,
)
col_row=col.row(align=True)
col_row.alignment = "EXPAND"
if not self.non_preview_collection_pinned:
col_row.prop(self, "local_non_preview_collection", text="")
####################
# - Default Value
####################
@property
def default_value(self) -> bpy.types.Object:
node_tree = self.node.id_data
if not self.local_target_object and self.target_object_pinned:
mk_and_assign_target_bl_obj(self, self.node, node_tree)
return self.local_target_object
return self.local_target_object
@default_value.setter
def default_value(self, value: typ.Any) -> None:
pass
####################
# - Cleanup
####################
def free(self) -> None:
if self.local_target_object:
bpy.data.meshes.remove(self.local_target_object.data, do_unlink=True)
####################
# - Socket Configuration
####################
class BlenderPreviewTargetSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.BlenderPreviewTarget
label: str
show_preview: bool = True
def init(self, bl_socket: BlenderPreviewTargetBLSocket) -> None:
pass
#bl_socket.show_preview = self.show_preview
####################
# - Blender Registration
####################
BL_REGISTER = [
BlenderMaxwellCreateAndAssignTargetBLObject,
BlenderPreviewTargetBLSocket,
]

View File

@ -4,13 +4,13 @@ import bpy
import pydantic as pyd import pydantic as pyd
from .. import base from .. import base
from ... import contracts from ... import contracts as ct
#################### ####################
# - Blender Socket # - Blender Socket
#################### ####################
class BlenderTextBLSocket(base.MaxwellSimSocket): class BlenderTextBLSocket(base.MaxwellSimSocket):
socket_type = contracts.SocketType.BlenderText socket_type = ct.SocketType.BlenderText
bl_label = "Blender Text" bl_label = "Blender Text"
#################### ####################
@ -44,7 +44,7 @@ class BlenderTextBLSocket(base.MaxwellSimSocket):
# - Socket Configuration # - Socket Configuration
#################### ####################
class BlenderTextSocketDef(pyd.BaseModel): class BlenderTextSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.BlenderText socket_type: ct.SocketType = ct.SocketType.BlenderText
def init(self, bl_socket: BlenderTextBLSocket) -> None: def init(self, bl_socket: BlenderTextBLSocket) -> None:
pass pass

View File

@ -1,45 +1,45 @@
from . import bound_box_socket from . import bound_cond
from . import bound_face_socket from . import bound_conds
MaxwellBoundBoxSocketDef = bound_box_socket.MaxwellBoundBoxSocketDef MaxwellBoundCondSocketDef = bound_cond.MaxwellBoundCondSocketDef
MaxwellBoundFaceSocketDef = bound_face_socket.MaxwellBoundFaceSocketDef MaxwellBoundCondsSocketDef = bound_conds.MaxwellBoundCondsSocketDef
from . import medium_socket from . import medium
from . import medium_non_linearity_socket from . import medium_non_linearity
MaxwellMediumSocketDef = medium_socket.MaxwellMediumSocketDef MaxwellMediumSocketDef = medium.MaxwellMediumSocketDef
MaxwellMediumNonLinearitySocketDef = medium_non_linearity_socket.MaxwellMediumNonLinearitySocketDef MaxwellMediumNonLinearitySocketDef = medium_non_linearity.MaxwellMediumNonLinearitySocketDef
from . import source_socket from . import source
from . import temporal_shape_socket from . import temporal_shape
MaxwellSourceSocketDef = source_socket.MaxwellSourceSocketDef MaxwellSourceSocketDef = source.MaxwellSourceSocketDef
MaxwellTemporalShapeSocketDef = temporal_shape_socket.MaxwellTemporalShapeSocketDef MaxwellTemporalShapeSocketDef = temporal_shape.MaxwellTemporalShapeSocketDef
from . import structure_socket from . import structure
MaxwellStructureSocketDef = structure_socket.MaxwellStructureSocketDef MaxwellStructureSocketDef = structure.MaxwellStructureSocketDef
from . import monitor_socket from . import monitor
MaxwellMonitorSocketDef = monitor_socket.MaxwellMonitorSocketDef MaxwellMonitorSocketDef = monitor.MaxwellMonitorSocketDef
from . import fdtd_sim_socket from . import fdtd_sim
from . import sim_grid_socket from . import sim_grid
from . import sim_grid_axis_socket from . import sim_grid_axis
from . import sim_domain_socket from . import sim_domain
MaxwellFDTDSimSocketDef = fdtd_sim_socket.MaxwellFDTDSimSocketDef MaxwellFDTDSimSocketDef = fdtd_sim.MaxwellFDTDSimSocketDef
MaxwellSimGridSocketDef = sim_grid_socket.MaxwellSimGridSocketDef MaxwellSimGridSocketDef = sim_grid.MaxwellSimGridSocketDef
MaxwellSimGridAxisSocketDef = sim_grid_axis_socket.MaxwellSimGridAxisSocketDef MaxwellSimGridAxisSocketDef = sim_grid_axis.MaxwellSimGridAxisSocketDef
MaxwellSimDomainSocketDef = sim_domain_socket.MaxwellSimDomainSocketDef MaxwellSimDomainSocketDef = sim_domain.MaxwellSimDomainSocketDef
BL_REGISTER = [ BL_REGISTER = [
*bound_box_socket.BL_REGISTER, *bound_cond.BL_REGISTER,
*bound_face_socket.BL_REGISTER, *bound_conds.BL_REGISTER,
*medium_socket.BL_REGISTER, *medium.BL_REGISTER,
*medium_non_linearity_socket.BL_REGISTER, *medium_non_linearity.BL_REGISTER,
*source_socket.BL_REGISTER, *source.BL_REGISTER,
*temporal_shape_socket.BL_REGISTER, *temporal_shape.BL_REGISTER,
*structure_socket.BL_REGISTER, *structure.BL_REGISTER,
*monitor_socket.BL_REGISTER, *monitor.BL_REGISTER,
*fdtd_sim_socket.BL_REGISTER, *fdtd_sim.BL_REGISTER,
*sim_grid_socket.BL_REGISTER, *sim_grid.BL_REGISTER,
*sim_grid_axis_socket.BL_REGISTER, *sim_grid_axis.BL_REGISTER,
*sim_domain_socket.BL_REGISTER, *sim_domain.BL_REGISTER,
] ]

View File

@ -8,8 +8,8 @@ import tidy3d as td
from .. import base from .. import base
from ... import contracts as ct from ... import contracts as ct
class MaxwellBoundFaceBLSocket(base.MaxwellSimSocket): class MaxwellBoundCondBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.MaxwellBoundFace socket_type = ct.SocketType.MaxwellBoundCond
bl_label = "Maxwell Bound Face" bl_label = "Maxwell Bound Face"
#################### ####################
@ -53,17 +53,17 @@ class MaxwellBoundFaceBLSocket(base.MaxwellSimSocket):
#################### ####################
# - Socket Configuration # - Socket Configuration
#################### ####################
class MaxwellBoundFaceSocketDef(pyd.BaseModel): class MaxwellBoundCondSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.MaxwellBoundFace socket_type: ct.SocketType = ct.SocketType.MaxwellBoundCond
default_choice: typx.Literal["PML", "PEC", "PMC", "PERIODIC"] = "PML" default_choice: typx.Literal["PML", "PEC", "PMC", "PERIODIC"] = "PML"
def init(self, bl_socket: MaxwellBoundFaceBLSocket) -> None: def init(self, bl_socket: MaxwellBoundCondBLSocket) -> None:
bl_socket.value = self.default_choice bl_socket.value = self.default_choice
#################### ####################
# - Blender Registration # - Blender Registration
#################### ####################
BL_REGISTER = [ BL_REGISTER = [
MaxwellBoundFaceBLSocket, MaxwellBoundCondBLSocket,
] ]

View File

@ -20,8 +20,8 @@ BOUND_MAP = {
"PERIODIC": td.Periodic(), "PERIODIC": td.Periodic(),
} }
class MaxwellBoundBoxBLSocket(base.MaxwellSimSocket): class MaxwellBoundCondsBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.MaxwellBoundBox socket_type = ct.SocketType.MaxwellBoundConds
bl_label = "Maxwell Bound Box" bl_label = "Maxwell Bound Box"
#################### ####################
@ -126,15 +126,15 @@ class MaxwellBoundBoxBLSocket(base.MaxwellSimSocket):
#################### ####################
# - Socket Configuration # - Socket Configuration
#################### ####################
class MaxwellBoundBoxSocketDef(pyd.BaseModel): class MaxwellBoundCondsSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.MaxwellBoundBox socket_type: ct.SocketType = ct.SocketType.MaxwellBoundConds
def init(self, bl_socket: MaxwellBoundBoxBLSocket) -> None: def init(self, bl_socket: MaxwellBoundCondsBLSocket) -> None:
pass pass
#################### ####################
# - Blender Registration # - Blender Registration
#################### ####################
BL_REGISTER = [ BL_REGISTER = [
MaxwellBoundBoxBLSocket, MaxwellBoundCondsBLSocket,
] ]

View File

@ -1,19 +1,19 @@
from . import integer_number_socket from . import integer_number
IntegerNumberSocketDef = integer_number_socket.IntegerNumberSocketDef IntegerNumberSocketDef = integer_number.IntegerNumberSocketDef
from . import rational_number_socket from . import rational_number
RationalNumberSocketDef = rational_number_socket.RationalNumberSocketDef RationalNumberSocketDef = rational_number.RationalNumberSocketDef
from . import real_number_socket from . import real_number
RealNumberSocketDef = real_number_socket.RealNumberSocketDef RealNumberSocketDef = real_number.RealNumberSocketDef
from . import complex_number_socket from . import complex_number
ComplexNumberSocketDef = complex_number_socket.ComplexNumberSocketDef ComplexNumberSocketDef = complex_number.ComplexNumberSocketDef
BL_REGISTER = [ BL_REGISTER = [
*integer_number_socket.BL_REGISTER, *integer_number.BL_REGISTER,
*rational_number_socket.BL_REGISTER, *rational_number.BL_REGISTER,
*real_number_socket.BL_REGISTER, *real_number.BL_REGISTER,
*complex_number_socket.BL_REGISTER, *complex_number.BL_REGISTER,
] ]

View File

@ -79,11 +79,6 @@ class ComplexNumberBLSocket(base.MaxwellSimSocket):
- Polar: r,t -> re^(it) - Polar: r,t -> re^(it)
""" """
# (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 = { self.raw_value = {
"CARTESIAN": (sp.re(value), sp.im(value)), "CARTESIAN": (sp.re(value), sp.im(value)),
"POLAR": (sp.Abs(value), sp.arg(value)), "POLAR": (sp.Abs(value), sp.arg(value)),
@ -115,9 +110,11 @@ class ComplexNumberBLSocket(base.MaxwellSimSocket):
class ComplexNumberSocketDef(pyd.BaseModel): class ComplexNumberSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.ComplexNumber socket_type: ct.SocketType = ct.SocketType.ComplexNumber
default_value: SympyExpr = sp.S(0 + 0j)
coord_sys: typ.Literal["CARTESIAN", "POLAR"] = "CARTESIAN" coord_sys: typ.Literal["CARTESIAN", "POLAR"] = "CARTESIAN"
def init(self, bl_socket: ComplexNumberBLSocket) -> None: def init(self, bl_socket: ComplexNumberBLSocket) -> None:
bl_socket.value = self.default_value
bl_socket.coord_sys = self.coord_sys bl_socket.coord_sys = self.coord_sys
#################### ####################

View File

@ -58,8 +58,10 @@ class RationalNumberBLSocket(base.MaxwellSimSocket):
class RationalNumberSocketDef(pyd.BaseModel): class RationalNumberSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.RationalNumber socket_type: ct.SocketType = ct.SocketType.RationalNumber
default_value: SympyExpr = sp.Rational(0, 1)
def init(self, bl_socket: RationalNumberBLSocket) -> None: def init(self, bl_socket: RationalNumberBLSocket) -> None:
pass bl_socket.value = self.default_value
#################### ####################
# - Blender Registration # - Blender Registration

View File

@ -1,64 +1,64 @@
from . import unit_system_socket from . import unit_system
PhysicalUnitSystemSocketDef = unit_system_socket.PhysicalUnitSystemSocketDef PhysicalUnitSystemSocketDef = unit_system.PhysicalUnitSystemSocketDef
from . import time_socket from . import time
PhysicalTimeSocketDef = time_socket.PhysicalTimeSocketDef PhysicalTimeSocketDef = time.PhysicalTimeSocketDef
from . import angle_socket from . import angle
PhysicalAngleSocketDef = angle_socket.PhysicalAngleSocketDef PhysicalAngleSocketDef = angle.PhysicalAngleSocketDef
from . import length_socket from . import length
from . import area_socket from . import area
from . import volume_socket from . import volume
PhysicalLengthSocketDef = length_socket.PhysicalLengthSocketDef PhysicalLengthSocketDef = length.PhysicalLengthSocketDef
PhysicalAreaSocketDef = area_socket.PhysicalAreaSocketDef PhysicalAreaSocketDef = area.PhysicalAreaSocketDef
PhysicalVolumeSocketDef = volume_socket.PhysicalVolumeSocketDef PhysicalVolumeSocketDef = volume.PhysicalVolumeSocketDef
from . import point_3d_socket from . import point_3d
PhysicalPoint3DSocketDef = point_3d_socket.PhysicalPoint3DSocketDef PhysicalPoint3DSocketDef = point_3d.PhysicalPoint3DSocketDef
from . import size_3d_socket from . import size_3d
PhysicalSize3DSocketDef = size_3d_socket.PhysicalSize3DSocketDef PhysicalSize3DSocketDef = size_3d.PhysicalSize3DSocketDef
from . import mass_socket from . import mass
PhysicalMassSocketDef = mass_socket.PhysicalMassSocketDef PhysicalMassSocketDef = mass.PhysicalMassSocketDef
from . import speed_socket from . import speed
from . import accel_scalar_socket from . import accel_scalar
from . import force_scalar_socket from . import force_scalar
PhysicalSpeedSocketDef = speed_socket.PhysicalSpeedSocketDef PhysicalSpeedSocketDef = speed.PhysicalSpeedSocketDef
PhysicalAccelScalarSocketDef = accel_scalar_socket.PhysicalAccelScalarSocketDef PhysicalAccelScalarSocketDef = accel_scalar.PhysicalAccelScalarSocketDef
PhysicalForceScalarSocketDef = force_scalar_socket.PhysicalForceScalarSocketDef PhysicalForceScalarSocketDef = force_scalar.PhysicalForceScalarSocketDef
from . import pol_socket from . import pol
PhysicalPolSocketDef = pol_socket.PhysicalPolSocketDef PhysicalPolSocketDef = pol.PhysicalPolSocketDef
from . import freq_socket from . import freq
PhysicalFreqSocketDef = freq_socket.PhysicalFreqSocketDef PhysicalFreqSocketDef = freq.PhysicalFreqSocketDef
BL_REGISTER = [ BL_REGISTER = [
*unit_system_socket.BL_REGISTER, *unit_system.BL_REGISTER,
*time_socket.BL_REGISTER, *time.BL_REGISTER,
*angle_socket.BL_REGISTER, *angle.BL_REGISTER,
*length_socket.BL_REGISTER, *length.BL_REGISTER,
*area_socket.BL_REGISTER, *area.BL_REGISTER,
*volume_socket.BL_REGISTER, *volume.BL_REGISTER,
*point_3d_socket.BL_REGISTER, *point_3d.BL_REGISTER,
*size_3d_socket.BL_REGISTER, *size_3d.BL_REGISTER,
*mass_socket.BL_REGISTER, *mass.BL_REGISTER,
*speed_socket.BL_REGISTER, *speed.BL_REGISTER,
*accel_scalar_socket.BL_REGISTER, *accel_scalar.BL_REGISTER,
*force_scalar_socket.BL_REGISTER, *force_scalar.BL_REGISTER,
*pol_socket.BL_REGISTER, *pol.BL_REGISTER,
*freq_socket.BL_REGISTER, *freq.BL_REGISTER,
] ]

View File

@ -152,19 +152,19 @@ class PhysicalUnitSystemBLSocket(base.MaxwellSimSocket):
default=default_unit_key_for(ST.PhysicalForceScalar), default=default_unit_key_for(ST.PhysicalForceScalar),
update=(lambda self, context: self.sync_prop("unit_force_scalar", context)), update=(lambda self, context: self.sync_prop("unit_force_scalar", context)),
) )
unit_accel_3d_vector: bpy.props.EnumProperty( unit_accel_3d: bpy.props.EnumProperty(
name="Accel3D Unit", name="Accel3D Unit",
description="Unit of 3D vector acceleration", description="Unit of 3D vector acceleration",
items=contract_units_to_items(ST.PhysicalAccel3DVector), items=contract_units_to_items(ST.PhysicalAccel3D),
default=default_unit_key_for(ST.PhysicalAccel3DVector), default=default_unit_key_for(ST.PhysicalAccel3D),
update=(lambda self, context: self.sync_prop("unit_accel_3d_vector", context)), update=(lambda self, context: self.sync_prop("unit_accel_3d", context)),
) )
unit_force_3d_vector: bpy.props.EnumProperty( unit_force_3d: bpy.props.EnumProperty(
name="Force3D Unit", name="Force3D Unit",
description="Unit of 3D vector force", description="Unit of 3D vector force",
items=contract_units_to_items(ST.PhysicalForce3DVector), items=contract_units_to_items(ST.PhysicalForce3D),
default=default_unit_key_for(ST.PhysicalForce3DVector), default=default_unit_key_for(ST.PhysicalForce3D),
update=(lambda self, context: self.sync_prop("unit_force_3d_vector", context)), update=(lambda self, context: self.sync_prop("unit_force_3d", context)),
) )
unit_freq: bpy.props.EnumProperty( unit_freq: bpy.props.EnumProperty(
@ -226,24 +226,19 @@ class PhysicalUnitSystemBLSocket(base.MaxwellSimSocket):
col_row.label(text="Accel") col_row.label(text="Accel")
col_row.prop(self, "unit_accel_scalar", text="") col_row.prop(self, "unit_accel_scalar", text="")
#col_row.prop(self, "unit_accel_2d_vector", text="") #col_row.prop(self, "unit_accel_2d_vector", text="")
col_row.prop(self, "unit_accel_3d_vector", text="") col_row.prop(self, "unit_accel_3d", text="")
col_row=col.row(align=True) col_row=col.row(align=True)
col_row.label(text="Force") col_row.label(text="Force")
col_row.prop(self, "unit_force_scalar", text="") col_row.prop(self, "unit_force_scalar", text="")
#col_row.prop(self, "unit_force_2d_vector", text="") #col_row.prop(self, "unit_force_2d_vector", text="")
col_row.prop(self, "unit_force_3d_vector", text="") col_row.prop(self, "unit_force_3d", text="")
col_row=col.row(align=True) col_row=col.row(align=True)
col_row.alignment = "EXPAND" col_row.alignment = "EXPAND"
col_row.label(text="Freq") col_row.label(text="Freq")
col_row.prop(self, "unit_freq", text="") col_row.prop(self, "unit_freq", text="")
col_row=col.row(align=True)
col_row.alignment = "EXPAND"
col_row.label(text="Vac WL")
col_row.prop(self, "unit_vac_wl", text="")
#################### ####################
# - Default Value # - Default Value
#################### ####################
@ -270,11 +265,10 @@ class PhysicalUnitSystemBLSocket(base.MaxwellSimSocket):
(ST.PhysicalSpeed, self.unit_speed), (ST.PhysicalSpeed, self.unit_speed),
(ST.PhysicalAccelScalar, self.unit_accel_scalar), (ST.PhysicalAccelScalar, self.unit_accel_scalar),
(ST.PhysicalForceScalar, self.unit_force_scalar), (ST.PhysicalForceScalar, self.unit_force_scalar),
(ST.PhysicalAccel3DVector, self.unit_accel_3d_vector), (ST.PhysicalAccel3D, self.unit_accel_3d),
(ST.PhysicalForce3DVector, self.unit_force_3d_vector), (ST.PhysicalForce3D, self.unit_force_3d),
(ST.PhysicalFreq, self.unit_freq), (ST.PhysicalFreq, self.unit_freq),
(ST.PhysicalVacWL, self.unit_vac_wl),
] ]
} }

View File

@ -1,18 +1,18 @@
from . import real_2d_vector_socket from . import real_2d_vector
from . import complex_2d_vector_socket from . import complex_2d_vector
Real2DVectorSocketDef = real_2d_vector_socket.Real2DVectorSocketDef Real2DVectorSocketDef = real_2d_vector.Real2DVectorSocketDef
Complex2DVectorSocketDef = complex_2d_vector_socket.Complex2DVectorSocketDef Complex2DVectorSocketDef = complex_2d_vector.Complex2DVectorSocketDef
from . import real_3d_vector_socket from . import real_3d_vector
from . import complex_3d_vector_socket from . import complex_3d_vector
Real3DVectorSocketDef = real_3d_vector_socket.Real3DVectorSocketDef Real3DVectorSocketDef = real_3d_vector.Real3DVectorSocketDef
Complex3DVectorSocketDef = complex_3d_vector_socket.Complex3DVectorSocketDef Complex3DVectorSocketDef = complex_3d_vector.Complex3DVectorSocketDef
BL_REGISTER = [ BL_REGISTER = [
*real_2d_vector_socket.BL_REGISTER, *real_2d_vector.BL_REGISTER,
*complex_2d_vector_socket.BL_REGISTER, *complex_2d_vector.BL_REGISTER,
*real_3d_vector_socket.BL_REGISTER, *real_3d_vector.BL_REGISTER,
*complex_3d_vector_socket.BL_REGISTER, *complex_3d_vector.BL_REGISTER,
] ]

View File

@ -3,12 +3,12 @@ import bpy
from .operators import types as operators_types from .operators import types as operators_types
class BlenderMaxwellAddonPreferences(bpy.types.AddonPreferences): class BlenderMaxwellAddonPreferences(bpy.types.AddonPreferences):
bl_idname = "blender_maxwell" bl_idname = "blender_maxwell_preferences"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
layout.operator(operators_types.BlenderMaxwellInstallDependencies, text="Install Dependencies") layout.operator(operators_types.BlenderMaxwellInstallDependencies, text="Install Dependencies")
layout.operator(operators_types.BlenderMaxwellUninstallDependencies, text="Uninstall Dependencies") layout.operator(operators_types.BlenderMaxwellUninstallDependencies, text="Uninstall Dependencies")
#################### ####################
# - Blender Registration # - Blender Registration

View File

@ -0,0 +1,27 @@
import typing_extensions as typx
import bpy
INVALID_BL_SOCKET_TYPES = {
"NodeSocketGeometry",
}
def interface(
geo_nodes,
direc: typx.Literal["INPUT", "OUTPUT"],
):
"""Returns 'valid' GeoNodes interface sockets, meaning that:
- The Blender socket type is not something invalid (ex. "Geometry").
- The socket has a default value.
- The socket's direction (input/output) matches the requested direction.
"""
return {
interface_item_name: bl_interface_socket
for interface_item_name, bl_interface_socket in (
geo_nodes.interface.items_tree.items()
)
if all([
bl_interface_socket.socket_type not in INVALID_BL_SOCKET_TYPES,
hasattr(bl_interface_socket, "default_value"),
bl_interface_socket.in_out == direc,
])
}

View File

@ -33,8 +33,7 @@ class _SympyExpr:
) -> pyd_core_schema.CoreSchema: ) -> pyd_core_schema.CoreSchema:
def validate_from_str(value: str) -> AllowedSympyExprs: def validate_from_str(value: str) -> AllowedSympyExprs:
if not isinstance(value, str): if not isinstance(value, str):
msg = f"Value {value} is not a string" return value
raise ValueError(msg)
try: try:
return sp.sympify(value) return sp.sympify(value)
@ -52,19 +51,14 @@ class _SympyExpr:
return value return value
from_expr_schema = pyd_core_schema.chain_schema([ sympy_expr_schema = pyd_core_schema.chain_schema([
pyd_core_schema.no_info_plain_validator_function(validate_from_expr),
pyd_core_schema.no_info_plain_validator_function(validate_from_str), pyd_core_schema.no_info_plain_validator_function(validate_from_str),
pyd_core_schema.no_info_plain_validator_function(validate_from_expr),
pyd_core_schema.is_instance_schema(AllowedSympyExprs),
]) ])
return pyd_core_schema.json_or_python_schema( return pyd_core_schema.json_or_python_schema(
json_schema=from_expr_schema, json_schema=sympy_expr_schema,
python_schema=pyd_core_schema.union_schema( python_schema=sympy_expr_schema,
[
# check if it's an instance first before doing any further work
pyd_core_schema.is_instance_schema(AllowedSympyExprs),
from_expr_schema,
]
),
serialization=pyd_core_schema.plain_serializer_function_ser_schema( serialization=pyd_core_schema.plain_serializer_function_ser_schema(
lambda instance: str(instance) lambda instance: str(instance)
), ),

BIN
code/demo.blend (Stored with Git LFS)

Binary file not shown.