refactor: Massive architectural changes.

See README.md for new, semi-finalized TODO list.
blender-plugin-mvp
Sofus Albert Høgsbro Rose 2024-03-10 11:56:37 +01:00
parent d95210dc34
commit 1ebb57cff7
Signed by: so-rose
GPG Key ID: AD901CB0F3701434
125 changed files with 6282 additions and 3574 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,46 @@
from .node_cats import NodeCategory as NC
NODE_CAT_LABELS = {
# Inputs/
NC.MAXWELLSIM_INPUTS: "Inputs",
NC.MAXWELLSIM_INPUTS_IMPORTERS: "Importers",
NC.MAXWELLSIM_INPUTS_SCENE: "Scene",
NC.MAXWELLSIM_INPUTS_PARAMETERS: "Parameters",
NC.MAXWELLSIM_INPUTS_CONSTANTS: "Constants",
NC.MAXWELLSIM_INPUTS_LISTS: "Lists",
# Outputs/
NC.MAXWELLSIM_OUTPUTS: "Outputs",
NC.MAXWELLSIM_OUTPUTS_VIEWERS: "Viewers",
NC.MAXWELLSIM_OUTPUTS_EXPORTERS: "Exporters",
NC.MAXWELLSIM_OUTPUTS_PLOTTERS: "Plotters",
# Sources/
NC.MAXWELLSIM_SOURCES: "Sources",
NC.MAXWELLSIM_SOURCES_TEMPORALSHAPES: "Temporal Shapes",
# Mediums/
NC.MAXWELLSIM_MEDIUMS: "Mediums",
NC.MAXWELLSIM_MEDIUMS_NONLINEARITIES: "Non-Linearities",
# Structures/
NC.MAXWELLSIM_STRUCTURES: "Structures",
NC.MAXWELLSIM_STRUCTURES_PRIMITIVES: "Primitives",
# Bounds/
NC.MAXWELLSIM_BOUNDS: "Bounds",
NC.MAXWELLSIM_BOUNDS_BOUNDFACES: "Bound Faces",
# Monitors/
NC.MAXWELLSIM_MONITORS: "Monitors",
NC.MAXWELLSIM_MONITORS_NEARFIELDPROJECTIONS: "Near-Field Projections",
# Simulations/
NC.MAXWELLSIM_SIMS: "Simulations",
NC.MAXWELLSIM_SIMGRIDAXES: "Sim Grid Axes",
# Utilities/
NC.MAXWELLSIM_UTILITIES: "Utilities",
NC.MAXWELLSIM_UTILITIES_CONVERTERS: "Converters",
NC.MAXWELLSIM_UTILITIES_OPERATIONS: "Operations",
}

View File

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

View File

@ -0,0 +1,147 @@
import enum
from ....utils.blender_type_enum import (
BlenderTypeEnum, append_cls_name_to_values
)
@append_cls_name_to_values
class NodeType(BlenderTypeEnum):
KitchenSink = enum.auto()
# Inputs
UnitSystem = enum.auto()
## Inputs / Scene
Time = enum.auto()
## Inputs / Importers
Tidy3DWebImporter = enum.auto()
## Inputs / Parameters
NumberParameter = enum.auto()
PhysicalParameter = enum.auto()
## Inputs / Constants
WaveConstant = enum.auto()
ScientificConstant = enum.auto()
NumberConstant = enum.auto()
PhysicalConstant = enum.auto()
BlenderConstant = enum.auto()
## Inputs / Lists
RealList = enum.auto()
ComplexList = enum.auto()
## Inputs /
InputFile = enum.auto()
# Outputs
## Outputs / Viewers
Viewer = enum.auto()
ValueViewer = enum.auto()
ConsoleViewer = enum.auto()
## Outputs / Exporters
JSONFileExporter = enum.auto()
Tidy3DWebExporter = enum.auto()
# Sources
## Sources / Temporal Shapes
GaussianPulseTemporalShape = enum.auto()
ContinuousWaveTemporalShape = enum.auto()
ListTemporalShape = enum.auto()
## Sources /
PointDipoleSource = enum.auto()
UniformCurrentSource = enum.auto()
PlaneWaveSource = enum.auto()
ModeSource = enum.auto()
GaussianBeamSource = enum.auto()
AstigmaticGaussianBeamSource = enum.auto()
TFSFSource = enum.auto()
EHEquivalenceSource = enum.auto()
EHSource = enum.auto()
# Mediums
LibraryMedium = enum.auto()
PECMedium = enum.auto()
IsotropicMedium = enum.auto()
AnisotropicMedium = enum.auto()
TripleSellmeierMedium = enum.auto()
SellmeierMedium = enum.auto()
PoleResidueMedium = enum.auto()
DrudeMedium = enum.auto()
DrudeLorentzMedium = enum.auto()
DebyeMedium = enum.auto()
## Mediums / Non-Linearities
AddNonLinearity = enum.auto()
ChiThreeSusceptibilityNonLinearity = enum.auto()
TwoPhotonAbsorptionNonLinearity = enum.auto()
KerrNonLinearity = enum.auto()
# Structures
ObjectStructure = enum.auto()
GeoNodesStructure = enum.auto()
ScriptedStructure = enum.auto()
## Structures / Primitives
BoxStructure = enum.auto()
SphereStructure = enum.auto()
CylinderStructure = enum.auto()
# Bounds
BoundBox = enum.auto()
## Bounds / Bound Faces
PMLBoundFace = enum.auto()
PECBoundFace = enum.auto()
PMCBoundFace = enum.auto()
BlochBoundFace = enum.auto()
PeriodicBoundFace = enum.auto()
AbsorbingBoundFace = enum.auto()
# Monitors
EHFieldMonitor = enum.auto()
FieldPowerFluxMonitor = enum.auto()
EpsilonTensorMonitor = enum.auto()
DiffractionMonitor = enum.auto()
## Monitors / Near-Field Projections
CartesianNearFieldProjectionMonitor = enum.auto()
ObservationAngleNearFieldProjectionMonitor = enum.auto()
KSpaceNearFieldProjectionMonitor = enum.auto()
# Sims
SimDomain = enum.auto()
SimGrid = enum.auto()
## Sims / Sim Grid Axis
AutomaticSimGridAxis = enum.auto()
ManualSimGridAxis = enum.auto()
UniformSimGridAxis = enum.auto()
ArraySimGridAxis = enum.auto()
## Sim /
FDTDSim = enum.auto()
# Utilities
Combine = enum.auto()
Separate = enum.auto()
Math = enum.auto()
## Utilities / Converters
WaveConverter = enum.auto()
## Utilities / Operations
ArrayOperation = enum.auto()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,11 @@
from . import json_file_exporter from . import json_file_exporter
from . import tidy3d_web_exporter
BL_REGISTER = [ BL_REGISTER = [
*json_file_exporter.BL_REGISTER, *json_file_exporter.BL_REGISTER,
*tidy3d_web_exporter.BL_REGISTER,
] ]
BL_NODES = { BL_NODES = {
**json_file_exporter.BL_NODES, **json_file_exporter.BL_NODES,
**tidy3d_web_exporter.BL_NODES,
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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