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
**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
[x] Wave Constant
- [ ] Implement export of frequency / wavelength ranges.
[ ] Unit System
- [ ] Implement export of frequency / wavelength array/range.
[-] Unit System
- [ ] Implement presets, including "Tidy3D" and "Blender", shown in the label row.
[ ] Constants / Blender Constant
[ ] Constants / Number Constant
[ ] Constants / Scientific Constant
[x] Constants / Number Constant
[ ] Constants / Physical Constant
- [ ] Pol: Elliptical plot 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
- [ ] 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.
## 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.
- [ ] 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
- [ ] Implement HDF-based export of Tidy3D-exported object (which includes ex. mesh data and such)
[ ] File Export / Array File Export
@ -41,16 +49,18 @@
## 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>)
- [ ] Project field values onto a plane object (managed)
## Sources
[ ] Temporal Shapes / Gaussian Pulse Temporal Shape
[ ] Temporal Shapes / Continuous Wave Temporal Shape
[x] Temporal Shapes / Gaussian Pulse Temporal Shape
[x] Temporal Shapes / Continuous Wave Temporal Shape
[ ] Temporal Shapes / Symbolic Temporal Shape
- [ ] Specify a Sympy function to generate appropriate array based on
[ ] Temporal Shapes / Array Temporal Shape
[ ] Point Dipole Source
[ ] Plane Wave Source
[x] Point Dipole 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.
[ ] Uniform Current Source
[ ] TFSF Source
@ -84,8 +94,9 @@
## Structures
[ ] BLObject Structure
[ ] GeoNodes Structure
- [ ] Use the modifier itself as memory, via the ManagedObj
[x] GeoNodes Structure
- [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.
[ ] Primitive Structures / Plane
@ -120,11 +131,11 @@
## Simulations
[-] FDTDSim
[-] Sim Domain
[x] Sim Domain
- [ ] By-Medium batching of Structures when building the td.Simulation object, which can have significant performance implications.
[-] Boundary Conds
- [ ] Rename from Bounds / BoundBox
[x] Boundary Conds
- [x] Rename from Bounds / BoundBox
[ ] Boundary Cond / PML Bound Face
- [ ] Implement dropdown for "Normal" and "Stable"
[ ] Boundary Cond / PEC Bound Face
@ -174,36 +185,59 @@
# 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
## Basic
[ ] Any
[ ] Bool
[ ] String
[x] Any
[x] Bool
[x] String
- [ ] 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
[ ] Object
[ ] Collection
[x] Object
- [ ] Implement default SocketDef object name
[x] Collection
- [ ] Implement default SocketDef collection name
[ ] Image
[x] Image
- [ ] Implement default SocketDef image name
[ ] GeoNodes
[ ] Text
[x] GeoNodes
- [ ] Implement default SocketDef geonodes name
[x] Text
- [ ] Implement default SocketDef object name
## Maxwell
[ ] Bound Conds
[x] Bound Conds
[ ] Bound Cond
[ ] Medium
[x] Medium
[ ] Medium Non-Linearity
[ ] Source
[x] Source
[ ] Temporal Shape
- [ ] Sane-default pulses for easy access.
[ ] Structure
[ ] Monitor
@ -217,47 +251,46 @@
[ ] Simulation Data
## Tidy3D
[ ] Cloud Task
## Number
[ ] Integer
[ ] Rational
[ ] Real
[ ] Complex
[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.
## 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/>)
[ ] Time
[x] Time
[ ] Angle
[x] Angle
[ ] Solid Angle (steradian)
[ ] Frequency (hertz)
[x] Frequency (hertz)
[ ] Angular Frequency (`rad*hertz`)
### Cartesian
[ ] Length
[ ] Area
[ ] Volume
[x] Length
[x] Area
[x] Volume
[ ] Point 1D
[ ] Point 2D
[ ] Point 3D
[x] Point 3D
[ ] Size 2D
[ ] Size 3D
[x] Size 3D
[ ] Rotation 3D
- [ ] Implement Euler methods
- [ ] Implement Quaternion methods
### Mechanical
[ ] Mass
[ ] Speed
[x] Speed
[ ] Velocity 3D
[ ] Acceleration Scalar
[x] Acceleration Scalar
[ ] Acceleration 3D
[ ] Force Scalar
[x] Force Scalar
[ ] Force 3D
[ ] Pressure
### Statistical
### Energy
[ ] Energy (joule)
[ ] Power (watt)
[ ] Temperature
@ -283,7 +316,7 @@
[ ] Illuminance (lux)
### Optical
[ ] Jones Polarization
[ ] Polarization
[ ] Polarization (Stokes)
@ -295,18 +328,21 @@
# Architecture
## Registration and Contracts
[ ] Finish the contract code converting from Blender sockets to our sockets based on dimensionality and the property description.
[ ] Refactor the node category code; it's ugly as all fuck.
[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.
[?] Would be nice with some kind of indicator somewhere to help set good socket descriptions when using geonodes and wanting units.
## Managed Objects
[ ] Implement modifier support on the managed BL object, with special attention paid to the needs of the GeoNodes socket.
- [ ] Implement preview toggling too, ex. using the relevant node tree collections
[x] Implement modifier support on the managed BL object, with special attention paid to the needs of the GeoNodes socket.
- [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.
[ ] Implement loading the xarray-defined voxels into OpenVDB, saving it, and loading it as a managed BL object with the volume setting.
[ ] Implement basic jax-driven volume voxel processing, especially cube based slicing.
[ ] Implement jax-driven linear interpolation of volume voxels to an image texture, whose pixels are sized according to the dimensions of another managed plane object (perhaps a uniquely described Managed BL object itself).
## 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
[ ] Dedicated `draw_preview`-type draw functions for plot customizations.
- [ ] 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
[ ] It seems that `node.inputs` and `node.outputs` allows the use of a `move` method, which may allow reordering sockets dynamically, which we should expose to the user as user-configurable ordering rules (maybe resolved with a constraint solver).
[?] Mechanism for dynamic names (ex. "Library Medium" becoming "Au Medium")
[ ] Mechanism for selecting a blender object managed by a particular node.
[-] Mechanism for selecting a blender object managed by a particular node.
[ ] 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
[ ] 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
## 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_colors import SOCKET_COLORS
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

View File

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

View File

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

View File

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

View File

@ -3,6 +3,8 @@ import typing as typx
import pydantic as pyd
import bpy
from ..bl import ManagedObjName, SocketName
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
ST.Any: (0.8, 0.8, 0.8, 1.0), # Light Grey
ST.Bool: (0.7, 0.7, 0.7, 1.0), # Medium Light Grey
ST.Text: (0.7, 0.7, 0.7, 1.0), # Medium Light Grey
ST.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.Secret: (0.0, 0.0, 0.0, 1.0), # Black
# Number
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.PhysicalAccelScalar: (0.7, 0.5, 0.3, 1.0), # Medium Orange
ST.PhysicalForceScalar: (0.6, 0.45, 0.25, 1.0), # Medium Dark Orange
ST.PhysicalAccel3DVector: (0.7, 0.5, 0.3, 1.0), # Medium Orange
ST.PhysicalForce3DVector: (0.6, 0.45, 0.25, 1.0), # Medium Dark Orange
ST.PhysicalAccel3D: (0.7, 0.5, 0.3, 1.0), # Medium 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.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.BlenderCollection: (0.6, 0.45, 0.9, 1.0), # Medium Light Purple
ST.BlenderImage: (0.5, 0.4, 0.8, 1.0), # Medium Purple
ST.BlenderVolume: (0.4, 0.35, 0.7, 1.0), # Medium Dark Purple
ST.BlenderGeoNodes: (0.3, 0.3, 0.6, 1.0), # Dark Purple
ST.BlenderText: (0.5, 0.5, 0.75, 1.0), # Light Lavender
ST.BlenderPreviewTarget: (0.5, 0.5, 0.75, 1.0), # Light Lavender
# Maxwell
ST.MaxwellSource: (1.0, 1.0, 0.5, 1.0), # Light Yellow
@ -59,8 +56,8 @@ SOCKET_COLORS = {
ST.MaxwellMedium: (0.8, 0.8, 0.4, 1.0), # Medium Yellow
ST.MaxwellMediumNonLinearity: (0.7, 0.7, 0.35, 1.0), # Medium Dark Yellow
ST.MaxwellStructure: (0.6, 0.6, 0.3, 1.0), # Dark Yellow
ST.MaxwellBoundBox: (0.9, 0.8, 0.5, 1.0), # Light Gold
ST.MaxwellBoundFace: (0.8, 0.7, 0.45, 1.0), # Medium Light Gold
ST.MaxwellBoundConds: (0.9, 0.8, 0.5, 1.0), # 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.MaxwellFDTDSim: (0.6, 0.5, 0.35, 1.0), # Medium 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
ST.Any: "CIRCLE",
ST.Bool: "CIRCLE",
ST.Text: "SQUARE",
ST.String: "SQUARE",
ST.FilePath: "SQUARE",
ST.Secret: "SQUARE",
# Number
ST.IntegerNumber: "CIRCLE",
@ -35,8 +34,8 @@ SOCKET_SHAPES = {
ST.PhysicalSpeed: "CIRCLE",
ST.PhysicalAccelScalar: "CIRCLE",
ST.PhysicalForceScalar: "CIRCLE",
ST.PhysicalAccel3DVector: "SQUARE_DOT",
ST.PhysicalForce3DVector: "SQUARE_DOT",
ST.PhysicalAccel3D: "SQUARE_DOT",
ST.PhysicalForce3D: "SQUARE_DOT",
ST.PhysicalPol: "DIAMOND",
ST.PhysicalFreq: "CIRCLE",
@ -44,10 +43,8 @@ SOCKET_SHAPES = {
ST.BlenderObject: "SQUARE",
ST.BlenderCollection: "SQUARE",
ST.BlenderImage: "DIAMOND",
ST.BlenderVolume: "DIAMOND",
ST.BlenderGeoNodes: "DIAMOND",
ST.BlenderText: "SQUARE",
ST.BlenderPreviewTarget: "SQUARE",
# Maxwell
ST.MaxwellSource: "CIRCLE",
@ -55,8 +52,8 @@ SOCKET_SHAPES = {
ST.MaxwellMedium: "CIRCLE",
ST.MaxwellMediumNonLinearity: "CIRCLE",
ST.MaxwellStructure: "SQUARE",
ST.MaxwellBoundBox: "SQUARE",
ST.MaxwellBoundFace: "DIAMOND",
ST.MaxwellBoundConds: "SQUARE",
ST.MaxwellBoundCond: "DIAMOND",
ST.MaxwellMonitor: "CIRCLE",
ST.MaxwellFDTDSim: "SQUARE",
ST.MaxwellSimGrid: "SQUARE",

View File

@ -9,9 +9,9 @@ class SocketType(BlenderTypeEnum):
# Base
Any = enum.auto()
Bool = enum.auto()
Text = enum.auto()
String = enum.auto()
FilePath = enum.auto()
Secret = enum.auto()
Color = enum.auto()
# Number
IntegerNumber = enum.auto()
@ -21,69 +21,116 @@ class SocketType(BlenderTypeEnum):
# Vector
Real2DVector = enum.auto()
Real2DVectorDir = enum.auto()
Complex2DVector = enum.auto()
Real3DVector = enum.auto()
Real3DVectorDir = enum.auto()
Complex3DVector = enum.auto()
# Physical
PhysicalUnitSystem = enum.auto()
PhysicalTime = enum.auto()
PhysicalAngle = enum.auto()
PhysicalLength = enum.auto()
PhysicalArea = enum.auto()
PhysicalVolume = enum.auto()
PhysicalPoint2D = enum.auto()
PhysicalPoint3D = enum.auto()
PhysicalSize2D = enum.auto()
PhysicalSize3D = enum.auto()
PhysicalMass = enum.auto()
PhysicalSpeed = enum.auto()
PhysicalAccelScalar = enum.auto()
PhysicalForceScalar = enum.auto()
PhysicalAccel3DVector = enum.auto()
PhysicalForce3DVector = enum.auto()
PhysicalPol = enum.auto()
PhysicalFreq = enum.auto()
# Blender
BlenderObject = enum.auto()
BlenderCollection = enum.auto()
BlenderImage = enum.auto()
BlenderVolume = enum.auto()
BlenderGeoNodes = enum.auto()
BlenderText = enum.auto()
BlenderPreviewTarget = enum.auto()
# Maxwell
MaxwellSource = enum.auto()
MaxwellTemporalShape = enum.auto()
MaxwellBoundConds = enum.auto()
MaxwellBoundCond = enum.auto()
MaxwellMedium = enum.auto()
MaxwellMediumNonLinearity = enum.auto()
MaxwellSource = enum.auto()
MaxwellTemporalShape = enum.auto()
MaxwellStructure = enum.auto()
MaxwellBoundBox = enum.auto()
MaxwellBoundFace = enum.auto()
MaxwellMonitor = enum.auto()
MaxwellFDTDSim = enum.auto()
MaxwellSimDomain = enum.auto()
MaxwellSimGrid = enum.auto()
MaxwellSimGridAxis = enum.auto()
MaxwellSimDomain = enum.auto()
# Tidy3D
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,
},
},
ST.PhysicalAccel3DVector: {
ST.PhysicalAccel3D: {
"default": "UM_S_SQ",
"values": {
"PM_S_SQ": spu.picometer / spu.second**2,
@ -206,7 +206,7 @@ SOCKET_UNITS = {
"FT_S_SQ": spu.feet / spu.second**2,
},
},
ST.PhysicalForce3DVector: {
ST.PhysicalForce3D: {
"default": "UNEWT",
"values": {
"KG_M_S_SQ": spu.kg * spu.m/spu.second**2,

View File

@ -27,14 +27,29 @@ class ManagedBLImage(ct.schemas.ManagedObj):
@name.setter
def name(self, value: str):
## TODO: Check that blender doesn't have any other images by the same name.
if (bl_image := bpy.data.images.get(self.name)):
bl_image.name = value
# Image Doesn't Exist
if not (bl_image := bpy.data.images.get(self._bl_image_name)):
# ...AND Desired Image Name is Not Taken
if not bpy.data.objects.get(value):
self._bl_image_name = value
return
# ...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):
if (bl_image := bpy.data.images.get(self.name)):
if not (bl_image := bpy.data.images.get(self.name)):
msg = "Can't free BL image that doesn't exist"
raise ValueError(msg)
bpy.data.images.remove(bl_image)
####################

View File

@ -13,42 +13,85 @@ import bmesh
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):
managed_obj_type = ct.ManagedObjType.ManagedBLObject
_bl_object_name: str
def __init__(self, name: str):
## TODO: Check that blender doesn't have any other objects by the same name.
self._bl_object_name = name
# Object Name
@property
def bl_object_name(self):
def name(self):
return self._bl_object_name
@bl_object_name.setter
def set_bl_object_name(self, value: str):
## TODO: Check that blender doesn't have any other objects by the same name.
if (bl_object := bpy.data.objects.get(self.bl_object_name)):
bl_object.name = value
@name.setter
def set_name(self, value: str) -> None:
# Object Doesn't Exist
if not (bl_object := bpy.data.objects.get(self._bl_object_name)):
# ...AND Desired Object Name is Not Taken
if not bpy.data.objects.get(value):
self._bl_object_name = value
return
# ...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
@property
def bl_mesh_name(self):
return self.bl_object_name + "Mesh"
return self.name
@property
def bl_volume_name(self):
return self.bl_object_name + "Volume"
return self.name
# Deallocation
def free(self):
if (bl_object := bpy.data.objects.get(self.bl_object_name)):
if not (bl_object := bpy.data.objects.get(self.name)):
return ## Nothing to do
# Delete the Underlying Datablock
## This automatically deletes the object too
if bl_object.type == "MESH":
bpy.data.meshes.remove(bl_object.data)
elif bl_object.type == "EMPTY":
bpy.data.meshes.remove(bl_object.data)
elif bl_object.type == "VOLUME":
bpy.data.volumes.remove(bl_object.data)
else:
@ -58,30 +101,58 @@ class ManagedBLObject(ct.schemas.ManagedObj):
####################
# - Actions
####################
def trigger_action(
def show_preview(
self,
action: typx.Literal["report", "enable_previews"],
):
if action == "report":
pass ## TODO: Cache invalidation.
kind: typx.Literal["MESH", "EMPTY", "VOLUME"],
empty_display_type: typx.Literal[
"PLAIN_AXES", "ARROWS", "SINGLE_ARROW", "CIRCLE", "CUBE",
"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:
"""Selects the managed Blender object globally, causing it to be ex.
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.data.objects['Suzanne'].select_set(True)
bl_object.select_set(True)
####################
# - Managed Object Management
####################
def bl_object(
self,
kind: typx.Literal["MESH", "VOLUME"],
kind: typx.Literal["MESH", "EMPTY", "VOLUME"],
):
"""Returns the managed blender object.
@ -90,27 +161,32 @@ class ManagedBLObject(ct.schemas.ManagedObj):
"""
# Remove Object (if mismatch)
if (
(bl_object := bpy.data.images.get(self.bl_object_name))
(bl_object := bpy.data.objects.get(self.name))
and bl_object.type != kind
):
self.free()
# Create Object w/Appropriate Data Block
if not (bl_object := bpy.data.images.get(self.bl_object_name)):
if bl_object.type == "MESH":
if not (bl_object := bpy.data.objects.get(self.name)):
if kind == "MESH":
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
else:
msg = f"Requested `bl_object` type {bl_object.type} is not valid"
raise ValueError(msg)
bl_object = bpy.data.objects.new(self.bl_object_name, bl_data)
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
####################
# - Data Properties
# - Mesh Data Properties
####################
@property
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.
"""
if (
(bl_object := bpy.data.objects.get(self.bl_object_name))
(bl_object := bpy.data.objects.get(self.name))
and bl_object.type == "MESH"
):
return bl_object.data
@ -128,13 +204,13 @@ class ManagedBLObject(ct.schemas.ManagedObj):
raise ValueError(msg)
@contextlib.contextmanager
def as_bmesh(
def mesh_as_bmesh(
self,
evaluate: bool = True,
triangulate: bool = False,
) -> bpy.types.Mesh:
if (
(bl_object := bpy.data.objects.get(self.bl_object_name))
(bl_object := bpy.data.objects.get(self.name))
and bl_object.type == "MESH"
):
bmesh_mesh = None
@ -156,14 +232,21 @@ class ManagedBLObject(ct.schemas.ManagedObj):
finally:
if bmesh_mesh: bmesh_mesh.free()
else:
msg = f"Requested BMesh from `bl_object` of type {bl_object.type}"
raise ValueError(msg)
@functools.cached_property
def as_arrays(self) -> dict:
@property
def mesh_as_arrays(self) -> dict:
## TODO: Cached
# Ensure Updated Geometry
bpy.context.view_layer.update()
## TODO: Must we?
# Compute Evaluted + Triangulated Mesh
_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)
# Optimized Vertex Copy
@ -186,6 +269,113 @@ class ManagedBLObject(ct.schemas.ManagedObj):
"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
#def volume(self) -> bpy.types.Volume:
# """Returns the object's volume data.

View File

@ -61,17 +61,6 @@ class MaxwellSimTree(bpy.types.NodeTree):
bl_label = "Maxwell Sim Editor"
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
####################
@ -81,6 +70,18 @@ class MaxwellSimTree(bpy.types.NodeTree):
for bl_socket in [*node.inputs, *node.outputs]:
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
####################
@ -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.
"""
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.
## - Else, the first link of the session will not update caches.
## - 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 outputs
@ -11,7 +11,7 @@ from . import simulations
#from . import utilities
BL_REGISTER = [
*kitchen_sink.BL_REGISTER,
#*kitchen_sink.BL_REGISTER,
*inputs.BL_REGISTER,
*outputs.BL_REGISTER,
*sources.BL_REGISTER,
@ -23,7 +23,7 @@ BL_REGISTER = [
# *utilities.BL_REGISTER,
]
BL_NODES = {
**kitchen_sink.BL_NODES,
#**kitchen_sink.BL_NODES,
**inputs.BL_NODES,
**outputs.BL_NODES,
**sources.BL_NODES,

View File

@ -67,7 +67,7 @@ class MaxwellSimNode(bpy.types.Node):
name="Sim Node Name",
description="The name of a particular MaxwellSimNode node, which can be used to help identify data managed by the node",
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
@ -151,7 +151,7 @@ class MaxwellSimNode(bpy.types.Node):
)
],
default=socket_set_names[0],
update=(lambda self, _: self._sync_sockets()),
update=(lambda self, _: self.sync_sockets()),
)
# Setup Preset Dropdown
@ -172,20 +172,23 @@ class MaxwellSimNode(bpy.types.Node):
],
default=list(cls.presets.keys())[0],
update=lambda self, context: (
self._sync_active_preset()()
self.sync_active_preset()()
),
)
####################
# - Generic Properties
####################
def _sync_sim_node_name(self, context):
for managed_obj in self.managed_objs.values():
managed_obj.name = self.sim_node_name
def sync_sim_node_name(self, context):
if (mobjs := CACHE[self.instance_id].get("managed_objs")) is None:
return
# Recurse Until Equal
if managed_obj.name != self.sim_node_name:
self.sim_node_name = managed_obj.name
for mobj_id, mobj in mobjs.items():
# Retrieve Managed Obj Definition
mobj_def = self.managed_obj_defs[mobj_id]
# Set Managed Obj Name
mobj.name = mobj_def.name_prefix + self.sim_node_name
## 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.
@ -205,7 +208,7 @@ class MaxwellSimNode(bpy.types.Node):
## - ManagedObjects MUST the same object by name.
## - We sync our 'sim_node_name' with all managed objects.
## - (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:
# Initialize the Managed Object Instance Cache
CACHE[self.instance_id]["managed_objs"] = {}
@ -304,7 +307,7 @@ class MaxwellSimNode(bpy.types.Node):
for model in deser.values()
],
"models": [
dict(model)
model.model_dump()
for model in deser.values()
if isinstance(model, pyd.BaseModel)
],
@ -336,7 +339,7 @@ class MaxwellSimNode(bpy.types.Node):
self.ser_loose_input_sockets = self._ser_loose_sockets(value)
# Synchronize Sockets
self._sync_sockets()
self.sync_sockets()
## TODO: Perhaps re-init() all loose sockets anyway?
@loose_output_sockets.setter
@ -346,7 +349,7 @@ class MaxwellSimNode(bpy.types.Node):
self.ser_loose_output_sockets = self._ser_loose_sockets(value)
# Synchronize Sockets
self._sync_sockets()
self.sync_sockets()
## 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():
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.
- Any non-existing active socket will be added and initialized.
@ -418,7 +421,7 @@ class MaxwellSimNode(bpy.types.Node):
####################
# - Preset Management
####################
def _sync_active_preset(self) -> None:
def sync_active_preset(self) -> None:
"""Applies the active preset by overwriting the value of
preset-defined input sockets.
"""
@ -555,7 +558,11 @@ class MaxwellSimNode(bpy.types.Node):
and socket_name == method._extra_data.get("changed_socket")
) or (
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)
@ -626,11 +633,11 @@ class MaxwellSimNode(bpy.types.Node):
## Only shown in draw_buttons if 'self.use_sim_node_name'
# Initialize Sockets
self._sync_sockets()
self.sync_sockets()
# Apply Default Preset
if self.active_preset:
self._sync_active_preset()
self.sync_active_preset()
def update(self) -> None:
pass
@ -672,6 +679,8 @@ def chain_event_decorator(
kind: ct.DataFlowKind = ct.DataFlowKind.Value,
input_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(),
managed_objs: set[str] = set(),
@ -715,6 +724,24 @@ def chain_event_decorator(
}
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
if props:
_props = {
@ -808,17 +835,25 @@ def computes_output_socket(
def on_value_changed(
socket_name: ct.SocketName | None = None,
prop_name: str | None = None,
any_loose_input_socket: bool = False,
kind: ct.DataFlowKind = ct.DataFlowKind.Value,
input_sockets: set[str] = set(),
props: set[str] = set(),
managed_objs: set[str] = set(),
):
if socket_name is not None and prop_name is not None:
msg = "Either socket_name or prop_name, not both"
if sum([
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)
req_params = {"self"} | (
{"input_sockets"} if input_sockets else set()
) | (
{"loose_input_sockets"} if any_loose_input_socket else set()
) | (
{"props"} if props else set()
) | (
@ -827,13 +862,14 @@ def on_value_changed(
return chain_event_decorator(
callback_type="on_value_changed",
index_by=(socket_name, prop_name),
extra_data={
"changed_socket": socket_name,
"changed_prop": prop_name,
"changed_loose_input": any_loose_input_socket,
},
kind=kind,
input_sockets=input_sockets,
loose_input_sockets=any_loose_input_socket,
props=props,
managed_objs=managed_objs,
req_params=req_params,
@ -841,8 +877,8 @@ def on_value_changed(
def on_show_preview(
kind: ct.DataFlowKind = ct.DataFlowKind.Value,
input_sockets: set[str] = set(), ## For now, presume
output_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 only same kind
props: set[str] = set(),
managed_objs: set[str] = set(),
):

View File

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

View File

@ -1,23 +1,26 @@
from . import wave_constant
#from . import unit_system
from . import importers
from . import constants
#from . import lists
#from . import scene
from . import web_importers
#from . import file_importers
BL_REGISTER = [
*importers.BL_REGISTER,
*wave_constant.BL_REGISTER,
# *unit_system.BL_REGISTER,
#
# *scene.BL_REGISTER,
*constants.BL_REGISTER,
#*lists.BL_REGISTER,
*web_importers.BL_REGISTER,
# *file_importers.BL_REGISTER,
]
BL_NODES = {
**importers.BL_NODES,
**wave_constant.BL_NODES,
# **unit_system.BL_NODES,
#
# **scene.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 number_constant
from . import number_constant
#from . import physical_constant
#from . import blender_constant
from . import blender_constant
BL_REGISTER = [
*wave_constant.BL_REGISTER,
# *scientific_constant.BL_REGISTER,
#
# *number_constant.BL_REGISTER,
*number_constant.BL_REGISTER,
# *physical_constant.BL_REGISTER,
# *blender_constant.BL_REGISTER,
*blender_constant.BL_REGISTER,
]
BL_NODES = {
**wave_constant.BL_NODES,
# **scientific_constant.BL_NODES,
#
# **number_constant.BL_NODES,
**number_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
from .... import contracts
from .... import contracts as ct
from .... import sockets
from ... import base
class BlenderConstantNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.BlenderConstant
class BlenderConstantNode(base.MaxwellSimNode):
node_type = ct.NodeType.BlenderConstant
bl_label = "Blender Constant"
#bl_icon = constants.ICON_SIM_INPUT
input_sockets = {}
input_socket_sets = {
"object": {
"value": sockets.BlenderObjectSocketDef(
label="Object",
),
"Object": {
"Value": sockets.BlenderObjectSocketDef(),
},
"collection": {
"value": sockets.BlenderCollectionSocketDef(
label="Collection",
),
"Collection": {
"Value": sockets.BlenderCollectionSocketDef(),
},
"image": {
"value": sockets.BlenderImageSocketDef(
label="Image",
),
"Text": {
"Value": sockets.BlenderTextSocketDef(),
},
"volume": {
"value": sockets.BlenderVolumeSocketDef(
label="Volume",
),
"Image": {
"Value": sockets.BlenderImageSocketDef(),
},
"text": {
"value": sockets.BlenderTextSocketDef(
label="Text",
),
},
"geonodes": {
"value": sockets.BlenderGeoNodesSocketDef(
label="GeoNodes",
),
"GeoNode Tree": {
"Value": sockets.BlenderGeoNodesSocketDef(),
},
}
output_sockets = {}
output_socket_sets = input_socket_sets
####################
# - Callbacks
####################
@base.computes_output_socket("value")
def compute_value(self: contracts.NodeTypeProtocol) -> typ.Any:
return self.compute_input("value")
@base.computes_output_socket(
"Value",
input_sockets={"Value"}
)
def compute_value(self, input_sockets) -> typ.Any:
return input_sockets["Value"]
@ -62,7 +46,7 @@ BL_REGISTER = [
BlenderConstantNode,
]
BL_NODES = {
contracts.NodeType.BlenderConstant: (
contracts.NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS
ct.NodeType.BlenderConstant: (
ct.NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS
)
}

View File

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

View File

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

View File

@ -46,7 +46,7 @@ class JSONFileExporterNode(base.MaxwellSimNode):
),
}
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 sockets
from .. import base
from ...managed_objs import managed_bl_object
class ConsoleViewOperator(bpy.types.Operator):
@ -50,17 +51,53 @@ class ViewerNode(base.MaxwellSimNode):
"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
####################
def draw_operators(self, context, layout):
row = layout.row(align=True)
row.label(text="Console")
row.operator(ConsoleViewOperator.bl_idname, text="Print")
split = layout.split(factor=0.4)
row = layout.row(align=True)
row.label(text="Plot")
row.operator(RefreshPlotViewOperator.bl_idname, text="", icon="FILE_REFRESH")
# Split LHS
col = split.column(align=False)
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
@ -75,12 +112,47 @@ class ViewerNode(base.MaxwellSimNode):
print(str(data))
####################
# - Update
# - Updates
####################
@base.on_value_changed(socket_name="Data")
def on_value_changed__data(self):
@base.on_value_changed(
socket_name="Data",
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")
####################
# - Blender Registration

View File

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

View File

@ -3,9 +3,13 @@ import sympy as sp
import sympy.physics.units as spu
import scipy as sc
from .....utils import analyze_geonodes
from ... import contracts as ct
from ... import sockets
from .. import base
from ... import managed_objs
GEONODES_DOMAIN_BOX = "domain_box"
class SimDomainNode(base.MaxwellSimNode):
node_type = ct.NodeType.SimDomain
@ -24,6 +28,13 @@ class SimDomainNode(base.MaxwellSimNode):
"Domain": sockets.MaxwellSimDomainSocketDef(),
}
managed_obj_defs = {
"domain_box": ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLObject(name),
name_prefix="domain_box_",
)
}
####################
# - Callbacks
####################
@ -47,6 +58,54 @@ class SimDomainNode(base.MaxwellSimNode):
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
####################

View File

@ -8,6 +8,7 @@ import bpy
from ... import contracts as ct
from ... import sockets
from .. import base
from ... import managed_objs
class PointDipoleSourceNode(base.MaxwellSimNode):
node_type = ct.NodeType.PointDipoleSource
@ -27,6 +28,13 @@ class PointDipoleSourceNode(base.MaxwellSimNode):
"Source": sockets.MaxwellSourceSocketDef(),
}
managed_obj_defs = {
"sphere_empty": ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLObject(name),
name_prefix="point_dipole_",
)
}
####################
# - Properties
####################
@ -77,6 +85,39 @@ class PointDipoleSourceNode(base.MaxwellSimNode):
)
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 geonodes_structure
#from . import scripted_structure
from . import geonodes_structure
from . import primitives
BL_REGISTER = [
# *object_structure.BL_REGISTER,
# *geonodes_structure.BL_REGISTER,
# *scripted_structure.BL_REGISTER,
*geonodes_structure.BL_REGISTER,
*primitives.BL_REGISTER,
]
BL_NODES = {
# **object_structure.BL_NODES,
# **geonodes_structure.BL_NODES,
# **scripted_structure.BL_NODES,
**geonodes_structure.BL_NODES,
**primitives.BL_NODES,
}

View File

@ -1,3 +1,5 @@
import typing as typ
import tidy3d as td
import numpy as np
import sympy as sp
@ -7,290 +9,174 @@ import bpy
from bpy_types import bpy_types
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 base
from ... import managed_objs
GEONODES_MODIFIER_NAME = "BLMaxwell_GeoNodes"
# 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
class GeoNodesStructureNode(base.MaxwellSimNode):
node_type = ct.NodeType.GeoNodesStructure
bl_label = "GeoNodes Structure"
#bl_icon = ...
####################
# - Sockets
####################
input_sockets = {
"preview_target": sockets.BlenderPreviewTargetSocketDef(
label="Preview Target",
),
"blender_unit_system": sockets.PhysicalUnitSystemSocketDef(
label="Blender Units",
),
"medium": sockets.MaxwellMediumSocketDef(
label="Medium",
),
"geo_nodes": sockets.BlenderGeoNodesSocketDef(
label="GeoNodes",
),
"Unit System": sockets.PhysicalUnitSystemSocketDef(),
"Medium": sockets.MaxwellMediumSocketDef(),
"GeoNodes": sockets.BlenderGeoNodesSocketDef(),
}
output_sockets = {
"structure": sockets.MaxwellStructureSocketDef(
label="Structure",
),
"Structure": sockets.MaxwellStructureSocketDef(),
}
managed_obj_defs = {
"geometry": ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLObject(name),
name_prefix="geonodes_",
)
}
####################
# - Output Socket Computation
####################
@base.computes_output_socket("structure")
def compute_simulation(self: contracts.NodeTypeProtocol) -> td.TriangleMesh:
# Extract the Blender Object
bl_object = self.compute_input("object")
# Ensure Updated Geometry
bpy.context.view_layer.update()
# Triangulate Object Mesh
bmesh_mesh = bmesh.new()
bmesh_mesh.from_object(
bl_object,
bpy.context.evaluated_depsgraph_get(),
@base.computes_output_socket(
"Structure",
input_sockets={"Medium"},
managed_objs={"geometry"},
)
bmesh.ops.triangulate(bmesh_mesh, faces=bmesh_mesh.faces)
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"]
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)
# Extract Geometry as Arrays
geometry_as_arrays = mobj.mesh_as_arrays
# Return TriMesh Structure
return td.Structure(
geometry=td.TriangleMesh.from_vertices_faces(vertices, faces),
medium=self.compute_input("medium")
geometry=td.TriangleMesh.from_vertices_faces(
geometry_as_arrays["verts"],
geometry_as_arrays["faces"],
),
medium=input_sockets["Medium"],
)
####################
# - Update Function
# - Event Methods
####################
def free(self) -> None:
bl_socket = self.g_input_bl_socket("preview_target")
@base.on_value_changed(
socket_name="GeoNodes",
bl_socket.free()
def update_cb(self) -> None:
bl_object = self.compute_input("preview_target")
if bl_object is None: return
geo_nodes = self.compute_input("geo_nodes")
if geo_nodes is None: return
bl_modifier = bl_object.modifiers.get(GEONODES_MODIFIER_NAME)
if bl_modifier is None: return
# Set GeoNodes Modifier Attributes
for idx, interface_item in enumerate(
geo_nodes.interface.items_tree.values()
):
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
managed_objs={"geometry"},
input_sockets={"GeoNodes"},
)
) ## What a bunch of spaghetti
else:
val = bl_socket.default_value
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.
# Retrieve Unit-System Corrected Modifier Value
bl_unit_system = self.compute_input("blender_unit_system")
Refreshes the Loose Input Sockets, which map directly to the GeoNodes tree input sockets.
"""
if not (geo_nodes := input_sockets["GeoNodes"]):
managed_objs["geometry"].free()
self.loose_input_sockets = {}
return
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
# Analyze GeoNodes
## Extract Valid Inputs (via GeoNodes Tree "Interface")
geonodes_interface = analyze_geonodes.interface(
geo_nodes, direc="INPUT"
)
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
# Set Loose Input Sockets
## Retrieve the appropriate SocketDef for the Blender Interface Socket
self.loose_input_sockets = {
socket_name: bl_socket_map.socket_def_from_bl_interface_socket(
bl_interface_socket
)() ## === <SocketType>SocketDef(), but with dynamic SocketDef
for socket_name, bl_interface_socket in geonodes_interface.items()
}
for bl_socket in bl_sockets_to_remove:
self.inputs.remove(bl_socket)
## Set Loose `socket.value` from Interface `default_value`
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
bl_object = self.compute_input("preview_target")
if bl_object is None: return
socket.value = bl_socket_map.value_from_bl(bl_interface_socket)
# Remove Existing GeoNodes Modifier
if GEONODES_MODIFIER_NAME in bl_object.modifiers:
modifier_to_remove = bl_object.modifiers[GEONODES_MODIFIER_NAME]
bl_object.modifiers.remove(modifier_to_remove)
## Implicitly triggers the loose-input `on_value_changed` for each.
# Retrieve GeoNodes Tree
geo_nodes = self.compute_input("geo_nodes")
if geo_nodes is None: return
@base.on_value_changed(
any_loose_input_socket=True,
# Add Non-Static Sockets from GeoNodes
for bl_socket_name, bl_socket in geo_nodes.interface.items_tree.items():
# For now, don't allow Geometry inputs.
if bl_socket.socket_type == "NodeSocketGeometry": continue
# Establish Dimensions of GeoNodes Input Sockets
if (
bl_socket.description.startswith("2D")
managed_objs={"geometry"},
input_sockets={"Unit System", "GeoNodes"},
)
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],
):
dimensions = 2
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
"""Called whenever a Loose Input Socket is altered.
# Choose Socket via. Description Hint (if exists)
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,
Synchronizes the change to the actual GeoNodes modifier, so that the change is immediately visible.
"""
# Retrieve Data
unit_system = input_sockets["Unit System"]
mobj = managed_objs["geometry"]
if not (geo_nodes := input_sockets["GeoNodes"]): return
# Analyze GeoNodes Interface (input direction)
## This retrieves NodeTreeSocketInterface elements
geonodes_interface = analyze_geonodes.interface(
geo_nodes, direc="INPUT"
)
if len([
(unit := _unit)
for _unit in contracts.SocketType_to_units[
contracts.SocketType[
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
## TODO: Check that Loose Sockets matches the Interface
## - If the user deletes an interface socket, bad things will happen.
## - We will try to set an identifier that doesn't exist!
## - Instead, this should update the loose input sockets.
elif bl_socket.socket_type in contracts.BLNodeSocket_to_SocketType[
dimensions
]:
self.inputs.new(
contracts.BLNodeSocket_to_SocketType[
dimensions
][bl_socket.socket_type],
bl_socket_name,
## Push Values to the GeoNodes Modifier
mobj.sync_geonodes_modifier(
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()
)
}
)
# Create New GeoNodes Modifier
if GEONODES_MODIFIER_NAME not in bl_object.modifiers:
modifier = bl_object.modifiers.new(
name=GEONODES_MODIFIER_NAME,
type="NODES",
####################
# - Event Methods
####################
@base.on_show_preview(
managed_objs={"geometry"},
)
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")
def on_show_preview(
self,
managed_objs: dict[str, ct.schemas.ManagedObj],
):
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)
"""Called whenever a Loose Input Socket is altered.
Synchronizes the change to the actual GeoNodes modifier, so that the change is immediately visible.
"""
managed_objs["geometry"].show_preview("MESH")
####################
@ -300,7 +186,7 @@ BL_REGISTER = [
GeoNodesStructureNode,
]
BL_NODES = {
contracts.NodeType.GeoNodesStructure: (
contracts.NodeCategory.MAXWELLSIM_STRUCTURES
ct.NodeType.GeoNodesStructure: (
ct.NodeCategory.MAXWELLSIM_STRUCTURES
)
}

View File

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

View File

@ -170,6 +170,19 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
def value(self, value: typ.Any) -> None:
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
def lazy_value(self) -> None:
raise NotImplementedError

View File

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

View File

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

View File

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

View File

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

View File

@ -18,7 +18,7 @@ class BlenderCollectionBLSocket(base.MaxwellSimSocket):
####################
raw_value: bpy.props.PointerProperty(
name="Blender Collection",
description="Represents a Blender collection",
description="A Blender collection",
type=bpy.types.Collection,
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_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):
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'}
@ -33,7 +42,7 @@ class BlenderGeoNodesBLSocket(base.MaxwellSimSocket):
name="Blender GeoNodes Tree",
description="Represents a Blender GeoNodes Tree",
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)),
)
@ -42,12 +51,16 @@ class BlenderGeoNodesBLSocket(base.MaxwellSimSocket):
####################
def draw_label_row(self, label_col_row, text):
label_col_row.label(text=text)
if self.raw_value:
label_col_row.operator(
"blender_maxwell.reset_geo_nodes_socket",
if not self.raw_value: return
op = label_col_row.operator(
BlenderMaxwellResetGeoNodesSocket.bl_idname,
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

View File

@ -7,26 +7,22 @@ from .. import base
from ... import contracts as ct
####################
# - Blender Socket
# - Create and Assign BL Object
####################
class BlenderMaxwellCreateAndAssignBLObject(bpy.types.Operator):
bl_idname = "blender_maxwell.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):
mesh = bpy.data.meshes.new("GenMesh")
new_bl_object = bpy.data.objects.new("GenObj", mesh)
node_tree = bpy.data.node_groups[self.node_tree_name]
node = node_tree.nodes[self.node_name]
socket = node.inputs[self.socket_name]
context.collection.objects.link(new_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()
socket.create_and_assign_bl_object()
return {'FINISHED'}
@ -52,15 +48,31 @@ class BlenderObjectBLSocket(base.MaxwellSimSocket):
####################
def draw_label_row(self, label_col_row, text):
label_col_row.label(text=text)
label_col_row.operator(
op = label_col_row.operator(
"blender_maxwell.create_and_assign_bl_object",
text="",
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:
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
####################

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
from .. import base
from ... import contracts
from ... import contracts as ct
####################
# - Blender Socket
####################
class BlenderTextBLSocket(base.MaxwellSimSocket):
socket_type = contracts.SocketType.BlenderText
socket_type = ct.SocketType.BlenderText
bl_label = "Blender Text"
####################
@ -44,7 +44,7 @@ class BlenderTextBLSocket(base.MaxwellSimSocket):
# - Socket Configuration
####################
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:
pass

View File

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

View File

@ -8,8 +8,8 @@ import tidy3d as td
from .. import base
from ... import contracts as ct
class MaxwellBoundFaceBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.MaxwellBoundFace
class MaxwellBoundCondBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.MaxwellBoundCond
bl_label = "Maxwell Bound Face"
####################
@ -53,17 +53,17 @@ class MaxwellBoundFaceBLSocket(base.MaxwellSimSocket):
####################
# - Socket Configuration
####################
class MaxwellBoundFaceSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.MaxwellBoundFace
class MaxwellBoundCondSocketDef(pyd.BaseModel):
socket_type: ct.SocketType = ct.SocketType.MaxwellBoundCond
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
####################
# - Blender Registration
####################
BL_REGISTER = [
MaxwellBoundFaceBLSocket,
MaxwellBoundCondBLSocket,
]

View File

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

View File

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

View File

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

View File

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

View File

@ -1,64 +1,64 @@
from . import unit_system_socket
PhysicalUnitSystemSocketDef = unit_system_socket.PhysicalUnitSystemSocketDef
from . import unit_system
PhysicalUnitSystemSocketDef = unit_system.PhysicalUnitSystemSocketDef
from . import time_socket
PhysicalTimeSocketDef = time_socket.PhysicalTimeSocketDef
from . import time
PhysicalTimeSocketDef = time.PhysicalTimeSocketDef
from . import angle_socket
PhysicalAngleSocketDef = angle_socket.PhysicalAngleSocketDef
from . import angle
PhysicalAngleSocketDef = angle.PhysicalAngleSocketDef
from . import length_socket
from . import area_socket
from . import volume_socket
PhysicalLengthSocketDef = length_socket.PhysicalLengthSocketDef
PhysicalAreaSocketDef = area_socket.PhysicalAreaSocketDef
PhysicalVolumeSocketDef = volume_socket.PhysicalVolumeSocketDef
from . import length
from . import area
from . import volume
PhysicalLengthSocketDef = length.PhysicalLengthSocketDef
PhysicalAreaSocketDef = area.PhysicalAreaSocketDef
PhysicalVolumeSocketDef = volume.PhysicalVolumeSocketDef
from . import point_3d_socket
PhysicalPoint3DSocketDef = point_3d_socket.PhysicalPoint3DSocketDef
from . import point_3d
PhysicalPoint3DSocketDef = point_3d.PhysicalPoint3DSocketDef
from . import size_3d_socket
PhysicalSize3DSocketDef = size_3d_socket.PhysicalSize3DSocketDef
from . import size_3d
PhysicalSize3DSocketDef = size_3d.PhysicalSize3DSocketDef
from . import mass_socket
PhysicalMassSocketDef = mass_socket.PhysicalMassSocketDef
from . import mass
PhysicalMassSocketDef = mass.PhysicalMassSocketDef
from . import speed_socket
from . import accel_scalar_socket
from . import force_scalar_socket
PhysicalSpeedSocketDef = speed_socket.PhysicalSpeedSocketDef
PhysicalAccelScalarSocketDef = accel_scalar_socket.PhysicalAccelScalarSocketDef
PhysicalForceScalarSocketDef = force_scalar_socket.PhysicalForceScalarSocketDef
from . import speed
from . import accel_scalar
from . import force_scalar
PhysicalSpeedSocketDef = speed.PhysicalSpeedSocketDef
PhysicalAccelScalarSocketDef = accel_scalar.PhysicalAccelScalarSocketDef
PhysicalForceScalarSocketDef = force_scalar.PhysicalForceScalarSocketDef
from . import pol_socket
PhysicalPolSocketDef = pol_socket.PhysicalPolSocketDef
from . import pol
PhysicalPolSocketDef = pol.PhysicalPolSocketDef
from . import freq_socket
PhysicalFreqSocketDef = freq_socket.PhysicalFreqSocketDef
from . import freq
PhysicalFreqSocketDef = freq.PhysicalFreqSocketDef
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,
*area_socket.BL_REGISTER,
*volume_socket.BL_REGISTER,
*length.BL_REGISTER,
*area.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,
*accel_scalar_socket.BL_REGISTER,
*force_scalar_socket.BL_REGISTER,
*speed.BL_REGISTER,
*accel_scalar.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),
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",
description="Unit of 3D vector acceleration",
items=contract_units_to_items(ST.PhysicalAccel3DVector),
default=default_unit_key_for(ST.PhysicalAccel3DVector),
update=(lambda self, context: self.sync_prop("unit_accel_3d_vector", context)),
items=contract_units_to_items(ST.PhysicalAccel3D),
default=default_unit_key_for(ST.PhysicalAccel3D),
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",
description="Unit of 3D vector force",
items=contract_units_to_items(ST.PhysicalForce3DVector),
default=default_unit_key_for(ST.PhysicalForce3DVector),
update=(lambda self, context: self.sync_prop("unit_force_3d_vector", context)),
items=contract_units_to_items(ST.PhysicalForce3D),
default=default_unit_key_for(ST.PhysicalForce3D),
update=(lambda self, context: self.sync_prop("unit_force_3d", context)),
)
unit_freq: bpy.props.EnumProperty(
@ -226,24 +226,19 @@ class PhysicalUnitSystemBLSocket(base.MaxwellSimSocket):
col_row.label(text="Accel")
col_row.prop(self, "unit_accel_scalar", 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.label(text="Force")
col_row.prop(self, "unit_force_scalar", 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.alignment = "EXPAND"
col_row.label(text="Freq")
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
####################
@ -270,11 +265,10 @@ class PhysicalUnitSystemBLSocket(base.MaxwellSimSocket):
(ST.PhysicalSpeed, self.unit_speed),
(ST.PhysicalAccelScalar, self.unit_accel_scalar),
(ST.PhysicalForceScalar, self.unit_force_scalar),
(ST.PhysicalAccel3DVector, self.unit_accel_3d_vector),
(ST.PhysicalForce3DVector, self.unit_force_3d_vector),
(ST.PhysicalAccel3D, self.unit_accel_3d),
(ST.PhysicalForce3D, self.unit_force_3d),
(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 complex_2d_vector_socket
Real2DVectorSocketDef = real_2d_vector_socket.Real2DVectorSocketDef
Complex2DVectorSocketDef = complex_2d_vector_socket.Complex2DVectorSocketDef
from . import real_2d_vector
from . import complex_2d_vector
Real2DVectorSocketDef = real_2d_vector.Real2DVectorSocketDef
Complex2DVectorSocketDef = complex_2d_vector.Complex2DVectorSocketDef
from . import real_3d_vector_socket
from . import complex_3d_vector_socket
Real3DVectorSocketDef = real_3d_vector_socket.Real3DVectorSocketDef
Complex3DVectorSocketDef = complex_3d_vector_socket.Complex3DVectorSocketDef
from . import real_3d_vector
from . import complex_3d_vector
Real3DVectorSocketDef = real_3d_vector.Real3DVectorSocketDef
Complex3DVectorSocketDef = complex_3d_vector.Complex3DVectorSocketDef
BL_REGISTER = [
*real_2d_vector_socket.BL_REGISTER,
*complex_2d_vector_socket.BL_REGISTER,
*real_2d_vector.BL_REGISTER,
*complex_2d_vector.BL_REGISTER,
*real_3d_vector_socket.BL_REGISTER,
*complex_3d_vector_socket.BL_REGISTER,
*real_3d_vector.BL_REGISTER,
*complex_3d_vector.BL_REGISTER,
]

View File

@ -3,7 +3,7 @@ import bpy
from .operators import types as operators_types
class BlenderMaxwellAddonPreferences(bpy.types.AddonPreferences):
bl_idname = "blender_maxwell"
bl_idname = "blender_maxwell_preferences"
def draw(self, context):
layout = self.layout

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

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

Binary file not shown.