Compare commits

..

10 Commits

Author SHA1 Message Date
Sofus Albert Høgsbro Rose ed21617746 feat: Demo-grade simulation feedback loop. 2024-03-13 19:10:54 +01:00
Sofus Albert Høgsbro Rose 9a148d7d97 feat: Continue to add features.
See README.md.
2024-03-12 09:01:50 +01:00
Sofus Albert Høgsbro Rose e7979f0f06 refactor: Continuing large-scale alterations.
The big news is that GeoNodes Structure is now implemented,
under the new and vastly more robust chaining system.

Upload to Tidy3D cloud is tested. Next is Monitors!
2024-03-11 16:35:41 +01:00
Sofus Albert Høgsbro Rose 6e7593b555 refactor: Massive architectural changes.
See README.md for new, semi-finalized TODO list.
2024-03-10 11:56:37 +01:00
Sofus Albert Høgsbro Rose 24b24504a0 feat: Various features (some very prototype).
It's very prototype-y. Cleanup pending.
2024-02-26 16:16:06 +01:00
Sofus Albert Høgsbro Rose 27fdb38262 feat: We did it, GeoNodes node w/live update!
We also implemented the TriMesh node, and established a strong
convention for updating nodes from sockets via. socket superclass
method. `trigger_updates`. It should be triggered as the
`update=` callback on **ALL PROPERTIES IN ALL SOCKETS**. This
method in turn calls the nodal `update()` function, which in turn
causes the node to chain-update all nodes linked to any output socket.

By default, `update()` is `pass`, so performance shouldn't be a concern,
but we should think about this deeper at some point.

Because update-chaining is done, we're ready for preview toggles on
node outputs. A lot of exciting things to do now!
2024-02-20 13:16:23 +01:00
Sofus Albert Høgsbro Rose 62006da0f9 feat: More sockets, nodes, fixes.
We're starting to have a very advanced socket-based language
for defining nodes. It's very practical already.

The priorities are
1. All the nodes!
2. Sockets, including default values, as needed.
3. Library constants, mediums.
4. Output socket previews w/geonodes, geonodes structures.
5. Utilities including arrays (and selected array multi-input)

The code is still very spaghetti. This is very much still the
"first, make it run" part of the system design.
2024-02-19 18:36:16 +01:00
Sofus Albert Høgsbro Rose 89087e7ec9 feat: Added accel socket, fixed default units. 2024-02-19 16:03:32 +01:00
Sofus Albert Høgsbro Rose 29d3a6a0fc feat: Custom units, def. all SocketType units. 2024-02-19 15:58:39 +01:00
Sofus Albert Høgsbro Rose ce53997b4f feat: Registered all nodes.
Also added several features including dynamic sockets
in nodes, abstracted units for sockets, and more.
2024-02-19 14:28:35 +01:00
266 changed files with 12877 additions and 2325 deletions

52
FUTURE.md 100644
View File

@ -0,0 +1,52 @@
# Projects / Plugins
## Larger Architectural Changes
[ ] Dedicated way of generating properties for sockets and nodes, incl. helping make better (less boilerplatey) use of callbacks
- Perhaps, we should also go 100% custom `PropertyGroup`, to the point that we have `nodes`, `sockets` and `props`.
- This would allow far simplified sockets (the more complex kinds), and help standardize use of ex. units in node properties.
- Having a dedicated base class for custom props would help avoid issues like forgetting to run `self.sync_prop` on every goddamn update method in every goddamn socket.
[ ] Dedicated way of handling node-specific operators without all the boilerplate.
## Field Data
[ ] Directly dealing with field data, instead of having field manipulations be baked into viz node(s).
[ ] Yee Cell Data as Attributes on By-Cell Point Cloud w/GeoNodes Integrations
- In effect, when we have xarray data defined based on Yee Cells ex. Poynting vector coordinates, let's import this to Blender as a simple point cloud centered at each cell and grant each an attribute corresponding to the data.
- What we can then do is use vanilla GeoNodes to ex. read the vector attribute, and draw small arrow meshes (maybe resampled which auto-interpolates the field values) from each point, thus effectively visualizing . vector fields and many other fun things.
- Of course, this is no good for volume cell data - but we can just overlay the raw volume cell data as we please. We can also, if we're sneaky, deal with our volume data as points as far as we can, and then finally do a "points to volume" type deal to make it sufficiently "fluffy/cloudy".
- I wonder if we could use the Attribute node in the shader editor to project interpolated values from points, onto a ex. plane mesh, in a way that would also be visualizable in the viewport.
## Tidy3D Features
[ ] Symmetry for Performance
- [ ] Implement <https://docs.flexcompute.com/projects/tidy3d/en/latest/notebooks/Symmetry.html>
[ ] Dispersive Model Fitting
[ ] Scattering Matrix Calculator
[ ] Resonance Finder
[ ] Adjoint Optimization
[ ] Design Space Exploration / Parameterization
## Preview Semantics
[ ] Node tree header toggle that toggles a modal operator on and off, which constantly checks the context of the selected nodes, and tries to `bl_select` them (which in turn, should cause the node base class to `bl_select` shit inside).
- Shouldn't survive a file save; always startup with this thing off.
[ ] Custom gizmos attached to preview toggles!
- There is a WIP for GN-driven gizmos: <https://projects.blender.org/blender/blender/pulls/112677>
- Probably best to wait for that, then just add gizmos to existing driven GN trees, as opposed to unholy OGL spaghetti.
[ ] Node-ManagedObj Selection binding
- BL to Node:
- Trigger: The post-depsgraph handler seems appropriate.
- Input: Read the object location (origin), using a unit system.
- Output: Write the input socket value.
- Condition: Input socket is unlinked. (If it's linked, then lock the object's position. Use sync_link_added() for that)
- Node to BL:
- Trigger: "Report" action on an input socket that the managed object declares reliance on.
- Input: The input socket value (linked or unlinked)
- Output: The object location (origin), using a unit system.
## Parametric Geometry UX
[ ] Consider allowing a mesh attribute (set in ex. geometry node) to specify the name of a medium.
- This allows assembling complex multi-medium structures in one geonodes tree.
- This should result in the spawning of several Medium input sockets in the GeoNodes structure node, named as the attributes are.
- The GeoNodes structure node should then output as array-like TriMeshes, for which mediums are correctly defined.
## Alternative Engines
[ ] Heat Solver
[ ] MEEP integration (<https://meep.readthedocs.io/en/latest/>)
- The main boost would be if we could setup a MEEP simulation entirely from a td.Simulation object.

587
README.md
View File

@ -1,243 +1,406 @@
# Node Design
Now that we can do all the cool things ex. presets and such, it's time to think more design.
# Nodes
**LEGEND**:
- [-] Exists but doesn't quite work good enough.
- [x] Done to working degree (the standard is "good enough for the demo").
- See check marks underneath
- [?] Unsure whether we should do this.
## Nodes
**NOTE**: Throughout, when an object can be selected (ex. for GeoNodes structure to affect), a button should be available to generate a new object for the occasion.
## Inputs
[x] Wave Constant
- [x] Implement export of frequency / wavelength array/range.
[-] Unit System
- [ ] Implement presets, including "Tidy3D" and "Blender", shown in the label row.
**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).
[ ] Constants / Scientific Constant
[x] Constants / Number Constant
[ ] Constants / Physical Constant
- [ ] Pol: Elliptical plot viz
- [ ] Pol: Poincare sphere viz
[x] Constants / Blender Constant
- Inputs
- Scene
- Time
- Object Info
[x] Web / Tidy3D Web Importer
- Parameter: Sympy variables.
- *type* Parameter
- Constant: Typed numbers.
- Scientific Constant
- *type* Constant
- Array
- From File: Concatenate two arrays.
- *type* Array Union
- Element: Create a 1-element array, with a typed value.
- *type* Array Element
[ ] File Import / JSON File Import
- [ ] Dropdown to choose various supported JSON-sourced objects incl.
[ ] File Import / Tidy3D File Import
- [ ] Implement HDF-based import of Tidy3D-exported object (which includes ex. mesh data and such)
[ ] File Import / Array File Import
- [ ] Standardize 1D and 2D array loading/saving on numpy's savetxt with gzip enabled.
- [ ] Implement datatype dropdown to guide format from disk, prefilled to detected.
- [ ] Implement unit system input to guide conversion from numpy data type.
- [ ] Implement a LazyValue to provide a data path that avoids having to load massive arrays every time always.
- File Data: Data from a file.
- *type* File Data
- Outputs
- Viewer
- Value Viewer: Live-monitoring.
- Console Viewer: w/Button to Print Types
- Exporter
- JSON File Export: Compatible with any socket implementing `.as_json()`.
- Plot
- *various kinds of plotting? To Blender datablocks primarily, maybe*.
## Outputs
[x] Viewer
- [ ] **BIG ONE**: Remove image preview when disabling plots.
- [ ] Either enforce singleton, or find a way to have several viewers at the same time.
- [ ] A setting that live-previews just a value.
- [ ] Pop-up multiline string print as alternative to console print.
- [x] Toggleable auto-plot, auto-3D-preview, auto-value-view, (?)auto-text-view.
- Sources
- **ALL**: Accept a Temporal Shape
[x] Web Export / Tidy3D Web Exporter
- [ ] We need better ways of doing checks before uploading, like for monitor data size. Maybe a SimInfo node?
- [ ] We need to be able to "delete and re-upload" (or maybe just delete from the interface).
- [x] Implement estimation of monitor storage
- [x] Implement cost estimation
- [?] Merge with the Tidy3D File Import (since both are working with HDFs; the web one only really does downloading too).
- Temporal Shapes
- Gaussian Pulse Temporal Shape
- Continuous Wave Temporal Shape
- Array Temporal Shape
[x] File Export / JSON File Export
[ ] File Import / Tidy3D File Export
- [ ] Implement HDF-based export of Tidy3D-exported object (which includes ex. mesh data and such)
[ ] File Export / Array File Export
- [ ] 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.
- Point Dipole Source
- Uniform Current Source
- Plane Wave Source
- Mode Source
- Gaussian Beam Source
- Astigmatic Gaussian Beam Source
- TFSF Source
## Viz
[x] Monitor Data Viz
- [x] Implement dropdown to choose which monitor in the SimulationData should be visualized (based on which are available in the SimulationData), and implement visualization based on every kind of monitor-adjascent output data type (<https://docs.flexcompute.com/projects/tidy3d/en/latest/api/output_data.html>)
- [ ] Project field values onto a plane object (managed)
- E/H Equivalence Array Source
- E/H Array Source
## Sources
[x] Temporal Shapes / Gaussian Pulse Temporal Shape
[x] Temporal Shapes / Continuous Wave Temporal Shape
[ ] Temporal Shapes / Symbolic Temporal Shape
- [ ] Specify a Sympy function to generate appropriate array based on
[ ] Temporal Shapes / Array Temporal Shape
[x] Point Dipole Source
- [ ] Consider a "real" mesh - the empty kind of gets stuck inside of the sim domain.
[-] Plane Wave Source
- [ ] **IMPORTANT**: Fix the math so that an actually valid construction emerges!!
- [x] 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
[ ] Non-Linearity / `chi_3` Susceptibility Non-Linearity
[ ] Non-Linearity / Two-Photon Absorption Non-Linearity
[ ] Non-Linearity / Kerr Non-Linearity
[ ] Space/Time epsilon/mu Modulation
## Structures
[ ] BLObject Structure
[x] GeoNodes Structure
- [x] Rewrite the `bl_socket_map.py`
- [x] Use the modifier itself as memory, via the ManagedObj
- [?] When GeoNodes themselves declare panels, implement a grid-like tab system to select which sockets should be exposed in the node at a given point in time.
[ ] Primitive Structures / Plane Structure
[x] Primitive Structures / Box Structure
[x] Primitive Structures / Sphere Structure
[ ] Primitive Structures / Cylinder Structure
[ ] Primitive Structures / Ring Structure
[ ] Primitive Structures / Capsule Structure
[ ] Primitive Structures / Cone Structure
## Monitors
- **ALL**: "Steady-State" / "Time Domain" (only if relevant).
[x] E/H Field Monitor
- [x] Monitor Domain as dropdown with Frequency or Time
- [x] 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
[x] Sim Domain
- [ ] By-Medium batching of Structures when building the td.Simulation object, which can have significant performance implications.
[x] Boundary Conds
- [x] Rename from Bounds / BoundBox
[ ] Boundary Cond / PML Bound Face
- [ ] Implement dropdown for "Normal" and "Stable"
[ ] Boundary Cond / PEC Bound Face
[ ] Boundary Cond / PMC Bound Face
[ ] Boundary Cond / Bloch Bound Face
[ ] Boundary Cond / Periodic Bound Face
[ ] Boundary Cond / Absorbing Bound Face
[ ] Sim Grid
[ ] Sim Grid Axes / Auto Sim Grid Axis
[ ] Sim Grid Axes / Manual Sim Grid Axis
[ ] Sim Grid Axes / Uniform Sim Grid Axis
[ ] Sim Grid Axes / Array Sim Grid Axis
## Converters
[ ] Math
- [ ] Implement common operations w/secondary choice of socket type based on a custom internal data structure
- [ ] Implement angfreq/frequency/vacwl conversion.
[ ] Separate
[ ] Combine
- [ ] Implement concatenation of sim-critical socket types into their multi-type
- Mediums
- **ALL**: Accept spatial field. Else, spatial uniformity.
- **ALL**: Accept non-linearity. Else, linear.
- **ALL**: Accept space-time modulation. Else, static.
# GeoNodes
[ ] Tests / Monkey (suzanne deserves to be simulated, she may need manifolding up though :))
[ ] Tests / Wood Pile
- 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.
[ ] Primitives / Plane
[ ] Primitives / Box
[ ] Primitives / Sphere
[ ] Primitives / Cylinder
[ ] Primitives / Ring
[ ] Primitives / Capsule
[ ] Primitives / Cone
- Linear Mediums
- PEC Medium
- Isotropic Medium
- Anisotropic Medium
[ ] 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
- 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
- Object Structure
- GeoNodes Structure
- Scripted Structure
- Primitives
- Box Structure
- Sphere Structure
- Cylinder Structure
[ ] Crystal Sphere Lattice / Sphere FCC Array
[ ] Crystal Sphere Lattice / Sphere BCC Array
- Bounds
- Bound Box
- Bound Faces
- PML Bound Face: "Normal"/"Stable"
- PEC Bound Face
- PMC Bound Face
- Bloch Bound Face
- Periodic Bound Face
- Absorbing Bound Face
- Monitors
- **ALL**: "Steady-State" / "Time Domain" (only if relevant).
- E/H Field Monitor: "Steady-State"
- Field Power Flux Monitor
- \epsilon Tensor Monitor
- Diffraction Monitor
- **TODO**: "Modal" solver monitoring (seems to be some kind of spatial+frequency feature, which an EM field can be decomposed into using a specially configured solver, which can be used to look for very particular kinds of effects by constraining investigations of a solver result to filter out everything that isn't these particular modes aka. features. Kind of a fourier-based redimensionalization, almost).
- **TODO**: Near-field projections like so:
- Cartesian Near-Field Projection Monitor
- Observation Angle Near-Field Projection Monitor
- K-Space Near-Field Projection Monitor
# Benchmark / Example Sims
[ ] Research-Grade Experiment
- Membrane 15nm thickness suspended in air
- Square lattice of holes period 900nm (900nm between each hole, air inside holes)
- Holes square radius 100nm
- Square lattice
- Analysis of transmission
- Guided mode resonance
[ ] Tunable Chiral Metasurface <https://docs.flexcompute.com/projects/tidy3d/en/latest/notebooks/TunableChiralMetasurface.html>
- Simulations
- FDTD Sim
# Sockets
## Basic
[x] Any
[x] Bool
[x] String
- [ ] Rename from "Text"
[x] File Path
[x] Color
- Grid Discretizations
- Sim Grid
- Grid Axis
- Automatic Grid Axis
- Manual Grid Axis
- Uniform Grid Axis
- Data-Driven Grid Axis
## Number
[x] Integer
[x] Rational
- [ ] Implement constrained SympyExpr check for Rational.
[x] Real
- [ ] Implement min/max for ex. 0..1 factor support.
- [ ] Implement constrained SympyExpr check for Rational.
[x] Complex
## Blender
[x] Object
- [ ] Implement default SocketDef object name
[x] Collection
- [ ] Implement default SocketDef collection name
[x] Image
- [ ] Implement default SocketDef image name
[x] GeoNodes
- [ ] Implement default SocketDef geonodes name
[x] Text
- [ ] Implement default SocketDef object name
## Maxwell
[x] Bound Conds
[ ] Bound Cond
[x] Medium
[ ] Medium Non-Linearity
[x] Source
[ ] Temporal Shape
- [ ] Sane-default pulses for easy access.
[ ] 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
[x] Cloud Task
- [ ] Implement switcher for API-key-having config filconfig file vs. direct entry of API key. It should be auto-filled with the config file when such a thing exists.
## Physical
[x] Unit System
- [ ] Implement more comprehensible UI; honestly, probably with the new panels (<https://developer.blender.org/docs/release_notes/4.1/python_api/>)
[x] Time
[x] Angle
[ ] Solid Angle (steradian)
[x] Frequency (hertz)
[ ] Angular Frequency (`rad*hertz`)
### Cartesian
[x] Length
[x] Area
[x] Volume
[ ] Point 1D
[ ] Point 2D
[x] Point 3D
[ ] Size 2D
[x] Size 3D
[ ] Rotation 3D
- [ ] Implement Euler methods
- [ ] Implement Quaternion methods
### Mechanical
[ ] Mass
[x] Speed
[ ] Velocity 3D
[x] Acceleration Scalar
[ ] Acceleration 3D
[x] Force Scalar
[ ] Force 3D
[ ] Pressure
### Energy
[ ] 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 (Stokes)
- Utilities
- Math: Contains a dropdown for operation.
- *type* Math: **Be careful about units :)**
- Operations
- Array Operation
## Sockets
- basic
- Any
- FilePath
- Text
- math
- IntegerNumber
- RationalNumber
- RealNumber
- ComplexNumber
- RealNumberField
- ComplexNumberField
- Real2DVector
- Complex2DVector
- Real2DVectorField
- Complex2DVectorField
- Real3DVector
- Complex3DVector
- Real3DVectorField
- Complex3DVectorField
- physics
- PhysicalTime
- PhysicalAngle
- PhysicalLength
- PhysicalArea
- PhysicalVolume
- PhysicalMass
- PhysicalLengthDensity
- PhysicalAreaDensity
- PhysicalVolumeDensity
- PhysicalSpeed
- PhysicalAcceleration
- PhysicalForce
- PhysicalPolarization
- PhysicalFrequency
- PhysicalSpectralDomain
- PhysicalSpectralDistribution
- blender
- BlenderObject
- BlenderCollection
- BlenderGeoNodes
- BlenderImage
- maxwell
- MaxwellMedium
- MaxwellMediumNonLinearity
- MaxwellStructure
- MaxwellBoundBox
- MaxwellBoundFace
- MaxwellMonitor
- MaxwellSimGrid
- FDTDSim
# Style
[ ] Rethink the meaning of color and shapes in node sockets, including whether dynamic functionality is needed when it comes to socket shape (ex. it might be nice to know whether a socket is array-like or uses units).
[ ] Rethink the meaning of color and shapes in node sockets, including whether dynamic functionality is needed when it comes to socket shape.
### GeoNode Trees
For ease of use, we can ship with premade node trees/groups for:
- Primitives
- Plane
- Box
- Sphere
- Cylinder
- Ring
- Capsule
- Cone
- Array
- Square Array: Takes a primitive shape.
- Hex Array: Takes a primitive shape.
- Hole Array
- Square Hole Array: Takes a primitive hole shape.
- Hex Hole Array: Takes a primitive hole shape.
- Cavities
- Hex Array w/ L-Cavity: Takes a primitive hole shape.
- Hex Array w/ H-Cavity: Takes a primitive hole shape.
- Crystal
- FCC Sphere Array: Takes a primitive spherical-like shape.
- BCC Sphere Array: Takes a primitive spherical-like shape.
- Wood Pile
# Architecture
## Registration and Contracts
[x] Finish the contract code converting from Blender sockets to our sockets based on dimensionality and the property description.
[ ] Refactor the node category code; it's ugly.
[?] Would be nice with some kind of indicator somewhere to help set good socket descriptions when using geonodes and wanting units.
When it comes to geometry, we do need to make sure
## Managed Objects
[x] Implement modifier support on the managed BL object, with special attention paid to the needs of the GeoNodes socket.
- [x] Implement preview toggling too, ex. using the relevant node tree collections
- Remember, the managed object is "dumb". It's the node's responsibility to react to any relevant `on_value_change`, and forward all state needed by the modifier to the managed obj. It's only the managed obj's responsibility to not update any modifier value that wouldn't change anything.
[ ] Implement loading the xarray-defined voxels into OpenVDB, saving it, and loading it as a managed BL object with the volume setting.
[ ] Implement basic jax-driven volume voxel processing, especially cube based slicing.
[ ] Implement jax-driven linear interpolation of volume voxels to an image texture, whose pixels are sized according to the dimensions of another managed plane object (perhaps a uniquely described Managed BL object itself).
### Notes
**NOTE**: When several geometries assigned to the same medium are assigned to the same `tidy3d.GeometryGroup`, there can apparently be "significant performance enhancement"s (<https://docs.flexcompute.com/projects/tidy3d/en/latest/_autosummary/tidy3d.GeometryGroup.html#tidy3d.GeometryGroup>).
- We can and should, in the Simulation builder (or just the structure concatenator), batch together structures with the same Medium.
## Utils or Services
[ ] Dedicated module for managing the interaction with the tidy3d cloud, to help nuke all the random caches out of existance.
**NOTE**: Some symmetries can be greatly important for performance. <https://docs.flexcompute.com/projects/tidy3d/en/latest/notebooks/Symmetry.html>
## Node Base Class
[ ] Dedicated `draw_preview`-type draw functions for plot customizations.
- [ ] For now, previewing isn't something I think should be part of the node
[ ] Custom `@cache`/`@lru_cache`/`@cached_property` which caches by instance ID (possibly based on `beartype` or `pydantic`).
[ ] When presets are used, if a preset is selected and the user alters a preset setting, then dynamically switch the preset indicator back to "Custom" to indicate that there is no active preset
[ ] It seems that `node.inputs` and `node.outputs` allows the use of a `move` method, which may allow reordering sockets dynamically, which we should expose to the user as user-configurable ordering rules (maybe resolved with a constraint solver).
[?] Mechanism for dynamic names (ex. "Library Medium" becoming "Au Medium")
[-] Mechanism for selecting a blender object managed by a particular node.
[ ] Mechanism for ex. specially coloring a node that is currently participating in the preview.
[ ] Custom callbacks when deleting a node (in `free()`), to ex. delete all previews with the viewer node.
## Socket Base Class
[ ] A feature `use_array` which allows a socket to declare that it can be both a single value and array-like (possibly constrained to a given shape). This should also allow the SocketDef to request that the input socket be initialised as a multi-input socket, once Blender updates to support those.
- [ ] 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
# TIDY3D BUGS
- Directly running `SimulationTask.get()` is bugged - it doesn't return some fields, including `created_at`. Listing tasks by folder is not broken.

View File

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

View File

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

View File

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

View File

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

View File

@ -1,471 +0,0 @@
import typing as typ
import typing_extensions as pytypes_ext
import enum
import sympy as sp
import sympy.physics.units as spu
import pydantic as pyd
import bpy
from ...utils.blender_type_enum import (
BlenderTypeEnum, append_cls_name_to_values
)
####################
# - 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_]+$',
)]
####################
# - Generic Types
####################
SocketReturnType = typ.TypeVar('SocketReturnType', covariant=True)
## - Covariance: If B subtypes A, then Container[B] subtypes Container[A].
## - This is absolutely what we want here.
####################
# - 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):
Any = enum.auto()
Text = enum.auto()
FilePath = enum.auto()
RationalNumber = enum.auto()
RealNumber = enum.auto()
ComplexNumber = enum.auto()
PhysicalLength = enum.auto()
PhysicalArea = enum.auto()
MaxwellSource = enum.auto()
MaxwellMedium = enum.auto()
MaxwellStructure = enum.auto()
MaxwellBound = enum.auto()
MaxwellFDTDSim = enum.auto()
####################
# - Node Types
####################
@append_cls_name_to_values
class NodeType(BlenderTypeEnum):
# Inputs
Time = enum.auto()
ObjectInfo = enum.auto()
FloatParameter = enum.auto()
ComplexParameter = enum.auto()
Vec3Parameter = enum.auto()
ScientificConstant = enum.auto()
FloatConstant = enum.auto()
ComplexConstant = enum.auto()
Vec3Constant = enum.auto()
FloatArrayElement = enum.auto()
ComplexArrayElement = enum.auto()
Vec3ArrayElement = enum.auto()
FloatDictElement = enum.auto()
ComplexDictElement = enum.auto()
Vec3DictElement = enum.auto()
FloatField = enum.auto()
ComplexField = enum.auto()
Vec3Field = enum.auto()
# Outputs
ValueViewer = enum.auto()
ConsoleViewer = enum.auto()
JSONFileExporter = enum.auto()
# Viz
TemporalShapeViz = enum.auto()
SourceViz = enum.auto()
StructureViz = enum.auto()
BoundViz = enum.auto()
FDTDViz = enum.auto()
# Sources
GaussianPulseTemporalShape = enum.auto()
ContinuousWaveTemporalShape = enum.auto()
DataDrivenTemporalShape = enum.auto()
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()
AddNonLinearity = enum.auto()
ChiThreeSusceptibilityNonLinearity = enum.auto()
TwoPhotonAbsorptionNonLinearity = enum.auto()
KerrNonLinearity = enum.auto()
# Structures
TriMeshStructure = enum.auto()
BoxStructure = enum.auto()
SphereStructure = enum.auto()
CylinderStructure = enum.auto()
GeoNodesStructure = enum.auto()
ScriptedStructure = enum.auto()
# Bounds
BoundBox = enum.auto()
PMLBoundFace = enum.auto()
PECBoundFace = enum.auto()
BlochBoundFace = enum.auto()
PeriodicBoundFace = enum.auto()
AbsorbingBoundFace = enum.auto()
# Monitors
EHFieldMonitor = enum.auto()
FieldPowerFluxMonitor = enum.auto()
EpsilonTensorMonitor = enum.auto()
DiffractionMonitor = enum.auto()
CartesianNearFieldProjectionMonitor = enum.auto()
ObservationAngleNearFieldProjectionMonitor = enum.auto()
KSpaceNearFieldProjectionMonitor = enum.auto()
# Simulations
FDTDSim = enum.auto()
SimulationGridDiscretization = enum.auto()
Automatic1DGridDiscretization = enum.auto()
Manual1DGridDiscretization = enum.auto()
Uniform1DGridDiscretization = enum.auto()
DataDriven1DGridDiscretization = enum.auto()
# Utilities
FloatMath = enum.auto()
ComplexMath = enum.auto()
Vec3Math = enum.auto()
FloatFieldMath = enum.auto()
ComplexFieldMath = enum.auto()
Vec3FieldMath = enum.auto()
SpectralMath = enum.auto()
####################
# - Node Category Types
####################
class NodeCategory(BlenderTypeEnum):
MAXWELL_SIM = enum.auto()
# Inputs/
MAXWELL_SIM_INPUTS = enum.auto()
MAXWELL_SIM_INPUTS_SCENE = enum.auto()
MAXWELL_SIM_INPUTS_PARAMETERS = enum.auto()
MAXWELL_SIM_INPUTS_CONSTANTS = enum.auto()
MAXWELL_SIM_INPUTS_ARRAY = enum.auto()
MAXWELL_SIM_INPUTS_ARRAY_ELEMENTS = enum.auto()
MAXWELL_SIM_INPUTS_ARRAY_UNIONS = enum.auto()
MAXWELL_SIM_INPUTS_DICTIONARY = enum.auto()
MAXWELL_SIM_INPUTS_DICTIONARY_ELEMENTS = enum.auto()
MAXWELL_SIM_INPUTS_DICTIONARY_UNIONS = enum.auto()
MAXWELL_SIM_INPUTS_FIELDS = enum.auto()
# Outputs/
MAXWELL_SIM_OUTPUTS = enum.auto()
MAXWELL_SIM_OUTPUTS_VIEWERS = enum.auto()
MAXWELL_SIM_OUTPUTS_EXPORTERS = enum.auto()
# Viz/
MAXWELL_SIM_VIZ = enum.auto()
# Sources/
MAXWELL_SIM_SOURCES = enum.auto()
MAXWELL_SIM_SOURCES_TEMPORALSHAPES = enum.auto()
MAXWELL_SIM_SOURCES_MODELLED = enum.auto()
MAXWELL_SIM_SOURCES_DATADRIVEN = enum.auto()
# Mediums/
MAXWELL_SIM_MEDIUMS = enum.auto()
MAXWELL_SIM_MEDIUMS_LINEARMEDIUMS = enum.auto()
MAXWELL_SIM_MEDIUMS_LINEARMEDIUMS_DIRECT = enum.auto()
MAXWELL_SIM_MEDIUMS_LINEARMEDIUMS_MODELLED = enum.auto()
MAXWELL_SIM_MEDIUMS_NONLINEARITIES = enum.auto()
# Structures/
MAXWELL_SIM_STRUCTURES = enum.auto()
MAXWELL_SIM_STRUCTURES_PRIMITIES = enum.auto()
MAXWELL_SIM_STRUCTURES_GENERATED = enum.auto()
# Bounds/
MAXWELL_SIM_BOUNDS = enum.auto()
MAXWELL_SIM_BOUNDS_BOUNDFACES = enum.auto()
# Monitors/
MAXWELL_SIM_MONITORS = enum.auto()
MAXWELL_SIM_MONITORS_NEARFIELDPROJECTIONS = enum.auto()
# Simulations/
MAXWELL_SIM_SIMULATIONS = enum.auto()
MAXWELL_SIM_SIMULATIONS_DISCRETIZATIONS = enum.auto()
MAXWELL_SIM_SIMULATIONS_DISCRETIZATIONS_1DGRID = enum.auto()
# Utilities/
MAXWELL_SIM_UTILITIES = enum.auto()
MAXWELL_SIM_UTILITIES_MATH = enum.auto()
MAXWELL_SIM_UTILITIES_FIELDMATH = enum.auto()
@classmethod
def get_tree(cls):
## TODO: Refactor
syllable_categories = [
node_category.value.split("_")
for node_category in cls
if node_category.value != "MAXWELL_SIM"
]
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.MAXWELL_SIM_INPUTS: "Inputs",
NodeCategory.MAXWELL_SIM_INPUTS_SCENE: "Scene",
NodeCategory.MAXWELL_SIM_INPUTS_PARAMETERS: "Parameters",
NodeCategory.MAXWELL_SIM_INPUTS_CONSTANTS: "Constants",
NodeCategory.MAXWELL_SIM_INPUTS_ARRAY: "Array",
NodeCategory.MAXWELL_SIM_INPUTS_ARRAY_ELEMENTS: "Elements",
NodeCategory.MAXWELL_SIM_INPUTS_ARRAY_UNIONS: "Unions",
NodeCategory.MAXWELL_SIM_INPUTS_DICTIONARY: "Dictionary",
NodeCategory.MAXWELL_SIM_INPUTS_DICTIONARY_ELEMENTS: "Elements",
NodeCategory.MAXWELL_SIM_INPUTS_DICTIONARY_UNIONS: "Unions",
NodeCategory.MAXWELL_SIM_INPUTS_FIELDS: "Fields",
# Outputs/
NodeCategory.MAXWELL_SIM_OUTPUTS: "Outputs",
NodeCategory.MAXWELL_SIM_OUTPUTS_VIEWERS: "Viewers",
NodeCategory.MAXWELL_SIM_OUTPUTS_EXPORTERS: "Exporters",
# Viz/
NodeCategory.MAXWELL_SIM_VIZ: "Viz",
# Sources/
NodeCategory.MAXWELL_SIM_SOURCES: "Sources",
NodeCategory.MAXWELL_SIM_SOURCES_TEMPORALSHAPES: "Temporal Shapes",
NodeCategory.MAXWELL_SIM_SOURCES_MODELLED: "Modelled",
NodeCategory.MAXWELL_SIM_SOURCES_DATADRIVEN: "Data-Driven",
# Mediums/
NodeCategory.MAXWELL_SIM_MEDIUMS: "Mediums",
NodeCategory.MAXWELL_SIM_MEDIUMS_LINEARMEDIUMS: "Linear Mediums",
NodeCategory.MAXWELL_SIM_MEDIUMS_LINEARMEDIUMS_DIRECT: "Direct",
NodeCategory.MAXWELL_SIM_MEDIUMS_LINEARMEDIUMS_MODELLED: "Modelled",
NodeCategory.MAXWELL_SIM_MEDIUMS_NONLINEARITIES: "Non-Linearities",
# Structures/
NodeCategory.MAXWELL_SIM_STRUCTURES: "Structures",
NodeCategory.MAXWELL_SIM_STRUCTURES_PRIMITIES: "Primitives",
NodeCategory.MAXWELL_SIM_STRUCTURES_GENERATED: "Generated",
# Bounds/
NodeCategory.MAXWELL_SIM_BOUNDS: "Bounds",
NodeCategory.MAXWELL_SIM_BOUNDS_BOUNDFACES: "Bound Faces",
# Monitors/
NodeCategory.MAXWELL_SIM_MONITORS: "Monitors",
NodeCategory.MAXWELL_SIM_MONITORS_NEARFIELDPROJECTIONS: "Near-Field Projections",
# Simulations/
NodeCategory.MAXWELL_SIM_SIMULATIONS: "Simulations",
NodeCategory.MAXWELL_SIM_SIMULATIONS_DISCRETIZATIONS: "Discretizations",
NodeCategory.MAXWELL_SIM_SIMULATIONS_DISCRETIZATIONS_1DGRID: "1D Grid Discretizations",
# Utilities/
NodeCategory.MAXWELL_SIM_UTILITIES: "Utilities",
NodeCategory.MAXWELL_SIM_UTILITIES_MATH: "Math",
NodeCategory.MAXWELL_SIM_UTILITIES_FIELDMATH: "Field Math",
}
####################
# - 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]
@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,56 @@
####################
# - 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_from_bl_desc import BL_SOCKET_DESCR_TYPE_MAP
from .socket_from_bl_direct import BL_SOCKET_DIRECT_TYPE_MAP
from .socket_from_bl_desc import BL_SOCKET_DESCR_ANNOT_STRING
####################
# - Node Types
####################
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,49 @@
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_BOUNDCONDS: "Bound Conds",
# 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",
# Viz/
NC.MAXWELLSIM_VIZ: "Viz",
}

