refactor: Massive architectural changes.
See README.md for new, semi-finalized TODO list.blender-plugin-mvp
parent
d95210dc34
commit
1ebb57cff7
572
code/README.md
572
code/README.md
|
@ -1,239 +1,397 @@
|
|||
# Node Design
|
||||
Now that we can do all the cool things ex. presets and such, it's time to think more design.
|
||||
# Nodes
|
||||
## Inputs
|
||||
[x] Wave Constant
|
||||
- [ ] Implement export of frequency / wavelength ranges.
|
||||
[ ] Unit System
|
||||
- [ ] Implement presets, including "Tidy3D" and "Blender", shown in the label row.
|
||||
|
||||
## 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.
|
||||
[ ] Constants / Blender Constant
|
||||
[ ] Constants / Number Constant
|
||||
[ ] Constants / Physical Constant
|
||||
- [ ] Pol: Elliptical plot viz
|
||||
- [ ] Pol: Poincare sphere viz
|
||||
[ ] Constants / Scientific Constant
|
||||
|
||||
**NOTE**: Throughout, all nodes that output floats/vectors should have a sympy dimension. Any node that takes floats/vectors should either have a pre-defined unit (exposed as a string in the node UI), or a selectable unit (ex. for value inputs).
|
||||
[ ] Web / Tidy3D Web Importer
|
||||
|
||||
- Inputs
|
||||
- Scene
|
||||
- Time
|
||||
- Unit System
|
||||
[ ] 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.
|
||||
|
||||
- Parameters: Sympy variables.
|
||||
- *type* Parameter
|
||||
- Constants: Typed numbers.
|
||||
- Scientific Constant
|
||||
## Outputs
|
||||
[ ] Viewer
|
||||
- [ ] A setting that live-previews just a value.
|
||||
- [ ] Pop-up multiline string print as alternative to console print.
|
||||
- [ ] Toggleable auto-plot, auto-3D-preview, auto-value-view, (?)auto-text-view.
|
||||
|
||||
- *type* Constant
|
||||
- Lists
|
||||
- *type* List Element
|
||||
[ ] 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.
|
||||
|
||||
- File Data: Data from a file.
|
||||
- Outputs
|
||||
- Viewers
|
||||
- Value Viewer: Live-monitoring.
|
||||
- Console Viewer: w/Button to Print Types
|
||||
- Exporters
|
||||
- JSON File Exporter: Compatible with any socket implementing `.as_json()`.
|
||||
- Plotters
|
||||
- *various kinds of plotting? To Blender datablocks primarily, maybe*.
|
||||
## Viz
|
||||
[ ] Monitor Data Viz
|
||||
- [ ] Implement dropdown to choose which monitor in the SimulationData should be visualized (based on which are available in the SimulationData), and implement visualization based on every kind of monitor-adjascent output data type (<https://docs.flexcompute.com/projects/tidy3d/en/latest/api/output_data.html>)
|
||||
|
||||
- Sources
|
||||
- **ALL**: Accept a Temporal Shape
|
||||
## Sources
|
||||
[ ] Temporal Shapes / Gaussian Pulse Temporal Shape
|
||||
[ ] Temporal Shapes / Continuous Wave Temporal Shape
|
||||
[ ] Temporal Shapes / Symbolic Temporal Shape
|
||||
- [ ] Specify a Sympy function to generate appropriate array based on
|
||||
[ ] Temporal Shapes / Array Temporal Shape
|
||||
|
||||
- Temporal Shapes
|
||||
- Gaussian Pulse Temporal Shape
|
||||
- Continuous Wave Temporal Shape
|
||||
- Array Temporal Shape
|
||||
[ ] Point Dipole Source
|
||||
[ ] Plane Wave Source
|
||||
- [ ] Implement an oriented vector input with 3D preview.
|
||||
[ ] Uniform Current Source
|
||||
[ ] TFSF Source
|
||||
|
||||
- Point Dipole Source
|
||||
- Uniform Current Source
|
||||
- Plane Wave Source
|
||||
- Mode Source
|
||||
- Gaussian Beam Source
|
||||
- Astigmatic Gaussian Beam Source
|
||||
- TFSF Source
|
||||
[ ] Gaussian Beam Source
|
||||
[ ] Astigmatic Gaussian Beam Source
|
||||
|
||||
- E/H Equivalence Array Source
|
||||
- E/H Array 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
|
||||
[ ] GeoNodes Structure
|
||||
- [ ] 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
|
||||
[ ] Primitive Structures / Box Structure
|
||||
[ ] Primitive Structures / Sphere
|
||||
[ ] Primitive Structures / Cylinder
|
||||
[ ] Primitive Structures / Ring
|
||||
[ ] Primitive Structures / Capsule
|
||||
[ ] Primitive Structures / Cone
|
||||
|
||||
## Monitors
|
||||
- **ALL**: "Steady-State" / "Time Domain" (only if relevant).
|
||||
|
||||
[ ] E/H Field Monitor
|
||||
- [ ] Monitor Domain as dropdown with Frequency or Time
|
||||
- [ ] Axis-aligned planar 2D (pixel) and coord-aligned box 3D (voxel).
|
||||
[ ] Field Power Flux Monitor
|
||||
- [ ] Monitor Domain as dropdown with Frequency or Time
|
||||
- [ ] Axis-aligned planar 2D (pixel) and coord-aligned box 3D (voxel).
|
||||
[ ] \epsilon Tensor Monitor
|
||||
- [ ] Axis-aligned planar 2D (pixel) and coord-aligned box 3D (voxel).
|
||||
[ ] Diffraction Monitor
|
||||
- [ ] Axis-aligned planar 2D (pixel)
|
||||
|
||||
[ ] Projected E/H Field Monitor / Cartesian Projected E/H Field Monitor
|
||||
- [ ] Use to implement the metalens: <https://docs.flexcompute.com/projects/tidy3d/en/latest/notebooks/Metalens.html>
|
||||
[ ] Projected E/H Field Monitor / Angle Projected E/H Field Monitor
|
||||
[ ] Projected E/H Field Monitor / K-Space Projected E/H Field Monitor
|
||||
|
||||
- **TODO**: "Modal" solver monitoring (seems to be some kind of spatial+frequency feature, which an EM field can be decomposed into using a specially configured solver, which can be used to look for very particular kinds of effects by constraining investigations of a solver result to filter out everything that isn't these particular modes aka. features. Kind of a fourier-based redimensionalization, almost).
|
||||
|
||||
## Simulations
|
||||
[-] FDTDSim
|
||||
|
||||
[-] Sim Domain
|
||||
- [ ] By-Medium batching of Structures when building the td.Simulation object, which can have significant performance implications.
|
||||
|
||||
[-] Boundary Conds
|
||||
- [ ] Rename from Bounds / BoundBox
|
||||
[ ] Boundary Cond / PML Bound Face
|
||||
- [ ] Implement dropdown for "Normal" and "Stable"
|
||||
[ ] Boundary Cond / PEC Bound Face
|
||||
[ ] Boundary Cond / PMC Bound Face
|
||||
[ ] Boundary Cond / Bloch Bound Face
|
||||
[ ] Boundary Cond / Periodic Bound Face
|
||||
[ ] Boundary Cond / Absorbing Bound Face
|
||||
|
||||
[ ] Sim Grid
|
||||
[ ] Sim Grid Axes / Auto Sim Grid Axis
|
||||
[ ] Sim Grid Axes / Manual Sim Grid Axis
|
||||
[ ] Sim Grid Axes / Uniform Sim Grid Axis
|
||||
[ ] Sim Grid Axes / Array Sim Grid Axis
|
||||
|
||||
## Converters
|
||||
[ ] Math
|
||||
- [ ] Implement common operations w/secondary choice of socket type based on a custom internal data structure
|
||||
- [ ] Implement angfreq/frequency/vacwl conversion.
|
||||
[ ] Separate
|
||||
[ ] Combine
|
||||
- [ ] Implement concatenation of sim-critical socket types into their multi-type
|
||||
|
||||
|
||||
|
||||
- Mediums
|
||||
- **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
|
||||
|
||||
- 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
|
||||
- [ ] Tunable Chiral Metasurface <https://docs.flexcompute.com/projects/tidy3d/en/latest/notebooks/TunableChiralMetasurface.html>
|
||||
|
||||
|
||||
|
||||
- Simulations
|
||||
- Sim Grid
|
||||
- Sim Grid Axis
|
||||
- Automatic Sim Grid Axis
|
||||
- Manual Sim Grid Axis
|
||||
- Uniform Sim Grid Axis
|
||||
- Array Sim Grid Axis
|
||||
# Sockets
|
||||
## Basic
|
||||
[ ] Any
|
||||
[ ] Bool
|
||||
[ ] String
|
||||
- [ ] Rename from "Text"
|
||||
[ ] File Path
|
||||
|
||||
- FDTD Sim
|
||||
## Blender
|
||||
[ ] Object
|
||||
[ ] Collection
|
||||
|
||||
[ ] Image
|
||||
|
||||
[ ] GeoNodes
|
||||
[ ] Text
|
||||
|
||||
## Maxwell
|
||||
[ ] Bound Conds
|
||||
[ ] Bound Cond
|
||||
|
||||
[ ] Medium
|
||||
[ ] Medium Non-Linearity
|
||||
|
||||
[ ] Source
|
||||
[ ] Temporal Shape
|
||||
|
||||
[ ] Structure
|
||||
[ ] Monitor
|
||||
|
||||
[ ] FDTD Sim
|
||||
[ ] Sim Domain
|
||||
- [?] Toggleable option to sync the simulation time duration to the scene end time (how to handle FPS vs time-step? Should we adjust the FPS such that there is one time step per frame, while keeping the definition of "second" aligned to a unit system?)
|
||||
[ ] Sim Grid
|
||||
[ ] Sim Grid Axis
|
||||
|
||||
[ ] Simulation Data
|
||||
|
||||
## Tidy3D
|
||||
[ ] Cloud Task
|
||||
|
||||
## Number
|
||||
[ ] Integer
|
||||
[ ] Rational
|
||||
[ ] Real
|
||||
[ ] Complex
|
||||
|
||||
## Physical
|
||||
[ ] Unit System
|
||||
- [ ] Implement more comprehensible UI; honestly, probably with the new panels (<https://developer.blender.org/docs/release_notes/4.1/python_api/>)
|
||||
|
||||
[ ] Time
|
||||
|
||||
[ ] Angle
|
||||
[ ] Solid Angle (steradian)
|
||||
|
||||
[ ] Frequency (hertz)
|
||||
[ ] Angular Frequency (`rad*hertz`)
|
||||
### Cartesian
|
||||
[ ] Length
|
||||
[ ] Area
|
||||
[ ] Volume
|
||||
|
||||
[ ] Point 1D
|
||||
[ ] Point 2D
|
||||
[ ] Point 3D
|
||||
|
||||
[ ] Size 2D
|
||||
[ ] Size 3D
|
||||
### Mechanical
|
||||
[ ] Mass
|
||||
|
||||
[ ] Speed
|
||||
[ ] Velocity 3D
|
||||
[ ] Acceleration Scalar
|
||||
[ ] Acceleration 3D
|
||||
[ ] Force Scalar
|
||||
[ ] Force 3D
|
||||
[ ] Pressure
|
||||
### Statistical
|
||||
[ ] Energy (joule)
|
||||
[ ] Power (watt)
|
||||
[ ] Temperature
|
||||
### Electrodynamical
|
||||
[ ] Current (ampere)
|
||||
[ ] Current Density 3D
|
||||
|
||||
[ ] Charge (coulomb)
|
||||
[ ] Voltage (volts)
|
||||
[ ] Capacitance (farad)
|
||||
[ ] Resistance (ohm)
|
||||
[ ] Electric Conductance (siemens)
|
||||
|
||||
[ ] Magnetic Flux (weber)
|
||||
[ ] Magnetic Flux Density (tesla)
|
||||
[ ] Inductance (henry)
|
||||
|
||||
[ ] Electric Field 3D (`volt*meter`)
|
||||
[ ] Magnetic Field 3D (tesla)
|
||||
### Luminal
|
||||
[ ] Luminous Intensity (candela)
|
||||
[ ] Luminous Flux (lumen)
|
||||
[ ] Illuminance (lux)
|
||||
### Optical
|
||||
[ ] Jones Polarization
|
||||
[ ] Polarization
|
||||
|
||||
|
||||
|
||||
- Utilities
|
||||
- Math: Contains a dropdown for operation.
|
||||
- *type* Math: **Be careful about units :)**
|
||||
- Operations
|
||||
- List Operation
|
||||
|
||||
## Sockets
|
||||
- basic
|
||||
- Any
|
||||
- FilePath
|
||||
- Text
|
||||
- number
|
||||
- IntegerNumber
|
||||
- RationalNumber
|
||||
|
||||
- RealNumber
|
||||
- ComplexNumber
|
||||
- RealNumberField
|
||||
- ComplexNumberField
|
||||
|
||||
- vector
|
||||
- Real2DVector
|
||||
- Complex2DVector
|
||||
- Real2DVectorField
|
||||
- Complex2DVectorField
|
||||
|
||||
- Real3DVector
|
||||
- Complex3DVector
|
||||
- Real3DVectorField
|
||||
- Complex3DVectorField
|
||||
- physics
|
||||
- PhysicalTime
|
||||
|
||||
- PhysicalAngle
|
||||
|
||||
- PhysicalLength
|
||||
- PhysicalArea
|
||||
- PhysicalVolume
|
||||
|
||||
- PhysicalMass
|
||||
- PhysicalLengthDensity
|
||||
- PhysicalAreaDensity
|
||||
- PhysicalVolumeDensity
|
||||
|
||||
- PhysicalSpeed
|
||||
- PhysicalAcceleration
|
||||
- PhysicalForce
|
||||
|
||||
- PhysicalPolarization
|
||||
|
||||
- PhysicalFrequency
|
||||
- PhysicalSpectralDistribution
|
||||
- blender
|
||||
- BlenderObject
|
||||
- BlenderCollection
|
||||
|
||||
- BlenderGeoNodes
|
||||
- BlenderImage
|
||||
- maxwell
|
||||
- MaxwellMedium
|
||||
- MaxwellMediumNonLinearity
|
||||
|
||||
- MaxwellStructure
|
||||
|
||||
- MaxwellBoundBox
|
||||
- MaxwellBoundFace
|
||||
|
||||
- MaxwellMonitor
|
||||
|
||||
- MaxwellSimGrid
|
||||
|
||||
- FDTDSim
|
||||
# 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
|
||||
[ ] Finish the contract code converting from Blender sockets to our sockets based on dimensionality and the property description.
|
||||
[ ] Refactor the node category code; it's ugly as all fuck.
|
||||
[?] 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
|
||||
[ ] Implement modifier support on the managed BL object, with special attention paid to the needs of the GeoNodes socket.
|
||||
- [ ] Implement preview toggling too, ex. using the relevant node tree collections
|
||||
- Remember, the managed object is "dumb". It's the node's responsibility to react to any relevant `on_value_change`, and forward all state needed by the modifier to the managed obj. It's only the managed obj's responsibility to not update any modifier value that wouldn't change anything.
|
||||
[ ] Implement loading the xarray-defined voxels into OpenVDB, saving it, and loading it as a managed BL object with the volume setting.
|
||||
[ ] Implement basic jax-driven volume voxel processing, especially cube based slicing.
|
||||
[ ] Implement jax-driven linear interpolation of volume voxels to an image texture, whose pixels are sized according to the dimensions of another managed plane object (perhaps a uniquely described Managed BL object itself).
|
||||
|
||||
### Notes
|
||||
**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.
|
||||
## 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.
|
||||
|
||||
**NOTE**: Some symmetries can be greatly important for performance. <https://docs.flexcompute.com/projects/tidy3d/en/latest/notebooks/Symmetry.html>
|
||||
## Socket Base Class
|
||||
[ ] A feature `use_array` which allows a socket to declare that it can be both a single value and array-like (possibly constrained to a given shape). This should also allow the SocketDef to request that the input socket be initialised as a multi-input socket, once Blender updates to support those.
|
||||
- [ ] Implement a shape-selector, with a dropdown for dimensionality and an appropriate `IntegerVectorProperty` for each kind of shape (supporting also straight-up inf), which is declared to the node that supports array-likeness so it can decide how exactly to expose properties in the array-like context of things.
|
||||
[ ] Make `to_socket`s no-consent to new links from `from_socket`s of differing type (we'll see if this controls the typing story enough for now, and how much we'll need capabilities in the long run)
|
||||
- [?] Alternatively, reject non matching link types, and red-mark non matching capabilities?
|
||||
|
||||
## Many Nodes
|
||||
[ ] Implement LazyValue stuff, including LazyParamValue on a new class of constant-like input nodes that really just emit ex. sympy variables.
|
||||
[?] Require a Unit System for nodes that construct Tidy3D objects
|
||||
[ ] Medium Features
|
||||
- [ ] Accept spatial field. Else, spatial uniformity.
|
||||
- [ ] Accept non-linearity. Else, linear.
|
||||
- [ ] Accept space-time modulation. Else, static.
|
||||
[ ] Modal Features
|
||||
- [ ] ModeSpec, for use by ModeSource, ModeMonitor, ModeSolverMonitor. Data includes ModeSolverData, ModeData, ScalarModeFieldDataArray, ModeAmpsDataArray, ModeIndexDataArray, ModeSolver.
|
||||
|
||||
## Development Tooling
|
||||
[ ] Implement `rye` support
|
||||
[ ] Setup neovim to be an ideal editor
|
||||
|
||||
## Version Churn
|
||||
[ ] Implement real StrEnum sockets, since they appear in py3.11
|
||||
[ ] Think about implementing new panels where appropriate (<https://developer.blender.org/docs/release_notes/4.1/python_api/>)
|
||||
[ ] Think about using the new bl4.1 file handler API to enable drag and drop creation of appropriate nodes (for importing files without hassle).
|
||||
[ ] Keep an eye on our manual `__annotations__` hacking; python 3.13 is apparently fucking with it.
|
||||
[ ] Plan for multi-input sockets <https://projects.blender.org/blender/blender/commit/14106150797a6ce35e006ffde18e78ea7ae67598> (for now, just use the "Combine" node and have seperate socket types for both).
|
||||
[ ] Keep an eye out for volume geonodes in 4.2 (July 16, 2024), which will better allow for more complicated volume processing (we might still want/need the jax based stuff after, but let's keep it minimal just in case)
|
||||
|
||||
## Packaging
|
||||
[ ] Allow specifying custom dir for keeping pip dependencies, so we can unify prod and dev (currently we hard-code a dev dependency path).
|
||||
[ ] Refactor top-level `__init__.py` to check dependencies first. If not everything is available, it should only register a minimal addon; specifically, a message telling the user that the addon requires additional dependencies (list which), and the button to install them. When the installation is done, re-check deps and register the rest of the addon.
|
||||
[ ] Use a Modal and multiline-text-like construction to print `pip install` as we install dependencies, so that the user has an idea that something is happening.
|
||||
[ ] Test on Windows
|
||||
|
||||
## Node Tree Cache Semantics
|
||||
|
||||
## Projects / Plugins
|
||||
### Field Data
|
||||
[ ] Directly dealing with field data, instead of having field manipulations be baked into viz node(s).
|
||||
[ ] Yee Cell Data as Attributes on By-Cell Point Cloud w/GeoNodes Integrations
|
||||
- In effect, when we have xarray data defined based on Yee Cells ex. Poynting vector coordinates, let's import this to Blender as a simple point cloud centered at each cell and grant each an attribute corresponding to the data.
|
||||
- What we can then do is use vanilla GeoNodes to ex. read the vector attribute, and draw small arrow meshes (maybe resampled which auto-interpolates the field values) from each point, thus effectively visualizing . vector fields and many other fun things.
|
||||
- Of course, this is no good for volume cell data - but we can just overlay the raw volume cell data as we please. We can also, if we're sneaky, deal with our volume data as points as far as we can, and then finally do a "points to volume" type deal to make it sufficiently "fluffy/cloudy".
|
||||
- I wonder if we could use the Attribute node in the shader editor to project interpolated values from points, onto a ex. plane mesh, in a way that would also be visualizable in the viewport.
|
||||
|
||||
### Tidy3D Features
|
||||
[ ] Symmetry for Performance
|
||||
- [ ] Implement <https://docs.flexcompute.com/projects/tidy3d/en/latest/notebooks/Symmetry.html>
|
||||
[ ] Dispersive Model Fitting
|
||||
[ ] Scattering Matrix Calculator
|
||||
[ ] Resonance Finder
|
||||
[ ] Adjoint Optimization
|
||||
[ ] Design Space Exploration / Parameterization
|
||||
|
||||
### Preview Semantics
|
||||
[ ] Custom gizmos attached to preview toggles!
|
||||
- There is a WIP for GN-driven gizmos: <https://projects.blender.org/blender/blender/pulls/112677>
|
||||
- Probably best to wait for that, then just add gizmos to existing driven GN trees, as opposed to unholy OGL spaghetti.
|
||||
[ ] Node-ManagedObj Selection binding
|
||||
- BL to Node:
|
||||
- Trigger: The post-depsgraph handler seems appropriate.
|
||||
- Input: Read the object location (origin), using a unit system.
|
||||
- Output: Write the input socket value.
|
||||
- Condition: Input socket is unlinked. (If it's linked, then lock the object's position. Use sync_link_added() for that)
|
||||
- Node to BL:
|
||||
- Trigger: "Report" action on an input socket that the managed object declares reliance on.
|
||||
- Input: The input socket value (linked or unlinked)
|
||||
- Output: The object location (origin), using a unit system.
|
||||
|
||||
### Parametric Geometry UX
|
||||
[ ] Consider allowing a mesh attribute (set in ex. geometry node) to specify the name of a medium.
|
||||
- This allows assembling complex multi-medium structures in one geonodes tree.
|
||||
- This should result in the spawning of several Medium input sockets in the GeoNodes structure node, named as the attributes are.
|
||||
- The GeoNodes structure node should then output as array-like TriMeshes, for which mediums are correctly defined.
|
||||
|
||||
### Alternative Engines
|
||||
[ ] MEEP integration (<https://meep.readthedocs.io/en/latest/>)
|
||||
- The main boost would be if we could setup a MEEP simulation entirely from a td.Simulation object.
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,7 +72,7 @@ def mk_node_categories(
|
|||
# - Blender Registration
|
||||
####################
|
||||
BL_NODE_CATEGORIES = mk_node_categories(
|
||||
contracts.NodeCategory.get_tree()["MAXWELLSIM"],
|
||||
ct.NodeCategory.get_tree()["MAXWELLSIM"],
|
||||
syllable_prefix = ["MAXWELLSIM"],
|
||||
)
|
||||
## TODO: refactor, this has a big code smell
|
||||
|
@ -82,7 +82,7 @@ BL_REGISTER = [
|
|||
|
||||
## 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
|
||||
|
|
|
@ -1,922 +0,0 @@
|
|||
import typing as typ
|
||||
import typing_extensions as pytypes_ext
|
||||
import enum
|
||||
|
||||
import sympy as sp
|
||||
|
||||
sp.printing.str.StrPrinter._default_settings['abbrev'] = True
|
||||
## When we str() a unit expression, use abbrevied units.
|
||||
|
||||
import sympy.physics.units as spu
|
||||
import pydantic as pyd
|
||||
import bpy
|
||||
|
||||
from ...utils.blender_type_enum import (
|
||||
BlenderTypeEnum, append_cls_name_to_values, wrap_values_in_MT
|
||||
)
|
||||
from ...utils import extra_sympy_units as spuex
|
||||
|
||||
####################
|
||||
# - String Types
|
||||
####################
|
||||
BlenderColorRGB = tuple[float, float, float, float]
|
||||
BlenderID = pytypes_ext.Annotated[str, pyd.StringConstraints(
|
||||
pattern=r'^[A-Z_]+$',
|
||||
)]
|
||||
|
||||
# Socket ID
|
||||
SocketName = pytypes_ext.Annotated[str, pyd.StringConstraints(
|
||||
pattern=r'^[a-zA-Z0-9_]+$',
|
||||
)]
|
||||
BLSocketName = pytypes_ext.Annotated[str, pyd.StringConstraints(
|
||||
pattern=r'^[a-zA-Z0-9_]+$',
|
||||
)]
|
||||
|
||||
# Socket ID
|
||||
PresetID = pytypes_ext.Annotated[str, pyd.StringConstraints(
|
||||
pattern=r'^[A-Z_]+$',
|
||||
)]
|
||||
|
||||
####################
|
||||
# - Sympy Expression Typing
|
||||
####################
|
||||
ALL_UNIT_SYMBOLS = {
|
||||
unit
|
||||
for unit in spu.__dict__.values()
|
||||
if isinstance(unit, spu.Quantity)
|
||||
}
|
||||
def has_units(expr: sp.Expr):
|
||||
return any(
|
||||
symbol in ALL_UNIT_SYMBOLS
|
||||
for symbol in expr.atoms(sp.Symbol)
|
||||
)
|
||||
def is_exactly_expressed_as_unit(expr: sp.Expr, unit) -> bool:
|
||||
#try:
|
||||
converted_expr = expr / unit
|
||||
|
||||
return (
|
||||
converted_expr.is_number
|
||||
and not converted_expr.has(spu.Quantity)
|
||||
)
|
||||
|
||||
####################
|
||||
# - Icon Types
|
||||
####################
|
||||
class Icon(BlenderTypeEnum):
|
||||
MaxwellSimTree = "MOD_SIMPLEDEFORM"
|
||||
|
||||
####################
|
||||
# - Tree Types
|
||||
####################
|
||||
@append_cls_name_to_values
|
||||
class TreeType(BlenderTypeEnum):
|
||||
MaxwellSim = enum.auto()
|
||||
|
||||
####################
|
||||
# - Socket Types
|
||||
####################
|
||||
@append_cls_name_to_values
|
||||
class SocketType(BlenderTypeEnum):
|
||||
# Base
|
||||
Any = enum.auto()
|
||||
Bool = enum.auto()
|
||||
Text = enum.auto()
|
||||
FilePath = enum.auto()
|
||||
|
||||
# Number
|
||||
IntegerNumber = enum.auto()
|
||||
RationalNumber = enum.auto()
|
||||
RealNumber = enum.auto()
|
||||
ComplexNumber = enum.auto()
|
||||
|
||||
# Vector
|
||||
Real2DVector = enum.auto()
|
||||
Complex2DVector = enum.auto()
|
||||
|
||||
Real3DVector = enum.auto()
|
||||
Complex3DVector = enum.auto()
|
||||
|
||||
# Physical
|
||||
PhysicalUnitSystem = enum.auto()
|
||||
PhysicalTime = enum.auto()
|
||||
|
||||
PhysicalAngle = enum.auto()
|
||||
|
||||
PhysicalLength = enum.auto()
|
||||
PhysicalArea = enum.auto()
|
||||
PhysicalVolume = enum.auto()
|
||||
|
||||
PhysicalPoint2D = enum.auto()
|
||||
PhysicalPoint3D = enum.auto()
|
||||
|
||||
PhysicalSize2D = enum.auto()
|
||||
PhysicalSize3D = enum.auto()
|
||||
|
||||
PhysicalMass = enum.auto()
|
||||
|
||||
PhysicalSpeed = enum.auto()
|
||||
PhysicalAccelScalar = enum.auto()
|
||||
PhysicalForceScalar = enum.auto()
|
||||
PhysicalAccel3DVector = enum.auto()
|
||||
PhysicalForce3DVector = enum.auto()
|
||||
|
||||
PhysicalPol = enum.auto()
|
||||
|
||||
PhysicalFreq = enum.auto()
|
||||
PhysicalVacWL = enum.auto()
|
||||
PhysicalSpecPowerDist = enum.auto()
|
||||
PhysicalSpecRelPermDist = enum.auto()
|
||||
|
||||
# Blender
|
||||
BlenderObject = enum.auto()
|
||||
BlenderCollection = enum.auto()
|
||||
|
||||
BlenderImage = enum.auto()
|
||||
BlenderVolume = enum.auto()
|
||||
|
||||
BlenderGeoNodes = enum.auto()
|
||||
BlenderText = enum.auto()
|
||||
|
||||
BlenderPreviewTarget = enum.auto()
|
||||
|
||||
# Maxwell
|
||||
MaxwellSource = enum.auto()
|
||||
MaxwellTemporalShape = enum.auto()
|
||||
|
||||
MaxwellMedium = enum.auto()
|
||||
MaxwellMediumNonLinearity = enum.auto()
|
||||
|
||||
MaxwellStructure = enum.auto()
|
||||
|
||||
MaxwellBoundBox = enum.auto()
|
||||
MaxwellBoundFace = enum.auto()
|
||||
|
||||
MaxwellMonitor = enum.auto()
|
||||
|
||||
MaxwellFDTDSim = enum.auto()
|
||||
MaxwellSimGrid = enum.auto()
|
||||
MaxwellSimGridAxis = enum.auto()
|
||||
|
||||
SocketType_to_units = {
|
||||
SocketType.PhysicalTime: {
|
||||
"default": "PS",
|
||||
"values": {
|
||||
"PS": spu.picosecond,
|
||||
"NS": spu.nanosecond,
|
||||
"MS": spu.microsecond,
|
||||
"MLSEC": spu.millisecond,
|
||||
"SEC": spu.second,
|
||||
"MIN": spu.minute,
|
||||
"HOUR": spu.hour,
|
||||
"DAY": spu.day,
|
||||
},
|
||||
},
|
||||
|
||||
SocketType.PhysicalAngle: {
|
||||
"default": "RADIAN",
|
||||
"values": {
|
||||
"RADIAN": spu.radian,
|
||||
"DEGREE": spu.degree,
|
||||
"STERAD": spu.steradian,
|
||||
"ANGMIL": spu.angular_mil,
|
||||
},
|
||||
},
|
||||
|
||||
SocketType.PhysicalLength: {
|
||||
"default": "UM",
|
||||
"values": {
|
||||
"PM": spu.picometer,
|
||||
"A": spu.angstrom,
|
||||
"NM": spu.nanometer,
|
||||
"UM": spu.micrometer,
|
||||
"MM": spu.millimeter,
|
||||
"CM": spu.centimeter,
|
||||
"M": spu.meter,
|
||||
"INCH": spu.inch,
|
||||
"FOOT": spu.foot,
|
||||
"YARD": spu.yard,
|
||||
"MILE": spu.mile,
|
||||
},
|
||||
},
|
||||
SocketType.PhysicalArea: {
|
||||
"default": "UM_SQ",
|
||||
"values": {
|
||||
"PM_SQ": spu.picometer**2,
|
||||
"A_SQ": spu.angstrom**2,
|
||||
"NM_SQ": spu.nanometer**2,
|
||||
"UM_SQ": spu.micrometer**2,
|
||||
"MM_SQ": spu.millimeter**2,
|
||||
"CM_SQ": spu.centimeter**2,
|
||||
"M_SQ": spu.meter**2,
|
||||
"INCH_SQ": spu.inch**2,
|
||||
"FOOT_SQ": spu.foot**2,
|
||||
"YARD_SQ": spu.yard**2,
|
||||
"MILE_SQ": spu.mile**2,
|
||||
},
|
||||
},
|
||||
SocketType.PhysicalVolume: {
|
||||
"default": "UM_CB",
|
||||
"values": {
|
||||
"PM_CB": spu.picometer**3,
|
||||
"A_CB": spu.angstrom**3,
|
||||
"NM_CB": spu.nanometer**3,
|
||||
"UM_CB": spu.micrometer**3,
|
||||
"MM_CB": spu.millimeter**3,
|
||||
"CM_CB": spu.centimeter**3,
|
||||
"M_CB": spu.meter**3,
|
||||
"ML": spu.milliliter,
|
||||
"L": spu.liter,
|
||||
"INCH_CB": spu.inch**3,
|
||||
"FOOT_CB": spu.foot**3,
|
||||
"YARD_CB": spu.yard**3,
|
||||
"MILE_CB": spu.mile**3,
|
||||
},
|
||||
},
|
||||
|
||||
SocketType.PhysicalPoint2D: {
|
||||
"default": "UM",
|
||||
"values": {
|
||||
"PM": spu.picometer,
|
||||
"A": spu.angstrom,
|
||||
"NM": spu.nanometer,
|
||||
"UM": spu.micrometer,
|
||||
"MM": spu.millimeter,
|
||||
"CM": spu.centimeter,
|
||||
"M": spu.meter,
|
||||
"INCH": spu.inch,
|
||||
"FOOT": spu.foot,
|
||||
"YARD": spu.yard,
|
||||
"MILE": spu.mile,
|
||||
},
|
||||
},
|
||||
SocketType.PhysicalPoint3D: {
|
||||
"default": "UM",
|
||||
"values": {
|
||||
"PM": spu.picometer,
|
||||
"A": spu.angstrom,
|
||||
"NM": spu.nanometer,
|
||||
"UM": spu.micrometer,
|
||||
"MM": spu.millimeter,
|
||||
"CM": spu.centimeter,
|
||||
"M": spu.meter,
|
||||
"INCH": spu.inch,
|
||||
"FOOT": spu.foot,
|
||||
"YARD": spu.yard,
|
||||
"MILE": spu.mile,
|
||||
},
|
||||
},
|
||||
|
||||
SocketType.PhysicalSize2D: {
|
||||
"default": "UM",
|
||||
"values": {
|
||||
"PM": spu.picometer,
|
||||
"A": spu.angstrom,
|
||||
"NM": spu.nanometer,
|
||||
"UM": spu.micrometer,
|
||||
"MM": spu.millimeter,
|
||||
"CM": spu.centimeter,
|
||||
"M": spu.meter,
|
||||
"INCH": spu.inch,
|
||||
"FOOT": spu.foot,
|
||||
"YARD": spu.yard,
|
||||
"MILE": spu.mile,
|
||||
},
|
||||
},
|
||||
SocketType.PhysicalSize3D: {
|
||||
"default": "UM",
|
||||
"values": {
|
||||
"PM": spu.picometer,
|
||||
"A": spu.angstrom,
|
||||
"NM": spu.nanometer,
|
||||
"UM": spu.micrometer,
|
||||
"MM": spu.millimeter,
|
||||
"CM": spu.centimeter,
|
||||
"M": spu.meter,
|
||||
"INCH": spu.inch,
|
||||
"FOOT": spu.foot,
|
||||
"YARD": spu.yard,
|
||||
"MILE": spu.mile,
|
||||
},
|
||||
},
|
||||
|
||||
SocketType.PhysicalMass: {
|
||||
"default": "UG",
|
||||
"values": {
|
||||
"E_REST": spu.electron_rest_mass,
|
||||
"DAL": spu.dalton,
|
||||
"UG": spu.microgram,
|
||||
"MG": spu.milligram,
|
||||
"G": spu.gram,
|
||||
"KG": spu.kilogram,
|
||||
"TON": spu.metric_ton,
|
||||
},
|
||||
},
|
||||
|
||||
SocketType.PhysicalSpeed: {
|
||||
"default": "UM_S",
|
||||
"values": {
|
||||
"PM_S": spu.picometer / spu.second,
|
||||
"NM_S": spu.nanometer / spu.second,
|
||||
"UM_S": spu.micrometer / spu.second,
|
||||
"MM_S": spu.millimeter / spu.second,
|
||||
"M_S": spu.meter / spu.second,
|
||||
"KM_S": spu.kilometer / spu.second,
|
||||
"KM_H": spu.kilometer / spu.hour,
|
||||
"FT_S": spu.feet / spu.second,
|
||||
"MI_H": spu.mile / spu.hour,
|
||||
},
|
||||
},
|
||||
SocketType.PhysicalAccelScalar: {
|
||||
"default": "UM_S_SQ",
|
||||
"values": {
|
||||
"PM_S_SQ": spu.picometer / spu.second**2,
|
||||
"NM_S_SQ": spu.nanometer / spu.second**2,
|
||||
"UM_S_SQ": spu.micrometer / spu.second**2,
|
||||
"MM_S_SQ": spu.millimeter / spu.second**2,
|
||||
"M_S_SQ": spu.meter / spu.second**2,
|
||||
"KM_S_SQ": spu.kilometer / spu.second**2,
|
||||
"FT_S_SQ": spu.feet / spu.second**2,
|
||||
},
|
||||
},
|
||||
SocketType.PhysicalForceScalar: {
|
||||
"default": "UNEWT",
|
||||
"values": {
|
||||
"KG_M_S_SQ": spu.kg * spu.m/spu.second**2,
|
||||
"NNEWT": spuex.nanonewton,
|
||||
"UNEWT": spuex.micronewton,
|
||||
"MNEWT": spuex.millinewton,
|
||||
"NEWT": spu.newton,
|
||||
},
|
||||
},
|
||||
SocketType.PhysicalAccel3DVector: {
|
||||
"default": "UM_S_SQ",
|
||||
"values": {
|
||||
"PM_S_SQ": spu.picometer / spu.second**2,
|
||||
"NM_S_SQ": spu.nanometer / spu.second**2,
|
||||
"UM_S_SQ": spu.micrometer / spu.second**2,
|
||||
"MM_S_SQ": spu.millimeter / spu.second**2,
|
||||
"M_S_SQ": spu.meter / spu.second**2,
|
||||
"KM_S_SQ": spu.kilometer / spu.second**2,
|
||||
"FT_S_SQ": spu.feet / spu.second**2,
|
||||
},
|
||||
},
|
||||
SocketType.PhysicalForce3DVector: {
|
||||
"default": "UNEWT",
|
||||
"values": {
|
||||
"KG_M_S_SQ": spu.kg * spu.m/spu.second**2,
|
||||
"NNEWT": spuex.nanonewton,
|
||||
"UNEWT": spuex.micronewton,
|
||||
"MNEWT": spuex.millinewton,
|
||||
"NEWT": spu.newton,
|
||||
},
|
||||
},
|
||||
|
||||
SocketType.PhysicalFreq: {
|
||||
"default": "THZ",
|
||||
"values": {
|
||||
"HZ": spu.hertz,
|
||||
"KHZ": spuex.kilohertz,
|
||||
"MHZ": spuex.megahertz,
|
||||
"GHZ": spuex.gigahertz,
|
||||
"THZ": spuex.terahertz,
|
||||
"PHZ": spuex.petahertz,
|
||||
"EHZ": spuex.exahertz,
|
||||
},
|
||||
},
|
||||
SocketType.PhysicalVacWL: {
|
||||
"default": "NM",
|
||||
"values": {
|
||||
"PM": spu.picometer, ## c(vac) = wl*freq
|
||||
"A": spu.angstrom,
|
||||
"NM": spu.nanometer,
|
||||
"UM": spu.micrometer,
|
||||
"MM": spu.millimeter,
|
||||
"CM": spu.centimeter,
|
||||
"M": spu.meter,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
SocketType_to_color = {
|
||||
# Basic
|
||||
SocketType.Any: (0.8, 0.8, 0.8, 1.0), # Light Grey
|
||||
SocketType.Bool: (0.7, 0.7, 0.7, 1.0), # Medium Light Grey
|
||||
SocketType.Text: (0.7, 0.7, 0.7, 1.0), # Medium Light Grey
|
||||
SocketType.FilePath: (0.6, 0.6, 0.6, 1.0), # Medium Grey
|
||||
|
||||
# Number
|
||||
SocketType.IntegerNumber: (0.5, 0.5, 1.0, 1.0), # Light Blue
|
||||
SocketType.RationalNumber: (0.4, 0.4, 0.9, 1.0), # Medium Light Blue
|
||||
SocketType.RealNumber: (0.3, 0.3, 0.8, 1.0), # Medium Blue
|
||||
SocketType.ComplexNumber: (0.2, 0.2, 0.7, 1.0), # Dark Blue
|
||||
|
||||
# Vector
|
||||
SocketType.Real2DVector: (0.5, 1.0, 0.5, 1.0), # Light Green
|
||||
SocketType.Complex2DVector: (0.4, 0.9, 0.4, 1.0), # Medium Light Green
|
||||
SocketType.Real3DVector: (0.3, 0.8, 0.3, 1.0), # Medium Green
|
||||
SocketType.Complex3DVector: (0.2, 0.7, 0.2, 1.0), # Dark Green
|
||||
|
||||
# Physical
|
||||
SocketType.PhysicalUnitSystem: (1.0, 0.5, 0.5, 1.0), # Light Red
|
||||
SocketType.PhysicalTime: (1.0, 0.5, 0.5, 1.0), # Light Red
|
||||
SocketType.PhysicalAngle: (0.9, 0.45, 0.45, 1.0), # Medium Light Red
|
||||
SocketType.PhysicalLength: (0.8, 0.4, 0.4, 1.0), # Medium Red
|
||||
SocketType.PhysicalArea: (0.7, 0.35, 0.35, 1.0), # Medium Dark Red
|
||||
SocketType.PhysicalVolume: (0.6, 0.3, 0.3, 1.0), # Dark Red
|
||||
SocketType.PhysicalPoint2D: (0.7, 0.35, 0.35, 1.0), # Medium Dark Red
|
||||
SocketType.PhysicalPoint3D: (0.6, 0.3, 0.3, 1.0), # Dark Red
|
||||
SocketType.PhysicalSize2D: (0.7, 0.35, 0.35, 1.0), # Medium Dark Red
|
||||
SocketType.PhysicalSize3D: (0.6, 0.3, 0.3, 1.0), # Dark Red
|
||||
SocketType.PhysicalMass: (0.9, 0.6, 0.4, 1.0), # Light Orange
|
||||
SocketType.PhysicalSpeed: (0.8, 0.55, 0.35, 1.0), # Medium Light Orange
|
||||
SocketType.PhysicalAccelScalar: (0.7, 0.5, 0.3, 1.0), # Medium Orange
|
||||
SocketType.PhysicalForceScalar: (0.6, 0.45, 0.25, 1.0), # Medium Dark Orange
|
||||
SocketType.PhysicalAccel3DVector: (0.7, 0.5, 0.3, 1.0), # Medium Orange
|
||||
SocketType.PhysicalForce3DVector: (0.6, 0.45, 0.25, 1.0), # Medium Dark Orange
|
||||
SocketType.PhysicalPol: (0.5, 0.4, 0.2, 1.0), # Dark Orange
|
||||
SocketType.PhysicalFreq: (1.0, 0.7, 0.5, 1.0), # Light Peach
|
||||
SocketType.PhysicalVacWL: (1.0, 0.7, 0.5, 1.0), # Light Peach
|
||||
SocketType.PhysicalSpecPowerDist: (0.9, 0.65, 0.45, 1.0), # Medium Light Peach
|
||||
SocketType.PhysicalSpecRelPermDist: (0.8, 0.6, 0.4, 1.0), # Medium Peach
|
||||
|
||||
# Blender
|
||||
SocketType.BlenderObject: (0.7, 0.5, 1.0, 1.0), # Light Purple
|
||||
SocketType.BlenderCollection: (0.6, 0.45, 0.9, 1.0), # Medium Light Purple
|
||||
SocketType.BlenderImage: (0.5, 0.4, 0.8, 1.0), # Medium Purple
|
||||
SocketType.BlenderVolume: (0.4, 0.35, 0.7, 1.0), # Medium Dark Purple
|
||||
SocketType.BlenderGeoNodes: (0.3, 0.3, 0.6, 1.0), # Dark Purple
|
||||
SocketType.BlenderText: (0.5, 0.5, 0.75, 1.0), # Light Lavender
|
||||
SocketType.BlenderPreviewTarget: (0.5, 0.5, 0.75, 1.0), # Light Lavender
|
||||
|
||||
# Maxwell
|
||||
SocketType.MaxwellSource: (1.0, 1.0, 0.5, 1.0), # Light Yellow
|
||||
SocketType.MaxwellTemporalShape: (0.9, 0.9, 0.45, 1.0), # Medium Light Yellow
|
||||
SocketType.MaxwellMedium: (0.8, 0.8, 0.4, 1.0), # Medium Yellow
|
||||
SocketType.MaxwellMediumNonLinearity: (0.7, 0.7, 0.35, 1.0), # Medium Dark Yellow
|
||||
SocketType.MaxwellStructure: (0.6, 0.6, 0.3, 1.0), # Dark Yellow
|
||||
SocketType.MaxwellBoundBox: (0.9, 0.8, 0.5, 1.0), # Light Gold
|
||||
SocketType.MaxwellBoundFace: (0.8, 0.7, 0.45, 1.0), # Medium Light Gold
|
||||
SocketType.MaxwellMonitor: (0.7, 0.6, 0.4, 1.0), # Medium Gold
|
||||
SocketType.MaxwellFDTDSim: (0.6, 0.5, 0.35, 1.0), # Medium Dark Gold
|
||||
SocketType.MaxwellSimGrid: (0.5, 0.4, 0.3, 1.0), # Dark Gold
|
||||
SocketType.MaxwellSimGridAxis: (0.4, 0.3, 0.25, 1.0), # Darkest Gold
|
||||
}
|
||||
|
||||
BLNodeSocket_to_SocketType = {
|
||||
1: {
|
||||
"NodeSocketStandard": SocketType.Any,
|
||||
"NodeSocketVirtual": SocketType.Any,
|
||||
"NodeSocketGeometry": SocketType.Any,
|
||||
"NodeSocketTexture": SocketType.Any,
|
||||
"NodeSocketShader": SocketType.Any,
|
||||
"NodeSocketMaterial": SocketType.Any,
|
||||
|
||||
"NodeSocketString": SocketType.Text,
|
||||
"NodeSocketBool": SocketType.Bool,
|
||||
"NodeSocketCollection": SocketType.BlenderCollection,
|
||||
"NodeSocketImage": SocketType.BlenderImage,
|
||||
"NodeSocketObject": SocketType.BlenderObject,
|
||||
|
||||
"NodeSocketFloat": SocketType.RealNumber,
|
||||
"NodeSocketFloatAngle": SocketType.PhysicalAngle,
|
||||
"NodeSocketFloatDistance": SocketType.PhysicalLength,
|
||||
"NodeSocketFloatFactor": SocketType.RealNumber,
|
||||
"NodeSocketFloatPercentage": SocketType.RealNumber,
|
||||
"NodeSocketFloatTime": SocketType.PhysicalTime,
|
||||
"NodeSocketFloatTimeAbsolute": SocketType.RealNumber,
|
||||
"NodeSocketFloatUnsigned": SocketType.RealNumber,
|
||||
|
||||
"NodeSocketInt": SocketType.IntegerNumber,
|
||||
"NodeSocketIntFactor": SocketType.IntegerNumber,
|
||||
"NodeSocketIntPercentage": SocketType.IntegerNumber,
|
||||
"NodeSocketIntUnsigned": SocketType.IntegerNumber,
|
||||
},
|
||||
2: {
|
||||
"NodeSocketVector": SocketType.Real3DVector,
|
||||
"NodeSocketVectorAcceleration": SocketType.Real3DVector,
|
||||
"NodeSocketVectorDirection": SocketType.Real3DVector,
|
||||
"NodeSocketVectorEuler": SocketType.Real3DVector,
|
||||
"NodeSocketVectorTranslation": SocketType.Real3DVector,
|
||||
"NodeSocketVectorVelocity": SocketType.Real3DVector,
|
||||
"NodeSocketVectorXYZ": SocketType.Real3DVector,
|
||||
#"NodeSocketVector": SocketType.Real2DVector,
|
||||
#"NodeSocketVectorAcceleration": SocketType.PhysicalAccel2D,
|
||||
#"NodeSocketVectorDirection": SocketType.PhysicalDir2D,
|
||||
#"NodeSocketVectorEuler": SocketType.PhysicalEuler2D,
|
||||
#"NodeSocketVectorTranslation": SocketType.PhysicalDispl2D,
|
||||
#"NodeSocketVectorVelocity": SocketType.PhysicalVel2D,
|
||||
#"NodeSocketVectorXYZ": SocketType.Real2DPoint,
|
||||
},
|
||||
3: {
|
||||
"NodeSocketRotation": SocketType.Real3DVector,
|
||||
|
||||
"NodeSocketColor": SocketType.Any,
|
||||
|
||||
"NodeSocketVector": SocketType.Real3DVector,
|
||||
#"NodeSocketVectorAcceleration": SocketType.PhysicalAccel3D,
|
||||
#"NodeSocketVectorDirection": SocketType.PhysicalDir3D,
|
||||
#"NodeSocketVectorEuler": SocketType.PhysicalEuler3D,
|
||||
#"NodeSocketVectorTranslation": SocketType.PhysicalDispl3D,
|
||||
"NodeSocketVectorTranslation": SocketType.PhysicalPoint3D,
|
||||
#"NodeSocketVectorVelocity": SocketType.PhysicalVel3D,
|
||||
"NodeSocketVectorXYZ": SocketType.PhysicalPoint3D,
|
||||
},
|
||||
}
|
||||
|
||||
BLNodeSocket_to_SocketType_by_desc = {
|
||||
1: {
|
||||
"Angle": SocketType.PhysicalAngle,
|
||||
|
||||
"Length": SocketType.PhysicalLength,
|
||||
"Area": SocketType.PhysicalArea,
|
||||
"Volume": SocketType.PhysicalVolume,
|
||||
|
||||
"Mass": SocketType.PhysicalMass,
|
||||
|
||||
"Speed": SocketType.PhysicalSpeed,
|
||||
"Accel": SocketType.PhysicalAccelScalar,
|
||||
"Force": SocketType.PhysicalForceScalar,
|
||||
|
||||
"Freq": SocketType.PhysicalFreq,
|
||||
},
|
||||
2: {
|
||||
#"2DCount": SocketType.Int2DVector,
|
||||
|
||||
#"2DPoint": SocketType.PhysicalPoint2D,
|
||||
#"2DSize": SocketType.PhysicalSize2D,
|
||||
#"2DPol": SocketType.PhysicalPol,
|
||||
"2DPoint": SocketType.PhysicalPoint3D,
|
||||
"2DSize": SocketType.PhysicalSize3D,
|
||||
},
|
||||
3: {
|
||||
#"Count": SocketType.Int3DVector,
|
||||
|
||||
"Point": SocketType.PhysicalPoint3D,
|
||||
"Size": SocketType.PhysicalSize3D,
|
||||
|
||||
#"Force": SocketType.PhysicalForce3D,
|
||||
|
||||
"Freq": SocketType.PhysicalSize3D,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
####################
|
||||
# - Node Types
|
||||
####################
|
||||
@append_cls_name_to_values
|
||||
class NodeType(BlenderTypeEnum):
|
||||
KitchenSink = enum.auto()
|
||||
|
||||
# Inputs
|
||||
UnitSystem = enum.auto()
|
||||
|
||||
## Inputs / Scene
|
||||
Time = enum.auto()
|
||||
|
||||
## Inputs / Parameters
|
||||
NumberParameter = enum.auto()
|
||||
PhysicalParameter = enum.auto()
|
||||
|
||||
## Inputs / Constants
|
||||
WaveConstant = enum.auto()
|
||||
ScientificConstant = enum.auto()
|
||||
NumberConstant = enum.auto()
|
||||
PhysicalConstant = enum.auto()
|
||||
BlenderConstant = enum.auto()
|
||||
|
||||
## Inputs / Lists
|
||||
RealList = enum.auto()
|
||||
ComplexList = enum.auto()
|
||||
|
||||
## Inputs /
|
||||
InputFile = enum.auto()
|
||||
|
||||
|
||||
# Outputs
|
||||
## Outputs / Viewers
|
||||
Viewer3D = enum.auto()
|
||||
ValueViewer = enum.auto()
|
||||
ConsoleViewer = enum.auto()
|
||||
|
||||
## Outputs / Exporters
|
||||
JSONFileExporter = enum.auto()
|
||||
|
||||
|
||||
# Sources
|
||||
## Sources / Temporal Shapes
|
||||
GaussianPulseTemporalShape = enum.auto()
|
||||
ContinuousWaveTemporalShape = enum.auto()
|
||||
ListTemporalShape = enum.auto()
|
||||
|
||||
## Sources /
|
||||
PointDipoleSource = enum.auto()
|
||||
UniformCurrentSource = enum.auto()
|
||||
PlaneWaveSource = enum.auto()
|
||||
ModeSource = enum.auto()
|
||||
GaussianBeamSource = enum.auto()
|
||||
AstigmaticGaussianBeamSource = enum.auto()
|
||||
TFSFSource = enum.auto()
|
||||
|
||||
EHEquivalenceSource = enum.auto()
|
||||
EHSource = enum.auto()
|
||||
|
||||
# Mediums
|
||||
LibraryMedium = enum.auto()
|
||||
|
||||
PECMedium = enum.auto()
|
||||
IsotropicMedium = enum.auto()
|
||||
AnisotropicMedium = enum.auto()
|
||||
|
||||
TripleSellmeierMedium = enum.auto()
|
||||
SellmeierMedium = enum.auto()
|
||||
PoleResidueMedium = enum.auto()
|
||||
DrudeMedium = enum.auto()
|
||||
DrudeLorentzMedium = enum.auto()
|
||||
DebyeMedium = enum.auto()
|
||||
|
||||
## Mediums / Non-Linearities
|
||||
AddNonLinearity = enum.auto()
|
||||
ChiThreeSusceptibilityNonLinearity = enum.auto()
|
||||
TwoPhotonAbsorptionNonLinearity = enum.auto()
|
||||
KerrNonLinearity = enum.auto()
|
||||
|
||||
# Structures
|
||||
ObjectStructure = enum.auto()
|
||||
GeoNodesStructure = enum.auto()
|
||||
ScriptedStructure = enum.auto()
|
||||
|
||||
## Structures / Primitives
|
||||
BoxStructure = enum.auto()
|
||||
SphereStructure = enum.auto()
|
||||
CylinderStructure = enum.auto()
|
||||
|
||||
|
||||
# Bounds
|
||||
BoundBox = enum.auto()
|
||||
|
||||
## Bounds / Bound Faces
|
||||
PMLBoundFace = enum.auto()
|
||||
PECBoundFace = enum.auto()
|
||||
PMCBoundFace = enum.auto()
|
||||
|
||||
BlochBoundFace = enum.auto()
|
||||
PeriodicBoundFace = enum.auto()
|
||||
AbsorbingBoundFace = enum.auto()
|
||||
|
||||
|
||||
# Monitors
|
||||
EHFieldMonitor = enum.auto()
|
||||
FieldPowerFluxMonitor = enum.auto()
|
||||
EpsilonTensorMonitor = enum.auto()
|
||||
DiffractionMonitor = enum.auto()
|
||||
|
||||
## Monitors / Near-Field Projections
|
||||
CartesianNearFieldProjectionMonitor = enum.auto()
|
||||
ObservationAngleNearFieldProjectionMonitor = enum.auto()
|
||||
KSpaceNearFieldProjectionMonitor = enum.auto()
|
||||
|
||||
|
||||
# Sims
|
||||
SimGrid = enum.auto()
|
||||
|
||||
## Sims / Sim Grid Axis
|
||||
AutomaticSimGridAxis = enum.auto()
|
||||
ManualSimGridAxis = enum.auto()
|
||||
UniformSimGridAxis = enum.auto()
|
||||
ArraySimGridAxis = enum.auto()
|
||||
|
||||
## Sim /
|
||||
FDTDSim = enum.auto()
|
||||
|
||||
|
||||
# Utilities
|
||||
Combine = enum.auto()
|
||||
Separate = enum.auto()
|
||||
Math = enum.auto()
|
||||
|
||||
## Utilities / Converters
|
||||
WaveConverter = enum.auto()
|
||||
|
||||
## Utilities / Operations
|
||||
ArrayOperation = enum.auto()
|
||||
|
||||
####################
|
||||
# - Node Category Types
|
||||
####################
|
||||
@wrap_values_in_MT
|
||||
class NodeCategory(BlenderTypeEnum):
|
||||
MAXWELLSIM = enum.auto()
|
||||
|
||||
# Inputs/
|
||||
MAXWELLSIM_INPUTS = enum.auto()
|
||||
MAXWELLSIM_INPUTS_SCENE = enum.auto()
|
||||
MAXWELLSIM_INPUTS_PARAMETERS = enum.auto()
|
||||
MAXWELLSIM_INPUTS_CONSTANTS = enum.auto()
|
||||
MAXWELLSIM_INPUTS_LISTS = enum.auto()
|
||||
|
||||
# Outputs/
|
||||
MAXWELLSIM_OUTPUTS = enum.auto()
|
||||
MAXWELLSIM_OUTPUTS_VIEWERS = enum.auto()
|
||||
MAXWELLSIM_OUTPUTS_EXPORTERS = enum.auto()
|
||||
MAXWELLSIM_OUTPUTS_PLOTTERS = enum.auto()
|
||||
|
||||
# Sources/
|
||||
MAXWELLSIM_SOURCES = enum.auto()
|
||||
MAXWELLSIM_SOURCES_TEMPORALSHAPES = enum.auto()
|
||||
|
||||
# Mediums/
|
||||
MAXWELLSIM_MEDIUMS = enum.auto()
|
||||
MAXWELLSIM_MEDIUMS_NONLINEARITIES = enum.auto()
|
||||
|
||||
# Structures/
|
||||
MAXWELLSIM_STRUCTURES = enum.auto()
|
||||
MAXWELLSIM_STRUCTURES_PRIMITIVES = enum.auto()
|
||||
|
||||
# Bounds/
|
||||
MAXWELLSIM_BOUNDS = enum.auto()
|
||||
MAXWELLSIM_BOUNDS_BOUNDFACES = enum.auto()
|
||||
|
||||
# Monitors/
|
||||
MAXWELLSIM_MONITORS = enum.auto()
|
||||
MAXWELLSIM_MONITORS_NEARFIELDPROJECTIONS = enum.auto()
|
||||
|
||||
# Simulations/
|
||||
MAXWELLSIM_SIMS = enum.auto()
|
||||
MAXWELLSIM_SIMGRIDAXES = enum.auto()
|
||||
|
||||
# Utilities/
|
||||
MAXWELLSIM_UTILITIES = enum.auto()
|
||||
MAXWELLSIM_UTILITIES_CONVERTERS = enum.auto()
|
||||
MAXWELLSIM_UTILITIES_OPERATIONS = enum.auto()
|
||||
|
||||
@classmethod
|
||||
def get_tree(cls):
|
||||
## TODO: Refactor
|
||||
syllable_categories = [
|
||||
node_category.value.split("_")
|
||||
for node_category in cls
|
||||
if node_category.value != "MAXWELLSIM"
|
||||
]
|
||||
|
||||
category_tree = {}
|
||||
for syllable_category in syllable_categories:
|
||||
# Set Current Subtree to Root
|
||||
current_category_subtree = category_tree
|
||||
|
||||
for i, syllable in enumerate(syllable_category):
|
||||
# Create New Category Subtree and/or Step to Subtree
|
||||
if syllable not in current_category_subtree:
|
||||
current_category_subtree[syllable] = {}
|
||||
current_category_subtree = current_category_subtree[syllable]
|
||||
|
||||
return category_tree
|
||||
|
||||
NodeCategory_to_category_label = {
|
||||
# Inputs/
|
||||
NodeCategory.MAXWELLSIM_INPUTS: "Inputs",
|
||||
NodeCategory.MAXWELLSIM_INPUTS_SCENE: "Scene",
|
||||
NodeCategory.MAXWELLSIM_INPUTS_PARAMETERS: "Parameters",
|
||||
NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS: "Constants",
|
||||
NodeCategory.MAXWELLSIM_INPUTS_LISTS: "Lists",
|
||||
|
||||
# Outputs/
|
||||
NodeCategory.MAXWELLSIM_OUTPUTS: "Outputs",
|
||||
NodeCategory.MAXWELLSIM_OUTPUTS_VIEWERS: "Viewers",
|
||||
NodeCategory.MAXWELLSIM_OUTPUTS_EXPORTERS: "Exporters",
|
||||
NodeCategory.MAXWELLSIM_OUTPUTS_PLOTTERS: "Plotters",
|
||||
|
||||
# Sources/
|
||||
NodeCategory.MAXWELLSIM_SOURCES: "Sources",
|
||||
NodeCategory.MAXWELLSIM_SOURCES_TEMPORALSHAPES: "Temporal Shapes",
|
||||
|
||||
# Mediums/
|
||||
NodeCategory.MAXWELLSIM_MEDIUMS: "Mediums",
|
||||
NodeCategory.MAXWELLSIM_MEDIUMS_NONLINEARITIES: "Non-Linearities",
|
||||
|
||||
# Structures/
|
||||
NodeCategory.MAXWELLSIM_STRUCTURES: "Structures",
|
||||
NodeCategory.MAXWELLSIM_STRUCTURES_PRIMITIVES: "Primitives",
|
||||
|
||||
# Bounds/
|
||||
NodeCategory.MAXWELLSIM_BOUNDS: "Bounds",
|
||||
NodeCategory.MAXWELLSIM_BOUNDS_BOUNDFACES: "Bound Faces",
|
||||
|
||||
# Monitors/
|
||||
NodeCategory.MAXWELLSIM_MONITORS: "Monitors",
|
||||
NodeCategory.MAXWELLSIM_MONITORS_NEARFIELDPROJECTIONS: "Near-Field Projections",
|
||||
|
||||
# Simulations/
|
||||
NodeCategory.MAXWELLSIM_SIMS: "Simulations",
|
||||
NodeCategory.MAXWELLSIM_SIMGRIDAXES: "Sim Grid Axes",
|
||||
|
||||
# Utilities/
|
||||
NodeCategory.MAXWELLSIM_UTILITIES: "Utilities",
|
||||
NodeCategory.MAXWELLSIM_UTILITIES_CONVERTERS: "Converters",
|
||||
NodeCategory.MAXWELLSIM_UTILITIES_OPERATIONS: "Operations",
|
||||
}
|
||||
|
||||
|
||||
|
||||
####################
|
||||
# - Protocols
|
||||
####################
|
||||
class SocketDefProtocol(typ.Protocol):
|
||||
socket_type: SocketType
|
||||
label: str
|
||||
|
||||
def init(self, bl_socket: bpy.types.NodeSocket) -> None:
|
||||
...
|
||||
|
||||
class PresetDef(pyd.BaseModel):
|
||||
label: str
|
||||
description: str
|
||||
values: dict[SocketName, typ.Any]
|
||||
|
||||
SocketReturnType = typ.TypeVar('SocketReturnType', covariant=True)
|
||||
## - Covariance: If B subtypes A, then Container[B] subtypes Container[A].
|
||||
## - This is absolutely what we want here.
|
||||
|
||||
#@typ.runtime_checkable
|
||||
#class BLSocketProtocol(typ.Protocol):
|
||||
# socket_type: SocketType
|
||||
# socket_color: BlenderColorRGB
|
||||
#
|
||||
# bl_label: str
|
||||
#
|
||||
# compatible_types: dict[typ.Type, set[typ.Callable[[typ.Any], bool]]]
|
||||
#
|
||||
# def draw(
|
||||
# self,
|
||||
# context: bpy.types.Context,
|
||||
# layout: bpy.types.UILayout,
|
||||
# node: bpy.types.Node,
|
||||
# text: str,
|
||||
# ) -> None:
|
||||
# ...
|
||||
#
|
||||
# @property
|
||||
# def default_value(self) -> typ.Any:
|
||||
# ...
|
||||
# @default_value.setter
|
||||
# def default_value(self, value: typ.Any) -> typ.Any:
|
||||
# ...
|
||||
#
|
||||
|
||||
@typ.runtime_checkable
|
||||
class NodeTypeProtocol(typ.Protocol):
|
||||
node_type: NodeType
|
||||
|
||||
bl_label: str
|
||||
|
||||
input_sockets: dict[SocketName, SocketDefProtocol]
|
||||
output_sockets: dict[SocketName, SocketDefProtocol]
|
||||
presets: dict[PresetID, PresetDef] | None
|
||||
|
||||
# Built-In Blender Methods
|
||||
def init(self, context: bpy.types.Context) -> None:
|
||||
...
|
||||
|
||||
def draw_buttons(
|
||||
self,
|
||||
context: bpy.types.Context,
|
||||
layout: bpy.types.UILayout,
|
||||
) -> None:
|
||||
...
|
||||
|
||||
@classmethod
|
||||
def poll(cls, ntree: bpy.types.NodeTree) -> None:
|
||||
...
|
||||
|
||||
# Socket Getters
|
||||
def g_input_bl_socket(
|
||||
self,
|
||||
input_socket_name: SocketName,
|
||||
) -> bpy.types.NodeSocket:
|
||||
...
|
||||
|
||||
def g_output_bl_socket(
|
||||
self,
|
||||
output_socket_name: SocketName,
|
||||
) -> bpy.types.NodeSocket:
|
||||
...
|
||||
|
||||
# Socket Methods
|
||||
def s_input_value(
|
||||
self,
|
||||
input_socket_name: SocketName,
|
||||
value: typ.Any
|
||||
) -> typ.Any:
|
||||
...
|
||||
|
||||
# Data-Flow Methods
|
||||
def compute_input(
|
||||
self,
|
||||
input_socket_name: SocketName,
|
||||
) -> typ.Any:
|
||||
...
|
||||
def compute_output(
|
||||
self,
|
||||
output_socket_name: SocketName,
|
||||
) -> typ.Any:
|
||||
...
|
|
@ -0,0 +1,52 @@
|
|||
####################
|
||||
# - String Types
|
||||
####################
|
||||
from .bl import SocketName
|
||||
from .bl import PresetName
|
||||
from .bl import ManagedObjName
|
||||
|
||||
from .bl import BLEnumID
|
||||
from .bl import BLColorRGBA
|
||||
|
||||
####################
|
||||
# - Icon Types
|
||||
####################
|
||||
from .icons import Icon
|
||||
|
||||
####################
|
||||
# - Tree Types
|
||||
####################
|
||||
from .trees import TreeType
|
||||
|
||||
####################
|
||||
# - Socket Types
|
||||
####################
|
||||
from .socket_types import SocketType
|
||||
|
||||
from .socket_units import SOCKET_UNITS
|
||||
from .socket_colors import SOCKET_COLORS
|
||||
from .socket_shapes import SOCKET_SHAPES
|
||||
from .socket_bl_maps import BLSocketToSocket
|
||||
|
||||
####################
|
||||
# - Node Types
|
||||
####################
|
||||
from .node_types import NodeType
|
||||
|
||||
from .node_cats import NodeCategory
|
||||
from .node_cat_labels import NODE_CAT_LABELS
|
||||
|
||||
####################
|
||||
# - Managed Obj Type
|
||||
####################
|
||||
from .managed_obj_type import ManagedObjType
|
||||
|
||||
####################
|
||||
# - Data Flows
|
||||
####################
|
||||
from .data_flows import DataFlowKind
|
||||
|
||||
####################
|
||||
# - Schemas
|
||||
####################
|
||||
from . import schemas
|
|
@ -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_]+$',
|
||||
)]
|
|
@ -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()
|
|
@ -0,0 +1,4 @@
|
|||
from ....utils.blender_type_enum import BlenderTypeEnum
|
||||
|
||||
class Icon(BlenderTypeEnum):
|
||||
SimNodeEditor = "MOD_SIMPLEDEFORM"
|
|
@ -0,0 +1,9 @@
|
|||
import enum
|
||||
|
||||
from ....utils.blender_type_enum import (
|
||||
BlenderTypeEnum
|
||||
)
|
||||
|
||||
class ManagedObjType(BlenderTypeEnum):
|
||||
ManagedBLObject = enum.auto()
|
||||
ManagedBLImage = enum.auto()
|
|
@ -0,0 +1,46 @@
|
|||
from .node_cats import NodeCategory as NC
|
||||
|
||||
NODE_CAT_LABELS = {
|
||||
# Inputs/
|
||||
NC.MAXWELLSIM_INPUTS: "Inputs",
|
||||
NC.MAXWELLSIM_INPUTS_IMPORTERS: "Importers",
|
||||
NC.MAXWELLSIM_INPUTS_SCENE: "Scene",
|
||||
NC.MAXWELLSIM_INPUTS_PARAMETERS: "Parameters",
|
||||
NC.MAXWELLSIM_INPUTS_CONSTANTS: "Constants",
|
||||
NC.MAXWELLSIM_INPUTS_LISTS: "Lists",
|
||||
|
||||
# Outputs/
|
||||
NC.MAXWELLSIM_OUTPUTS: "Outputs",
|
||||
NC.MAXWELLSIM_OUTPUTS_VIEWERS: "Viewers",
|
||||
NC.MAXWELLSIM_OUTPUTS_EXPORTERS: "Exporters",
|
||||
NC.MAXWELLSIM_OUTPUTS_PLOTTERS: "Plotters",
|
||||
|
||||
# Sources/
|
||||
NC.MAXWELLSIM_SOURCES: "Sources",
|
||||
NC.MAXWELLSIM_SOURCES_TEMPORALSHAPES: "Temporal Shapes",
|
||||
|
||||
# Mediums/
|
||||
NC.MAXWELLSIM_MEDIUMS: "Mediums",
|
||||
NC.MAXWELLSIM_MEDIUMS_NONLINEARITIES: "Non-Linearities",
|
||||
|
||||
# Structures/
|
||||
NC.MAXWELLSIM_STRUCTURES: "Structures",
|
||||
NC.MAXWELLSIM_STRUCTURES_PRIMITIVES: "Primitives",
|
||||
|
||||
# Bounds/
|
||||
NC.MAXWELLSIM_BOUNDS: "Bounds",
|
||||
NC.MAXWELLSIM_BOUNDS_BOUNDFACES: "Bound Faces",
|
||||
|
||||
# Monitors/
|
||||
NC.MAXWELLSIM_MONITORS: "Monitors",
|
||||
NC.MAXWELLSIM_MONITORS_NEARFIELDPROJECTIONS: "Near-Field Projections",
|
||||
|
||||
# Simulations/
|
||||
NC.MAXWELLSIM_SIMS: "Simulations",
|
||||
NC.MAXWELLSIM_SIMGRIDAXES: "Sim Grid Axes",
|
||||
|
||||
# Utilities/
|
||||
NC.MAXWELLSIM_UTILITIES: "Utilities",
|
||||
NC.MAXWELLSIM_UTILITIES_CONVERTERS: "Converters",
|
||||
NC.MAXWELLSIM_UTILITIES_OPERATIONS: "Operations",
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
import enum
|
||||
|
||||
from ....utils.blender_type_enum import (
|
||||
BlenderTypeEnum, wrap_values_in_MT
|
||||
)
|
||||
|
||||
@wrap_values_in_MT
|
||||
class NodeCategory(BlenderTypeEnum):
|
||||
MAXWELLSIM = enum.auto()
|
||||
|
||||
# Inputs/
|
||||
MAXWELLSIM_INPUTS = enum.auto()
|
||||
MAXWELLSIM_INPUTS_IMPORTERS = enum.auto()
|
||||
MAXWELLSIM_INPUTS_SCENE = enum.auto()
|
||||
MAXWELLSIM_INPUTS_PARAMETERS = enum.auto()
|
||||
MAXWELLSIM_INPUTS_CONSTANTS = enum.auto()
|
||||
MAXWELLSIM_INPUTS_LISTS = enum.auto()
|
||||
|
||||
# Outputs/
|
||||
MAXWELLSIM_OUTPUTS = enum.auto()
|
||||
MAXWELLSIM_OUTPUTS_VIEWERS = enum.auto()
|
||||
MAXWELLSIM_OUTPUTS_EXPORTERS = enum.auto()
|
||||
MAXWELLSIM_OUTPUTS_PLOTTERS = enum.auto()
|
||||
|
||||
# Sources/
|
||||
MAXWELLSIM_SOURCES = enum.auto()
|
||||
MAXWELLSIM_SOURCES_TEMPORALSHAPES = enum.auto()
|
||||
|
||||
# Mediums/
|
||||
MAXWELLSIM_MEDIUMS = enum.auto()
|
||||
MAXWELLSIM_MEDIUMS_NONLINEARITIES = enum.auto()
|
||||
|
||||
# Structures/
|
||||
MAXWELLSIM_STRUCTURES = enum.auto()
|
||||
MAXWELLSIM_STRUCTURES_PRIMITIVES = enum.auto()
|
||||
|
||||
# Bounds/
|
||||
MAXWELLSIM_BOUNDS = enum.auto()
|
||||
MAXWELLSIM_BOUNDS_BOUNDFACES = enum.auto()
|
||||
|
||||
# Monitors/
|
||||
MAXWELLSIM_MONITORS = enum.auto()
|
||||
MAXWELLSIM_MONITORS_NEARFIELDPROJECTIONS = enum.auto()
|
||||
|
||||
# Simulations/
|
||||
MAXWELLSIM_SIMS = enum.auto()
|
||||
MAXWELLSIM_SIMGRIDAXES = enum.auto()
|
||||
|
||||
# Utilities/
|
||||
MAXWELLSIM_UTILITIES = enum.auto()
|
||||
MAXWELLSIM_UTILITIES_CONVERTERS = enum.auto()
|
||||
MAXWELLSIM_UTILITIES_OPERATIONS = enum.auto()
|
||||
|
||||
@classmethod
|
||||
def get_tree(cls):
|
||||
## TODO: Refactor
|
||||
syllable_categories = [
|
||||
str(node_category.value).split("_")
|
||||
for node_category in cls
|
||||
if node_category.value != "MAXWELLSIM"
|
||||
]
|
||||
|
||||
category_tree = {}
|
||||
for syllable_category in syllable_categories:
|
||||
# Set Current Subtree to Root
|
||||
current_category_subtree = category_tree
|
||||
|
||||
for i, syllable in enumerate(syllable_category):
|
||||
# Create New Category Subtree and/or Step to Subtree
|
||||
if syllable not in current_category_subtree:
|
||||
current_category_subtree[syllable] = {}
|
||||
current_category_subtree = current_category_subtree[syllable]
|
||||
|
||||
return category_tree
|
|
@ -0,0 +1,147 @@
|
|||
import enum
|
||||
|
||||
from ....utils.blender_type_enum import (
|
||||
BlenderTypeEnum, append_cls_name_to_values
|
||||
)
|
||||
|
||||
@append_cls_name_to_values
|
||||
class NodeType(BlenderTypeEnum):
|
||||
KitchenSink = enum.auto()
|
||||
|
||||
# Inputs
|
||||
UnitSystem = enum.auto()
|
||||
|
||||
## Inputs / Scene
|
||||
Time = enum.auto()
|
||||
|
||||
## Inputs / Importers
|
||||
Tidy3DWebImporter = enum.auto()
|
||||
|
||||
## Inputs / Parameters
|
||||
NumberParameter = enum.auto()
|
||||
PhysicalParameter = enum.auto()
|
||||
|
||||
## Inputs / Constants
|
||||
WaveConstant = enum.auto()
|
||||
ScientificConstant = enum.auto()
|
||||
NumberConstant = enum.auto()
|
||||
PhysicalConstant = enum.auto()
|
||||
BlenderConstant = enum.auto()
|
||||
|
||||
## Inputs / Lists
|
||||
RealList = enum.auto()
|
||||
ComplexList = enum.auto()
|
||||
|
||||
## Inputs /
|
||||
InputFile = enum.auto()
|
||||
|
||||
|
||||
# Outputs
|
||||
## Outputs / Viewers
|
||||
Viewer = enum.auto()
|
||||
ValueViewer = enum.auto()
|
||||
ConsoleViewer = enum.auto()
|
||||
|
||||
## Outputs / Exporters
|
||||
JSONFileExporter = enum.auto()
|
||||
Tidy3DWebExporter = enum.auto()
|
||||
|
||||
|
||||
# Sources
|
||||
## Sources / Temporal Shapes
|
||||
GaussianPulseTemporalShape = enum.auto()
|
||||
ContinuousWaveTemporalShape = enum.auto()
|
||||
ListTemporalShape = enum.auto()
|
||||
|
||||
## Sources /
|
||||
PointDipoleSource = enum.auto()
|
||||
UniformCurrentSource = enum.auto()
|
||||
PlaneWaveSource = enum.auto()
|
||||
ModeSource = enum.auto()
|
||||
GaussianBeamSource = enum.auto()
|
||||
AstigmaticGaussianBeamSource = enum.auto()
|
||||
TFSFSource = enum.auto()
|
||||
|
||||
EHEquivalenceSource = enum.auto()
|
||||
EHSource = enum.auto()
|
||||
|
||||
# Mediums
|
||||
LibraryMedium = enum.auto()
|
||||
|
||||
PECMedium = enum.auto()
|
||||
IsotropicMedium = enum.auto()
|
||||
AnisotropicMedium = enum.auto()
|
||||
|
||||
TripleSellmeierMedium = enum.auto()
|
||||
SellmeierMedium = enum.auto()
|
||||
PoleResidueMedium = enum.auto()
|
||||
DrudeMedium = enum.auto()
|
||||
DrudeLorentzMedium = enum.auto()
|
||||
DebyeMedium = enum.auto()
|
||||
|
||||
## Mediums / Non-Linearities
|
||||
AddNonLinearity = enum.auto()
|
||||
ChiThreeSusceptibilityNonLinearity = enum.auto()
|
||||
TwoPhotonAbsorptionNonLinearity = enum.auto()
|
||||
KerrNonLinearity = enum.auto()
|
||||
|
||||
# Structures
|
||||
ObjectStructure = enum.auto()
|
||||
GeoNodesStructure = enum.auto()
|
||||
ScriptedStructure = enum.auto()
|
||||
|
||||
## Structures / Primitives
|
||||
BoxStructure = enum.auto()
|
||||
SphereStructure = enum.auto()
|
||||
CylinderStructure = enum.auto()
|
||||
|
||||
|
||||
# Bounds
|
||||
BoundBox = enum.auto()
|
||||
|
||||
## Bounds / Bound Faces
|
||||
PMLBoundFace = enum.auto()
|
||||
PECBoundFace = enum.auto()
|
||||
PMCBoundFace = enum.auto()
|
||||
|
||||
BlochBoundFace = enum.auto()
|
||||
PeriodicBoundFace = enum.auto()
|
||||
AbsorbingBoundFace = enum.auto()
|
||||
|
||||
|
||||
# Monitors
|
||||
EHFieldMonitor = enum.auto()
|
||||
FieldPowerFluxMonitor = enum.auto()
|
||||
EpsilonTensorMonitor = enum.auto()
|
||||
DiffractionMonitor = enum.auto()
|
||||
|
||||
## Monitors / Near-Field Projections
|
||||
CartesianNearFieldProjectionMonitor = enum.auto()
|
||||
ObservationAngleNearFieldProjectionMonitor = enum.auto()
|
||||
KSpaceNearFieldProjectionMonitor = enum.auto()
|
||||
|
||||
|
||||
# Sims
|
||||
SimDomain = enum.auto()
|
||||
SimGrid = enum.auto()
|
||||
|
||||
## Sims / Sim Grid Axis
|
||||
AutomaticSimGridAxis = enum.auto()
|
||||
ManualSimGridAxis = enum.auto()
|
||||
UniformSimGridAxis = enum.auto()
|
||||
ArraySimGridAxis = enum.auto()
|
||||
|
||||
## Sim /
|
||||
FDTDSim = enum.auto()
|
||||
|
||||
|
||||
# Utilities
|
||||
Combine = enum.auto()
|
||||
Separate = enum.auto()
|
||||
Math = enum.auto()
|
||||
|
||||
## Utilities / Converters
|
||||
WaveConverter = enum.auto()
|
||||
|
||||
## Utilities / Operations
|
||||
ArrayOperation = enum.auto()
|
|
@ -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
|
|
@ -0,0 +1,31 @@
|
|||
import typing as typ
|
||||
import typing as typx
|
||||
|
||||
import pydantic as pyd
|
||||
|
||||
from ..bl import ManagedObjName, SocketName
|
||||
from ..managed_obj_type import ManagedObjType
|
||||
|
||||
class ManagedObj(typ.Protocol):
|
||||
managed_obj_type: ManagedObjType
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: ManagedObjName,
|
||||
):
|
||||
...
|
||||
|
||||
@property
|
||||
def name(self) -> str: ...
|
||||
@name.setter
|
||||
def name(self, value: str): ...
|
||||
|
||||
def free(self):
|
||||
...
|
||||
|
||||
def bl_select(self):
|
||||
"""If this is a managed Blender object, and the operation "select this in Blender" makes sense, then do so.
|
||||
|
||||
Else, do nothing.
|
||||
"""
|
||||
pass
|
|
@ -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 = ""
|
|
@ -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):
|
||||
|
|
@ -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]
|
|
@ -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:
|
||||
...
|
|
@ -0,0 +1,119 @@
|
|||
import sympy.physics.units as spu
|
||||
from ....utils import extra_sympy_units as spuex
|
||||
|
||||
from .socket_types import SocketType as ST
|
||||
|
||||
Dimensions = int ## Num. Elements in the Socket
|
||||
BLSocketType = str ## A Blender-Defined Socket Type
|
||||
|
||||
class BLSocketToSocket:
|
||||
"""Encodes ways of converting blender sockets of known dimensionality
|
||||
to a corresponding SocketType.
|
||||
|
||||
"Dimensionality" is simply how many elements the blender socket has.
|
||||
The user must explicitly specify this, as blender allows a variable
|
||||
number of elements for some sockets, and we do not.
|
||||
"""
|
||||
####################
|
||||
# - Direct BLSocketType -> SocketType
|
||||
####################
|
||||
by_bl_socket_type: dict[Dimensions, dict[BLSocketType, ST]] = {
|
||||
1: {
|
||||
"NodeSocketStandard": ST.Any,
|
||||
"NodeSocketVirtual": ST.Any,
|
||||
"NodeSocketGeometry": ST.Any,
|
||||
"NodeSocketTexture": ST.Any,
|
||||
"NodeSocketShader": ST.Any,
|
||||
"NodeSocketMaterial": ST.Any,
|
||||
|
||||
"NodeSocketString": ST.Text,
|
||||
"NodeSocketBool": ST.Bool,
|
||||
"NodeSocketCollection": ST.BlenderCollection,
|
||||
"NodeSocketImage": ST.BlenderImage,
|
||||
"NodeSocketObject": ST.BlenderObject,
|
||||
|
||||
"NodeSocketFloat": ST.RealNumber,
|
||||
"NodeSocketFloatAngle": ST.PhysicalAngle,
|
||||
"NodeSocketFloatDistance": ST.PhysicalLength,
|
||||
"NodeSocketFloatFactor": ST.RealNumber,
|
||||
"NodeSocketFloatPercentage": ST.RealNumber,
|
||||
"NodeSocketFloatTime": ST.PhysicalTime,
|
||||
"NodeSocketFloatTimeAbsolute": ST.RealNumber,
|
||||
"NodeSocketFloatUnsigned": ST.RealNumber,
|
||||
|
||||
"NodeSocketInt": ST.IntegerNumber,
|
||||
"NodeSocketIntFactor": ST.IntegerNumber,
|
||||
"NodeSocketIntPercentage": ST.IntegerNumber,
|
||||
"NodeSocketIntUnsigned": ST.IntegerNumber,
|
||||
},
|
||||
2: {
|
||||
"NodeSocketVector": ST.Real3DVector,
|
||||
"NodeSocketVectorAcceleration": ST.Real3DVector,
|
||||
"NodeSocketVectorDirection": ST.Real3DVector,
|
||||
"NodeSocketVectorEuler": ST.Real3DVector,
|
||||
"NodeSocketVectorTranslation": ST.Real3DVector,
|
||||
"NodeSocketVectorVelocity": ST.Real3DVector,
|
||||
"NodeSocketVectorXYZ": ST.Real3DVector,
|
||||
#"NodeSocketVector": ST.Real2DVector,
|
||||
#"NodeSocketVectorAcceleration": ST.PhysicalAccel2D,
|
||||
#"NodeSocketVectorDirection": ST.PhysicalDir2D,
|
||||
#"NodeSocketVectorEuler": ST.PhysicalEuler2D,
|
||||
#"NodeSocketVectorTranslation": ST.PhysicalDispl2D,
|
||||
#"NodeSocketVectorVelocity": ST.PhysicalVel2D,
|
||||
#"NodeSocketVectorXYZ": ST.Real2DPoint,
|
||||
},
|
||||
3: {
|
||||
"NodeSocketRotation": ST.Real3DVector,
|
||||
|
||||
"NodeSocketColor": ST.Any,
|
||||
|
||||
"NodeSocketVector": ST.Real3DVector,
|
||||
#"NodeSocketVectorAcceleration": ST.PhysicalAccel3D,
|
||||
#"NodeSocketVectorDirection": ST.PhysicalDir3D,
|
||||
#"NodeSocketVectorEuler": ST.PhysicalEuler3D,
|
||||
#"NodeSocketVectorTranslation": ST.PhysicalDispl3D,
|
||||
"NodeSocketVectorTranslation": ST.PhysicalPoint3D,
|
||||
#"NodeSocketVectorVelocity": ST.PhysicalVel3D,
|
||||
"NodeSocketVectorXYZ": ST.PhysicalPoint3D,
|
||||
},
|
||||
}
|
||||
|
||||
####################
|
||||
# - BLSocket Description-Driven SocketType Choice
|
||||
####################
|
||||
by_description = {
|
||||
1: {
|
||||
"Angle": ST.PhysicalAngle,
|
||||
|
||||
"Length": ST.PhysicalLength,
|
||||
"Area": ST.PhysicalArea,
|
||||
"Volume": ST.PhysicalVolume,
|
||||
|
||||
"Mass": ST.PhysicalMass,
|
||||
|
||||
"Speed": ST.PhysicalSpeed,
|
||||
"Accel": ST.PhysicalAccelScalar,
|
||||
"Force": ST.PhysicalForceScalar,
|
||||
|
||||
"Freq": ST.PhysicalFreq,
|
||||
},
|
||||
2: {
|
||||
#"2DCount": ST.Int2DVector,
|
||||
|
||||
#"2DPoint": ST.PhysicalPoint2D,
|
||||
#"2DSize": ST.PhysicalSize2D,
|
||||
#"2DPol": ST.PhysicalPol,
|
||||
"2DPoint": ST.PhysicalPoint3D,
|
||||
"2DSize": ST.PhysicalSize3D,
|
||||
},
|
||||
3: {
|
||||
#"Count": ST.Int3DVector,
|
||||
|
||||
"Point": ST.PhysicalPoint3D,
|
||||
"Size": ST.PhysicalSize3D,
|
||||
|
||||
#"Force": ST.PhysicalForce3D,
|
||||
|
||||
"Freq": ST.PhysicalSize3D,
|
||||
},
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
import sympy.physics.units as spu
|
||||
from ....utils import extra_sympy_units as spuex
|
||||
|
||||
from .socket_types import SocketType as ST
|
||||
|
||||
## TODO: Don't just presume sRGB.
|
||||
SOCKET_COLORS = {
|
||||
# Basic
|
||||
ST.Any: (0.8, 0.8, 0.8, 1.0), # Light Grey
|
||||
ST.Bool: (0.7, 0.7, 0.7, 1.0), # Medium Light Grey
|
||||
ST.Text: (0.7, 0.7, 0.7, 1.0), # Medium Light Grey
|
||||
ST.FilePath: (0.6, 0.6, 0.6, 1.0), # Medium Grey
|
||||
ST.Secret: (0.0, 0.0, 0.0, 1.0), # Black
|
||||
|
||||
# Number
|
||||
ST.IntegerNumber: (0.5, 0.5, 1.0, 1.0), # Light Blue
|
||||
ST.RationalNumber: (0.4, 0.4, 0.9, 1.0), # Medium Light Blue
|
||||
ST.RealNumber: (0.3, 0.3, 0.8, 1.0), # Medium Blue
|
||||
ST.ComplexNumber: (0.2, 0.2, 0.7, 1.0), # Dark Blue
|
||||
|
||||
# Vector
|
||||
ST.Real2DVector: (0.5, 1.0, 0.5, 1.0), # Light Green
|
||||
ST.Complex2DVector: (0.4, 0.9, 0.4, 1.0), # Medium Light Green
|
||||
ST.Real3DVector: (0.3, 0.8, 0.3, 1.0), # Medium Green
|
||||
ST.Complex3DVector: (0.2, 0.7, 0.2, 1.0), # Dark Green
|
||||
|
||||
# Physical
|
||||
ST.PhysicalUnitSystem: (1.0, 0.5, 0.5, 1.0), # Light Red
|
||||
ST.PhysicalTime: (1.0, 0.5, 0.5, 1.0), # Light Red
|
||||
ST.PhysicalAngle: (0.9, 0.45, 0.45, 1.0), # Medium Light Red
|
||||
ST.PhysicalLength: (0.8, 0.4, 0.4, 1.0), # Medium Red
|
||||
ST.PhysicalArea: (0.7, 0.35, 0.35, 1.0), # Medium Dark Red
|
||||
ST.PhysicalVolume: (0.6, 0.3, 0.3, 1.0), # Dark Red
|
||||
ST.PhysicalPoint2D: (0.7, 0.35, 0.35, 1.0), # Medium Dark Red
|
||||
ST.PhysicalPoint3D: (0.6, 0.3, 0.3, 1.0), # Dark Red
|
||||
ST.PhysicalSize2D: (0.7, 0.35, 0.35, 1.0), # Medium Dark Red
|
||||
ST.PhysicalSize3D: (0.6, 0.3, 0.3, 1.0), # Dark Red
|
||||
ST.PhysicalMass: (0.9, 0.6, 0.4, 1.0), # Light Orange
|
||||
ST.PhysicalSpeed: (0.8, 0.55, 0.35, 1.0), # Medium Light Orange
|
||||
ST.PhysicalAccelScalar: (0.7, 0.5, 0.3, 1.0), # Medium Orange
|
||||
ST.PhysicalForceScalar: (0.6, 0.45, 0.25, 1.0), # Medium Dark Orange
|
||||
ST.PhysicalAccel3DVector: (0.7, 0.5, 0.3, 1.0), # Medium Orange
|
||||
ST.PhysicalForce3DVector: (0.6, 0.45, 0.25, 1.0), # Medium Dark Orange
|
||||
ST.PhysicalPol: (0.5, 0.4, 0.2, 1.0), # Dark Orange
|
||||
ST.PhysicalFreq: (1.0, 0.7, 0.5, 1.0), # Light Peach
|
||||
|
||||
# Blender
|
||||
ST.BlenderObject: (0.7, 0.5, 1.0, 1.0), # Light Purple
|
||||
ST.BlenderCollection: (0.6, 0.45, 0.9, 1.0), # Medium Light Purple
|
||||
ST.BlenderImage: (0.5, 0.4, 0.8, 1.0), # Medium Purple
|
||||
ST.BlenderVolume: (0.4, 0.35, 0.7, 1.0), # Medium Dark Purple
|
||||
ST.BlenderGeoNodes: (0.3, 0.3, 0.6, 1.0), # Dark Purple
|
||||
ST.BlenderText: (0.5, 0.5, 0.75, 1.0), # Light Lavender
|
||||
ST.BlenderPreviewTarget: (0.5, 0.5, 0.75, 1.0), # Light Lavender
|
||||
|
||||
# Maxwell
|
||||
ST.MaxwellSource: (1.0, 1.0, 0.5, 1.0), # Light Yellow
|
||||
ST.MaxwellTemporalShape: (0.9, 0.9, 0.45, 1.0), # Medium Light Yellow
|
||||
ST.MaxwellMedium: (0.8, 0.8, 0.4, 1.0), # Medium Yellow
|
||||
ST.MaxwellMediumNonLinearity: (0.7, 0.7, 0.35, 1.0), # Medium Dark Yellow
|
||||
ST.MaxwellStructure: (0.6, 0.6, 0.3, 1.0), # Dark Yellow
|
||||
ST.MaxwellBoundBox: (0.9, 0.8, 0.5, 1.0), # Light Gold
|
||||
ST.MaxwellBoundFace: (0.8, 0.7, 0.45, 1.0), # Medium Light Gold
|
||||
ST.MaxwellMonitor: (0.7, 0.6, 0.4, 1.0), # Medium Gold
|
||||
ST.MaxwellFDTDSim: (0.6, 0.5, 0.35, 1.0), # Medium Dark Gold
|
||||
ST.MaxwellSimGrid: (0.5, 0.4, 0.3, 1.0), # Dark Gold
|
||||
ST.MaxwellSimGridAxis: (0.4, 0.3, 0.25, 1.0), # Darkest Gold
|
||||
ST.MaxwellSimDomain: (0.4, 0.3, 0.25, 1.0), # Darkest Gold
|
||||
|
||||
# Tidy3D
|
||||
ST.Tidy3DCloudTask: (0.4, 0.3, 0.25, 1.0), # Darkest Gold
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
from .socket_types import SocketType as ST
|
||||
|
||||
SOCKET_SHAPES = {
|
||||
# Basic
|
||||
ST.Any: "CIRCLE",
|
||||
ST.Bool: "CIRCLE",
|
||||
ST.Text: "SQUARE",
|
||||
ST.FilePath: "SQUARE",
|
||||
ST.Secret: "SQUARE",
|
||||
|
||||
# Number
|
||||
ST.IntegerNumber: "CIRCLE",
|
||||
ST.RationalNumber: "CIRCLE",
|
||||
ST.RealNumber: "CIRCLE",
|
||||
ST.ComplexNumber: "CIRCLE_DOT",
|
||||
|
||||
# Vector
|
||||
ST.Real2DVector: "SQUARE_DOT",
|
||||
ST.Complex2DVector: "DIAMOND_DOT",
|
||||
ST.Real3DVector: "SQUARE_DOT",
|
||||
ST.Complex3DVector: "DIAMOND_DOT",
|
||||
|
||||
# Physical
|
||||
ST.PhysicalUnitSystem: "CIRCLE",
|
||||
ST.PhysicalTime: "CIRCLE",
|
||||
ST.PhysicalAngle: "DIAMOND",
|
||||
ST.PhysicalLength: "SQUARE",
|
||||
ST.PhysicalArea: "SQUARE",
|
||||
ST.PhysicalVolume: "SQUARE",
|
||||
ST.PhysicalPoint2D: "DIAMOND",
|
||||
ST.PhysicalPoint3D: "DIAMOND",
|
||||
ST.PhysicalSize2D: "SQUARE",
|
||||
ST.PhysicalSize3D: "SQUARE",
|
||||
ST.PhysicalMass: "CIRCLE",
|
||||
ST.PhysicalSpeed: "CIRCLE",
|
||||
ST.PhysicalAccelScalar: "CIRCLE",
|
||||
ST.PhysicalForceScalar: "CIRCLE",
|
||||
ST.PhysicalAccel3DVector: "SQUARE_DOT",
|
||||
ST.PhysicalForce3DVector: "SQUARE_DOT",
|
||||
ST.PhysicalPol: "DIAMOND",
|
||||
ST.PhysicalFreq: "CIRCLE",
|
||||
|
||||
# Blender
|
||||
ST.BlenderObject: "SQUARE",
|
||||
ST.BlenderCollection: "SQUARE",
|
||||
ST.BlenderImage: "DIAMOND",
|
||||
ST.BlenderVolume: "DIAMOND",
|
||||
ST.BlenderGeoNodes: "DIAMOND",
|
||||
ST.BlenderText: "SQUARE",
|
||||
ST.BlenderPreviewTarget: "SQUARE",
|
||||
|
||||
# Maxwell
|
||||
ST.MaxwellSource: "CIRCLE",
|
||||
ST.MaxwellTemporalShape: "CIRCLE",
|
||||
ST.MaxwellMedium: "CIRCLE",
|
||||
ST.MaxwellMediumNonLinearity: "CIRCLE",
|
||||
ST.MaxwellStructure: "SQUARE",
|
||||
ST.MaxwellBoundBox: "SQUARE",
|
||||
ST.MaxwellBoundFace: "DIAMOND",
|
||||
ST.MaxwellMonitor: "CIRCLE",
|
||||
ST.MaxwellFDTDSim: "SQUARE",
|
||||
ST.MaxwellSimGrid: "SQUARE",
|
||||
ST.MaxwellSimGridAxis: "DIAMOND",
|
||||
ST.MaxwellSimDomain: "SQUARE",
|
||||
|
||||
# Tidy3D
|
||||
ST.Tidy3DCloudTask: "CIRCLE",
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
import enum
|
||||
|
||||
from ....utils.blender_type_enum import (
|
||||
BlenderTypeEnum, append_cls_name_to_values, wrap_values_in_MT
|
||||
)
|
||||
|
||||
@append_cls_name_to_values
|
||||
class SocketType(BlenderTypeEnum):
|
||||
# Base
|
||||
Any = enum.auto()
|
||||
Bool = enum.auto()
|
||||
Text = enum.auto()
|
||||
FilePath = enum.auto()
|
||||
Secret = enum.auto()
|
||||
|
||||
# Number
|
||||
IntegerNumber = enum.auto()
|
||||
RationalNumber = enum.auto()
|
||||
RealNumber = enum.auto()
|
||||
ComplexNumber = enum.auto()
|
||||
|
||||
# Vector
|
||||
Real2DVector = enum.auto()
|
||||
Complex2DVector = enum.auto()
|
||||
|
||||
Real3DVector = enum.auto()
|
||||
Complex3DVector = enum.auto()
|
||||
|
||||
# Physical
|
||||
PhysicalUnitSystem = enum.auto()
|
||||
PhysicalTime = enum.auto()
|
||||
|
||||
PhysicalAngle = enum.auto()
|
||||
|
||||
PhysicalLength = enum.auto()
|
||||
PhysicalArea = enum.auto()
|
||||
PhysicalVolume = enum.auto()
|
||||
|
||||
PhysicalPoint2D = enum.auto()
|
||||
PhysicalPoint3D = enum.auto()
|
||||
|
||||
PhysicalSize2D = enum.auto()
|
||||
PhysicalSize3D = enum.auto()
|
||||
|
||||
PhysicalMass = enum.auto()
|
||||
|
||||
PhysicalSpeed = enum.auto()
|
||||
PhysicalAccelScalar = enum.auto()
|
||||
PhysicalForceScalar = enum.auto()
|
||||
PhysicalAccel3DVector = enum.auto()
|
||||
PhysicalForce3DVector = enum.auto()
|
||||
|
||||
PhysicalPol = enum.auto()
|
||||
|
||||
PhysicalFreq = enum.auto()
|
||||
|
||||
# Blender
|
||||
BlenderObject = enum.auto()
|
||||
BlenderCollection = enum.auto()
|
||||
|
||||
BlenderImage = enum.auto()
|
||||
BlenderVolume = enum.auto()
|
||||
|
||||
BlenderGeoNodes = enum.auto()
|
||||
BlenderText = enum.auto()
|
||||
|
||||
BlenderPreviewTarget = enum.auto()
|
||||
|
||||
# Maxwell
|
||||
MaxwellSource = enum.auto()
|
||||
MaxwellTemporalShape = enum.auto()
|
||||
|
||||
MaxwellMedium = enum.auto()
|
||||
MaxwellMediumNonLinearity = enum.auto()
|
||||
|
||||
MaxwellStructure = enum.auto()
|
||||
|
||||
MaxwellBoundBox = enum.auto()
|
||||
MaxwellBoundFace = enum.auto()
|
||||
|
||||
MaxwellMonitor = enum.auto()
|
||||
|
||||
MaxwellFDTDSim = enum.auto()
|
||||
MaxwellSimGrid = enum.auto()
|
||||
MaxwellSimGridAxis = enum.auto()
|
||||
MaxwellSimDomain = enum.auto()
|
||||
|
||||
# Tidy3D
|
||||
Tidy3DCloudTask = enum.auto()
|
|
@ -0,0 +1,265 @@
|
|||
import sympy.physics.units as spu
|
||||
from ....utils import extra_sympy_units as spuex
|
||||
|
||||
from .socket_types import SocketType as ST
|
||||
|
||||
SOCKET_UNITS = {
|
||||
ST.PhysicalTime: {
|
||||
"default": "PS",
|
||||
"values": {
|
||||
"PS": spu.picosecond,
|
||||
"NS": spu.nanosecond,
|
||||
"MS": spu.microsecond,
|
||||
"MLSEC": spu.millisecond,
|
||||
"SEC": spu.second,
|
||||
"MIN": spu.minute,
|
||||
"HOUR": spu.hour,
|
||||
"DAY": spu.day,
|
||||
},
|
||||
},
|
||||
|
||||
ST.PhysicalAngle: {
|
||||
"default": "RADIAN",
|
||||
"values": {
|
||||
"RADIAN": spu.radian,
|
||||
"DEGREE": spu.degree,
|
||||
"STERAD": spu.steradian,
|
||||
"ANGMIL": spu.angular_mil,
|
||||
},
|
||||
},
|
||||
|
||||
ST.PhysicalLength: {
|
||||
"default": "UM",
|
||||
"values": {
|
||||
"PM": spu.picometer,
|
||||
"A": spu.angstrom,
|
||||
"NM": spu.nanometer,
|
||||
"UM": spu.micrometer,
|
||||
"MM": spu.millimeter,
|
||||
"CM": spu.centimeter,
|
||||
"M": spu.meter,
|
||||
"INCH": spu.inch,
|
||||
"FOOT": spu.foot,
|
||||
"YARD": spu.yard,
|
||||
"MILE": spu.mile,
|
||||
},
|
||||
},
|
||||
ST.PhysicalArea: {
|
||||
"default": "UM_SQ",
|
||||
"values": {
|
||||
"PM_SQ": spu.picometer**2,
|
||||
"A_SQ": spu.angstrom**2,
|
||||
"NM_SQ": spu.nanometer**2,
|
||||
"UM_SQ": spu.micrometer**2,
|
||||
"MM_SQ": spu.millimeter**2,
|
||||
"CM_SQ": spu.centimeter**2,
|
||||
"M_SQ": spu.meter**2,
|
||||
"INCH_SQ": spu.inch**2,
|
||||
"FOOT_SQ": spu.foot**2,
|
||||
"YARD_SQ": spu.yard**2,
|
||||
"MILE_SQ": spu.mile**2,
|
||||
},
|
||||
},
|
||||
ST.PhysicalVolume: {
|
||||
"default": "UM_CB",
|
||||
"values": {
|
||||
"PM_CB": spu.picometer**3,
|
||||
"A_CB": spu.angstrom**3,
|
||||
"NM_CB": spu.nanometer**3,
|
||||
"UM_CB": spu.micrometer**3,
|
||||
"MM_CB": spu.millimeter**3,
|
||||
"CM_CB": spu.centimeter**3,
|
||||
"M_CB": spu.meter**3,
|
||||
"ML": spu.milliliter,
|
||||
"L": spu.liter,
|
||||
"INCH_CB": spu.inch**3,
|
||||
"FOOT_CB": spu.foot**3,
|
||||
"YARD_CB": spu.yard**3,
|
||||
"MILE_CB": spu.mile**3,
|
||||
},
|
||||
},
|
||||
|
||||
ST.PhysicalPoint2D: {
|
||||
"default": "UM",
|
||||
"values": {
|
||||
"PM": spu.picometer,
|
||||
"A": spu.angstrom,
|
||||
"NM": spu.nanometer,
|
||||
"UM": spu.micrometer,
|
||||
"MM": spu.millimeter,
|
||||
"CM": spu.centimeter,
|
||||
"M": spu.meter,
|
||||
"INCH": spu.inch,
|
||||
"FOOT": spu.foot,
|
||||
"YARD": spu.yard,
|
||||
"MILE": spu.mile,
|
||||
},
|
||||
},
|
||||
ST.PhysicalPoint3D: {
|
||||
"default": "UM",
|
||||
"values": {
|
||||
"PM": spu.picometer,
|
||||
"A": spu.angstrom,
|
||||
"NM": spu.nanometer,
|
||||
"UM": spu.micrometer,
|
||||
"MM": spu.millimeter,
|
||||
"CM": spu.centimeter,
|
||||
"M": spu.meter,
|
||||
"INCH": spu.inch,
|
||||
"FOOT": spu.foot,
|
||||
"YARD": spu.yard,
|
||||
"MILE": spu.mile,
|
||||
},
|
||||
},
|
||||
|
||||
ST.PhysicalSize2D: {
|
||||
"default": "UM",
|
||||
"values": {
|
||||
"PM": spu.picometer,
|
||||
"A": spu.angstrom,
|
||||
"NM": spu.nanometer,
|
||||
"UM": spu.micrometer,
|
||||
"MM": spu.millimeter,
|
||||
"CM": spu.centimeter,
|
||||
"M": spu.meter,
|
||||
"INCH": spu.inch,
|
||||
"FOOT": spu.foot,
|
||||
"YARD": spu.yard,
|
||||
"MILE": spu.mile,
|
||||
},
|
||||
},
|
||||
ST.PhysicalSize3D: {
|
||||
"default": "UM",
|
||||
"values": {
|
||||
"PM": spu.picometer,
|
||||
"A": spu.angstrom,
|
||||
"NM": spu.nanometer,
|
||||
"UM": spu.micrometer,
|
||||
"MM": spu.millimeter,
|
||||
"CM": spu.centimeter,
|
||||
"M": spu.meter,
|
||||
"INCH": spu.inch,
|
||||
"FOOT": spu.foot,
|
||||
"YARD": spu.yard,
|
||||
"MILE": spu.mile,
|
||||
},
|
||||
},
|
||||
|
||||
ST.PhysicalMass: {
|
||||
"default": "UG",
|
||||
"values": {
|
||||
"E_REST": spu.electron_rest_mass,
|
||||
"DAL": spu.dalton,
|
||||
"UG": spu.microgram,
|
||||
"MG": spu.milligram,
|
||||
"G": spu.gram,
|
||||
"KG": spu.kilogram,
|
||||
"TON": spu.metric_ton,
|
||||
},
|
||||
},
|
||||
|
||||
ST.PhysicalSpeed: {
|
||||
"default": "UM_S",
|
||||
"values": {
|
||||
"PM_S": spu.picometer / spu.second,
|
||||
"NM_S": spu.nanometer / spu.second,
|
||||
"UM_S": spu.micrometer / spu.second,
|
||||
"MM_S": spu.millimeter / spu.second,
|
||||
"M_S": spu.meter / spu.second,
|
||||
"KM_S": spu.kilometer / spu.second,
|
||||
"KM_H": spu.kilometer / spu.hour,
|
||||
"FT_S": spu.feet / spu.second,
|
||||
"MI_H": spu.mile / spu.hour,
|
||||
},
|
||||
},
|
||||
ST.PhysicalAccelScalar: {
|
||||
"default": "UM_S_SQ",
|
||||
"values": {
|
||||
"PM_S_SQ": spu.picometer / spu.second**2,
|
||||
"NM_S_SQ": spu.nanometer / spu.second**2,
|
||||
"UM_S_SQ": spu.micrometer / spu.second**2,
|
||||
"MM_S_SQ": spu.millimeter / spu.second**2,
|
||||
"M_S_SQ": spu.meter / spu.second**2,
|
||||
"KM_S_SQ": spu.kilometer / spu.second**2,
|
||||
"FT_S_SQ": spu.feet / spu.second**2,
|
||||
},
|
||||
},
|
||||
ST.PhysicalForceScalar: {
|
||||
"default": "UNEWT",
|
||||
"values": {
|
||||
"KG_M_S_SQ": spu.kg * spu.m/spu.second**2,
|
||||
"NNEWT": spuex.nanonewton,
|
||||
"UNEWT": spuex.micronewton,
|
||||
"MNEWT": spuex.millinewton,
|
||||
"NEWT": spu.newton,
|
||||
},
|
||||
},
|
||||
ST.PhysicalAccel3DVector: {
|
||||
"default": "UM_S_SQ",
|
||||
"values": {
|
||||
"PM_S_SQ": spu.picometer / spu.second**2,
|
||||
"NM_S_SQ": spu.nanometer / spu.second**2,
|
||||
"UM_S_SQ": spu.micrometer / spu.second**2,
|
||||
"MM_S_SQ": spu.millimeter / spu.second**2,
|
||||
"M_S_SQ": spu.meter / spu.second**2,
|
||||
"KM_S_SQ": spu.kilometer / spu.second**2,
|
||||
"FT_S_SQ": spu.feet / spu.second**2,
|
||||
},
|
||||
},
|
||||
ST.PhysicalForce3DVector: {
|
||||
"default": "UNEWT",
|
||||
"values": {
|
||||
"KG_M_S_SQ": spu.kg * spu.m/spu.second**2,
|
||||
"NNEWT": spuex.nanonewton,
|
||||
"UNEWT": spuex.micronewton,
|
||||
"MNEWT": spuex.millinewton,
|
||||
"NEWT": spu.newton,
|
||||
},
|
||||
},
|
||||
|
||||
ST.PhysicalFreq: {
|
||||
"default": "THZ",
|
||||
"values": {
|
||||
"HZ": spu.hertz,
|
||||
"KHZ": spuex.kilohertz,
|
||||
"MHZ": spuex.megahertz,
|
||||
"GHZ": spuex.gigahertz,
|
||||
"THZ": spuex.terahertz,
|
||||
"PHZ": spuex.petahertz,
|
||||
"EHZ": spuex.exahertz,
|
||||
},
|
||||
},
|
||||
ST.PhysicalPol: {
|
||||
"default": "RADIAN",
|
||||
"values": {
|
||||
"RADIAN": spu.radian,
|
||||
"DEGREE": spu.degree,
|
||||
"STERAD": spu.steradian,
|
||||
"ANGMIL": spu.angular_mil,
|
||||
},
|
||||
},
|
||||
ST.MaxwellMedium: {
|
||||
"default": "NM",
|
||||
"values": {
|
||||
"PM": spu.picometer, ## c(vac) = wl*freq
|
||||
"A": spu.angstrom,
|
||||
"NM": spu.nanometer,
|
||||
"UM": spu.micrometer,
|
||||
"MM": spu.millimeter,
|
||||
"CM": spu.centimeter,
|
||||
"M": spu.meter,
|
||||
},
|
||||
},
|
||||
ST.MaxwellMonitor: {
|
||||
"default": "NM",
|
||||
"values": {
|
||||
"PM": spu.picometer, ## c(vac) = wl*freq
|
||||
"A": spu.angstrom,
|
||||
"NM": spu.nanometer,
|
||||
"UM": spu.micrometer,
|
||||
"MM": spu.millimeter,
|
||||
"CM": spu.centimeter,
|
||||
"M": spu.meter,
|
||||
},
|
||||
},
|
||||
}
|
|
@ -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()
|
|
@ -0,0 +1,2 @@
|
|||
from .managed_bl_image import ManagedBLImage
|
||||
from .managed_bl_object import ManagedBLObject
|
|
@ -0,0 +1,190 @@
|
|||
import typing as typ
|
||||
import typing_extensions as typx
|
||||
import io
|
||||
|
||||
import numpy as np
|
||||
import pydantic as pyd
|
||||
import matplotlib.axis as mpl_ax
|
||||
|
||||
import bpy
|
||||
|
||||
from .. import contracts as ct
|
||||
|
||||
AREA_TYPE = "IMAGE_EDITOR"
|
||||
SPACE_TYPE = "IMAGE_EDITOR"
|
||||
|
||||
class ManagedBLImage(ct.schemas.ManagedObj):
|
||||
managed_obj_type = ct.ManagedObjType.ManagedBLImage
|
||||
_bl_image_name: str
|
||||
|
||||
def __init__(self, name: str):
|
||||
## TODO: Check that blender doesn't have any other images by the same name.
|
||||
self._bl_image_name = name
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._bl_image_name
|
||||
|
||||
@name.setter
|
||||
def name(self, value: str):
|
||||
## TODO: Check that blender doesn't have any other images by the same name.
|
||||
if (bl_image := bpy.data.images.get(self.name)):
|
||||
bl_image.name = value
|
||||
|
||||
self._bl_image_name = value
|
||||
|
||||
def free(self):
|
||||
if (bl_image := bpy.data.images.get(self.name)):
|
||||
bpy.data.images.remove(bl_image)
|
||||
|
||||
####################
|
||||
# - Managed Object Management
|
||||
####################
|
||||
def bl_image(
|
||||
self,
|
||||
width_px: int,
|
||||
height_px: int,
|
||||
color_model: typx.Literal["RGB", "RGBA"],
|
||||
dtype: typx.Literal["uint8", "float32"],
|
||||
):
|
||||
"""Returns the managed blender image.
|
||||
|
||||
If the requested image properties are different from the image's, then delete the old image make a new image with correct geometry.
|
||||
"""
|
||||
channels = 4 if color_model == "RGBA" else 3
|
||||
|
||||
# Remove Image (if mismatch)
|
||||
if (
|
||||
(bl_image := bpy.data.images.get(self.name))
|
||||
and (
|
||||
bl_image.size[0] != width_px
|
||||
or bl_image.size[1] != height_px
|
||||
or bl_image.channels != channels
|
||||
or bl_image.is_float ^ (dtype == "float32")
|
||||
)
|
||||
):
|
||||
self.free()
|
||||
|
||||
# Create Image w/Geometry (if none exists)
|
||||
if not (bl_image := bpy.data.images.get(self.name)):
|
||||
bl_image = bpy.data.images.new(
|
||||
self.name,
|
||||
width=width_px,
|
||||
height=height_px,
|
||||
)
|
||||
|
||||
return bl_image
|
||||
|
||||
####################
|
||||
# - Editor UX Manipulation
|
||||
####################
|
||||
@property
|
||||
def preview_area(self) -> bpy.types.Area:
|
||||
"""Returns the visible preview area in the Blender UI.
|
||||
If none are valid, return None.
|
||||
"""
|
||||
valid_areas = [
|
||||
area
|
||||
for area in bpy.context.screen.areas
|
||||
if area.type == AREA_TYPE
|
||||
]
|
||||
if valid_areas:
|
||||
return valid_areas[0]
|
||||
|
||||
@property
|
||||
def preview_space(self) -> bpy.types.SpaceProperties:
|
||||
"""Returns the visible preview space in the visible preview area of
|
||||
the Blender UI
|
||||
"""
|
||||
if (preview_area := self.preview_area):
|
||||
return next(
|
||||
space
|
||||
for space in preview_area.spaces
|
||||
if space.type == SPACE_TYPE
|
||||
)
|
||||
|
||||
####################
|
||||
# - Actions
|
||||
####################
|
||||
def bl_select(self) -> None:
|
||||
"""Synchronizes the managed object to the preview, by manipulating
|
||||
relevant editors.
|
||||
"""
|
||||
if (bl_image := bpy.data.images.get(self.name)):
|
||||
self.preview_space.image = bl_image
|
||||
|
||||
####################
|
||||
# - Special Methods
|
||||
####################
|
||||
def mpl_plot_to_image(
|
||||
self,
|
||||
func_plotter: typ.Callable[[mpl_ax.Axis], None],
|
||||
width_inches: float | None = None,
|
||||
height_inches: float | None = None,
|
||||
dpi: int | None = None,
|
||||
bl_select: bool = False,
|
||||
):
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
# Compute Image Geometry
|
||||
if (preview_area := self.preview_area):
|
||||
# Retrieve DPI from Blender Preferences
|
||||
_dpi = bpy.context.preferences.system.dpi
|
||||
|
||||
# Retrieve Image Geometry from Area
|
||||
width_px = preview_area.width
|
||||
height_px = preview_area.height
|
||||
|
||||
# Compute Inches
|
||||
_width_inches = width_px / _dpi
|
||||
_height_inches = height_px / _dpi
|
||||
|
||||
elif width_inches and height_inches and dpi:
|
||||
# Copy Parameters
|
||||
_dpi = dpi
|
||||
_width_inches = height_inches
|
||||
_height_inches = height_inches
|
||||
|
||||
# Compute Pixel Geometry
|
||||
width_px = int(_width_inches * _dpi)
|
||||
height_px = int(_height_inches * _dpi)
|
||||
|
||||
else:
|
||||
msg = f"There must either be a preview area, or defined `width_inches`, `height_inches`, and `dpi`"
|
||||
raise ValueError(msg)
|
||||
|
||||
# Compute Plot Dimensions
|
||||
aspect_ratio = _width_inches / _height_inches
|
||||
|
||||
# Create MPL Figure, Axes, and Compute Figure Geometry
|
||||
fig, ax = plt.subplots(
|
||||
figsize=[_width_inches, _height_inches],
|
||||
dpi=_dpi,
|
||||
)
|
||||
ax.set_aspect(aspect_ratio)
|
||||
cmp_width_px, cmp_height_px = fig.canvas.get_width_height()
|
||||
## Use computed pixel w/h to preempt off-by-one size errors.
|
||||
|
||||
# Plot w/User Parameter
|
||||
func_plotter(ax)
|
||||
|
||||
# Save Figure to BytesIO
|
||||
with io.BytesIO() as buff:
|
||||
fig.savefig(buff, format='raw', dpi=dpi)
|
||||
buff.seek(0)
|
||||
image_data = np.frombuffer(
|
||||
buff.getvalue(),
|
||||
dtype=np.uint8,
|
||||
).reshape([cmp_height_px, cmp_width_px, -1])
|
||||
|
||||
image_data = np.flipud(image_data).astype(np.float32) / 255
|
||||
plt.close(fig)
|
||||
|
||||
# Optimized Write to Blender Image
|
||||
bl_image = self.bl_image(cmp_width_px, cmp_height_px, "RGBA", "uint8")
|
||||
bl_image.pixels.foreach_set(image_data.ravel())
|
||||
bl_image.update()
|
||||
|
||||
if bl_select:
|
||||
self.bl_select()
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
import typing as typ
|
||||
import typing_extensions as typx
|
||||
import functools
|
||||
import contextlib
|
||||
import io
|
||||
|
||||
import numpy as np
|
||||
import pydantic as pyd
|
||||
import matplotlib.axis as mpl_ax
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
|
||||
from .. import contracts as ct
|
||||
|
||||
class ManagedBLObject(ct.schemas.ManagedObj):
|
||||
managed_obj_type = ct.ManagedObjType.ManagedBLObject
|
||||
_bl_object_name: str
|
||||
|
||||
def __init__(self, name: str):
|
||||
## TODO: Check that blender doesn't have any other objects by the same name.
|
||||
self._bl_object_name = name
|
||||
|
||||
# Object Name
|
||||
@property
|
||||
def bl_object_name(self):
|
||||
return self._bl_object_name
|
||||
|
||||
@bl_object_name.setter
|
||||
def set_bl_object_name(self, value: str):
|
||||
## TODO: Check that blender doesn't have any other objects by the same name.
|
||||
if (bl_object := bpy.data.objects.get(self.bl_object_name)):
|
||||
bl_object.name = value
|
||||
|
||||
self._bl_object_name = value
|
||||
|
||||
# Object Datablock Name
|
||||
@property
|
||||
def bl_mesh_name(self):
|
||||
return self.bl_object_name + "Mesh"
|
||||
|
||||
@property
|
||||
def bl_volume_name(self):
|
||||
return self.bl_object_name + "Volume"
|
||||
|
||||
# Deallocation
|
||||
def free(self):
|
||||
if (bl_object := bpy.data.objects.get(self.bl_object_name)):
|
||||
# Delete the Underlying Datablock
|
||||
if bl_object.type == "MESH":
|
||||
bpy.data.meshes.remove(bl_object.data)
|
||||
elif bl_object.type == "VOLUME":
|
||||
bpy.data.volumes.remove(bl_object.data)
|
||||
else:
|
||||
msg = f"Type of to-delete `bl_object`, {bl_object.type}, is not valid"
|
||||
raise ValueError(msg)
|
||||
|
||||
####################
|
||||
# - Actions
|
||||
####################
|
||||
def trigger_action(
|
||||
self,
|
||||
action: typx.Literal["report", "enable_previews"],
|
||||
):
|
||||
if action == "report":
|
||||
pass ## TODO: Cache invalidation.
|
||||
|
||||
if action == "enable_previews":
|
||||
|
||||
pass ## Image "previews" don't need enabling.
|
||||
|
||||
def bl_select(self) -> None:
|
||||
"""Selects the managed Blender object globally, causing it to be ex.
|
||||
outlined in the 3D viewport.
|
||||
"""
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
bpy.data.objects['Suzanne'].select_set(True)
|
||||
|
||||
####################
|
||||
# - Managed Object Management
|
||||
####################
|
||||
def bl_object(
|
||||
self,
|
||||
kind: typx.Literal["MESH", "VOLUME"],
|
||||
):
|
||||
"""Returns the managed blender object.
|
||||
|
||||
If the requested object data type is different, then delete the old
|
||||
object and recreate.
|
||||
"""
|
||||
# Remove Object (if mismatch)
|
||||
if (
|
||||
(bl_object := bpy.data.images.get(self.bl_object_name))
|
||||
and bl_object.type != kind
|
||||
):
|
||||
self.free()
|
||||
|
||||
# Create Object w/Appropriate Data Block
|
||||
if not (bl_object := bpy.data.images.get(self.bl_object_name)):
|
||||
if bl_object.type == "MESH":
|
||||
bl_data = bpy.data.meshes.new(self.bl_mesh_name)
|
||||
elif bl_object.type == "VOLUME":
|
||||
raise NotImplementedError
|
||||
else:
|
||||
msg = f"Requested `bl_object` type {bl_object.type} is not valid"
|
||||
raise ValueError(msg)
|
||||
|
||||
bl_object = bpy.data.objects.new(self.bl_object_name, bl_data)
|
||||
|
||||
return bl_object
|
||||
|
||||
####################
|
||||
# - Data Properties
|
||||
####################
|
||||
@property
|
||||
def raw_mesh(self) -> bpy.types.Mesh:
|
||||
"""Returns the object's raw mesh data.
|
||||
|
||||
Raises an error if the object has no mesh data.
|
||||
"""
|
||||
if (
|
||||
(bl_object := bpy.data.objects.get(self.bl_object_name))
|
||||
and bl_object.type == "MESH"
|
||||
):
|
||||
return bl_object.data
|
||||
|
||||
msg = f"Requested MESH data from `bl_object` of type {bl_object.type}"
|
||||
raise ValueError(msg)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def as_bmesh(
|
||||
self,
|
||||
evaluate: bool = True,
|
||||
triangulate: bool = False,
|
||||
) -> bpy.types.Mesh:
|
||||
if (
|
||||
(bl_object := bpy.data.objects.get(self.bl_object_name))
|
||||
and bl_object.type == "MESH"
|
||||
):
|
||||
bmesh_mesh = None
|
||||
try:
|
||||
bmesh_mesh = bmesh.new()
|
||||
if evaluate:
|
||||
bmesh_mesh.from_object(
|
||||
bl_object,
|
||||
bpy.context.evaluated_depsgraph_get(),
|
||||
)
|
||||
else:
|
||||
bmesh_mesh.from_object(bl_object)
|
||||
|
||||
if triangulate:
|
||||
bmesh.ops.triangulate(bmesh_mesh, faces=bmesh_mesh.faces)
|
||||
|
||||
yield bmesh_mesh
|
||||
|
||||
finally:
|
||||
if bmesh_mesh: bmesh_mesh.free()
|
||||
|
||||
msg = f"Requested BMesh from `bl_object` of type {bl_object.type}"
|
||||
raise ValueError(msg)
|
||||
|
||||
@functools.cached_property
|
||||
def as_arrays(self) -> dict:
|
||||
# Compute Evaluted + Triangulated Mesh
|
||||
_mesh = bpy.data.meshes.new(name="TemporaryMesh")
|
||||
with self.as_bmesh(evaluate=True, triangulate=True) as bmesh_mesh:
|
||||
bmesh_mesh.to_mesh(_mesh)
|
||||
|
||||
# Optimized Vertex Copy
|
||||
## See <https://blog.michelanders.nl/2016/02/copying-vertices-to-numpy-arrays-in_4.html>
|
||||
verts = np.zeros(3 * len(_mesh.vertices), dtype=np.float64)
|
||||
_mesh.vertices.foreach_get('co', verts)
|
||||
verts.shape = (-1, 3)
|
||||
|
||||
# Optimized Triangle Copy
|
||||
## To understand, read it, **carefully**.
|
||||
faces = np.zeros(3 * len(_mesh.polygons), dtype=np.uint64)
|
||||
_mesh.polygons.foreach_get('vertices', faces)
|
||||
faces.shape = (-1, 3)
|
||||
|
||||
# Remove Temporary Mesh
|
||||
bpy.data.meshes.remove(_mesh)
|
||||
|
||||
return {
|
||||
"verts": verts,
|
||||
"faces": faces,
|
||||
}
|
||||
|
||||
#@property
|
||||
#def volume(self) -> bpy.types.Volume:
|
||||
# """Returns the object's volume data.
|
||||
#
|
||||
# Raises an error if the object has no volume data.
|
||||
# """
|
||||
# if (
|
||||
# (bl_object := bpy.data.objects.get(self.bl_object_name))
|
||||
# and bl_object.type == "VOLUME"
|
||||
# ):
|
||||
# return bl_object.data
|
||||
#
|
||||
# msg = f"Requested VOLUME data from `bl_object` of type {bl_object.type}"
|
||||
# raise ValueError(msg)
|
|
@ -1,92 +1,184 @@
|
|||
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 BLENDER_MAXWELL_PT_MaxwellSimTreePanel(bpy.types.Panel):
|
||||
bl_label = "Node Tree Custom Prop"
|
||||
bl_idname = "NODE_PT_custom_prop"
|
||||
bl_space_type = 'NODE_EDITOR'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = 'Item'
|
||||
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 = {}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.space_data.tree_type == contracts.TreeType.MaxwellSim.value
|
||||
# Fill Cache
|
||||
self.regenerate()
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
node_tree = context.space_data.node_tree
|
||||
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)
|
||||
|
||||
layout.prop(node_tree, "preview_collection")
|
||||
layout.prop(node_tree, "non_preview_collection")
|
||||
def regenerate(self) -> DeltaNodeLinkCache:
|
||||
current_link_ptrs_to_links = {
|
||||
link.as_pointer(): link for link in self._node_tree.links
|
||||
}
|
||||
current_link_ptrs = set(current_link_ptrs_to_links.keys())
|
||||
|
||||
# Compute Delta
|
||||
added_link_ptrs = current_link_ptrs - self.link_ptrs
|
||||
removed_link_ptrs = self.link_ptrs - current_link_ptrs
|
||||
|
||||
# Update Caches Incrementally
|
||||
self.remove(removed_link_ptrs)
|
||||
|
||||
self.link_ptrs |= added_link_ptrs
|
||||
for link_ptr in added_link_ptrs:
|
||||
link = current_link_ptrs_to_links[link_ptr]
|
||||
|
||||
self.link_ptrs_to_links[link_ptr] = link
|
||||
self.link_ptrs_from_sockets[link_ptr] = link.from_socket
|
||||
self.link_ptrs_to_sockets[link_ptr] = link.to_socket
|
||||
|
||||
return {"added": added_link_ptrs, "removed": removed_link_ptrs}
|
||||
|
||||
####################
|
||||
# - Node Tree Definition
|
||||
####################
|
||||
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
|
||||
|
||||
managed_collection: bpy.props.PointerProperty(
|
||||
name="Managed Collection",
|
||||
description="Collection of Blender objects managed by this tree",
|
||||
type=bpy.types.Collection,
|
||||
)
|
||||
preview_collection: bpy.props.PointerProperty(
|
||||
name="Preview Collection",
|
||||
description="Collection of Blender objects that will be previewed",
|
||||
type=bpy.types.Collection,
|
||||
update=(lambda self, context: self.trigger_updates())
|
||||
)
|
||||
non_preview_collection: bpy.props.PointerProperty(
|
||||
name="Non-Preview Collection",
|
||||
description="Collection of Blender objects that will NOT be previewed",
|
||||
type=bpy.types.Collection,
|
||||
update=(lambda self, context: self.trigger_updates())
|
||||
)
|
||||
|
||||
def trigger_updates(self):
|
||||
pass
|
||||
####################
|
||||
# - Lock Methods
|
||||
####################
|
||||
def unlock_all(self):
|
||||
for node in self.nodes:
|
||||
node.locked = False
|
||||
for bl_socket in [*node.inputs, *node.outputs]:
|
||||
bl_socket.locked = False
|
||||
|
||||
####################
|
||||
# - Update Methods
|
||||
####################
|
||||
def sync_node_removed(self, node: bpy.types.Node):
|
||||
"""Run by `Node.free()` when a node is being removed.
|
||||
|
||||
Removes node input links from the internal cache (so we don't attempt to update non-existant sockets).
|
||||
"""
|
||||
for bl_socket in node.inputs.values():
|
||||
# Retrieve Socket Links (if any)
|
||||
self._node_link_cache.remove({
|
||||
link.as_pointer()
|
||||
for link in bl_socket.links
|
||||
})
|
||||
## ONLY Input Socket Links are Removed from the NodeLink Cache
|
||||
## - update() handles link-removal from still-existing node just fine.
|
||||
## - update() does NOT handle link-removal of non-existant nodes.
|
||||
|
||||
def update(self):
|
||||
"""Run by Blender when 'something changes' in the node tree.
|
||||
|
||||
Updates an internal node link cache, then updates sockets that just lost/gained an input link.
|
||||
"""
|
||||
if not hasattr(self, "_node_link_cache"):
|
||||
self._node_link_cache = NodeLinkCache(self)
|
||||
## We presume update() is run before the first link is altered.
|
||||
## - Else, the first link of the session will not update caches.
|
||||
## - We remain slightly unsure of the semantics.
|
||||
## - More testing needed to prevent this 'first-link bug'.
|
||||
return
|
||||
|
||||
# Compute Changes to NodeLink Cache
|
||||
delta_links = self._node_link_cache.regenerate()
|
||||
|
||||
link_alterations = {
|
||||
"to_remove": [],
|
||||
"to_add": [],
|
||||
}
|
||||
for link_ptr in delta_links["removed"]:
|
||||
from_socket = self._node_link_cache.link_ptrs_from_sockets[link_ptr]
|
||||
to_socket = self._node_link_cache.link_ptrs_to_sockets[link_ptr]
|
||||
|
||||
# Update Socket Caches
|
||||
self._node_link_cache.link_ptrs_from_sockets.pop(link_ptr, None)
|
||||
self._node_link_cache.link_ptrs_to_sockets.pop(link_ptr, None)
|
||||
|
||||
# Trigger Report Chain on Socket that Just Lost a Link
|
||||
## Aka. Forward-Refresh Caches Relying on Linkage
|
||||
if not (
|
||||
consent_removal := to_socket.sync_link_removed(from_socket)
|
||||
):
|
||||
# Did Not Consent to Removal: Queue Add Link
|
||||
link_alterations["to_add"].append((from_socket, to_socket))
|
||||
|
||||
for link_ptr in delta_links["added"]:
|
||||
link = self._node_link_cache.link_ptrs_to_links.get(link_ptr)
|
||||
if link is None: continue
|
||||
|
||||
# Trigger Report Chain on Socket that Just Gained a Link
|
||||
## Aka. Forward-Refresh Caches Relying on Linkage
|
||||
|
||||
if not (
|
||||
consent_added := link.to_socket.sync_link_added(link)
|
||||
):
|
||||
# Did Not Consent to Addition: Queue Remove Link
|
||||
link_alterations["to_remove"].append(link)
|
||||
|
||||
# Execute Queued Operations
|
||||
## - Especially undoing undesirable link changes.
|
||||
## - This is important for locked graphs, whose links must not change.
|
||||
for link in link_alterations["to_remove"]:
|
||||
self.links.remove(link)
|
||||
for from_socket, to_socket in link_alterations["to_add"]:
|
||||
self.links.new(from_socket, to_socket)
|
||||
|
||||
# If Queued Operations: Regenerate Cache
|
||||
## - This prevents the next update() from picking up on alterations.
|
||||
if link_alterations["to_remove"] or link_alterations["to_add"]:
|
||||
self._node_link_cache.regenerate()
|
||||
|
||||
####################
|
||||
# - Post-Load Handler
|
||||
####################
|
||||
def initialize_sim_tree_node_link_cache(scene: bpy.types.Scene):
|
||||
"""Whenever a file is loaded, create/regenerate the NodeLinkCache in all trees.
|
||||
"""
|
||||
for node_tree in bpy.data.node_groups:
|
||||
if node_tree.bl_idname == "MaxwellSimTree":
|
||||
if not hasattr(node_tree, "_node_link_cache"):
|
||||
node_tree._node_link_cache = NodeLinkCache(node_tree)
|
||||
else:
|
||||
node_tree._node_link_cache.regenerate()
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
####################
|
||||
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'}
|
||||
#)
|
||||
|
|
|
@ -5,10 +5,10 @@ from . import outputs
|
|||
from . import sources
|
||||
from . import mediums
|
||||
from . import structures
|
||||
from . import bounds
|
||||
from . import monitors
|
||||
#from . import bounds
|
||||
#from . import monitors
|
||||
from . import simulations
|
||||
from . import utilities
|
||||
#from . import utilities
|
||||
|
||||
BL_REGISTER = [
|
||||
*kitchen_sink.BL_REGISTER,
|
||||
|
@ -17,10 +17,10 @@ BL_REGISTER = [
|
|||
*sources.BL_REGISTER,
|
||||
*mediums.BL_REGISTER,
|
||||
*structures.BL_REGISTER,
|
||||
*bounds.BL_REGISTER,
|
||||
*monitors.BL_REGISTER,
|
||||
# *bounds.BL_REGISTER,
|
||||
# *monitors.BL_REGISTER,
|
||||
*simulations.BL_REGISTER,
|
||||
*utilities.BL_REGISTER,
|
||||
# *utilities.BL_REGISTER,
|
||||
]
|
||||
BL_NODES = {
|
||||
**kitchen_sink.BL_NODES,
|
||||
|
@ -29,8 +29,8 @@ BL_NODES = {
|
|||
**sources.BL_NODES,
|
||||
**mediums.BL_NODES,
|
||||
**structures.BL_NODES,
|
||||
**bounds.BL_NODES,
|
||||
**monitors.BL_NODES,
|
||||
# **bounds.BL_NODES,
|
||||
# **monitors.BL_NODES,
|
||||
**simulations.BL_NODES,
|
||||
**utilities.BL_NODES,
|
||||
# **utilities.BL_NODES,
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,20 +1,23 @@
|
|||
from . import unit_system
|
||||
#from . import unit_system
|
||||
|
||||
from . import importers
|
||||
from . import constants
|
||||
from . import lists
|
||||
from . import scene
|
||||
#from . import lists
|
||||
#from . import scene
|
||||
|
||||
BL_REGISTER = [
|
||||
*unit_system.BL_REGISTER,
|
||||
|
||||
*scene.BL_REGISTER,
|
||||
*importers.BL_REGISTER,
|
||||
# *unit_system.BL_REGISTER,
|
||||
#
|
||||
# *scene.BL_REGISTER,
|
||||
*constants.BL_REGISTER,
|
||||
*lists.BL_REGISTER,
|
||||
#*lists.BL_REGISTER,
|
||||
]
|
||||
BL_NODES = {
|
||||
**unit_system.BL_NODES,
|
||||
|
||||
**scene.BL_NODES,
|
||||
**importers.BL_NODES,
|
||||
# **unit_system.BL_NODES,
|
||||
#
|
||||
# **scene.BL_NODES,
|
||||
**constants.BL_NODES,
|
||||
**lists.BL_NODES,
|
||||
# **lists.BL_NODES,
|
||||
}
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
from . import wave_constant
|
||||
from . import scientific_constant
|
||||
|
||||
from . import number_constant
|
||||
from . import physical_constant
|
||||
from . import blender_constant
|
||||
#from . import scientific_constant
|
||||
#
|
||||
#from . import number_constant
|
||||
#from . import physical_constant
|
||||
#from . import blender_constant
|
||||
|
||||
BL_REGISTER = [
|
||||
*wave_constant.BL_REGISTER,
|
||||
*scientific_constant.BL_REGISTER,
|
||||
|
||||
*number_constant.BL_REGISTER,
|
||||
*physical_constant.BL_REGISTER,
|
||||
*blender_constant.BL_REGISTER,
|
||||
# *scientific_constant.BL_REGISTER,
|
||||
#
|
||||
# *number_constant.BL_REGISTER,
|
||||
# *physical_constant.BL_REGISTER,
|
||||
# *blender_constant.BL_REGISTER,
|
||||
]
|
||||
BL_NODES = {
|
||||
**wave_constant.BL_NODES,
|
||||
**scientific_constant.BL_NODES,
|
||||
|
||||
**number_constant.BL_NODES,
|
||||
**physical_constant.BL_NODES,
|
||||
**blender_constant.BL_NODES,
|
||||
# **scientific_constant.BL_NODES,
|
||||
#
|
||||
# **number_constant.BL_NODES,
|
||||
# **physical_constant.BL_NODES,
|
||||
# **blender_constant.BL_NODES,
|
||||
}
|
||||
|
|
|
@ -3,75 +3,64 @@ import sympy as sp
|
|||
import sympy.physics.units as spu
|
||||
import scipy as sc
|
||||
|
||||
from .... import contracts
|
||||
from .... import contracts as ct
|
||||
from .... import sockets
|
||||
from ... import base
|
||||
|
||||
vac_speed_of_light = (
|
||||
VAC_SPEED_OF_LIGHT = (
|
||||
sc.constants.speed_of_light
|
||||
* spu.meter/spu.second
|
||||
)
|
||||
|
||||
class WaveConstantNode(base.MaxwellSimTreeNode):
|
||||
node_type = contracts.NodeType.WaveConstant
|
||||
class WaveConstantNode(base.MaxwellSimNode):
|
||||
node_type = ct.NodeType.WaveConstant
|
||||
|
||||
bl_label = "Wave Constant"
|
||||
|
||||
input_sockets = {}
|
||||
input_socket_sets = {
|
||||
"vac_wl": {
|
||||
"vac_wl": sockets.PhysicalVacWLSocketDef(
|
||||
label="Vac WL",
|
||||
),
|
||||
"Vacuum WL": {
|
||||
"WL": sockets.PhysicalLengthSocketDef(),
|
||||
},
|
||||
"freq": {
|
||||
"freq": sockets.PhysicalFreqSocketDef(
|
||||
label="Freq",
|
||||
),
|
||||
"Frequency": {
|
||||
"Freq": sockets.PhysicalFreqSocketDef(),
|
||||
},
|
||||
}
|
||||
output_sockets = {
|
||||
"vac_wl": sockets.PhysicalVacWLSocketDef(
|
||||
label="Vac WL",
|
||||
),
|
||||
"freq": sockets.PhysicalVacWLSocketDef(
|
||||
label="Freq",
|
||||
),
|
||||
"WL": sockets.PhysicalLengthSocketDef(),
|
||||
"Freq": sockets.PhysicalFreqSocketDef(),
|
||||
}
|
||||
output_socket_sets = {}
|
||||
|
||||
####################
|
||||
# - Callbacks
|
||||
####################
|
||||
@base.computes_output_socket("vac_wl")
|
||||
def compute_vac_wl(self: contracts.NodeTypeProtocol) -> sp.Expr:
|
||||
if self.socket_set == "vac_wl":
|
||||
return self.compute_input("vac_wl")
|
||||
|
||||
elif self.socket_set == "freq":
|
||||
freq = self.compute_input("freq")
|
||||
@base.computes_output_socket(
|
||||
"WL",
|
||||
kind=ct.DataFlowKind.Value,
|
||||
input_sockets={"WL", "Freq"},
|
||||
)
|
||||
def compute_vac_wl(self, input_socket_values: dict) -> sp.Expr:
|
||||
if (vac_wl := input_socket_values["WL"]):
|
||||
return vac_wl
|
||||
elif (freq := input_socket_values["Freq"]):
|
||||
return spu.convert_to(
|
||||
vac_speed_of_light / freq,
|
||||
VAC_SPEED_OF_LIGHT / freq,
|
||||
spu.meter,
|
||||
)
|
||||
|
||||
raise ValueError("No valid socket set.")
|
||||
raise RuntimeError("Vac WL and Freq are both non-truthy")
|
||||
|
||||
@base.computes_output_socket("freq")
|
||||
def compute_freq(self: contracts.NodeTypeProtocol) -> sp.Expr:
|
||||
if self.socket_set == "vac_wl":
|
||||
vac_wl = self.compute_input("vac_wl")
|
||||
@base.computes_output_socket(
|
||||
"Freq",
|
||||
input_sockets={"WL", "Freq"},
|
||||
)
|
||||
def compute_freq(self, input_sockets: dict) -> sp.Expr:
|
||||
if (vac_wl := input_sockets["WL"]):
|
||||
return spu.convert_to(
|
||||
vac_speed_of_light / vac_wl,
|
||||
VAC_SPEED_OF_LIGHT / vac_wl,
|
||||
spu.hertz,
|
||||
)
|
||||
|
||||
elif self.socket_set == "freq":
|
||||
return self.compute_input("freq")
|
||||
|
||||
raise ValueError("No valid socket set.")
|
||||
|
||||
|
||||
elif (freq := input_sockets["Freq"]):
|
||||
return freq
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
|
@ -80,7 +69,7 @@ BL_REGISTER = [
|
|||
WaveConstantNode,
|
||||
]
|
||||
BL_NODES = {
|
||||
contracts.NodeType.WaveConstant: (
|
||||
contracts.NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS
|
||||
ct.NodeType.WaveConstant: (
|
||||
ct.NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
import functools
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
import typing as typ
|
||||
from pathlib import Path
|
||||
|
||||
import bpy
|
||||
import sympy as sp
|
||||
import pydantic as pyd
|
||||
import tidy3d as td
|
||||
import tidy3d.web as _td_web
|
||||
|
||||
from ......utils.auth_td_web import g_td_web, is_td_web_authed
|
||||
from .... import contracts as ct
|
||||
from .... import sockets
|
||||
from ... import base
|
||||
|
||||
@functools.cache
|
||||
def task_status(task_id: str):
|
||||
task = _td_web.api.webapi.get_info(task_id)
|
||||
return task.status
|
||||
|
||||
####################
|
||||
# - Node
|
||||
####################
|
||||
class Tidy3DWebImporterNode(base.MaxwellSimNode):
|
||||
node_type = ct.NodeType.Tidy3DWebImporter
|
||||
bl_label = "Tidy3DWebImporter"
|
||||
|
||||
input_sockets = {
|
||||
"Cloud Task": sockets.Tidy3DCloudTaskSocketDef(
|
||||
task_exists=True,
|
||||
),
|
||||
}
|
||||
output_sockets = {}
|
||||
|
||||
####################
|
||||
# - UI
|
||||
####################
|
||||
def draw_info(self, context, layout): pass
|
||||
|
||||
####################
|
||||
# - Output Methods
|
||||
####################
|
||||
@base.computes_output_socket(
|
||||
"FDTD Sim",
|
||||
input_sockets={"Cloud Task"},
|
||||
)
|
||||
def compute_cloud_task(self, input_sockets: dict) -> str:
|
||||
if not isinstance(task_id := input_sockets["Cloud Task"], str):
|
||||
msg ="Input task does not exist"
|
||||
raise ValueError(msg)
|
||||
|
||||
# Load the Simulation
|
||||
td_web = g_td_web(None) ## Presume already auth'ed
|
||||
with tempfile.NamedTemporaryFile(delete=False) as f:
|
||||
_path_tmp = Path(f.name)
|
||||
_path_tmp.rename(f.name + ".json")
|
||||
path_tmp = Path(f.name + ".json")
|
||||
|
||||
cloud_sim = _td_web.api.webapi.load_simulation(
|
||||
task_id,
|
||||
path=str(path_tmp),
|
||||
)
|
||||
Path(path_tmp).unlink()
|
||||
|
||||
return cloud_sim
|
||||
|
||||
####################
|
||||
# - Update
|
||||
####################
|
||||
@base.on_value_changed(
|
||||
socket_name="Cloud Task",
|
||||
input_sockets={"Cloud Task"}
|
||||
)
|
||||
def on_value_changed__cloud_task(self, input_sockets: dict):
|
||||
task_status.cache_clear()
|
||||
if (
|
||||
(task_id := input_sockets["Cloud Task"]) is None
|
||||
or isinstance(task_id, dict)
|
||||
or task_status(task_id) != "success"
|
||||
or not is_td_web_authed
|
||||
):
|
||||
if self.loose_output_sockets: self.loose_output_sockets = {}
|
||||
return
|
||||
|
||||
td_web = g_td_web(None) ## Presume already auth'ed
|
||||
|
||||
self.loose_output_sockets = {
|
||||
"FDTD Sim": sockets.MaxwellFDTDSimSocketDef(),
|
||||
"FDTD Sim Data": sockets.AnySocketDef(),
|
||||
}
|
||||
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
####################
|
||||
BL_REGISTER = [
|
||||
Tidy3DWebImporterNode,
|
||||
]
|
||||
BL_NODES = {
|
||||
ct.NodeType.Tidy3DWebImporter: (
|
||||
ct.NodeCategory.MAXWELLSIM_INPUTS_IMPORTERS
|
||||
)
|
||||
}
|
|
@ -1,13 +1,15 @@
|
|||
from pathlib import Path
|
||||
|
||||
import tidy3d as td
|
||||
import sympy as sp
|
||||
import sympy.physics.units as spu
|
||||
|
||||
from .. import contracts
|
||||
from .. import contracts as ct
|
||||
from .. import sockets
|
||||
from . import base
|
||||
|
||||
class KitchenSinkNode(base.MaxwellSimTreeNode):
|
||||
node_type = contracts.NodeType.KitchenSink
|
||||
class KitchenSinkNode(base.MaxwellSimNode):
|
||||
node_type = ct.NodeType.KitchenSink
|
||||
|
||||
bl_label = "Kitchen Sink"
|
||||
#bl_icon = ...
|
||||
|
@ -15,75 +17,75 @@ class KitchenSinkNode(base.MaxwellSimTreeNode):
|
|||
####################
|
||||
# - Sockets
|
||||
####################
|
||||
input_sockets = {}
|
||||
input_sockets = {
|
||||
"Static Data": sockets.AnySocketDef(),
|
||||
}
|
||||
input_socket_sets = {
|
||||
"basic": {
|
||||
"basic_any": sockets.AnySocketDef(label="Any"),
|
||||
"basic_bool": sockets.BoolSocketDef(label="Bool"),
|
||||
"basic_filepath": sockets.FilePathSocketDef(label="FilePath"),
|
||||
"basic_text": sockets.TextSocketDef(label="Text"),
|
||||
"Basic": {
|
||||
"Any": sockets.AnySocketDef(),
|
||||
"Bool": sockets.BoolSocketDef(),
|
||||
"FilePath": sockets.FilePathSocketDef(),
|
||||
"Text": sockets.TextSocketDef(),
|
||||
},
|
||||
"number": {
|
||||
"number_integer": sockets.IntegerNumberSocketDef(label="IntegerNumber"),
|
||||
"number_rational": sockets.RationalNumberSocketDef(label="RationalNumber"),
|
||||
"number_real": sockets.RealNumberSocketDef(label="RealNumber"),
|
||||
"number_complex": sockets.ComplexNumberSocketDef(label="ComplexNumber"),
|
||||
"Number": {
|
||||
"Integer": sockets.IntegerNumberSocketDef(),
|
||||
"Rational": sockets.RationalNumberSocketDef(),
|
||||
"Real": sockets.RealNumberSocketDef(),
|
||||
"Complex": sockets.ComplexNumberSocketDef(),
|
||||
},
|
||||
"vector": {
|
||||
"vector_real2dvector": sockets.Real2DVectorSocketDef(label="Real2DVector"),
|
||||
"vector_complex2dvector": sockets.Complex2DVectorSocketDef(label="Complex2DVector"),
|
||||
"vector_real3dvector": sockets.Real3DVectorSocketDef(label="Real3DVector"),
|
||||
"vector_complex3dvector": sockets.Complex3DVectorSocketDef(label="Complex3DVector"),
|
||||
"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": {
|
||||
"physical_time": sockets.PhysicalTimeSocketDef(label="PhysicalTime"),
|
||||
#"physical_point_2d": sockets.PhysicalPoint2DSocketDef(label="PhysicalPoint2D"),
|
||||
"physical_angle": sockets.PhysicalAngleSocketDef(label="PhysicalAngle"),
|
||||
"physical_length": sockets.PhysicalLengthSocketDef(label="PhysicalLength"),
|
||||
"physical_area": sockets.PhysicalAreaSocketDef(label="PhysicalArea"),
|
||||
"physical_volume": sockets.PhysicalVolumeSocketDef(label="PhysicalVolume"),
|
||||
"physical_point_3d": sockets.PhysicalPoint3DSocketDef(label="PhysicalPoint3D"),
|
||||
#"physical_size_2d": sockets.PhysicalSize2DSocketDef(label="PhysicalSize2D"),
|
||||
"physical_size_3d": sockets.PhysicalSize3DSocketDef(label="PhysicalSize3D"),
|
||||
"physical_mass": sockets.PhysicalMassSocketDef(label="PhysicalMass"),
|
||||
"physical_speed": sockets.PhysicalSpeedSocketDef(label="PhysicalSpeed"),
|
||||
"physical_accel_scalar": sockets.PhysicalAccelScalarSocketDef(label="PhysicalAccelScalar"),
|
||||
"physical_force_scalar": sockets.PhysicalForceScalarSocketDef(label="PhysicalForceScalar"),
|
||||
#"physical_accel_3dvector": sockets.PhysicalAccel3DVectorSocketDef(label="PhysicalAccel3DVector"),
|
||||
#"physical_force_3dvector": sockets.PhysicalForce3DVectorSocketDef(label="PhysicalForce3DVector"),
|
||||
"physical_pol": sockets.PhysicalPolSocketDef(label="PhysicalPol"),
|
||||
"physical_freq": sockets.PhysicalFreqSocketDef(label="PhysicalFreq"),
|
||||
"physical_spec_power_dist": sockets.PhysicalSpecPowerDistSocketDef(label="PhysicalSpecPowerDist"),
|
||||
"physical_spec_rel_perm_dist": sockets.PhysicalSpecRelPermDistSocketDef(label="PhysicalSpecRelPermDist"),
|
||||
"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": {
|
||||
"blender_object": sockets.BlenderObjectSocketDef(label="BlenderObject"),
|
||||
"blender_collection": sockets.BlenderCollectionSocketDef(label="BlenderCollection"),
|
||||
"blender_image": sockets.BlenderImageSocketDef(label="BlenderImage"),
|
||||
"blender_volume": sockets.BlenderVolumeSocketDef(label="BlenderVolume"),
|
||||
"blender_geonodes": sockets.BlenderGeoNodesSocketDef(label="BlenderGeoNodes"),
|
||||
"blender_text": sockets.BlenderTextSocketDef(label="BlenderText"),
|
||||
"Blender": {
|
||||
"Object": sockets.BlenderObjectSocketDef(),
|
||||
"Collection": sockets.BlenderCollectionSocketDef(),
|
||||
"Image": sockets.BlenderImageSocketDef(),
|
||||
"GeoNodes": sockets.BlenderGeoNodesSocketDef(),
|
||||
"Text": sockets.BlenderTextSocketDef(),
|
||||
},
|
||||
"maxwell": {
|
||||
"maxwell_source": sockets.MaxwellSourceSocketDef(label="MaxwellSource"),
|
||||
"maxwell_temporal_shape": sockets.MaxwellTemporalShapeSocketDef(label="MaxwellTemporalShape"),
|
||||
"maxwell_medium": sockets.MaxwellMediumSocketDef(label="MaxwellMedium"),
|
||||
#"maxwell_medium_nonlinearity": sockets.MaxwellMediumNonLinearitySocketDef(label="MaxwellMediumNonLinearity"),
|
||||
"maxwell_structure": sockets.MaxwellStructureSocketDef(label="MaxwellMedium"),
|
||||
"maxwell_bound_box": sockets.MaxwellBoundBoxSocketDef(label="MaxwellBoundBox"),
|
||||
"maxwell_bound_face": sockets.MaxwellBoundFaceSocketDef(label="MaxwellBoundFace"),
|
||||
"maxwell_monitor": sockets.MaxwellMonitorSocketDef(label="MaxwellMonitor"),
|
||||
"maxwell_fdtd_sim": sockets.MaxwellFDTDSimSocketDef(label="MaxwellFDTDSim"),
|
||||
"maxwell_sim_grid": sockets.MaxwellSimGridSocketDef(label="MaxwellSimGrid"),
|
||||
"maxwell_sim_grid_axis": sockets.MaxwellSimGridAxisSocketDef(label="MaxwellSimGridAxis"),
|
||||
"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 = {}
|
||||
output_socket_sets = {
|
||||
k + " Output": v
|
||||
for k, v in input_socket_sets.items()
|
||||
output_sockets = {
|
||||
"Static Data": sockets.AnySocketDef(),
|
||||
}
|
||||
output_socket_sets = input_socket_sets
|
||||
|
||||
|
||||
|
||||
|
@ -94,7 +96,7 @@ BL_REGISTER = [
|
|||
KitchenSinkNode,
|
||||
]
|
||||
BL_NODES = {
|
||||
contracts.NodeType.KitchenSink: (
|
||||
contracts.NodeCategory.MAXWELLSIM_INPUTS
|
||||
ct.NodeType.KitchenSink: (
|
||||
ct.NodeCategory.MAXWELLSIM_INPUTS
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,47 +1,47 @@
|
|||
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
|
||||
#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 = [
|
||||
*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,
|
||||
# *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 = {
|
||||
**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,
|
||||
# **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,
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import typing as typ
|
||||
import functools
|
||||
|
||||
import bpy
|
||||
import tidy3d as td
|
||||
import sympy as sp
|
||||
|
@ -6,37 +9,33 @@ import numpy as np
|
|||
import scipy as sc
|
||||
|
||||
from .....utils import extra_sympy_units as spuex
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
from ... import sockets
|
||||
from ... import managed_objs
|
||||
from .. import base
|
||||
|
||||
class ExperimentOperator00(bpy.types.Operator):
|
||||
bl_idname = "blender_maxwell.experiment_operator_00"
|
||||
bl_label = "exp"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
node = context.node
|
||||
node.invoke_matplotlib_and_update_image()
|
||||
return {'FINISHED'}
|
||||
|
||||
class LibraryMediumNode(base.MaxwellSimTreeNode):
|
||||
node_type = contracts.NodeType.LibraryMedium
|
||||
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"
|
||||
#bl_icon = ...
|
||||
|
||||
####################
|
||||
# - Sockets
|
||||
####################
|
||||
input_sockets = {}
|
||||
output_sockets = {
|
||||
"medium": sockets.MaxwellMediumSocketDef(
|
||||
label="Medium"
|
||||
),
|
||||
"Medium": sockets.MaxwellMediumSocketDef(),
|
||||
}
|
||||
|
||||
managed_obj_defs = {
|
||||
"nk_plot": ct.schemas.ManagedObjDef(
|
||||
mk=lambda name: managed_objs.ManagedBLImage(name),
|
||||
name_prefix="nkplot_",
|
||||
)
|
||||
}
|
||||
|
||||
####################
|
||||
|
@ -61,19 +60,12 @@ class LibraryMediumNode(base.MaxwellSimTreeNode):
|
|||
if mat_key != "graphene" ## For some reason, it's unique...
|
||||
],
|
||||
default="Au",
|
||||
update=(lambda self,context: self.update()),
|
||||
update=(lambda self, context: self.sync_prop("material", context)),
|
||||
)
|
||||
|
||||
####################
|
||||
# - UI
|
||||
####################
|
||||
def draw_props(self, context, layout):
|
||||
layout.prop(self, "material", text="")
|
||||
|
||||
def draw_info(self, context, layout):
|
||||
layout.operator(ExperimentOperator00.bl_idname, text="Experiment")
|
||||
vac_speed_of_light = sc.constants.speed_of_light * spu.meter/spu.second
|
||||
|
||||
@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(
|
||||
|
@ -82,80 +74,89 @@ class LibraryMediumNode(base.MaxwellSimTreeNode):
|
|||
) / 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),
|
||||
VAC_SPEED_OF_LIGHT / (val * spu.hertz),
|
||||
spu.nanometer,
|
||||
) / spu.nanometer
|
||||
for val in mat.medium.frequency_range
|
||||
for val in reversed(mat.medium.frequency_range)
|
||||
]
|
||||
|
||||
layout.label(text=f"nm: [{nm_range[1].n(2)}, {nm_range[0].n(2)}]")
|
||||
layout.label(text=f"THz: [{freq_range[0].n(2)}, {freq_range[1].n(2)}]")
|
||||
return sp.pretty(
|
||||
[nm_range[0].n(4), nm_range[1].n(4)],
|
||||
use_unicode=True
|
||||
)
|
||||
|
||||
####################
|
||||
# - Output Socket Computation
|
||||
# - UI
|
||||
####################
|
||||
@base.computes_output_socket("medium")
|
||||
def compute_medium(self: contracts.NodeTypeProtocol) -> td.AbstractMedium:
|
||||
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
|
||||
|
||||
####################
|
||||
# - Experiment
|
||||
# - Event Callbacks
|
||||
####################
|
||||
def invoke_matplotlib_and_update_image(self):
|
||||
import matplotlib.pyplot as plt
|
||||
mat = td.material_library[self.material]
|
||||
@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
|
||||
]
|
||||
|
||||
aspect_ratio = 1.0
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == 'IMAGE_EDITOR':
|
||||
width = area.width
|
||||
height = area.height
|
||||
aspect_ratio = width / height
|
||||
|
||||
# Generate a plot with matplotlib
|
||||
fig_width = 6
|
||||
fig_height = fig_width / aspect_ratio
|
||||
fig, ax = plt.subplots(figsize=(fig_width, fig_height))
|
||||
ax.set_aspect(aspect_ratio)
|
||||
mat.medium.plot(
|
||||
np.linspace(*mat.medium.frequency_range[:2], 50),
|
||||
ax=ax,
|
||||
managed_objs["nk_plot"].mpl_plot_to_image(
|
||||
lambda ax: medium.plot(medium.frequency_range, ax=ax),
|
||||
bl_select=True,
|
||||
)
|
||||
|
||||
# Save the plot to a temporary file
|
||||
temp_plot_file = bpy.path.abspath('//temp_plot.png')
|
||||
fig.savefig(temp_plot_file, bbox_inches='tight')
|
||||
plt.close(fig) # Close the figure to free up memory
|
||||
|
||||
# Load or reload the image in Blender
|
||||
if "matplotlib_plot" in bpy.data.images:
|
||||
image = bpy.data.images["matplotlib_plot"]
|
||||
image.reload()
|
||||
else:
|
||||
image = bpy.data.images.load(temp_plot_file)
|
||||
image.name = "matplotlib_plot"
|
||||
|
||||
# Write the plot to an image datablock in Blender
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == 'IMAGE_EDITOR':
|
||||
for space in area.spaces:
|
||||
if space.type == 'IMAGE_EDITOR':
|
||||
space.image = image
|
||||
return True
|
||||
|
||||
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
####################
|
||||
BL_REGISTER = [
|
||||
ExperimentOperator00,
|
||||
LibraryMediumNode,
|
||||
]
|
||||
BL_NODES = {
|
||||
contracts.NodeType.LibraryMedium: (
|
||||
contracts.NodeCategory.MAXWELLSIM_MEDIUMS
|
||||
ct.NodeType.LibraryMedium: (
|
||||
ct.NodeCategory.MAXWELLSIM_MEDIUMS
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
from . import viewers
|
||||
from . import viewer
|
||||
from . import exporters
|
||||
from . import plotters
|
||||
|
||||
BL_REGISTER = [
|
||||
*viewer.BL_REGISTER,
|
||||
*exporters.BL_REGISTER,
|
||||
]
|
||||
BL_NODES = {
|
||||
**viewer.BL_NODES,
|
||||
**exporters.BL_NODES,
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
from . import json_file_exporter
|
||||
from . import tidy3d_web_exporter
|
||||
|
||||
BL_REGISTER = [
|
||||
*json_file_exporter.BL_REGISTER,
|
||||
*tidy3d_web_exporter.BL_REGISTER,
|
||||
]
|
||||
BL_NODES = {
|
||||
**json_file_exporter.BL_NODES,
|
||||
**tidy3d_web_exporter.BL_NODES,
|
||||
}
|
||||
|
|
|
@ -7,39 +7,13 @@ import sympy as sp
|
|||
import pydantic as pyd
|
||||
import tidy3d as td
|
||||
|
||||
from .... import contracts
|
||||
from .... import contracts as ct
|
||||
from .... import sockets
|
||||
from ... import base
|
||||
|
||||
####################
|
||||
# - Operators
|
||||
####################
|
||||
class JSONFileExporterPrintJSON(bpy.types.Operator):
|
||||
bl_idname = "blender_maxwell.json_file_exporter_print_json"
|
||||
bl_label = "Print the JSON of what's linked into a JSONFileExporterNode."
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
node = context.node
|
||||
print(node.linked_data_as_json())
|
||||
return {'FINISHED'}
|
||||
|
||||
class JSONFileExporterMeshData(bpy.types.Operator):
|
||||
bl_idname = "blender_maxwell.json_file_exporter_mesh_data"
|
||||
bl_label = "Print any mesh data linked into a JSONFileExporterNode."
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
node = context.node
|
||||
print(node.linked_mesh_data())
|
||||
return {'FINISHED'}
|
||||
|
||||
class JSONFileExporterSaveJSON(bpy.types.Operator):
|
||||
bl_idname = "blender_maxwell.json_file_exporter_save_json"
|
||||
bl_label = "Save the JSON of what's linked into a JSONFileExporterNode."
|
||||
|
@ -56,22 +30,24 @@ class JSONFileExporterSaveJSON(bpy.types.Operator):
|
|||
####################
|
||||
# - Node
|
||||
####################
|
||||
class JSONFileExporterNode(base.MaxwellSimTreeNode):
|
||||
node_type = contracts.NodeType.JSONFileExporter
|
||||
class JSONFileExporterNode(base.MaxwellSimNode):
|
||||
node_type = ct.NodeType.JSONFileExporter
|
||||
|
||||
bl_label = "JSON File Exporter"
|
||||
#bl_icon = constants.ICON_SIM_INPUT
|
||||
|
||||
input_sockets = {
|
||||
"json_path": sockets.FilePathSocketDef(
|
||||
label="JSON Path",
|
||||
default_path="simulation.json"
|
||||
"Data": sockets.AnySocketDef(),
|
||||
"JSON Path": sockets.FilePathSocketDef(
|
||||
default_path=Path("simulation.json")
|
||||
),
|
||||
"data": sockets.AnySocketDef(
|
||||
label="Data",
|
||||
"JSON Indent": sockets.IntegerNumberSocketDef(
|
||||
default_value=4,
|
||||
),
|
||||
}
|
||||
output_sockets = {}
|
||||
output_sockets = {
|
||||
"JSON String": sockets.TextSocketDef(),
|
||||
}
|
||||
|
||||
####################
|
||||
# - UI Layout
|
||||
|
@ -81,53 +57,50 @@ class JSONFileExporterNode(base.MaxwellSimTreeNode):
|
|||
context: bpy.types.Context,
|
||||
layout: bpy.types.UILayout,
|
||||
) -> None:
|
||||
layout.operator(JSONFileExporterPrintJSON.bl_idname, text="Print")
|
||||
layout.operator(JSONFileExporterSaveJSON.bl_idname, text="Save")
|
||||
layout.operator(JSONFileExporterMeshData.bl_idname, text="Mesh Info")
|
||||
layout.operator(JSONFileExporterSaveJSON.bl_idname, text="Save JSON")
|
||||
|
||||
####################
|
||||
# - Methods
|
||||
####################
|
||||
def linked_data_as_json(self) -> str | None:
|
||||
if self.g_input_bl_socket("data").is_linked:
|
||||
data: typ.Any = self.compute_input("data")
|
||||
|
||||
# Tidy3D Objects: Call .json()
|
||||
if hasattr(data, "json"):
|
||||
return data.json()
|
||||
|
||||
# Pydantic Models: Call .model_dump_json()
|
||||
elif isinstance(data, pyd.BaseModel):
|
||||
return data.model_dump_json()
|
||||
|
||||
else:
|
||||
json.dumps(data)
|
||||
|
||||
def linked_mesh_data(self) -> str | None:
|
||||
if self.g_input_bl_socket("data").is_linked:
|
||||
data: typ.Any = self.compute_input("data")
|
||||
|
||||
if isinstance(data, td.Structure):
|
||||
return data.geometry
|
||||
|
||||
def export_data_as_json(self) -> None:
|
||||
if (data := self.linked_data_as_json()):
|
||||
data_dict = json.loads(data)
|
||||
with self.compute_input("json_path").open("w") as f:
|
||||
json.dump(data_dict, f, ensure_ascii=False, indent=4)
|
||||
if (json_str := self.compute_output("JSON String")):
|
||||
data_dict = json.loads(json_str)
|
||||
with self._compute_input("JSON Path").open("w") as f:
|
||||
indent = self._compute_input("JSON Indent")
|
||||
json.dump(data_dict, f, ensure_ascii=False, indent=indent)
|
||||
|
||||
####################
|
||||
# - Output Sockets
|
||||
####################
|
||||
@base.computes_output_socket(
|
||||
"JSON String",
|
||||
input_sockets={"Data"},
|
||||
)
|
||||
def compute_json_string(self, input_sockets: dict[str, typ.Any]) -> str | None:
|
||||
if not (data := input_sockets["Data"]):
|
||||
return None
|
||||
|
||||
# Tidy3D Objects: Call .json()
|
||||
if hasattr(data, "json"):
|
||||
return data.json()
|
||||
|
||||
# Pydantic Models: Call .model_dump_json()
|
||||
elif isinstance(data, pyd.BaseModel):
|
||||
return data.model_dump_json()
|
||||
|
||||
else:
|
||||
json.dumps(data)
|
||||
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
####################
|
||||
BL_REGISTER = [
|
||||
JSONFileExporterPrintJSON,
|
||||
JSONFileExporterMeshData,
|
||||
JSONFileExporterSaveJSON,
|
||||
JSONFileExporterNode,
|
||||
]
|
||||
BL_NODES = {
|
||||
contracts.NodeType.JSONFileExporter: (
|
||||
contracts.NodeCategory.MAXWELLSIM_OUTPUTS_EXPORTERS
|
||||
ct.NodeType.JSONFileExporter: (
|
||||
ct.NodeCategory.MAXWELLSIM_OUTPUTS_EXPORTERS
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,393 @@
|
|||
import json
|
||||
import tempfile
|
||||
import functools
|
||||
import typing as typ
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import bpy
|
||||
import sympy as sp
|
||||
import pydantic as pyd
|
||||
import tidy3d as td
|
||||
import tidy3d.web as _td_web
|
||||
|
||||
from ......utils.auth_td_web import g_td_web, is_td_web_authed
|
||||
from .... import contracts as ct
|
||||
from .... import sockets
|
||||
from ... import base
|
||||
|
||||
####################
|
||||
# - Task Getters
|
||||
####################
|
||||
## TODO: We should probably refactor this setup.
|
||||
@functools.cache
|
||||
def estimated_task_cost(task_id: str):
|
||||
return _td_web.api.webapi.estimate_cost(task_id)
|
||||
|
||||
@functools.cache
|
||||
def billed_task_cost(task_id: str):
|
||||
return _td_web.api.webapi.real_cost(task_id)
|
||||
|
||||
@functools.cache
|
||||
def task_status(task_id: str):
|
||||
task = _td_web.api.webapi.get_info(task_id)
|
||||
return task.status
|
||||
|
||||
####################
|
||||
# - Progress Timer
|
||||
####################
|
||||
## TODO: We should probably refactor this too.
|
||||
class Tidy3DTaskStatusModalOperator(bpy.types.Operator):
|
||||
bl_idname = "blender_maxwell.tidy_3d_task_status_modal_operator"
|
||||
bl_label = "Tidy3D Task Status Modal Operator"
|
||||
|
||||
_timer = None
|
||||
_task_id = None
|
||||
_node = None
|
||||
_status = None
|
||||
_reported_done = False
|
||||
|
||||
def modal(self, context, event):
|
||||
# Retrieve New Status
|
||||
task_status.cache_clear()
|
||||
new_status = task_status(self._task_id)
|
||||
if new_status != self._status:
|
||||
task_status.cache_clear()
|
||||
self._status = new_status
|
||||
|
||||
# Check Done Status
|
||||
if self._status in {"success", "error"}:
|
||||
# Report Done
|
||||
if not self._reported_done:
|
||||
self._node.trigger_action("value_changed")
|
||||
self._reported_done = True
|
||||
|
||||
# Finish when Billing is Known
|
||||
if not billed_task_cost(self._task_id):
|
||||
billed_task_cost.cache_clear()
|
||||
else:
|
||||
return {'FINISHED'}
|
||||
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
def execute(self, context):
|
||||
node = context.node
|
||||
wm = context.window_manager
|
||||
|
||||
self._timer = wm.event_timer_add(0.25, window=context.window)
|
||||
self._task_id = node.uploaded_task_id
|
||||
self._node = node
|
||||
self._status = task_status(self._task_id)
|
||||
|
||||
wm.modal_handler_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
####################
|
||||
# - Web Uploader / Loader / Runner / Releaser
|
||||
####################
|
||||
## TODO: We should probably refactor this too.
|
||||
class Tidy3DWebUploadOperator(bpy.types.Operator):
|
||||
bl_idname = "blender_maxwell.tidy_3d_web_upload_operator"
|
||||
bl_label = "Tidy3D Web Upload Operator"
|
||||
bl_description = "Upload the attached (locked) simulation, such that it is ready to run on the Tidy3D cloud"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
space = context.space_data
|
||||
return (
|
||||
space.type == 'NODE_EDITOR'
|
||||
and space.node_tree is not None
|
||||
and space.node_tree.bl_idname == "MaxwellSimTreeType"
|
||||
and is_td_web_authed()
|
||||
and hasattr(context, "node")
|
||||
and context.node.lock_tree
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
node = context.node
|
||||
node.web_upload()
|
||||
return {'FINISHED'}
|
||||
|
||||
class Tidy3DLoadUploadedOperator(bpy.types.Operator):
|
||||
bl_idname = "blender_maxwell.tidy_3d_load_uploaded_operator"
|
||||
bl_label = "Tidy3D Load Uploaded Operator"
|
||||
bl_description = "Load an already-uploaded simulation, as selected in the dropdown of the 'Cloud Task' socket"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
space = context.space_data
|
||||
return (
|
||||
space.type == 'NODE_EDITOR'
|
||||
and space.node_tree is not None
|
||||
and space.node_tree.bl_idname == "MaxwellSimTreeType"
|
||||
and is_td_web_authed()
|
||||
and hasattr(context, "node")
|
||||
and context.node.lock_tree
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
node = context.node
|
||||
node.load_uploaded_task()
|
||||
|
||||
# Load Simulation to Compare
|
||||
## Load Local Sim
|
||||
local_sim = node._compute_input("FDTD Sim")
|
||||
|
||||
## Load Cloud Sim
|
||||
task_id = node.compute_output("Cloud Task")
|
||||
with tempfile.NamedTemporaryFile(delete=False) as f:
|
||||
_path_tmp = Path(f.name)
|
||||
_path_tmp.rename(f.name + ".json")
|
||||
path_tmp = Path(f.name + ".json")
|
||||
cloud_sim = _td_web.api.webapi.load_simulation(task_id, path=str(path_tmp))
|
||||
|
||||
Path(path_tmp).unlink()
|
||||
|
||||
## Compare
|
||||
if local_sim != cloud_sim:
|
||||
node.release_uploaded_task()
|
||||
msg = "Loaded simulation doesn't match input simulation"
|
||||
raise ValueError(msg)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
class RunUploadedTidy3DSim(bpy.types.Operator):
|
||||
bl_idname = "blender_maxwell.run_uploaded_tidy_3d_sim"
|
||||
bl_label = "Run Uploaded Tidy3D Sim"
|
||||
bl_description = "Run the currently uploaded (and loaded) simulation"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
space = context.space_data
|
||||
return (
|
||||
space.type == 'NODE_EDITOR'
|
||||
and space.node_tree is not None
|
||||
and space.node_tree.bl_idname == "MaxwellSimTreeType"
|
||||
and is_td_web_authed()
|
||||
and hasattr(context, "node")
|
||||
and context.node.lock_tree
|
||||
and context.node.uploaded_task_id
|
||||
and task_status(context.node.uploaded_task_id) == "draft"
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
node = context.node
|
||||
node.run_uploaded_task()
|
||||
bpy.ops.blender_maxwell.tidy_3d_task_status_modal_operator()
|
||||
return {'FINISHED'}
|
||||
|
||||
class ReleaseTidy3DExportOperator(bpy.types.Operator):
|
||||
bl_idname = "blender_maxwell.release_tidy_3d_export_operator"
|
||||
bl_label = "Release Tidy3D Export Operator"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
space = context.space_data
|
||||
return (
|
||||
space.type == 'NODE_EDITOR'
|
||||
and space.node_tree is not None
|
||||
and space.node_tree.bl_idname == "MaxwellSimTreeType"
|
||||
and is_td_web_authed()
|
||||
and hasattr(context, "node")
|
||||
and context.node.lock_tree
|
||||
and context.node.uploaded_task_id
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
node = context.node
|
||||
node.release_uploaded_task()
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
|
||||
####################
|
||||
# - Web Exporter Node
|
||||
####################
|
||||
class Tidy3DWebExporterNode(base.MaxwellSimNode):
|
||||
node_type = ct.NodeType.Tidy3DWebExporter
|
||||
bl_label = "Tidy3DWebExporter"
|
||||
|
||||
input_sockets = {
|
||||
"FDTD Sim": sockets.MaxwellFDTDSimSocketDef(),
|
||||
"Cloud Task": sockets.Tidy3DCloudTaskSocketDef(
|
||||
task_exists=False,
|
||||
),
|
||||
}
|
||||
output_sockets = {
|
||||
"Cloud Task": sockets.Tidy3DCloudTaskSocketDef(
|
||||
task_exists=True,
|
||||
),
|
||||
}
|
||||
|
||||
lock_tree: bpy.props.BoolProperty(
|
||||
name="Whether to lock the attached tree",
|
||||
description="Whether or not to lock the attached tree",
|
||||
default=False,
|
||||
update=(lambda self, context: self.sync_lock_tree(context)),
|
||||
)
|
||||
uploaded_task_id: bpy.props.StringProperty(
|
||||
name="Uploaded Task ID",
|
||||
description="The uploaded task ID",
|
||||
default="",
|
||||
)
|
||||
|
||||
####################
|
||||
# - Sync Methods
|
||||
####################
|
||||
def sync_lock_tree(self, context):
|
||||
node_tree = self.id_data
|
||||
|
||||
if self.lock_tree:
|
||||
self.trigger_action("enable_lock")
|
||||
self.locked = False
|
||||
for bl_socket in self.inputs:
|
||||
if bl_socket.name == "FDTD Sim": continue
|
||||
bl_socket.locked = False
|
||||
|
||||
else:
|
||||
self.trigger_action("disable_lock")
|
||||
|
||||
####################
|
||||
# - Output Socket Callbacks
|
||||
####################
|
||||
def web_upload(self):
|
||||
if not (sim := self._compute_input("FDTD Sim")):
|
||||
raise ValueError("Must attach simulation")
|
||||
|
||||
if not (new_task_dict := self._compute_input("Cloud Task")):
|
||||
raise ValueError("No valid cloud task defined")
|
||||
|
||||
td_web = g_td_web(None) ## Presume already auth'ed
|
||||
|
||||
self.uploaded_task_id = td_web.api.webapi.upload(
|
||||
sim,
|
||||
**new_task_dict,
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
self.inputs["Cloud Task"].sync_task_loaded(self.uploaded_task_id)
|
||||
|
||||
def load_uploaded_task(self):
|
||||
self.inputs["Cloud Task"].sync_task_loaded(None)
|
||||
self.uploaded_task_id = self._compute_input("Cloud Task")
|
||||
|
||||
self.trigger_action("value_changed")
|
||||
|
||||
def run_uploaded_task(self):
|
||||
td_web = g_td_web(None) ## Presume already auth'ed
|
||||
td_web.api.webapi.start(self.uploaded_task_id)
|
||||
|
||||
self.trigger_action("value_changed")
|
||||
|
||||
def release_uploaded_task(self):
|
||||
self.uploaded_task_id = ""
|
||||
self.inputs["Cloud Task"].sync_task_released(specify_new_task=True)
|
||||
|
||||
self.trigger_action("value_changed")
|
||||
|
||||
####################
|
||||
# - UI
|
||||
####################
|
||||
def draw_operators(self, context, layout):
|
||||
is_authed = is_td_web_authed()
|
||||
has_uploaded_task_id = bool(self.uploaded_task_id)
|
||||
|
||||
# Row: Run Simulation
|
||||
row = layout.row(align=True)
|
||||
if has_uploaded_task_id: row.enabled = False
|
||||
row.operator(
|
||||
Tidy3DWebUploadOperator.bl_idname,
|
||||
text="Upload Sim",
|
||||
)
|
||||
tree_lock_icon = "LOCKED" if self.lock_tree else "UNLOCKED"
|
||||
row.prop(self, "lock_tree", toggle=True, icon=tree_lock_icon, text="")
|
||||
|
||||
# Row: Run Simulation
|
||||
row = layout.row(align=True)
|
||||
if is_authed and has_uploaded_task_id:
|
||||
run_sim_text = f"Run Sim (~{estimated_task_cost(self.uploaded_task_id):.3f} credits)"
|
||||
else:
|
||||
run_sim_text = f"Run Sim"
|
||||
|
||||
row.operator(
|
||||
RunUploadedTidy3DSim.bl_idname,
|
||||
text=run_sim_text,
|
||||
)
|
||||
if has_uploaded_task_id:
|
||||
tree_lock_icon = "LOOP_BACK"
|
||||
row.operator(
|
||||
ReleaseTidy3DExportOperator.bl_idname,
|
||||
icon="LOOP_BACK",
|
||||
text="",
|
||||
)
|
||||
else:
|
||||
row.operator(
|
||||
Tidy3DLoadUploadedOperator.bl_idname,
|
||||
icon="TRIA_UP_BAR",
|
||||
text="",
|
||||
)
|
||||
|
||||
# Row: Simulation Progress
|
||||
if is_authed and has_uploaded_task_id:
|
||||
progress = {
|
||||
"draft": (0.0, "Waiting to Run..."),
|
||||
"initialized": (0.0, "Initializing..."),
|
||||
"queued": (0.0, "Queued..."),
|
||||
"preprocessing": (0.05, "Pre-processing..."),
|
||||
"running": (0.2, "Running..."),
|
||||
"postprocessing": (0.85, "Post-processing..."),
|
||||
"success": (1.0, f"Success (={billed_task_cost(self.uploaded_task_id)} credits)"),
|
||||
"error": (1.0, f"Error (={billed_task_cost(self.uploaded_task_id)} credits)"),
|
||||
}[task_status(self.uploaded_task_id)]
|
||||
|
||||
layout.separator()
|
||||
row = layout.row(align=True)
|
||||
row.progress(
|
||||
factor=progress[0],
|
||||
type="BAR",
|
||||
text=progress[1],
|
||||
)
|
||||
|
||||
####################
|
||||
# - Output Methods
|
||||
####################
|
||||
@base.computes_output_socket(
|
||||
"Cloud Task",
|
||||
input_sockets={"Cloud Task"},
|
||||
)
|
||||
def compute_cloud_task(self, input_sockets: dict) -> str | None:
|
||||
if self.uploaded_task_id: return self.uploaded_task_id
|
||||
return None
|
||||
|
||||
####################
|
||||
# - Update
|
||||
####################
|
||||
@base.on_value_changed(socket_name="FDTD Sim")
|
||||
def on_value_changed__fdtd_sim(self):
|
||||
estimated_task_cost.cache_clear()
|
||||
task_status.cache_clear()
|
||||
billed_task_cost.cache_clear()
|
||||
|
||||
@base.on_value_changed(socket_name="Cloud Task")
|
||||
def on_value_changed__cloud_task(self):
|
||||
estimated_task_cost.cache_clear()
|
||||
task_status.cache_clear()
|
||||
billed_task_cost.cache_clear()
|
||||
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
####################
|
||||
BL_REGISTER = [
|
||||
Tidy3DWebUploadOperator,
|
||||
Tidy3DTaskStatusModalOperator,
|
||||
RunUploadedTidy3DSim,
|
||||
Tidy3DLoadUploadedOperator,
|
||||
ReleaseTidy3DExportOperator,
|
||||
Tidy3DWebExporterNode,
|
||||
]
|
||||
BL_NODES = {
|
||||
ct.NodeType.Tidy3DWebExporter: (
|
||||
ct.NodeCategory.MAXWELLSIM_OUTPUTS_EXPORTERS
|
||||
)
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
BL_REGISTER = []
|
||||
BL_NODES = {}
|
|
@ -0,0 +1,97 @@
|
|||
import functools
|
||||
import typing as typ
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import bpy
|
||||
import sympy as sp
|
||||
import pydantic as pyd
|
||||
import tidy3d as td
|
||||
|
||||
from ... import contracts as ct
|
||||
from ... import sockets
|
||||
from .. import base
|
||||
|
||||
|
||||
class ConsoleViewOperator(bpy.types.Operator):
|
||||
bl_idname = "blender_maxwell.console_view_operator"
|
||||
bl_label = "View Plots"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
node = context.node
|
||||
node.print_data_to_console()
|
||||
return {'FINISHED'}
|
||||
|
||||
class RefreshPlotViewOperator(bpy.types.Operator):
|
||||
bl_idname = "blender_maxwell.refresh_plot_view_operator"
|
||||
bl_label = "Refresh Plots"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
node = context.node
|
||||
node.trigger_action("value_changed", "Data")
|
||||
return {'FINISHED'}
|
||||
|
||||
####################
|
||||
# - Node
|
||||
####################
|
||||
class ViewerNode(base.MaxwellSimNode):
|
||||
node_type = ct.NodeType.Viewer
|
||||
bl_label = "Viewer"
|
||||
|
||||
input_sockets = {
|
||||
"Data": sockets.AnySocketDef(),
|
||||
}
|
||||
|
||||
####################
|
||||
# - UI
|
||||
####################
|
||||
def draw_operators(self, context, layout):
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Console")
|
||||
row.operator(ConsoleViewOperator.bl_idname, text="Print")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Plot")
|
||||
row.operator(RefreshPlotViewOperator.bl_idname, text="", icon="FILE_REFRESH")
|
||||
|
||||
####################
|
||||
# - Methods
|
||||
####################
|
||||
def print_data_to_console(self):
|
||||
if not (data := self._compute_input("Data")):
|
||||
return
|
||||
|
||||
if isinstance(data, sp.Basic):
|
||||
sp.pprint(data, use_unicode=True)
|
||||
|
||||
print(str(data))
|
||||
|
||||
####################
|
||||
# - Update
|
||||
####################
|
||||
@base.on_value_changed(socket_name="Data")
|
||||
def on_value_changed__data(self):
|
||||
self.trigger_action("show_plot")
|
||||
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
####################
|
||||
BL_REGISTER = [
|
||||
ConsoleViewOperator,
|
||||
RefreshPlotViewOperator,
|
||||
ViewerNode,
|
||||
]
|
||||
BL_NODES = {
|
||||
ct.NodeType.Viewer: (
|
||||
ct.NodeCategory.MAXWELLSIM_OUTPUTS
|
||||
)
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
from . import viewer_3d
|
||||
from . import value_viewer
|
||||
from . import console_viewer
|
||||
|
||||
BL_REGISTER = [
|
||||
*viewer_3d.BL_REGISTER,
|
||||
*value_viewer.BL_REGISTER,
|
||||
*console_viewer.BL_REGISTER,
|
||||
]
|
||||
BL_NODES = {
|
||||
**viewer_3d.BL_NODES,
|
||||
**value_viewer.BL_NODES,
|
||||
**console_viewer.BL_NODES,
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
####################
|
||||
# - Blender Registration
|
||||
####################
|
||||
BL_REGISTER = []
|
||||
BL_NODES = {}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
####################
|
||||
# - Blender Registration
|
||||
####################
|
||||
BL_REGISTER = []
|
||||
BL_NODES = {}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
import typing as typ
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import bpy
|
||||
import sympy as sp
|
||||
import pydantic as pyd
|
||||
import tidy3d as td
|
||||
|
||||
from .... import contracts
|
||||
from .... import sockets
|
||||
from ... import base
|
||||
|
||||
INTERNAL_GEONODES = {
|
||||
|
||||
}
|
||||
|
||||
####################
|
||||
# - Node
|
||||
####################
|
||||
class Viewer3DNode(base.MaxwellSimTreeNode):
|
||||
node_type = contracts.NodeType.Viewer3D
|
||||
|
||||
bl_label = "3D Viewer"
|
||||
|
||||
input_sockets = {
|
||||
"data": sockets.AnySocketDef(
|
||||
label="Data",
|
||||
),
|
||||
}
|
||||
output_sockets = {}
|
||||
|
||||
####################
|
||||
# - Update
|
||||
####################
|
||||
def update_cb(self):
|
||||
pass
|
||||
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
####################
|
||||
BL_REGISTER = [
|
||||
Viewer3DNode,
|
||||
]
|
||||
BL_NODES = {
|
||||
contracts.NodeType.Viewer3D: (
|
||||
contracts.NodeCategory.MAXWELLSIM_OUTPUTS_VIEWERS
|
||||
)
|
||||
}
|
|
@ -1,15 +1,19 @@
|
|||
from . import sim_grid
|
||||
from . import sim_grid_axes
|
||||
from . import sim_domain
|
||||
|
||||
#from . import sim_grid
|
||||
#from . import sim_grid_axes
|
||||
|
||||
from . import fdtd_sim
|
||||
|
||||
BL_REGISTER = [
|
||||
*sim_grid.BL_REGISTER,
|
||||
*sim_grid_axes.BL_REGISTER,
|
||||
*sim_domain.BL_REGISTER,
|
||||
# *sim_grid.BL_REGISTER,
|
||||
# *sim_grid_axes.BL_REGISTER,
|
||||
*fdtd_sim.BL_REGISTER,
|
||||
]
|
||||
BL_NODES = {
|
||||
**sim_grid.BL_NODES,
|
||||
**sim_grid_axes.BL_NODES,
|
||||
**sim_domain.BL_NODES,
|
||||
# **sim_grid.BL_NODES,
|
||||
# **sim_grid_axes.BL_NODES,
|
||||
**fdtd_sim.BL_NODES,
|
||||
}
|
||||
|
|
|
@ -2,71 +2,57 @@ import tidy3d as td
|
|||
import sympy as sp
|
||||
import sympy.physics.units as spu
|
||||
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
from ... import sockets
|
||||
from .. import base
|
||||
|
||||
class FDTDSimNode(base.MaxwellSimTreeNode):
|
||||
node_type = contracts.NodeType.FDTDSim
|
||||
|
||||
class FDTDSimNode(base.MaxwellSimNode):
|
||||
node_type = ct.NodeType.FDTDSim
|
||||
bl_label = "FDTD Simulation"
|
||||
#bl_icon = ...
|
||||
|
||||
####################
|
||||
# - Sockets
|
||||
####################
|
||||
input_sockets = {
|
||||
"run_time": sockets.PhysicalTimeSocketDef(
|
||||
label="Run Time",
|
||||
),
|
||||
"domain": sockets.PhysicalSize3DSocketDef(
|
||||
label="Domain",
|
||||
),
|
||||
"ambient_medium": sockets.MaxwellMediumSocketDef(
|
||||
label="Ambient Medium",
|
||||
),
|
||||
"source": sockets.MaxwellSourceSocketDef(
|
||||
label="Source",
|
||||
),
|
||||
"structure": sockets.MaxwellStructureSocketDef(
|
||||
label="Structure",
|
||||
),
|
||||
"bound": sockets.MaxwellBoundBoxSocketDef(
|
||||
label="Bound",
|
||||
),
|
||||
"Domain": sockets.MaxwellSimDomainSocketDef(),
|
||||
"BCs": sockets.MaxwellBoundBoxSocketDef(),
|
||||
"Sources": sockets.MaxwellSourceSocketDef(),
|
||||
"Structures": sockets.MaxwellStructureSocketDef(),
|
||||
"Monitors": sockets.MaxwellMonitorSocketDef(),
|
||||
}
|
||||
output_sockets = {
|
||||
"fdtd_sim": sockets.MaxwellFDTDSimSocketDef(
|
||||
label="FDTD Sim",
|
||||
),
|
||||
"FDTD Sim": sockets.MaxwellFDTDSimSocketDef(),
|
||||
}
|
||||
|
||||
####################
|
||||
# - Output Socket Computation
|
||||
####################
|
||||
@base.computes_output_socket("fdtd_sim")
|
||||
def compute_simulation(self: contracts.NodeTypeProtocol) -> td.Simulation:
|
||||
_run_time = self.compute_input("run_time")
|
||||
_domain = self.compute_input("domain")
|
||||
ambient_medium = self.compute_input("ambient_medium")
|
||||
structures = [self.compute_input("structure")]
|
||||
sources = [self.compute_input("source")]
|
||||
bound = self.compute_input("bound")
|
||||
@base.computes_output_socket(
|
||||
"FDTD Sim",
|
||||
kind=ct.DataFlowKind.Value,
|
||||
input_sockets={
|
||||
"Sources", "Structures", "Domain", "BCs", "Monitors"
|
||||
},
|
||||
)
|
||||
def compute_fdtd_sim(self, input_sockets: dict) -> sp.Expr:
|
||||
sim_domain = input_sockets["Domain"]
|
||||
sources = input_sockets["Sources"]
|
||||
structures = input_sockets["Structures"]
|
||||
bounds = input_sockets["BCs"]
|
||||
monitors = input_sockets["Monitors"]
|
||||
|
||||
run_time = spu.convert_to(_run_time, spu.second) / spu.second
|
||||
domain = tuple(spu.convert_to(_domain, spu.um) / spu.um)
|
||||
if not isinstance(sources, list):
|
||||
sources = [sources]
|
||||
if not isinstance(structures, list):
|
||||
structures = [structures]
|
||||
|
||||
return td.Simulation(
|
||||
size=domain,
|
||||
medium=ambient_medium,
|
||||
**sim_domain, ## run_time=, size=, grid=, medium=
|
||||
structures=structures,
|
||||
sources=sources,
|
||||
boundary_spec=bound,
|
||||
run_time=run_time,
|
||||
boundary_spec=bounds,
|
||||
)
|
||||
|
||||
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
####################
|
||||
|
@ -74,7 +60,7 @@ BL_REGISTER = [
|
|||
FDTDSimNode,
|
||||
]
|
||||
BL_NODES = {
|
||||
contracts.NodeType.FDTDSim: (
|
||||
contracts.NodeCategory.MAXWELLSIM_SIMS
|
||||
ct.NodeType.FDTDSim: (
|
||||
ct.NodeCategory.MAXWELLSIM_SIMS
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import bpy
|
||||
import sympy as sp
|
||||
import sympy.physics.units as spu
|
||||
import scipy as sc
|
||||
|
||||
from ... import contracts as ct
|
||||
from ... import sockets
|
||||
from .. import base
|
||||
|
||||
class SimDomainNode(base.MaxwellSimNode):
|
||||
node_type = ct.NodeType.SimDomain
|
||||
bl_label = "Sim Domain"
|
||||
|
||||
input_sockets = {
|
||||
"Duration": sockets.PhysicalTimeSocketDef(
|
||||
default_value = 5 * spu.ps,
|
||||
default_unit = spu.ps,
|
||||
),
|
||||
"Size": sockets.PhysicalSize3DSocketDef(),
|
||||
"Grid": sockets.MaxwellSimGridSocketDef(),
|
||||
"Ambient Medium": sockets.MaxwellMediumSocketDef(),
|
||||
}
|
||||
output_sockets = {
|
||||
"Domain": sockets.MaxwellSimDomainSocketDef(),
|
||||
}
|
||||
|
||||
####################
|
||||
# - Callbacks
|
||||
####################
|
||||
@base.computes_output_socket(
|
||||
"Domain",
|
||||
input_sockets={"Duration", "Size", "Grid", "Ambient Medium"},
|
||||
)
|
||||
def compute_sim_domain(self, input_sockets: dict) -> sp.Expr:
|
||||
if all([
|
||||
(_duration := input_sockets["Duration"]),
|
||||
(_size := input_sockets["Size"]),
|
||||
(grid := input_sockets["Grid"]),
|
||||
(medium := input_sockets["Ambient Medium"]),
|
||||
]):
|
||||
duration = spu.convert_to(_duration, spu.second) / spu.second
|
||||
size = tuple(spu.convert_to(_size, spu.um) / spu.um)
|
||||
return dict(
|
||||
run_time=duration,
|
||||
size=size,
|
||||
grid_spec=grid,
|
||||
medium=medium,
|
||||
)
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
####################
|
||||
BL_REGISTER = [
|
||||
SimDomainNode,
|
||||
]
|
||||
BL_NODES = {
|
||||
ct.NodeType.SimDomain: (
|
||||
ct.NodeCategory.MAXWELLSIM_SIMS
|
||||
)
|
||||
}
|
|
@ -1,27 +1,27 @@
|
|||
from . import temporal_shapes
|
||||
|
||||
from . import point_dipole_source
|
||||
from . import uniform_current_source
|
||||
#from . import uniform_current_source
|
||||
from . import plane_wave_source
|
||||
from . import gaussian_beam_source
|
||||
from . import astigmatic_gaussian_beam_source
|
||||
from . import tfsf_source
|
||||
#from . import gaussian_beam_source
|
||||
#from . import astigmatic_gaussian_beam_source
|
||||
#from . import tfsf_source
|
||||
|
||||
BL_REGISTER = [
|
||||
*temporal_shapes.BL_REGISTER,
|
||||
*point_dipole_source.BL_REGISTER,
|
||||
*uniform_current_source.BL_REGISTER,
|
||||
# *uniform_current_source.BL_REGISTER,
|
||||
*plane_wave_source.BL_REGISTER,
|
||||
*gaussian_beam_source.BL_REGISTER,
|
||||
*astigmatic_gaussian_beam_source.BL_REGISTER,
|
||||
*tfsf_source.BL_REGISTER,
|
||||
# *gaussian_beam_source.BL_REGISTER,
|
||||
# *astigmatic_gaussian_beam_source.BL_REGISTER,
|
||||
# *tfsf_source.BL_REGISTER,
|
||||
]
|
||||
BL_NODES = {
|
||||
**temporal_shapes.BL_NODES,
|
||||
**point_dipole_source.BL_NODES,
|
||||
**uniform_current_source.BL_NODES,
|
||||
# **uniform_current_source.BL_NODES,
|
||||
**plane_wave_source.BL_NODES,
|
||||
**gaussian_beam_source.BL_NODES,
|
||||
**astigmatic_gaussian_beam_source.BL_NODES,
|
||||
**tfsf_source.BL_NODES,
|
||||
# **gaussian_beam_source.BL_NODES,
|
||||
# **astigmatic_gaussian_beam_source.BL_NODES,
|
||||
# **tfsf_source.BL_NODES,
|
||||
}
|
||||
|
|
|
@ -4,79 +4,93 @@ import tidy3d as td
|
|||
import sympy as sp
|
||||
import sympy.physics.units as spu
|
||||
|
||||
from ... import contracts
|
||||
import bpy
|
||||
|
||||
from ... import contracts as ct
|
||||
from ... import sockets
|
||||
from .. import base
|
||||
|
||||
class PlaneWaveSourceNode(base.MaxwellSimTreeNode):
|
||||
node_type = contracts.NodeType.PlaneWaveSource
|
||||
|
||||
class PlaneWaveSourceNode(base.MaxwellSimNode):
|
||||
node_type = ct.NodeType.PlaneWaveSource
|
||||
bl_label = "Plane Wave Source"
|
||||
#bl_icon = ...
|
||||
|
||||
####################
|
||||
# - Sockets
|
||||
####################
|
||||
input_sockets = {
|
||||
"temporal_shape": sockets.MaxwellTemporalShapeSocketDef(
|
||||
label="Temporal Shape",
|
||||
),
|
||||
"center": sockets.PhysicalPoint3DSocketDef(
|
||||
label="Center",
|
||||
),
|
||||
"size": sockets.PhysicalSize3DSocketDef(
|
||||
label="Size",
|
||||
),
|
||||
"direction": sockets.BoolSocketDef(
|
||||
label="+ Direction?",
|
||||
"Temporal Shape": sockets.MaxwellTemporalShapeSocketDef(),
|
||||
"Center": sockets.PhysicalPoint3DSocketDef(),
|
||||
"Direction": sockets.BoolSocketDef(
|
||||
default_value=True,
|
||||
),
|
||||
"angle_theta": sockets.PhysicalAngleSocketDef(
|
||||
label="θ",
|
||||
),
|
||||
"angle_phi": sockets.PhysicalAngleSocketDef(
|
||||
label="φ",
|
||||
),
|
||||
"angle_pol": sockets.PhysicalAngleSocketDef(
|
||||
label="Pol Angle",
|
||||
),
|
||||
"Pol": sockets.PhysicalPolSocketDef(),
|
||||
}
|
||||
output_sockets = {
|
||||
"source": sockets.MaxwellSourceSocketDef(
|
||||
label="Source",
|
||||
),
|
||||
"Source": sockets.MaxwellSourceSocketDef(),
|
||||
}
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
inj_axis: bpy.props.EnumProperty(
|
||||
name="Injection Axis",
|
||||
description="Axis to inject plane wave along",
|
||||
items=[
|
||||
("X", "X", "X-Axis"),
|
||||
("Y", "Y", "Y-Axis"),
|
||||
("Z", "Z", "Z-Axis"),
|
||||
],
|
||||
default="Y",
|
||||
update=(lambda self, context: self.sync_prop("inj_axis")),
|
||||
)
|
||||
|
||||
####################
|
||||
# - Output Socket Computation
|
||||
####################
|
||||
@base.computes_output_socket("source")
|
||||
def compute_source(self: contracts.NodeTypeProtocol) -> td.PointDipole:
|
||||
temporal_shape = self.compute_input("temporal_shape")
|
||||
_center = self.compute_input("center")
|
||||
_size = self.compute_input("size")
|
||||
_direction = self.compute_input("direction")
|
||||
_angle_theta = self.compute_input("angle_theta")
|
||||
_angle_phi = self.compute_input("angle_phi")
|
||||
_angle_pol = self.compute_input("angle_pol")
|
||||
@base.computes_output_socket(
|
||||
"Source",
|
||||
input_sockets={"Temporal Shape", "Center", "Direction", "Pol"},
|
||||
props={"inj_axis"},
|
||||
)
|
||||
def compute_source(self, input_sockets: dict, props: dict):
|
||||
temporal_shape = input_sockets["Temporal Shape"]
|
||||
_center = input_sockets["Center"]
|
||||
_direction = input_sockets["Direction"]
|
||||
_inj_axis = props["inj_axis"]
|
||||
pol = input_sockets["Pol"]
|
||||
|
||||
direction = {
|
||||
False: "-",
|
||||
True: "+",
|
||||
}[_direction]
|
||||
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
|
||||
size = tuple(
|
||||
0 if val == 1.0 else math.inf
|
||||
for val in spu.convert_to(_size, spu.um) / spu.um
|
||||
)
|
||||
angle_theta = spu.convert_to(_angle_theta, spu.rad) / spu.rad
|
||||
angle_phi = spu.convert_to(_angle_phi, spu.rad) / spu.rad
|
||||
angle_pol = spu.convert_to(_angle_pol, spu.rad) / spu.rad
|
||||
size = {
|
||||
"X": (0, math.inf, math.inf),
|
||||
"Y": (math.inf, 0, math.inf),
|
||||
"Z": (math.inf, math.inf, 0),
|
||||
}[_inj_axis]
|
||||
|
||||
S0, S1, S2, S3 = tuple(pol)
|
||||
|
||||
chi = 0.5 * sp.atan2(S2, S1)
|
||||
psi = 0.5 * sp.asin(S3/S0)
|
||||
## chi: Pol angle
|
||||
## psi: Ellipticity
|
||||
|
||||
## TODO: Something's wonky.
|
||||
#angle_theta = chi
|
||||
#angle_phi = psi
|
||||
pol_angle = sp.pi/2 - chi
|
||||
|
||||
# Display the results
|
||||
return td.PlaneWave(
|
||||
center=center,
|
||||
center=tuple(_center),
|
||||
size=size,
|
||||
source_time=temporal_shape,
|
||||
direction="+" if _direction else "-",
|
||||
angle_theta=angle_theta,
|
||||
angle_phi=angle_phi,
|
||||
pol_angle=angle_pol,
|
||||
#angle_theta=angle_theta,
|
||||
#angle_phi=angle_phi,
|
||||
#pol_angle=pol_angle,
|
||||
)
|
||||
|
||||
|
||||
|
@ -88,7 +102,7 @@ BL_REGISTER = [
|
|||
PlaneWaveSourceNode,
|
||||
]
|
||||
BL_NODES = {
|
||||
contracts.NodeType.PlaneWaveSource: (
|
||||
contracts.NodeCategory.MAXWELLSIM_SOURCES
|
||||
ct.NodeType.PlaneWaveSource: (
|
||||
ct.NodeCategory.MAXWELLSIM_SOURCES
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,59 +1,81 @@
|
|||
import typing as typ
|
||||
import tidy3d as td
|
||||
import sympy as sp
|
||||
import sympy.physics.units as spu
|
||||
|
||||
from ... import contracts
|
||||
import bpy
|
||||
|
||||
from ... import contracts as ct
|
||||
from ... import sockets
|
||||
from .. import base
|
||||
|
||||
class PointDipoleSourceNode(base.MaxwellSimTreeNode):
|
||||
node_type = contracts.NodeType.PointDipoleSource
|
||||
|
||||
class PointDipoleSourceNode(base.MaxwellSimNode):
|
||||
node_type = ct.NodeType.PointDipoleSource
|
||||
bl_label = "Point Dipole Source"
|
||||
#bl_icon = ...
|
||||
|
||||
####################
|
||||
# - Sockets
|
||||
####################
|
||||
input_sockets = {
|
||||
"polarization": sockets.PhysicalPolSocketDef(
|
||||
label="Polarization",
|
||||
),
|
||||
"temporal_shape": sockets.MaxwellTemporalShapeSocketDef(
|
||||
label="Temporal Shape",
|
||||
),
|
||||
"center": sockets.PhysicalPoint3DSocketDef(
|
||||
label="Center",
|
||||
),
|
||||
"interpolate": sockets.BoolSocketDef(
|
||||
label="Interpolate",
|
||||
"Temporal Shape": sockets.MaxwellTemporalShapeSocketDef(),
|
||||
"Center": sockets.PhysicalPoint3DSocketDef(),
|
||||
"Interpolate": sockets.BoolSocketDef(
|
||||
default_value=True,
|
||||
),
|
||||
}
|
||||
output_sockets = {
|
||||
"source": sockets.MaxwellSourceSocketDef(
|
||||
label="Source",
|
||||
),
|
||||
"Source": sockets.MaxwellSourceSocketDef(),
|
||||
}
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
pol_axis: bpy.props.EnumProperty(
|
||||
name="Polarization Axis",
|
||||
description="Polarization Axis",
|
||||
items=[
|
||||
("EX", "Ex", "Electric field in x-dir"),
|
||||
("EY", "Ey", "Electric field in y-dir"),
|
||||
("EZ", "Ez", "Electric field in z-dir"),
|
||||
],
|
||||
default="EX",
|
||||
update=(lambda self, context: self.sync_prop("pol_axis")),
|
||||
)
|
||||
|
||||
####################
|
||||
# - UI
|
||||
####################
|
||||
def draw_props(self, context, layout):
|
||||
layout.prop(self, "pol_axis", text="Pol Axis")
|
||||
|
||||
####################
|
||||
# - Output Socket Computation
|
||||
####################
|
||||
@base.computes_output_socket("source")
|
||||
def compute_source(self: contracts.NodeTypeProtocol) -> td.PointDipole:
|
||||
polarization = self.compute_input("polarization")
|
||||
temporal_shape = self.compute_input("temporal_shape")
|
||||
_center = self.compute_input("center")
|
||||
interpolate = self.compute_input("interpolate")
|
||||
@base.computes_output_socket(
|
||||
"Source",
|
||||
input_sockets={"Temporal Shape", "Center", "Interpolate"},
|
||||
props={"pol_axis"},
|
||||
)
|
||||
def compute_source(self, input_sockets: dict[str, typ.Any], props: dict[str, typ.Any]) -> td.PointDipole:
|
||||
pol_axis = {
|
||||
"EX": "Ex",
|
||||
"EY": "Ey",
|
||||
"EZ": "Ez",
|
||||
}[props["pol_axis"]]
|
||||
|
||||
temporal_shape = input_sockets["Temporal Shape"]
|
||||
_center = input_sockets["Center"]
|
||||
interpolate = input_sockets["Interpolate"]
|
||||
|
||||
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
|
||||
|
||||
return td.PointDipole(
|
||||
_res = td.PointDipole(
|
||||
center=center,
|
||||
source_time=temporal_shape,
|
||||
interpolate=interpolate,
|
||||
polarization=polarization,
|
||||
polarization=pol_axis,
|
||||
)
|
||||
return _res
|
||||
|
||||
|
||||
|
||||
|
@ -64,7 +86,7 @@ BL_REGISTER = [
|
|||
PointDipoleSourceNode,
|
||||
]
|
||||
BL_NODES = {
|
||||
contracts.NodeType.PointDipoleSource: (
|
||||
contracts.NodeCategory.MAXWELLSIM_SOURCES
|
||||
ct.NodeType.PointDipoleSource: (
|
||||
ct.NodeCategory.MAXWELLSIM_SOURCES
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
from . import gaussian_pulse_temporal_shape
|
||||
from . import continuous_wave_temporal_shape
|
||||
from . import array_temporal_shape
|
||||
#from . import continuous_wave_temporal_shape
|
||||
#from . import array_temporal_shape
|
||||
|
||||
BL_REGISTER = [
|
||||
*gaussian_pulse_temporal_shape.BL_REGISTER,
|
||||
*continuous_wave_temporal_shape.BL_REGISTER,
|
||||
*array_temporal_shape.BL_REGISTER,
|
||||
# *continuous_wave_temporal_shape.BL_REGISTER,
|
||||
# *array_temporal_shape.BL_REGISTER,
|
||||
]
|
||||
BL_NODES = {
|
||||
**gaussian_pulse_temporal_shape.BL_NODES,
|
||||
**continuous_wave_temporal_shape.BL_NODES,
|
||||
**array_temporal_shape.BL_NODES,
|
||||
# **continuous_wave_temporal_shape.BL_NODES,
|
||||
# **array_temporal_shape.BL_NODES,
|
||||
}
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
import typing as typ
|
||||
|
||||
import tidy3d as td
|
||||
import numpy as np
|
||||
import sympy as sp
|
||||
import sympy.physics.units as spu
|
||||
|
||||
from .... import contracts
|
||||
import bpy
|
||||
|
||||
from ......utils import extra_sympy_units as spuex
|
||||
from .... import contracts as ct
|
||||
from .... import sockets
|
||||
from .... import managed_objs
|
||||
from ... import base
|
||||
|
||||
class GaussianPulseTemporalShapeNode(base.MaxwellSimTreeNode):
|
||||
node_type = contracts.NodeType.GaussianPulseTemporalShape
|
||||
class GaussianPulseTemporalShapeNode(base.MaxwellSimNode):
|
||||
node_type = ct.NodeType.GaussianPulseTemporalShape
|
||||
|
||||
bl_label = "Gaussian Pulse Temporal Shape"
|
||||
#bl_icon = ...
|
||||
|
@ -19,45 +26,85 @@ class GaussianPulseTemporalShapeNode(base.MaxwellSimTreeNode):
|
|||
#"amplitude": sockets.RealNumberSocketDef(
|
||||
# label="Temporal Shape",
|
||||
#), ## Should have a unit of some kind...
|
||||
"phase": sockets.PhysicalAngleSocketDef(
|
||||
label="Phase",
|
||||
"Freq Center": sockets.PhysicalFreqSocketDef(
|
||||
default_value=500 * spuex.terahertz,
|
||||
),
|
||||
"freq_center": sockets.PhysicalFreqSocketDef(
|
||||
label="Freq Center",
|
||||
"Freq Std.": sockets.PhysicalFreqSocketDef(
|
||||
default_value=200 * spuex.terahertz,
|
||||
),
|
||||
"freq_std": sockets.PhysicalFreqSocketDef(
|
||||
label="Freq STD",
|
||||
),
|
||||
"time_delay_rel_ang_freq": sockets.RealNumberSocketDef(
|
||||
label="Time Delay rel. Ang. Freq",
|
||||
"Phase": sockets.PhysicalAngleSocketDef(),
|
||||
"Delay rel. AngFreq": sockets.RealNumberSocketDef(
|
||||
default_value=5.0,
|
||||
),
|
||||
"remove_dc_component": sockets.BoolSocketDef(
|
||||
label="Remove DC",
|
||||
"Remove DC": sockets.BoolSocketDef(
|
||||
default_value=True,
|
||||
),
|
||||
}
|
||||
output_sockets = {
|
||||
"temporal_shape": sockets.MaxwellTemporalShapeSocketDef(
|
||||
label="Temporal Shape",
|
||||
),
|
||||
"Temporal Shape": sockets.MaxwellTemporalShapeSocketDef(),
|
||||
}
|
||||
|
||||
managed_obj_defs = {
|
||||
"amp_time": ct.schemas.ManagedObjDef(
|
||||
mk=lambda name: managed_objs.ManagedBLImage(name),
|
||||
name_prefix="amp_time_",
|
||||
)
|
||||
}
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
plot_time_start: bpy.props.FloatProperty(
|
||||
name="Plot Time Start (ps)",
|
||||
description="The instance ID of a particular MaxwellSimNode instance, used to index caches",
|
||||
default=0.0,
|
||||
update=(lambda self, context: self.sync_prop("plot_time_start", context)),
|
||||
)
|
||||
plot_time_end: bpy.props.FloatProperty(
|
||||
name="Plot Time End (ps)",
|
||||
description="The instance ID of a particular MaxwellSimNode instance, used to index caches",
|
||||
default=5,
|
||||
update=(lambda self, context: self.sync_prop("plot_time_start", context)),
|
||||
)
|
||||
|
||||
####################
|
||||
# - UI
|
||||
####################
|
||||
def draw_props(self, context, layout):
|
||||
layout.label(text="Plot Settings")
|
||||
split = layout.split(factor=0.6)
|
||||
|
||||
col = split.column()
|
||||
col.label(text="t-Range (ps)")
|
||||
|
||||
col = split.column()
|
||||
col.prop(self, "plot_time_start", text="")
|
||||
col.prop(self, "plot_time_end", text="")
|
||||
|
||||
####################
|
||||
# - Output Socket Computation
|
||||
####################
|
||||
@base.computes_output_socket("temporal_shape")
|
||||
def compute_source(self: contracts.NodeTypeProtocol) -> td.PointDipole:
|
||||
_phase = self.compute_input("phase")
|
||||
_freq_center = self.compute_input("freq_center")
|
||||
_freq_std = self.compute_input("freq_std")
|
||||
time_delay_rel_ang_freq = self.compute_input("time_delay_rel_ang_freq")
|
||||
remove_dc_component = self.compute_input("remove_dc_component")
|
||||
@base.computes_output_socket(
|
||||
"Temporal Shape",
|
||||
input_sockets={
|
||||
"Freq Center", "Freq Std.", "Phase", "Delay rel. AngFreq",
|
||||
"Remove DC",
|
||||
}
|
||||
)
|
||||
def compute_source(self, input_sockets: dict) -> td.GaussianPulse:
|
||||
if (
|
||||
(_freq_center := input_sockets["Freq Center"]) is None
|
||||
or (_freq_std := input_sockets["Freq Std."]) is None
|
||||
or (_phase := input_sockets["Phase"]) is None
|
||||
or (time_delay_rel_ang_freq := input_sockets["Delay rel. AngFreq"]) is None
|
||||
or (remove_dc_component := input_sockets["Remove DC"]) is None
|
||||
):
|
||||
raise ValueError("Inputs not defined")
|
||||
|
||||
cheating_amplitude = 1.0
|
||||
phase = spu.convert_to(_phase, spu.radian) / spu.radian
|
||||
freq_center = spu.convert_to(_freq_center, spu.hertz) / spu.hertz
|
||||
freq_std = spu.convert_to(_freq_std, spu.hertz) / spu.hertz
|
||||
phase = spu.convert_to(_phase, spu.radian) / spu.radian
|
||||
|
||||
return td.GaussianPulse(
|
||||
amplitude=cheating_amplitude,
|
||||
|
@ -68,6 +115,29 @@ class GaussianPulseTemporalShapeNode(base.MaxwellSimTreeNode):
|
|||
remove_dc_component=remove_dc_component,
|
||||
)
|
||||
|
||||
@base.on_show_plot(
|
||||
managed_objs={"amp_time"},
|
||||
props={"plot_time_start", "plot_time_end"},
|
||||
output_sockets={"Temporal Shape"},
|
||||
stop_propagation=True,
|
||||
)
|
||||
def on_show_plot(
|
||||
self,
|
||||
managed_objs: dict[str, ct.schemas.ManagedObj],
|
||||
output_sockets: dict[str, typ.Any],
|
||||
props: dict[str, typ.Any],
|
||||
):
|
||||
temporal_shape = output_sockets["Temporal Shape"]
|
||||
plot_time_start = props["plot_time_start"] * 1e-15
|
||||
plot_time_end = props["plot_time_end"] * 1e-15
|
||||
|
||||
times = np.linspace(plot_time_start, plot_time_end)
|
||||
|
||||
managed_objs["amp_time"].mpl_plot_to_image(
|
||||
lambda ax: temporal_shape.plot_spectrum(times, ax=ax),
|
||||
bl_select=True,
|
||||
)
|
||||
|
||||
|
||||
|
||||
####################
|
||||
|
@ -77,7 +147,7 @@ BL_REGISTER = [
|
|||
GaussianPulseTemporalShapeNode,
|
||||
]
|
||||
BL_NODES = {
|
||||
contracts.NodeType.GaussianPulseTemporalShape: (
|
||||
contracts.NodeCategory.MAXWELLSIM_SOURCES_TEMPORALSHAPES
|
||||
ct.NodeType.GaussianPulseTemporalShape: (
|
||||
ct.NodeCategory.MAXWELLSIM_SOURCES_TEMPORALSHAPES
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
from . import object_structure
|
||||
from . import geonodes_structure
|
||||
from . import scripted_structure
|
||||
#from . import object_structure
|
||||
#from . import geonodes_structure
|
||||
#from . import scripted_structure
|
||||
|
||||
from . import primitives
|
||||
|
||||
BL_REGISTER = [
|
||||
*object_structure.BL_REGISTER,
|
||||
*geonodes_structure.BL_REGISTER,
|
||||
*scripted_structure.BL_REGISTER,
|
||||
# *object_structure.BL_REGISTER,
|
||||
# *geonodes_structure.BL_REGISTER,
|
||||
# *scripted_structure.BL_REGISTER,
|
||||
*primitives.BL_REGISTER,
|
||||
]
|
||||
BL_NODES = {
|
||||
**object_structure.BL_NODES,
|
||||
**geonodes_structure.BL_NODES,
|
||||
**scripted_structure.BL_NODES,
|
||||
# **object_structure.BL_NODES,
|
||||
# **geonodes_structure.BL_NODES,
|
||||
# **scripted_structure.BL_NODES,
|
||||
**primitives.BL_NODES,
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
from . import box_structure
|
||||
from . import cylinder_structure
|
||||
from . import sphere_structure
|
||||
#from . import cylinder_structure
|
||||
#from . import sphere_structure
|
||||
|
||||
BL_REGISTER = [
|
||||
*box_structure.BL_REGISTER,
|
||||
*cylinder_structure.BL_REGISTER,
|
||||
*sphere_structure.BL_REGISTER,
|
||||
# *cylinder_structure.BL_REGISTER,
|
||||
# *sphere_structure.BL_REGISTER,
|
||||
]
|
||||
BL_NODES = {
|
||||
**box_structure.BL_NODES,
|
||||
**cylinder_structure.BL_NODES,
|
||||
**sphere_structure.BL_NODES,
|
||||
# **cylinder_structure.BL_NODES,
|
||||
# **sphere_structure.BL_NODES,
|
||||
}
|
||||
|
|
|
@ -2,43 +2,37 @@ import tidy3d as td
|
|||
import sympy as sp
|
||||
import sympy.physics.units as spu
|
||||
|
||||
from .... import contracts
|
||||
from .... import contracts as ct
|
||||
from .... import sockets
|
||||
from ... import base
|
||||
|
||||
class BoxStructureNode(base.MaxwellSimTreeNode):
|
||||
node_type = contracts.NodeType.BoxStructure
|
||||
class BoxStructureNode(base.MaxwellSimNode):
|
||||
node_type = ct.NodeType.BoxStructure
|
||||
bl_label = "Box Structure"
|
||||
#bl_icon = ...
|
||||
|
||||
####################
|
||||
# - Sockets
|
||||
####################
|
||||
input_sockets = {
|
||||
"medium": sockets.MaxwellMediumSocketDef(
|
||||
label="Medium",
|
||||
),
|
||||
"center": sockets.PhysicalPoint3DSocketDef(
|
||||
label="Center",
|
||||
),
|
||||
"size": sockets.PhysicalSize3DSocketDef(
|
||||
label="Size",
|
||||
),
|
||||
"Medium": sockets.MaxwellMediumSocketDef(),
|
||||
"Center": sockets.PhysicalPoint3DSocketDef(),
|
||||
"Size": sockets.PhysicalSize3DSocketDef(),
|
||||
}
|
||||
output_sockets = {
|
||||
"structure": sockets.MaxwellStructureSocketDef(
|
||||
label="Structure",
|
||||
),
|
||||
"Structure": sockets.MaxwellStructureSocketDef(),
|
||||
}
|
||||
|
||||
####################
|
||||
# - Output Socket Computation
|
||||
####################
|
||||
@base.computes_output_socket("structure")
|
||||
def compute_simulation(self: contracts.NodeTypeProtocol) -> td.Box:
|
||||
medium = self.compute_input("medium")
|
||||
_center = self.compute_input("center")
|
||||
_size = self.compute_input("size")
|
||||
@base.computes_output_socket(
|
||||
"Structure",
|
||||
input_sockets={"Medium", "Center", "Size"},
|
||||
)
|
||||
def compute_simulation(self, input_sockets: dict) -> td.Box:
|
||||
medium = input_sockets["Medium"]
|
||||
_center = input_sockets["Center"]
|
||||
_size = input_sockets["Size"]
|
||||
|
||||
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
|
||||
size = tuple(spu.convert_to(_size, spu.um) / spu.um)
|
||||
|
@ -60,7 +54,7 @@ BL_REGISTER = [
|
|||
BoxStructureNode,
|
||||
]
|
||||
BL_NODES = {
|
||||
contracts.NodeType.BoxStructure: (
|
||||
contracts.NodeCategory.MAXWELLSIM_STRUCTURES_PRIMITIVES
|
||||
ct.NodeType.BoxStructure: (
|
||||
ct.NodeCategory.MAXWELLSIM_STRUCTURES_PRIMITIVES
|
||||
)
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ from ... import contracts
|
|||
from ... import sockets
|
||||
from .. import base
|
||||
|
||||
class CombineNode(base.MaxwellSimTreeNode):
|
||||
class CombineNode(base.MaxwellSimNode):
|
||||
node_type = contracts.NodeType.Combine
|
||||
bl_label = "Combine"
|
||||
#bl_icon = ...
|
||||
|
|
|
@ -31,23 +31,19 @@ PhysicalAccelScalarSocketDef = physical.PhysicalAccelScalarSocketDef
|
|||
PhysicalForceScalarSocketDef = physical.PhysicalForceScalarSocketDef
|
||||
PhysicalPolSocketDef = physical.PhysicalPolSocketDef
|
||||
PhysicalFreqSocketDef = physical.PhysicalFreqSocketDef
|
||||
PhysicalVacWLSocketDef = physical.PhysicalVacWLSocketDef
|
||||
PhysicalSpecRelPermDistSocketDef = physical.PhysicalSpecRelPermDistSocketDef
|
||||
PhysicalSpecPowerDistSocketDef = physical.PhysicalSpecPowerDistSocketDef
|
||||
|
||||
from . import blender
|
||||
BlenderObjectSocketDef = blender.BlenderObjectSocketDef
|
||||
BlenderCollectionSocketDef = blender.BlenderCollectionSocketDef
|
||||
BlenderImageSocketDef = blender.BlenderImageSocketDef
|
||||
BlenderVolumeSocketDef = blender.BlenderVolumeSocketDef
|
||||
BlenderGeoNodesSocketDef = blender.BlenderGeoNodesSocketDef
|
||||
BlenderTextSocketDef = blender.BlenderTextSocketDef
|
||||
BlenderPreviewTargetSocketDef = blender.BlenderPreviewTargetSocketDef
|
||||
|
||||
from . import maxwell
|
||||
MaxwellBoundBoxSocketDef = maxwell.MaxwellBoundBoxSocketDef
|
||||
MaxwellBoundFaceSocketDef = maxwell.MaxwellBoundFaceSocketDef
|
||||
MaxwellMediumSocketDef = maxwell.MaxwellMediumSocketDef
|
||||
MaxwellMediumNonLinearitySocketDef = maxwell.MaxwellMediumNonLinearitySocketDef
|
||||
MaxwellSourceSocketDef = maxwell.MaxwellSourceSocketDef
|
||||
MaxwellTemporalShapeSocketDef = maxwell.MaxwellTemporalShapeSocketDef
|
||||
MaxwellStructureSocketDef = maxwell.MaxwellStructureSocketDef
|
||||
|
@ -55,6 +51,10 @@ MaxwellMonitorSocketDef = maxwell.MaxwellMonitorSocketDef
|
|||
MaxwellFDTDSimSocketDef = maxwell.MaxwellFDTDSimSocketDef
|
||||
MaxwellSimGridSocketDef = maxwell.MaxwellSimGridSocketDef
|
||||
MaxwellSimGridAxisSocketDef = maxwell.MaxwellSimGridAxisSocketDef
|
||||
MaxwellSimDomainSocketDef = maxwell.MaxwellSimDomainSocketDef
|
||||
|
||||
from . import tidy3d
|
||||
Tidy3DCloudTaskSocketDef = tidy3d.Tidy3DCloudTaskSocketDef
|
||||
|
||||
BL_REGISTER = [
|
||||
*basic.BL_REGISTER,
|
||||
|
@ -63,4 +63,5 @@ BL_REGISTER = [
|
|||
*physical.BL_REGISTER,
|
||||
*blender.BL_REGISTER,
|
||||
*maxwell.BL_REGISTER,
|
||||
*tidy3d.BL_REGISTER,
|
||||
]
|
||||
|
|
|
@ -1,175 +1,316 @@
|
|||
import typing as typ
|
||||
import typing_extensions as typx
|
||||
import functools
|
||||
|
||||
import bpy
|
||||
|
||||
import pydantic as pyd
|
||||
import sympy as sp
|
||||
import sympy.physics.units as spu
|
||||
from .. import contracts
|
||||
from .. import contracts as ct
|
||||
|
||||
class BLSocket(bpy.types.NodeSocket):
|
||||
"""A base type for nodes that greatly simplifies the implementation of
|
||||
reliable, powerful nodes.
|
||||
class MaxwellSimSocket(bpy.types.NodeSocket):
|
||||
# Fundamentals
|
||||
socket_type: ct.SocketType
|
||||
bl_label: str
|
||||
|
||||
Should be used together with `contracts.BLSocketProtocol`.
|
||||
"""
|
||||
# Style
|
||||
display_shape: typx.Literal[
|
||||
"CIRCLE", "SQUARE", "DIAMOND", "CIRCLE_DOT", "SQUARE_DOT",
|
||||
"DIAMOND_DOT",
|
||||
]
|
||||
socket_color: tuple
|
||||
|
||||
# Options
|
||||
#link_limit: int = 0
|
||||
use_units: bool = False
|
||||
|
||||
# Computed
|
||||
bl_idname: str
|
||||
|
||||
####################
|
||||
# - Initialization
|
||||
####################
|
||||
def __init_subclass__(cls, **kwargs: typ.Any):
|
||||
super().__init_subclass__(**kwargs) ## Yucky superclass setup.
|
||||
|
||||
# Set bl_idname
|
||||
cls.bl_idname = cls.socket_type.value
|
||||
cls.socket_color = contracts.SocketType_to_color[
|
||||
cls.socket_type.value
|
||||
]
|
||||
# Setup Blender ID for Node
|
||||
if not hasattr(cls, "socket_type"):
|
||||
msg = f"Socket class {cls} does not define 'socket_type'"
|
||||
raise ValueError(msg)
|
||||
cls.bl_idname = str(cls.socket_type.value)
|
||||
|
||||
# Setup Locked Property for Node
|
||||
cls.__annotations__["locked"] = bpy.props.BoolProperty(
|
||||
name="Locked State",
|
||||
description="The lock-state of a particular socket, which determines the socket's user editability",
|
||||
default=False,
|
||||
)
|
||||
|
||||
# Setup Style
|
||||
cls.socket_color = ct.SOCKET_COLORS[cls.socket_type]
|
||||
cls.socket_shape = ct.SOCKET_SHAPES[cls.socket_type]
|
||||
|
||||
# Configure Use of Units
|
||||
if (
|
||||
hasattr(cls, "use_units")
|
||||
and cls.socket_type in contracts.SocketType_to_units
|
||||
):
|
||||
# Set Unit Properties
|
||||
cls.__annotations__["raw_unit"] = bpy.props.EnumProperty(
|
||||
if cls.use_units:
|
||||
if not (socket_units := ct.SOCKET_UNITS.get(cls.socket_type)):
|
||||
msg = "Tried to `use_units` on {cls.bl_idname} socket, but `SocketType` has no units defined in `contracts.SOCKET_UNITS`"
|
||||
raise RuntimeError(msg)
|
||||
|
||||
# Current Unit
|
||||
cls.__annotations__["active_unit"] = bpy.props.EnumProperty(
|
||||
name="Unit",
|
||||
description="Choose a unit",
|
||||
items=[
|
||||
(unit_name, str(unit_value), str(unit_value))
|
||||
for unit_name, unit_value in contracts.SocketType_to_units[
|
||||
cls.socket_type
|
||||
]["values"].items()
|
||||
for unit_name, unit_value in socket_units["values"].items()
|
||||
],
|
||||
default=contracts.SocketType_to_units[
|
||||
cls.socket_type
|
||||
]["default"],
|
||||
update=lambda self, context: self._update_unit(),
|
||||
)
|
||||
cls.__annotations__["raw_unit_previous"] = bpy.props.StringProperty(
|
||||
default=contracts.SocketType_to_units[
|
||||
cls.socket_type
|
||||
]["default"]
|
||||
default=socket_units["default"],
|
||||
update=lambda self, context: self.sync_unit_change(),
|
||||
)
|
||||
|
||||
# Declare Node Property: 'preset' EnumProperty
|
||||
if hasattr(cls, "draw_preview"):
|
||||
cls.__annotations__["preview_active"] = bpy.props.BoolProperty(
|
||||
name="Preview",
|
||||
description="Preview the socket value",
|
||||
default=False,
|
||||
# Previous Unit (for conversion)
|
||||
cls.__annotations__["prev_active_unit"] = bpy.props.StringProperty(
|
||||
default=socket_units["default"],
|
||||
)
|
||||
|
||||
####################
|
||||
# - Internal Methods
|
||||
# - Action Chain
|
||||
####################
|
||||
def trigger_action(
|
||||
self,
|
||||
action: typx.Literal["enable_lock", "disable_lock", "value_changed", "show_preview", "show_plot"],
|
||||
) -> None:
|
||||
"""Called whenever the socket's output value has changed.
|
||||
|
||||
This also invalidates any of the socket's caches.
|
||||
|
||||
When called on an input node, the containing node's
|
||||
`trigger_action` method will be called with this socket.
|
||||
|
||||
When called on a linked output node, the linked socket's
|
||||
`trigger_action` method will be called.
|
||||
"""
|
||||
# Forwards Chains
|
||||
if action in {"value_changed"}:
|
||||
## Input Socket
|
||||
if not self.is_output:
|
||||
self.node.trigger_action(action, socket_name=self.name)
|
||||
|
||||
## Linked Output Socket
|
||||
elif self.is_output and self.is_linked:
|
||||
for link in self.links:
|
||||
link.to_socket.trigger_action(action)
|
||||
|
||||
# Backwards Chains
|
||||
elif action in {"enable_lock", "disable_lock", "show_preview", "show_plot"}:
|
||||
if action == "enable_lock":
|
||||
self.locked = True
|
||||
|
||||
if action == "disable_lock":
|
||||
self.locked = False
|
||||
|
||||
## Output Socket
|
||||
if self.is_output:
|
||||
self.node.trigger_action(action, socket_name=self.name)
|
||||
|
||||
## Linked Input Socket
|
||||
elif not self.is_output and self.is_linked:
|
||||
for link in self.links:
|
||||
link.from_socket.trigger_action(action)
|
||||
|
||||
####################
|
||||
# - Action Chain: Event Handlers
|
||||
####################
|
||||
def sync_prop(self, prop_name: str, context: bpy.types.Context):
|
||||
"""Called when a property has been updated.
|
||||
"""
|
||||
if not hasattr(self, prop_name):
|
||||
msg = f"Property {prop_name} not defined on socket {self}"
|
||||
raise RuntimeError(msg)
|
||||
|
||||
self.trigger_action("value_changed")
|
||||
|
||||
def sync_link_added(self, link) -> bool:
|
||||
"""Called when a link has been added to this (input) socket.
|
||||
|
||||
Returns a bool, whether or not the socket consents to the link change.
|
||||
"""
|
||||
if self.locked: return False
|
||||
if self.is_output:
|
||||
msg = f"Tried to sync 'link add' on output socket"
|
||||
raise RuntimeError(msg)
|
||||
|
||||
self.trigger_action("value_changed")
|
||||
|
||||
return True
|
||||
|
||||
def sync_link_removed(self, from_socket) -> bool:
|
||||
"""Called when a link has been removed from this (input) socket.
|
||||
|
||||
Returns a bool, whether or not the socket consents to the link change.
|
||||
"""
|
||||
if self.locked: return False
|
||||
if self.is_output:
|
||||
msg = f"Tried to sync 'link add' on output socket"
|
||||
raise RuntimeError(msg)
|
||||
|
||||
self.trigger_action("value_changed")
|
||||
|
||||
return True
|
||||
|
||||
####################
|
||||
# - Data Chain
|
||||
####################
|
||||
@property
|
||||
def units(self) -> dict[str, sp.Expr]:
|
||||
return contracts.SocketType_to_units[
|
||||
def value(self) -> typ.Any:
|
||||
raise NotImplementedError
|
||||
|
||||
@value.setter
|
||||
def value(self, value: typ.Any) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def lazy_value(self) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
@lazy_value.setter
|
||||
def lazy_value(self, lazy_value: typ.Any) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def capabilities(self) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def _compute_data(
|
||||
self,
|
||||
kind: ct.DataFlowKind = ct.DataFlowKind.Value,
|
||||
) -> typ.Any:
|
||||
"""Computes the internal data of this socket, ONLY.
|
||||
|
||||
**NOTE**: Low-level method. Use `compute_data` instead.
|
||||
"""
|
||||
if kind == ct.DataFlowKind.Value:
|
||||
return self.value
|
||||
if kind == ct.DataFlowKind.LazyValue:
|
||||
return self.lazy_value
|
||||
if kind == ct.DataFlowKind.Capabilities:
|
||||
return self.capabilities
|
||||
return None
|
||||
|
||||
def compute_data(
|
||||
self,
|
||||
kind: ct.DataFlowKind = ct.DataFlowKind.Value,
|
||||
):
|
||||
"""Computes the value of this socket, including all relevant factors:
|
||||
- If input socket, and unlinked, compute internal data.
|
||||
- If input socket, and linked, compute linked socket data.
|
||||
- If output socket, ask node for data.
|
||||
"""
|
||||
# Compute Output Socket
|
||||
if self.is_output:
|
||||
return self.node.compute_output(self.name, kind=kind)
|
||||
|
||||
# Compute Input Socket
|
||||
## Unlinked: Retrieve Socket Value
|
||||
if not self.is_linked: return self._compute_data(kind)
|
||||
|
||||
## Linked: Compute Output of Linked Sockets
|
||||
linked_values = [
|
||||
link.from_socket.compute_data(kind)
|
||||
for link in self.links
|
||||
]
|
||||
|
||||
## Return Single Value / List of Values
|
||||
if len(linked_values) == 1: return linked_values[0]
|
||||
return linked_values
|
||||
|
||||
####################
|
||||
# - Unit Properties
|
||||
####################
|
||||
@functools.cached_property
|
||||
def possible_units(self) -> dict[str, sp.Expr]:
|
||||
if not self.use_units:
|
||||
msg = "Tried to get possible units for socket {self}, but socket doesn't `use_units`"
|
||||
raise ValueError(msg)
|
||||
|
||||
return ct.SOCKET_UNITS[
|
||||
self.socket_type
|
||||
]["values"]
|
||||
|
||||
@property
|
||||
def unit(self) -> sp.Expr:
|
||||
return contracts.SocketType_to_units[
|
||||
self.socket_type
|
||||
]["values"][self.raw_unit]
|
||||
|
||||
@unit.setter
|
||||
def unit(self, value) -> sp.Expr:
|
||||
raw_unit_name = [
|
||||
raw_unit_name
|
||||
for raw_unit_name, unit_value in contracts.SocketType_to_units[
|
||||
self.socket_type
|
||||
]["values"].items()
|
||||
if value == unit_value
|
||||
][0]
|
||||
|
||||
self.raw_unit = raw_unit_name
|
||||
return self.possible_units[self.active_unit]
|
||||
|
||||
@property
|
||||
def _unit_previous(self) -> sp.Expr:
|
||||
return contracts.SocketType_to_units[
|
||||
self.socket_type
|
||||
]["values"][self.raw_unit_previous]
|
||||
def prev_unit(self) -> sp.Expr:
|
||||
return self.possible_units[self.prev_active_unit]
|
||||
|
||||
@_unit_previous.setter
|
||||
def _unit_previous(self, value) -> sp.Expr:
|
||||
raw_unit_name = [
|
||||
raw_unit_name
|
||||
for raw_unit_name, unit_value in contracts.SocketType_to_units[
|
||||
self.socket_type
|
||||
]["values"].items()
|
||||
if value == unit_value
|
||||
][0]
|
||||
@unit.setter
|
||||
def unit(self, value: str | sp.Expr) -> None:
|
||||
# Retrieve Unit by String
|
||||
if isinstance(value, str) and value in self.possible_units:
|
||||
self.active_unit = self.possible_units[value]
|
||||
return
|
||||
|
||||
self.raw_unit_previous = raw_unit_name
|
||||
# Retrieve =1 Matching Unit Name
|
||||
matching_unit_names = [
|
||||
unit_name
|
||||
for unit_name, unit_sympy in self.possible_units.items()
|
||||
if value == unit_sympy
|
||||
]
|
||||
if len(matching_unit_names) == 0:
|
||||
msg = f"Tried to set unit for socket {self} with value {value}, but it is not one of possible units {''.join(possible.units.values())} for this socket (as defined in `contracts.SOCKET_UNITS`)"
|
||||
raise ValueError(msg)
|
||||
|
||||
def value_as_unit(self, value) -> typ.Any:
|
||||
"""Return the given value expresse as the current internal unit,
|
||||
without the unit.
|
||||
if len(matching_unit_names) > 1:
|
||||
msg = f"Tried to set unit for socket {self} with value {value}, but multiple possible matching units {''.join(possible.units.values())} for this socket (as defined in `contracts.SOCKET_UNITS`); there may only be one"
|
||||
raise RuntimeError(msg)
|
||||
|
||||
self.active_unit = matching_unit_names[0]
|
||||
|
||||
def sync_unit_change(self) -> None:
|
||||
"""In unit-aware sockets, the internal `value()` property multiplies the Blender property value by the current active unit.
|
||||
|
||||
When the unit is changed, `value()` will display the old scalar with the new unit.
|
||||
To fix this, we need to update the scalar to use the new unit.
|
||||
|
||||
Can be overridden if more specific logic is required.
|
||||
"""
|
||||
|
||||
if hasattr(self, "raw_value") and hasattr(self, "unit"):
|
||||
# (Guard) Value Compatibility
|
||||
if not self.is_compatible(value):
|
||||
msg = f"Tried setting socket ({self}) to incompatible value ({value}) of type {type(value)}"
|
||||
raise ValueError(msg)
|
||||
prev_value = self.value / self.unit * self.prev_unit
|
||||
## After changing units, self.value is expressed in the wrong unit.
|
||||
## - Therefore, we removing the new unit, and re-add the prev unit.
|
||||
## - Using only self.value avoids implementation-specific details.
|
||||
|
||||
# Return Converted Unit
|
||||
return spu.convert_to(
|
||||
value, self.unit
|
||||
) / self.unit
|
||||
else:
|
||||
raise ValueError("Tried to get 'raw_value_as_unit', but class has no 'raw_value'")
|
||||
self.value = spu.convert_to(
|
||||
prev_value,
|
||||
self.unit
|
||||
) ## Now, the unit conversion can be done correctly.
|
||||
|
||||
def _update_unit(self) -> None:
|
||||
"""Convert (if needed) the `raw_value` property, to use the unit
|
||||
set in the `unit` property.
|
||||
self.prev_active_unit = self.active_unit
|
||||
|
||||
If the `raw_value` property isn't set, this only sets "unit_previous".
|
||||
|
||||
Run right after setting the `unit` property, in order to synchronize
|
||||
the value with the new unit.
|
||||
####################
|
||||
# - Style
|
||||
####################
|
||||
def draw_color(
|
||||
self,
|
||||
context: bpy.types.Context,
|
||||
node: bpy.types.Node,
|
||||
) -> ct.BLColorRGBA:
|
||||
"""Color of the socket icon, when embedded in a node.
|
||||
"""
|
||||
if hasattr(self, "raw_value") and hasattr(self, "unit"):
|
||||
if hasattr(self.raw_value, "__getitem__"):
|
||||
self.raw_value = tuple(spu.convert_to(
|
||||
sp.Matrix(tuple(self.raw_value)) * self._unit_previous,
|
||||
self.unit,
|
||||
) / self.unit)
|
||||
else:
|
||||
self.raw_value = spu.convert_to(
|
||||
self.raw_value * self._unit_previous,
|
||||
self.unit,
|
||||
) / self.unit
|
||||
return self.socket_color
|
||||
|
||||
self._unit_previous = self.unit
|
||||
|
||||
####################
|
||||
# - Callback Dispatcher
|
||||
####################
|
||||
def trigger_updates(self) -> None:
|
||||
if not self.is_output:
|
||||
self.node.update()
|
||||
|
||||
####################
|
||||
# - Methods
|
||||
####################
|
||||
def is_compatible(self, value: typ.Any) -> bool:
|
||||
if not hasattr(self, "compatible_types"):
|
||||
return True
|
||||
|
||||
for compatible_type, checks in self.compatible_types.items():
|
||||
if (
|
||||
compatible_type is typ.Any or
|
||||
isinstance(value, compatible_type)
|
||||
):
|
||||
return all(check(self, value) for check in checks)
|
||||
|
||||
return False
|
||||
|
||||
####################
|
||||
# - UI
|
||||
####################
|
||||
@classmethod
|
||||
def draw_color_simple(cls) -> contracts.BlenderColorRGB:
|
||||
def draw_color_simple(cls) -> ct.BLColorRGBA:
|
||||
"""Fallback color of the socket icon (ex.when not embedded in a node).
|
||||
"""
|
||||
return cls.socket_color
|
||||
|
||||
####################
|
||||
# - UI Methods
|
||||
####################
|
||||
def draw(
|
||||
self,
|
||||
context: bpy.types.Context,
|
||||
|
@ -177,6 +318,10 @@ class BLSocket(bpy.types.NodeSocket):
|
|||
node: bpy.types.Node,
|
||||
text: str,
|
||||
) -> None:
|
||||
"""Called by Blender to draw the socket UI.
|
||||
"""
|
||||
if self.locked: layout.enabled = False
|
||||
|
||||
if self.is_output:
|
||||
self.draw_output(context, layout, node, text)
|
||||
else:
|
||||
|
@ -189,44 +334,31 @@ class BLSocket(bpy.types.NodeSocket):
|
|||
node: bpy.types.Node,
|
||||
text: str,
|
||||
) -> None:
|
||||
"""Draws the socket UI, when the socket is an input socket.
|
||||
"""
|
||||
# Draw Linked Input: Label Row
|
||||
if self.is_linked:
|
||||
layout.label(text=text)
|
||||
return
|
||||
|
||||
# Column
|
||||
col = layout.column(align=True)
|
||||
# Parent Column
|
||||
col = layout.column(align=False)
|
||||
|
||||
# Row: Label & Preview Toggle
|
||||
label_col_row = col.row(align=True)
|
||||
if hasattr(self, "draw_label_row"):
|
||||
self.draw_label_row(label_col_row, text)
|
||||
elif hasattr(self, "raw_unit"):
|
||||
label_col_row.label(text=text)
|
||||
label_col_row.prop(self, "raw_unit", text="")
|
||||
# Draw Label Row
|
||||
row = col.row(align=True)
|
||||
if self.use_units:
|
||||
split = row.split(factor=0.65, align=True)
|
||||
|
||||
_row = split.row(align=True)
|
||||
self.draw_label_row(_row, text)
|
||||
|
||||
_col = split.column(align=True)
|
||||
_col.prop(self, "active_unit", text="")
|
||||
else:
|
||||
label_col_row.label(text=text)
|
||||
self.draw_label_row(row, text)
|
||||
|
||||
if hasattr(self, "draw_preview"):
|
||||
label_col_row.prop(
|
||||
self,
|
||||
"preview_active",
|
||||
toggle=True,
|
||||
text="",
|
||||
icon="SEQ_PREVIEW",
|
||||
)
|
||||
|
||||
# Row: Preview (in Box)
|
||||
if hasattr(self, "draw_preview"):
|
||||
if self.preview_active:
|
||||
col_box = col.box()
|
||||
self.draw_preview(col_box)
|
||||
|
||||
# Row(s): Value
|
||||
if hasattr(self, "draw_value"):
|
||||
self.draw_value(col)
|
||||
elif hasattr(self, "raw_value"):
|
||||
#col_row = col.row(align=True)
|
||||
col.prop(self, "raw_value", text="")
|
||||
# Draw Value Row(s)
|
||||
self.draw_value(col)
|
||||
|
||||
def draw_output(
|
||||
self,
|
||||
|
@ -235,23 +367,28 @@ class BLSocket(bpy.types.NodeSocket):
|
|||
node: bpy.types.Node,
|
||||
text: str,
|
||||
) -> None:
|
||||
col = layout.column()
|
||||
row_col = col.row()
|
||||
row_col.alignment = "RIGHT"
|
||||
# Row: Label & Preview Toggle
|
||||
if hasattr(self, "draw_preview"):
|
||||
row_col.prop(
|
||||
self,
|
||||
"preview_active",
|
||||
toggle=True,
|
||||
text="",
|
||||
icon="SEQ_PREVIEW",
|
||||
)
|
||||
"""Draws the socket UI, when the socket is an output socket.
|
||||
"""
|
||||
layout.label(text=text)
|
||||
|
||||
row_col.label(text=text)
|
||||
####################
|
||||
# - UI Methods
|
||||
####################
|
||||
def draw_label_row(
|
||||
self,
|
||||
row: bpy.types.UILayout,
|
||||
text: str,
|
||||
) -> None:
|
||||
"""Called to draw the label row (same height as socket shape).
|
||||
|
||||
Can be overridden.
|
||||
"""
|
||||
row.label(text=text)
|
||||
|
||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||
"""Called to draw the value column in unlinked input sockets.
|
||||
|
||||
Can be overridden.
|
||||
"""
|
||||
pass
|
||||
|
||||
# Row: Preview (in box)
|
||||
if hasattr(self, "draw_preview"):
|
||||
if self.preview_active:
|
||||
col_box = col.box()
|
||||
self.draw_preview(col_box)
|
||||
|
|
|
@ -5,46 +5,20 @@ import sympy as sp
|
|||
import pydantic as pyd
|
||||
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
####################
|
||||
# - Blender Socket
|
||||
####################
|
||||
class AnyBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.Any
|
||||
socket_color = (0.0, 0.0, 0.0, 1.0)
|
||||
|
||||
class AnyBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.Any
|
||||
bl_label = "Any"
|
||||
|
||||
compatible_types = {
|
||||
typ.Any: {},
|
||||
}
|
||||
|
||||
####################
|
||||
# - Socket UI
|
||||
####################
|
||||
def draw_label_row(self, label_col_row: bpy.types.UILayout, text: str) -> None:
|
||||
"""Draw the value of the real number.
|
||||
"""
|
||||
label_col_row.label(text=text)
|
||||
|
||||
####################
|
||||
# - Computation of Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> None:
|
||||
return None
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: typ.Any) -> None:
|
||||
pass
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class AnySocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.Any
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.Any
|
||||
|
||||
def init(self, bl_socket: AnyBLSocket) -> None:
|
||||
pass
|
||||
|
|
|
@ -5,27 +5,23 @@ import sympy as sp
|
|||
import pydantic as pyd
|
||||
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
####################
|
||||
# - Blender Socket
|
||||
####################
|
||||
class BoolBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.Bool
|
||||
class BoolBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.Bool
|
||||
bl_label = "Bool"
|
||||
|
||||
compatible_types = {
|
||||
bool: {},
|
||||
}
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
raw_value: bpy.props.BoolProperty(
|
||||
name="Boolean",
|
||||
description="Represents a boolean",
|
||||
description="Represents a boolean value",
|
||||
default=False,
|
||||
update=(lambda self, context: self.trigger_updates()),
|
||||
update=(lambda self, context: self.sync_prop("raw_value", context)),
|
||||
)
|
||||
|
||||
####################
|
||||
|
@ -35,36 +31,27 @@ class BoolBLSocket(base.BLSocket):
|
|||
label_col_row.label(text=text)
|
||||
label_col_row.prop(self, "raw_value", text="")
|
||||
|
||||
def draw_value(self, label_col_row: bpy.types.UILayout) -> None:
|
||||
pass
|
||||
|
||||
####################
|
||||
# - Computation of Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> str:
|
||||
def value(self) -> bool:
|
||||
return self.raw_value
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: typ.Any) -> None:
|
||||
# (Guard) Value Compatibility
|
||||
if not self.is_compatible(value):
|
||||
msg = f"Tried setting socket ({self}) to incompatible value ({value}) of type {type(value)}"
|
||||
raise ValueError(msg)
|
||||
|
||||
self.raw_value = bool(value)
|
||||
@value.setter
|
||||
def value(self, value: bool) -> None:
|
||||
self.raw_value = value
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class BoolSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.Bool
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.Bool
|
||||
|
||||
default_value: bool = False
|
||||
|
||||
def init(self, bl_socket: BoolBLSocket) -> None:
|
||||
bl_socket.raw_value = self.default_value
|
||||
bl_socket.value = self.default_value
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
|
|
|
@ -6,29 +6,25 @@ import sympy as sp
|
|||
import pydantic as pyd
|
||||
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
####################
|
||||
# - Blender Socket
|
||||
####################
|
||||
class FilePathBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.FilePath
|
||||
class FilePathBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.FilePath
|
||||
bl_label = "File Path"
|
||||
|
||||
compatible_types = {
|
||||
Path: {},
|
||||
}
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
raw_value: bpy.props.StringProperty(
|
||||
name="File Path",
|
||||
description="Represents the path to a file",
|
||||
#default="",
|
||||
subtype="FILE_PATH",
|
||||
update=(lambda self, context: self.trigger_updates()),
|
||||
update=(lambda self, context: self.sync_prop("raw_value", context)),
|
||||
)
|
||||
## TODO: Use bpy methods to constrain the path
|
||||
|
||||
####################
|
||||
# - Socket UI
|
||||
|
@ -41,39 +37,23 @@ class FilePathBLSocket(base.BLSocket):
|
|||
# - Computation of Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> Path:
|
||||
"""Return the text.
|
||||
def value(self) -> Path:
|
||||
return Path(bpy.path.abspath(self.raw_value))
|
||||
|
||||
Returns:
|
||||
The text as a string.
|
||||
"""
|
||||
|
||||
return Path(str(self.raw_value))
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: typ.Any) -> None:
|
||||
"""Set the real number from some compatible type, namely
|
||||
real sympy expressions with no symbols, or floats.
|
||||
"""
|
||||
|
||||
# (Guard) Value Compatibility
|
||||
if not self.is_compatible(value):
|
||||
msg = f"Tried setting socket ({self}) to incompatible value ({value}) of type {type(value)}"
|
||||
raise ValueError(msg)
|
||||
|
||||
self.raw_value = str(Path(value))
|
||||
@value.setter
|
||||
def value(self, value: Path) -> None:
|
||||
self.raw_value = bpy.path.relpath(str(value))
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class FilePathSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.FilePath
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.FilePath
|
||||
|
||||
default_path: Path = Path("")
|
||||
|
||||
def init(self, bl_socket: FilePathBLSocket) -> None:
|
||||
bl_socket.default_value = self.default_path
|
||||
bl_socket.value = self.default_path
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
|
|
|
@ -5,21 +5,15 @@ import sympy as sp
|
|||
import pydantic as pyd
|
||||
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
####################
|
||||
# - Blender Socket
|
||||
####################
|
||||
class TextBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.Text
|
||||
socket_color = (0.2, 0.2, 0.2, 1.0)
|
||||
|
||||
class TextBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.Text
|
||||
bl_label = "Text"
|
||||
|
||||
compatible_types = {
|
||||
str: {},
|
||||
}
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
|
@ -27,7 +21,7 @@ class TextBLSocket(base.BLSocket):
|
|||
name="Text",
|
||||
description="Represents some text",
|
||||
default="",
|
||||
update=(lambda self, context: self.trigger_updates()),
|
||||
update=(lambda self, context: self.sync_prop("raw_value", context)),
|
||||
)
|
||||
|
||||
####################
|
||||
|
@ -38,44 +32,27 @@ class TextBLSocket(base.BLSocket):
|
|||
"""
|
||||
label_col_row.prop(self, "raw_value", text=text)
|
||||
|
||||
def draw_value(self, label_col_row: bpy.types.UILayout) -> None:
|
||||
pass
|
||||
|
||||
####################
|
||||
# - Computation of Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> str:
|
||||
"""Return the text.
|
||||
|
||||
Returns:
|
||||
The text as a string.
|
||||
"""
|
||||
|
||||
def value(self) -> str:
|
||||
return self.raw_value
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: typ.Any) -> None:
|
||||
"""Set the real number from some compatible type, namely
|
||||
real sympy expressions with no symbols, or floats.
|
||||
"""
|
||||
|
||||
# (Guard) Value Compatibility
|
||||
if not self.is_compatible(value):
|
||||
msg = f"Tried setting socket ({self}) to incompatible value ({value}) of type {type(value)}"
|
||||
raise ValueError(msg)
|
||||
|
||||
self.raw_value = str(value)
|
||||
@value.setter
|
||||
def value(self, value: str) -> None:
|
||||
self.raw_value = value
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class TextSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.Text
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.Text
|
||||
|
||||
default_text: str = ""
|
||||
|
||||
def init(self, bl_socket: TextBLSocket) -> None:
|
||||
pass
|
||||
bl_socket.value = self.default_text
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
|
|
|
@ -4,25 +4,18 @@ BlenderObjectSocketDef = object_socket.BlenderObjectSocketDef
|
|||
BlenderCollectionSocketDef = collection_socket.BlenderCollectionSocketDef
|
||||
|
||||
from . import image_socket
|
||||
from . import volume_socket
|
||||
BlenderImageSocketDef = image_socket.BlenderImageSocketDef
|
||||
BlenderVolumeSocketDef = volume_socket.BlenderVolumeSocketDef
|
||||
|
||||
from . import geonodes_socket
|
||||
from . import text_socket
|
||||
BlenderGeoNodesSocketDef = geonodes_socket.BlenderGeoNodesSocketDef
|
||||
BlenderTextSocketDef = text_socket.BlenderTextSocketDef
|
||||
|
||||
from . import target_socket
|
||||
BlenderPreviewTargetSocketDef = target_socket.BlenderPreviewTargetSocketDef
|
||||
|
||||
BL_REGISTER = [
|
||||
*object_socket.BL_REGISTER,
|
||||
*collection_socket.BL_REGISTER,
|
||||
|
||||
*image_socket.BL_REGISTER,
|
||||
*volume_socket.BL_REGISTER,
|
||||
*target_socket.BL_REGISTER,
|
||||
|
||||
*geonodes_socket.BL_REGISTER,
|
||||
*text_socket.BL_REGISTER,
|
||||
|
|
|
@ -4,14 +4,14 @@ import bpy
|
|||
import pydantic as pyd
|
||||
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
####################
|
||||
# - Blender Socket
|
||||
####################
|
||||
class BlenderCollectionBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.BlenderCollection
|
||||
bl_label = "BlenderCollection"
|
||||
class BlenderCollectionBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.BlenderCollection
|
||||
bl_label = "Blender Collection"
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
|
@ -20,26 +20,31 @@ class BlenderCollectionBLSocket(base.BLSocket):
|
|||
name="Blender Collection",
|
||||
description="Represents a Blender collection",
|
||||
type=bpy.types.Collection,
|
||||
update=(lambda self, context: self.trigger_updates()),
|
||||
update=(lambda self, context: self.sync_prop("raw_value", context)),
|
||||
)
|
||||
|
||||
####################
|
||||
# - UI
|
||||
####################
|
||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||
col.prop(self, "raw_value", text="")
|
||||
|
||||
####################
|
||||
# - Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> bpy.types.Collection | None:
|
||||
def value(self) -> bpy.types.Collection | None:
|
||||
return self.raw_value
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: bpy.types.Collection) -> None:
|
||||
@value.setter
|
||||
def value(self, value: bpy.types.Collection) -> None:
|
||||
self.raw_value = value
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class BlenderCollectionSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.BlenderCollection
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.BlenderCollection
|
||||
|
||||
def init(self, bl_socket: BlenderCollectionBLSocket) -> None:
|
||||
pass
|
||||
|
|
|
@ -4,7 +4,7 @@ import bpy
|
|||
import pydantic as pyd
|
||||
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
####################
|
||||
# - Operators
|
||||
|
@ -22,28 +22,19 @@ class BlenderMaxwellResetGeoNodesSocket(bpy.types.Operator):
|
|||
####################
|
||||
# - Blender Socket
|
||||
####################
|
||||
class BlenderGeoNodesBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.BlenderGeoNodes
|
||||
bl_label = "BlenderGeoNodes"
|
||||
class BlenderGeoNodesBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.BlenderGeoNodes
|
||||
bl_label = "Geometry Node Tree"
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
def update_geonodes_node(self):
|
||||
if hasattr(self.node, "update_sockets_from_geonodes"):
|
||||
self.node.update_sockets_from_geonodes()
|
||||
else:
|
||||
raise ValueError("Node doesn't have GeoNodes socket update method.")
|
||||
|
||||
# Run the Usual Updates
|
||||
self.trigger_updates()
|
||||
|
||||
raw_value: bpy.props.PointerProperty(
|
||||
name="Blender GeoNodes Tree",
|
||||
description="Represents a Blender GeoNodes Tree",
|
||||
type=bpy.types.NodeTree,
|
||||
poll=(lambda self, obj: obj.bl_idname == 'GeometryNodeTree'),
|
||||
update=(lambda self, context: self.update_geonodes_node()),
|
||||
update=(lambda self, context: self.sync_prop("raw_value", context)),
|
||||
)
|
||||
|
||||
####################
|
||||
|
@ -58,23 +49,28 @@ class BlenderGeoNodesBLSocket(base.BLSocket):
|
|||
icon="FILE_REFRESH",
|
||||
)
|
||||
|
||||
####################
|
||||
# - UI
|
||||
####################
|
||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||
col.prop(self, "raw_value", text="")
|
||||
|
||||
####################
|
||||
# - Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> bpy.types.Object | None:
|
||||
def value(self) -> bpy.types.NodeTree | None:
|
||||
return self.raw_value
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: bpy.types.Object) -> None:
|
||||
@value.setter
|
||||
def value(self, value: bpy.types.NodeTree) -> None:
|
||||
self.raw_value = value
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class BlenderGeoNodesSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.BlenderGeoNodes
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.BlenderGeoNodes
|
||||
|
||||
def init(self, bl_socket: BlenderGeoNodesBLSocket) -> None:
|
||||
pass
|
||||
|
|
|
@ -4,32 +4,47 @@ import bpy
|
|||
import pydantic as pyd
|
||||
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
####################
|
||||
# - Blender Socket
|
||||
####################
|
||||
class BlenderImageBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.BlenderImage
|
||||
bl_label = "BlenderImage"
|
||||
class BlenderImageBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.BlenderImage
|
||||
bl_label = "Blender Image"
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
raw_value: bpy.props.PointerProperty(
|
||||
name="Blender Image",
|
||||
description="Represents a Blender Image",
|
||||
type=bpy.types.Image,
|
||||
update=(lambda self, context: self.sync_prop("raw_value", context)),
|
||||
)
|
||||
|
||||
####################
|
||||
# - UI
|
||||
####################
|
||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||
col.prop(self, "raw_value", text="")
|
||||
|
||||
####################
|
||||
# - Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> None:
|
||||
pass
|
||||
def value(self) -> bpy.types.Image | None:
|
||||
return self.raw_value
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: typ.Any) -> None:
|
||||
pass
|
||||
@value.setter
|
||||
def value(self, value: bpy.types.Image) -> None:
|
||||
self.raw_value = value
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class BlenderImageSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.BlenderImage
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.BlenderImage
|
||||
|
||||
def init(self, bl_socket: BlenderImageBLSocket) -> None:
|
||||
pass
|
||||
|
|
|
@ -4,7 +4,7 @@ import bpy
|
|||
import pydantic as pyd
|
||||
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
####################
|
||||
# - Blender Socket
|
||||
|
@ -13,6 +13,7 @@ class BlenderMaxwellCreateAndAssignBLObject(bpy.types.Operator):
|
|||
bl_idname = "blender_maxwell.create_and_assign_bl_object"
|
||||
bl_label = "Create and Assign BL Object"
|
||||
|
||||
## TODO: Refactor
|
||||
def execute(self, context):
|
||||
mesh = bpy.data.meshes.new("GenMesh")
|
||||
new_bl_object = bpy.data.objects.new("GenObj", mesh)
|
||||
|
@ -32,9 +33,9 @@ class BlenderMaxwellCreateAndAssignBLObject(bpy.types.Operator):
|
|||
####################
|
||||
# - Blender Socket
|
||||
####################
|
||||
class BlenderObjectBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.BlenderObject
|
||||
bl_label = "BlenderObject"
|
||||
class BlenderObjectBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.BlenderObject
|
||||
bl_label = "Blender Object"
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
|
@ -43,7 +44,7 @@ class BlenderObjectBLSocket(base.BLSocket):
|
|||
name="Blender Object",
|
||||
description="Represents a Blender object",
|
||||
type=bpy.types.Object,
|
||||
update=(lambda self, context: self.trigger_updates()),
|
||||
update=(lambda self, context: self.sync_prop("raw_value", context)),
|
||||
)
|
||||
|
||||
####################
|
||||
|
@ -57,23 +58,25 @@ class BlenderObjectBLSocket(base.BLSocket):
|
|||
icon="ADD",
|
||||
)
|
||||
|
||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||
col.prop(self, "raw_value", text="")
|
||||
|
||||
####################
|
||||
# - Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> bpy.types.Object | None:
|
||||
def value(self) -> bpy.types.Object | None:
|
||||
return self.raw_value
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: bpy.types.Object) -> None:
|
||||
@value.setter
|
||||
def value(self, value: bpy.types.Object) -> None:
|
||||
self.raw_value = value
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class BlenderObjectSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.BlenderObject
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.BlenderObject
|
||||
|
||||
def init(self, bl_socket: BlenderObjectBLSocket) -> None:
|
||||
pass
|
||||
|
|
|
@ -9,27 +9,42 @@ from ... import contracts
|
|||
####################
|
||||
# - Blender Socket
|
||||
####################
|
||||
class BlenderTextBLSocket(base.BLSocket):
|
||||
class BlenderTextBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = contracts.SocketType.BlenderText
|
||||
bl_label = "BlenderText"
|
||||
bl_label = "Blender Text"
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
raw_value: bpy.props.PointerProperty(
|
||||
name="Blender Text",
|
||||
description="Represents a Blender text datablock",
|
||||
type=bpy.types.Text,
|
||||
update=(lambda self, context: self.sync_prop("raw_value", context)),
|
||||
)
|
||||
|
||||
####################
|
||||
# - UI
|
||||
####################
|
||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||
col.prop(self, "raw_value", text="")
|
||||
|
||||
####################
|
||||
# - Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> None:
|
||||
pass
|
||||
def value(self) -> bpy.types.Text:
|
||||
return self.raw_value
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: typ.Any) -> None:
|
||||
pass
|
||||
@value.setter
|
||||
def value(self, value: bpy.types.Text) -> None:
|
||||
self.raw_value = value
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class BlenderTextSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.BlenderText
|
||||
label: str
|
||||
|
||||
def init(self, bl_socket: BlenderTextBLSocket) -> None:
|
||||
pass
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
import typing as typ
|
||||
|
||||
import bpy
|
||||
import pydantic as pyd
|
||||
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
|
||||
####################
|
||||
# - Blender Socket
|
||||
####################
|
||||
class BlenderVolumeBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.BlenderVolume
|
||||
bl_label = "BlenderVolume"
|
||||
|
||||
####################
|
||||
# - Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> None:
|
||||
pass
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: typ.Any) -> None:
|
||||
pass
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class BlenderVolumeSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.BlenderVolume
|
||||
label: str
|
||||
|
||||
def init(self, bl_socket: BlenderVolumeBLSocket) -> None:
|
||||
pass
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
####################
|
||||
BL_REGISTER = [
|
||||
BlenderVolumeBLSocket
|
||||
]
|
|
@ -4,7 +4,9 @@ MaxwellBoundBoxSocketDef = bound_box_socket.MaxwellBoundBoxSocketDef
|
|||
MaxwellBoundFaceSocketDef = bound_face_socket.MaxwellBoundFaceSocketDef
|
||||
|
||||
from . import medium_socket
|
||||
from . import medium_non_linearity_socket
|
||||
MaxwellMediumSocketDef = medium_socket.MaxwellMediumSocketDef
|
||||
MaxwellMediumNonLinearitySocketDef = medium_non_linearity_socket.MaxwellMediumNonLinearitySocketDef
|
||||
|
||||
from . import source_socket
|
||||
from . import temporal_shape_socket
|
||||
|
@ -20,15 +22,18 @@ MaxwellMonitorSocketDef = monitor_socket.MaxwellMonitorSocketDef
|
|||
from . import fdtd_sim_socket
|
||||
from . import sim_grid_socket
|
||||
from . import sim_grid_axis_socket
|
||||
from . import sim_domain_socket
|
||||
MaxwellFDTDSimSocketDef = fdtd_sim_socket.MaxwellFDTDSimSocketDef
|
||||
MaxwellSimGridSocketDef = sim_grid_socket.MaxwellSimGridSocketDef
|
||||
MaxwellSimGridAxisSocketDef = sim_grid_axis_socket.MaxwellSimGridAxisSocketDef
|
||||
MaxwellSimDomainSocketDef = sim_domain_socket.MaxwellSimDomainSocketDef
|
||||
|
||||
|
||||
BL_REGISTER = [
|
||||
*bound_box_socket.BL_REGISTER,
|
||||
*bound_face_socket.BL_REGISTER,
|
||||
*medium_socket.BL_REGISTER,
|
||||
*medium_non_linearity_socket.BL_REGISTER,
|
||||
*source_socket.BL_REGISTER,
|
||||
*temporal_shape_socket.BL_REGISTER,
|
||||
*structure_socket.BL_REGISTER,
|
||||
|
@ -36,4 +41,5 @@ BL_REGISTER = [
|
|||
*fdtd_sim_socket.BL_REGISTER,
|
||||
*sim_grid_socket.BL_REGISTER,
|
||||
*sim_grid_axis_socket.BL_REGISTER,
|
||||
*sim_domain_socket.BL_REGISTER,
|
||||
]
|
||||
|
|
|
@ -5,7 +5,7 @@ import pydantic as pyd
|
|||
import tidy3d as td
|
||||
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
BOUND_FACE_ITEMS = [
|
||||
("PML", "PML", "Perfectly matched layer"),
|
||||
|
@ -13,98 +13,121 @@ BOUND_FACE_ITEMS = [
|
|||
("PMC", "PMC", "Perfect magnetic conductor"),
|
||||
("PERIODIC", "Periodic", "Infinitely periodic layer"),
|
||||
]
|
||||
BOUND_MAP = {
|
||||
"PML": td.PML(),
|
||||
"PEC": td.PECBoundary(),
|
||||
"PMC": td.PMCBoundary(),
|
||||
"PERIODIC": td.Periodic(),
|
||||
}
|
||||
|
||||
class MaxwellBoundBoxBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.MaxwellBoundBox
|
||||
class MaxwellBoundBoxBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.MaxwellBoundBox
|
||||
bl_label = "Maxwell Bound Box"
|
||||
|
||||
compatible_types = {
|
||||
td.BoundarySpec: {}
|
||||
}
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
show_definition: bpy.props.BoolProperty(
|
||||
name="Show Bounds Definition",
|
||||
description="Toggle to show bound faces",
|
||||
default=False,
|
||||
update=(lambda self, context: self.sync_prop("show_definition", context)),
|
||||
)
|
||||
|
||||
x_pos: bpy.props.EnumProperty(
|
||||
name="+x Bound Face",
|
||||
description="+x choice of default boundary face",
|
||||
items=BOUND_FACE_ITEMS,
|
||||
default="PML",
|
||||
update=(lambda self, context: self.trigger_updates()),
|
||||
update=(lambda self, context: self.sync_prop("x_pos", context)),
|
||||
)
|
||||
x_neg: bpy.props.EnumProperty(
|
||||
name="-x Bound Face",
|
||||
description="-x choice of default boundary face",
|
||||
items=BOUND_FACE_ITEMS,
|
||||
default="PML",
|
||||
update=(lambda self, context: self.trigger_updates()),
|
||||
update=(lambda self, context: self.sync_prop("x_neg", context)),
|
||||
)
|
||||
y_pos: bpy.props.EnumProperty(
|
||||
name="+y Bound Face",
|
||||
description="+y choice of default boundary face",
|
||||
items=BOUND_FACE_ITEMS,
|
||||
default="PML",
|
||||
update=(lambda self, context: self.trigger_updates()),
|
||||
update=(lambda self, context: self.sync_prop("y_pos", context)),
|
||||
)
|
||||
y_neg: bpy.props.EnumProperty(
|
||||
name="-y Bound Face",
|
||||
description="-y choice of default boundary face",
|
||||
items=BOUND_FACE_ITEMS,
|
||||
default="PML",
|
||||
update=(lambda self, context: self.trigger_updates()),
|
||||
update=(lambda self, context: self.sync_prop("y_neg", context)),
|
||||
)
|
||||
z_pos: bpy.props.EnumProperty(
|
||||
name="+z Bound Face",
|
||||
description="+z choice of default boundary face",
|
||||
items=BOUND_FACE_ITEMS,
|
||||
default="PML",
|
||||
update=(lambda self, context: self.trigger_updates()),
|
||||
update=(lambda self, context: self.sync_prop("z_pos", context)),
|
||||
)
|
||||
z_neg: bpy.props.EnumProperty(
|
||||
name="-z Bound Face",
|
||||
description="-z choice of default boundary face",
|
||||
items=BOUND_FACE_ITEMS,
|
||||
default="PML",
|
||||
update=(lambda self, context: self.trigger_updates()),
|
||||
update=(lambda self, context: self.sync_prop("z_neg", context)),
|
||||
)
|
||||
|
||||
####################
|
||||
# - UI
|
||||
####################
|
||||
def draw_label_row(self, row: bpy.types.UILayout, text) -> None:
|
||||
row.label(text=text)
|
||||
row.prop(
|
||||
self, "show_definition", toggle=True, text="", icon="MOD_LENGTH"
|
||||
)
|
||||
|
||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||
col.label(text="-/+ x")
|
||||
col_row = col.row(align=True)
|
||||
col_row.prop(self, "x_neg", text="")
|
||||
col_row.prop(self, "x_pos", text="")
|
||||
if not self.show_definition: return
|
||||
|
||||
col.label(text="-/+ y")
|
||||
col_row = col.row(align=True)
|
||||
col_row.prop(self, "y_neg", text="")
|
||||
col_row.prop(self, "y_pos", text="")
|
||||
for axis in ["x", "y", "z"]:
|
||||
row = col.row(align=False)
|
||||
split = row.split(factor=0.2, align=False)
|
||||
|
||||
col.label(text="-/+ z")
|
||||
col_row = col.row(align=True)
|
||||
col_row.prop(self, "z_neg", text="")
|
||||
col_row.prop(self, "z_pos", text="")
|
||||
_col = split.column(align=True)
|
||||
_col.alignment = "RIGHT"
|
||||
_col.label(text=axis + " -")
|
||||
_col.label(text=" +")
|
||||
|
||||
_col = split.column(align=True)
|
||||
_col.prop(self, axis + "_neg", text="")
|
||||
_col.prop(self, axis + "_pos", text="")
|
||||
|
||||
|
||||
####################
|
||||
# - Computation of Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> td.BoundarySpec:
|
||||
return td.BoundarySpec()
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: typ.Any) -> None:
|
||||
return None
|
||||
def value(self) -> td.BoundarySpec:
|
||||
return td.BoundarySpec(
|
||||
x=td.Boundary(
|
||||
plus=BOUND_MAP[self.x_pos],
|
||||
minus=BOUND_MAP[self.x_neg],
|
||||
),
|
||||
y=td.Boundary(
|
||||
plus=BOUND_MAP[self.y_pos],
|
||||
minus=BOUND_MAP[self.y_neg],
|
||||
),
|
||||
z=td.Boundary(
|
||||
plus=BOUND_MAP[self.z_pos],
|
||||
minus=BOUND_MAP[self.z_neg],
|
||||
),
|
||||
)
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class MaxwellBoundBoxSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.MaxwellBoundBox
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.MaxwellBoundBox
|
||||
|
||||
def init(self, bl_socket: MaxwellBoundBoxBLSocket) -> None:
|
||||
pass
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
import typing as typ
|
||||
import typing_extensions as typx
|
||||
|
||||
import bpy
|
||||
import pydantic as pyd
|
||||
import tidy3d as td
|
||||
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
class MaxwellBoundFaceBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.MaxwellBoundFace
|
||||
class MaxwellBoundFaceBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.MaxwellBoundFace
|
||||
bl_label = "Maxwell Bound Face"
|
||||
|
||||
####################
|
||||
|
@ -24,21 +25,20 @@ class MaxwellBoundFaceBLSocket(base.BLSocket):
|
|||
("PERIODIC", "Periodic", "Infinitely periodic layer"),
|
||||
],
|
||||
default="PML",
|
||||
update=(lambda self, context: self.trigger_updates()),
|
||||
update=(lambda self, context: self.sync_prop("default_choice", context)),
|
||||
)
|
||||
|
||||
####################
|
||||
# - UI
|
||||
####################
|
||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||
col_row = col.row(align=True)
|
||||
col_row.prop(self, "default_choice", text="")
|
||||
col.prop(self, "default_choice", text="")
|
||||
|
||||
####################
|
||||
# - Computation of Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> td.BoundarySpec:
|
||||
def value(self) -> td.BoundarySpec:
|
||||
return {
|
||||
"PML": td.PML(num_layers=12),
|
||||
"PEC": td.PECBoundary(),
|
||||
|
@ -46,21 +46,20 @@ class MaxwellBoundFaceBLSocket(base.BLSocket):
|
|||
"PERIODIC": td.Periodic(),
|
||||
}[self.default_choice]
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: typ.Any) -> None:
|
||||
return None
|
||||
@value.setter
|
||||
def value(self, value: typx.Literal["PML", "PEC", "PMC", "PERIODIC"]) -> None:
|
||||
self.default_choice = value
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class MaxwellBoundFaceSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.MaxwellBoundFace
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.MaxwellBoundFace
|
||||
|
||||
default_choice: str = "PML"
|
||||
default_choice: typx.Literal["PML", "PEC", "PMC", "PERIODIC"] = "PML"
|
||||
|
||||
def init(self, bl_socket: MaxwellBoundFaceBLSocket) -> None:
|
||||
bl_socket.default_choice = self.default_choice
|
||||
bl_socket.value = self.default_choice
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
|
|
|
@ -5,33 +5,17 @@ import pydantic as pyd
|
|||
import tidy3d as td
|
||||
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
class MaxwellFDTDSimBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.MaxwellFDTDSim
|
||||
bl_label = "Maxwell Source"
|
||||
|
||||
compatible_types = {
|
||||
td.Simulation: {},
|
||||
}
|
||||
|
||||
####################
|
||||
# - Computation of Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> None:
|
||||
return None
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: typ.Any) -> None:
|
||||
pass
|
||||
class MaxwellFDTDSimBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.MaxwellFDTDSim
|
||||
bl_label = "Maxwell FDTD Simulation"
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class MaxwellFDTDSimSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.MaxwellFDTDSim
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.MaxwellFDTDSim
|
||||
|
||||
def init(self, bl_socket: MaxwellFDTDSimBLSocket) -> None:
|
||||
pass
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import typing as typ
|
||||
|
||||
import bpy
|
||||
import pydantic as pyd
|
||||
import tidy3d as td
|
||||
|
||||
from .. import base
|
||||
from ... import contracts as ct
|
||||
|
||||
class MaxwellMediumNonLinearityBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.MaxwellMediumNonLinearity
|
||||
bl_label = "Medium Non-Linearity"
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class MaxwellMediumNonLinearitySocketDef(pyd.BaseModel):
|
||||
socket_type: ct.SocketType = ct.SocketType.MaxwellMediumNonLinearity
|
||||
|
||||
def init(self, bl_socket: MaxwellMediumNonLinearityBLSocket) -> None:
|
||||
pass
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
####################
|
||||
BL_REGISTER = [
|
||||
MaxwellMediumNonLinearityBLSocket,
|
||||
]
|
|
@ -2,83 +2,114 @@ import typing as typ
|
|||
|
||||
import bpy
|
||||
import pydantic as pyd
|
||||
import sympy as sp
|
||||
import sympy.physics.units as spu
|
||||
import tidy3d as td
|
||||
import scipy as sc
|
||||
|
||||
from .....utils.pydantic_sympy import ConstrSympyExpr, Complex
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
class MaxwellMediumBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.MaxwellMedium
|
||||
VAC_SPEED_OF_LIGHT = (
|
||||
sc.constants.speed_of_light
|
||||
* spu.meter/spu.second
|
||||
)
|
||||
|
||||
class MaxwellMediumBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.MaxwellMedium
|
||||
bl_label = "Maxwell Medium"
|
||||
|
||||
compatible_types = {
|
||||
td.components.medium.AbstractMedium: {}
|
||||
}
|
||||
use_units = True
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
rel_permittivity: bpy.props.FloatProperty(
|
||||
name="Permittivity",
|
||||
description="Represents a simple, real permittivity.",
|
||||
default=0.0,
|
||||
wl: bpy.props.FloatProperty(
|
||||
name="WL",
|
||||
description="WL to evaluate conductivity at",
|
||||
default=500.0,
|
||||
precision=4,
|
||||
update=(lambda self, context: self.trigger_updates()),
|
||||
step=50,
|
||||
update=(lambda self, context: self.sync_prop("wl", context)),
|
||||
)
|
||||
|
||||
rel_permittivity: bpy.props.FloatVectorProperty(
|
||||
name="Relative Permittivity",
|
||||
description="Represents a simple, complex permittivity",
|
||||
size=2,
|
||||
default=(1.0, 0.0),
|
||||
precision=2,
|
||||
update=(lambda self, context: self.sync_prop("rel_permittivity", context)),
|
||||
)
|
||||
|
||||
####################
|
||||
# - Socket UI
|
||||
####################
|
||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||
"""Draw the value of the area, including a toggle for
|
||||
specifying the active unit.
|
||||
"""
|
||||
col_row = col.row(align=True)
|
||||
col_row.prop(self, "rel_permittivity", text="ϵr")
|
||||
col.prop(self, "wl", text="λ")
|
||||
col.separator(factor=1.0)
|
||||
|
||||
split = col.split(factor=0.35, align=False)
|
||||
|
||||
col = split.column(align=True)
|
||||
col.label(text="ϵ_r (ℂ)")
|
||||
|
||||
col = split.column(align=True)
|
||||
col.prop(self, "rel_permittivity", text="")
|
||||
|
||||
####################
|
||||
# - Computation of Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> td.Medium:
|
||||
"""Return the built-in medium representation as a `tidy3d` object,
|
||||
ready to use in the simulation.
|
||||
|
||||
Returns:
|
||||
A completely normal medium with permittivity set.
|
||||
"""
|
||||
|
||||
return td.Medium(
|
||||
permittivity=self.rel_permittivity,
|
||||
def value(self) -> td.Medium:
|
||||
freq = spu.convert_to(
|
||||
VAC_SPEED_OF_LIGHT / (self.wl*self.unit),
|
||||
spu.hertz,
|
||||
) / spu.hertz
|
||||
return td.Medium.from_nk(
|
||||
n=self.rel_permittivity[0],
|
||||
k=self.rel_permittivity[1],
|
||||
freq=freq,
|
||||
)
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: typ.Any) -> None:
|
||||
"""Set the built-in medium representation by adjusting the
|
||||
permittivity, ONLY.
|
||||
@value.setter
|
||||
def value(
|
||||
self,
|
||||
value: tuple[ConstrSympyExpr(allow_variables=False), complex]
|
||||
) -> None:
|
||||
_wl, rel_permittivity = value
|
||||
|
||||
Args:
|
||||
value: Must be a tidy3d.Medium, or similar subclass.
|
||||
"""
|
||||
wl = float(
|
||||
spu.convert_to(
|
||||
_wl,
|
||||
self.unit,
|
||||
) / self.unit
|
||||
)
|
||||
self.wl = wl
|
||||
self.rel_permittivity = (rel_permittivity.real, rel_permittivity.imag)
|
||||
|
||||
# ONLY Allow td.Medium
|
||||
if isinstance(value, td.Medium):
|
||||
self.rel_permittivity = value.permittivity
|
||||
def sync_unit_change(self):
|
||||
"""Override unit change to only alter frequency unit."""
|
||||
|
||||
msg = f"Tried setting MaxwellMedium socket ({self}) to something that isn't a simple `tidy3d.Medium`"
|
||||
raise ValueError(msg)
|
||||
self.value = (
|
||||
self.wl * self.prev_unit,
|
||||
complex(*self.rel_permittivity)
|
||||
)
|
||||
self.prev_active_unit = self.active_unit
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class MaxwellMediumSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.MaxwellMedium
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.MaxwellMedium
|
||||
|
||||
rel_permittivity: float = 1.0
|
||||
default_permittivity_real: float = 1.0
|
||||
default_permittivity_imag: float = 0.0
|
||||
|
||||
def init(self, bl_socket: MaxwellMediumBLSocket) -> None:
|
||||
bl_socket.rel_permittivity = self.rel_permittivity
|
||||
bl_socket.rel_permittivity = (
|
||||
self.default_permittivity_real, self.default_permittivity_imag
|
||||
)
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
|
|
|
@ -1,37 +1,54 @@
|
|||
import typing as typ
|
||||
|
||||
import bpy
|
||||
import sympy.physics.units as spu
|
||||
import pydantic as pyd
|
||||
import tidy3d as td
|
||||
import scipy as sc
|
||||
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
class MaxwellMonitorBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.MaxwellMonitor
|
||||
bl_label = "Maxwell Bound Box"
|
||||
VAC_SPEED_OF_LIGHT = (
|
||||
sc.constants.speed_of_light
|
||||
* spu.meter/spu.second
|
||||
)
|
||||
|
||||
compatible_types = {
|
||||
td.BoundarySpec: {}
|
||||
}
|
||||
class MaxwellMonitorBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.MaxwellMonitor
|
||||
bl_label = "Maxwell Monitor"
|
||||
use_units = True
|
||||
|
||||
####################
|
||||
# - Computation of Default Value
|
||||
# - Properties
|
||||
####################
|
||||
wl: bpy.props.FloatProperty(
|
||||
name="WL",
|
||||
description="WL to store in monitor",
|
||||
default=500.0,
|
||||
precision=4,
|
||||
step=50,
|
||||
update=(lambda self, context: self.sync_prop("wl", context)),
|
||||
)
|
||||
|
||||
@property
|
||||
def default_value(self) -> td.BoundarySpec:
|
||||
return td.BoundarySpec()
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: typ.Any) -> None:
|
||||
return None
|
||||
def value(self) -> td.Monitor:
|
||||
freq = spu.convert_to(
|
||||
VAC_SPEED_OF_LIGHT / (self.wl*self.unit),
|
||||
spu.hertz,
|
||||
) / spu.hertz
|
||||
return td.FieldMonitor(
|
||||
size=(td.inf, td.inf, 0),
|
||||
freqs=[freq],
|
||||
name="fields",
|
||||
colocate=True,
|
||||
)
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class MaxwellMonitorSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.MaxwellMonitor
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.MaxwellMonitor
|
||||
|
||||
def init(self, bl_socket: MaxwellMonitorBLSocket) -> None:
|
||||
pass
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import typing as typ
|
||||
|
||||
import bpy
|
||||
import pydantic as pyd
|
||||
import tidy3d as td
|
||||
|
||||
from .. import base
|
||||
from ... import contracts as ct
|
||||
|
||||
class MaxwellSimDomainBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.MaxwellSimDomain
|
||||
bl_label = "Sim Domain"
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class MaxwellSimDomainSocketDef(pyd.BaseModel):
|
||||
socket_type: ct.SocketType = ct.SocketType.MaxwellSimDomain
|
||||
|
||||
def init(self, bl_socket: MaxwellSimDomainBLSocket) -> None:
|
||||
pass
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
####################
|
||||
BL_REGISTER = [
|
||||
MaxwellSimDomainBLSocket,
|
||||
]
|
|
@ -5,33 +5,17 @@ import pydantic as pyd
|
|||
import tidy3d as td
|
||||
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
class MaxwellSimGridAxisBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.MaxwellSimGridAxis
|
||||
class MaxwellSimGridAxisBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.MaxwellSimGridAxis
|
||||
bl_label = "Maxwell Bound Box"
|
||||
|
||||
compatible_types = {
|
||||
td.BoundarySpec: {}
|
||||
}
|
||||
|
||||
####################
|
||||
# - Computation of Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> td.BoundarySpec:
|
||||
return td.BoundarySpec()
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: typ.Any) -> None:
|
||||
return None
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class MaxwellSimGridAxisSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.MaxwellSimGridAxis
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.MaxwellSimGridAxis
|
||||
|
||||
def init(self, bl_socket: MaxwellSimGridAxisBLSocket) -> None:
|
||||
pass
|
||||
|
|
|
@ -5,36 +5,56 @@ import pydantic as pyd
|
|||
import tidy3d as td
|
||||
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
class MaxwellSimGridBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.MaxwellSimGrid
|
||||
bl_label = "Maxwell Bound Box"
|
||||
class MaxwellSimGridBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.MaxwellSimGrid
|
||||
bl_label = "Maxwell Sim Grid"
|
||||
|
||||
compatible_types = {
|
||||
td.BoundarySpec: {}
|
||||
}
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
min_steps_per_wl: bpy.props.FloatProperty(
|
||||
name="Minimum Steps per Wavelength",
|
||||
description="How many grid steps to ensure per wavelength",
|
||||
default=10.0,
|
||||
min=0.01,
|
||||
#step=10,
|
||||
precision=2,
|
||||
update=(lambda self, context: self.sync_prop("min_steps_per_wl", context)),
|
||||
)
|
||||
|
||||
####################
|
||||
# - Socket UI
|
||||
####################
|
||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||
split = col.split(factor=0.5, align=False)
|
||||
|
||||
col = split.column(align=True)
|
||||
col.label(text="min. stp/λ")
|
||||
|
||||
col = split.column(align=True)
|
||||
col.prop(self, "min_steps_per_wl", text="")
|
||||
|
||||
####################
|
||||
# - Computation of Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> td.BoundarySpec:
|
||||
return td.BoundarySpec()
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: typ.Any) -> None:
|
||||
return None
|
||||
def value(self) -> td.GridSpec:
|
||||
return td.GridSpec.auto(
|
||||
min_steps_per_wvl=self.min_steps_per_wl,
|
||||
)
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class MaxwellSimGridSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.MaxwellSimGrid
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.MaxwellSimGrid
|
||||
|
||||
min_steps_per_wl: float = 10.0
|
||||
|
||||
def init(self, bl_socket: MaxwellSimGridBLSocket) -> None:
|
||||
pass
|
||||
bl_socket.min_steps_per_wl = self.min_steps_per_wl
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
|
|
|
@ -5,33 +5,17 @@ import pydantic as pyd
|
|||
import tidy3d as td
|
||||
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
class MaxwellSourceBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.MaxwellSource
|
||||
class MaxwellSourceBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.MaxwellSource
|
||||
bl_label = "Maxwell Source"
|
||||
|
||||
compatible_types = {
|
||||
td.components.base_sim.source.AbstractSource: {}
|
||||
}
|
||||
|
||||
####################
|
||||
# - Computation of Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> td.Medium:
|
||||
return None
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: typ.Any) -> None:
|
||||
pass
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class MaxwellSourceSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.MaxwellSource
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.MaxwellSource
|
||||
|
||||
def init(self, bl_socket: MaxwellSourceBLSocket) -> None:
|
||||
pass
|
||||
|
|
|
@ -2,36 +2,19 @@ import typing as typ
|
|||
|
||||
import bpy
|
||||
import pydantic as pyd
|
||||
import tidy3d as td
|
||||
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
class MaxwellStructureBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.MaxwellStructure
|
||||
class MaxwellStructureBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.MaxwellStructure
|
||||
bl_label = "Maxwell Structure"
|
||||
|
||||
compatible_types = {
|
||||
td.components.structure.AbstractStructure: {}
|
||||
}
|
||||
|
||||
####################
|
||||
# - Computation of Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> None:
|
||||
return None
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: typ.Any) -> None:
|
||||
pass
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class MaxwellStructureSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.MaxwellStructure
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.MaxwellStructure
|
||||
|
||||
def init(self, bl_socket: MaxwellStructureBLSocket) -> None:
|
||||
pass
|
||||
|
|
|
@ -5,29 +5,17 @@ import pydantic as pyd
|
|||
import tidy3d as td
|
||||
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
class MaxwellTemporalShapeBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.MaxwellTemporalShape
|
||||
class MaxwellTemporalShapeBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.MaxwellTemporalShape
|
||||
bl_label = "Maxwell Temporal Shape"
|
||||
|
||||
####################
|
||||
# - Computation of Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> td.Medium:
|
||||
return None
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: typ.Any) -> None:
|
||||
pass
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class MaxwellTemporalShapeSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.MaxwellTemporalShape
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.MaxwellTemporalShape
|
||||
|
||||
def init(self, bl_socket: MaxwellTemporalShapeBLSocket) -> None:
|
||||
pass
|
||||
|
|
|
@ -4,24 +4,17 @@ import bpy
|
|||
import sympy as sp
|
||||
import pydantic as pyd
|
||||
|
||||
from .....utils.pydantic_sympy import SympyExpr
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
####################
|
||||
# - Blender Socket
|
||||
####################
|
||||
class ComplexNumberBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.ComplexNumber
|
||||
class ComplexNumberBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.ComplexNumber
|
||||
bl_label = "Complex Number"
|
||||
|
||||
compatible_types = {
|
||||
complex: {},
|
||||
sp.Expr: {
|
||||
lambda self, v: v.is_complex,
|
||||
lambda self, v: len(v.free_symbols) == 0,
|
||||
},
|
||||
}
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
|
@ -31,7 +24,7 @@ class ComplexNumberBLSocket(base.BLSocket):
|
|||
size=2,
|
||||
default=(0.0, 0.0),
|
||||
subtype='NONE',
|
||||
update=(lambda self, context: self.trigger_updates()),
|
||||
update=(lambda self, context: self.sync_prop("raw_value", context)),
|
||||
)
|
||||
coord_sys: bpy.props.EnumProperty(
|
||||
name="Coordinate System",
|
||||
|
@ -41,7 +34,7 @@ class ComplexNumberBLSocket(base.BLSocket):
|
|||
("POLAR", "Polar", "Use Polar Coordinates", "DRIVER_ROTATIONAL_DIFFERENCE", 1),
|
||||
],
|
||||
default="CARTESIAN",
|
||||
update=lambda self, context: self._update_coord_sys(),
|
||||
update=lambda self, context: self._sync_coord_sys(context),
|
||||
)
|
||||
|
||||
####################
|
||||
|
@ -55,34 +48,11 @@ class ComplexNumberBLSocket(base.BLSocket):
|
|||
col_row.prop(self, "raw_value", text="")
|
||||
col.prop(self, "coord_sys", text="")
|
||||
|
||||
def draw_preview(self, col_box: bpy.types.UILayout) -> None:
|
||||
"""Draw a live-preview value for the complex number, into the
|
||||
given preview box.
|
||||
|
||||
- Cartesian: a,b -> a + ib
|
||||
- Polar: r,t -> re^(it)
|
||||
|
||||
Returns:
|
||||
The sympy expression representing the complex number.
|
||||
"""
|
||||
if self.coord_sys == "CARTESIAN":
|
||||
text = f"= {self.default_value.n(2)}"
|
||||
|
||||
elif self.coord_sys == "POLAR":
|
||||
r = sp.Abs(self.default_value).n(2)
|
||||
theta_rad = sp.arg(self.default_value).n(2)
|
||||
text = f"= {r*sp.exp(sp.I*theta_rad)}"
|
||||
|
||||
else:
|
||||
raise RuntimeError("Invalid coordinate system for complex number")
|
||||
|
||||
col_box.label(text=text)
|
||||
|
||||
####################
|
||||
# - Computation of Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> sp.Expr:
|
||||
def value(self) -> SympyExpr:
|
||||
"""Return the complex number as a sympy expression, of a form
|
||||
determined by the coordinate system.
|
||||
|
||||
|
@ -100,8 +70,8 @@ class ComplexNumberBLSocket(base.BLSocket):
|
|||
"POLAR": v1 * sp.exp(sp.I*v2),
|
||||
}[self.coord_sys]
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: typ.Any) -> None:
|
||||
@value.setter
|
||||
def value(self, value: SympyExpr) -> None:
|
||||
"""Set the complex number from a sympy expression, using an internal
|
||||
representation determined by the coordinate system.
|
||||
|
||||
|
@ -122,7 +92,7 @@ class ComplexNumberBLSocket(base.BLSocket):
|
|||
####################
|
||||
# - Internal Update Methods
|
||||
####################
|
||||
def _update_coord_sys(self):
|
||||
def _sync_coord_sys(self, context: bpy.types.Context):
|
||||
if self.coord_sys == "CARTESIAN":
|
||||
r, theta_rad = self.raw_value
|
||||
self.raw_value = (
|
||||
|
@ -137,20 +107,17 @@ class ComplexNumberBLSocket(base.BLSocket):
|
|||
sp.arg(cart_value) if y != 0 else 0,
|
||||
)
|
||||
|
||||
self.trigger_updates()
|
||||
self.sync_prop("coord_sys", context)
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class ComplexNumberSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.ComplexNumber
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.ComplexNumber
|
||||
|
||||
preview: bool = False
|
||||
coord_sys: typ.Literal["CARTESIAN", "POLAR"] = "CARTESIAN"
|
||||
|
||||
def init(self, bl_socket: ComplexNumberBLSocket) -> None:
|
||||
bl_socket.preview_active = self.preview
|
||||
bl_socket.coord_sys = self.coord_sys
|
||||
|
||||
####################
|
||||
|
|
|
@ -4,14 +4,14 @@ import bpy
|
|||
import pydantic as pyd
|
||||
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
####################
|
||||
# - Blender Socket
|
||||
####################
|
||||
class IntegerNumberBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.IntegerNumber
|
||||
bl_label = "IntegerNumber"
|
||||
class IntegerNumberBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.IntegerNumber
|
||||
bl_label = "Integer Number"
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
|
@ -20,31 +20,37 @@ class IntegerNumberBLSocket(base.BLSocket):
|
|||
name="Integer",
|
||||
description="Represents an integer",
|
||||
default=0,
|
||||
update=(lambda self, context: self.trigger_updates()),
|
||||
update=(lambda self, context: self.sync_prop("raw_value", context)),
|
||||
)
|
||||
|
||||
####################
|
||||
# - Socket UI
|
||||
####################
|
||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||
col_row = col.row()
|
||||
col_row.prop(self, "raw_value", text="")
|
||||
|
||||
####################
|
||||
# - Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> None:
|
||||
def value(self) -> int:
|
||||
return self.raw_value
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: typ.Any) -> None:
|
||||
self.raw_value = int(value)
|
||||
@value.setter
|
||||
def value(self, value: int) -> None:
|
||||
self.raw_value = value
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class IntegerNumberSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.IntegerNumber
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.IntegerNumber
|
||||
|
||||
default_value: int = 0
|
||||
|
||||
def init(self, bl_socket: IntegerNumberBLSocket) -> None:
|
||||
bl_socket.raw_value = self.default_value
|
||||
bl_socket.value = self.default_value
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
|
|
|
@ -1,34 +1,62 @@
|
|||
import typing as typ
|
||||
|
||||
import bpy
|
||||
import sympy as sp
|
||||
import pydantic as pyd
|
||||
|
||||
from .....utils.pydantic_sympy import SympyExpr
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
####################
|
||||
# - Blender Socket
|
||||
####################
|
||||
class RationalNumberBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.RationalNumber
|
||||
class RationalNumberBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.RationalNumber
|
||||
bl_label = "Rational Number"
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
raw_value: bpy.props.IntVectorProperty(
|
||||
name="Rational Number",
|
||||
description="Represents a rational number (int / int)",
|
||||
size=2,
|
||||
default=(1, 1),
|
||||
subtype='NONE',
|
||||
update=(lambda self, context: self.sync_prop("raw_value", context)),
|
||||
)
|
||||
|
||||
####################
|
||||
# - UI
|
||||
####################
|
||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||
col_row = col.row(align=True)
|
||||
col_row.prop(self, "raw_value", text="")
|
||||
|
||||
####################
|
||||
# - Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> None:
|
||||
pass
|
||||
def value(self) -> sp.Rational:
|
||||
p, q = self.raw_value
|
||||
return sp.Rational(p, q)
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: typ.Any) -> None:
|
||||
pass
|
||||
@value.setter
|
||||
def value(self, value: float | tuple[int, int] | SympyExpr) -> None:
|
||||
if isinstance(value, float):
|
||||
approx_rational = sp.nsimplify(value)
|
||||
self.raw_value = (approx_rational.p, approx_rational.q)
|
||||
elif isinstance(value, tuple):
|
||||
self.raw_value = value
|
||||
else:
|
||||
self.raw_value = (value.p, value.q)
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class RationalNumberSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.RationalNumber
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.RationalNumber
|
||||
|
||||
def init(self, bl_socket: RationalNumberBLSocket) -> None:
|
||||
pass
|
||||
|
|
|
@ -4,24 +4,17 @@ import bpy
|
|||
import sympy as sp
|
||||
import pydantic as pyd
|
||||
|
||||
from .....utils.pydantic_sympy import SympyExpr
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
####################
|
||||
# - Blender Socket
|
||||
####################
|
||||
class RealNumberBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.RealNumber
|
||||
class RealNumberBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.RealNumber
|
||||
bl_label = "Real Number"
|
||||
|
||||
compatible_types = {
|
||||
float: {},
|
||||
sp.Expr: {
|
||||
lambda self, v: v.is_real,
|
||||
lambda self, v: len(v.free_symbols) == 0,
|
||||
},
|
||||
}
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
|
@ -30,46 +23,40 @@ class RealNumberBLSocket(base.BLSocket):
|
|||
description="Represents a real number",
|
||||
default=0.0,
|
||||
precision=6,
|
||||
update=(lambda self, context: self.trigger_updates()),
|
||||
update=(lambda self, context: self.sync_prop("raw_value", context)),
|
||||
)
|
||||
|
||||
####################
|
||||
# - Socket UI
|
||||
####################
|
||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||
col_row = col.row()
|
||||
col_row.prop(self, "raw_value", text="")
|
||||
|
||||
####################
|
||||
# - Computation of Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> float:
|
||||
"""Return the real number.
|
||||
|
||||
Returns:
|
||||
The real number as a float.
|
||||
"""
|
||||
|
||||
def value(self) -> float:
|
||||
return self.raw_value
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: typ.Any) -> None:
|
||||
"""Set the real number from some compatible type, namely
|
||||
real sympy expressions with no symbols, or floats.
|
||||
"""
|
||||
|
||||
# (Guard) Value Compatibility
|
||||
if not self.is_compatible(value):
|
||||
msg = f"Tried setting socket ({self}) to incompatible value ({value}) of type {type(value)}"
|
||||
raise ValueError(msg)
|
||||
|
||||
self.raw_value = float(value)
|
||||
@value.setter
|
||||
def value(self, value: float | SympyExpr) -> None:
|
||||
if isinstance(value, float):
|
||||
self.raw_value = value
|
||||
else:
|
||||
float(value.n())
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class RealNumberSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.RealNumber
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.RealNumber
|
||||
|
||||
default_value: float = 0.0
|
||||
|
||||
def init(self, bl_socket: RealNumberBLSocket) -> None:
|
||||
bl_socket.default_value = self.default_value
|
||||
bl_socket.value = self.default_value
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
|
|
|
@ -34,14 +34,7 @@ from . import pol_socket
|
|||
PhysicalPolSocketDef = pol_socket.PhysicalPolSocketDef
|
||||
|
||||
from . import freq_socket
|
||||
from . import vac_wl_socket
|
||||
PhysicalFreqSocketDef = freq_socket.PhysicalFreqSocketDef
|
||||
PhysicalVacWLSocketDef = vac_wl_socket.PhysicalVacWLSocketDef
|
||||
|
||||
from . import spec_rel_permit_dist_socket
|
||||
from . import spec_power_dist_socket
|
||||
PhysicalSpecRelPermDistSocketDef = spec_rel_permit_dist_socket.PhysicalSpecRelPermDistSocketDef
|
||||
PhysicalSpecPowerDistSocketDef = spec_power_dist_socket.PhysicalSpecPowerDistSocketDef
|
||||
|
||||
|
||||
BL_REGISTER = [
|
||||
|
@ -68,7 +61,4 @@ BL_REGISTER = [
|
|||
*pol_socket.BL_REGISTER,
|
||||
|
||||
*freq_socket.BL_REGISTER,
|
||||
*vac_wl_socket.BL_REGISTER,
|
||||
*spec_rel_permit_dist_socket.BL_REGISTER,
|
||||
*spec_power_dist_socket.BL_REGISTER,
|
||||
]
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
import typing as typ
|
||||
|
||||
import bpy
|
||||
import sympy.physics.units as spu
|
||||
import pydantic as pyd
|
||||
|
||||
from .....utils.pydantic_sympy import SympyExpr
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
####################
|
||||
# - Blender Socket
|
||||
####################
|
||||
class PhysicalAccelScalarBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.PhysicalAccelScalar
|
||||
bl_label = "PhysicalAccel"
|
||||
class PhysicalAccelScalarBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.PhysicalAccelScalar
|
||||
bl_label = "Accel Scalar"
|
||||
use_units = True
|
||||
|
||||
####################
|
||||
|
@ -22,28 +24,33 @@ class PhysicalAccelScalarBLSocket(base.BLSocket):
|
|||
description="Represents the unitless part of the acceleration",
|
||||
default=0.0,
|
||||
precision=6,
|
||||
update=(lambda self, context: self.trigger_updates()),
|
||||
update=(lambda self, context: self.sync_prop("raw_value", context)),
|
||||
)
|
||||
|
||||
####################
|
||||
# - Socket UI
|
||||
####################
|
||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||
col.prop(self, "raw_value", text="")
|
||||
|
||||
####################
|
||||
# - Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> None:
|
||||
def value(self) -> SympyExpr:
|
||||
return self.raw_value * self.unit
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: typ.Any) -> None:
|
||||
self.raw_value = self.value_as_unit(value)
|
||||
@value.setter
|
||||
def value(self, value: SympyExpr) -> None:
|
||||
self.raw_value = spu.convert_to(value, self.unit) / self.unit
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class PhysicalAccelScalarSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.PhysicalAccelScalar
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.PhysicalAccelScalar
|
||||
|
||||
default_unit: typ.Any | None = None
|
||||
default_unit: SympyExpr | None = None
|
||||
|
||||
def init(self, bl_socket: PhysicalAccelScalarBLSocket) -> None:
|
||||
if self.default_unit:
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
import typing as typ
|
||||
|
||||
import bpy
|
||||
import sympy.physics.units as spu
|
||||
import pydantic as pyd
|
||||
|
||||
from .....utils.pydantic_sympy import SympyExpr
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
####################
|
||||
# - Blender Socket
|
||||
####################
|
||||
class PhysicalAngleBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.PhysicalAngle
|
||||
bl_label = "PhysicalAngle"
|
||||
class PhysicalAngleBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.PhysicalAngle
|
||||
bl_label = "Physical Angle"
|
||||
use_units = True
|
||||
|
||||
####################
|
||||
|
@ -22,28 +24,33 @@ class PhysicalAngleBLSocket(base.BLSocket):
|
|||
description="Represents the unitless part of the acceleration",
|
||||
default=0.0,
|
||||
precision=4,
|
||||
update=(lambda self, context: self.trigger_updates()),
|
||||
update=(lambda self, context: self.sync_prop("raw_value", context)),
|
||||
)
|
||||
|
||||
####################
|
||||
# - Socket UI
|
||||
####################
|
||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||
col.prop(self, "raw_value", text="")
|
||||
|
||||
####################
|
||||
# - Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> None:
|
||||
def value(self) -> SympyExpr:
|
||||
return self.raw_value * self.unit
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: typ.Any) -> None:
|
||||
self.raw_value = self.value_as_unit(value)
|
||||
@value.setter
|
||||
def value(self, value: SympyExpr) -> None:
|
||||
self.raw_value = spu.convert_to(value, self.unit) / self.unit
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class PhysicalAngleSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.PhysicalAngle
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.PhysicalAngle
|
||||
|
||||
default_unit: typ.Any | None = None
|
||||
default_unit: SympyExpr | None = None
|
||||
|
||||
def init(self, bl_socket: PhysicalAngleBLSocket) -> None:
|
||||
if self.default_unit:
|
||||
|
|
|
@ -5,25 +5,15 @@ import sympy as sp
|
|||
import sympy.physics.units as spu
|
||||
import pydantic as pyd
|
||||
|
||||
from .....utils.pydantic_sympy import SympyExpr
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
class PhysicalAreaBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.PhysicalArea
|
||||
class PhysicalAreaBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.PhysicalArea
|
||||
bl_label = "Physical Area"
|
||||
use_units = True
|
||||
|
||||
compatible_types = {
|
||||
sp.Expr: {
|
||||
lambda self, v: v.is_real,
|
||||
lambda self, v: len(v.free_symbols) == 0,
|
||||
lambda self, v: any(
|
||||
contracts.is_exactly_expressed_as_unit(v, unit)
|
||||
for unit in self.units.values()
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
|
@ -32,19 +22,14 @@ class PhysicalAreaBLSocket(base.BLSocket):
|
|||
description="Represents the unitless part of the area",
|
||||
default=0.0,
|
||||
precision=6,
|
||||
update=(lambda self, context: self.trigger_updates()),
|
||||
update=(lambda self, context: self.sync_prop("raw_value", context)),
|
||||
)
|
||||
|
||||
####################
|
||||
# - Socket UI
|
||||
####################
|
||||
def draw_label_row(self, label_col_row: bpy.types.UILayout, text: str) -> None:
|
||||
label_col_row.label(text=text)
|
||||
label_col_row.prop(self, "raw_unit", text="")
|
||||
|
||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||
col_row = col.row(align=True)
|
||||
col_row.prop(self, "raw_value", text="")
|
||||
col.prop(self, "raw_value", text="")
|
||||
|
||||
####################
|
||||
# - Computation of Default Value
|
||||
|
@ -73,8 +58,7 @@ class PhysicalAreaBLSocket(base.BLSocket):
|
|||
# - Socket Configuration
|
||||
####################
|
||||
class PhysicalAreaSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.PhysicalArea
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.PhysicalArea
|
||||
|
||||
default_unit: typ.Any | None = None
|
||||
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
import typing as typ
|
||||
|
||||
import bpy
|
||||
import sympy.physics.units as spu
|
||||
import pydantic as pyd
|
||||
|
||||
from .....utils.pydantic_sympy import SympyExpr
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
####################
|
||||
# - Blender Socket
|
||||
####################
|
||||
class PhysicalForceScalarBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.PhysicalForceScalar
|
||||
bl_label = "PhysicalForceScalar"
|
||||
class PhysicalForceScalarBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.PhysicalForceScalar
|
||||
bl_label = "Force Scalar"
|
||||
use_units = True
|
||||
|
||||
####################
|
||||
|
@ -22,28 +24,33 @@ class PhysicalForceScalarBLSocket(base.BLSocket):
|
|||
description="Represents the unitless part of the force",
|
||||
default=0.0,
|
||||
precision=6,
|
||||
update=(lambda self, context: self.trigger_updates()),
|
||||
update=(lambda self, context: self.sync_prop("raw_value", context)),
|
||||
)
|
||||
|
||||
####################
|
||||
# - Socket UI
|
||||
####################
|
||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||
col.prop(self, "raw_value", text="")
|
||||
|
||||
####################
|
||||
# - Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> None:
|
||||
def value(self) -> SympyExpr:
|
||||
return self.raw_value * self.unit
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: typ.Any) -> None:
|
||||
self.raw_value = self.value_as_unit(value)
|
||||
@value.setter
|
||||
def value(self, value: SympyExpr) -> None:
|
||||
self.raw_value = spu.convert_to(value, self.unit) / self.unit
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class PhysicalForceScalarSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.PhysicalForceScalar
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.PhysicalForceScalar
|
||||
|
||||
default_unit: typ.Any | None = None
|
||||
default_unit: SympyExpr | None = None
|
||||
|
||||
def init(self, bl_socket: PhysicalForceScalarBLSocket) -> None:
|
||||
if self.default_unit:
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
import typing as typ
|
||||
|
||||
import bpy
|
||||
import sympy.physics.units as spu
|
||||
import pydantic as pyd
|
||||
|
||||
from .....utils.pydantic_sympy import SympyExpr
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
####################
|
||||
# - Blender Socket
|
||||
####################
|
||||
class PhysicalFreqBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.PhysicalFreq
|
||||
bl_label = "PhysicalFreq"
|
||||
class PhysicalFreqBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.PhysicalFreq
|
||||
bl_label = "Frequency"
|
||||
use_units = True
|
||||
|
||||
####################
|
||||
|
@ -22,29 +24,40 @@ class PhysicalFreqBLSocket(base.BLSocket):
|
|||
description="Represents the unitless part of the frequency",
|
||||
default=0.0,
|
||||
precision=6,
|
||||
update=(lambda self, context: self.trigger_updates()),
|
||||
update=(lambda self, context: self.sync_prop("raw_value", context)),
|
||||
)
|
||||
|
||||
####################
|
||||
# - Socket UI
|
||||
####################
|
||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||
col.prop(self, "raw_value", text="")
|
||||
|
||||
####################
|
||||
# - Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> None:
|
||||
def value(self) -> SympyExpr:
|
||||
return self.raw_value * self.unit
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: typ.Any) -> None:
|
||||
self.raw_value = self.value_as_unit(value)
|
||||
@value.setter
|
||||
def value(self, value: SympyExpr) -> None:
|
||||
self.raw_value = spu.convert_to(value, self.unit) / self.unit
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class PhysicalFreqSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.PhysicalFreq
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.PhysicalFreq
|
||||
|
||||
default_value: SympyExpr | None = None
|
||||
default_unit: SympyExpr | None = None
|
||||
|
||||
def init(self, bl_socket: PhysicalFreqBLSocket) -> None:
|
||||
pass
|
||||
if self.default_value:
|
||||
bl_socket.value = self.default_value
|
||||
if self.default_unit:
|
||||
bl_socket.unit = self.default_unit
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
import typing as typ
|
||||
|
||||
import bpy
|
||||
import sympy.physics.units as spu
|
||||
import pydantic as pyd
|
||||
|
||||
from .....utils.pydantic_sympy import SympyExpr
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
####################
|
||||
# - Blender Socket
|
||||
####################
|
||||
class PhysicalLengthBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.PhysicalLength
|
||||
class PhysicalLengthBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.PhysicalLength
|
||||
bl_label = "PhysicalLength"
|
||||
use_units = True
|
||||
|
||||
|
@ -22,28 +24,33 @@ class PhysicalLengthBLSocket(base.BLSocket):
|
|||
description="Represents the unitless part of the force",
|
||||
default=0.0,
|
||||
precision=6,
|
||||
update=(lambda self, context: self.trigger_updates()),
|
||||
update=(lambda self, context: self.sync_prop("raw_value", context)),
|
||||
)
|
||||
|
||||
####################
|
||||
# - Socket UI
|
||||
####################
|
||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||
col.prop(self, "raw_value", text="")
|
||||
|
||||
####################
|
||||
# - Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> None:
|
||||
def value(self) -> SympyExpr:
|
||||
return self.raw_value * self.unit
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: typ.Any) -> None:
|
||||
self.raw_value = self.value_as_unit(value)
|
||||
@value.setter
|
||||
def value(self, value: SympyExpr) -> None:
|
||||
self.raw_value = spu.convert_to(value, self.unit) / self.unit
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class PhysicalLengthSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.PhysicalLength
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.PhysicalLength
|
||||
|
||||
default_unit: typ.Any | None = None
|
||||
default_unit: SympyExpr | None = None
|
||||
|
||||
def init(self, bl_socket: PhysicalLengthBLSocket) -> None:
|
||||
if self.default_unit:
|
||||
|
|
|
@ -1,34 +1,57 @@
|
|||
import typing as typ
|
||||
|
||||
import bpy
|
||||
import sympy.physics.units as spu
|
||||
import pydantic as pyd
|
||||
|
||||
from .....utils.pydantic_sympy import SympyExpr
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
####################
|
||||
# - Blender Socket
|
||||
####################
|
||||
class PhysicalMassBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.PhysicalMass
|
||||
bl_label = "PhysicalMass"
|
||||
class PhysicalMassBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.PhysicalMass
|
||||
bl_label = "Mass"
|
||||
use_units = True
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
raw_value: bpy.props.FloatProperty(
|
||||
name="Unitless Mass",
|
||||
description="Represents the unitless part of mass",
|
||||
default=0.0,
|
||||
precision=6,
|
||||
update=(lambda self, context: self.sync_prop("raw_value", context)),
|
||||
)
|
||||
|
||||
####################
|
||||
# - Socket UI
|
||||
####################
|
||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||
col.prop(self, "raw_value", text="")
|
||||
|
||||
####################
|
||||
# - Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> None:
|
||||
pass
|
||||
def value(self) -> SympyExpr:
|
||||
return self.raw_value * self.unit
|
||||
|
||||
@value.setter
|
||||
def value(self, value: SympyExpr) -> None:
|
||||
self.raw_value = spu.convert_to(value, self.unit) / self.unit
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: typ.Any) -> None:
|
||||
pass
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class PhysicalMassSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.PhysicalMass
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.PhysicalMass
|
||||
|
||||
default_unit: SympyExpr | None = None
|
||||
|
||||
def init(self, bl_socket: PhysicalMassBLSocket) -> None:
|
||||
pass
|
||||
|
|
|
@ -5,25 +5,15 @@ import sympy as sp
|
|||
import sympy.physics.units as spu
|
||||
import pydantic as pyd
|
||||
|
||||
from .....utils.pydantic_sympy import SympyExpr
|
||||
from .. import base
|
||||
from ... import contracts
|
||||
from ... import contracts as ct
|
||||
|
||||
class PhysicalPoint3DBLSocket(base.BLSocket):
|
||||
socket_type = contracts.SocketType.PhysicalPoint3D
|
||||
bl_label = "Physical Volume"
|
||||
class PhysicalPoint3DBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.PhysicalPoint3D
|
||||
bl_label = "Volume"
|
||||
use_units = True
|
||||
|
||||
compatible_types = {
|
||||
sp.Expr: {
|
||||
lambda self, v: v.is_real,
|
||||
lambda self, v: len(v.free_symbols) == 0,
|
||||
lambda self, v: any(
|
||||
contracts.is_exactly_expressed_as_unit(v, unit)
|
||||
for unit in self.units.values()
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
|
@ -33,26 +23,31 @@ class PhysicalPoint3DBLSocket(base.BLSocket):
|
|||
size=3,
|
||||
default=(0.0, 0.0, 0.0),
|
||||
precision=4,
|
||||
update=(lambda self, context: self.trigger_updates()),
|
||||
update=(lambda self, context: self.sync_prop("raw_value", context)),
|
||||
)
|
||||
|
||||
####################
|
||||
# - Computation of Default Value
|
||||
# - Socket UI
|
||||
####################
|
||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||
col.prop(self, "raw_value", text="")
|
||||
|
||||
####################
|
||||
# - Default Value
|
||||
####################
|
||||
@property
|
||||
def default_value(self) -> sp.Expr:
|
||||
def value(self) -> sp.MatrixBase:
|
||||
return sp.Matrix(tuple(self.raw_value)) * self.unit
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: typ.Any) -> None:
|
||||
self.raw_value = self.value_as_unit(value)
|
||||
@value.setter
|
||||
def value(self, value: SympyExpr) -> None:
|
||||
self.raw_value = tuple(spu.convert_to(value, self.unit) / self.unit)
|
||||
|
||||
####################
|
||||
# - Socket Configuration
|
||||
####################
|
||||
class PhysicalPoint3DSocketDef(pyd.BaseModel):
|
||||
socket_type: contracts.SocketType = contracts.SocketType.PhysicalPoint3D
|
||||
label: str
|
||||
socket_type: ct.SocketType = ct.SocketType.PhysicalPoint3D
|
||||
|
||||
default_unit: typ.Any | None = None
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue