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!main
parent
4c207b96e0
commit
6912344aaf
|
@ -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.
|
194
README.md
194
README.md
|
@ -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.
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
||||
####################
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(),
|
||||
):
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
####################
|
||||
# - Blender Registration
|
||||
####################
|
||||
BL_REGISTER = []
|
||||
BL_NODES = {}
|
|
@ -1,5 +0,0 @@
|
|||
####################
|
||||
# - Blender Registration
|
||||
####################
|
||||
BL_REGISTER = []
|
||||
BL_NODES = {}
|
|
@ -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,
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
####################
|
||||
# - Blender Registration
|
||||
####################
|
||||
BL_REGISTER = []
|
||||
BL_NODES = {}
|
|
@ -1,5 +0,0 @@
|
|||
####################
|
||||
# - Blender Registration
|
||||
####################
|
||||
BL_REGISTER = []
|
||||
BL_NODES = {}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -46,7 +46,7 @@ class JSONFileExporterNode(base.MaxwellSimNode):
|
|||
),
|
||||
}
|
||||
output_sockets = {
|
||||
"JSON String": sockets.TextSocketDef(),
|
||||
"JSON String": sockets.StringSocketDef(),
|
||||
}
|
||||
|
||||
####################
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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
|
||||
####################
|
||||
|
|
|
@ -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",
|
||||
)
|
||||
|
||||
|
||||
|
||||
####################
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
]
|
||||
|
|
|
@ -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
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
||||
|
|
|
@ -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)),
|
||||
)
|
|
@ -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
|
|
@ -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
|
||||
####################
|
|
@ -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,
|
||||
]
|
|
@ -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
|
|
@ -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,
|
||||
]
|
||||
|
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
||||
####################
|
|
@ -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
|
|
@ -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,
|
||||
]
|
||||
|
|
|
@ -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),
|
||||
]
|
||||
}
|
||||
|
|
@ -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,
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
])
|
||||
}
|
|
@ -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)
|
||||
),
|
||||
|
|
Loading…
Reference in New Issue