View File

@ -0,0 +1,77 @@
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_BOUNDCONDS = 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()
# Viz/
MAXWELLSIM_VIZ = 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,152 @@
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
BoundConds = enum.auto()
## Bounds / Bound Faces
PMLBoundCond = enum.auto()
PECBoundCond = enum.auto()
PMCBoundCond = enum.auto()
BlochBoundCond = enum.auto()
PeriodicBoundCond = enum.auto()
AbsorbingBoundCond = 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()
# Viz
FDTDSimDataViz = 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,33 @@
import typing as typ
import typing as typx
import pydantic as pyd
import bpy
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,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.String: (0.7, 0.7, 0.7, 1.0), # Medium Light Grey
ST.FilePath: (0.6, 0.6, 0.6, 1.0), # Medium Grey
# 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.Integer2DVector: (0.5, 1.0, 0.5, 1.0), # Light Green
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.Integer3DVector: (0.3, 0.8, 0.3, 1.0), # Medium 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.PhysicalAccel3D: (0.7, 0.5, 0.3, 1.0), # Medium Orange
ST.PhysicalForce3D: (0.6, 0.45, 0.25, 1.0), # Medium Dark Orange
ST.PhysicalPol: (0.5, 0.4, 0.2, 1.0), # Dark Orange
ST.PhysicalFreq: (1.0, 0.7, 0.5, 1.0), # Light Peach
# 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.BlenderGeoNodes: (0.3, 0.3, 0.6, 1.0), # Dark Purple
ST.BlenderText: (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.MaxwellBoundConds: (0.9, 0.8, 0.5, 1.0), # Light Gold
ST.MaxwellBoundCond: (0.8, 0.7, 0.45, 1.0), # Medium Light Gold
ST.MaxwellMonitor: (0.7, 0.6, 0.4, 1.0), # Medium Gold
ST.MaxwellFDTDSim: (0.6, 0.5, 0.35, 1.0), # Medium Dark Gold
ST.MaxwellFDTDSimData: (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,78 @@
from .socket_types import SocketType as ST
BL_SOCKET_DESCR_ANNOT_STRING = ":: "
BL_SOCKET_DESCR_TYPE_MAP = {
("Time", "NodeSocketFloat", 1): ST.PhysicalTime,
("Angle", "NodeSocketFloat", 1): ST.PhysicalAngle,
("SolidAngle", "NodeSocketFloat", 1): ST.PhysicalSolidAngle,
("Rotation", "NodeSocketVector", 2): ST.PhysicalRot2D,
("Rotation", "NodeSocketVector", 3): ST.PhysicalRot3D,
("Freq", "NodeSocketFloat", 1): ST.PhysicalFreq,
("AngFreq", "NodeSocketFloat", 1): ST.PhysicalAngFreq,
## Cartesian
("Length", "NodeSocketFloat", 1): ST.PhysicalLength,
("Area", "NodeSocketFloat", 1): ST.PhysicalArea,
("Volume", "NodeSocketFloat", 1): ST.PhysicalVolume,
("Disp", "NodeSocketVector", 2): ST.PhysicalDisp2D,
("Disp", "NodeSocketVector", 3): ST.PhysicalDisp3D,
("Point", "NodeSocketFloat", 1): ST.PhysicalPoint1D,
("Point", "NodeSocketVector", 2): ST.PhysicalPoint2D,
("Point", "NodeSocketVector", 3): ST.PhysicalPoint3D,
("Size", "NodeSocketVector", 2): ST.PhysicalSize2D,
("Size", "NodeSocketVector", 3): ST.PhysicalSize3D,
## Mechanical
("Mass", "NodeSocketFloat", 1): ST.PhysicalMass,
("Speed", "NodeSocketFloat", 1): ST.PhysicalSpeed,
("Vel", "NodeSocketVector", 2): ST.PhysicalVel2D,
("Vel", "NodeSocketVector", 3): ST.PhysicalVel3D,
("Accel", "NodeSocketFloat", 1): ST.PhysicalAccelScalar,
("Accel", "NodeSocketVector", 2): ST.PhysicalAccel2D,
("Accel", "NodeSocketVector", 3): ST.PhysicalAccel3D,
("Force", "NodeSocketFloat", 1): ST.PhysicalForceScalar,
("Force", "NodeSocketVector", 2): ST.PhysicalForce2D,
("Force", "NodeSocketVector", 3): ST.PhysicalForce3D,
("Pressure", "NodeSocketFloat", 1): ST.PhysicalPressure,
## Energetic
("Energy", "NodeSocketFloat", 1): ST.PhysicalEnergy,
("Power", "NodeSocketFloat", 1): ST.PhysicalPower,
("Temp", "NodeSocketFloat", 1): ST.PhysicalTemp,
## ELectrodynamical
("Curr", "NodeSocketFloat", 1): ST.PhysicalCurr,
("CurrDens", "NodeSocketVector", 2): ST.PhysicalCurrDens2D,
("CurrDens", "NodeSocketVector", 3): ST.PhysicalCurrDens3D,
("Charge", "NodeSocketFloat", 1): ST.PhysicalCharge,
("Voltage", "NodeSocketFloat", 1): ST.PhysicalVoltage,
("Capacitance", "NodeSocketFloat", 1): ST.PhysicalCapacitance,
("Resistance", "NodeSocketFloat", 1): ST.PhysicalResistance,
("Conductance", "NodeSocketFloat", 1): ST.PhysicalConductance,
("MagFlux", "NodeSocketFloat", 1): ST.PhysicalMagFlux,
("MagFluxDens", "NodeSocketFloat", 1): ST.PhysicalMagFluxDens,
("Inductance", "NodeSocketFloat", 1): ST.PhysicalInductance,
("EField", "NodeSocketFloat", 2): ST.PhysicalEField3D,
("EField", "NodeSocketFloat", 3): ST.PhysicalEField2D,
("HField", "NodeSocketFloat", 2): ST.PhysicalHField3D,
("HField", "NodeSocketFloat", 3): ST.PhysicalHField2D,
## Luminal
("LumIntensity", "NodeSocketFloat", 1): ST.PhysicalLumIntensity,
("LumFlux", "NodeSocketFloat", 1): ST.PhysicalLumFlux,
("Illuminance", "NodeSocketFloat", 1): ST.PhysicalIlluminance,
## Optical
("PolJones", "NodeSocketFloat", 2): ST.PhysicalPolJones,
("Pol", "NodeSocketFloat", 4): ST.PhysicalPol,
}

View File

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

View File

@ -0,0 +1,68 @@
from .socket_types import SocketType as ST
SOCKET_SHAPES = {
# Basic
ST.Any: "CIRCLE",
ST.Bool: "CIRCLE",
ST.String: "CIRCLE",
ST.FilePath: "CIRCLE",
# Number
ST.IntegerNumber: "CIRCLE",
ST.RationalNumber: "CIRCLE",
ST.RealNumber: "CIRCLE",
ST.ComplexNumber: "CIRCLE",
# Vector
ST.Integer2DVector: "CIRCLE",
ST.Real2DVector: "CIRCLE",
ST.Complex2DVector: "CIRCLE",
ST.Integer3DVector: "CIRCLE",
ST.Real3DVector: "CIRCLE",
ST.Complex3DVector: "CIRCLE",
# Physical
ST.PhysicalUnitSystem: "CIRCLE",
ST.PhysicalTime: "CIRCLE",
ST.PhysicalAngle: "CIRCLE",
ST.PhysicalLength: "CIRCLE",
ST.PhysicalArea: "CIRCLE",
ST.PhysicalVolume: "CIRCLE",
ST.PhysicalPoint2D: "CIRCLE",
ST.PhysicalPoint3D: "CIRCLE",
ST.PhysicalSize2D: "CIRCLE",
ST.PhysicalSize3D: "CIRCLE",
ST.PhysicalMass: "CIRCLE",
ST.PhysicalSpeed: "CIRCLE",
ST.PhysicalAccelScalar: "CIRCLE",
ST.PhysicalForceScalar: "CIRCLE",
ST.PhysicalAccel3D: "CIRCLE",
ST.PhysicalForce3D: "CIRCLE",
ST.PhysicalPol: "CIRCLE",
ST.PhysicalFreq: "CIRCLE",
# Blender
ST.BlenderObject: "DIAMOND",
ST.BlenderCollection: "DIAMOND",
ST.BlenderImage: "DIAMOND",
ST.BlenderGeoNodes: "DIAMOND",
ST.BlenderText: "DIAMOND",
# Maxwell
ST.MaxwellSource: "CIRCLE",
ST.MaxwellTemporalShape: "CIRCLE",
ST.MaxwellMedium: "CIRCLE",
ST.MaxwellMediumNonLinearity: "CIRCLE",
ST.MaxwellStructure: "CIRCLE",
ST.MaxwellBoundConds: "CIRCLE",
ST.MaxwellBoundCond: "CIRCLE",
ST.MaxwellMonitor: "CIRCLE",
ST.MaxwellFDTDSim: "CIRCLE",
ST.MaxwellFDTDSimData: "CIRCLE",
ST.MaxwellSimGrid: "CIRCLE",
ST.MaxwellSimGridAxis: "CIRCLE",
ST.MaxwellSimDomain: "CIRCLE",
# Tidy3D
ST.Tidy3DCloudTask: "DIAMOND",
}

View File

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

View File

@ -0,0 +1,266 @@
import sympy.physics.units as spu
from ....utils import extra_sympy_units as spux
from .socket_types import SocketType as ST
SOCKET_UNITS = {
ST.PhysicalTime: {
"default": "PS",
"values": {
"FS": spux.femtosecond,
"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": spux.nanonewton,
"UNEWT": spux.micronewton,
"MNEWT": spux.millinewton,
"NEWT": spu.newton,
},
},
ST.PhysicalAccel3D: {
"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.PhysicalForce3D: {
"default": "UNEWT",
"values": {
"KG_M_S_SQ": spu.kg * spu.m/spu.second**2,
"NNEWT": spux.nanonewton,
"UNEWT": spux.micronewton,
"MNEWT": spux.millinewton,
"NEWT": spu.newton,
},
},
ST.PhysicalFreq: {
"default": "THZ",
"values": {
"HZ": spu.hertz,
"KHZ": spux.kilohertz,
"MHZ": spux.megahertz,
"GHZ": spux.gigahertz,
"THZ": spux.terahertz,
"PHZ": spux.petahertz,
"EHZ": spux.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,205 @@
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):
# Image Doesn't Exist
if not (bl_image := bpy.data.images.get(self._bl_image_name)):
# ...AND Desired Image Name is Not Taken
if not bpy.data.objects.get(value):
self._bl_image_name = value
return
# ...AND Desired Image Name is Taken
else:
msg = f"Desired name {value} for BL image is taken"
raise ValueError(msg)
# Object DOES Exist
bl_image.name = value
self._bl_image_name = bl_image.name
## - When name exists, Blender adds .### to prevent overlap.
## - `set_name` is allowed to change the name; nodes account for this.
def free(self):
if not (bl_image := bpy.data.images.get(self.name)):
msg = "Can't free BL image that doesn't exist"
raise ValueError(msg)
bpy.data.images.remove(bl_image)
####################
# - 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,392 @@
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
ModifierType = typx.Literal["NODES", "ARRAY"]
MODIFIER_NAMES = {
"NODES": "BLMaxwell_GeoNodes",
"ARRAY": "BLMaxwell_Array",
}
MANAGED_COLLECTION_NAME = "BLMaxwell"
PREVIEW_COLLECTION_NAME = "BLMaxwell Visible"
def bl_collection(
collection_name: str, view_layer_exclude: bool
) -> bpy.types.Collection:
# Init the "Managed Collection"
# Ensure Collection exists (and is in the Scene collection)
if collection_name not in bpy.data.collections:
collection = bpy.data.collections.new(collection_name)
bpy.context.scene.collection.children.link(collection)
else:
collection = bpy.data.collections[collection_name]
## Ensure synced View Layer exclusion
if (layer_collection := bpy.context.view_layer.layer_collection.children[
collection_name
]).exclude != view_layer_exclude:
layer_collection.exclude = view_layer_exclude
return collection
class ManagedBLObject(ct.schemas.ManagedObj):
managed_obj_type = ct.ManagedObjType.ManagedBLObject
_bl_object_name: str
def __init__(self, name: str):
self._bl_object_name = name
# Object Name
@property
def name(self):
return self._bl_object_name
@name.setter
def set_name(self, value: str) -> None:
# Object Doesn't Exist
if not (bl_object := bpy.data.objects.get(self._bl_object_name)):
# ...AND Desired Object Name is Not Taken
if not bpy.data.objects.get(value):
self._bl_object_name = value
return
# ...AND Desired Object Name is Taken
else:
msg = f"Desired name {value} for BL object is taken"
raise ValueError(msg)
# Object DOES Exist
bl_object.name = value
self._bl_object_name = bl_object.name
## - When name exists, Blender adds .### to prevent overlap.
## - `set_name` is allowed to change the name; nodes account for this.
# Object Datablock Name
@property
def bl_mesh_name(self):
return self.name
@property
def bl_volume_name(self):
return self.name
# Deallocation
def free(self):
if not (bl_object := bpy.data.objects.get(self.name)):
return ## Nothing to do
# Delete the Underlying Datablock
## This automatically deletes the object too
if bl_object.type == "MESH":
bpy.data.meshes.remove(bl_object.data)
elif bl_object.type == "EMPTY":
bpy.data.meshes.remove(bl_object.data)
elif bl_object.type == "VOLUME":
bpy.data.volumes.remove(bl_object.data)
else:
msg = f"Type of to-delete `bl_object`, {bl_object.type}, is not valid"
raise ValueError(msg)
####################
# - Actions
####################
def show_preview(
self,
kind: typx.Literal["MESH", "EMPTY", "VOLUME"],
empty_display_type: typx.Literal[
"PLAIN_AXES", "ARROWS", "SINGLE_ARROW", "CIRCLE", "CUBE",
"SPHERE", "CONE", "IMAGE",
] | None = None,
) -> None:
"""Moves the managed Blender object to the preview collection.
If it's already included, do nothing.
"""
bl_object = self.bl_object(kind)
if bl_object.name not in (preview_collection := bl_collection(
PREVIEW_COLLECTION_NAME, view_layer_exclude=False
)).objects:
preview_collection.objects.link(bl_object)
if kind == "EMPTY" and empty_display_type is not None:
bl_object.empty_display_type = empty_display_type
def hide_preview(
self,
kind: typx.Literal["MESH", "EMPTY", "VOLUME"],
) -> None:
"""Removes the managed Blender object from the preview collection.
If it's already removed, do nothing.
"""
bl_object = self.bl_object(kind)
if bl_object.name not in (preview_collection := bl_collection(
PREVIEW_COLLECTION_NAME, view_layer_exclude=False
)).objects:
preview_collection.objects.unlink(bl_object)
def bl_select(self) -> None:
"""Selects the managed Blender object globally, causing it to be ex.
outlined in the 3D viewport.
"""
if not (bl_object := bpy.data.objects.get(self.name)):
msg = "Managed BLObject does not exist"
raise ValueError(msg)
bpy.ops.object.select_all(action='DESELECT')
bl_object.select_set(True)
####################
# - Managed Object Management
####################
def bl_object(
self,
kind: typx.Literal["MESH", "EMPTY", "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.objects.get(self.name))
and bl_object.type != kind
):
self.free()
# Create Object w/Appropriate Data Block
if not (bl_object := bpy.data.objects.get(self.name)):
if kind == "MESH":
bl_data = bpy.data.meshes.new(self.bl_mesh_name)
elif kind == "EMPTY":
bl_data = None
elif kind == "VOLUME":
raise NotImplementedError
else:
msg = f"Requested `bl_object` type {bl_object.type} is not valid"
raise ValueError(msg)
bl_object = bpy.data.objects.new(self.name, bl_data)
bl_collection(
MANAGED_COLLECTION_NAME, view_layer_exclude=True
).objects.link(bl_object)
return bl_object
####################
# - Mesh 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.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 mesh_as_bmesh(
self,
evaluate: bool = True,
triangulate: bool = False,
) -> bpy.types.Mesh:
if (
(bl_object := bpy.data.objects.get(self.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()
else:
msg = f"Requested BMesh from `bl_object` of type {bl_object.type}"
raise ValueError(msg)
@property
def mesh_as_arrays(self) -> dict:
## TODO: Cached
# Ensure Updated Geometry
bpy.context.view_layer.update()
## TODO: Must we?
# Compute Evaluted + Triangulated Mesh
_mesh = bpy.data.meshes.new(name="TemporaryMesh")
with self.mesh_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,
}
####################
# - Modifier Methods
####################
def bl_modifier(
self,
modifier_type: ModifierType,
):
"""Creates a new modifier for the current `bl_object`.
For all Blender modifier type names, see: <https://docs.blender.org/api/current/bpy_types_enum_items/object_modifier_type_items.html#rna-enum-object-modifier-type-items>
"""
if not (bl_object := bpy.data.objects.get(self.name)):
msg = "Can't add modifier to BL object that doesn't exist"
raise ValueError(msg)
# (Create and) Return Modifier
bl_modifier_name = MODIFIER_NAMES[modifier_type]
if bl_modifier_name not in bl_object.modifiers:
return bl_object.modifiers.new(
name=bl_modifier_name,
type=modifier_type,
)
return bl_object.modifiers[bl_modifier_name]
def modifier_attrs(self, modifier_type: ModifierType) -> dict:
"""Based on the modifier type, retrieve a representative dictionary of modifier attributes.
The attributes can then easily be set using `setattr`.
"""
bl_modifier = self.bl_modifier(modifier_type)
if modifier_type == "NODES":
return {
"node_group": bl_modifier.node_group,
}
elif modifier_type == "ARRAY":
raise NotImplementedError
def s_modifier_attrs(
self,
modifier_type: ModifierType,
modifier_attrs: dict,
):
bl_modifier = self.bl_modifier(modifier_type)
if modifier_type == "NODES":
if bl_modifier.node_group != modifier_attrs["node_group"]:
bl_modifier.node_group = modifier_attrs["node_group"]
elif modifier_type == "ARRAY":
raise NotImplementedError
####################
# - GeoNodes Modifier
####################
def sync_geonodes_modifier(
self,
geonodes_node_group,
geonodes_identifier_to_value: dict,
):
"""Push the given GeoNodes Interface values to a GeoNodes modifier attached to a managed MESH object.
The values must be compatible with the `default_value`s of the interface sockets.
If there is no object, it is created.
If the object isn't a MESH object, it is made so.
If the GeoNodes modifier doesn't exist, it is created.
If the GeoNodes node group doesn't match, it is changed.
Only differing interface values are actually changed.
"""
bl_object = self.bl_object("MESH")
# Get (/make) a GeoModes Modifier
bl_modifier = self.bl_modifier("NODES")
# Set GeoNodes Modifier Attributes (specifically, the 'node_group')
self.s_modifier_attrs("NODES", {"node_group": geonodes_node_group})
# Set GeoNodes Values
modifier_altered = False
for interface_identifier, value in (
geonodes_identifier_to_value.items()
):
if bl_modifier[interface_identifier] != value:
# Quickly Determine if IDPropertyArray is Equal
if hasattr(
bl_modifier[interface_identifier],
"to_list"
) and tuple(
bl_modifier[interface_identifier].to_list()
) == value:
continue
# Quickly Determine int/float Mismatch
if isinstance(
bl_modifier[interface_identifier],
float,
) and isinstance(value, int):
value = float(value)
bl_modifier[interface_identifier] = value
modifier_altered = True
# Update DepGraph (if anything changed)
if modifier_altered:
bl_object.data.update()
#@property
#def volume(self) -> bpy.types.Volume:
# """Returns the object's volume data.
#
# 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,57 +1,185 @@
import typing as typ
import bpy
from . import contracts
from . import contracts as ct
ICON_SIM_TREE = 'MOD_SIMPLEDEFORM'
####################
# - Cache Management
####################
MemAddr = int
class DeltaNodeLinkCache(typ.TypedDict):
added: set[MemAddr]
removed: set[MemAddr]
class NodeLinkCache:
def __init__(self, node_tree: bpy.types.NodeTree):
# Initialize Parameters
self._node_tree = node_tree
self.link_ptrs_to_links = {}
self.link_ptrs = set()
self.link_ptrs_from_sockets = {}
self.link_ptrs_to_sockets = {}
# Fill Cache
self.regenerate()
def remove(self, link_ptrs: set[MemAddr]) -> None:
for link_ptr in link_ptrs:
self.link_ptrs.remove(link_ptr)
self.link_ptrs_to_links.pop(link_ptr, None)
def regenerate(self) -> DeltaNodeLinkCache:
current_link_ptrs_to_links = {
link.as_pointer(): link for link in self._node_tree.links
}
current_link_ptrs = set(current_link_ptrs_to_links.keys())
# Compute Delta
added_link_ptrs = current_link_ptrs - self.link_ptrs
removed_link_ptrs = self.link_ptrs - current_link_ptrs
# Update Caches Incrementally
self.remove(removed_link_ptrs)
self.link_ptrs |= added_link_ptrs
for link_ptr in added_link_ptrs:
link = current_link_ptrs_to_links[link_ptr]
self.link_ptrs_to_links[link_ptr] = link
self.link_ptrs_from_sockets[link_ptr] = link.from_socket
self.link_ptrs_to_sockets[link_ptr] = link.to_socket
return {"added": added_link_ptrs, "removed": removed_link_ptrs}
####################
# - Node Tree Definition
####################
class MaxwellSimTree(bpy.types.NodeTree):
bl_idname = contracts.TreeType.MaxwellSim
bl_idname = ct.TreeType.MaxwellSim.value
bl_label = "Maxwell Sim Editor"
bl_icon = contracts.Icon.MaxwellSimTree
bl_icon = ct.Icon.SimNodeEditor.value
####################
# - 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
####################
# - Init Methods
####################
def on_load(self):
"""Run by Blender when loading the NodeSimTree, ex. on file load, on creation, etc. .
It's a bit of a "fake" function - in practicality, it's triggered on the first update() function.
"""
## TODO: Consider tying this to an "on_load" handler
self._node_link_cache = NodeLinkCache(self)
####################
# - Update Methods
####################
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.on_load()
## We presume update() is run before the first link is altered.
## - Else, the first link of the session will not update caches.
## - We remain slightly unsure of the semantics.
## - More testing needed to prevent this 'first-link bug'.
return
# Compute Changes to NodeLink Cache
delta_links = self._node_link_cache.regenerate()
link_alterations = {
"to_remove": [],
"to_add": [],
}
for link_ptr in delta_links["removed"]:
from_socket = self._node_link_cache.link_ptrs_from_sockets[link_ptr]
to_socket = self._node_link_cache.link_ptrs_to_sockets[link_ptr]
# Update Socket Caches
self._node_link_cache.link_ptrs_from_sockets.pop(link_ptr, None)
self._node_link_cache.link_ptrs_to_sockets.pop(link_ptr, None)
# Trigger Report Chain on Socket that Just Lost a Link
## Aka. Forward-Refresh Caches Relying on Linkage
if not (
consent_removal := to_socket.sync_link_removed(from_socket)
):
# Did Not Consent to Removal: Queue Add Link
link_alterations["to_add"].append((from_socket, to_socket))
for link_ptr in delta_links["added"]:
link = self._node_link_cache.link_ptrs_to_links.get(link_ptr)
if link is None: continue
# Trigger Report Chain on Socket that Just Gained a Link
## Aka. Forward-Refresh Caches Relying on Linkage
if not (
consent_added := link.to_socket.sync_link_added(link)
):
# Did Not Consent to Addition: Queue Remove Link
link_alterations["to_remove"].append(link)
# Execute Queued Operations
## - Especially undoing undesirable link changes.
## - This is important for locked graphs, whose links must not change.
for link in link_alterations["to_remove"]:
self.links.remove(link)
for from_socket, to_socket in link_alterations["to_add"]:
self.links.new(from_socket, to_socket)
# If Queued Operations: Regenerate Cache
## - This prevents the next update() from picking up on alterations.
if link_alterations["to_remove"] or link_alterations["to_add"]:
self._node_link_cache.regenerate()
####################
# - Post-Load Handler
####################
def initialize_sim_tree_node_link_cache(scene: bpy.types.Scene):
"""Whenever a file is loaded, create/regenerate the NodeLinkCache in all trees.
"""
for node_tree in bpy.data.node_groups:
if node_tree.bl_idname == "MaxwellSimTree":
if not hasattr(node_tree, "_node_link_cache"):
node_tree._node_link_cache = NodeLinkCache(node_tree)
else:
node_tree._node_link_cache.regenerate()
####################
# - Blender Registration
####################
bpy.app.handlers.load_post.append(initialize_sim_tree_node_link_cache)
BL_REGISTER = [
MaxwellSimTree,
]
####################
# - Red Edges on Error
####################
## TODO: Refactor
#def link_callback_new(context):
# print("A THING HAPPENED")
# node_tree_type = contracts.TreeType.MaxwellSim.value
# link = context.link
#
# if not (
# link.from_node.node_tree.bl_idname == node_tree_type
# and link.to_node.node_tree.bl_idname == node_tree_type
# ):
# return
#
# source_node = link.from_node
#
# source_socket_name = source_node.g_output_socket_name(
# link.from_socket.name
# )
# link_data = source_node.compute_output(source_socket_name)
#
# destination_socket = link.to_socket
# link.is_valid = destination_socket.is_compatible(link_data)
#
# print(source_node, destination_socket, link.is_valid)
#
#bpy.msgbus.subscribe_rna(
# key=("active", "node_tree"),
# owner=MaxwellSimTree,
# args=(bpy.context,),
# notify=link_callback_new,
# options={'PERSISTENT'}
#)

View File

@ -1,23 +1,39 @@
#from . import kitchen_sink
from . import inputs
from . import outputs
from . import sources
from . import mediums
from . import simulations
from . import structures
#from . import bounds
from . import monitors
from . import simulations
from . import utilities
from . import viz
BL_REGISTER = [
#*kitchen_sink.BL_REGISTER,
*inputs.BL_REGISTER,
*outputs.BL_REGISTER,
*mediums.BL_REGISTER,
*sources.BL_REGISTER,
*simulations.BL_REGISTER,
*mediums.BL_REGISTER,
*structures.BL_REGISTER,
# *bounds.BL_REGISTER,
*monitors.BL_REGISTER,
*simulations.BL_REGISTER,
*utilities.BL_REGISTER,
*viz.BL_REGISTER,
]
BL_NODES = {
#**kitchen_sink.BL_NODES,
**inputs.BL_NODES,
**outputs.BL_NODES,
**sources.BL_NODES,
**mediums.BL_NODES,
**simulations.BL_NODES,
**structures.BL_NODES,
# **bounds.BL_NODES,
**monitors.BL_NODES,
**simulations.BL_NODES,
**utilities.BL_NODES,
**viz.BL_NODES,
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -0,0 +1,71 @@
import tidy3d as td
import sympy as sp
import sympy.physics.units as spu
from ... import contracts as ct
from ... import sockets
from .. import base
class BoundCondsNode(base.MaxwellSimNode):
node_type = ct.NodeType.BoundConds
bl_label = "Bound Box"
#bl_icon = ...
####################
# - Sockets
####################
input_sockets = {
"+X": sockets.MaxwellBoundCondSocketDef(),
"-X": sockets.MaxwellBoundCondSocketDef(),
"+Y": sockets.MaxwellBoundCondSocketDef(),
"-Y": sockets.MaxwellBoundCondSocketDef(),
"+Z": sockets.MaxwellBoundCondSocketDef(),
"-Z": sockets.MaxwellBoundCondSocketDef(),
}
output_sockets = {
"BCs": sockets.MaxwellBoundCondsSocketDef(),
}
####################
# - Output Socket Computation
####################
@base.computes_output_socket(
"BCs",
input_sockets={"+X", "-X", "+Y", "-Y", "+Z", "-Z"}
)
def compute_simulation(self, input_sockets) -> td.BoundarySpec:
x_pos = input_sockets["+X"]
x_neg = input_sockets["-X"]
y_pos = input_sockets["+Y"]
y_neg = input_sockets["-Y"]
z_pos = input_sockets["+Z"]
z_neg = input_sockets["-Z"]
return td.BoundarySpec(
x=td.Boundary(
plus=x_pos,
minus=x_neg,
),
y=td.Boundary(
plus=y_pos,
minus=y_neg,
),
z=td.Boundary(
plus=z_pos,
minus=z_neg,
),
)
####################
# - Blender Registration
####################
BL_REGISTER = [
BoundCondsNode,
]
BL_NODES = {
ct.NodeType.BoundConds: (
ct.NodeCategory.MAXWELLSIM_BOUNDS
)
}

View File

@ -0,0 +1,26 @@
from . import pml_bound_face
from . import pec_bound_face
from . import pmc_bound_face
from . import bloch_bound_face
from . import periodic_bound_face
from . import absorbing_bound_face
BL_REGISTER = [
*pml_bound_face.BL_REGISTER,
*pec_bound_face.BL_REGISTER,
*pmc_bound_face.BL_REGISTER,
*bloch_bound_face.BL_REGISTER,
*periodic_bound_face.BL_REGISTER,
*absorbing_bound_face.BL_REGISTER,
]
BL_NODES = {
**pml_bound_face.BL_NODES,
**pec_bound_face.BL_NODES,
**pmc_bound_face.BL_NODES,
**bloch_bound_face.BL_NODES,
**periodic_bound_face.BL_NODES,
**absorbing_bound_face.BL_NODES,
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,52 @@
import typing as typ
from .... import contracts as ct
from .... import sockets
from ... import base
class BlenderConstantNode(base.MaxwellSimNode):
node_type = ct.NodeType.BlenderConstant
bl_label = "Blender Constant"
input_socket_sets = {
"Object": {
"Value": sockets.BlenderObjectSocketDef(),
},
"Collection": {
"Value": sockets.BlenderCollectionSocketDef(),
},
"Text": {
"Value": sockets.BlenderTextSocketDef(),
},
"Image": {
"Value": sockets.BlenderImageSocketDef(),
},
"GeoNode Tree": {
"Value": sockets.BlenderGeoNodesSocketDef(),
},
}
output_socket_sets = input_socket_sets
####################
# - Callbacks
####################
@base.computes_output_socket(
"Value",
input_sockets={"Value"}
)
def compute_value(self, input_sockets) -> typ.Any:
return input_sockets["Value"]
####################
# - Blender Registration
####################
BL_REGISTER = [
BlenderConstantNode,
]
BL_NODES = {
ct.NodeType.BlenderConstant: (
ct.NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS
)
}

View File

@ -1,44 +0,0 @@
import bpy
import sympy as sp
from .... import contracts
from .... import sockets
from ... import base
class ComplexConstantNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.ComplexConstant
bl_label = "Complex Constant"
#bl_icon = constants.ICON_SIM_INPUT
input_sockets = {
"value": sockets.ComplexNumberSocketDef(
label="Complex",
),
}
output_sockets = {
"value": sockets.ComplexNumberSocketDef(
label="Complex",
),
}
####################
# - Callbacks
####################
@base.computes_output_socket("value", sp.Expr)
def compute_value(self: contracts.NodeTypeProtocol) -> sp.Expr:
return self.compute_input("value")
####################
# - Blender Registration
####################
BL_REGISTER = [
ComplexConstantNode,
]
BL_NODES = {
contracts.NodeType.ComplexConstant: (
contracts.NodeCategory.MAXWELL_SIM_INPUTS_CONSTANTS
)
}

View File

@ -0,0 +1,52 @@
import typing as typ
import bpy
import sympy as sp
from .... import contracts as ct
from .... import sockets
from ... import base
class NumberConstantNode(base.MaxwellSimNode):
node_type = ct.NodeType.NumberConstant
bl_label = "Numerical Constant"
input_socket_sets = {
"Integer": {
"Value": sockets.IntegerNumberSocketDef(),
},
"Rational": {
"Value": sockets.RationalNumberSocketDef(),
},
"Real": {
"Value": sockets.RealNumberSocketDef(),
},
"Complex": {
"Value": sockets.ComplexNumberSocketDef(),
},
}
output_socket_sets = input_socket_sets
####################
# - Callbacks
####################
@base.computes_output_socket(
"Value",
input_sockets={"Value"}
)
def compute_value(self, input_sockets) -> typ.Any:
return input_sockets["Value"]
####################
# - Blender Registration
####################
BL_REGISTER = [
NumberConstantNode,
]
BL_NODES = {
ct.NodeType.NumberConstant: (
ct.NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS
)
}

View File

@ -0,0 +1,75 @@
import bpy
import sympy as sp
from .... import contracts
from .... import sockets
from ... import base
class PhysicalConstantNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.PhysicalConstant
bl_label = "Physical Constant"
#bl_icon = constants.ICON_SIM_INPUT
input_sockets = {}
input_socket_sets = {
"time": {
"value": sockets.PhysicalTimeSocketDef(
label="Time",
),
},
"angle": {
"value": sockets.PhysicalAngleSocketDef(
label="Angle",
),
},
"length": {
"value": sockets.PhysicalLengthSocketDef(
label="Length",
),
},
"area": {
"value": sockets.PhysicalAreaSocketDef(
label="Area",
),
},
"volume": {
"value": sockets.PhysicalVolumeSocketDef(
label="Volume",
),
},
"point_3d": {
"value": sockets.PhysicalPoint3DSocketDef(
label="3D Point",
),
},
"size_3d": {
"value": sockets.PhysicalSize3DSocketDef(
label="3D Size",
),
},
## I got bored so maybe the rest later
}
output_sockets = {}
output_socket_sets = input_socket_sets
####################
# - Callbacks
####################
@base.computes_output_socket("value")
def compute_value(self: contracts.NodeTypeProtocol) -> sp.Expr:
return self.compute_input("value")
####################
# - Blender Registration
####################
BL_REGISTER = [
PhysicalConstantNode,
]
BL_NODES = {
contracts.NodeType.PhysicalConstant: (
contracts.NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS
)
}

View File

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

View File

@ -0,0 +1,43 @@
import bpy
import sympy as sp
from ... import contracts as ct
from ... import sockets
from .. import base
class PhysicalUnitSystemNode(base.MaxwellSimNode):
node_type = ct.NodeType.UnitSystem
bl_label = "Unit System"
input_sockets = {
"Unit System": sockets.PhysicalUnitSystemSocketDef(
show_by_default=True,
),
}
output_sockets = {
"Unit System": sockets.PhysicalUnitSystemSocketDef(),
}
####################
# - Callbacks
####################
@base.computes_output_socket(
"Unit System",
input_sockets = {"Unit System"},
)
def compute_value(self, input_sockets) -> dict:
return input_sockets["Unit System"]
####################
# - Blender Registration
####################
BL_REGISTER = [
PhysicalUnitSystemNode,
]
BL_NODES = {
ct.NodeType.UnitSystem: (
ct.NodeCategory.MAXWELLSIM_INPUTS
)
}

View File

@ -0,0 +1,165 @@
import bpy
import sympy as sp
import sympy.physics.units as spu
import scipy as sc
from .....utils import extra_sympy_units as spux
from ... import contracts as ct
from ... import sockets
from .. import base
VAC_SPEED_OF_LIGHT = (
sc.constants.speed_of_light
* spu.meter/spu.second
)
class WaveConstantNode(base.MaxwellSimNode):
node_type = ct.NodeType.WaveConstant
bl_label = "Wave Constant"
input_socket_sets = {
# Single
"Vacuum WL": {
"WL": sockets.PhysicalLengthSocketDef(
default_value=500*spu.nm,
default_unit=spu.nm,
),
},
"Frequency": {
"Freq": sockets.PhysicalFreqSocketDef(
default_value=500*spux.THz,
default_unit=spux.THz,
),
},
# Listy
"Vacuum WLs": {
"WLs": sockets.PhysicalLengthSocketDef(
is_list=True,
),
},
"Frequencies": {
"Freqs": sockets.PhysicalFreqSocketDef(
is_list=True,
),
},
}
####################
# - Callbacks
####################
@base.computes_output_socket(
"WL",
input_sockets={"WL", "Freq"},
)
def compute_vac_wl(self, input_sockets: dict) -> sp.Expr:
if (vac_wl := input_sockets["WL"]) is not None:
return vac_wl
elif (freq := input_sockets["Freq"]) is not None:
return spu.convert_to(
VAC_SPEED_OF_LIGHT / freq,
spu.meter,
)
raise RuntimeError("Vac WL and Freq are both None")
@base.computes_output_socket(
"Freq",
input_sockets={"WL", "Freq"},
)
def compute_freq(self, input_sockets: dict) -> sp.Expr:
if (vac_wl := input_sockets["WL"]) is not None:
return spu.convert_to(
VAC_SPEED_OF_LIGHT / vac_wl,
spu.hertz,
)
elif (freq := input_sockets["Freq"]) is not None:
return freq
raise RuntimeError("Vac WL and Freq are both None")
####################
# - Listy Callbacks
####################
@base.computes_output_socket(
"WLs",
input_sockets={"WLs", "Freqs"},
)
def compute_vac_wls(self, input_sockets: dict) -> sp.Expr:
if (vac_wls := input_sockets["WLs"]) is not None:
return vac_wls
elif (freqs := input_sockets["Freqs"]) is not None:
return [
spu.convert_to(
VAC_SPEED_OF_LIGHT / freq,
spu.meter,
)
for freq in freqs
][::-1]
raise RuntimeError("Vac WLs and Freqs are both None")
@base.computes_output_socket(
"Freqs",
input_sockets={"WLs", "Freqs"},
)
def compute_freqs(self, input_sockets: dict) -> sp.Expr:
if (vac_wls := input_sockets["WLs"]) is not None:
return [
spu.convert_to(
VAC_SPEED_OF_LIGHT / vac_wl,
spu.hertz,
)
for vac_wl in vac_wls
][::-1]
elif (freqs := input_sockets["Freqs"]) is not None:
return freqs
raise RuntimeError("Vac WLs and Freqs are both None")
####################
# - Callbacks
####################
@base.on_value_changed(
prop_name="active_socket_set",
props={"active_socket_set"}
)
def on_value_changed__active_socket_set(self, props: dict):
# Singular: Normal Output Sockets
if props["active_socket_set"] in {"Vacuum WL", "Frequency"}:
self.loose_output_sockets = {}
self.loose_output_sockets = {
"Freq": sockets.PhysicalFreqSocketDef(),
"WL": sockets.PhysicalLengthSocketDef(),
}
# Plural: Listy Output Sockets
elif props["active_socket_set"] in {"Vacuum WLs", "Frequencies"}:
self.loose_output_sockets = {}
self.loose_output_sockets = {
"Freqs": sockets.PhysicalFreqSocketDef(is_list=True),
"WLs": sockets.PhysicalLengthSocketDef(is_list=True),
}
else:
msg = f"Active socket set invalid for wave constant: {props['active_socket_set']}"
raise RuntimeError(msg)
@base.on_init()
def on_init(self):
self.on_value_changed__active_socket_set()
####################
# - Blender Registration
####################
BL_REGISTER = [
WaveConstantNode,
]
BL_NODES = {
ct.NodeType.WaveConstant: (
ct.NodeCategory.MAXWELLSIM_INPUTS
)
}

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,138 @@
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 import tdcloud
from .... import contracts as ct
from .... import sockets
from ... import base
CACHE = {}
####################
# - Node
####################
class Tidy3DWebImporterNode(base.MaxwellSimNode):
node_type = ct.NodeType.Tidy3DWebImporter
bl_label = "Tidy3DWebImporter"
input_sockets = {
"Cloud Task": sockets.Tidy3DCloudTaskSocketDef(
should_exist=True,
),
"Cache Path": sockets.FilePathSocketDef(
default_path=Path("loaded_simulation.hdf5")
)
}
####################
# - Output Methods
####################
@base.computes_output_socket(
"FDTD Sim Data",
input_sockets={"Cloud Task", "Cache Path"},
)
def compute_fdtd_sim_data(self, input_sockets: dict) -> str:
global CACHE
if not CACHE.get(self.instance_id):
CACHE[self.instance_id] = {"fdtd_sim_data": None}
if CACHE[self.instance_id]["fdtd_sim_data"] is not None:
return CACHE[self.instance_id]["fdtd_sim_data"]
if not (
(cloud_task := input_sockets["Cloud Task"]) is not None
and isinstance(cloud_task, tdcloud.CloudTask)
and cloud_task.status == "success"
):
msg ="Won't attempt getting SimData"
raise RuntimeError(msg)
# Load the Simulation
cache_path = input_sockets["Cache Path"]
if cache_path is None:
print("CACHE PATH IS NONE WHY")
return ## I guess?
if cache_path.is_file():
sim_data = td.SimulationData.from_file(str(cache_path))
else:
sim_data = td_web.api.webapi.load(
cloud_task.task_id,
path=str(cache_path),
)
CACHE[self.instance_id]["fdtd_sim_data"] = sim_data
return sim_data
@base.computes_output_socket(
"FDTD Sim",
input_sockets={"Cloud Task"},
)
def compute_fdtd_sim(self, input_sockets: dict) -> str:
if not isinstance(
cloud_task := input_sockets["Cloud Task"],
tdcloud.CloudTask
):
msg ="Input cloud task does not exist"
raise RuntimeError(msg)
# Load the Simulation
with tempfile.NamedTemporaryFile(delete=False) as f:
_path_tmp = Path(f.name)
_path_tmp.rename(f.name + ".json")
path_tmp = Path(f.name + ".json")
sim = td_web.api.webapi.load_simulation(
cloud_task.task_id,
path=str(path_tmp),
) ## TODO: Don't use td_web directly. Only through tdcloud
Path(path_tmp).unlink()
return sim
####################
# - Update
####################
@base.on_value_changed(
socket_name="Cloud Task",
input_sockets={"Cloud Task"}
)
def on_value_changed__cloud_task(self, input_sockets: dict):
if (
(cloud_task := input_sockets["Cloud Task"]) is not None
and isinstance(cloud_task, tdcloud.CloudTask)
and cloud_task.status == "success"
):
self.loose_output_sockets = {
"FDTD Sim Data": sockets.MaxwellFDTDSimDataSocketDef(),
"FDTD Sim": sockets.MaxwellFDTDSimSocketDef(),
}
return
self.loose_output_sockets = {}
@base.on_init()
def on_init(self):
self.on_value_changed__cloud_task()
####################
# - Blender Registration
####################
BL_REGISTER = [
Tidy3DWebImporterNode,
]
BL_NODES = {
ct.NodeType.Tidy3DWebImporter: (
ct.NodeCategory.MAXWELLSIM_INPUTS_IMPORTERS
)
}

View File

@ -0,0 +1,102 @@
from pathlib import Path
import tidy3d as td
import sympy as sp
import sympy.physics.units as spu
from .. import contracts as ct
from .. import sockets
from . import base
class KitchenSinkNode(base.MaxwellSimNode):
node_type = ct.NodeType.KitchenSink
bl_label = "Kitchen Sink"
#bl_icon = ...
####################
# - Sockets
####################
input_sockets = {
"Static Data": sockets.AnySocketDef(),
}
input_socket_sets = {
"Basic": {
"Any": sockets.AnySocketDef(),
"Bool": sockets.BoolSocketDef(),
"FilePath": sockets.FilePathSocketDef(),
"Text": sockets.TextSocketDef(),
},
"Number": {
"Integer": sockets.IntegerNumberSocketDef(),
"Rational": sockets.RationalNumberSocketDef(),
"Real": sockets.RealNumberSocketDef(),
"Complex": sockets.ComplexNumberSocketDef(),
},
"Vector": {
"Real 2D": sockets.Real2DVectorSocketDef(),
"Real 3D": sockets.Real3DVectorSocketDef(
default_value=sp.Matrix([0.0, 0.0, 0.0])
),
"Complex 2D": sockets.Complex2DVectorSocketDef(),
"Complex 3D": sockets.Complex3DVectorSocketDef(),
},
"Physical": {
"Time": sockets.PhysicalTimeSocketDef(),
#"physical_point_2d": sockets.PhysicalPoint2DSocketDef(),
"Angle": sockets.PhysicalAngleSocketDef(),
"Length": sockets.PhysicalLengthSocketDef(),
"Area": sockets.PhysicalAreaSocketDef(),
"Volume": sockets.PhysicalVolumeSocketDef(),
"Point 3D": sockets.PhysicalPoint3DSocketDef(),
##"physical_size_2d": sockets.PhysicalSize2DSocketDef(),
"Size 3D": sockets.PhysicalSize3DSocketDef(),
"Mass": sockets.PhysicalMassSocketDef(),
"Speed": sockets.PhysicalSpeedSocketDef(),
"Accel Scalar": sockets.PhysicalAccelScalarSocketDef(),
"Force Scalar": sockets.PhysicalForceScalarSocketDef(),
#"physical_accel_3dvector": sockets.PhysicalAccel3DVectorSocketDef(),
##"physical_force_3dvector": sockets.PhysicalForce3DVectorSocketDef(),
"Pol": sockets.PhysicalPolSocketDef(),
"Freq": sockets.PhysicalFreqSocketDef(),
},
"Blender": {
"Object": sockets.BlenderObjectSocketDef(),
"Collection": sockets.BlenderCollectionSocketDef(),
"Image": sockets.BlenderImageSocketDef(),
"GeoNodes": sockets.BlenderGeoNodesSocketDef(),
"Text": sockets.BlenderTextSocketDef(),
},
"Maxwell": {
"Source": sockets.MaxwellSourceSocketDef(),
"Temporal Shape": sockets.MaxwellTemporalShapeSocketDef(),
"Medium": sockets.MaxwellMediumSocketDef(),
"Medium Non-Linearity": sockets.MaxwellMediumNonLinearitySocketDef(),
"Structure": sockets.MaxwellStructureSocketDef(),
"Bound Box": sockets.MaxwellBoundBoxSocketDef(),
"Bound Face": sockets.MaxwellBoundFaceSocketDef(),
"Monitor": sockets.MaxwellMonitorSocketDef(),
"FDTD Sim": sockets.MaxwellFDTDSimSocketDef(),
"Sim Grid": sockets.MaxwellSimGridSocketDef(),
"Sim Grid Axis": sockets.MaxwellSimGridAxisSocketDef(),
},
}
output_sockets = {
"Static Data": sockets.AnySocketDef(),
}
output_socket_sets = input_socket_sets
####################
# - Blender Registration
####################
BL_REGISTER = [
KitchenSinkNode,
]
BL_NODES = {
ct.NodeType.KitchenSink: (
ct.NodeCategory.MAXWELLSIM_INPUTS
)
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,80 @@
import tidy3d as td
import sympy as sp
import sympy.physics.units as spu
from ... import contracts
from ... import sockets
from .. import base
class DrudeLorentzMediumNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.DrudeLorentzMedium
bl_label = "Drude-Lorentz Medium"
#bl_icon = ...
####################
# - Sockets
####################
input_sockets = {
"eps_inf": sockets.RealNumberSocketDef(
label=f"εr_∞",
),
} | {
f"del_eps{i}": sockets.RealNumberSocketDef(
label=f"Δεr_{i}",
)
for i in [1, 2, 3]
} | {
f"f{i}": sockets.PhysicalFreqSocketDef(
label=f"f_{i}",
)
for i in [1, 2, 3]
} | {
f"delta{i}": sockets.PhysicalFreqSocketDef(
label=f"δ_{i}",
)
for i in [1, 2, 3]
}
output_sockets = {
"medium": sockets.MaxwellMediumSocketDef(
label="Medium"
),
}
####################
# - Output Socket Computation
####################
@base.computes_output_socket("medium")
def compute_medium(self: contracts.NodeTypeProtocol) -> td.Sellmeier:
## Retrieval
return td.Lorentz(
eps_inf=self.compute_input(f"eps_inf"),
coeffs = [
(
self.compute_input(f"del_eps{i}"),
spu.convert_to(
self.compute_input(f"f{i}"),
spu.hertz,
) / spu.hertz,
spu.convert_to(
self.compute_input(f"delta{i}"),
spu.hertz,
) / spu.hertz,
)
for i in [1, 2, 3]
]
)
####################
# - Blender Registration
####################
BL_REGISTER = [
DrudeLorentzMediumNode,
]
BL_NODES = {
contracts.NodeType.DrudeLorentzMedium: (
contracts.NodeCategory.MAXWELLSIM_MEDIUMS
)
}

View File

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

View File

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

View File

@ -0,0 +1,162 @@
import typing as typ
import functools
import bpy
import tidy3d as td
import sympy as sp
import sympy.physics.units as spu
import numpy as np
import scipy as sc
from .....utils import extra_sympy_units as spuex
from ... import contracts as ct
from ... import sockets
from ... import managed_objs
from .. import base
VAC_SPEED_OF_LIGHT = (
sc.constants.speed_of_light
* spu.meter/spu.second
)
class LibraryMediumNode(base.MaxwellSimNode):
node_type = ct.NodeType.LibraryMedium
bl_label = "Library Medium"
####################
# - Sockets
####################
input_sockets = {}
output_sockets = {
"Medium": sockets.MaxwellMediumSocketDef(),
}
managed_obj_defs = {
"nk_plot": ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLImage(name),
name_prefix="",
)
}
####################
# - Properties
####################
material: bpy.props.EnumProperty(
name="",
description="",
#icon="NODE_MATERIAL",
items=[
(
mat_key,
td.material_library[mat_key].name,
", ".join([
ref.journal
for ref in td.material_library[mat_key].variants[
td.material_library[mat_key].default
].reference
])
)
for mat_key in td.material_library
if mat_key != "graphene" ## For some reason, it's unique...
],
default="Au",
update=(lambda self, context: self.sync_prop("material", context)),
)
@property
def freq_range_str(self) -> tuple[sp.Expr, sp.Expr]:
## TODO: Cache (node instances don't seem able to keep data outside of properties, not even cached_property)
mat = td.material_library[self.material]
freq_range = [
spu.convert_to(
val * spu.hertz,
spuex.terahertz,
) / spuex.terahertz
for val in mat.medium.frequency_range
]
return sp.pretty(
[freq_range[0].n(4), freq_range[1].n(4)],
use_unicode=True
)
@property
def nm_range_str(self) -> str:
## TODO: Cache (node instances don't seem able to keep data outside of properties, not even cached_property)
mat = td.material_library[self.material]
nm_range = [
spu.convert_to(
VAC_SPEED_OF_LIGHT / (val * spu.hertz),
spu.nanometer,
) / spu.nanometer
for val in reversed(mat.medium.frequency_range)
]
return sp.pretty(
[nm_range[0].n(4), nm_range[1].n(4)],
use_unicode=True
)
####################
# - UI
####################
def draw_props(self, context, layout):
layout.prop(self, "material", text="")
def draw_info(self, context, col):
# UI Drawing
split = col.split(factor=0.23, align=True)
_col = split.column(align=True)
_col.alignment = "LEFT"
_col.label(text="nm")
_col.label(text="THz")
_col = split.column(align=True)
_col.alignment = "RIGHT"
_col.label(text=self.nm_range_str)
_col.label(text=self.freq_range_str)
####################
# - Output Sockets
####################
@base.computes_output_socket("Medium")
def compute_vac_wl(self) -> sp.Expr:
return td.material_library[self.material].medium
####################
# - Event Callbacks
####################
@base.on_show_plot(
managed_objs={"nk_plot"},
props={"material"},
stop_propagation=True, ## Plot only the first plottable node
)
def on_show_plot(
self,
managed_objs: dict[str, ct.schemas.ManagedObj],
props: dict[str, typ.Any],
):
medium = td.material_library[props["material"]].medium
freq_range = [
spu.convert_to(
val * spu.hertz,
spuex.terahertz,
) / spu.hertz
for val in medium.frequency_range
]
managed_objs["nk_plot"].mpl_plot_to_image(
lambda ax: medium.plot(medium.frequency_range, ax=ax),
bl_select=True,
)
####################
# - Blender Registration
####################
BL_REGISTER = [
LibraryMediumNode,
]
BL_NODES = {
ct.NodeType.LibraryMedium: (
ct.NodeCategory.MAXWELLSIM_MEDIUMS
)
}

View File

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

View File

@ -1,84 +0,0 @@
import bpy
import gpu
import gpu_extras
import sympy.physics.units as spu
from .... import types, constants
from ... import node_base
class TripleSellmeierMediumNode(node_base.MaxwellSimTreeNode):
bl_idname = types.NodeType.TripleSellmeierMedium.value
bl_label = "Triple Sellmeier Medium"
bl_icon = constants.ICON_SIM_MEDIUM
input_sockets = {
"B1": (types.SocketType.RealNumber, "B1"),
"B2": (types.SocketType.RealNumber, "B2"),
"B3": (types.SocketType.RealNumber, "B3"),
"C1": (types.SocketType.DimenArea, "C1"),
"C2": (types.SocketType.DimenArea, "C2"),
"C3": (types.SocketType.DimenArea, "C3"),
}
output_sockets = {
"medium": (types.SocketType.MaxwellMedium, "Medium")
}
input_unit_defaults = {
"B1": None,
"B2": None,
"B3": None,
"C1": spu.um**2,
"C2": spu.um**2,
"C3": spu.um**2,
}
socket_presets = {
"_description": [
('BK7', "BK7 Glass", "Borosilicate crown glass (known as BK7)"),
('FUSED_SILICA', "Fused Silica", "Fused silica aka. SiO2"),
],
"_default": "BK7",
"_values": {
"BK7": {
"B1": 1.03961212,
"B2": 0.231792344,
"B3": 1.01046945,
"C1": 6.00069867e-3 * spu.um**2,
"C2": 2.00179144e-2 * spu.um**2,
"C3": 103.560653 * spu.um**2,
},
"FUSED_SILICA": {
"B1": 0.696166300,
"B2": 0.407942600,
"B3": 0.897479400,
"C1": 4.67914826e-3 * spu.um**2,
"C2": 1.35120631e-2 * spu.um**2,
"C3": 97.9340025 * spu.um**2,
},
}
}
####################
# - Properties
####################
def draw_buttons(self, context, layout):
layout.prop(self, 'preset', text="")
####################
# - Callbacks
####################
@node_base.output_socket_cb("medium")
def compute_medium(self):
pass
####################
# - Blender Registration
####################
BL_REGISTER = [
TripleSellmeierMediumNode,
]
BL_NODES = {
types.NodeType.TripleSellmeierMedium: (
types.NodeCategory.MAXWELL_SIM_MEDIUMS_LINEARMEDIUMS
)
}

View File

@ -0,0 +1,17 @@
from . import add_non_linearity
from . import chi_3_susceptibility_non_linearity
from . import kerr_non_linearity
from . import two_photon_absorption_non_linearity
BL_REGISTER = [
*add_non_linearity.BL_REGISTER,
*chi_3_susceptibility_non_linearity.BL_REGISTER,
*kerr_non_linearity.BL_REGISTER,
*two_photon_absorption_non_linearity.BL_REGISTER,
]
BL_NODES = {
**add_non_linearity.BL_NODES,
**chi_3_susceptibility_non_linearity.BL_NODES,
**kerr_non_linearity.BL_NODES,
**two_photon_absorption_non_linearity.BL_NODES,
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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