Compare commits
No commits in common. "c2db40ca6d788e9286ba6aba4b78c16a3cedb38c" and "a4e764ba219eb496f7bf598ff2dd1cfa9cb7244f" have entirely different histories.
c2db40ca6d
...
a4e764ba21
33
TODO.md
33
TODO.md
|
@ -33,7 +33,6 @@
|
||||||
## Outputs
|
## Outputs
|
||||||
[x] Viewer
|
[x] Viewer
|
||||||
- [ ] **BIG ONE**: Remove image preview when disabling plots.
|
- [ ] **BIG ONE**: Remove image preview when disabling plots.
|
||||||
- [ ] Declare Preview unit system on the viewer node.
|
|
||||||
- [ ] Either enforce singleton, or find a way to have several viewers at the same time.
|
- [ ] Either enforce singleton, or find a way to have several viewers at the same time.
|
||||||
- [ ] A setting that live-previews just a value.
|
- [ ] A setting that live-previews just a value.
|
||||||
- [ ] Pop-up multiline string print as alternative to console print.
|
- [ ] Pop-up multiline string print as alternative to console print.
|
||||||
|
@ -175,24 +174,13 @@
|
||||||
[ ] Tests / Monkey (suzanne deserves to be simulated, she may need manifolding up though :))
|
[ ] Tests / Monkey (suzanne deserves to be simulated, she may need manifolding up though :))
|
||||||
[ ] Tests / Wood Pile
|
[ ] Tests / Wood Pile
|
||||||
|
|
||||||
[ ] Structures / Primitives / Plane
|
[ ] Primitives / Plane
|
||||||
[x] Structures / Primitives / Box
|
[ ] Primitives / Box
|
||||||
[x] Structures / Primitives / Sphere
|
[ ] Primitives / Sphere
|
||||||
[ ] Structures / Primitives / Cylinder
|
[ ] Primitives / Cylinder
|
||||||
[x] Structures / Primitives / Ring
|
[ ] Primitives / Ring
|
||||||
[ ] Structures / Primitives / Capsule
|
[ ] Primitives / Capsule
|
||||||
[ ] Structures / Primitives / Cone
|
[ ] Primitives / Cone
|
||||||
|
|
||||||
[ ] Structures / Arrays / Square
|
|
||||||
[ ] Structures / Arrays / Square-Hole
|
|
||||||
[ ] Structures / Arrays / Cyl
|
|
||||||
[ ] Structures / Arrays / Cyl-Hole
|
|
||||||
[x] Structures / Arrays / Box
|
|
||||||
[x] Structures / Arrays / Sphere
|
|
||||||
[ ] Structures / Arrays / Cylinder
|
|
||||||
[-] Structures / Arrays / Ring
|
|
||||||
[ ] Structures / Arrays / Capsule
|
|
||||||
[ ] Structures / Arrays / Cone
|
|
||||||
|
|
||||||
[ ] Array / Square Array **NOTE: Ring and cylinder**
|
[ ] Array / Square Array **NOTE: Ring and cylinder**
|
||||||
[ ] Array / Hex Array **NOTE: Ring and cylinder**
|
[ ] Array / Hex Array **NOTE: Ring and cylinder**
|
||||||
|
@ -349,15 +337,12 @@
|
||||||
|
|
||||||
# Architecture
|
# Architecture
|
||||||
## CRITICAL
|
## CRITICAL
|
||||||
With these things in place, we're in tip top shape:
|
With these things in place
|
||||||
[ ] Linkability / Appendability of library GeoNodes groups, including being able to semantically ask for a particular GeoNodes tree without 'magic strings' that are entirely end-user-file dependent, is completely critical. especially
|
[ ] Linkability / Appendability of library GeoNodes groups, including being able to semantically ask for a particular GeoNodes tree without 'magic strings' that are entirely end-user-file dependent, is completely critical. especially
|
||||||
[ ] Simplify the boilerplate needed to add a particular 3D preview driven by the input sockets of a particular GeoNodes group. It's currently hard for all the wrong reasons, and greatly halts our velocity in developing useful 3D previews of any/everything.
|
|
||||||
[ ] Finalize Viewer node unit systems.
|
|
||||||
[ ] Introduce a simplified (maybe caching) method of translating sympy-enabled values, ex. 'Center', into values for external use (ex. in a Tidy3D object or in a Blender preview) based on
|
|
||||||
[ ] Abstract the actual unit system dict-like data structure out from the UnitSystem socket.
|
|
||||||
[ ] Ship the addon with libraries of GeoNodes groups (with NO dependency on the addon), which are linked (internal use) or appended (end-user-selected structures) when needed for previewing.
|
[ ] Ship the addon with libraries of GeoNodes groups (with NO dependency on the addon), which are linked (internal use) or appended (end-user-selected structures) when needed for previewing.
|
||||||
- I don't know that library overrides are the correct approach when it comes to structures used by the end-user. It's extremely easy to make a change to a library structure (or have one made for us by a Blender update!) that completely wrecks all end-user simulations that use it, or override it. By appending, the structure becomes 'part of' the user's simulation, which also makes it quite a bit easier for the user to alter (even drastically) for their own needs.
|
- I don't know that library overrides are the correct approach when it comes to structures used by the end-user. It's extremely easy to make a change to a library structure (or have one made for us by a Blender update!) that completely wrecks all end-user simulations that use it, or override it. By appending, the structure becomes 'part of' the user's simulation, which also makes it quite a bit easier for the user to alter (even drastically) for their own needs.
|
||||||
[ ] License header UI for MaxwellSimTrees, to clarify the AGPL-compatible potentially user-selected license that trees must be distributed under.
|
[ ] License header UI for MaxwellSimTrees, to clarify the AGPL-compatible potentially user-selected license that trees must be distributed under.
|
||||||
|
[ ] Simplify the boilerplate needed to add a particular 3D preview driven by the input sockets of a particular GeoNodes group. It's currently hard for all the wrong reasons, and greatly halts our velocity in developing useful 3D previews of any/everything.
|
||||||
|
|
||||||
## Registration and Contracts
|
## Registration and Contracts
|
||||||
[x] Finish the contract code converting from Blender sockets to our sockets based on dimensionality and the property description.
|
[x] Finish the contract code converting from Blender sockets to our sockets based on dimensionality and the property description.
|
||||||
|
|
|
@ -14,6 +14,7 @@ dependencies = [
|
||||||
"networkx==3.2.*",
|
"networkx==3.2.*",
|
||||||
"rich==12.5.*",
|
"rich==12.5.*",
|
||||||
"rtree==1.2.*",
|
"rtree==1.2.*",
|
||||||
|
|
||||||
# Pin Blender 4.1.0-Compatible Versions
|
# Pin Blender 4.1.0-Compatible Versions
|
||||||
## The dependency resolver will report if anything is wonky.
|
## The dependency resolver will report if anything is wonky.
|
||||||
"urllib3==1.26.8",
|
"urllib3==1.26.8",
|
||||||
|
@ -99,7 +100,6 @@ ignore = [
|
||||||
"Q001", # Conflicts w/Formatter
|
"Q001", # Conflicts w/Formatter
|
||||||
"Q002", # Conflicts w/Formatter
|
"Q002", # Conflicts w/Formatter
|
||||||
"Q003", # Conflicts w/Formatter
|
"Q003", # Conflicts w/Formatter
|
||||||
"D206", # Conflicts w/Formatter
|
|
||||||
"B008", # FastAPI uses this for Depends(), Security(), etc. .
|
"B008", # FastAPI uses this for Depends(), Security(), etc. .
|
||||||
"E701", # class foo(Parent): pass or if simple: return are perfectly elegant
|
"E701", # class foo(Parent): pass or if simple: return are perfectly elegant
|
||||||
"ERA001", # 'Commented-out code' seems to be just about anything to ruff
|
"ERA001", # 'Commented-out code' seems to be just about anything to ruff
|
||||||
|
|
|
@ -46,10 +46,9 @@ BL_REGISTER__BEFORE_DEPS = [
|
||||||
def BL_REGISTER__AFTER_DEPS(path_deps: Path):
|
def BL_REGISTER__AFTER_DEPS(path_deps: Path):
|
||||||
log.info('Loading After-Deps BL_REGISTER')
|
log.info('Loading After-Deps BL_REGISTER')
|
||||||
with pydeps.importable_addon_deps(path_deps):
|
with pydeps.importable_addon_deps(path_deps):
|
||||||
from . import assets, node_trees, operators
|
from . import node_trees, operators
|
||||||
return [
|
return [
|
||||||
*operators.BL_REGISTER,
|
*operators.BL_REGISTER,
|
||||||
*assets.BL_REGISTER,
|
|
||||||
*node_trees.BL_REGISTER,
|
*node_trees.BL_REGISTER,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -63,10 +62,9 @@ BL_KEYMAP_ITEM_DEFS__BEFORE_DEPS = [
|
||||||
def BL_KEYMAP_ITEM_DEFS__AFTER_DEPS(path_deps: Path):
|
def BL_KEYMAP_ITEM_DEFS__AFTER_DEPS(path_deps: Path):
|
||||||
log.info('Loading After-Deps BL_KEYMAP_ITEM_DEFS')
|
log.info('Loading After-Deps BL_KEYMAP_ITEM_DEFS')
|
||||||
with pydeps.importable_addon_deps(path_deps):
|
with pydeps.importable_addon_deps(path_deps):
|
||||||
from . import assets, operators
|
from . import operators
|
||||||
return [
|
return [
|
||||||
*operators.BL_KEYMAP_ITEM_DEFS,
|
*operators.BL_KEYMAP_ITEM_DEFS,
|
||||||
*assets.BL_KEYMAP_ITEM_DEFS,
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
from . import import_geonodes
|
|
||||||
|
|
||||||
BL_REGISTER = [
|
|
||||||
*import_geonodes.BL_REGISTER,
|
|
||||||
]
|
|
||||||
|
|
||||||
BL_KEYMAP_ITEM_DEFS = [
|
|
||||||
*import_geonodes.BL_KEYMAP_ITEM_DEFS,
|
|
||||||
]
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'BL_REGISTER',
|
|
||||||
'BL_KEYMAP_ITEM_DEFS',
|
|
||||||
]
|
|
BIN
src/blender_maxwell/assets/assets.blend (Stored with Git LFS)
BIN
src/blender_maxwell/assets/assets.blend (Stored with Git LFS)
Binary file not shown.
|
@ -1,10 +0,0 @@
|
||||||
# This is an Asset Catalog Definition file for Blender.
|
|
||||||
#
|
|
||||||
# Empty lines and lines starting with `#` will be ignored.
|
|
||||||
# The first non-ignored line should be the version indicator.
|
|
||||||
# Other lines are of the format "UUID:catalog/path/for/assets:simple catalog name"
|
|
||||||
|
|
||||||
VERSION 1
|
|
||||||
|
|
||||||
783a7efe-c424-42b1-9771-42c862515891:Structures:Structures
|
|
||||||
ccb80eec-7e20-453d-89fb-0486b7abf7d4:Structures/Primitives:Structures-Primitives
|
|
|
@ -1,10 +0,0 @@
|
||||||
# This is an Asset Catalog Definition file for Blender.
|
|
||||||
#
|
|
||||||
# Empty lines and lines starting with `#` will be ignored.
|
|
||||||
# The first non-ignored line should be the version indicator.
|
|
||||||
# Other lines are of the format "UUID:catalog/path/for/assets:simple catalog name"
|
|
||||||
|
|
||||||
VERSION 1
|
|
||||||
|
|
||||||
783a7efe-c424-42b1-9771-42c862515891:Structures:Structures
|
|
||||||
ccb80eec-7e20-453d-89fb-0486b7abf7d4:Structures/Primitives:Structures-Primitives
|
|
BIN
src/blender_maxwell/assets/geonodes/primitives/box.blend (Stored with Git LFS)
BIN
src/blender_maxwell/assets/geonodes/primitives/box.blend (Stored with Git LFS)
Binary file not shown.
Binary file not shown.
BIN
src/blender_maxwell/assets/geonodes/primitives/ring.blend (Stored with Git LFS)
BIN
src/blender_maxwell/assets/geonodes/primitives/ring.blend (Stored with Git LFS)
Binary file not shown.
BIN
src/blender_maxwell/assets/geonodes/primitives/sphere.blend (Stored with Git LFS)
BIN
src/blender_maxwell/assets/geonodes/primitives/sphere.blend (Stored with Git LFS)
Binary file not shown.
BIN
src/blender_maxwell/assets/geonodes/template.blend (Stored with Git LFS)
BIN
src/blender_maxwell/assets/geonodes/template.blend (Stored with Git LFS)
Binary file not shown.
|
@ -1,320 +0,0 @@
|
||||||
"""Provides for the linking and/or appending of geometry nodes trees from vendored libraries included in Blender maxwell."""
|
|
||||||
|
|
||||||
import enum
|
|
||||||
import typing as typ
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import bpy
|
|
||||||
import typing_extensions as typx
|
|
||||||
|
|
||||||
from .. import info
|
|
||||||
from ..utils import logger
|
|
||||||
|
|
||||||
log = logger.get(__name__)
|
|
||||||
|
|
||||||
BLOperatorStatus: typ.TypeAlias = set[
|
|
||||||
typx.Literal['RUNNING_MODAL', 'CANCELLED', 'FINISHED', 'PASS_THROUGH', 'INTERFACE']
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - GeoNodes Specification
|
|
||||||
####################
|
|
||||||
class GeoNodes(enum.StrEnum):
|
|
||||||
"""Defines available GeoNodes groups vendored as part of Blender Maxwell.
|
|
||||||
|
|
||||||
The value of this StrEnum is both the name of the .blend file containing the GeoNodes group, and of the GeoNodes group itself.
|
|
||||||
"""
|
|
||||||
|
|
||||||
PrimitiveBox = 'box'
|
|
||||||
PrimitiveRing = 'ring'
|
|
||||||
PrimitiveSphere = 'sphere'
|
|
||||||
|
|
||||||
|
|
||||||
# GeoNodes Path Mapping
|
|
||||||
GN_PRIMITIVES_PATH = info.PATH_ASSETS / 'geonodes' / 'primitives'
|
|
||||||
GN_PARENT_PATHS: dict[GeoNodes, Path] = {
|
|
||||||
GeoNodes.PrimitiveBox: GN_PRIMITIVES_PATH,
|
|
||||||
GeoNodes.PrimitiveRing: GN_PRIMITIVES_PATH,
|
|
||||||
GeoNodes.PrimitiveSphere: GN_PRIMITIVES_PATH,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Import GeoNodes (Link/Append)
|
|
||||||
####################
|
|
||||||
ImportMethod: typ.TypeAlias = typx.Literal['append', 'link']
|
|
||||||
|
|
||||||
|
|
||||||
def import_geonodes(
|
|
||||||
geonodes: GeoNodes,
|
|
||||||
import_method: ImportMethod,
|
|
||||||
force_import: bool = False,
|
|
||||||
) -> bpy.types.GeometryNodeGroup:
|
|
||||||
"""Given a pre-defined GeoNodes group packaged with Blender Maxwell.
|
|
||||||
|
|
||||||
The procedure is as follows:
|
|
||||||
|
|
||||||
- Link it to the current .blend file.
|
|
||||||
- Retrieve the node group and return it.
|
|
||||||
"""
|
|
||||||
if geonodes in bpy.data.node_groups and not force_import:
|
|
||||||
return bpy.data.node_groups[geonodes]
|
|
||||||
|
|
||||||
filename = geonodes
|
|
||||||
filepath = str(
|
|
||||||
GN_PARENT_PATHS[geonodes] / (geonodes + '.blend') / 'NodeTree' / geonodes
|
|
||||||
)
|
|
||||||
directory = filepath.removesuffix(geonodes)
|
|
||||||
log.info(
|
|
||||||
'%s GeoNodes (filename=%s, directory=%s, filepath=%s)',
|
|
||||||
'Linking' if import_method == 'link' else 'Appending',
|
|
||||||
filename,
|
|
||||||
directory,
|
|
||||||
filepath,
|
|
||||||
)
|
|
||||||
bpy.ops.wm.append(
|
|
||||||
filepath=filepath,
|
|
||||||
directory=directory,
|
|
||||||
filename=filename,
|
|
||||||
check_existing=False,
|
|
||||||
set_fake=True,
|
|
||||||
link=import_method == 'link',
|
|
||||||
)
|
|
||||||
|
|
||||||
return bpy.data.node_groups[geonodes]
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - GeoNodes Asset Shelf Panel for MaxwellSimTree
|
|
||||||
####################
|
|
||||||
class NodeAssetPanel(bpy.types.Panel):
|
|
||||||
bl_idname = 'blender_maxwell.panel__node_asset_panel'
|
|
||||||
bl_label = 'Node GeoNodes Asset Panel'
|
|
||||||
bl_space_type = 'NODE_EDITOR'
|
|
||||||
bl_region_type = 'UI'
|
|
||||||
bl_category = 'Assets'
|
|
||||||
|
|
||||||
# @classmethod
|
|
||||||
# def poll(cls, context):
|
|
||||||
# return (
|
|
||||||
# (space := context.get('space_data')) is not None
|
|
||||||
# and (node_tree := space.get('node_tree')) is not None
|
|
||||||
# and (node_tree.bl_idname == 'MaxwellSimTreeType')
|
|
||||||
# )
|
|
||||||
|
|
||||||
def draw(self, context):
|
|
||||||
layout = self.layout
|
|
||||||
workspace = context.workspace
|
|
||||||
wm = context.window_manager
|
|
||||||
|
|
||||||
# list_id must be unique otherwise behaviour gets weird when the template_asset_view is shown twice
|
|
||||||
# (drag operator stops working in AssetPanelDrag, clickable area of all Assets in AssetPanelNoDrag gets
|
|
||||||
# reduced to below the Asset name and clickable area of Current File Assets in AssetPanelDrag gets
|
|
||||||
# reduced as if it didn't have a drag operator)
|
|
||||||
_activate_op_props, _drag_op_props = layout.template_asset_view(
|
|
||||||
'geo_nodes_asset_shelf',
|
|
||||||
workspace,
|
|
||||||
'asset_library_reference',
|
|
||||||
wm,
|
|
||||||
'active_asset_list',
|
|
||||||
wm,
|
|
||||||
'active_asset_index',
|
|
||||||
drag_operator=AppendGeoNodes.bl_idname,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Append GeoNodes Operator
|
|
||||||
####################
|
|
||||||
def get_view_location(region, coords, ui_scale):
|
|
||||||
x, y = region.view2d.region_to_view(*coords)
|
|
||||||
return x / ui_scale, y / ui_scale
|
|
||||||
|
|
||||||
|
|
||||||
class AppendGeoNodes(bpy.types.Operator):
|
|
||||||
"""Operator allowing the user to append a vendored GeoNodes tree for use in a simulation."""
|
|
||||||
|
|
||||||
bl_idname = 'blender_maxwell.blends__import_geo_nodes'
|
|
||||||
bl_label = 'Import GeoNode Tree'
|
|
||||||
bl_description = 'Append a geometry node tree from the Blender Maxwell plugin, either via linking or appending'
|
|
||||||
bl_options = frozenset({'REGISTER'})
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Properties
|
|
||||||
####################
|
|
||||||
_asset: bpy.types.AssetRepresentation | None = None
|
|
||||||
_start_drag_x: bpy.props.IntProperty()
|
|
||||||
_start_drag_y: bpy.props.IntProperty()
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - UI
|
|
||||||
####################
|
|
||||||
def draw(self, _: bpy.types.Context) -> None:
|
|
||||||
"""Draws the UI of the operator."""
|
|
||||||
layout = self.layout
|
|
||||||
col = layout.column()
|
|
||||||
col.prop(self, 'geonodes_to_append', expand=True)
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Execution
|
|
||||||
####################
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context: bpy.types.Context) -> bool:
|
|
||||||
"""Defines when the operator can be run.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Whether the operator can be run.
|
|
||||||
"""
|
|
||||||
return context.asset is not None
|
|
||||||
|
|
||||||
def invoke(self, context, event):
|
|
||||||
self._start_drag_x = event.mouse_x
|
|
||||||
self._start_drag_y = event.mouse_y
|
|
||||||
return self.execute(context)
|
|
||||||
|
|
||||||
def execute(self, context: bpy.types.Context) -> BLOperatorStatus:
|
|
||||||
"""Initializes the while-dragging modal handler, which executes custom logic when the mouse button is released.
|
|
||||||
|
|
||||||
Runs in response to drag_handler of a `UILayout.template_asset_view`.
|
|
||||||
"""
|
|
||||||
asset: bpy.types.AssetRepresentation = context.asset
|
|
||||||
log.debug('Dragging Asset: %s', asset.name)
|
|
||||||
|
|
||||||
# Store Asset for Modal & Drag Start
|
|
||||||
self._asset = context.asset
|
|
||||||
|
|
||||||
# Register Modal Operator & Tag Area for Redraw
|
|
||||||
context.window_manager.modal_handler_add(self)
|
|
||||||
context.area.tag_redraw()
|
|
||||||
|
|
||||||
# Set Modal Cursor
|
|
||||||
context.window.cursor_modal_set('CROSS')
|
|
||||||
|
|
||||||
# Return Status of Running Modal
|
|
||||||
return {'RUNNING_MODAL'}
|
|
||||||
|
|
||||||
def modal(
|
|
||||||
self, context: bpy.types.Context, event: bpy.types.Event
|
|
||||||
) -> BLOperatorStatus:
|
|
||||||
"""When LMB is released, creates a GeoNodes Structure node.
|
|
||||||
|
|
||||||
Runs in response to events in the node editor while dragging an asset from the side panel.
|
|
||||||
"""
|
|
||||||
if (asset := self._asset) is None:
|
|
||||||
return {'PASS_THROUGH'}
|
|
||||||
|
|
||||||
if event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
|
|
||||||
log.debug('Released Dragged Asset: %s', asset.name)
|
|
||||||
area = context.area
|
|
||||||
editor_region = next(
|
|
||||||
region for region in area.regions.values() if region.type == 'WINDOW'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check if Mouse Coordinates are:
|
|
||||||
## - INSIDE of Node Editor
|
|
||||||
## - INSIDE of Node Editor's WINDOW Region
|
|
||||||
if (
|
|
||||||
(event.mouse_x >= area.x and event.mouse_x < area.x + area.width)
|
|
||||||
and (event.mouse_y >= area.y and event.mouse_y < area.y + area.height)
|
|
||||||
) and (
|
|
||||||
(
|
|
||||||
event.mouse_x >= editor_region.x
|
|
||||||
and event.mouse_x < editor_region.x + editor_region.width
|
|
||||||
)
|
|
||||||
and (
|
|
||||||
event.mouse_y >= editor_region.y
|
|
||||||
and event.mouse_y < editor_region.y + editor_region.height
|
|
||||||
)
|
|
||||||
):
|
|
||||||
log.info(
|
|
||||||
'Asset "%s" Released in Main Window of Node Editor', asset.name
|
|
||||||
)
|
|
||||||
space = context.space_data
|
|
||||||
node_tree = space.node_tree
|
|
||||||
|
|
||||||
# Computing GeoNodes View Location
|
|
||||||
## 1. node_tree.cursor_location gives clicked loc, not released.
|
|
||||||
## 2. event.mouse_region_* has inverted x wrt. event.mouse_*.
|
|
||||||
## - View2D.region_to_view expects the event.mouse_* order.
|
|
||||||
## - Is it a bug? Who knows!
|
|
||||||
## 3. We compute it manually, to avoid the jank.
|
|
||||||
node_location = get_view_location(
|
|
||||||
editor_region,
|
|
||||||
[
|
|
||||||
event.mouse_x - editor_region.x,
|
|
||||||
event.mouse_y - editor_region.y,
|
|
||||||
],
|
|
||||||
context.preferences.system.ui_scale,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create GeoNodes Structure Node
|
|
||||||
## 1. Deselect other nodes
|
|
||||||
## 2. Select the new one
|
|
||||||
## 3. Move it into place
|
|
||||||
## 4. Redraw (so we see the new node right away)
|
|
||||||
log.info(
|
|
||||||
'Creating GeoNodes Structure Node at (%d, %d)',
|
|
||||||
*tuple(node_location),
|
|
||||||
)
|
|
||||||
bpy.ops.node.select_all(action='DESELECT')
|
|
||||||
node = node_tree.nodes.new('GeoNodesStructureNodeType')
|
|
||||||
node.select = True
|
|
||||||
node.location.x = node_location[0]
|
|
||||||
node.location.y = node_location[1]
|
|
||||||
context.area.tag_redraw()
|
|
||||||
|
|
||||||
# Import & Attach the GeoNodes Tree to the Node
|
|
||||||
geonodes = import_geonodes(asset.name, 'append')
|
|
||||||
node.inputs['GeoNodes'].value = geonodes
|
|
||||||
|
|
||||||
# Restore the Pre-Modal Mouse Cursor Shape
|
|
||||||
context.window.cursor_modal_restore()
|
|
||||||
return {'FINISHED'}
|
|
||||||
|
|
||||||
return {'RUNNING_MODAL'}
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Blender Registration
|
|
||||||
####################
|
|
||||||
# def initialize_asset_libraries(_: bpy.types.Scene):
|
|
||||||
# bpy.app.handlers.load_post.append(initialize_asset_libraries)
|
|
||||||
## TODO: Move to top-level registration.
|
|
||||||
|
|
||||||
asset_libraries = bpy.context.preferences.filepaths.asset_libraries
|
|
||||||
if (
|
|
||||||
asset_library_idx := asset_libraries.find('Blender Maxwell')
|
|
||||||
) != -1 and asset_libraries['Blender Maxwell'].path != str(info.PATH_ASSETS):
|
|
||||||
bpy.ops.preferences.asset_library_remove(asset_library_idx)
|
|
||||||
|
|
||||||
if 'Blender Maxwell' not in asset_libraries:
|
|
||||||
bpy.ops.preferences.asset_library_add()
|
|
||||||
asset_library = asset_libraries[-1] ## Since the operator adds to the end
|
|
||||||
asset_library.name = 'Blender Maxwell'
|
|
||||||
asset_library.path = str(info.PATH_ASSETS)
|
|
||||||
|
|
||||||
bpy.types.WindowManager.active_asset_list = bpy.props.CollectionProperty(
|
|
||||||
type=bpy.types.AssetHandle
|
|
||||||
)
|
|
||||||
bpy.types.WindowManager.active_asset_index = bpy.props.IntProperty()
|
|
||||||
## TODO: Do something differently
|
|
||||||
|
|
||||||
BL_REGISTER = [
|
|
||||||
# GeoNodesAssetShelf,
|
|
||||||
NodeAssetPanel,
|
|
||||||
AppendGeoNodes,
|
|
||||||
]
|
|
||||||
|
|
||||||
BL_KEYMAP_ITEM_DEFS = [
|
|
||||||
# {
|
|
||||||
# '_': [
|
|
||||||
# AppendGeoNodes.bl_idname,
|
|
||||||
# 'LEFTMOUSE',
|
|
||||||
# 'CLICK_DRAG',
|
|
||||||
# ],
|
|
||||||
# 'ctrl': False,
|
|
||||||
# 'shift': False,
|
|
||||||
# 'alt': False,
|
|
||||||
# }
|
|
||||||
]
|
|
|
@ -3,54 +3,38 @@ from pathlib import Path
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
PATH_ADDON_ROOT = Path(__file__).resolve().parent
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Addon Info
|
# - Addon Info
|
||||||
####################
|
####################
|
||||||
|
PATH_ADDON_ROOT = Path(__file__).resolve().parent
|
||||||
|
|
||||||
|
# Addon Information
|
||||||
|
## bl_info is filled with PROJ_SPEC when packing the .zip.
|
||||||
with (PATH_ADDON_ROOT / 'pyproject.toml').open('rb') as f:
|
with (PATH_ADDON_ROOT / 'pyproject.toml').open('rb') as f:
|
||||||
PROJ_SPEC = tomllib.load(f)
|
PROJ_SPEC = tomllib.load(f)
|
||||||
## bl_info is filled with PROJ_SPEC when packing the .zip.
|
|
||||||
|
|
||||||
ADDON_NAME = PROJ_SPEC['project']['name']
|
ADDON_NAME = PROJ_SPEC['project']['name']
|
||||||
ADDON_VERSION = PROJ_SPEC['project']['version']
|
ADDON_VERSION = PROJ_SPEC['project']['version']
|
||||||
|
|
||||||
####################
|
# PyDeps Path Info
|
||||||
# - Asset Info
|
|
||||||
####################
|
|
||||||
PATH_ASSETS = PATH_ADDON_ROOT / 'assets'
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - PyDeps Info
|
|
||||||
####################
|
|
||||||
PATH_REQS = PATH_ADDON_ROOT / 'requirements.lock'
|
|
||||||
DEFAULT_PATH_DEPS = PATH_ADDON_ROOT / '.addon_dependencies'
|
|
||||||
## requirements.lock is written when packing the .zip.
|
## requirements.lock is written when packing the .zip.
|
||||||
## By default, the addon pydeps are kept in the addon dir.
|
## By default, the addon pydeps are kept in the addon dir.
|
||||||
|
PATH_REQS = PATH_ADDON_ROOT / 'requirements.lock'
|
||||||
|
DEFAULT_PATH_DEPS = PATH_ADDON_ROOT / '.addon_dependencies'
|
||||||
|
|
||||||
####################
|
# Logging Info
|
||||||
# - Local Addon Cache
|
|
||||||
####################
|
|
||||||
ADDON_CACHE = PATH_ADDON_ROOT / '.addon_cache'
|
|
||||||
ADDON_CACHE.mkdir(exist_ok=True)
|
|
||||||
## TODO: Addon preferences?
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Logging Info
|
|
||||||
####################
|
|
||||||
DEFAULT_LOG_PATH = PATH_ADDON_ROOT / 'addon.log'
|
|
||||||
DEFAULT_LOG_PATH.touch(exist_ok=True)
|
|
||||||
## By default, the addon file log writes to the addon dir.
|
## By default, the addon file log writes to the addon dir.
|
||||||
## The initial .log_level contents are written when packing the .zip.
|
## The initial .log_level contents are written when packing the .zip.
|
||||||
## Subsequent changes are managed by nodeps.utils.simple_logger.py.
|
## Subsequent changes are managed by nodeps.utils.simple_logger.py.
|
||||||
|
DEFAULT_LOG_PATH = PATH_ADDON_ROOT / 'addon.log'
|
||||||
|
DEFAULT_LOG_PATH.touch(exist_ok=True)
|
||||||
|
|
||||||
PATH_BOOTSTRAP_LOG_LEVEL = PATH_ADDON_ROOT / '.bootstrap_log_level'
|
PATH_BOOTSTRAP_LOG_LEVEL = PATH_ADDON_ROOT / '.bootstrap_log_level'
|
||||||
with PATH_BOOTSTRAP_LOG_LEVEL.open('r') as f:
|
with PATH_BOOTSTRAP_LOG_LEVEL.open('r') as f:
|
||||||
BOOTSTRAP_LOG_LEVEL = int(f.read().strip())
|
BOOTSTRAP_LOG_LEVEL = int(f.read().strip())
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Addon Prefs Info
|
# - Addon Getters
|
||||||
####################
|
####################
|
||||||
def addon_prefs() -> bpy.types.AddonPreferences | None:
|
def addon_prefs() -> bpy.types.AddonPreferences | None:
|
||||||
if (addon := bpy.context.preferences.addons.get(ADDON_NAME)) is None:
|
if (addon := bpy.context.preferences.addons.get(ADDON_NAME)) is None:
|
||||||
|
|
|
@ -5,7 +5,10 @@ sp.printing.str.StrPrinter._default_settings['abbrev'] = True
|
||||||
## By configuring this in __init__.py, we guarantee it for all subimports.
|
## By configuring this in __init__.py, we guarantee it for all subimports.
|
||||||
## (Unless, elsewhere, this setting is changed. Be careful!)
|
## (Unless, elsewhere, this setting is changed. Be careful!)
|
||||||
|
|
||||||
from . import categories, node_tree, nodes, sockets
|
from . import sockets
|
||||||
|
from . import node_tree
|
||||||
|
from . import nodes
|
||||||
|
from . import categories
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
*sockets.BL_REGISTER,
|
*sockets.BL_REGISTER,
|
||||||
|
|
|
@ -1,29 +1,44 @@
|
||||||
"""Tools for translating between BLMaxwell sockets and pure Blender sockets.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
BL_SOCKET_3D_TYPE_PREFIXES: Blender socket prefixes which indicate that the Blender socket has three values.
|
|
||||||
BL_SOCKET_4D_TYPE_PREFIXES: Blender socket prefixes which indicate that the Blender socket has four values.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import functools
|
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
|
import sympy.physics.units as spu
|
||||||
|
import typing_extensions as typx
|
||||||
|
|
||||||
from ...utils import extra_sympy_units as spux
|
|
||||||
from ...utils import logger as _logger
|
from ...utils import logger as _logger
|
||||||
from . import contracts as ct
|
from . import contracts as ct
|
||||||
from . import sockets
|
from . import sockets as sck
|
||||||
|
from .contracts import SocketType as ST
|
||||||
|
|
||||||
log = _logger.get(__name__)
|
log = _logger.get(__name__)
|
||||||
|
|
||||||
BLSocketType: typ.TypeAlias = str ## A Blender-Defined Socket Type
|
# TODO: Caching?
|
||||||
BLSocketValue: typ.TypeAlias = typ.Any ## A Blender Socket Value
|
# TODO: Move the manual labor stuff to contracts
|
||||||
BLSocketSize: typ.TypeAlias = int
|
|
||||||
DescType: typ.TypeAlias = str
|
BLSocketType = str ## A Blender-Defined Socket Type
|
||||||
Unit: typ.TypeAlias = typ.Any ## Type of a valid unit
|
BLSocketSize = int
|
||||||
## TODO: Move this kind of thing to contracts
|
DescType = str
|
||||||
|
Unit = typ.Any ## Type of a valid unit
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Socket to SocketDef
|
||||||
|
####################
|
||||||
|
SOCKET_DEFS = {
|
||||||
|
socket_type: getattr(
|
||||||
|
sck,
|
||||||
|
socket_type.value.removesuffix('SocketType') + 'SocketDef',
|
||||||
|
)
|
||||||
|
for socket_type in ST
|
||||||
|
if hasattr(sck, socket_type.value.removesuffix('SocketType') + 'SocketDef')
|
||||||
|
}
|
||||||
|
## TODO: Bit of a hack. Is it robust enough?
|
||||||
|
|
||||||
|
for socket_type in ST:
|
||||||
|
if not hasattr(
|
||||||
|
sck,
|
||||||
|
socket_type.value.removesuffix('SocketType') + 'SocketDef',
|
||||||
|
):
|
||||||
|
log.warning('Missing SocketDef for %s', socket_type.value)
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -38,11 +53,9 @@ BL_SOCKET_4D_TYPE_PREFIXES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@functools.lru_cache(maxsize=4096)
|
def size_from_bl_interface_socket(
|
||||||
def _size_from_bl_socket(
|
bl_interface_socket: bpy.types.NodeTreeInterfaceSocket,
|
||||||
description: str,
|
) -> typx.Literal[1, 2, 3, 4]:
|
||||||
bl_socket_type: BLSocketType,
|
|
||||||
):
|
|
||||||
"""Parses the `size`, aka. number of elements, contained within the `default_value` of a Blender interface socket.
|
"""Parses the `size`, aka. number of elements, contained within the `default_value` of a Blender interface socket.
|
||||||
|
|
||||||
Since there are no 2D sockets in Blender, the user can specify "2D" in the Blender socket's description to "promise" that only the first two values will be used.
|
Since there are no 2D sockets in Blender, the user can specify "2D" in the Blender socket's description to "promise" that only the first two values will be used.
|
||||||
|
@ -52,15 +65,15 @@ def _size_from_bl_socket(
|
||||||
- For 3D sockets, a hard-coded list of Blender node socket types is used.
|
- For 3D sockets, a hard-coded list of Blender node socket types is used.
|
||||||
- Else, it is a 1D socket type.
|
- Else, it is a 1D socket type.
|
||||||
"""
|
"""
|
||||||
if description.startswith('2D'):
|
if bl_interface_socket.description.startswith('2D'):
|
||||||
return 2
|
return 2
|
||||||
if any(
|
if any(
|
||||||
bl_socket_type.startswith(bl_socket_3d_type_prefix)
|
bl_interface_socket.socket_type.startswith(bl_socket_3d_type_prefix)
|
||||||
for bl_socket_3d_type_prefix in BL_SOCKET_3D_TYPE_PREFIXES
|
for bl_socket_3d_type_prefix in BL_SOCKET_3D_TYPE_PREFIXES
|
||||||
):
|
):
|
||||||
return 3
|
return 3
|
||||||
if any(
|
if any(
|
||||||
bl_socket_type.startswith(bl_socket_4d_type_prefix)
|
bl_interface_socket.socket_type.startswith(bl_socket_4d_type_prefix)
|
||||||
for bl_socket_4d_type_prefix in BL_SOCKET_4D_TYPE_PREFIXES
|
for bl_socket_4d_type_prefix in BL_SOCKET_4D_TYPE_PREFIXES
|
||||||
):
|
):
|
||||||
return 4
|
return 4
|
||||||
|
@ -71,185 +84,176 @@ def _size_from_bl_socket(
|
||||||
####################
|
####################
|
||||||
# - BL Socket Type / Unit Parser
|
# - BL Socket Type / Unit Parser
|
||||||
####################
|
####################
|
||||||
@functools.lru_cache(maxsize=4096)
|
def parse_bl_interface_socket(
|
||||||
def _socket_type_from_bl_socket(
|
bl_interface_socket: bpy.types.NodeTreeInterfaceSocket,
|
||||||
description: str,
|
) -> tuple[ST, sp.Expr | None]:
|
||||||
bl_socket_type: BLSocketType,
|
"""Parse a Blender interface socket by parsing its description, falling back to any direct type links.
|
||||||
) -> ct.SocketType:
|
|
||||||
"""Parse a Blender socket for a matching BLMaxwell socket type, relying on both the Blender socket type and user-generated hints in the description.
|
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
description: The description from Blender socket, aka. `bl_socket.description`.
|
bl_interface_socket: An interface socket associated with the global input to a node tree.
|
||||||
bl_socket_type: The Blender socket type, aka. `bl_socket.socket_type`.
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The type of a MaxwellSimSocket that corresponds to the Blender socket.
|
The type of a corresponding MaxwellSimSocket, as well as a unit (if a particular unit was requested by the Blender interface socket).
|
||||||
"""
|
"""
|
||||||
size = _size_from_bl_socket(description, bl_socket_type)
|
size = size_from_bl_interface_socket(bl_interface_socket)
|
||||||
|
|
||||||
# Determine Socket Type Directly
|
# Determine Direct Socket Type
|
||||||
## The naive mapping from BL socket -> Maxwell socket may be good enough.
|
|
||||||
if (
|
if (
|
||||||
direct_socket_type := ct.BL_SOCKET_DIRECT_TYPE_MAP.get((bl_socket_type, size))
|
direct_socket_type := ct.BL_SOCKET_DIRECT_TYPE_MAP.get(
|
||||||
|
(bl_interface_socket.socket_type, size)
|
||||||
|
)
|
||||||
) is None:
|
) is None:
|
||||||
msg = "Blender interface socket has no mapping among 'MaxwellSimSocket's."
|
msg = "Blender interface socket has no mapping among 'MaxwellSimSocket's."
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
# (No Description) Return Direct Socket Type
|
# (Maybe) Return Direct Socket Type
|
||||||
if ct.BL_SOCKET_DESCR_ANNOT_STRING not in description:
|
## When there's no description, that's it; return.
|
||||||
return direct_socket_type
|
if not ct.BL_SOCKET_DESCR_ANNOT_STRING in bl_interface_socket.description:
|
||||||
|
return (direct_socket_type, None)
|
||||||
|
|
||||||
# Parse Description for Socket Type
|
# Parse Description for Socket Type
|
||||||
## The "2D" token is special; don't include it if it's there.
|
tokens = (
|
||||||
descr_params = description.split(ct.BL_SOCKET_DESCR_ANNOT_STRING)[0]
|
_tokens
|
||||||
directive = (
|
if (_tokens := bl_interface_socket.description.split(' '))[0] != '2D'
|
||||||
_tokens[0] if (_tokens := descr_params.split(' '))[0] != '2D' else _tokens[1]
|
else _tokens[1:]
|
||||||
)
|
) ## Don't include the "2D" token, if defined.
|
||||||
if directive == 'Preview':
|
|
||||||
return direct_socket_type ## TODO: Preview element handling
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
socket_type := ct.BL_SOCKET_DESCR_TYPE_MAP.get(
|
socket_type := ct.BL_SOCKET_DESCR_TYPE_MAP.get(
|
||||||
(directive, bl_socket_type, size)
|
(tokens[0], bl_interface_socket.socket_type, size)
|
||||||
)
|
)
|
||||||
) is None:
|
) is None:
|
||||||
msg = f'Socket description "{(directive, bl_socket_type, size)}" doesn\'t map to a socket type + unit'
|
return (
|
||||||
raise ValueError(msg)
|
direct_socket_type,
|
||||||
|
None,
|
||||||
|
) ## Description doesn't map to anything
|
||||||
|
|
||||||
return socket_type
|
# Determine Socket Unit (to use instead of "unit system")
|
||||||
|
## This is entirely OPTIONAL
|
||||||
|
socket_unit = None
|
||||||
|
if socket_type in ct.SOCKET_UNITS:
|
||||||
|
## Case: Unit is User-Defined
|
||||||
|
if len(tokens) > 1 and '(' in tokens[1] and ')' in tokens[1]:
|
||||||
|
# Compute (<unit_str>) as Unit Token
|
||||||
|
unit_token = tokens[1].removeprefix('(').removesuffix(')')
|
||||||
|
|
||||||
|
# Compare Unit Token to Valid Sympy-Printed Units
|
||||||
|
socket_unit = (
|
||||||
|
_socket_unit
|
||||||
|
if (
|
||||||
|
_socket_unit := [
|
||||||
|
unit
|
||||||
|
for unit in ct.SOCKET_UNITS[socket_type][
|
||||||
|
'values'
|
||||||
|
].values()
|
||||||
|
if str(unit) == unit_token
|
||||||
|
]
|
||||||
|
)
|
||||||
|
else ct.SOCKET_UNITS[socket_type]['values'][
|
||||||
|
ct.SOCKET_UNITS[socket_type]['default']
|
||||||
|
]
|
||||||
|
)
|
||||||
|
## TODO: Enforce abbreviated sympy printing here, not globally
|
||||||
|
|
||||||
|
return (socket_type, socket_unit)
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - BL Socket Interface Definition
|
# - BL Socket Interface Definition
|
||||||
####################
|
####################
|
||||||
@functools.lru_cache(maxsize=4096)
|
def socket_def_from_bl_interface_socket(
|
||||||
def _socket_def_from_bl_socket(
|
|
||||||
description: str,
|
|
||||||
bl_socket_type: BLSocketType,
|
|
||||||
) -> ct.SocketType:
|
|
||||||
return sockets.SOCKET_DEFS[_socket_type_from_bl_socket(description, bl_socket_type)]
|
|
||||||
|
|
||||||
|
|
||||||
def socket_def_from_bl_socket(
|
|
||||||
bl_interface_socket: bpy.types.NodeTreeInterfaceSocket,
|
bl_interface_socket: bpy.types.NodeTreeInterfaceSocket,
|
||||||
) -> ct.schemas.SocketDef:
|
):
|
||||||
"""Computes an appropriate (no-arg) SocketDef from the given `bl_interface_socket`, by parsing it."""
|
"""Computes an appropriate (no-arg) SocketDef from the given `bl_interface_socket`, by parsing it."""
|
||||||
return _socket_def_from_bl_socket(
|
return SOCKET_DEFS[parse_bl_interface_socket(bl_interface_socket)[0]]
|
||||||
bl_interface_socket.description, bl_interface_socket.bl_socket_idname
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Extract Default Interface Socket Value
|
# - Extract Default Interface Socket Value
|
||||||
####################
|
####################
|
||||||
def _read_bl_socket_default_value(
|
def value_from_bl(
|
||||||
description: str,
|
|
||||||
bl_socket_type: BLSocketType,
|
|
||||||
bl_socket_value: BLSocketValue,
|
|
||||||
unit_system: dict | None = None,
|
|
||||||
allow_unit_not_in_unit_system: bool = False,
|
|
||||||
) -> typ.Any:
|
|
||||||
# Parse the BL Socket Type and Value
|
|
||||||
## The 'lambda' delays construction until size is determined.
|
|
||||||
socket_type = _socket_type_from_bl_socket(description, bl_socket_type)
|
|
||||||
parsed_socket_value = {
|
|
||||||
1: lambda: bl_socket_value,
|
|
||||||
2: lambda: sp.Matrix(tuple(bl_socket_value)[:2]),
|
|
||||||
3: lambda: sp.Matrix(tuple(bl_socket_value)),
|
|
||||||
4: lambda: sp.Matrix(tuple(bl_socket_value)),
|
|
||||||
}[_size_from_bl_socket(description, bl_socket_type)]()
|
|
||||||
|
|
||||||
# Add Unit-System Unit to Parsed
|
|
||||||
## Use the matching socket type to lookup the unit in the unit system.
|
|
||||||
if unit_system is not None:
|
|
||||||
if (unit := unit_system.get(socket_type)) is None:
|
|
||||||
if allow_unit_not_in_unit_system:
|
|
||||||
return parsed_socket_value
|
|
||||||
|
|
||||||
msg = f'Unit system does not provide a unit for {socket_type}'
|
|
||||||
raise RuntimeError(msg)
|
|
||||||
|
|
||||||
return parsed_socket_value * unit
|
|
||||||
return parsed_socket_value
|
|
||||||
|
|
||||||
|
|
||||||
def read_bl_socket_default_value(
|
|
||||||
bl_interface_socket: bpy.types.NodeTreeInterfaceSocket,
|
bl_interface_socket: bpy.types.NodeTreeInterfaceSocket,
|
||||||
unit_system: dict | None = None,
|
unit_system: dict | None = None,
|
||||||
allow_unit_not_in_unit_system: bool = False,
|
|
||||||
) -> typ.Any:
|
) -> typ.Any:
|
||||||
"""Reads the `default_value` of a Blender socket, guaranteeing a well-formed value consistent with the passed unit system.
|
"""Reads the value of any Blender socket, and writes its `default_value` to the `value` of any `MaxwellSimSocket`.
|
||||||
|
- If the size of the Blender socket is >1, then `value` is written to as a `sympy.Matrix`.
|
||||||
Arguments:
|
- If a unit system is given, then the Blender socket is matched to a `MaxwellSimSocket`, which is used to lookup an appropriate unit in the given `unit_system`.
|
||||||
bl_interface_socket: The Blender interface socket to analyze for description, socket type, and default value.
|
|
||||||
unit_system: The mapping from BLMaxwell SocketType to corresponding unit, used to apply the appropriate unit to the output.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The parsed, well-formed version of `bl_socket.default_value`, of the appropriate form and unit.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return _read_bl_socket_default_value(
|
## TODO: Consider sympy.S()'ing the default_value
|
||||||
bl_interface_socket.description,
|
parsed_bl_socket_value = {
|
||||||
bl_interface_socket.bl_socket_idname,
|
1: lambda: bl_interface_socket.default_value,
|
||||||
bl_interface_socket.default_value,
|
2: lambda: sp.Matrix(tuple(bl_interface_socket.default_value)[:2]),
|
||||||
unit_system=unit_system,
|
3: lambda: sp.Matrix(tuple(bl_interface_socket.default_value)),
|
||||||
allow_unit_not_in_unit_system=allow_unit_not_in_unit_system,
|
4: lambda: sp.Matrix(tuple(bl_interface_socket.default_value)),
|
||||||
)
|
}[size_from_bl_interface_socket(bl_interface_socket)]()
|
||||||
|
## The 'lambda' delays construction until size is determined
|
||||||
|
|
||||||
|
socket_type, unit = parse_bl_interface_socket(bl_interface_socket)
|
||||||
|
|
||||||
|
# Add Unit to Parsed (if relevant)
|
||||||
|
if unit is not None:
|
||||||
|
parsed_bl_socket_value *= unit
|
||||||
|
elif unit_system is not None:
|
||||||
|
parsed_bl_socket_value *= unit_system[socket_type]
|
||||||
|
|
||||||
|
return parsed_bl_socket_value
|
||||||
|
|
||||||
|
|
||||||
def _writable_bl_socket_value(
|
####################
|
||||||
description: str,
|
# - Convert to Blender-Compatible Value
|
||||||
bl_socket_type: BLSocketType,
|
####################
|
||||||
value: typ.Any,
|
def make_scalar_bl_compat(scalar: typ.Any) -> typ.Any:
|
||||||
unit_system: dict | None = None,
|
"""Blender doesn't accept ex. Sympy numbers as values.
|
||||||
allow_unit_not_in_unit_system: bool = False,
|
Therefore, we need to do some conforming.
|
||||||
) -> typ.Any:
|
|
||||||
socket_type = _socket_type_from_bl_socket(description, bl_socket_type)
|
|
||||||
|
|
||||||
# Retrieve Unit-System Unit
|
Currently hard-coded; this is probably best.
|
||||||
if unit_system is not None:
|
|
||||||
if (unit := unit_system.get(socket_type)) is None:
|
|
||||||
if allow_unit_not_in_unit_system:
|
|
||||||
_bl_socket_value = value
|
|
||||||
else:
|
|
||||||
msg = f'Unit system does not provide a unit for {socket_type}'
|
|
||||||
raise RuntimeError(msg)
|
|
||||||
else:
|
|
||||||
_bl_socket_value = spux.scale_to_unit(value, unit)
|
|
||||||
else:
|
|
||||||
_bl_socket_value = value
|
|
||||||
|
|
||||||
# Compute Blender Socket Value
|
|
||||||
if isinstance(_bl_socket_value, sp.Basic):
|
|
||||||
bl_socket_value = spux.sympy_to_python(_bl_socket_value)
|
|
||||||
else:
|
|
||||||
bl_socket_value = _bl_socket_value
|
|
||||||
|
|
||||||
if _size_from_bl_socket(description, bl_socket_type) == 2: # noqa: PLR2004
|
|
||||||
bl_socket_value = bl_socket_value[:2]
|
|
||||||
return bl_socket_value
|
|
||||||
|
|
||||||
|
|
||||||
def writable_bl_socket_value(
|
|
||||||
bl_interface_socket: bpy.types.NodeTreeInterfaceSocket,
|
|
||||||
value: typ.Any,
|
|
||||||
unit_system: dict | None = None,
|
|
||||||
allow_unit_not_in_unit_system: bool = False,
|
|
||||||
) -> typ.Any:
|
|
||||||
"""Processes a value to be ready-to-write to a Blender socket.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
bl_interface_socket: The Blender interface socket to analyze
|
|
||||||
value: The value to prepare for writing to the given Blender socket.
|
|
||||||
unit_system: The mapping from BLMaxwell SocketType to corresponding unit, used to scale the value to the the appropriate unit.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A value corresponding to the input, which is guaranteed to be compatible with the Blender socket (incl. via a GeoNodes modifier), as well as correctly scaled with respect to the given unit system.
|
|
||||||
"""
|
"""
|
||||||
return _writable_bl_socket_value(
|
if isinstance(scalar, sp.Integer):
|
||||||
bl_interface_socket.description,
|
return int(scalar)
|
||||||
bl_interface_socket.bl_socket_idname,
|
elif isinstance(scalar, sp.Float):
|
||||||
value,
|
return float(scalar)
|
||||||
unit_system=unit_system,
|
elif isinstance(scalar, sp.Rational):
|
||||||
allow_unit_not_in_unit_system=allow_unit_not_in_unit_system,
|
return float(scalar)
|
||||||
|
elif isinstance(scalar, sp.Expr):
|
||||||
|
return float(scalar.n())
|
||||||
|
## TODO: More?
|
||||||
|
|
||||||
|
return scalar
|
||||||
|
|
||||||
|
|
||||||
|
def value_to_bl(
|
||||||
|
bl_interface_socket: bpy.types.NodeSocket,
|
||||||
|
value: typ.Any,
|
||||||
|
unit_system: dict | None = None,
|
||||||
|
) -> typ.Any:
|
||||||
|
socket_type, unit = parse_bl_interface_socket(bl_interface_socket)
|
||||||
|
|
||||||
|
# Set Socket
|
||||||
|
if unit is not None:
|
||||||
|
bl_socket_value = spu.convert_to(value, unit) / unit
|
||||||
|
elif unit_system is not None and socket_type in unit_system:
|
||||||
|
bl_socket_value = (
|
||||||
|
spu.convert_to(value, unit_system[socket_type])
|
||||||
|
/ unit_system[socket_type]
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
bl_socket_value = value
|
||||||
|
|
||||||
|
return {
|
||||||
|
1: lambda: make_scalar_bl_compat(bl_socket_value),
|
||||||
|
2: lambda: tuple(
|
||||||
|
[
|
||||||
|
make_scalar_bl_compat(bl_socket_value[0]),
|
||||||
|
make_scalar_bl_compat(bl_socket_value[1]),
|
||||||
|
bl_interface_socket.default_value[2],
|
||||||
|
## Don't touch (unused) 3rd bl_socket coordinate
|
||||||
|
]
|
||||||
|
),
|
||||||
|
3: lambda: tuple(
|
||||||
|
[make_scalar_bl_compat(el) for el in bl_socket_value]
|
||||||
|
),
|
||||||
|
4: lambda: tuple(
|
||||||
|
[make_scalar_bl_compat(el) for el in bl_socket_value]
|
||||||
|
),
|
||||||
|
}[size_from_bl_interface_socket(bl_interface_socket)]()
|
||||||
|
## The 'lambda' delays construction until size is determined
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import nodeitems_utils
|
import nodeitems_utils
|
||||||
|
|
||||||
from . import contracts as ct
|
from . import contracts as ct
|
||||||
from .nodes import BL_NODES
|
from .nodes import BL_NODES
|
||||||
|
|
||||||
|
@ -82,7 +81,9 @@ BL_NODE_CATEGORIES = mk_node_categories(
|
||||||
syllable_prefix=['MAXWELLSIM'],
|
syllable_prefix=['MAXWELLSIM'],
|
||||||
)
|
)
|
||||||
## TODO: refactor, this has a big code smell
|
## TODO: refactor, this has a big code smell
|
||||||
BL_REGISTER = [*DYNAMIC_SUBMENU_REGISTRATIONS] ## Must be run after, right now.
|
BL_REGISTER = [
|
||||||
|
*DYNAMIC_SUBMENU_REGISTRATIONS
|
||||||
|
] ## Must be run after, right now.
|
||||||
|
|
||||||
|
|
||||||
## TEST - TODO this is a big code smell
|
## TEST - TODO this is a big code smell
|
||||||
|
|
|
@ -30,8 +30,6 @@ from .socket_units import SOCKET_UNITS
|
||||||
from .socket_colors import SOCKET_COLORS
|
from .socket_colors import SOCKET_COLORS
|
||||||
from .socket_shapes import SOCKET_SHAPES
|
from .socket_shapes import SOCKET_SHAPES
|
||||||
|
|
||||||
from .unit_systems import UNITS_BLENDER, UNITS_TIDY3D
|
|
||||||
|
|
||||||
from .socket_from_bl_desc import BL_SOCKET_DESCR_TYPE_MAP
|
from .socket_from_bl_desc import BL_SOCKET_DESCR_TYPE_MAP
|
||||||
from .socket_from_bl_direct import BL_SOCKET_DIRECT_TYPE_MAP
|
from .socket_from_bl_direct import BL_SOCKET_DIRECT_TYPE_MAP
|
||||||
|
|
||||||
|
@ -75,8 +73,6 @@ __all__ = [
|
||||||
'SOCKET_UNITS',
|
'SOCKET_UNITS',
|
||||||
'SOCKET_COLORS',
|
'SOCKET_COLORS',
|
||||||
'SOCKET_SHAPES',
|
'SOCKET_SHAPES',
|
||||||
'UNITS_BLENDER',
|
|
||||||
'UNITS_TIDY3D',
|
|
||||||
'BL_SOCKET_DESCR_TYPE_MAP',
|
'BL_SOCKET_DESCR_TYPE_MAP',
|
||||||
'BL_SOCKET_DIRECT_TYPE_MAP',
|
'BL_SOCKET_DIRECT_TYPE_MAP',
|
||||||
'BL_SOCKET_DESCR_ANNOT_STRING',
|
'BL_SOCKET_DESCR_ANNOT_STRING',
|
||||||
|
|
|
@ -4,10 +4,5 @@ from ....utils.blender_type_enum import BlenderTypeEnum
|
||||||
|
|
||||||
|
|
||||||
class ManagedObjType(BlenderTypeEnum):
|
class ManagedObjType(BlenderTypeEnum):
|
||||||
|
ManagedBLObject = enum.auto()
|
||||||
ManagedBLImage = enum.auto()
|
ManagedBLImage = enum.auto()
|
||||||
|
|
||||||
ManagedBLCollection = enum.auto()
|
|
||||||
ManagedBLEmpty = enum.auto()
|
|
||||||
ManagedBLMesh = enum.auto()
|
|
||||||
ManagedBLVolume = enum.auto()
|
|
||||||
ManagedBLModifier = enum.auto()
|
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import sympy.physics.units as spu
|
||||||
|
from ....utils import extra_sympy_units as spuex
|
||||||
|
|
||||||
from .socket_types import SocketType as ST
|
from .socket_types import SocketType as ST
|
||||||
|
|
||||||
## TODO: Don't just presume sRGB.
|
## TODO: Don't just presume sRGB.
|
||||||
|
@ -39,7 +42,6 @@ SOCKET_COLORS = {
|
||||||
ST.PhysicalPol: (0.5, 0.4, 0.2, 1.0), # 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
|
ST.PhysicalFreq: (1.0, 0.7, 0.5, 1.0), # Light Peach
|
||||||
# Blender
|
# Blender
|
||||||
ST.BlenderMaterial: (0.8, 0.6, 1.0, 1.0), # Lighter Purple
|
|
||||||
ST.BlenderObject: (0.7, 0.5, 1.0, 1.0), # Light Purple
|
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.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.BlenderImage: (0.5, 0.4, 0.8, 1.0), # Medium Purple
|
||||||
|
|
|
@ -1,15 +1,11 @@
|
||||||
from .socket_types import SocketType as ST
|
from .socket_types import SocketType as ST
|
||||||
|
|
||||||
BL_SOCKET_DIRECT_TYPE_MAP = {
|
BL_SOCKET_DIRECT_TYPE_MAP = {
|
||||||
# Blender
|
('NodeSocketString', 1): ST.String,
|
||||||
|
('NodeSocketBool', 1): ST.Bool,
|
||||||
('NodeSocketCollection', 1): ST.BlenderCollection,
|
('NodeSocketCollection', 1): ST.BlenderCollection,
|
||||||
('NodeSocketImage', 1): ST.BlenderImage,
|
('NodeSocketImage', 1): ST.BlenderImage,
|
||||||
('NodeSocketObject', 1): ST.BlenderObject,
|
('NodeSocketObject', 1): ST.BlenderObject,
|
||||||
('NodeSocketMaterial', 1): ST.BlenderMaterial,
|
|
||||||
# Basic
|
|
||||||
('NodeSocketString', 1): ST.String,
|
|
||||||
('NodeSocketBool', 1): ST.Bool,
|
|
||||||
# Float
|
|
||||||
('NodeSocketFloat', 1): ST.RealNumber,
|
('NodeSocketFloat', 1): ST.RealNumber,
|
||||||
# ("NodeSocketFloatAngle", 1): ST.PhysicalAngle,
|
# ("NodeSocketFloatAngle", 1): ST.PhysicalAngle,
|
||||||
# ("NodeSocketFloatDistance", 1): ST.PhysicalLength,
|
# ("NodeSocketFloatDistance", 1): ST.PhysicalLength,
|
||||||
|
@ -17,14 +13,12 @@ BL_SOCKET_DIRECT_TYPE_MAP = {
|
||||||
('NodeSocketFloatPercentage', 1): ST.RealNumber,
|
('NodeSocketFloatPercentage', 1): ST.RealNumber,
|
||||||
# ("NodeSocketFloatTime", 1): ST.PhysicalTime,
|
# ("NodeSocketFloatTime", 1): ST.PhysicalTime,
|
||||||
# ("NodeSocketFloatTimeAbsolute", 1): ST.PhysicalTime,
|
# ("NodeSocketFloatTimeAbsolute", 1): ST.PhysicalTime,
|
||||||
# Int
|
|
||||||
('NodeSocketInt', 1): ST.IntegerNumber,
|
('NodeSocketInt', 1): ST.IntegerNumber,
|
||||||
('NodeSocketIntFactor', 1): ST.IntegerNumber,
|
('NodeSocketIntFactor', 1): ST.IntegerNumber,
|
||||||
('NodeSocketIntPercentage', 1): ST.IntegerNumber,
|
('NodeSocketIntPercentage', 1): ST.IntegerNumber,
|
||||||
('NodeSocketIntUnsigned', 1): ST.IntegerNumber,
|
('NodeSocketIntUnsigned', 1): ST.IntegerNumber,
|
||||||
# Array-Like
|
|
||||||
('NodeSocketColor', 3): ST.Color,
|
|
||||||
('NodeSocketRotation', 2): ST.PhysicalRot2D,
|
('NodeSocketRotation', 2): ST.PhysicalRot2D,
|
||||||
|
('NodeSocketColor', 3): ST.Color,
|
||||||
('NodeSocketVector', 2): ST.Real2DVector,
|
('NodeSocketVector', 2): ST.Real2DVector,
|
||||||
('NodeSocketVector', 3): ST.Real3DVector,
|
('NodeSocketVector', 3): ST.Real3DVector,
|
||||||
# ("NodeSocketVectorAcceleration", 2): ST.PhysicalAccel2D,
|
# ("NodeSocketVectorAcceleration", 2): ST.PhysicalAccel2D,
|
||||||
|
|
|
@ -38,7 +38,6 @@ SOCKET_SHAPES = {
|
||||||
ST.PhysicalPol: 'CIRCLE',
|
ST.PhysicalPol: 'CIRCLE',
|
||||||
ST.PhysicalFreq: 'CIRCLE',
|
ST.PhysicalFreq: 'CIRCLE',
|
||||||
# Blender
|
# Blender
|
||||||
ST.BlenderMaterial: 'DIAMOND',
|
|
||||||
ST.BlenderObject: 'DIAMOND',
|
ST.BlenderObject: 'DIAMOND',
|
||||||
ST.BlenderCollection: 'DIAMOND',
|
ST.BlenderCollection: 'DIAMOND',
|
||||||
ST.BlenderImage: 'DIAMOND',
|
ST.BlenderImage: 'DIAMOND',
|
||||||
|
|
|
@ -3,6 +3,7 @@ import enum
|
||||||
from ....utils.blender_type_enum import (
|
from ....utils.blender_type_enum import (
|
||||||
BlenderTypeEnum,
|
BlenderTypeEnum,
|
||||||
append_cls_name_to_values,
|
append_cls_name_to_values,
|
||||||
|
wrap_values_in_MT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,7 +34,6 @@ class SocketType(BlenderTypeEnum):
|
||||||
Complex3DVector = enum.auto()
|
Complex3DVector = enum.auto()
|
||||||
|
|
||||||
# Blender
|
# Blender
|
||||||
BlenderMaterial = enum.auto()
|
|
||||||
BlenderObject = enum.auto()
|
BlenderObject = enum.auto()
|
||||||
BlenderCollection = enum.auto()
|
BlenderCollection = enum.auto()
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import sympy.physics.units as spu
|
import sympy.physics.units as spu
|
||||||
|
|
||||||
from ....utils import extra_sympy_units as spux
|
from ....utils import extra_sympy_units as spux
|
||||||
from .socket_types import SocketType as ST # noqa: N817
|
|
||||||
|
from .socket_types import SocketType as ST
|
||||||
|
|
||||||
SOCKET_UNITS = {
|
SOCKET_UNITS = {
|
||||||
ST.PhysicalTime: {
|
ST.PhysicalTime: {
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
import typing as typ
|
|
||||||
|
|
||||||
import sympy.physics.units as spu
|
|
||||||
|
|
||||||
from ....utils import extra_sympy_units as spux
|
|
||||||
from ....utils.pydantic_sympy import SympyExpr
|
|
||||||
from .socket_types import SocketType as ST # noqa: N817
|
|
||||||
from .socket_units import SOCKET_UNITS
|
|
||||||
|
|
||||||
|
|
||||||
def _socket_units(socket_type):
|
|
||||||
return SOCKET_UNITS[socket_type]['values']
|
|
||||||
|
|
||||||
|
|
||||||
UnitSystem: typ.TypeAlias = dict[ST, SympyExpr]
|
|
||||||
####################
|
|
||||||
# - Unit Systems
|
|
||||||
####################
|
|
||||||
UNITS_BLENDER: UnitSystem = {
|
|
||||||
ST.PhysicalTime: spu.picosecond,
|
|
||||||
ST.PhysicalAngle: spu.radian,
|
|
||||||
ST.PhysicalLength: spu.micrometer,
|
|
||||||
ST.PhysicalArea: spu.micrometer**2,
|
|
||||||
ST.PhysicalVolume: spu.micrometer**3,
|
|
||||||
ST.PhysicalPoint2D: spu.micrometer,
|
|
||||||
ST.PhysicalPoint3D: spu.micrometer,
|
|
||||||
ST.PhysicalSize2D: spu.micrometer,
|
|
||||||
ST.PhysicalSize3D: spu.micrometer,
|
|
||||||
ST.PhysicalMass: spu.microgram,
|
|
||||||
ST.PhysicalSpeed: spu.um / spu.second,
|
|
||||||
ST.PhysicalAccelScalar: spu.um / spu.second**2,
|
|
||||||
ST.PhysicalForceScalar: spux.micronewton,
|
|
||||||
ST.PhysicalAccel3D: spu.um / spu.second**2,
|
|
||||||
ST.PhysicalForce3D: spux.micronewton,
|
|
||||||
ST.PhysicalFreq: spux.terahertz,
|
|
||||||
ST.PhysicalPol: spu.radian,
|
|
||||||
} ## TODO: Load (dynamically?) from addon preferences
|
|
||||||
|
|
||||||
UNITS_TIDY3D: UnitSystem = {
|
|
||||||
## https://docs.flexcompute.com/projects/tidy3d/en/latest/faq/docs/faq/What-are-the-units-used-in-the-simulation.html
|
|
||||||
ST.PhysicalTime: spu.second,
|
|
||||||
ST.PhysicalAngle: spu.radian,
|
|
||||||
ST.PhysicalLength: spu.micrometer,
|
|
||||||
ST.PhysicalArea: spu.micrometer**2,
|
|
||||||
ST.PhysicalVolume: spu.micrometer**3,
|
|
||||||
ST.PhysicalPoint2D: spu.micrometer,
|
|
||||||
ST.PhysicalPoint3D: spu.micrometer,
|
|
||||||
ST.PhysicalSize2D: spu.micrometer,
|
|
||||||
ST.PhysicalSize3D: spu.micrometer,
|
|
||||||
ST.PhysicalMass: spu.microgram,
|
|
||||||
ST.PhysicalSpeed: spu.um / spu.second,
|
|
||||||
ST.PhysicalAccelScalar: spu.um / spu.second**2,
|
|
||||||
ST.PhysicalForceScalar: spux.micronewton,
|
|
||||||
ST.PhysicalAccel3D: spu.um / spu.second**2,
|
|
||||||
ST.PhysicalForce3D: spux.micronewton,
|
|
||||||
ST.PhysicalFreq: spu.hertz,
|
|
||||||
ST.PhysicalPol: spu.radian,
|
|
||||||
}
|
|
|
@ -1,19 +1,2 @@
|
||||||
#from .managed_bl_empty import ManagedBLEmpty
|
|
||||||
from .managed_bl_image import ManagedBLImage
|
from .managed_bl_image import ManagedBLImage
|
||||||
|
from .managed_bl_object import ManagedBLObject
|
||||||
# from .managed_bl_collection import ManagedBLCollection
|
|
||||||
# from .managed_bl_object import ManagedBLObject
|
|
||||||
from .managed_bl_mesh import ManagedBLMesh
|
|
||||||
|
|
||||||
# from .managed_bl_volume import ManagedBLVolume
|
|
||||||
from .managed_bl_modifier import ManagedBLModifier
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
#'ManagedBLEmpty',
|
|
||||||
'ManagedBLImage',
|
|
||||||
#'ManagedBLCollection',
|
|
||||||
#'ManagedBLObject',
|
|
||||||
'ManagedBLMesh',
|
|
||||||
#'ManagedBLVolume',
|
|
||||||
'ManagedBLModifier',
|
|
||||||
]
|
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
import functools
|
|
||||||
|
|
||||||
import bpy
|
|
||||||
|
|
||||||
from ....utils import logger
|
|
||||||
|
|
||||||
log = logger.get(__name__)
|
|
||||||
|
|
||||||
MANAGED_COLLECTION_NAME = 'BLMaxwell'
|
|
||||||
PREVIEW_COLLECTION_NAME = 'BLMaxwell Visible'
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Global Collection Handling
|
|
||||||
####################
|
|
||||||
@functools.cache
|
|
||||||
def collection(collection_name: str, view_layer_exclude: bool) -> bpy.types.Collection:
|
|
||||||
# Init the "Managed Collection"
|
|
||||||
# Ensure Collection exists (and is in the Scene collection)
|
|
||||||
if collection_name not in bpy.data.collections:
|
|
||||||
collection = bpy.data.collections.new(collection_name)
|
|
||||||
bpy.context.scene.collection.children.link(collection)
|
|
||||||
else:
|
|
||||||
collection = bpy.data.collections[collection_name]
|
|
||||||
|
|
||||||
## Ensure synced View Layer exclusion
|
|
||||||
if (
|
|
||||||
layer_collection := bpy.context.view_layer.layer_collection.children[
|
|
||||||
collection_name
|
|
||||||
]
|
|
||||||
).exclude != view_layer_exclude:
|
|
||||||
layer_collection.exclude = view_layer_exclude
|
|
||||||
|
|
||||||
return collection
|
|
||||||
|
|
||||||
|
|
||||||
def managed_collection() -> bpy.types.Collection:
|
|
||||||
return collection(MANAGED_COLLECTION_NAME, view_layer_exclude=True)
|
|
||||||
|
|
||||||
|
|
||||||
def preview_collection() -> bpy.types.Collection:
|
|
||||||
return collection(PREVIEW_COLLECTION_NAME, view_layer_exclude=False)
|
|
|
@ -1,10 +1,12 @@
|
||||||
import io
|
|
||||||
import typing as typ
|
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
|
import bpy
|
||||||
import matplotlib.axis as mpl_ax
|
|
||||||
import numpy as np
|
|
||||||
import typing_extensions as typx
|
|
||||||
|
|
||||||
from .. import contracts as ct
|
from .. import contracts as ct
|
||||||
|
|
||||||
|
@ -107,7 +109,9 @@ class ManagedBLImage(ct.schemas.ManagedObj):
|
||||||
"""
|
"""
|
||||||
if preview_area := self.preview_area:
|
if preview_area := self.preview_area:
|
||||||
return next(
|
return next(
|
||||||
space for space in preview_area.spaces if space.type == SPACE_TYPE
|
space
|
||||||
|
for space in preview_area.spaces
|
||||||
|
if space.type == SPACE_TYPE
|
||||||
)
|
)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -157,7 +161,7 @@ class ManagedBLImage(ct.schemas.ManagedObj):
|
||||||
height_px = int(_height_inches * _dpi)
|
height_px = int(_height_inches * _dpi)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
msg = 'There must either be a preview area, or defined `width_inches`, `height_inches`, and `dpi`'
|
msg = f'There must either be a preview area, or defined `width_inches`, `height_inches`, and `dpi`'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
# Compute Plot Dimensions
|
# Compute Plot Dimensions
|
||||||
|
|
|
@ -1,237 +0,0 @@
|
||||||
import contextlib
|
|
||||||
|
|
||||||
import bmesh
|
|
||||||
import bpy
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from ....utils import logger
|
|
||||||
from .. import contracts as ct
|
|
||||||
from .managed_bl_collection import managed_collection, preview_collection
|
|
||||||
|
|
||||||
log = logger.get(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - BLMesh
|
|
||||||
####################
|
|
||||||
class ManagedBLMesh(ct.schemas.ManagedObj):
|
|
||||||
managed_obj_type = ct.ManagedObjType.ManagedBLMesh
|
|
||||||
_bl_object_name: str | None = None
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - BL Object Name
|
|
||||||
####################
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
return self._bl_object_name
|
|
||||||
|
|
||||||
@name.setter
|
|
||||||
def name(self, value: str) -> None:
|
|
||||||
log.info(
|
|
||||||
'Changing BLMesh w/Name "%s" to Name "%s"', self._bl_object_name, value
|
|
||||||
)
|
|
||||||
|
|
||||||
if (bl_object := bpy.data.objects.get(value)) is None:
|
|
||||||
log.info(
|
|
||||||
'Desired BLMesh Name "%s" Not Taken',
|
|
||||||
value,
|
|
||||||
)
|
|
||||||
|
|
||||||
if self._bl_object_name is None:
|
|
||||||
log.info(
|
|
||||||
'Set New BLMesh Name to "%s"',
|
|
||||||
value,
|
|
||||||
)
|
|
||||||
elif (bl_object := bpy.data.objects.get(self._bl_object_name)) is not None:
|
|
||||||
log.info(
|
|
||||||
'Changed BLMesh Name to "%s"',
|
|
||||||
value,
|
|
||||||
)
|
|
||||||
bl_object.name = value
|
|
||||||
else:
|
|
||||||
msg = f'ManagedBLMesh with name "{self._bl_object_name}" was deleted'
|
|
||||||
raise RuntimeError(msg)
|
|
||||||
|
|
||||||
# Set Internal Name
|
|
||||||
self._bl_object_name = value
|
|
||||||
else:
|
|
||||||
log.info(
|
|
||||||
'Desired BLMesh Name "%s" is Taken. Using Blender Rename',
|
|
||||||
value,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set Name Anyway, but Respect Blender's Renaming
|
|
||||||
## When a name already exists, Blender adds .### to prevent overlap.
|
|
||||||
## `set_name` is allowed to change the name; nodes account for this.
|
|
||||||
bl_object.name = value
|
|
||||||
self._bl_object_name = bl_object.name
|
|
||||||
|
|
||||||
log.info(
|
|
||||||
'Changed BLMesh Name to "%s"',
|
|
||||||
bl_object.name,
|
|
||||||
)
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Allocation
|
|
||||||
####################
|
|
||||||
def __init__(self, name: str):
|
|
||||||
self.name = name
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Deallocation
|
|
||||||
####################
|
|
||||||
def free(self):
|
|
||||||
if (bl_object := bpy.data.objects.get(self.name)) is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Delete the Underlying Datablock
|
|
||||||
## This automatically deletes the object too
|
|
||||||
log.info('Removing "%s" BLMesh', bl_object.type)
|
|
||||||
bpy.data.meshes.remove(bl_object.data)
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Actions
|
|
||||||
####################
|
|
||||||
def show_preview(self) -> None:
|
|
||||||
"""Moves the managed Blender object to the preview collection.
|
|
||||||
|
|
||||||
If it's already included, do nothing.
|
|
||||||
"""
|
|
||||||
if (bl_object := bpy.data.objects.get(self.name)) is not None:
|
|
||||||
if bl_object.name not in preview_collection().objects:
|
|
||||||
log.info('Moving "%s" to Preview Collection', bl_object.name)
|
|
||||||
preview_collection().objects.link(bl_object)
|
|
||||||
else:
|
|
||||||
msg = 'Managed BLMesh does not exist'
|
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
def hide_preview(self) -> None:
|
|
||||||
"""Removes the managed Blender object from the preview collection.
|
|
||||||
|
|
||||||
If it's already removed, do nothing.
|
|
||||||
"""
|
|
||||||
if (bl_object := bpy.data.objects.get(self.name)) is not None:
|
|
||||||
if bl_object.name in preview_collection().objects:
|
|
||||||
log.info('Removing "%s" from Preview Collection', bl_object.name)
|
|
||||||
preview_collection().objects.unlink(bl_object)
|
|
||||||
else:
|
|
||||||
msg = 'Managed BLMesh does not exist'
|
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
def bl_select(self) -> None:
|
|
||||||
"""Selects the managed Blender object, causing it to be ex. outlined in the 3D viewport."""
|
|
||||||
if (bl_object := bpy.data.objects.get(self.name)) is not None:
|
|
||||||
bpy.ops.object.select_all(action='DESELECT')
|
|
||||||
bl_object.select_set(True)
|
|
||||||
|
|
||||||
msg = 'Managed BLMesh does not exist'
|
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - BLMesh Management
|
|
||||||
####################
|
|
||||||
def bl_object(self, location: tuple[float, float, float] = (0, 0, 0)):
|
|
||||||
"""Returns the managed blender object."""
|
|
||||||
# Create Object w/Appropriate Data Block
|
|
||||||
if not (bl_object := bpy.data.objects.get(self.name)):
|
|
||||||
log.info(
|
|
||||||
'Creating BLMesh Object "%s"',
|
|
||||||
self.name,
|
|
||||||
)
|
|
||||||
bl_data = bpy.data.meshes.new(self.name)
|
|
||||||
bl_object = bpy.data.objects.new(self.name, bl_data)
|
|
||||||
log.debug(
|
|
||||||
'Linking "%s" to Base Collection',
|
|
||||||
bl_object.name,
|
|
||||||
)
|
|
||||||
managed_collection().objects.link(bl_object)
|
|
||||||
|
|
||||||
for i, coord in enumerate(location):
|
|
||||||
if bl_object.location[i] != coord:
|
|
||||||
bl_object.location[i] = coord
|
|
||||||
|
|
||||||
return bl_object
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Mesh Data Properties
|
|
||||||
####################
|
|
||||||
@property
|
|
||||||
def mesh_data(self) -> bpy.types.Mesh:
|
|
||||||
"""Directly loads the Blender mesh data.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: If the object has no mesh data.
|
|
||||||
"""
|
|
||||||
if bl_object := bpy.data.objects.get(self.name):
|
|
||||||
return bl_object.data
|
|
||||||
|
|
||||||
msg = f'Requested mesh data from {self.name} of type {bl_object.type}'
|
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def mesh_as_bmesh(
|
|
||||||
self,
|
|
||||||
evaluate: bool = True,
|
|
||||||
triangulate: bool = False,
|
|
||||||
) -> bpy.types.Mesh:
|
|
||||||
if (bl_object := bpy.data.objects.get(self.name)) and bl_object.type == 'MESH':
|
|
||||||
bmesh_mesh = None
|
|
||||||
try:
|
|
||||||
bmesh_mesh = bmesh.new()
|
|
||||||
if evaluate:
|
|
||||||
bmesh_mesh.from_object(
|
|
||||||
bl_object,
|
|
||||||
bpy.context.evaluated_depsgraph_get(),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
bmesh_mesh.from_object(bl_object)
|
|
||||||
|
|
||||||
if triangulate:
|
|
||||||
bmesh.ops.triangulate(bmesh_mesh, faces=bmesh_mesh.faces)
|
|
||||||
|
|
||||||
yield bmesh_mesh
|
|
||||||
|
|
||||||
finally:
|
|
||||||
if bmesh_mesh:
|
|
||||||
bmesh_mesh.free()
|
|
||||||
|
|
||||||
else:
|
|
||||||
msg = f'Requested BMesh from "{self.name}" of type "{bl_object.type}"'
|
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def mesh_as_arrays(self) -> dict:
|
|
||||||
## TODO: Cached
|
|
||||||
|
|
||||||
# Ensure Updated Geometry
|
|
||||||
log.debug('Updating View Layer')
|
|
||||||
bpy.context.view_layer.update()
|
|
||||||
|
|
||||||
# Compute Evaluted + Triangulated Mesh
|
|
||||||
log.debug('Casting BMesh of "%s" to Temporary Mesh', self.name)
|
|
||||||
_mesh = bpy.data.meshes.new(name='TemporaryMesh')
|
|
||||||
with self.mesh_as_bmesh(evaluate=True, triangulate=True) as bmesh_mesh:
|
|
||||||
bmesh_mesh.to_mesh(_mesh)
|
|
||||||
|
|
||||||
# Optimized Vertex Copy
|
|
||||||
## See <https://blog.michelanders.nl/2016/02/copying-vertices-to-numpy-arrays-in_4.html>
|
|
||||||
log.debug('Copying Vertices from "%s"', self.name)
|
|
||||||
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**.
|
|
||||||
log.debug('Copying Faces from "%s"', self.name)
|
|
||||||
faces = np.zeros(3 * len(_mesh.polygons), dtype=np.uint64)
|
|
||||||
_mesh.polygons.foreach_get('vertices', faces)
|
|
||||||
faces.shape = (-1, 3)
|
|
||||||
|
|
||||||
# Remove Temporary Mesh
|
|
||||||
log.debug('Removing Temporary Mesh')
|
|
||||||
bpy.data.meshes.remove(_mesh)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'verts': verts,
|
|
||||||
'faces': faces,
|
|
||||||
}
|
|
|
@ -1,212 +0,0 @@
|
||||||
import typing as typ
|
|
||||||
|
|
||||||
import bpy
|
|
||||||
import typing_extensions as typx
|
|
||||||
|
|
||||||
from ....utils import analyze_geonodes, logger
|
|
||||||
from .. import bl_socket_map
|
|
||||||
from .. import contracts as ct
|
|
||||||
|
|
||||||
log = logger.get(__name__)
|
|
||||||
|
|
||||||
ModifierType: typ.TypeAlias = typx.Literal['NODES', 'ARRAY']
|
|
||||||
|
|
||||||
|
|
||||||
NodeTreeInterfaceID: typ.TypeAlias = str
|
|
||||||
|
|
||||||
|
|
||||||
class ModifierAttrsNODES(typ.TypedDict):
|
|
||||||
node_group: bpy.types.GeometryNodeTree
|
|
||||||
unit_system: bpy.types.GeometryNodeTree
|
|
||||||
inputs: dict[NodeTreeInterfaceID, typ.Any]
|
|
||||||
|
|
||||||
|
|
||||||
class ModifierAttrsARRAY(typ.TypedDict):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
ModifierAttrs: typ.TypeAlias = ModifierAttrsNODES | ModifierAttrsARRAY
|
|
||||||
MODIFIER_NAMES = {
|
|
||||||
'NODES': 'BLMaxwell_GeoNodes',
|
|
||||||
'ARRAY': 'BLMaxwell_Array',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Read Modifier Information
|
|
||||||
####################
|
|
||||||
def read_modifier(bl_modifier: bpy.types.Modifier) -> ModifierAttrs:
|
|
||||||
if bl_modifier.type == 'NODES':
|
|
||||||
return {
|
|
||||||
'node_group': bl_modifier.node_group,
|
|
||||||
}
|
|
||||||
elif bl_modifier.type == 'ARRAY':
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Write Modifier Information
|
|
||||||
####################
|
|
||||||
def write_modifier_geonodes(
|
|
||||||
bl_modifier: bpy.types.Modifier,
|
|
||||||
modifier_attrs: ModifierAttrsNODES,
|
|
||||||
) -> bool:
|
|
||||||
modifier_altered = False
|
|
||||||
# Alter GeoNodes Group
|
|
||||||
if bl_modifier.node_group != modifier_attrs['node_group']:
|
|
||||||
log.info(
|
|
||||||
'Changing GeoNodes Modifier NodeTree from "%s" to "%s"',
|
|
||||||
str(bl_modifier.node_group),
|
|
||||||
str(modifier_attrs['node_group']),
|
|
||||||
)
|
|
||||||
bl_modifier.node_group = modifier_attrs['node_group']
|
|
||||||
modifier_altered = True
|
|
||||||
|
|
||||||
# Alter GeoNodes Modifier Inputs
|
|
||||||
## First we retrieve the interface items by-Socket Name
|
|
||||||
geonodes_interface = analyze_geonodes.interface(
|
|
||||||
bl_modifier.node_group, direc='INPUT'
|
|
||||||
)
|
|
||||||
for (
|
|
||||||
socket_name,
|
|
||||||
value,
|
|
||||||
) in modifier_attrs['inputs'].items():
|
|
||||||
# Compute Writable BL Socket Value
|
|
||||||
## Analyzes the socket and unitsys to prep a ready-to-write value.
|
|
||||||
## Write directly to the modifier dict.
|
|
||||||
bl_socket_value = bl_socket_map.writable_bl_socket_value(
|
|
||||||
geonodes_interface[socket_name],
|
|
||||||
value,
|
|
||||||
unit_system=modifier_attrs['unit_system'],
|
|
||||||
allow_unit_not_in_unit_system=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Compute Interface ID from Socket Name
|
|
||||||
## We can't index the modifier by socket name; only by Interface ID.
|
|
||||||
## Still, we require that socket names are unique.
|
|
||||||
iface_id = geonodes_interface[socket_name].identifier
|
|
||||||
|
|
||||||
# IF List-Like: Alter Differing Elements
|
|
||||||
if isinstance(bl_socket_value, tuple):
|
|
||||||
for i, bl_socket_subvalue in enumerate(bl_socket_value):
|
|
||||||
if bl_modifier[iface_id][i] != bl_socket_subvalue:
|
|
||||||
bl_modifier[iface_id][i] = bl_socket_subvalue
|
|
||||||
modifier_altered = True
|
|
||||||
|
|
||||||
# IF int/float Mismatch: Assign Float-Cast of Integer
|
|
||||||
## Blender is strict; only floats can set float vals.
|
|
||||||
## We are less strict; if the user passes an int, that's okay.
|
|
||||||
elif isinstance(bl_socket_value, int) and isinstance(
|
|
||||||
bl_modifier[iface_id],
|
|
||||||
float,
|
|
||||||
):
|
|
||||||
bl_modifier[iface_id] = float(bl_socket_value)
|
|
||||||
modifier_altered = True
|
|
||||||
else:
|
|
||||||
bl_modifier[iface_id] = bl_socket_value
|
|
||||||
modifier_altered = True
|
|
||||||
|
|
||||||
return modifier_altered
|
|
||||||
|
|
||||||
|
|
||||||
def write_modifier(
|
|
||||||
bl_modifier: bpy.types.Modifier,
|
|
||||||
modifier_attrs: ModifierAttrs,
|
|
||||||
) -> bool:
|
|
||||||
"""Writes modifier attributes to the modifier, changing only what's needed.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
True if the modifier was altered.
|
|
||||||
"""
|
|
||||||
modifier_altered = False
|
|
||||||
if bl_modifier.type == 'NODES':
|
|
||||||
modifier_altered = write_modifier_geonodes(bl_modifier, modifier_attrs)
|
|
||||||
elif bl_modifier.type == 'ARRAY':
|
|
||||||
raise NotImplementedError
|
|
||||||
else:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
return modifier_altered
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - ManagedObj
|
|
||||||
####################
|
|
||||||
class ManagedBLModifier(ct.schemas.ManagedObj):
|
|
||||||
managed_obj_type = ct.ManagedObjType.ManagedBLModifier
|
|
||||||
_modifier_name: str | None = None
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - BL Object Name
|
|
||||||
####################
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
return self._modifier_name
|
|
||||||
|
|
||||||
@name.setter
|
|
||||||
def name(self, value: str) -> None:
|
|
||||||
## TODO: Handle name conflict within same BLObject
|
|
||||||
log.info(
|
|
||||||
'Changing BLModifier w/Name "%s" to Name "%s"', self._modifier_name, value
|
|
||||||
)
|
|
||||||
self._modifier_name = value
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Allocation
|
|
||||||
####################
|
|
||||||
def __init__(self, name: str):
|
|
||||||
self.name = name
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Deallocation
|
|
||||||
####################
|
|
||||||
def free(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Modifiers
|
|
||||||
####################
|
|
||||||
def bl_modifier(
|
|
||||||
self,
|
|
||||||
bl_object: bpy.types.Object,
|
|
||||||
modifier_type: ModifierType,
|
|
||||||
modifier_attrs: ModifierAttrs,
|
|
||||||
):
|
|
||||||
"""Creates a new modifier for the current `bl_object`.
|
|
||||||
|
|
||||||
- Modifier Type Names: <https://docs.blender.org/api/current/bpy_types_enum_items/object_modifier_type_items.html#rna-enum-object-modifier-type-items>
|
|
||||||
"""
|
|
||||||
# Remove Mismatching Modifier
|
|
||||||
modifier_was_removed = False
|
|
||||||
if (
|
|
||||||
bl_modifier := bl_object.modifiers.get(self.name)
|
|
||||||
) and bl_modifier.type != modifier_type:
|
|
||||||
log.info(
|
|
||||||
'Removing (recreating) BLModifier "%s" on BLObject "%s" (existing modifier_type is "%s", but "%s" is requested)',
|
|
||||||
bl_modifier.name,
|
|
||||||
bl_object.name,
|
|
||||||
bl_modifier.type,
|
|
||||||
modifier_type,
|
|
||||||
)
|
|
||||||
self.free()
|
|
||||||
modifier_was_removed = True
|
|
||||||
|
|
||||||
# Create Modifier
|
|
||||||
if bl_modifier is None or modifier_was_removed:
|
|
||||||
log.info(
|
|
||||||
'Creating BLModifier "%s" on BLObject "%s" with modifier_type "%s"',
|
|
||||||
self.name,
|
|
||||||
bl_object.name,
|
|
||||||
modifier_type,
|
|
||||||
)
|
|
||||||
bl_modifier = bl_object.modifiers.new(
|
|
||||||
name=self.name,
|
|
||||||
type=modifier_type,
|
|
||||||
)
|
|
||||||
|
|
||||||
if modifier_altered := write_modifier(bl_modifier, modifier_attrs):
|
|
||||||
bl_object.data.update()
|
|
||||||
|
|
||||||
return bl_modifier
|
|
|
@ -1,106 +1,106 @@
|
||||||
import contextlib
|
import typing as typ
|
||||||
|
|
||||||
import bmesh
|
|
||||||
import bpy
|
|
||||||
import numpy as np
|
|
||||||
import typing_extensions as typx
|
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 ....utils import logger
|
|
||||||
from .. import contracts as ct
|
from .. import contracts as ct
|
||||||
from .managed_bl_collection import managed_collection, preview_collection
|
|
||||||
|
|
||||||
log = logger.get(__name__)
|
|
||||||
|
|
||||||
ModifierType = typx.Literal['NODES', 'ARRAY']
|
ModifierType = typx.Literal['NODES', 'ARRAY']
|
||||||
MODIFIER_NAMES = {
|
MODIFIER_NAMES = {
|
||||||
'NODES': 'BLMaxwell_GeoNodes',
|
'NODES': 'BLMaxwell_GeoNodes',
|
||||||
'ARRAY': 'BLMaxwell_Array',
|
'ARRAY': 'BLMaxwell_Array',
|
||||||
}
|
}
|
||||||
|
MANAGED_COLLECTION_NAME = 'BLMaxwell'
|
||||||
|
PREVIEW_COLLECTION_NAME = 'BLMaxwell Visible'
|
||||||
|
|
||||||
|
|
||||||
|
def bl_collection(
|
||||||
|
collection_name: str, view_layer_exclude: bool
|
||||||
|
) -> bpy.types.Collection:
|
||||||
|
# Init the "Managed Collection"
|
||||||
|
# Ensure Collection exists (and is in the Scene collection)
|
||||||
|
if collection_name not in bpy.data.collections:
|
||||||
|
collection = bpy.data.collections.new(collection_name)
|
||||||
|
bpy.context.scene.collection.children.link(collection)
|
||||||
|
else:
|
||||||
|
collection = bpy.data.collections[collection_name]
|
||||||
|
|
||||||
|
## Ensure synced View Layer exclusion
|
||||||
|
if (
|
||||||
|
layer_collection := bpy.context.view_layer.layer_collection.children[
|
||||||
|
collection_name
|
||||||
|
]
|
||||||
|
).exclude != view_layer_exclude:
|
||||||
|
layer_collection.exclude = view_layer_exclude
|
||||||
|
|
||||||
|
return collection
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - BLObject
|
|
||||||
####################
|
|
||||||
class ManagedBLObject(ct.schemas.ManagedObj):
|
class ManagedBLObject(ct.schemas.ManagedObj):
|
||||||
managed_obj_type = ct.ManagedObjType.ManagedBLObject
|
managed_obj_type = ct.ManagedObjType.ManagedBLObject
|
||||||
_bl_object_name: str | None = None
|
_bl_object_name: str
|
||||||
|
|
||||||
####################
|
def __init__(self, name: str):
|
||||||
# - BL Object Name
|
self._bl_object_name = name
|
||||||
####################
|
|
||||||
|
# Object Name
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
return self._bl_object_name
|
return self._bl_object_name
|
||||||
|
|
||||||
@name.setter
|
@name.setter
|
||||||
def name(self, value: str) -> None:
|
def set_name(self, value: str) -> None:
|
||||||
log.info(
|
# Object Doesn't Exist
|
||||||
'Changing BLObject w/Name "%s" to Name "%s"', self._bl_object_name, value
|
if not (bl_object := bpy.data.objects.get(self._bl_object_name)):
|
||||||
)
|
# ...AND Desired Object Name is Not Taken
|
||||||
|
|
||||||
if not bpy.data.objects.get(value):
|
if not bpy.data.objects.get(value):
|
||||||
log.info(
|
|
||||||
'Desired BLObject Name "%s" Not Taken',
|
|
||||||
value,
|
|
||||||
)
|
|
||||||
|
|
||||||
if self._bl_object_name is None:
|
|
||||||
log.info(
|
|
||||||
'Set New BLObject Name to "%s"',
|
|
||||||
value,
|
|
||||||
)
|
|
||||||
elif bl_object := bpy.data.objects.get(self._bl_object_name):
|
|
||||||
log.info(
|
|
||||||
'Changed BLObject Name to "%s"',
|
|
||||||
value,
|
|
||||||
)
|
|
||||||
bl_object.name = value
|
|
||||||
else:
|
|
||||||
msg = f'ManagedBLObject with name "{self._bl_object_name}" was deleted'
|
|
||||||
raise RuntimeError(msg)
|
|
||||||
|
|
||||||
# Set Internal Name
|
|
||||||
self._bl_object_name = value
|
self._bl_object_name = value
|
||||||
else:
|
return
|
||||||
log.info(
|
|
||||||
'Desired BLObject Name "%s" is Taken. Using Blender Rename',
|
|
||||||
value,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set Name Anyway, but Respect Blender's Renaming
|
# ...AND Desired Object Name is Taken
|
||||||
## When a name already exists, Blender adds .### to prevent overlap.
|
else:
|
||||||
## `set_name` is allowed to change the name; nodes account for this.
|
msg = f'Desired name {value} for BL object is taken'
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
# Object DOES Exist
|
||||||
bl_object.name = value
|
bl_object.name = value
|
||||||
self._bl_object_name = bl_object.name
|
self._bl_object_name = bl_object.name
|
||||||
|
## - When name exists, Blender adds .### to prevent overlap.
|
||||||
|
## - `set_name` is allowed to change the name; nodes account for this.
|
||||||
|
|
||||||
log.info(
|
# Object Datablock Name
|
||||||
'Changed BLObject Name to "%s"',
|
@property
|
||||||
bl_object.name,
|
def bl_mesh_name(self):
|
||||||
)
|
return self.name
|
||||||
|
|
||||||
####################
|
@property
|
||||||
# - Allocation
|
def bl_volume_name(self):
|
||||||
####################
|
return self.name
|
||||||
def __init__(self, name: str):
|
|
||||||
self.name = name
|
|
||||||
|
|
||||||
####################
|
# Deallocation
|
||||||
# - Deallocation
|
|
||||||
####################
|
|
||||||
def free(self):
|
def free(self):
|
||||||
if (bl_object := bpy.data.objects.get(self.name)) is None:
|
if not (bl_object := bpy.data.objects.get(self.name)):
|
||||||
return
|
return ## Nothing to do
|
||||||
|
|
||||||
# Delete the Underlying Datablock
|
# Delete the Underlying Datablock
|
||||||
## This automatically deletes the object too
|
## This automatically deletes the object too
|
||||||
log.info('Removing "%s" BLObject', bl_object.type)
|
if bl_object.type == 'MESH':
|
||||||
if bl_object.type in {'MESH', 'EMPTY'}:
|
bpy.data.meshes.remove(bl_object.data)
|
||||||
|
elif bl_object.type == 'EMPTY':
|
||||||
bpy.data.meshes.remove(bl_object.data)
|
bpy.data.meshes.remove(bl_object.data)
|
||||||
elif bl_object.type == 'VOLUME':
|
elif bl_object.type == 'VOLUME':
|
||||||
bpy.data.volumes.remove(bl_object.data)
|
bpy.data.volumes.remove(bl_object.data)
|
||||||
else:
|
else:
|
||||||
msg = f'BLObject "{bl_object.name}" has invalid kind "{bl_object.type}"'
|
msg = f'Type of to-delete `bl_object`, {bl_object.type}, is not valid'
|
||||||
raise RuntimeError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Actions
|
# - Actions
|
||||||
|
@ -125,17 +125,17 @@ class ManagedBLObject(ct.schemas.ManagedObj):
|
||||||
If it's already included, do nothing.
|
If it's already included, do nothing.
|
||||||
"""
|
"""
|
||||||
bl_object = self.bl_object(kind)
|
bl_object = self.bl_object(kind)
|
||||||
if bl_object.name not in preview_collection().objects:
|
if (
|
||||||
log.info('Moving "%s" to Preview Collection', bl_object.name)
|
bl_object.name
|
||||||
preview_collection().objects.link(bl_object)
|
not in (
|
||||||
|
preview_collection := bl_collection(
|
||||||
# Display Parameters
|
PREVIEW_COLLECTION_NAME, view_layer_exclude=False
|
||||||
if kind == 'EMPTY' and empty_display_type is not None:
|
|
||||||
log.info(
|
|
||||||
'Setting Empty Display Type "%s" for "%s"',
|
|
||||||
empty_display_type,
|
|
||||||
bl_object.name,
|
|
||||||
)
|
)
|
||||||
|
).objects
|
||||||
|
):
|
||||||
|
preview_collection.objects.link(bl_object)
|
||||||
|
|
||||||
|
if kind == 'EMPTY' and empty_display_type is not None:
|
||||||
bl_object.empty_display_type = empty_display_type
|
bl_object.empty_display_type = empty_display_type
|
||||||
|
|
||||||
def hide_preview(
|
def hide_preview(
|
||||||
|
@ -147,23 +147,29 @@ class ManagedBLObject(ct.schemas.ManagedObj):
|
||||||
If it's already removed, do nothing.
|
If it's already removed, do nothing.
|
||||||
"""
|
"""
|
||||||
bl_object = self.bl_object(kind)
|
bl_object = self.bl_object(kind)
|
||||||
if bl_object.name not in preview_collection().objects:
|
if (
|
||||||
log.info('Removing "%s" from Preview Collection', bl_object.name)
|
bl_object.name
|
||||||
|
not in (
|
||||||
|
preview_collection := bl_collection(
|
||||||
|
PREVIEW_COLLECTION_NAME, view_layer_exclude=False
|
||||||
|
)
|
||||||
|
).objects
|
||||||
|
):
|
||||||
preview_collection.objects.unlink(bl_object)
|
preview_collection.objects.unlink(bl_object)
|
||||||
|
|
||||||
def bl_select(self) -> None:
|
def bl_select(self) -> None:
|
||||||
"""Selects the managed Blender object globally, causing it to be ex.
|
"""Selects the managed Blender object globally, causing it to be ex.
|
||||||
outlined in the 3D viewport.
|
outlined in the 3D viewport.
|
||||||
"""
|
"""
|
||||||
if (bl_object := bpy.data.objects.get(self.name)) is not None:
|
if not (bl_object := bpy.data.objects.get(self.name)):
|
||||||
bpy.ops.object.select_all(action='DESELECT')
|
|
||||||
bl_object.select_set(True)
|
|
||||||
|
|
||||||
msg = 'Managed BLObject does not exist'
|
msg = 'Managed BLObject does not exist'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
bpy.ops.object.select_all(action='DESELECT')
|
||||||
|
bl_object.select_set(True)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - BLObject Management
|
# - Managed Object Management
|
||||||
####################
|
####################
|
||||||
def bl_object(
|
def bl_object(
|
||||||
self,
|
self,
|
||||||
|
@ -171,42 +177,33 @@ class ManagedBLObject(ct.schemas.ManagedObj):
|
||||||
):
|
):
|
||||||
"""Returns the managed blender object.
|
"""Returns the managed blender object.
|
||||||
|
|
||||||
If the requested object data kind is different, then delete the old
|
If the requested object data type is different, then delete the old
|
||||||
object and recreate.
|
object and recreate.
|
||||||
"""
|
"""
|
||||||
# Remove Object (if mismatch)
|
# Remove Object (if mismatch)
|
||||||
if (bl_object := bpy.data.objects.get(self.name)) and bl_object.type != kind:
|
if (
|
||||||
log.info(
|
bl_object := bpy.data.objects.get(self.name)
|
||||||
'Removing (recreating) "%s" (existing kind is "%s", but "%s" is requested)',
|
) and bl_object.type != kind:
|
||||||
bl_object.name,
|
|
||||||
bl_object.type,
|
|
||||||
kind,
|
|
||||||
)
|
|
||||||
self.free()
|
self.free()
|
||||||
|
|
||||||
# Create Object w/Appropriate Data Block
|
# Create Object w/Appropriate Data Block
|
||||||
if not (bl_object := bpy.data.objects.get(self.name)):
|
if not (bl_object := bpy.data.objects.get(self.name)):
|
||||||
log.info(
|
|
||||||
'Creating "%s" with kind "%s"',
|
|
||||||
self.name,
|
|
||||||
kind,
|
|
||||||
)
|
|
||||||
if kind == 'MESH':
|
if kind == 'MESH':
|
||||||
bl_data = bpy.data.meshes.new(self.name)
|
bl_data = bpy.data.meshes.new(self.bl_mesh_name)
|
||||||
elif kind == 'EMPTY':
|
elif kind == 'EMPTY':
|
||||||
bl_data = None
|
bl_data = None
|
||||||
elif kind == 'VOLUME':
|
elif kind == 'VOLUME':
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
else:
|
else:
|
||||||
msg = f'Created BLObject w/invalid kind "{bl_object.type}" for "{self.name}"'
|
msg = (
|
||||||
|
f'Requested `bl_object` type {bl_object.type} is not valid'
|
||||||
|
)
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
bl_object = bpy.data.objects.new(self.name, bl_data)
|
bl_object = bpy.data.objects.new(self.name, bl_data)
|
||||||
log.debug(
|
bl_collection(
|
||||||
'Linking "%s" to Base Collection',
|
MANAGED_COLLECTION_NAME, view_layer_exclude=True
|
||||||
bl_object.name,
|
).objects.link(bl_object)
|
||||||
)
|
|
||||||
managed_collection().objects.link(bl_object)
|
|
||||||
|
|
||||||
return bl_object
|
return bl_object
|
||||||
|
|
||||||
|
@ -214,16 +211,17 @@ class ManagedBLObject(ct.schemas.ManagedObj):
|
||||||
# - Mesh Data Properties
|
# - Mesh Data Properties
|
||||||
####################
|
####################
|
||||||
@property
|
@property
|
||||||
def mesh_data(self) -> bpy.types.Mesh:
|
def raw_mesh(self) -> bpy.types.Mesh:
|
||||||
"""Directly loads the Blender mesh data.
|
"""Returns the object's raw mesh data.
|
||||||
|
|
||||||
Raises:
|
Raises an error if the object has no mesh data.
|
||||||
ValueError: If the object has no mesh data.
|
|
||||||
"""
|
"""
|
||||||
if (bl_object := bpy.data.objects.get(self.name)) and bl_object.type == 'MESH':
|
if (
|
||||||
|
bl_object := bpy.data.objects.get(self.name)
|
||||||
|
) and bl_object.type == 'MESH':
|
||||||
return bl_object.data
|
return bl_object.data
|
||||||
|
|
||||||
msg = f'Requested mesh data from {self.name} of type {bl_object.type}'
|
msg = f'Requested MESH data from `bl_object` of type {bl_object.type}'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
|
@ -232,7 +230,9 @@ class ManagedBLObject(ct.schemas.ManagedObj):
|
||||||
evaluate: bool = True,
|
evaluate: bool = True,
|
||||||
triangulate: bool = False,
|
triangulate: bool = False,
|
||||||
) -> bpy.types.Mesh:
|
) -> bpy.types.Mesh:
|
||||||
if (bl_object := bpy.data.objects.get(self.name)) and bl_object.type == 'MESH':
|
if (
|
||||||
|
bl_object := bpy.data.objects.get(self.name)
|
||||||
|
) and bl_object.type == 'MESH':
|
||||||
bmesh_mesh = None
|
bmesh_mesh = None
|
||||||
try:
|
try:
|
||||||
bmesh_mesh = bmesh.new()
|
bmesh_mesh = bmesh.new()
|
||||||
|
@ -254,7 +254,7 @@ class ManagedBLObject(ct.schemas.ManagedObj):
|
||||||
bmesh_mesh.free()
|
bmesh_mesh.free()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
msg = f'Requested BMesh from "{self.name}" of type "{bl_object.type}"'
|
msg = f'Requested BMesh from `bl_object` of type {bl_object.type}'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -262,31 +262,27 @@ class ManagedBLObject(ct.schemas.ManagedObj):
|
||||||
## TODO: Cached
|
## TODO: Cached
|
||||||
|
|
||||||
# Ensure Updated Geometry
|
# Ensure Updated Geometry
|
||||||
log.debug('Updating View Layer')
|
|
||||||
bpy.context.view_layer.update()
|
bpy.context.view_layer.update()
|
||||||
|
## TODO: Must we?
|
||||||
|
|
||||||
# Compute Evaluted + Triangulated Mesh
|
# Compute Evaluted + Triangulated Mesh
|
||||||
log.debug('Casting BMesh of "%s" to Temporary Mesh', self.name)
|
|
||||||
_mesh = bpy.data.meshes.new(name='TemporaryMesh')
|
_mesh = bpy.data.meshes.new(name='TemporaryMesh')
|
||||||
with self.mesh_as_bmesh(evaluate=True, triangulate=True) as bmesh_mesh:
|
with self.mesh_as_bmesh(evaluate=True, triangulate=True) as bmesh_mesh:
|
||||||
bmesh_mesh.to_mesh(_mesh)
|
bmesh_mesh.to_mesh(_mesh)
|
||||||
|
|
||||||
# Optimized Vertex Copy
|
# Optimized Vertex Copy
|
||||||
## See <https://blog.michelanders.nl/2016/02/copying-vertices-to-numpy-arrays-in_4.html>
|
## See <https://blog.michelanders.nl/2016/02/copying-vertices-to-numpy-arrays-in_4.html>
|
||||||
log.debug('Copying Vertices from "%s"', self.name)
|
|
||||||
verts = np.zeros(3 * len(_mesh.vertices), dtype=np.float64)
|
verts = np.zeros(3 * len(_mesh.vertices), dtype=np.float64)
|
||||||
_mesh.vertices.foreach_get('co', verts)
|
_mesh.vertices.foreach_get('co', verts)
|
||||||
verts.shape = (-1, 3)
|
verts.shape = (-1, 3)
|
||||||
|
|
||||||
# Optimized Triangle Copy
|
# Optimized Triangle Copy
|
||||||
## To understand, read it, **carefully**.
|
## To understand, read it, **carefully**.
|
||||||
log.debug('Copying Faces from "%s"', self.name)
|
|
||||||
faces = np.zeros(3 * len(_mesh.polygons), dtype=np.uint64)
|
faces = np.zeros(3 * len(_mesh.polygons), dtype=np.uint64)
|
||||||
_mesh.polygons.foreach_get('vertices', faces)
|
_mesh.polygons.foreach_get('vertices', faces)
|
||||||
faces.shape = (-1, 3)
|
faces.shape = (-1, 3)
|
||||||
|
|
||||||
# Remove Temporary Mesh
|
# Remove Temporary Mesh
|
||||||
log.debug('Removing Temporary Mesh')
|
|
||||||
bpy.data.meshes.remove(_mesh)
|
bpy.data.meshes.remove(_mesh)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -295,7 +291,7 @@ class ManagedBLObject(ct.schemas.ManagedObj):
|
||||||
}
|
}
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Modifiers
|
# - Modifier Methods
|
||||||
####################
|
####################
|
||||||
def bl_modifier(
|
def bl_modifier(
|
||||||
self,
|
self,
|
||||||
|
@ -303,10 +299,10 @@ class ManagedBLObject(ct.schemas.ManagedObj):
|
||||||
):
|
):
|
||||||
"""Creates a new modifier for the current `bl_object`.
|
"""Creates a new modifier for the current `bl_object`.
|
||||||
|
|
||||||
- Modifier Type Names: <https://docs.blender.org/api/current/bpy_types_enum_items/object_modifier_type_items.html#rna-enum-object-modifier-type-items>
|
For all Blender modifier type names, see: <https://docs.blender.org/api/current/bpy_types_enum_items/object_modifier_type_items.html#rna-enum-object-modifier-type-items>
|
||||||
"""
|
"""
|
||||||
if not (bl_object := bpy.data.objects.get(self.name)):
|
if not (bl_object := bpy.data.objects.get(self.name)):
|
||||||
msg = f'Tried to add modifier to "{self.name}", but it has no bl_object'
|
msg = "Can't add modifier to BL object that doesn't exist"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
# (Create and) Return Modifier
|
# (Create and) Return Modifier
|
||||||
|
@ -380,7 +376,8 @@ class ManagedBLObject(ct.schemas.ManagedObj):
|
||||||
# Quickly Determine if IDPropertyArray is Equal
|
# Quickly Determine if IDPropertyArray is Equal
|
||||||
if (
|
if (
|
||||||
hasattr(bl_modifier[interface_identifier], 'to_list')
|
hasattr(bl_modifier[interface_identifier], 'to_list')
|
||||||
and tuple(bl_modifier[interface_identifier].to_list()) == value
|
and tuple(bl_modifier[interface_identifier].to_list())
|
||||||
|
== value
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -398,3 +395,18 @@ class ManagedBLObject(ct.schemas.ManagedObj):
|
||||||
# Update DepGraph (if anything changed)
|
# Update DepGraph (if anything changed)
|
||||||
if modifier_altered:
|
if modifier_altered:
|
||||||
bl_object.data.update()
|
bl_object.data.update()
|
||||||
|
|
||||||
|
# @property
|
||||||
|
# def volume(self) -> bpy.types.Volume:
|
||||||
|
# """Returns the object's volume data.
|
||||||
|
#
|
||||||
|
# Raises an error if the object has no volume data.
|
||||||
|
# """
|
||||||
|
# if (
|
||||||
|
# (bl_object := bpy.data.objects.get(self.bl_object_name))
|
||||||
|
# and bl_object.type == "VOLUME"
|
||||||
|
# ):
|
||||||
|
# return bl_object.data
|
||||||
|
#
|
||||||
|
# msg = f"Requested VOLUME data from `bl_object` of type {bl_object.type}"
|
||||||
|
# raise ValueError(msg)
|
||||||
|
|
|
@ -2,11 +2,7 @@ import typing as typ
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
from ...utils import logger
|
|
||||||
from . import contracts as ct
|
from . import contracts as ct
|
||||||
from .managed_objs.managed_bl_collection import preview_collection
|
|
||||||
|
|
||||||
log = logger.get(__name__)
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Cache Management
|
# - Cache Management
|
||||||
|
@ -77,15 +73,6 @@ class MaxwellSimTree(bpy.types.NodeTree):
|
||||||
for bl_socket in [*node.inputs, *node.outputs]:
|
for bl_socket in [*node.inputs, *node.outputs]:
|
||||||
bl_socket.locked = False
|
bl_socket.locked = False
|
||||||
|
|
||||||
def unpreview_all(self):
|
|
||||||
log.info('Disabling All 3D Previews')
|
|
||||||
for node in self.nodes:
|
|
||||||
if node.preview_active:
|
|
||||||
node.preview_active = False
|
|
||||||
|
|
||||||
for bl_object in preview_collection().objects.values():
|
|
||||||
preview_collection().objects.unlink(bl_object)
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Init Methods
|
# - Init Methods
|
||||||
####################
|
####################
|
||||||
|
@ -138,7 +125,9 @@ class MaxwellSimTree(bpy.types.NodeTree):
|
||||||
'to_add': [],
|
'to_add': [],
|
||||||
}
|
}
|
||||||
for link_ptr in delta_links['removed']:
|
for link_ptr in delta_links['removed']:
|
||||||
from_socket = self._node_link_cache.link_ptrs_from_sockets[link_ptr]
|
from_socket = self._node_link_cache.link_ptrs_from_sockets[
|
||||||
|
link_ptr
|
||||||
|
]
|
||||||
to_socket = self._node_link_cache.link_ptrs_to_sockets[link_ptr]
|
to_socket = self._node_link_cache.link_ptrs_to_sockets[link_ptr]
|
||||||
|
|
||||||
# Update Socket Caches
|
# Update Socket Caches
|
||||||
|
@ -147,7 +136,9 @@ class MaxwellSimTree(bpy.types.NodeTree):
|
||||||
|
|
||||||
# Trigger Report Chain on Socket that Just Lost a Link
|
# Trigger Report Chain on Socket that Just Lost a Link
|
||||||
## Aka. Forward-Refresh Caches Relying on Linkage
|
## Aka. Forward-Refresh Caches Relying on Linkage
|
||||||
if not (consent_removal := to_socket.sync_link_removed(from_socket)):
|
if not (
|
||||||
|
consent_removal := to_socket.sync_link_removed(from_socket)
|
||||||
|
):
|
||||||
# Did Not Consent to Removal: Queue Add Link
|
# Did Not Consent to Removal: Queue Add Link
|
||||||
link_alterations['to_add'].append((from_socket, to_socket))
|
link_alterations['to_add'].append((from_socket, to_socket))
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
# from . import kitchen_sink
|
# from . import kitchen_sink
|
||||||
|
|
||||||
|
from . import inputs
|
||||||
|
from . import outputs
|
||||||
|
from . import sources
|
||||||
|
from . import mediums
|
||||||
|
from . import structures
|
||||||
|
|
||||||
# from . import bounds
|
# from . import bounds
|
||||||
from . import (
|
from . import monitors
|
||||||
inputs,
|
from . import simulations
|
||||||
mediums,
|
from . import utilities
|
||||||
monitors,
|
from . import viz
|
||||||
outputs,
|
|
||||||
simulations,
|
|
||||||
sources,
|
|
||||||
structures,
|
|
||||||
utilities,
|
|
||||||
viz,
|
|
||||||
)
|
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
# *kitchen_sink.BL_REGISTER,
|
# *kitchen_sink.BL_REGISTER,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import inspect
|
||||||
import json
|
import json
|
||||||
import typing as typ
|
import typing as typ
|
||||||
import uuid
|
import uuid
|
||||||
|
@ -21,7 +22,7 @@ _DEFAULT_LOOSE_SOCKET_SER = json.dumps(
|
||||||
'socket_def_names': [],
|
'socket_def_names': [],
|
||||||
'models': [],
|
'models': [],
|
||||||
}
|
}
|
||||||
) ## TODO: What in the jesus christ is this
|
)
|
||||||
|
|
||||||
|
|
||||||
class MaxwellSimNode(bpy.types.Node):
|
class MaxwellSimNode(bpy.types.Node):
|
||||||
|
@ -42,18 +43,16 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
# Sockets
|
# Sockets
|
||||||
_output_socket_methods: dict
|
_output_socket_methods: dict
|
||||||
|
|
||||||
input_sockets: typ.ClassVar[dict[str, ct.schemas.SocketDef]] = {}
|
input_sockets: dict[str, ct.schemas.SocketDef] = {}
|
||||||
output_sockets: typ.ClassVar[dict[str, ct.schemas.SocketDef]] = {}
|
output_sockets: dict[str, ct.schemas.SocketDef] = {}
|
||||||
input_socket_sets: typ.ClassVar[dict[str, dict[str, ct.schemas.SocketDef]]] = {}
|
input_socket_sets: dict[str, dict[str, ct.schemas.SocketDef]] = {}
|
||||||
output_socket_sets: typ.ClassVar[dict[str, dict[str, ct.schemas.SocketDef]]] = {}
|
output_socket_sets: dict[str, dict[str, ct.schemas.SocketDef]] = {}
|
||||||
|
|
||||||
# Presets
|
# Presets
|
||||||
presets: typ.ClassVar = {}
|
presets = {}
|
||||||
|
|
||||||
# Managed Objects
|
# Managed Objects
|
||||||
managed_obj_defs: typ.ClassVar[
|
managed_obj_defs: dict[ct.ManagedObjName, ct.schemas.ManagedObjDef] = {}
|
||||||
dict[ct.ManagedObjName, ct.schemas.ManagedObjDef]
|
|
||||||
] = {}
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Initialization
|
# - Initialization
|
||||||
|
@ -83,14 +82,6 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
update=(lambda self, context: self.sync_sim_node_name(context)),
|
update=(lambda self, context: self.sync_sim_node_name(context)),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Setup "Previewing" Property for Node
|
|
||||||
cls.__annotations__['preview_active'] = bpy.props.BoolProperty(
|
|
||||||
name='Preview Active',
|
|
||||||
description='Whether the preview (if any) is currently active',
|
|
||||||
default=False,
|
|
||||||
update=lambda self, context: self.sync_preview_active(context),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Setup Locked Property for Node
|
# Setup Locked Property for Node
|
||||||
cls.__annotations__['locked'] = bpy.props.BoolProperty(
|
cls.__annotations__['locked'] = bpy.props.BoolProperty(
|
||||||
name='Locked State',
|
name='Locked State',
|
||||||
|
@ -105,30 +96,34 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
|
|
||||||
# Setup Callback Methods
|
# Setup Callback Methods
|
||||||
cls._output_socket_methods = {
|
cls._output_socket_methods = {
|
||||||
(method.extra_data['output_socket_name'], method.extra_data['kind']): method
|
method._index_by: method
|
||||||
for attr_name in dir(cls)
|
for attr_name in dir(cls)
|
||||||
if hasattr(method := getattr(cls, attr_name), 'action_type')
|
if hasattr(method := getattr(cls, attr_name), '_callback_type')
|
||||||
and method.action_type == 'computes_output_socket'
|
and method._callback_type == 'computes_output_socket'
|
||||||
and hasattr(method, 'extra_data')
|
|
||||||
and method.extra_data
|
|
||||||
}
|
}
|
||||||
cls._on_value_changed_methods = {
|
cls._on_value_changed_methods = {
|
||||||
method
|
method
|
||||||
for attr_name in dir(cls)
|
for attr_name in dir(cls)
|
||||||
if hasattr(method := getattr(cls, attr_name), 'action_type')
|
if hasattr(method := getattr(cls, attr_name), '_callback_type')
|
||||||
and method.action_type == 'on_value_changed'
|
and method._callback_type == 'on_value_changed'
|
||||||
|
}
|
||||||
|
cls._on_show_preview = {
|
||||||
|
method
|
||||||
|
for attr_name in dir(cls)
|
||||||
|
if hasattr(method := getattr(cls, attr_name), '_callback_type')
|
||||||
|
and method._callback_type == 'on_show_preview'
|
||||||
}
|
}
|
||||||
cls._on_show_plot = {
|
cls._on_show_plot = {
|
||||||
method
|
method
|
||||||
for attr_name in dir(cls)
|
for attr_name in dir(cls)
|
||||||
if hasattr(method := getattr(cls, attr_name), 'action_type')
|
if hasattr(method := getattr(cls, attr_name), '_callback_type')
|
||||||
and method.action_type == 'on_show_plot'
|
and method._callback_type == 'on_show_plot'
|
||||||
}
|
}
|
||||||
cls._on_init = {
|
cls._on_init = {
|
||||||
method
|
method
|
||||||
for attr_name in dir(cls)
|
for attr_name in dir(cls)
|
||||||
if hasattr(method := getattr(cls, attr_name), 'action_type')
|
if hasattr(method := getattr(cls, attr_name), '_callback_type')
|
||||||
and method.action_type == 'on_init'
|
and method._callback_type == 'on_init'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Setup Socket Set Dropdown
|
# Setup Socket Set Dropdown
|
||||||
|
@ -140,7 +135,7 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
_input_socket_set_names := list(cls.input_socket_sets.keys())
|
_input_socket_set_names := list(cls.input_socket_sets.keys())
|
||||||
) + [
|
) + [
|
||||||
output_socket_set_name
|
output_socket_set_name
|
||||||
for output_socket_set_name in cls.output_socket_sets
|
for output_socket_set_name in cls.output_socket_sets.keys()
|
||||||
if output_socket_set_name not in _input_socket_set_names
|
if output_socket_set_name not in _input_socket_set_names
|
||||||
]
|
]
|
||||||
socket_set_ids = [
|
socket_set_ids = [
|
||||||
|
@ -162,11 +157,12 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
for socket_set_id, socket_set_name in zip(
|
for socket_set_id, socket_set_name in zip(
|
||||||
socket_set_ids,
|
socket_set_ids,
|
||||||
socket_set_names,
|
socket_set_names,
|
||||||
strict=False,
|
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
default=socket_set_names[0],
|
default=socket_set_names[0],
|
||||||
update=lambda self, context: self.sync_active_socket_set(context),
|
update=lambda self, context: self.sync_active_socket_set(
|
||||||
|
context
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Setup Preset Dropdown
|
# Setup Preset Dropdown
|
||||||
|
@ -185,8 +181,8 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
)
|
)
|
||||||
for preset_name, preset_def in cls.presets.items()
|
for preset_name, preset_def in cls.presets.items()
|
||||||
],
|
],
|
||||||
default=next(cls.presets.keys()),
|
default=list(cls.presets.keys())[0],
|
||||||
update=lambda self, _: (self.sync_active_preset()()),
|
update=lambda self, context: (self.sync_active_preset()()),
|
||||||
)
|
)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -196,7 +192,7 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
self.sync_sockets()
|
self.sync_sockets()
|
||||||
self.sync_prop('active_socket_set', context)
|
self.sync_prop('active_socket_set', context)
|
||||||
|
|
||||||
def sync_sim_node_name(self, _):
|
def sync_sim_node_name(self, context):
|
||||||
if (mobjs := CACHE[self.instance_id].get('managed_objs')) is None:
|
if (mobjs := CACHE[self.instance_id].get('managed_objs')) is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -211,26 +207,12 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
## - If altered, set the 'sim_node_name' to the altered name.
|
## - If altered, set the 'sim_node_name' to the altered name.
|
||||||
## - This will cause recursion, but only once.
|
## - This will cause recursion, but only once.
|
||||||
|
|
||||||
def sync_preview_active(self, _: bpy.types.Context):
|
|
||||||
log.info(
|
|
||||||
'Changed Preview Active in "%s" to "%s"',
|
|
||||||
self.name,
|
|
||||||
self.preview_active,
|
|
||||||
)
|
|
||||||
for method in self._on_value_changed_methods:
|
|
||||||
if 'preview_active' in method.extra_data['changed_props']:
|
|
||||||
log.info(
|
|
||||||
'Running Previewer Callback "%s" in "%s")',
|
|
||||||
method.__name__,
|
|
||||||
self.name,
|
|
||||||
)
|
|
||||||
method(self)
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Managed Object Properties
|
# - Managed Object Properties
|
||||||
####################
|
####################
|
||||||
@property
|
@property
|
||||||
def managed_objs(self):
|
def managed_objs(self):
|
||||||
|
global CACHE
|
||||||
if not CACHE.get(self.instance_id):
|
if not CACHE.get(self.instance_id):
|
||||||
CACHE[self.instance_id] = {}
|
CACHE[self.instance_id] = {}
|
||||||
|
|
||||||
|
@ -247,7 +229,9 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
# Fill w/Managed Objects by Name Socket
|
# Fill w/Managed Objects by Name Socket
|
||||||
for mobj_id, mobj_def in self.managed_obj_defs.items():
|
for mobj_id, mobj_def in self.managed_obj_defs.items():
|
||||||
name = mobj_def.name_prefix + self.sim_node_name
|
name = mobj_def.name_prefix + self.sim_node_name
|
||||||
CACHE[self.instance_id]['managed_objs'][mobj_id] = mobj_def.mk(name)
|
CACHE[self.instance_id]['managed_objs'][mobj_id] = mobj_def.mk(
|
||||||
|
name
|
||||||
|
)
|
||||||
|
|
||||||
return CACHE[self.instance_id]['managed_objs']
|
return CACHE[self.instance_id]['managed_objs']
|
||||||
|
|
||||||
|
@ -269,7 +253,9 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
|
|
||||||
# Retrieve Active Socket Set Sockets
|
# Retrieve Active Socket Set Sockets
|
||||||
socket_sets = (
|
socket_sets = (
|
||||||
self.input_socket_sets if direc == 'input' else self.output_socket_sets
|
self.input_socket_sets
|
||||||
|
if direc == 'input'
|
||||||
|
else self.output_socket_sets
|
||||||
)
|
)
|
||||||
active_socket_set_sockets = socket_sets.get(self.active_socket_set)
|
active_socket_set_sockets = socket_sets.get(self.active_socket_set)
|
||||||
|
|
||||||
|
@ -279,13 +265,24 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
return active_socket_set_sockets
|
return active_socket_set_sockets
|
||||||
|
|
||||||
def active_sockets(self, direc: typx.Literal['input', 'output']):
|
def active_sockets(self, direc: typx.Literal['input', 'output']):
|
||||||
static_sockets = self.input_sockets if direc == 'input' else self.output_sockets
|
static_sockets = (
|
||||||
|
self.input_sockets if direc == 'input' else self.output_sockets
|
||||||
|
)
|
||||||
|
socket_sets = (
|
||||||
|
self.input_socket_sets
|
||||||
|
if direc == 'input'
|
||||||
|
else self.output_socket_sets
|
||||||
|
)
|
||||||
loose_sockets = (
|
loose_sockets = (
|
||||||
self.loose_input_sockets if direc == 'input' else self.loose_output_sockets
|
self.loose_input_sockets
|
||||||
|
if direc == 'input'
|
||||||
|
else self.loose_output_sockets
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
static_sockets | self.active_socket_set_sockets(direc=direc) | loose_sockets
|
static_sockets
|
||||||
|
| self.active_socket_set_sockets(direc=direc)
|
||||||
|
| loose_sockets
|
||||||
)
|
)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -305,8 +302,12 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
)
|
)
|
||||||
|
|
||||||
## Internal Serialization/Deserialization Methods (yuck)
|
## Internal Serialization/Deserialization Methods (yuck)
|
||||||
def _ser_loose_sockets(self, deser: dict[str, ct.schemas.SocketDef]) -> str:
|
def _ser_loose_sockets(
|
||||||
if not all(isinstance(model, pyd.BaseModel) for model in deser.values()):
|
self, deser: dict[str, ct.schemas.SocketDef]
|
||||||
|
) -> str:
|
||||||
|
if not all(
|
||||||
|
isinstance(model, pyd.BaseModel) for model in deser.values()
|
||||||
|
):
|
||||||
msg = 'Trying to deserialize loose sockets with invalid SocketDefs (they must be `pydantic` BaseModels).'
|
msg = 'Trying to deserialize loose sockets with invalid SocketDefs (they must be `pydantic` BaseModels).'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
@ -324,7 +325,9 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
}
|
}
|
||||||
) ## Big reliance on order-preservation of dicts here.)
|
) ## Big reliance on order-preservation of dicts here.)
|
||||||
|
|
||||||
def _deser_loose_sockets(self, ser: str) -> dict[str, ct.schemas.SocketDef]:
|
def _deser_loose_sockets(
|
||||||
|
self, ser: str
|
||||||
|
) -> dict[str, ct.schemas.SocketDef]:
|
||||||
semi_deser = json.loads(ser)
|
semi_deser = json.loads(ser)
|
||||||
return {
|
return {
|
||||||
socket_name: getattr(sockets, socket_def_name)(**model_kwargs)
|
socket_name: getattr(sockets, socket_def_name)(**model_kwargs)
|
||||||
|
@ -332,7 +335,6 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
semi_deser['socket_names'],
|
semi_deser['socket_names'],
|
||||||
semi_deser['socket_def_names'],
|
semi_deser['socket_def_names'],
|
||||||
semi_deser['models'],
|
semi_deser['models'],
|
||||||
strict=False,
|
|
||||||
)
|
)
|
||||||
if hasattr(sockets, socket_def_name)
|
if hasattr(sockets, socket_def_name)
|
||||||
}
|
}
|
||||||
|
@ -352,11 +354,6 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
self,
|
self,
|
||||||
value: dict[str, ct.schemas.SocketDef],
|
value: dict[str, ct.schemas.SocketDef],
|
||||||
) -> None:
|
) -> None:
|
||||||
log.info(
|
|
||||||
'Setting Loose Input Sockets on "%s" to "%s"',
|
|
||||||
self.bl_label,
|
|
||||||
str(value),
|
|
||||||
)
|
|
||||||
if not value:
|
if not value:
|
||||||
self.ser_loose_input_sockets = _DEFAULT_LOOSE_SOCKET_SER
|
self.ser_loose_input_sockets = _DEFAULT_LOOSE_SOCKET_SER
|
||||||
else:
|
else:
|
||||||
|
@ -451,7 +448,9 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
# - Preset Management
|
# - Preset Management
|
||||||
####################
|
####################
|
||||||
def sync_active_preset(self) -> None:
|
def sync_active_preset(self) -> None:
|
||||||
"""Applies the active preset by overwriting the value of preset-defined input sockets."""
|
"""Applies the active preset by overwriting the value of
|
||||||
|
preset-defined input sockets.
|
||||||
|
"""
|
||||||
if not (preset_def := self.presets.get(self.active_preset)):
|
if not (preset_def := self.presets.get(self.active_preset)):
|
||||||
msg = f'Tried to apply active preset, but the active preset ({self.active_preset}) is not in presets ({self.presets})'
|
msg = f'Tried to apply active preset, but the active preset ({self.active_preset}) is not in presets ({self.presets})'
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
@ -508,7 +507,7 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
|
|
||||||
## TODO: Side panel buttons for fanciness.
|
## TODO: Side panel buttons for fanciness.
|
||||||
|
|
||||||
def draw_plot_settings(self, _: bpy.types.Context, layout: bpy.types.UILayout):
|
def draw_plot_settings(self, context, layout):
|
||||||
if self.locked:
|
if self.locked:
|
||||||
layout.enabled = False
|
layout.enabled = False
|
||||||
|
|
||||||
|
@ -523,14 +522,16 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
"""Computes the data of an input socket, by socket name and data flow kind, by asking the socket nicely via `bl_socket.compute_data`.
|
"""Computes the data of an input socket, by socket name and data flow kind, by asking the socket nicely via `bl_socket.compute_data`.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
input_socket_name: The name of the input socket, as defined in `self.input_sockets`.
|
input_socket_name: The name of the input socket, as defined in
|
||||||
kind: The kind of data flow to compute.
|
`self.input_sockets`.
|
||||||
|
kind: The data flow kind to compute retrieve.
|
||||||
"""
|
"""
|
||||||
if bl_socket := self.inputs.get(input_socket_name):
|
if not (bl_socket := self.inputs.get(input_socket_name)):
|
||||||
return bl_socket.compute_data(kind=kind)
|
return None
|
||||||
|
# msg = f"Input socket name {input_socket_name} is not an active input sockets."
|
||||||
|
# raise ValueError(msg)
|
||||||
|
|
||||||
msg = f'Input socket "{input_socket_name}" on "{self.bl_idname}" is not an active input socket'
|
return bl_socket.compute_data(kind=kind)
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
def compute_output(
|
def compute_output(
|
||||||
self,
|
self,
|
||||||
|
@ -543,25 +544,27 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
This method is run to produce the value.
|
This method is run to produce the value.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
output_socket_name: The name declaring the output socket, for which this method computes the output.
|
output_socket_name: The name declaring the output socket,
|
||||||
kind: The DataFlowKind to use when computing the output socket value.
|
for which this method computes the output.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The value of the output socket, as computed by the dedicated method
|
The value of the output socket, as computed by the dedicated method
|
||||||
registered using the `@computes_output_socket` decorator.
|
registered using the `@computes_output_socket` decorator.
|
||||||
"""
|
"""
|
||||||
if output_socket_method := self._output_socket_methods.get(
|
if not (
|
||||||
|
output_socket_method := self._output_socket_methods.get(
|
||||||
(output_socket_name, kind)
|
(output_socket_name, kind)
|
||||||
|
)
|
||||||
):
|
):
|
||||||
return output_socket_method(self)
|
msg = f'No output method for ({output_socket_name}, {str(kind.value)}'
|
||||||
|
|
||||||
msg = f'No output method for ({output_socket_name}, {kind.value!s}'
|
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
return output_socket_method(self)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Action Chain
|
# - Action Chain
|
||||||
####################
|
####################
|
||||||
def sync_prop(self, prop_name: str, _: bpy.types.Context):
|
def sync_prop(self, prop_name: str, context: bpy.types.Context):
|
||||||
"""Called when a property has been updated."""
|
"""Called when a property has been updated."""
|
||||||
if not hasattr(self, prop_name):
|
if not hasattr(self, prop_name):
|
||||||
msg = f'Property {prop_name} not defined on socket {self}'
|
msg = f'Property {prop_name} not defined on socket {self}'
|
||||||
|
@ -586,13 +589,6 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
Invalidates (recursively) the cache of any managed object or
|
Invalidates (recursively) the cache of any managed object or
|
||||||
output socket method that implicitly depends on this input socket.
|
output socket method that implicitly depends on this input socket.
|
||||||
"""
|
"""
|
||||||
#log.debug(
|
|
||||||
# 'Action "%s" Triggered in "%s" (socket_name="%s", prop_name="%s")',
|
|
||||||
# action,
|
|
||||||
# self.name,
|
|
||||||
# socket_name,
|
|
||||||
# prop_name,
|
|
||||||
#)
|
|
||||||
# Forwards Chains
|
# Forwards Chains
|
||||||
if action == 'value_changed':
|
if action == 'value_changed':
|
||||||
# Run User Callbacks
|
# Run User Callbacks
|
||||||
|
@ -602,20 +598,20 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
if (
|
if (
|
||||||
(
|
(
|
||||||
socket_name
|
socket_name
|
||||||
and socket_name in method.extra_data['changed_sockets']
|
and socket_name
|
||||||
|
in method._extra_data.get('changed_sockets')
|
||||||
|
)
|
||||||
|
or (
|
||||||
|
prop_name
|
||||||
|
and prop_name
|
||||||
|
in method._extra_data.get('changed_props')
|
||||||
)
|
)
|
||||||
or (prop_name and prop_name in method.extra_data['changed_props'])
|
|
||||||
or (
|
or (
|
||||||
socket_name
|
socket_name
|
||||||
and method.extra_data['changed_loose_input']
|
and method._extra_data['changed_loose_input']
|
||||||
and socket_name in self.loose_input_sockets
|
and socket_name in self.loose_input_sockets
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
#log.debug(
|
|
||||||
# 'Running Value-Change Callback "%s" in "%s")',
|
|
||||||
# method.__name__,
|
|
||||||
# self.name,
|
|
||||||
#)
|
|
||||||
method(self)
|
method(self)
|
||||||
|
|
||||||
# Propagate via Output Sockets
|
# Propagate via Output Sockets
|
||||||
|
@ -639,15 +635,8 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
|
|
||||||
elif action == 'show_preview':
|
elif action == 'show_preview':
|
||||||
# Run User Callbacks
|
# Run User Callbacks
|
||||||
## "On Show Preview" callbacks are 'on_value_changed' callbacks...
|
for method in self._on_show_preview:
|
||||||
## ...which simply hook into the 'preview_active' property.
|
method(self)
|
||||||
## By (maybe) altering 'preview_active', callbacks run as needed.
|
|
||||||
if not self.preview_active:
|
|
||||||
log.info(
|
|
||||||
'Activating Preview in "%s")',
|
|
||||||
self.name,
|
|
||||||
)
|
|
||||||
self.preview_active = True
|
|
||||||
|
|
||||||
## Propagate via Input Sockets
|
## Propagate via Input Sockets
|
||||||
for bl_socket in self.active_bl_sockets('input'):
|
for bl_socket in self.active_bl_sockets('input'):
|
||||||
|
@ -659,7 +648,7 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
## ...because they can stop propagation, they should go first.
|
## ...because they can stop propagation, they should go first.
|
||||||
for method in self._on_show_plot:
|
for method in self._on_show_plot:
|
||||||
method(self)
|
method(self)
|
||||||
if method.extra_data['stop_propagation']:
|
if method._extra_data['stop_propagation']:
|
||||||
return
|
return
|
||||||
|
|
||||||
## Propagate via Input Sockets
|
## Propagate via Input Sockets
|
||||||
|
@ -675,10 +664,13 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
|
|
||||||
Restricted to the MaxwellSimTreeType.
|
Restricted to the MaxwellSimTreeType.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return node_tree.bl_idname == ct.TreeType.MaxwellSim.value
|
return node_tree.bl_idname == ct.TreeType.MaxwellSim.value
|
||||||
|
|
||||||
def init(self, context: bpy.types.Context):
|
def init(self, context: bpy.types.Context):
|
||||||
"""Run (by Blender) on node creation."""
|
"""Run (by Blender) on node creation."""
|
||||||
|
global CACHE
|
||||||
|
|
||||||
# Initialize Cache and Instance ID
|
# Initialize Cache and Instance ID
|
||||||
self.instance_id = str(uuid.uuid4())
|
self.instance_id = str(uuid.uuid4())
|
||||||
CACHE[self.instance_id] = {}
|
CACHE[self.instance_id] = {}
|
||||||
|
@ -703,6 +695,7 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
|
|
||||||
def free(self) -> None:
|
def free(self) -> None:
|
||||||
"""Run (by Blender) when deleting the node."""
|
"""Run (by Blender) when deleting the node."""
|
||||||
|
global CACHE
|
||||||
if not CACHE.get(self.instance_id):
|
if not CACHE.get(self.instance_id):
|
||||||
CACHE[self.instance_id] = {}
|
CACHE[self.instance_id] = {}
|
||||||
node_tree = self.id_data
|
node_tree = self.id_data
|
||||||
|
@ -732,3 +725,306 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
# Finally: Free Instance Cache
|
# Finally: Free Instance Cache
|
||||||
if self.instance_id in CACHE:
|
if self.instance_id in CACHE:
|
||||||
del CACHE[self.instance_id]
|
del CACHE[self.instance_id]
|
||||||
|
|
||||||
|
|
||||||
|
def chain_event_decorator(
|
||||||
|
callback_type: typ.Literal[
|
||||||
|
'computes_output_socket',
|
||||||
|
'on_value_changed',
|
||||||
|
'on_show_preview',
|
||||||
|
'on_show_plot',
|
||||||
|
'on_init',
|
||||||
|
],
|
||||||
|
index_by: typ.Any | None = None,
|
||||||
|
extra_data: dict[str, typ.Any] | None = None,
|
||||||
|
kind: ct.DataFlowKind = ct.DataFlowKind.Value,
|
||||||
|
input_sockets: set[str] = set(), ## For now, presume
|
||||||
|
output_sockets: set[str] = set(), ## For now, presume
|
||||||
|
loose_input_sockets: bool = False,
|
||||||
|
loose_output_sockets: bool = False,
|
||||||
|
props: set[str] = set(),
|
||||||
|
managed_objs: set[str] = set(),
|
||||||
|
req_params: set[str] = set(),
|
||||||
|
):
|
||||||
|
def decorator(method: typ.Callable) -> typ.Callable:
|
||||||
|
# Check Function Signature Validity
|
||||||
|
func_sig = set(inspect.signature(method).parameters.keys())
|
||||||
|
|
||||||
|
## Too Little
|
||||||
|
if func_sig != req_params and func_sig.issubset(req_params):
|
||||||
|
msg = f'Decorated method {method.__name__} is missing arguments {req_params - func_sig}'
|
||||||
|
|
||||||
|
## Too Much
|
||||||
|
if func_sig != req_params and func_sig.issuperset(req_params):
|
||||||
|
msg = f'Decorated method {method.__name__} has superfluous arguments {func_sig - req_params}'
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
## Just Right :)
|
||||||
|
|
||||||
|
# TODO: Check Function Annotation Validity
|
||||||
|
# - w/pydantic and/or socket capabilities
|
||||||
|
|
||||||
|
def decorated(node: MaxwellSimNode):
|
||||||
|
# Assemble Keyword Arguments
|
||||||
|
method_kw_args = {}
|
||||||
|
|
||||||
|
## Add Input Sockets
|
||||||
|
if input_sockets:
|
||||||
|
_input_sockets = {
|
||||||
|
input_socket_name: node._compute_input(
|
||||||
|
input_socket_name, kind
|
||||||
|
)
|
||||||
|
for input_socket_name in input_sockets
|
||||||
|
}
|
||||||
|
method_kw_args |= dict(input_sockets=_input_sockets)
|
||||||
|
|
||||||
|
## Add Output Sockets
|
||||||
|
if output_sockets:
|
||||||
|
_output_sockets = {
|
||||||
|
output_socket_name: node.compute_output(
|
||||||
|
output_socket_name, kind
|
||||||
|
)
|
||||||
|
for output_socket_name in output_sockets
|
||||||
|
}
|
||||||
|
method_kw_args |= dict(output_sockets=_output_sockets)
|
||||||
|
|
||||||
|
## Add Loose Sockets
|
||||||
|
if loose_input_sockets:
|
||||||
|
_loose_input_sockets = {
|
||||||
|
input_socket_name: node._compute_input(
|
||||||
|
input_socket_name, kind
|
||||||
|
)
|
||||||
|
for input_socket_name in node.loose_input_sockets
|
||||||
|
}
|
||||||
|
method_kw_args |= dict(
|
||||||
|
loose_input_sockets=_loose_input_sockets
|
||||||
|
)
|
||||||
|
if loose_output_sockets:
|
||||||
|
_loose_output_sockets = {
|
||||||
|
output_socket_name: node.compute_output(
|
||||||
|
output_socket_name, kind
|
||||||
|
)
|
||||||
|
for output_socket_name in node.loose_output_sockets
|
||||||
|
}
|
||||||
|
method_kw_args |= dict(
|
||||||
|
loose_output_sockets=_loose_output_sockets
|
||||||
|
)
|
||||||
|
|
||||||
|
## Add Props
|
||||||
|
if props:
|
||||||
|
_props = {
|
||||||
|
prop_name: getattr(node, prop_name) for prop_name in props
|
||||||
|
}
|
||||||
|
method_kw_args |= dict(props=_props)
|
||||||
|
|
||||||
|
## Add Managed Object
|
||||||
|
if managed_objs:
|
||||||
|
_managed_objs = {
|
||||||
|
managed_obj_name: node.managed_objs[managed_obj_name]
|
||||||
|
for managed_obj_name in managed_objs
|
||||||
|
}
|
||||||
|
method_kw_args |= dict(managed_objs=_managed_objs)
|
||||||
|
|
||||||
|
# Call Method
|
||||||
|
return method(
|
||||||
|
node,
|
||||||
|
**method_kw_args,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set Attributes for Discovery
|
||||||
|
decorated._callback_type = callback_type
|
||||||
|
if index_by:
|
||||||
|
decorated._index_by = index_by
|
||||||
|
if extra_data:
|
||||||
|
decorated._extra_data = extra_data
|
||||||
|
|
||||||
|
return decorated
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Decorator: Output Socket
|
||||||
|
####################
|
||||||
|
def computes_output_socket(
|
||||||
|
output_socket_name: ct.SocketName,
|
||||||
|
kind: ct.DataFlowKind = ct.DataFlowKind.Value,
|
||||||
|
input_sockets: set[str] = set(),
|
||||||
|
props: set[str] = set(),
|
||||||
|
managed_objs: set[str] = set(),
|
||||||
|
cacheable: bool = True,
|
||||||
|
):
|
||||||
|
"""Given a socket name, defines a function-that-makes-a-function (aka.
|
||||||
|
decorator) which has the name of the socket attached.
|
||||||
|
|
||||||
|
Must be used as a decorator, ex. `@compute_output_socket("name")`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
output_socket_name: The name of the output socket to attach the
|
||||||
|
decorated method to.
|
||||||
|
input_sockets: The values of these input sockets will be computed
|
||||||
|
using `_compute_input`, then passed to the decorated function
|
||||||
|
as `input_sockets: list[Any]`. If the input socket doesn't exist (ex. is contained in an inactive loose socket or socket set), then None is returned.
|
||||||
|
managed_objs: These managed objects will be passed to the
|
||||||
|
function as `managed_objs: list[Any]`.
|
||||||
|
kind: Requests for this `output_socket_name, DataFlowKind` pair will
|
||||||
|
be returned by the decorated function.
|
||||||
|
cacheable: The output of th
|
||||||
|
be returned by the decorated function.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The decorator, which takes the output-socket-computing method
|
||||||
|
and returns a new output-socket-computing method, now annotated
|
||||||
|
and discoverable by the `MaxwellSimTreeNode`.
|
||||||
|
"""
|
||||||
|
req_params = (
|
||||||
|
{'self'}
|
||||||
|
| ({'input_sockets'} if input_sockets else set())
|
||||||
|
| ({'props'} if props else set())
|
||||||
|
| ({'managed_objs'} if managed_objs else set())
|
||||||
|
)
|
||||||
|
|
||||||
|
return chain_event_decorator(
|
||||||
|
callback_type='computes_output_socket',
|
||||||
|
index_by=(output_socket_name, kind),
|
||||||
|
kind=kind,
|
||||||
|
input_sockets=input_sockets,
|
||||||
|
props=props,
|
||||||
|
managed_objs=managed_objs,
|
||||||
|
req_params=req_params,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Decorator: On Show Preview
|
||||||
|
####################
|
||||||
|
def on_value_changed(
|
||||||
|
socket_name: set[ct.SocketName] | ct.SocketName | None = None,
|
||||||
|
prop_name: set[str] | str | None = None,
|
||||||
|
any_loose_input_socket: bool = False,
|
||||||
|
kind: ct.DataFlowKind = ct.DataFlowKind.Value,
|
||||||
|
input_sockets: set[str] = set(),
|
||||||
|
props: set[str] = set(),
|
||||||
|
managed_objs: set[str] = set(),
|
||||||
|
):
|
||||||
|
if (
|
||||||
|
sum(
|
||||||
|
[
|
||||||
|
int(socket_name is not None),
|
||||||
|
int(prop_name is not None),
|
||||||
|
int(any_loose_input_socket),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
> 1
|
||||||
|
):
|
||||||
|
msg = 'Define only one of socket_name, prop_name or any_loose_input_socket'
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
req_params = (
|
||||||
|
{'self'}
|
||||||
|
| ({'input_sockets'} if input_sockets else set())
|
||||||
|
| ({'loose_input_sockets'} if any_loose_input_socket else set())
|
||||||
|
| ({'props'} if props else set())
|
||||||
|
| ({'managed_objs'} if managed_objs else set())
|
||||||
|
)
|
||||||
|
|
||||||
|
return chain_event_decorator(
|
||||||
|
callback_type='on_value_changed',
|
||||||
|
extra_data={
|
||||||
|
'changed_sockets': (
|
||||||
|
socket_name if isinstance(socket_name, set) else {socket_name}
|
||||||
|
),
|
||||||
|
'changed_props': (
|
||||||
|
prop_name if isinstance(prop_name, set) else {prop_name}
|
||||||
|
),
|
||||||
|
'changed_loose_input': any_loose_input_socket,
|
||||||
|
},
|
||||||
|
kind=kind,
|
||||||
|
input_sockets=input_sockets,
|
||||||
|
loose_input_sockets=any_loose_input_socket,
|
||||||
|
props=props,
|
||||||
|
managed_objs=managed_objs,
|
||||||
|
req_params=req_params,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def on_show_preview(
|
||||||
|
kind: ct.DataFlowKind = ct.DataFlowKind.Value,
|
||||||
|
input_sockets: set[str] = set(), ## For now, presume only same kind
|
||||||
|
output_sockets: set[str] = set(), ## For now, presume only same kind
|
||||||
|
props: set[str] = set(),
|
||||||
|
managed_objs: set[str] = set(),
|
||||||
|
):
|
||||||
|
req_params = (
|
||||||
|
{'self'}
|
||||||
|
| ({'input_sockets'} if input_sockets else set())
|
||||||
|
| ({'output_sockets'} if output_sockets else set())
|
||||||
|
| ({'props'} if props else set())
|
||||||
|
| ({'managed_objs'} if managed_objs else set())
|
||||||
|
)
|
||||||
|
|
||||||
|
return chain_event_decorator(
|
||||||
|
callback_type='on_show_preview',
|
||||||
|
kind=kind,
|
||||||
|
input_sockets=input_sockets,
|
||||||
|
output_sockets=output_sockets,
|
||||||
|
props=props,
|
||||||
|
managed_objs=managed_objs,
|
||||||
|
req_params=req_params,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def on_show_plot(
|
||||||
|
kind: ct.DataFlowKind = ct.DataFlowKind.Value,
|
||||||
|
input_sockets: set[str] = set(),
|
||||||
|
output_sockets: set[str] = set(),
|
||||||
|
props: set[str] = set(),
|
||||||
|
managed_objs: set[str] = set(),
|
||||||
|
stop_propagation: bool = False,
|
||||||
|
):
|
||||||
|
req_params = (
|
||||||
|
{'self'}
|
||||||
|
| ({'input_sockets'} if input_sockets else set())
|
||||||
|
| ({'output_sockets'} if output_sockets else set())
|
||||||
|
| ({'props'} if props else set())
|
||||||
|
| ({'managed_objs'} if managed_objs else set())
|
||||||
|
)
|
||||||
|
|
||||||
|
return chain_event_decorator(
|
||||||
|
callback_type='on_show_plot',
|
||||||
|
extra_data={
|
||||||
|
'stop_propagation': stop_propagation,
|
||||||
|
},
|
||||||
|
kind=kind,
|
||||||
|
input_sockets=input_sockets,
|
||||||
|
output_sockets=output_sockets,
|
||||||
|
props=props,
|
||||||
|
managed_objs=managed_objs,
|
||||||
|
req_params=req_params,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def on_init(
|
||||||
|
kind: ct.DataFlowKind = ct.DataFlowKind.Value,
|
||||||
|
input_sockets: set[str] = set(),
|
||||||
|
output_sockets: set[str] = set(),
|
||||||
|
props: set[str] = set(),
|
||||||
|
managed_objs: set[str] = set(),
|
||||||
|
):
|
||||||
|
req_params = (
|
||||||
|
{'self'}
|
||||||
|
| ({'input_sockets'} if input_sockets else set())
|
||||||
|
| ({'output_sockets'} if output_sockets else set())
|
||||||
|
| ({'props'} if props else set())
|
||||||
|
| ({'managed_objs'} if managed_objs else set())
|
||||||
|
)
|
||||||
|
|
||||||
|
return chain_event_decorator(
|
||||||
|
callback_type='on_init',
|
||||||
|
kind=kind,
|
||||||
|
input_sockets=input_sockets,
|
||||||
|
output_sockets=output_sockets,
|
||||||
|
props=props,
|
||||||
|
managed_objs=managed_objs,
|
||||||
|
req_params=req_params,
|
||||||
|
)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from . import bound_box, bound_faces
|
from . import bound_box
|
||||||
|
from . import bound_faces
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
*bound_box.BL_REGISTER,
|
*bound_box.BL_REGISTER,
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
|
import sympy as sp
|
||||||
|
import sympy.physics.units as spu
|
||||||
|
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from ... import sockets
|
from ... import sockets
|
||||||
from .. import base, events
|
from .. import base
|
||||||
|
|
||||||
|
|
||||||
class BoundCondsNode(base.MaxwellSimNode):
|
class BoundCondsNode(base.MaxwellSimNode):
|
||||||
|
@ -28,7 +30,7 @@ class BoundCondsNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - Output Socket Computation
|
# - Output Socket Computation
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@base.computes_output_socket(
|
||||||
'BCs', input_sockets={'+X', '-X', '+Y', '-Y', '+Z', '-Z'}
|
'BCs', input_sockets={'+X', '-X', '+Y', '-Y', '+Z', '-Z'}
|
||||||
)
|
)
|
||||||
def compute_simulation(self, input_sockets) -> td.BoundarySpec:
|
def compute_simulation(self, input_sockets) -> td.BoundarySpec:
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
from . import (
|
from . import pml_bound_face
|
||||||
absorbing_bound_face,
|
from . import pec_bound_face
|
||||||
bloch_bound_face,
|
from . import pmc_bound_face
|
||||||
pec_bound_face,
|
|
||||||
periodic_bound_face,
|
from . import bloch_bound_face
|
||||||
pmc_bound_face,
|
from . import periodic_bound_face
|
||||||
pml_bound_face,
|
from . import absorbing_bound_face
|
||||||
)
|
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
*pml_bound_face.BL_REGISTER,
|
*pml_bound_face.BL_REGISTER,
|
||||||
|
|
|
@ -1,294 +0,0 @@
|
||||||
import enum
|
|
||||||
import inspect
|
|
||||||
import typing as typ
|
|
||||||
from types import MappingProxyType
|
|
||||||
|
|
||||||
from ....utils import extra_sympy_units as spux
|
|
||||||
from ....utils import logger
|
|
||||||
from .. import contracts as ct
|
|
||||||
from .base import MaxwellSimNode
|
|
||||||
|
|
||||||
log = logger.get(__name__)
|
|
||||||
|
|
||||||
UnitSystemID = str
|
|
||||||
UnitSystem = dict[ct.SocketType, typ.Any]
|
|
||||||
|
|
||||||
|
|
||||||
class EventCallbackType(enum.StrEnum):
|
|
||||||
"""Names of actions that support callbacks."""
|
|
||||||
|
|
||||||
computes_output_socket = enum.auto()
|
|
||||||
on_value_changed = enum.auto()
|
|
||||||
on_show_plot = enum.auto()
|
|
||||||
on_init = enum.auto()
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Event Callback Information
|
|
||||||
####################
|
|
||||||
class EventCallbackData_ComputesOutputSocket(typ.TypedDict): # noqa: N801
|
|
||||||
"""Extra data used to select a method to compute output sockets."""
|
|
||||||
|
|
||||||
output_socket_name: ct.SocketName
|
|
||||||
kind: ct.DataFlowKind
|
|
||||||
|
|
||||||
|
|
||||||
class EventCallbackData_OnValueChanged(typ.TypedDict): # noqa: N801
|
|
||||||
"""Extra data used to select a method to compute output sockets."""
|
|
||||||
|
|
||||||
changed_sockets: set[ct.SocketName]
|
|
||||||
changed_props: set[str]
|
|
||||||
changed_loose_input: set[str]
|
|
||||||
|
|
||||||
|
|
||||||
class EventCallbackData_OnShowPlot(typ.TypedDict): # noqa: N801
|
|
||||||
"""Extra data in the callback, used when showing a plot."""
|
|
||||||
|
|
||||||
stop_propagation: bool
|
|
||||||
|
|
||||||
|
|
||||||
class EventCallbackData_OnInit(typ.TypedDict): # noqa: D101, N801
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
EventCallbackData: typ.TypeAlias = (
|
|
||||||
EventCallbackData_ComputesOutputSocket
|
|
||||||
| EventCallbackData_OnValueChanged
|
|
||||||
| EventCallbackData_OnShowPlot
|
|
||||||
| EventCallbackData_OnInit
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Event Decorator
|
|
||||||
####################
|
|
||||||
ManagedObjName: typ.TypeAlias = str
|
|
||||||
PropName: typ.TypeAlias = str
|
|
||||||
|
|
||||||
|
|
||||||
def event_decorator(
|
|
||||||
action_type: EventCallbackType,
|
|
||||||
extra_data: EventCallbackData,
|
|
||||||
kind: ct.DataFlowKind = ct.DataFlowKind.Value,
|
|
||||||
props: set[PropName] = frozenset(),
|
|
||||||
managed_objs: set[ManagedObjName] = frozenset(),
|
|
||||||
input_sockets: set[ct.SocketName] = frozenset(),
|
|
||||||
output_sockets: set[ct.SocketName] = frozenset(),
|
|
||||||
all_loose_input_sockets: bool = False,
|
|
||||||
all_loose_output_sockets: bool = False,
|
|
||||||
unit_systems: dict[UnitSystemID, UnitSystem] = MappingProxyType({}),
|
|
||||||
scale_input_sockets: dict[ct.SocketName, UnitSystemID] = MappingProxyType({}),
|
|
||||||
scale_output_sockets: dict[ct.SocketName, UnitSystemID] = MappingProxyType({}),
|
|
||||||
):
|
|
||||||
"""Returns a decorator for a method of `MaxwellSimNode`, declaring it as able respond to events passing through a node.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
action_type: A name describing which event the decorator should respond to.
|
|
||||||
Set to `return_method.action_type`
|
|
||||||
extra_data: A dictionary that provides the caller with additional per-`action_type` information.
|
|
||||||
This might include parameters to help select the most appropriate method(s) to respond to an event with, or actions to take after running the callback.
|
|
||||||
kind: The `ct.DataFlowKind` used to compute all input and output socket data for methods with.
|
|
||||||
Only affects data passed to the decorated method; namely `input_sockets`, `output_sockets`, and their loose variants.
|
|
||||||
props: Set of `props` to compute, then pass to the decorated method.
|
|
||||||
managed_objs: Set of `managed_objs` to retrieve, then pass to the decorated method.
|
|
||||||
input_sockets: Set of `input_sockets` to compute, then pass to the decorated method.
|
|
||||||
output_sockets: Set of `output_sockets` to compute, then pass to the decorated method.
|
|
||||||
all_loose_input_sockets: Whether to compute all loose input sockets and pass them to the decorated method.
|
|
||||||
Used when the names of the loose input sockets are unknown, but all of their values are needed.
|
|
||||||
all_loose_output_sockets: Whether to compute all loose output sockets and pass them to the decorated method.
|
|
||||||
Used when the names of the loose output sockets are unknown, but all of their values are needed.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A decorator, which can be applied to a method of `MaxwellSimNode`.
|
|
||||||
When a `MaxwellSimNode` subclass initializes, such a decorated method will be picked up on.
|
|
||||||
|
|
||||||
When the `action_type` action passes through the node, then `extra_data` is used to determine
|
|
||||||
"""
|
|
||||||
req_params = (
|
|
||||||
{'self'}
|
|
||||||
| ({'props'} if props else set())
|
|
||||||
| ({'managed_objs'} if managed_objs else set())
|
|
||||||
| ({'input_sockets'} if input_sockets else set())
|
|
||||||
| ({'output_sockets'} if output_sockets else set())
|
|
||||||
| ({'loose_input_sockets'} if all_loose_input_sockets else set())
|
|
||||||
| ({'loose_output_sockets'} if all_loose_output_sockets else set())
|
|
||||||
| ({'unit_systems'} if unit_systems else set())
|
|
||||||
)
|
|
||||||
|
|
||||||
# TODO: Check that all Unit System IDs referenced are also defined in 'unit_systems'.
|
|
||||||
## TODO: More ex. introspective checks and such, to make it really hard to write invalid methods.
|
|
||||||
|
|
||||||
def decorator(method: typ.Callable) -> typ.Callable:
|
|
||||||
# Check Function Signature Validity
|
|
||||||
func_sig = set(inspect.signature(method).parameters.keys())
|
|
||||||
|
|
||||||
## Too Few Arguments
|
|
||||||
if func_sig != req_params and func_sig.issubset(req_params):
|
|
||||||
msg = f'Decorated method {method.__name__} is missing arguments {req_params - func_sig}'
|
|
||||||
|
|
||||||
## Too Many Arguments
|
|
||||||
if func_sig != req_params and func_sig.issuperset(req_params):
|
|
||||||
msg = f'Decorated method {method.__name__} has superfluous arguments {func_sig - req_params}'
|
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
# TODO: Check Function Annotation Validity
|
|
||||||
## - socket capabilities
|
|
||||||
|
|
||||||
def decorated(node: MaxwellSimNode):
|
|
||||||
method_kw_args = {} ## Keyword Arguments for Decorated Method
|
|
||||||
|
|
||||||
# Compute Requested Props
|
|
||||||
if props:
|
|
||||||
_props = {prop_name: getattr(node, prop_name) for prop_name in props}
|
|
||||||
method_kw_args |= {'props': _props}
|
|
||||||
|
|
||||||
# Retrieve Requested Managed Objects
|
|
||||||
if managed_objs:
|
|
||||||
_managed_objs = {
|
|
||||||
managed_obj_name: node.managed_objs[managed_obj_name]
|
|
||||||
for managed_obj_name in managed_objs
|
|
||||||
}
|
|
||||||
method_kw_args |= {'managed_objs': _managed_objs}
|
|
||||||
|
|
||||||
# Requested Sockets
|
|
||||||
## Compute Requested Input Sockets
|
|
||||||
if input_sockets:
|
|
||||||
_input_sockets = {
|
|
||||||
input_socket_name: node._compute_input(input_socket_name, kind)
|
|
||||||
for input_socket_name in input_sockets
|
|
||||||
}
|
|
||||||
|
|
||||||
# Scale Specified Input Sockets to Unit System
|
|
||||||
## First, scale the input socket value to the given unit system
|
|
||||||
## Then, convert the symbol-less sympy scalar to a python type.
|
|
||||||
for input_socket_name, unit_system_id in scale_input_sockets.items():
|
|
||||||
unit_system = unit_systems[unit_system_id]
|
|
||||||
_input_sockets[input_socket_name] = spux.sympy_to_python(
|
|
||||||
spux.scale_to_unit(
|
|
||||||
_input_sockets[input_socket_name],
|
|
||||||
unit_system[node.inputs[input_socket_name].socket_type],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
method_kw_args |= {'input_sockets': _input_sockets}
|
|
||||||
|
|
||||||
## Compute Requested Output Sockets
|
|
||||||
if output_sockets:
|
|
||||||
_output_sockets = {
|
|
||||||
output_socket_name: node.compute_output(output_socket_name, kind)
|
|
||||||
for output_socket_name in output_sockets
|
|
||||||
}
|
|
||||||
|
|
||||||
# Scale Specified Output Sockets to Unit System
|
|
||||||
## First, scale the output socket value to the given unit system
|
|
||||||
## Then, convert the symbol-less sympy scalar to a python type.
|
|
||||||
for output_socket_name, unit_system_id in scale_output_sockets.items():
|
|
||||||
unit_system = unit_systems[unit_system_id]
|
|
||||||
_output_sockets[output_socket_name] = spux.sympy_to_python(
|
|
||||||
spux.scale_to_unit(
|
|
||||||
_output_sockets[output_socket_name],
|
|
||||||
unit_system[node.outputs[output_socket_name].socket_type],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
method_kw_args |= {'output_sockets': _output_sockets}
|
|
||||||
|
|
||||||
# Loose Sockets
|
|
||||||
## Compute All Loose Input Sockets
|
|
||||||
if all_loose_input_sockets:
|
|
||||||
_loose_input_sockets = {
|
|
||||||
input_socket_name: node._compute_input(input_socket_name, kind)
|
|
||||||
for input_socket_name in node.loose_input_sockets
|
|
||||||
}
|
|
||||||
method_kw_args |= {'loose_input_sockets': _loose_input_sockets}
|
|
||||||
|
|
||||||
## Compute All Loose Output Sockets
|
|
||||||
if all_loose_output_sockets:
|
|
||||||
_loose_output_sockets = {
|
|
||||||
output_socket_name: node.compute_output(output_socket_name, kind)
|
|
||||||
for output_socket_name in node.loose_output_sockets
|
|
||||||
}
|
|
||||||
method_kw_args |= {'loose_output_sockets': _loose_output_sockets}
|
|
||||||
|
|
||||||
# Unit Systems
|
|
||||||
if unit_systems:
|
|
||||||
method_kw_args |= {'unit_systems': unit_systems}
|
|
||||||
|
|
||||||
# Call Method
|
|
||||||
return method(
|
|
||||||
node,
|
|
||||||
**method_kw_args,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set Decorated Attributes and Return
|
|
||||||
## Fix Introspection + Documentation
|
|
||||||
decorated.__name__ = method.__name__
|
|
||||||
decorated.__module__ = method.__module__
|
|
||||||
decorated.__qualname__ = method.__qualname__
|
|
||||||
decorated.__doc__ = method.__doc__
|
|
||||||
|
|
||||||
## Add Spice
|
|
||||||
decorated.action_type = action_type
|
|
||||||
decorated.extra_data = extra_data
|
|
||||||
|
|
||||||
return decorated
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Simplified Event Callbacks
|
|
||||||
####################
|
|
||||||
def computes_output_socket(
|
|
||||||
output_socket_name: ct.SocketName,
|
|
||||||
kind: ct.DataFlowKind = ct.DataFlowKind.Value,
|
|
||||||
**kwargs,
|
|
||||||
):
|
|
||||||
return event_decorator(
|
|
||||||
action_type='computes_output_socket',
|
|
||||||
extra_data={
|
|
||||||
'output_socket_name': output_socket_name,
|
|
||||||
'kind': kind,
|
|
||||||
},
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
## TODO: Consider changing socket_name and prop_name to more obvious names.
|
|
||||||
def on_value_changed(
|
|
||||||
socket_name: set[ct.SocketName] | ct.SocketName | None = None,
|
|
||||||
prop_name: set[str] | str | None = None,
|
|
||||||
any_loose_input_socket: bool = False,
|
|
||||||
**kwargs,
|
|
||||||
):
|
|
||||||
return event_decorator(
|
|
||||||
action_type=EventCallbackType.on_value_changed,
|
|
||||||
extra_data={
|
|
||||||
'changed_sockets': (
|
|
||||||
socket_name if isinstance(socket_name, set) else {socket_name}
|
|
||||||
),
|
|
||||||
'changed_props': (prop_name if isinstance(prop_name, set) else {prop_name}),
|
|
||||||
'changed_loose_input': any_loose_input_socket,
|
|
||||||
},
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def on_show_plot(
|
|
||||||
stop_propagation: bool = False,
|
|
||||||
**kwargs,
|
|
||||||
):
|
|
||||||
return event_decorator(
|
|
||||||
action_type=EventCallbackType.on_show_plot,
|
|
||||||
extra_data={
|
|
||||||
'stop_propagation': stop_propagation,
|
|
||||||
},
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def on_init(**kwargs):
|
|
||||||
return event_decorator(
|
|
||||||
action_type=EventCallbackType.on_init,
|
|
||||||
extra_data={},
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
|
@ -1,6 +1,8 @@
|
||||||
# from . import scientific_constant
|
# from . import scientific_constant
|
||||||
|
from . import number_constant
|
||||||
|
|
||||||
# from . import physical_constant
|
# from . import physical_constant
|
||||||
from . import blender_constant, number_constant
|
from . import blender_constant
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
# *scientific_constant.BL_REGISTER,
|
# *scientific_constant.BL_REGISTER,
|
||||||
|
|
|
@ -2,14 +2,14 @@ import typing as typ
|
||||||
|
|
||||||
from .... import contracts as ct
|
from .... import contracts as ct
|
||||||
from .... import sockets
|
from .... import sockets
|
||||||
from ... import base, events
|
from ... import base
|
||||||
|
|
||||||
|
|
||||||
class BlenderConstantNode(base.MaxwellSimNode):
|
class BlenderConstantNode(base.MaxwellSimNode):
|
||||||
node_type = ct.NodeType.BlenderConstant
|
node_type = ct.NodeType.BlenderConstant
|
||||||
bl_label = 'Blender Constant'
|
bl_label = 'Blender Constant'
|
||||||
|
|
||||||
input_socket_sets: typ.ClassVar = {
|
input_socket_sets = {
|
||||||
'Object': {
|
'Object': {
|
||||||
'Value': sockets.BlenderObjectSocketDef(),
|
'Value': sockets.BlenderObjectSocketDef(),
|
||||||
},
|
},
|
||||||
|
@ -31,7 +31,7 @@ class BlenderConstantNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - Callbacks
|
# - Callbacks
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket('Value', input_sockets={'Value'})
|
@base.computes_output_socket('Value', input_sockets={'Value'})
|
||||||
def compute_value(self, input_sockets) -> typ.Any:
|
def compute_value(self, input_sockets) -> typ.Any:
|
||||||
return input_sockets['Value']
|
return input_sockets['Value']
|
||||||
|
|
||||||
|
@ -42,4 +42,6 @@ class BlenderConstantNode(base.MaxwellSimNode):
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
BlenderConstantNode,
|
BlenderConstantNode,
|
||||||
]
|
]
|
||||||
BL_NODES = {ct.NodeType.BlenderConstant: (ct.NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS)}
|
BL_NODES = {
|
||||||
|
ct.NodeType.BlenderConstant: (ct.NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS)
|
||||||
|
}
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import sympy as sp
|
||||||
|
|
||||||
from .... import contracts as ct
|
from .... import contracts as ct
|
||||||
from .... import sockets
|
from .... import sockets
|
||||||
from ... import base, events
|
from ... import base
|
||||||
|
|
||||||
|
|
||||||
class NumberConstantNode(base.MaxwellSimNode):
|
class NumberConstantNode(base.MaxwellSimNode):
|
||||||
node_type = ct.NodeType.NumberConstant
|
node_type = ct.NodeType.NumberConstant
|
||||||
bl_label = 'Numerical Constant'
|
bl_label = 'Numerical Constant'
|
||||||
|
|
||||||
input_socket_sets: typ.ClassVar = {
|
input_socket_sets = {
|
||||||
'Integer': {
|
'Integer': {
|
||||||
'Value': sockets.IntegerNumberSocketDef(),
|
'Value': sockets.IntegerNumberSocketDef(),
|
||||||
},
|
},
|
||||||
|
@ -28,7 +31,7 @@ class NumberConstantNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - Callbacks
|
# - Callbacks
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket('Value', input_sockets={'Value'})
|
@base.computes_output_socket('Value', input_sockets={'Value'})
|
||||||
def compute_value(self, input_sockets) -> typ.Any:
|
def compute_value(self, input_sockets) -> typ.Any:
|
||||||
return input_sockets['Value']
|
return input_sockets['Value']
|
||||||
|
|
||||||
|
@ -39,4 +42,6 @@ class NumberConstantNode(base.MaxwellSimNode):
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
NumberConstantNode,
|
NumberConstantNode,
|
||||||
]
|
]
|
||||||
BL_NODES = {ct.NodeType.NumberConstant: (ct.NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS)}
|
BL_NODES = {
|
||||||
|
ct.NodeType.NumberConstant: (ct.NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS)
|
||||||
|
}
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
import typing as typ
|
import bpy
|
||||||
|
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
|
|
||||||
from .... import contracts, sockets
|
from .... import contracts
|
||||||
from ... import base, events
|
from .... import sockets
|
||||||
|
from ... import base
|
||||||
|
|
||||||
|
|
||||||
class PhysicalConstantNode(base.MaxwellSimTreeNode):
|
class PhysicalConstantNode(base.MaxwellSimTreeNode):
|
||||||
node_type = contracts.NodeType.PhysicalConstant
|
node_type = contracts.NodeType.PhysicalConstant
|
||||||
bl_label = 'Physical Constant'
|
|
||||||
|
|
||||||
input_socket_sets: typ.ClassVar = {
|
bl_label = 'Physical Constant'
|
||||||
|
# bl_icon = constants.ICON_SIM_INPUT
|
||||||
|
|
||||||
|
input_sockets = {}
|
||||||
|
input_socket_sets = {
|
||||||
'time': {
|
'time': {
|
||||||
'value': sockets.PhysicalTimeSocketDef(
|
'value': sockets.PhysicalTimeSocketDef(
|
||||||
label='Time',
|
label='Time',
|
||||||
|
@ -48,12 +51,13 @@ class PhysicalConstantNode(base.MaxwellSimTreeNode):
|
||||||
},
|
},
|
||||||
## I got bored so maybe the rest later
|
## I got bored so maybe the rest later
|
||||||
}
|
}
|
||||||
output_socket_sets: typ.ClassVar = input_socket_sets
|
output_sockets = {}
|
||||||
|
output_socket_sets = input_socket_sets
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Callbacks
|
# - Callbacks
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket('value')
|
@base.computes_output_socket('value')
|
||||||
def compute_value(self: contracts.NodeTypeProtocol) -> sp.Expr:
|
def compute_value(self: contracts.NodeTypeProtocol) -> sp.Expr:
|
||||||
return self.compute_input('value')
|
return self.compute_input('value')
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
## TODO: Discover dropdown options from sci_constants.py
|
|
||||||
####################
|
####################
|
||||||
# - Blender Registration
|
# - Blender Registration
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from ... import sockets
|
from ... import sockets
|
||||||
from .. import base, events
|
from .. import base
|
||||||
|
|
||||||
|
|
||||||
class PhysicalUnitSystemNode(base.MaxwellSimNode):
|
class PhysicalUnitSystemNode(base.MaxwellSimNode):
|
||||||
node_type = ct.NodeType.UnitSystem
|
node_type = ct.NodeType.UnitSystem
|
||||||
|
@ -19,7 +18,7 @@ class PhysicalUnitSystemNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - Callbacks
|
# - Callbacks
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@base.computes_output_socket(
|
||||||
'Unit System',
|
'Unit System',
|
||||||
input_sockets={'Unit System'},
|
input_sockets={'Unit System'},
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,20 +1,22 @@
|
||||||
import typing as typ
|
import bpy
|
||||||
|
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
import sympy.physics.units as spu
|
import sympy.physics.units as spu
|
||||||
|
import scipy as sc
|
||||||
|
|
||||||
from .....utils import extra_sympy_units as spux
|
from .....utils import extra_sympy_units as spux
|
||||||
from .....utils import sci_constants as constants
|
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from ... import sockets
|
from ... import sockets
|
||||||
from .. import base, events
|
from .. import base
|
||||||
|
|
||||||
|
VAC_SPEED_OF_LIGHT = sc.constants.speed_of_light * spu.meter / spu.second
|
||||||
|
|
||||||
|
|
||||||
class WaveConstantNode(base.MaxwellSimNode):
|
class WaveConstantNode(base.MaxwellSimNode):
|
||||||
node_type = ct.NodeType.WaveConstant
|
node_type = ct.NodeType.WaveConstant
|
||||||
|
|
||||||
bl_label = 'Wave Constant'
|
bl_label = 'Wave Constant'
|
||||||
|
|
||||||
input_socket_sets: typ.ClassVar = {
|
input_socket_sets = {
|
||||||
# Single
|
# Single
|
||||||
'Vacuum WL': {
|
'Vacuum WL': {
|
||||||
'WL': sockets.PhysicalLengthSocketDef(
|
'WL': sockets.PhysicalLengthSocketDef(
|
||||||
|
@ -42,68 +44,85 @@ class WaveConstantNode(base.MaxwellSimNode):
|
||||||
}
|
}
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Event Methods: Listy Output
|
# - Callbacks
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@base.computes_output_socket(
|
||||||
'WL',
|
'WL',
|
||||||
input_sockets={'WL', 'Freq'},
|
input_sockets={'WL', 'Freq'},
|
||||||
)
|
)
|
||||||
def compute_vac_wl(self, input_sockets: dict) -> sp.Expr:
|
def compute_vac_wl(self, input_sockets: dict) -> sp.Expr:
|
||||||
if (vac_wl := input_sockets['WL']) is not None:
|
if (vac_wl := input_sockets['WL']) is not None:
|
||||||
return vac_wl
|
return vac_wl
|
||||||
if (freq := input_sockets['Freq']) is not None:
|
|
||||||
return constants.vac_speed_of_light / freq
|
|
||||||
|
|
||||||
msg = 'Vac WL and Freq are both None'
|
elif (freq := input_sockets['Freq']) is not None:
|
||||||
raise RuntimeError(msg)
|
return spu.convert_to(
|
||||||
|
VAC_SPEED_OF_LIGHT / freq,
|
||||||
|
spu.meter,
|
||||||
|
)
|
||||||
|
|
||||||
@events.computes_output_socket(
|
raise RuntimeError('Vac WL and Freq are both None')
|
||||||
|
|
||||||
|
@base.computes_output_socket(
|
||||||
'Freq',
|
'Freq',
|
||||||
input_sockets={'WL', 'Freq'},
|
input_sockets={'WL', 'Freq'},
|
||||||
)
|
)
|
||||||
def compute_freq(self, input_sockets: dict) -> sp.Expr:
|
def compute_freq(self, input_sockets: dict) -> sp.Expr:
|
||||||
if (vac_wl := input_sockets['WL']) is not None:
|
if (vac_wl := input_sockets['WL']) is not None:
|
||||||
return constants.vac_speed_of_light / vac_wl
|
return spu.convert_to(
|
||||||
if (freq := input_sockets['Freq']) is not None:
|
VAC_SPEED_OF_LIGHT / vac_wl,
|
||||||
|
spu.hertz,
|
||||||
|
)
|
||||||
|
elif (freq := input_sockets['Freq']) is not None:
|
||||||
return freq
|
return freq
|
||||||
|
|
||||||
msg = 'Vac WL and Freq are both None'
|
raise RuntimeError('Vac WL and Freq are both None')
|
||||||
raise RuntimeError(msg)
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Event Methods: Listy Output
|
# - Listy Callbacks
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@base.computes_output_socket(
|
||||||
'WLs',
|
'WLs',
|
||||||
input_sockets={'WLs', 'Freqs'},
|
input_sockets={'WLs', 'Freqs'},
|
||||||
)
|
)
|
||||||
def compute_vac_wls(self, input_sockets: dict) -> sp.Expr:
|
def compute_vac_wls(self, input_sockets: dict) -> sp.Expr:
|
||||||
if (vac_wls := input_sockets['WLs']) is not None:
|
if (vac_wls := input_sockets['WLs']) is not None:
|
||||||
return vac_wls
|
return vac_wls
|
||||||
if (freqs := input_sockets['Freqs']) is not None:
|
elif (freqs := input_sockets['Freqs']) is not None:
|
||||||
return [constants.vac_speed_of_light / freq for freq in freqs][::-1]
|
return [
|
||||||
|
spu.convert_to(
|
||||||
|
VAC_SPEED_OF_LIGHT / freq,
|
||||||
|
spu.meter,
|
||||||
|
)
|
||||||
|
for freq in freqs
|
||||||
|
][::-1]
|
||||||
|
|
||||||
msg = 'Vac WL and Freq are both None'
|
raise RuntimeError('Vac WLs and Freqs are both None')
|
||||||
raise RuntimeError(msg)
|
|
||||||
|
|
||||||
@events.computes_output_socket(
|
@base.computes_output_socket(
|
||||||
'Freqs',
|
'Freqs',
|
||||||
input_sockets={'WLs', 'Freqs'},
|
input_sockets={'WLs', 'Freqs'},
|
||||||
)
|
)
|
||||||
def compute_freqs(self, input_sockets: dict) -> sp.Expr:
|
def compute_freqs(self, input_sockets: dict) -> sp.Expr:
|
||||||
if (vac_wls := input_sockets['WLs']) is not None:
|
if (vac_wls := input_sockets['WLs']) is not None:
|
||||||
return [constants.vac_speed_of_light / vac_wl for vac_wl in vac_wls][::-1]
|
return [
|
||||||
if (freqs := input_sockets['Freqs']) is not None:
|
spu.convert_to(
|
||||||
|
VAC_SPEED_OF_LIGHT / vac_wl,
|
||||||
|
spu.hertz,
|
||||||
|
)
|
||||||
|
for vac_wl in vac_wls
|
||||||
|
][::-1]
|
||||||
|
elif (freqs := input_sockets['Freqs']) is not None:
|
||||||
return freqs
|
return freqs
|
||||||
|
|
||||||
msg = 'Vac WL and Freq are both None'
|
raise RuntimeError('Vac WLs and Freqs are both None')
|
||||||
raise RuntimeError(msg)
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Event Methods
|
# - Callbacks
|
||||||
####################
|
####################
|
||||||
@events.on_value_changed(prop_name='active_socket_set', props={'active_socket_set'})
|
@base.on_value_changed(
|
||||||
def on_active_socket_set_changed(self, props: dict):
|
prop_name='active_socket_set', props={'active_socket_set'}
|
||||||
|
)
|
||||||
|
def on_value_changed__active_socket_set(self, props: dict):
|
||||||
# Singular: Normal Output Sockets
|
# Singular: Normal Output Sockets
|
||||||
if props['active_socket_set'] in {'Vacuum WL', 'Frequency'}:
|
if props['active_socket_set'] in {'Vacuum WL', 'Frequency'}:
|
||||||
self.loose_output_sockets = {}
|
self.loose_output_sockets = {}
|
||||||
|
@ -124,9 +143,9 @@ class WaveConstantNode(base.MaxwellSimNode):
|
||||||
msg = f"Active socket set invalid for wave constant: {props['active_socket_set']}"
|
msg = f"Active socket set invalid for wave constant: {props['active_socket_set']}"
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
@events.on_init()
|
@base.on_init()
|
||||||
def on_init(self):
|
def on_init(self):
|
||||||
self.on_active_socket_set_changed()
|
self.on_value_changed__active_socket_set()
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -1,20 +1,15 @@
|
||||||
import typing as typ
|
import tempfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from ...... import info
|
import tidy3d as td
|
||||||
|
import tidy3d.web as td_web
|
||||||
|
|
||||||
from ......services import tdcloud
|
from ......services import tdcloud
|
||||||
from .... import contracts as ct
|
from .... import contracts as ct
|
||||||
from .... import sockets
|
from .... import sockets
|
||||||
from ... import base, events
|
from ... import base
|
||||||
|
|
||||||
|
CACHE = {}
|
||||||
def _sim_data_cache_path(task_id: str) -> Path:
|
|
||||||
"""Compute an appropriate location for caching simulations downloaded from the internet, unique to each task ID.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
task_id: The ID of the Tidy3D cloud task.
|
|
||||||
"""
|
|
||||||
return info.ADDON_CACHE / task_id / 'sim_data.hdf5'
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -24,42 +19,87 @@ class Tidy3DWebImporterNode(base.MaxwellSimNode):
|
||||||
node_type = ct.NodeType.Tidy3DWebImporter
|
node_type = ct.NodeType.Tidy3DWebImporter
|
||||||
bl_label = 'Tidy3DWebImporter'
|
bl_label = 'Tidy3DWebImporter'
|
||||||
|
|
||||||
input_sockets: typ.ClassVar = {
|
input_sockets = {
|
||||||
'Cloud Task': sockets.Tidy3DCloudTaskSocketDef(
|
'Cloud Task': sockets.Tidy3DCloudTaskSocketDef(
|
||||||
should_exist=True,
|
should_exist=True,
|
||||||
),
|
),
|
||||||
|
'Cache Path': sockets.FilePathSocketDef(
|
||||||
|
default_path=Path('loaded_simulation.hdf5')
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Event Methods
|
# - Output Methods
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@base.computes_output_socket(
|
||||||
'FDTD Sim Data',
|
'FDTD Sim Data',
|
||||||
|
input_sockets={'Cloud Task', 'Cache Path'},
|
||||||
|
)
|
||||||
|
def compute_fdtd_sim_data(self, input_sockets: dict) -> str:
|
||||||
|
global CACHE
|
||||||
|
if not CACHE.get(self.instance_id):
|
||||||
|
CACHE[self.instance_id] = {'fdtd_sim_data': None}
|
||||||
|
|
||||||
|
if CACHE[self.instance_id]['fdtd_sim_data'] is not None:
|
||||||
|
return CACHE[self.instance_id]['fdtd_sim_data']
|
||||||
|
|
||||||
|
if not (
|
||||||
|
(cloud_task := input_sockets['Cloud Task']) is not None
|
||||||
|
and isinstance(cloud_task, tdcloud.CloudTask)
|
||||||
|
and cloud_task.status == 'success'
|
||||||
|
):
|
||||||
|
msg = "Won't attempt getting SimData"
|
||||||
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
|
# Load the Simulation
|
||||||
|
cache_path = input_sockets['Cache Path']
|
||||||
|
if cache_path is None:
|
||||||
|
print('CACHE PATH IS NONE WHY')
|
||||||
|
return ## I guess?
|
||||||
|
if cache_path.is_file():
|
||||||
|
sim_data = td.SimulationData.from_file(str(cache_path))
|
||||||
|
|
||||||
|
else:
|
||||||
|
sim_data = td_web.api.webapi.load(
|
||||||
|
cloud_task.task_id,
|
||||||
|
path=str(cache_path),
|
||||||
|
)
|
||||||
|
|
||||||
|
CACHE[self.instance_id]['fdtd_sim_data'] = sim_data
|
||||||
|
return sim_data
|
||||||
|
|
||||||
|
@base.computes_output_socket(
|
||||||
|
'FDTD Sim',
|
||||||
input_sockets={'Cloud Task'},
|
input_sockets={'Cloud Task'},
|
||||||
)
|
)
|
||||||
def compute_sim_data(self, input_sockets: dict) -> str:
|
def compute_fdtd_sim(self, input_sockets: dict) -> str:
|
||||||
# Validate Task Availability
|
if not isinstance(
|
||||||
if (cloud_task := input_sockets['Cloud Task']) is None:
|
cloud_task := input_sockets['Cloud Task'], tdcloud.CloudTask
|
||||||
msg = f'"{self.bl_label}" CloudTask doesn\'t exist'
|
):
|
||||||
|
msg = 'Input cloud task does not exist'
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
# Validate Task Existence
|
# Load the Simulation
|
||||||
if not isinstance(cloud_task, tdcloud.CloudTask):
|
with tempfile.NamedTemporaryFile(delete=False) as f:
|
||||||
msg = f'"{self.bl_label}" CloudTask input "{cloud_task}" has wrong "should_exists", as it isn\'t an instance of tdcloud.CloudTask'
|
_path_tmp = Path(f.name)
|
||||||
raise TypeError(msg)
|
_path_tmp.rename(f.name + '.json')
|
||||||
|
path_tmp = Path(f.name + '.json')
|
||||||
|
|
||||||
# Validate Task Status
|
sim = td_web.api.webapi.load_simulation(
|
||||||
if cloud_task.status != 'success':
|
cloud_task.task_id,
|
||||||
msg = f'"{self.bl_label}" CloudTask is "{cloud_task.status}", not "success"'
|
path=str(path_tmp),
|
||||||
raise RuntimeError(msg)
|
) ## TODO: Don't use td_web directly. Only through tdcloud
|
||||||
|
Path(path_tmp).unlink()
|
||||||
|
|
||||||
# Download and Return SimData
|
return sim
|
||||||
return tdcloud.TidyCloudTasks.download_task_sim_data(
|
|
||||||
cloud_task, _sim_data_cache_path(cloud_task.task_id)
|
####################
|
||||||
|
# - Update
|
||||||
|
####################
|
||||||
|
@base.on_value_changed(
|
||||||
|
socket_name='Cloud Task', input_sockets={'Cloud Task'}
|
||||||
)
|
)
|
||||||
|
def on_value_changed__cloud_task(self, input_sockets: dict):
|
||||||
@events.on_value_changed(socket_name='Cloud Task', input_sockets={'Cloud Task'})
|
|
||||||
def on_cloud_task_changed(self, input_sockets: dict):
|
|
||||||
if (
|
if (
|
||||||
(cloud_task := input_sockets['Cloud Task']) is not None
|
(cloud_task := input_sockets['Cloud Task']) is not None
|
||||||
and isinstance(cloud_task, tdcloud.CloudTask)
|
and isinstance(cloud_task, tdcloud.CloudTask)
|
||||||
|
@ -67,13 +107,15 @@ class Tidy3DWebImporterNode(base.MaxwellSimNode):
|
||||||
):
|
):
|
||||||
self.loose_output_sockets = {
|
self.loose_output_sockets = {
|
||||||
'FDTD Sim Data': sockets.MaxwellFDTDSimDataSocketDef(),
|
'FDTD Sim Data': sockets.MaxwellFDTDSimDataSocketDef(),
|
||||||
|
'FDTD Sim': sockets.MaxwellFDTDSimSocketDef(),
|
||||||
}
|
}
|
||||||
else:
|
return
|
||||||
|
|
||||||
self.loose_output_sockets = {}
|
self.loose_output_sockets = {}
|
||||||
|
|
||||||
@events.on_init()
|
@base.on_init()
|
||||||
def on_init(self):
|
def on_init(self):
|
||||||
self.on_cloud_task_changed()
|
self.on_value_changed__cloud_task()
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -83,5 +125,7 @@ BL_REGISTER = [
|
||||||
Tidy3DWebImporterNode,
|
Tidy3DWebImporterNode,
|
||||||
]
|
]
|
||||||
BL_NODES = {
|
BL_NODES = {
|
||||||
ct.NodeType.Tidy3DWebImporter: (ct.NodeCategory.MAXWELLSIM_INPUTS_IMPORTERS)
|
ct.NodeType.Tidy3DWebImporter: (
|
||||||
|
ct.NodeCategory.MAXWELLSIM_INPUTS_IMPORTERS
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import tidy3d as td
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
|
import sympy.physics.units as spu
|
||||||
|
|
||||||
from .. import contracts as ct
|
from .. import contracts as ct
|
||||||
from .. import sockets
|
from .. import sockets
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import sympy.physics.units as spu
|
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
|
import sympy as sp
|
||||||
|
import sympy.physics.units as spu
|
||||||
|
|
||||||
from ... import contracts, sockets
|
from ... import contracts
|
||||||
from .. import base, events
|
from ... import sockets
|
||||||
|
from .. import base
|
||||||
|
|
||||||
|
|
||||||
class DrudeLorentzMediumNode(base.MaxwellSimTreeNode):
|
class DrudeLorentzMediumNode(base.MaxwellSimTreeNode):
|
||||||
|
@ -17,7 +19,7 @@ class DrudeLorentzMediumNode(base.MaxwellSimTreeNode):
|
||||||
input_sockets = (
|
input_sockets = (
|
||||||
{
|
{
|
||||||
'eps_inf': sockets.RealNumberSocketDef(
|
'eps_inf': sockets.RealNumberSocketDef(
|
||||||
label='εr_∞',
|
label=f'εr_∞',
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
|
@ -46,11 +48,11 @@ class DrudeLorentzMediumNode(base.MaxwellSimTreeNode):
|
||||||
####################
|
####################
|
||||||
# - Output Socket Computation
|
# - Output Socket Computation
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket('medium')
|
@base.computes_output_socket('medium')
|
||||||
def compute_medium(self: contracts.NodeTypeProtocol) -> td.Sellmeier:
|
def compute_medium(self: contracts.NodeTypeProtocol) -> td.Sellmeier:
|
||||||
## Retrieval
|
## Retrieval
|
||||||
return td.Lorentz(
|
return td.Lorentz(
|
||||||
eps_inf=self.compute_input('eps_inf'),
|
eps_inf=self.compute_input(f'eps_inf'),
|
||||||
coeffs=[
|
coeffs=[
|
||||||
(
|
(
|
||||||
self.compute_input(f'del_eps{i}'),
|
self.compute_input(f'del_eps{i}'),
|
||||||
|
@ -77,5 +79,7 @@ BL_REGISTER = [
|
||||||
DrudeLorentzMediumNode,
|
DrudeLorentzMediumNode,
|
||||||
]
|
]
|
||||||
BL_NODES = {
|
BL_NODES = {
|
||||||
contracts.NodeType.DrudeLorentzMedium: (contracts.NodeCategory.MAXWELLSIM_MEDIUMS)
|
contracts.NodeType.DrudeLorentzMedium: (
|
||||||
|
contracts.NodeCategory.MAXWELLSIM_MEDIUMS
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
import functools
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import scipy as sc
|
import tidy3d as td
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
import sympy.physics.units as spu
|
import sympy.physics.units as spu
|
||||||
import tidy3d as td
|
import numpy as np
|
||||||
|
import scipy as sc
|
||||||
|
|
||||||
from .....utils import extra_sympy_units as spuex
|
from .....utils import extra_sympy_units as spuex
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from ... import managed_objs, sockets
|
from ... import sockets
|
||||||
from .. import base, events
|
from ... import managed_objs
|
||||||
|
from .. import base
|
||||||
|
|
||||||
VAC_SPEED_OF_LIGHT = sc.constants.speed_of_light * spu.meter / spu.second
|
VAC_SPEED_OF_LIGHT = sc.constants.speed_of_light * spu.meter / spu.second
|
||||||
|
|
||||||
|
@ -72,7 +75,9 @@ class LibraryMediumNode(base.MaxwellSimNode):
|
||||||
/ spuex.terahertz
|
/ spuex.terahertz
|
||||||
for val in mat.medium.frequency_range
|
for val in mat.medium.frequency_range
|
||||||
]
|
]
|
||||||
return sp.pretty([freq_range[0].n(4), freq_range[1].n(4)], use_unicode=True)
|
return sp.pretty(
|
||||||
|
[freq_range[0].n(4), freq_range[1].n(4)], use_unicode=True
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def nm_range_str(self) -> str:
|
def nm_range_str(self) -> str:
|
||||||
|
@ -86,7 +91,9 @@ class LibraryMediumNode(base.MaxwellSimNode):
|
||||||
/ spu.nanometer
|
/ spu.nanometer
|
||||||
for val in reversed(mat.medium.frequency_range)
|
for val in reversed(mat.medium.frequency_range)
|
||||||
]
|
]
|
||||||
return sp.pretty([nm_range[0].n(4), nm_range[1].n(4)], use_unicode=True)
|
return sp.pretty(
|
||||||
|
[nm_range[0].n(4), nm_range[1].n(4)], use_unicode=True
|
||||||
|
)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - UI
|
# - UI
|
||||||
|
@ -111,14 +118,14 @@ class LibraryMediumNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - Output Sockets
|
# - Output Sockets
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket('Medium')
|
@base.computes_output_socket('Medium')
|
||||||
def compute_vac_wl(self) -> sp.Expr:
|
def compute_vac_wl(self) -> sp.Expr:
|
||||||
return td.material_library[self.material].medium
|
return td.material_library[self.material].medium
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Event Callbacks
|
# - Event Callbacks
|
||||||
####################
|
####################
|
||||||
@events.on_show_plot(
|
@base.on_show_plot(
|
||||||
managed_objs={'nk_plot'},
|
managed_objs={'nk_plot'},
|
||||||
props={'material'},
|
props={'material'},
|
||||||
stop_propagation=True, ## Plot only the first plottable node
|
stop_propagation=True, ## Plot only the first plottable node
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
from . import (
|
from . import add_non_linearity
|
||||||
add_non_linearity,
|
from . import chi_3_susceptibility_non_linearity
|
||||||
chi_3_susceptibility_non_linearity,
|
from . import kerr_non_linearity
|
||||||
kerr_non_linearity,
|
from . import two_photon_absorption_non_linearity
|
||||||
two_photon_absorption_non_linearity,
|
|
||||||
)
|
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
*add_non_linearity.BL_REGISTER,
|
*add_non_linearity.BL_REGISTER,
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import sympy.physics.units as spu
|
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
|
import sympy as sp
|
||||||
|
import sympy.physics.units as spu
|
||||||
|
|
||||||
from ... import contracts, sockets
|
from ... import contracts
|
||||||
from .. import base, events
|
from ... import sockets
|
||||||
|
from .. import base
|
||||||
|
|
||||||
|
|
||||||
class TripleSellmeierMediumNode(base.MaxwellSimTreeNode):
|
class TripleSellmeierMediumNode(base.MaxwellSimTreeNode):
|
||||||
|
@ -20,7 +22,9 @@ class TripleSellmeierMediumNode(base.MaxwellSimTreeNode):
|
||||||
)
|
)
|
||||||
for i in [1, 2, 3]
|
for i in [1, 2, 3]
|
||||||
} | {
|
} | {
|
||||||
f'C{i}': sockets.PhysicalAreaSocketDef(label=f'C{i}', default_unit=spu.um**2)
|
f'C{i}': sockets.PhysicalAreaSocketDef(
|
||||||
|
label=f'C{i}', default_unit=spu.um**2
|
||||||
|
)
|
||||||
for i in [1, 2, 3]
|
for i in [1, 2, 3]
|
||||||
}
|
}
|
||||||
output_sockets = {
|
output_sockets = {
|
||||||
|
@ -60,7 +64,7 @@ class TripleSellmeierMediumNode(base.MaxwellSimTreeNode):
|
||||||
####################
|
####################
|
||||||
# - Output Socket Computation
|
# - Output Socket Computation
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket('medium')
|
@base.computes_output_socket('medium')
|
||||||
def compute_medium(self: contracts.NodeTypeProtocol) -> td.Sellmeier:
|
def compute_medium(self: contracts.NodeTypeProtocol) -> td.Sellmeier:
|
||||||
## Retrieval
|
## Retrieval
|
||||||
# B1 = self.compute_input("B1")
|
# B1 = self.compute_input("B1")
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from . import eh_field_monitor, field_power_flux_monitor
|
from . import eh_field_monitor
|
||||||
|
from . import field_power_flux_monitor
|
||||||
# from . import epsilon_tensor_monitor
|
# from . import epsilon_tensor_monitor
|
||||||
# from . import diffraction_monitor
|
# from . import diffraction_monitor
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,24 @@
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
import functools
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import tidy3d as td
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
import sympy.physics.units as spu
|
import sympy.physics.units as spu
|
||||||
import tidy3d as td
|
import numpy as np
|
||||||
|
import scipy as sc
|
||||||
|
|
||||||
from .....assets.import_geonodes import GeoNodes, import_geonodes
|
from .....utils import analyze_geonodes
|
||||||
from .....utils import extra_sympy_units as spux
|
from .....utils import extra_sympy_units as spux
|
||||||
from .....utils import logger
|
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from ... import managed_objs, sockets
|
from ... import sockets
|
||||||
from .. import base, events
|
from ... import managed_objs
|
||||||
|
from .. import base
|
||||||
|
|
||||||
log = logger.get(__name__)
|
GEONODES_MONITOR_BOX = 'monitor_box'
|
||||||
|
|
||||||
|
|
||||||
class EHFieldMonitorNode(base.MaxwellSimNode):
|
class EHFieldMonitorNode(base.MaxwellSimNode):
|
||||||
"""Node providing for the monitoring of electromagnetic fields within a given planar region or volume."""
|
|
||||||
|
|
||||||
node_type = ct.NodeType.EHFieldMonitor
|
node_type = ct.NodeType.EHFieldMonitor
|
||||||
bl_label = 'E/H Field Monitor'
|
bl_label = 'E/H Field Monitor'
|
||||||
use_sim_node_name = True
|
use_sim_node_name = True
|
||||||
|
@ -24,14 +26,14 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - Sockets
|
# - Sockets
|
||||||
####################
|
####################
|
||||||
input_sockets: typ.ClassVar = {
|
input_sockets = {
|
||||||
'Center': sockets.PhysicalPoint3DSocketDef(),
|
'Center': sockets.PhysicalPoint3DSocketDef(),
|
||||||
'Size': sockets.PhysicalSize3DSocketDef(),
|
'Size': sockets.PhysicalSize3DSocketDef(),
|
||||||
'Samples/Space': sockets.Integer3DVectorSocketDef(
|
'Samples/Space': sockets.Integer3DVectorSocketDef(
|
||||||
default_value=sp.Matrix([10, 10, 10])
|
default_value=sp.Matrix([10, 10, 10])
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
input_socket_sets: typ.ClassVar = {
|
input_socket_sets = {
|
||||||
'Freq Domain': {
|
'Freq Domain': {
|
||||||
'Freqs': sockets.PhysicalFreqSocketDef(
|
'Freqs': sockets.PhysicalFreqSocketDef(
|
||||||
is_list=True,
|
is_list=True,
|
||||||
|
@ -39,31 +41,43 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
|
||||||
},
|
},
|
||||||
'Time Domain': {
|
'Time Domain': {
|
||||||
'Rec Start': sockets.PhysicalTimeSocketDef(),
|
'Rec Start': sockets.PhysicalTimeSocketDef(),
|
||||||
'Rec Stop': sockets.PhysicalTimeSocketDef(default_value=200 * spux.fs),
|
'Rec Stop': sockets.PhysicalTimeSocketDef(
|
||||||
|
default_value=200 * spux.fs
|
||||||
|
),
|
||||||
'Samples/Time': sockets.IntegerNumberSocketDef(
|
'Samples/Time': sockets.IntegerNumberSocketDef(
|
||||||
default_value=100,
|
default_value=100,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
output_sockets: typ.ClassVar = {
|
output_sockets = {
|
||||||
'Monitor': sockets.MaxwellMonitorSocketDef(),
|
'Monitor': sockets.MaxwellMonitorSocketDef(),
|
||||||
}
|
}
|
||||||
|
|
||||||
managed_obj_defs: typ.ClassVar = {
|
managed_obj_defs = {
|
||||||
'mesh': ct.schemas.ManagedObjDef(
|
'monitor_box': ct.schemas.ManagedObjDef(
|
||||||
mk=lambda name: managed_objs.ManagedBLMesh(name),
|
mk=lambda name: managed_objs.ManagedBLObject(name),
|
||||||
),
|
name_prefix='',
|
||||||
'modifier': ct.schemas.ManagedObjDef(
|
)
|
||||||
mk=lambda name: managed_objs.ManagedBLModifier(name),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Properties
|
||||||
|
####################
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - UI
|
||||||
|
####################
|
||||||
|
def draw_props(self, context, layout):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def draw_info(self, context, col):
|
||||||
|
pass
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Output Sockets
|
# - Output Sockets
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@base.computes_output_socket(
|
||||||
'Monitor',
|
'Monitor',
|
||||||
props={'active_socket_set', 'sim_node_name'},
|
|
||||||
input_sockets={
|
input_sockets={
|
||||||
'Rec Start',
|
'Rec Start',
|
||||||
'Rec Stop',
|
'Rec Stop',
|
||||||
|
@ -73,90 +87,107 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
|
||||||
'Samples/Time',
|
'Samples/Time',
|
||||||
'Freqs',
|
'Freqs',
|
||||||
},
|
},
|
||||||
unit_systems={'Tidy3DUnits': ct.UNITS_TIDY3D},
|
props={'active_socket_set', 'sim_node_name'},
|
||||||
scale_input_sockets={
|
|
||||||
'Center': 'Tidy3DUnits',
|
|
||||||
'Size': 'Tidy3DUnits',
|
|
||||||
'Samples/Space': 'Tidy3DUnits',
|
|
||||||
'Rec Start': 'Tidy3DUnits',
|
|
||||||
'Rec Stop': 'Tidy3DUnits',
|
|
||||||
'Samples/Time': 'Tidy3DUnits',
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
def compute_monitor(
|
def compute_monitor(
|
||||||
self, input_sockets: dict, props: dict, unit_systems: dict,
|
self, input_sockets: dict, props: dict
|
||||||
) -> td.FieldMonitor | td.FieldTimeMonitor:
|
) -> td.FieldTimeMonitor:
|
||||||
|
_center = input_sockets['Center']
|
||||||
|
_size = input_sockets['Size']
|
||||||
|
_samples_space = input_sockets['Samples/Space']
|
||||||
|
|
||||||
|
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
|
||||||
|
size = tuple(spu.convert_to(_size, spu.um) / spu.um)
|
||||||
|
samples_space = tuple(_samples_space)
|
||||||
|
|
||||||
if props['active_socket_set'] == 'Freq Domain':
|
if props['active_socket_set'] == 'Freq Domain':
|
||||||
freqs = input_sockets['Freqs']
|
freqs = input_sockets['Freqs']
|
||||||
|
|
||||||
log.info(
|
|
||||||
'Computing FieldMonitor (name="%s") with center="%s", size="%s"',
|
|
||||||
props['sim_node_name'],
|
|
||||||
input_sockets['Center'],
|
|
||||||
input_sockets['Size'],
|
|
||||||
)
|
|
||||||
return td.FieldMonitor(
|
return td.FieldMonitor(
|
||||||
center=input_sockets['Center'],
|
center=center,
|
||||||
size=input_sockets['Size'],
|
size=size,
|
||||||
name=props['sim_node_name'],
|
name=props['sim_node_name'],
|
||||||
interval_space=input_sockets['Samples/Space'],
|
interval_space=samples_space,
|
||||||
freqs=[
|
freqs=[
|
||||||
float(spu.convert_to(freq, spu.hertz) / spu.hertz) for freq in freqs
|
float(spu.convert_to(freq, spu.hertz) / spu.hertz)
|
||||||
|
for freq in freqs
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
## Time Domain
|
else: ## Time Domain
|
||||||
log.info(
|
_rec_start = input_sockets['Rec Start']
|
||||||
'Computing FieldTimeMonitor (name=%s) with center=%s, size=%s',
|
_rec_stop = input_sockets['Rec Stop']
|
||||||
props['sim_node_name'],
|
samples_time = input_sockets['Samples/Time']
|
||||||
input_sockets['Center'],
|
|
||||||
input_sockets['Size'],
|
rec_start = spu.convert_to(_rec_start, spu.second) / spu.second
|
||||||
)
|
rec_stop = spu.convert_to(_rec_stop, spu.second) / spu.second
|
||||||
|
|
||||||
return td.FieldTimeMonitor(
|
return td.FieldTimeMonitor(
|
||||||
center=input_sockets['Center'],
|
center=center,
|
||||||
size=input_sockets['Size'],
|
size=size,
|
||||||
name=props['sim_node_name'],
|
name=props['sim_node_name'],
|
||||||
start=input_sockets['Rec Start'],
|
start=rec_start,
|
||||||
stop=input_sockets['Rec Stop'],
|
stop=rec_stop,
|
||||||
interval=input_sockets['Samples/Time'],
|
interval=samples_time,
|
||||||
interval_space=input_sockets['Samples/Space'],
|
interval_space=samples_space,
|
||||||
)
|
)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Preview - Changes to Input Sockets
|
# - Preview - Changes to Input Sockets
|
||||||
####################
|
####################
|
||||||
@events.on_value_changed(
|
@base.on_value_changed(
|
||||||
socket_name={'Center', 'Size'},
|
socket_name={'Center', 'Size'},
|
||||||
prop_name='preview_active',
|
|
||||||
props={'preview_active'},
|
|
||||||
input_sockets={'Center', 'Size'},
|
input_sockets={'Center', 'Size'},
|
||||||
managed_objs={'mesh', 'modifier'},
|
managed_objs={'monitor_box'},
|
||||||
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
|
||||||
scale_input_sockets={
|
|
||||||
'Center': 'BlenderUnits',
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
def on_inputs_changed(
|
def on_value_changed__center_size(
|
||||||
self,
|
self,
|
||||||
props: dict,
|
|
||||||
managed_objs: dict[str, ct.schemas.ManagedObj],
|
|
||||||
input_sockets: dict,
|
input_sockets: dict,
|
||||||
unit_systems: dict,
|
managed_objs: dict[str, ct.schemas.ManagedObj],
|
||||||
):
|
):
|
||||||
# Push Input Values to GeoNodes Modifier
|
_center = input_sockets['Center']
|
||||||
managed_objs['modifier'].bl_modifier(
|
center = tuple(
|
||||||
managed_objs['mesh'].bl_object(location=input_sockets['Center']),
|
[float(el) for el in spu.convert_to(_center, spu.um) / spu.um]
|
||||||
'NODES',
|
)
|
||||||
{
|
|
||||||
'node_group': import_geonodes(GeoNodes.PrimitiveBox, 'link'),
|
_size = input_sockets['Size']
|
||||||
'unit_system': unit_systems['BlenderUnits'],
|
size = tuple(
|
||||||
'inputs': {
|
[float(el) for el in spu.convert_to(_size, spu.um) / spu.um]
|
||||||
'Size': input_sockets['Size'],
|
)
|
||||||
},
|
## TODO: Preview unit system?? Presume um for now
|
||||||
|
|
||||||
|
# Retrieve Hard-Coded GeoNodes and Analyze Input
|
||||||
|
geo_nodes = bpy.data.node_groups[GEONODES_MONITOR_BOX]
|
||||||
|
geonodes_interface = analyze_geonodes.interface(
|
||||||
|
geo_nodes, direc='INPUT'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Sync Modifier Inputs
|
||||||
|
managed_objs['monitor_box'].sync_geonodes_modifier(
|
||||||
|
geonodes_node_group=geo_nodes,
|
||||||
|
geonodes_identifier_to_value={
|
||||||
|
geonodes_interface['Size'].identifier: size,
|
||||||
|
## TODO: Use 'bl_socket_map.value_to_bl`!
|
||||||
|
## - This accounts for auto-conversion, unit systems, etc. .
|
||||||
|
## - We could keep it in the node base class...
|
||||||
|
## - ...But it needs aligning with Blender, too. Hmm.
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
# Push Preview State
|
|
||||||
if props['preview_active']:
|
# Sync Object Position
|
||||||
managed_objs['mesh'].show_preview()
|
managed_objs['monitor_box'].bl_object('MESH').location = center
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Preview - Show Preview
|
||||||
|
####################
|
||||||
|
@base.on_show_preview(
|
||||||
|
managed_objs={'monitor_box'},
|
||||||
|
)
|
||||||
|
def on_show_preview(
|
||||||
|
self,
|
||||||
|
managed_objs: dict[str, ct.schemas.ManagedObj],
|
||||||
|
):
|
||||||
|
managed_objs['monitor_box'].show_preview('MESH')
|
||||||
|
self.on_value_changed__center_size()
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
import functools
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
import tidy3d as td
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
import sympy.physics.units as spu
|
import sympy.physics.units as spu
|
||||||
import tidy3d as td
|
import numpy as np
|
||||||
|
import scipy as sc
|
||||||
|
|
||||||
from .....assets.import_geonodes import GeoNodes, import_geonodes
|
from .....utils import analyze_geonodes
|
||||||
from .....utils import extra_sympy_units as spux
|
from .....utils import extra_sympy_units as spux
|
||||||
from .....utils import logger
|
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from ... import managed_objs, sockets
|
from ... import sockets
|
||||||
from .. import base, events
|
from ... import managed_objs
|
||||||
|
from .. import base
|
||||||
|
|
||||||
log = logger.get(__name__)
|
GEONODES_MONITOR_BOX = 'monitor_flux_box'
|
||||||
|
|
||||||
|
|
||||||
class FieldPowerFluxMonitorNode(base.MaxwellSimNode):
|
class FieldPowerFluxMonitorNode(base.MaxwellSimNode):
|
||||||
|
@ -23,7 +26,7 @@ class FieldPowerFluxMonitorNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - Sockets
|
# - Sockets
|
||||||
####################
|
####################
|
||||||
input_sockets: typ.ClassVar = {
|
input_sockets = {
|
||||||
'Center': sockets.PhysicalPoint3DSocketDef(),
|
'Center': sockets.PhysicalPoint3DSocketDef(),
|
||||||
'Size': sockets.PhysicalSize3DSocketDef(),
|
'Size': sockets.PhysicalSize3DSocketDef(),
|
||||||
'Samples/Space': sockets.Integer3DVectorSocketDef(
|
'Samples/Space': sockets.Integer3DVectorSocketDef(
|
||||||
|
@ -31,7 +34,7 @@ class FieldPowerFluxMonitorNode(base.MaxwellSimNode):
|
||||||
),
|
),
|
||||||
'Direction': sockets.BoolSocketDef(),
|
'Direction': sockets.BoolSocketDef(),
|
||||||
}
|
}
|
||||||
input_socket_sets: typ.ClassVar = {
|
input_socket_sets = {
|
||||||
'Freq Domain': {
|
'Freq Domain': {
|
||||||
'Freqs': sockets.PhysicalFreqSocketDef(
|
'Freqs': sockets.PhysicalFreqSocketDef(
|
||||||
is_list=True,
|
is_list=True,
|
||||||
|
@ -39,31 +42,43 @@ class FieldPowerFluxMonitorNode(base.MaxwellSimNode):
|
||||||
},
|
},
|
||||||
'Time Domain': {
|
'Time Domain': {
|
||||||
'Rec Start': sockets.PhysicalTimeSocketDef(),
|
'Rec Start': sockets.PhysicalTimeSocketDef(),
|
||||||
'Rec Stop': sockets.PhysicalTimeSocketDef(default_value=200 * spux.fs),
|
'Rec Stop': sockets.PhysicalTimeSocketDef(
|
||||||
|
default_value=200 * spux.fs
|
||||||
|
),
|
||||||
'Samples/Time': sockets.IntegerNumberSocketDef(
|
'Samples/Time': sockets.IntegerNumberSocketDef(
|
||||||
default_value=100,
|
default_value=100,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
output_sockets: typ.ClassVar = {
|
output_sockets = {
|
||||||
'Monitor': sockets.MaxwellMonitorSocketDef(),
|
'Monitor': sockets.MaxwellMonitorSocketDef(),
|
||||||
}
|
}
|
||||||
|
|
||||||
managed_obj_defs: typ.ClassVar = {
|
managed_obj_defs = {
|
||||||
'mesh': ct.schemas.ManagedObjDef(
|
'monitor_box': ct.schemas.ManagedObjDef(
|
||||||
mk=lambda name: managed_objs.ManagedBLMesh(name),
|
mk=lambda name: managed_objs.ManagedBLObject(name),
|
||||||
),
|
name_prefix='',
|
||||||
'modifier': ct.schemas.ManagedObjDef(
|
)
|
||||||
mk=lambda name: managed_objs.ManagedBLModifier(name),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Event Methods: Computation
|
# - Properties
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
|
||||||
|
####################
|
||||||
|
# - UI
|
||||||
|
####################
|
||||||
|
def draw_props(self, context, layout):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def draw_info(self, context, col):
|
||||||
|
pass
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Output Sockets
|
||||||
|
####################
|
||||||
|
@base.computes_output_socket(
|
||||||
'Monitor',
|
'Monitor',
|
||||||
props={'active_socket_set', 'sim_node_name'},
|
|
||||||
input_sockets={
|
input_sockets={
|
||||||
'Rec Start',
|
'Rec Start',
|
||||||
'Rec Stop',
|
'Rec Stop',
|
||||||
|
@ -74,86 +89,113 @@ class FieldPowerFluxMonitorNode(base.MaxwellSimNode):
|
||||||
'Freqs',
|
'Freqs',
|
||||||
'Direction',
|
'Direction',
|
||||||
},
|
},
|
||||||
unit_systems={'Tidy3DUnits': ct.UNITS_TIDY3D},
|
props={'active_socket_set', 'sim_node_name'},
|
||||||
scale_input_sockets={
|
|
||||||
'Center': 'Tidy3DUnits',
|
|
||||||
'Size': 'Tidy3DUnits',
|
|
||||||
'Samples/Space': 'Tidy3DUnits',
|
|
||||||
'Rec Start': 'Tidy3DUnits',
|
|
||||||
'Rec Stop': 'Tidy3DUnits',
|
|
||||||
'Samples/Time': 'Tidy3DUnits',
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
def compute_monitor(self, input_sockets: dict, props: dict) -> td.FieldTimeMonitor:
|
def compute_monitor(
|
||||||
|
self, input_sockets: dict, props: dict
|
||||||
|
) -> td.FieldTimeMonitor:
|
||||||
|
_center = input_sockets['Center']
|
||||||
|
_size = input_sockets['Size']
|
||||||
|
_samples_space = input_sockets['Samples/Space']
|
||||||
|
|
||||||
|
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
|
||||||
|
size = tuple(spu.convert_to(_size, spu.um) / spu.um)
|
||||||
|
samples_space = tuple(_samples_space)
|
||||||
|
|
||||||
direction = '+' if input_sockets['Direction'] else '-'
|
direction = '+' if input_sockets['Direction'] else '-'
|
||||||
|
|
||||||
if props['active_socket_set'] == 'Freq Domain':
|
if props['active_socket_set'] == 'Freq Domain':
|
||||||
freqs = input_sockets['Freqs']
|
freqs = input_sockets['Freqs']
|
||||||
|
|
||||||
log.info(
|
|
||||||
'Computing FluxMonitor (name="%s") with center="%s", size="%s"',
|
|
||||||
props['sim_node_name'],
|
|
||||||
input_sockets['Center'],
|
|
||||||
input_sockets['Size'],
|
|
||||||
)
|
|
||||||
return td.FluxMonitor(
|
return td.FluxMonitor(
|
||||||
center=input_sockets['Center'],
|
center=center,
|
||||||
size=input_sockets['Size'],
|
size=size,
|
||||||
name=props['sim_node_name'],
|
name=props['sim_node_name'],
|
||||||
interval_space=input_sockets['Samples/Space'],
|
interval_space=samples_space,
|
||||||
freqs=[
|
freqs=[
|
||||||
float(spu.convert_to(freq, spu.hertz) / spu.hertz) for freq in freqs
|
float(spu.convert_to(freq, spu.hertz) / spu.hertz)
|
||||||
|
for freq in freqs
|
||||||
],
|
],
|
||||||
normal_dir=direction,
|
normal_dir=direction,
|
||||||
)
|
)
|
||||||
|
else: ## Time Domain
|
||||||
|
_rec_start = input_sockets['Rec Start']
|
||||||
|
_rec_stop = input_sockets['Rec Stop']
|
||||||
|
samples_time = input_sockets['Samples/Time']
|
||||||
|
|
||||||
return td.FluxTimeMonitor(
|
rec_start = spu.convert_to(_rec_start, spu.second) / spu.second
|
||||||
center=input_sockets['Center'],
|
rec_stop = spu.convert_to(_rec_stop, spu.second) / spu.second
|
||||||
size=input_sockets['Size'],
|
|
||||||
|
return td.FieldTimeMonitor(
|
||||||
|
center=center,
|
||||||
|
size=size,
|
||||||
name=props['sim_node_name'],
|
name=props['sim_node_name'],
|
||||||
start=input_sockets['Rec Start'],
|
start=rec_start,
|
||||||
stop=input_sockets['Rec Stop'],
|
stop=rec_stop,
|
||||||
interval=input_sockets['Samples/Time'],
|
interval=samples_time,
|
||||||
interval_space=input_sockets['Samples/Space'],
|
interval_space=samples_space,
|
||||||
normal_dir=direction,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Preview - Changes to Input Sockets
|
# - Preview - Changes to Input Sockets
|
||||||
####################
|
####################
|
||||||
@events.on_value_changed(
|
@base.on_value_changed(
|
||||||
socket_name={'Center', 'Size'},
|
socket_name={'Center', 'Size'},
|
||||||
prop_name='preview_active',
|
input_sockets={'Center', 'Size', 'Direction'},
|
||||||
props={'preview_active'},
|
managed_objs={'monitor_box'},
|
||||||
input_sockets={'Center', 'Size'},
|
|
||||||
managed_objs={'mesh', 'modifier'},
|
|
||||||
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
|
||||||
scale_input_sockets={
|
|
||||||
'Center': 'BlenderUnits',
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
def on_inputs_changed(
|
def on_value_changed__center_size(
|
||||||
self,
|
self,
|
||||||
props: dict,
|
|
||||||
managed_objs: dict[str, ct.schemas.ManagedObj],
|
|
||||||
input_sockets: dict,
|
input_sockets: dict,
|
||||||
unit_systems: dict,
|
managed_objs: dict[str, ct.schemas.ManagedObj],
|
||||||
):
|
):
|
||||||
# Push Input Values to GeoNodes Modifier
|
_center = input_sockets['Center']
|
||||||
managed_objs['modifier'].bl_modifier(
|
center = tuple(
|
||||||
managed_objs['mesh'].bl_object(location=input_sockets['Center']),
|
[float(el) for el in spu.convert_to(_center, spu.um) / spu.um]
|
||||||
'NODES',
|
)
|
||||||
{
|
|
||||||
'node_group': import_geonodes(GeoNodes.PrimitiveBox, 'link'),
|
_size = input_sockets['Size']
|
||||||
'unit_system': unit_systems['BlenderUnits'],
|
size = tuple(
|
||||||
'inputs': {
|
[float(el) for el in spu.convert_to(_size, spu.um) / spu.um]
|
||||||
'Size': input_sockets['Size'],
|
)
|
||||||
},
|
## TODO: Preview unit system?? Presume um for now
|
||||||
|
|
||||||
|
# Retrieve Hard-Coded GeoNodes and Analyze Input
|
||||||
|
geo_nodes = bpy.data.node_groups[GEONODES_MONITOR_BOX]
|
||||||
|
geonodes_interface = analyze_geonodes.interface(
|
||||||
|
geo_nodes, direc='INPUT'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Sync Modifier Inputs
|
||||||
|
managed_objs['monitor_box'].sync_geonodes_modifier(
|
||||||
|
geonodes_node_group=geo_nodes,
|
||||||
|
geonodes_identifier_to_value={
|
||||||
|
geonodes_interface['Size'].identifier: size,
|
||||||
|
geonodes_interface['Direction'].identifier: input_sockets[
|
||||||
|
'Direction'
|
||||||
|
],
|
||||||
|
## TODO: Use 'bl_socket_map.value_to_bl`!
|
||||||
|
## - This accounts for auto-conversion, unit systems, etc. .
|
||||||
|
## - We could keep it in the node base class...
|
||||||
|
## - ...But it needs aligning with Blender, too. Hmm.
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
# Push Preview State
|
|
||||||
if props['preview_active']:
|
# Sync Object Position
|
||||||
managed_objs['mesh'].show_preview()
|
managed_objs['monitor_box'].bl_object('MESH').location = center
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Preview - Show Preview
|
||||||
|
####################
|
||||||
|
@base.on_show_preview(
|
||||||
|
managed_objs={'monitor_box'},
|
||||||
|
)
|
||||||
|
def on_show_preview(
|
||||||
|
self,
|
||||||
|
managed_objs: dict[str, ct.schemas.ManagedObj],
|
||||||
|
):
|
||||||
|
managed_objs['monitor_box'].show_preview('MESH')
|
||||||
|
self.on_value_changed__center_size()
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -162,4 +204,6 @@ class FieldPowerFluxMonitorNode(base.MaxwellSimNode):
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
FieldPowerFluxMonitorNode,
|
FieldPowerFluxMonitorNode,
|
||||||
]
|
]
|
||||||
BL_NODES = {ct.NodeType.FieldPowerFluxMonitor: (ct.NodeCategory.MAXWELLSIM_MONITORS)}
|
BL_NODES = {
|
||||||
|
ct.NodeType.FieldPowerFluxMonitor: (ct.NodeCategory.MAXWELLSIM_MONITORS)
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from . import exporters, viewer
|
from . import viewer
|
||||||
|
from . import exporters
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
*viewer.BL_REGISTER,
|
*viewer.BL_REGISTER,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from . import json_file_exporter, tidy3d_web_exporter
|
from . import json_file_exporter
|
||||||
|
from . import tidy3d_web_exporter
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
*json_file_exporter.BL_REGISTER,
|
*json_file_exporter.BL_REGISTER,
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
import json
|
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
import sympy as sp
|
||||||
import pydantic as pyd
|
import pydantic as pyd
|
||||||
|
import tidy3d as td
|
||||||
|
|
||||||
from .... import contracts as ct
|
from .... import contracts as ct
|
||||||
from .... import sockets
|
from .... import sockets
|
||||||
from ... import base, events
|
from ... import base
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -38,7 +40,9 @@ class JSONFileExporterNode(base.MaxwellSimNode):
|
||||||
|
|
||||||
input_sockets = {
|
input_sockets = {
|
||||||
'Data': sockets.AnySocketDef(),
|
'Data': sockets.AnySocketDef(),
|
||||||
'JSON Path': sockets.FilePathSocketDef(default_path=Path('simulation.json')),
|
'JSON Path': sockets.FilePathSocketDef(
|
||||||
|
default_path=Path('simulation.json')
|
||||||
|
),
|
||||||
'JSON Indent': sockets.IntegerNumberSocketDef(
|
'JSON Indent': sockets.IntegerNumberSocketDef(
|
||||||
default_value=4,
|
default_value=4,
|
||||||
),
|
),
|
||||||
|
@ -70,11 +74,13 @@ class JSONFileExporterNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - Output Sockets
|
# - Output Sockets
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@base.computes_output_socket(
|
||||||
'JSON String',
|
'JSON String',
|
||||||
input_sockets={'Data'},
|
input_sockets={'Data'},
|
||||||
)
|
)
|
||||||
def compute_json_string(self, input_sockets: dict[str, typ.Any]) -> str | None:
|
def compute_json_string(
|
||||||
|
self, input_sockets: dict[str, typ.Any]
|
||||||
|
) -> str | None:
|
||||||
if not (data := input_sockets['Data']):
|
if not (data := input_sockets['Data']):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -98,5 +104,7 @@ BL_REGISTER = [
|
||||||
JSONFileExporterNode,
|
JSONFileExporterNode,
|
||||||
]
|
]
|
||||||
BL_NODES = {
|
BL_NODES = {
|
||||||
ct.NodeType.JSONFileExporter: (ct.NodeCategory.MAXWELLSIM_OUTPUTS_EXPORTERS)
|
ct.NodeType.JSONFileExporter: (
|
||||||
|
ct.NodeCategory.MAXWELLSIM_OUTPUTS_EXPORTERS
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import bpy
|
||||||
from ......services import tdcloud
|
from ......services import tdcloud
|
||||||
from .... import contracts as ct
|
from .... import contracts as ct
|
||||||
from .... import sockets
|
from .... import sockets
|
||||||
from ... import base, events
|
from ... import base
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -77,7 +77,9 @@ class ReloadTrackedTask(bpy.types.Operator):
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
node = context.node
|
node = context.node
|
||||||
if (cloud_task := tdcloud.TidyCloudTasks.task(node.tracked_task_id)) is None:
|
if (
|
||||||
|
cloud_task := tdcloud.TidyCloudTasks.task(node.tracked_task_id)
|
||||||
|
) is None:
|
||||||
msg = "Tried to reload tracked task, but it doesn't exist"
|
msg = "Tried to reload tracked task, but it doesn't exist"
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
|
@ -103,9 +105,13 @@ class EstCostTrackedTask(bpy.types.Operator):
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
node = context.node
|
node = context.node
|
||||||
if (
|
if (
|
||||||
task_info := tdcloud.TidyCloudTasks.task_info(context.node.tracked_task_id)
|
task_info := tdcloud.TidyCloudTasks.task_info(
|
||||||
|
context.node.tracked_task_id
|
||||||
|
)
|
||||||
) is None:
|
) is None:
|
||||||
msg = "Tried to estimate cost of tracked task, but it doesn't exist"
|
msg = (
|
||||||
|
"Tried to estimate cost of tracked task, but it doesn't exist"
|
||||||
|
)
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
node.cache_est_cost = task_info.cost_est()
|
node.cache_est_cost = task_info.cost_est()
|
||||||
|
@ -229,13 +235,13 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
|
||||||
msg = 'Tried to upload simulation, but none is attached'
|
msg = 'Tried to upload simulation, but none is attached'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
if (new_task := self._compute_input('Cloud Task')) is None or isinstance(
|
if (
|
||||||
|
new_task := self._compute_input('Cloud Task')
|
||||||
|
) is None or isinstance(
|
||||||
new_task,
|
new_task,
|
||||||
tdcloud.CloudTask,
|
tdcloud.CloudTask,
|
||||||
):
|
):
|
||||||
msg = (
|
msg = 'Tried to upload simulation to new task, but existing task was selected'
|
||||||
'Tried to upload simulation to new task, but existing task was selected'
|
|
||||||
)
|
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
# Create Cloud Task
|
# Create Cloud Task
|
||||||
|
@ -256,7 +262,9 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
|
||||||
self.tracked_task_id = cloud_task.task_id
|
self.tracked_task_id = cloud_task.task_id
|
||||||
|
|
||||||
def run_tracked_task(self):
|
def run_tracked_task(self):
|
||||||
if (cloud_task := tdcloud.TidyCloudTasks.task(self.tracked_task_id)) is None:
|
if (
|
||||||
|
cloud_task := tdcloud.TidyCloudTasks.task(self.tracked_task_id)
|
||||||
|
) is None:
|
||||||
msg = "Tried to run tracked task, but it doesn't exist"
|
msg = "Tried to run tracked task, but it doesn't exist"
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
|
@ -294,7 +302,9 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
|
||||||
|
|
||||||
def draw_info(self, context, layout):
|
def draw_info(self, context, layout):
|
||||||
# Connection Info
|
# Connection Info
|
||||||
auth_icon = 'CHECKBOX_HLT' if tdcloud.IS_AUTHENTICATED else 'CHECKBOX_DEHLT'
|
auth_icon = (
|
||||||
|
'CHECKBOX_HLT' if tdcloud.IS_AUTHENTICATED else 'CHECKBOX_DEHLT'
|
||||||
|
)
|
||||||
conn_icon = 'CHECKBOX_HLT' if tdcloud.IS_ONLINE else 'CHECKBOX_DEHLT'
|
conn_icon = 'CHECKBOX_HLT' if tdcloud.IS_ONLINE else 'CHECKBOX_DEHLT'
|
||||||
|
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
|
@ -328,7 +338,9 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
|
||||||
## Split: Right Column
|
## Split: Right Column
|
||||||
col = split.column(align=False)
|
col = split.column(align=False)
|
||||||
col.alignment = 'RIGHT'
|
col.alignment = 'RIGHT'
|
||||||
col.label(text=f'{self.cache_total_monitor_data / 1_000_000:.2f}MB')
|
col.label(
|
||||||
|
text=f'{self.cache_total_monitor_data / 1_000_000:.2f}MB'
|
||||||
|
)
|
||||||
|
|
||||||
# Cloud Task Info
|
# Cloud Task Info
|
||||||
if self.tracked_task_id and tdcloud.IS_AUTHENTICATED:
|
if self.tracked_task_id and tdcloud.IS_AUTHENTICATED:
|
||||||
|
@ -371,7 +383,9 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
|
||||||
|
|
||||||
## Split: Right Column
|
## Split: Right Column
|
||||||
cost_est = (
|
cost_est = (
|
||||||
f'{self.cache_est_cost:.2f}' if self.cache_est_cost >= 0 else 'TBD'
|
f'{self.cache_est_cost:.2f}'
|
||||||
|
if self.cache_est_cost >= 0
|
||||||
|
else 'TBD'
|
||||||
)
|
)
|
||||||
cost_real = (
|
cost_real = (
|
||||||
f'{task_info.cost_real:.2f}'
|
f'{task_info.cost_real:.2f}'
|
||||||
|
@ -390,12 +404,16 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - Output Methods
|
# - Output Methods
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@base.computes_output_socket(
|
||||||
'Cloud Task',
|
'Cloud Task',
|
||||||
input_sockets={'Cloud Task'},
|
input_sockets={'Cloud Task'},
|
||||||
)
|
)
|
||||||
def compute_cloud_task(self, input_sockets: dict) -> tdcloud.CloudTask | None:
|
def compute_cloud_task(
|
||||||
if isinstance(cloud_task := input_sockets['Cloud Task'], tdcloud.CloudTask):
|
self, input_sockets: dict
|
||||||
|
) -> tdcloud.CloudTask | None:
|
||||||
|
if isinstance(
|
||||||
|
cloud_task := input_sockets['Cloud Task'], tdcloud.CloudTask
|
||||||
|
):
|
||||||
return cloud_task
|
return cloud_task
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
@ -403,7 +421,7 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - Output Methods
|
# - Output Methods
|
||||||
####################
|
####################
|
||||||
@events.on_value_changed(
|
@base.on_value_changed(
|
||||||
socket_name='FDTD Sim',
|
socket_name='FDTD Sim',
|
||||||
input_sockets={'FDTD Sim'},
|
input_sockets={'FDTD Sim'},
|
||||||
)
|
)
|
||||||
|
@ -428,5 +446,7 @@ BL_REGISTER = [
|
||||||
Tidy3DWebExporterNode,
|
Tidy3DWebExporterNode,
|
||||||
]
|
]
|
||||||
BL_NODES = {
|
BL_NODES = {
|
||||||
ct.NodeType.Tidy3DWebExporter: (ct.NodeCategory.MAXWELLSIM_OUTPUTS_EXPORTERS)
|
ct.NodeType.Tidy3DWebExporter: (
|
||||||
|
ct.NodeCategory.MAXWELLSIM_OUTPUTS_EXPORTERS
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
|
import functools
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
|
import pydantic as pyd
|
||||||
|
import tidy3d as td
|
||||||
|
|
||||||
from .....utils import logger
|
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from ... import sockets
|
from ... import sockets
|
||||||
from ...managed_objs.managed_bl_collection import preview_collection
|
from .. import base
|
||||||
from .. import base, events
|
from ...managed_objs import managed_bl_object
|
||||||
|
|
||||||
log = logger.get(__name__)
|
|
||||||
console = logger.OUTPUT_CONSOLE
|
|
||||||
|
|
||||||
|
|
||||||
class ConsoleViewOperator(bpy.types.Operator):
|
class ConsoleViewOperator(bpy.types.Operator):
|
||||||
|
@ -48,7 +49,7 @@ class ViewerNode(base.MaxwellSimNode):
|
||||||
node_type = ct.NodeType.Viewer
|
node_type = ct.NodeType.Viewer
|
||||||
bl_label = 'Viewer'
|
bl_label = 'Viewer'
|
||||||
|
|
||||||
input_sockets: typ.ClassVar = {
|
input_sockets = {
|
||||||
'Data': sockets.AnySocketDef(),
|
'Data': sockets.AnySocketDef(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,13 +67,9 @@ class ViewerNode(base.MaxwellSimNode):
|
||||||
name='Auto 3D Preview',
|
name='Auto 3D Preview',
|
||||||
description="Whether to auto-preview anything 3D, that's plugged into the viewer node",
|
description="Whether to auto-preview anything 3D, that's plugged into the viewer node",
|
||||||
default=False,
|
default=False,
|
||||||
update=lambda self, context: self.sync_prop('auto_3d_preview', context),
|
update=lambda self, context: self.sync_prop(
|
||||||
)
|
'auto_3d_preview', context
|
||||||
|
),
|
||||||
cache__data_was_unlinked: bpy.props.BoolProperty(
|
|
||||||
name='Data Was Unlinked',
|
|
||||||
description="Whether the Data input was unlinked last time it was checked.",
|
|
||||||
default=True,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -114,44 +111,51 @@ class ViewerNode(base.MaxwellSimNode):
|
||||||
return
|
return
|
||||||
|
|
||||||
if isinstance(data, sp.Basic):
|
if isinstance(data, sp.Basic):
|
||||||
console.print(sp.pretty(data, use_unicode=True))
|
sp.pprint(data, use_unicode=True)
|
||||||
else:
|
|
||||||
console.print(data)
|
print(str(data))
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Event Methods
|
# - Updates
|
||||||
####################
|
####################
|
||||||
@events.on_value_changed(
|
@base.on_value_changed(
|
||||||
socket_name='Data',
|
|
||||||
props={'auto_plot'},
|
|
||||||
)
|
|
||||||
def on_changed_2d_data(self, props):
|
|
||||||
# Show Plot
|
|
||||||
## Don't have to un-show other plots.
|
|
||||||
if self.inputs['Data'].is_linked and props['auto_plot']:
|
|
||||||
self.trigger_action('show_plot')
|
|
||||||
|
|
||||||
@events.on_value_changed(
|
|
||||||
socket_name='Data',
|
socket_name='Data',
|
||||||
props={'auto_3d_preview'},
|
props={'auto_3d_preview'},
|
||||||
)
|
)
|
||||||
def on_changed_3d_data(self, props):
|
def on_value_changed__data(self, props):
|
||||||
# Data Not Attached
|
# Show Plot
|
||||||
if not self.inputs['Data'].is_linked:
|
## Don't have to un-show other plots.
|
||||||
self.cache__data_was_unlinked = True
|
if self.auto_plot:
|
||||||
|
self.trigger_action('show_plot')
|
||||||
|
|
||||||
# Data Just Attached
|
# Remove Anything Previewed
|
||||||
elif self.cache__data_was_unlinked:
|
preview_collection = managed_bl_object.bl_collection(
|
||||||
node_tree = self.id_data
|
managed_bl_object.PREVIEW_COLLECTION_NAME,
|
||||||
|
view_layer_exclude=False,
|
||||||
|
)
|
||||||
|
for bl_object in preview_collection.objects.values():
|
||||||
|
preview_collection.objects.unlink(bl_object)
|
||||||
|
|
||||||
# Unpreview Everything
|
# Preview Anything that Should be Previewed (maybe)
|
||||||
node_tree.unpreview_all()
|
if props['auto_3d_preview']:
|
||||||
|
self.trigger_action('show_preview')
|
||||||
# Enable Previews in Tree
|
|
||||||
|
@base.on_value_changed(
|
||||||
|
prop_name='auto_3d_preview',
|
||||||
|
props={'auto_3d_preview'},
|
||||||
|
)
|
||||||
|
def on_value_changed__auto_3d_preview(self, props):
|
||||||
|
# Remove Anything Previewed
|
||||||
|
preview_collection = managed_bl_object.bl_collection(
|
||||||
|
managed_bl_object.PREVIEW_COLLECTION_NAME,
|
||||||
|
view_layer_exclude=False,
|
||||||
|
)
|
||||||
|
for bl_object in preview_collection.objects.values():
|
||||||
|
preview_collection.objects.unlink(bl_object)
|
||||||
|
|
||||||
|
# Preview Anything that Should be Previewed (maybe)
|
||||||
if props['auto_3d_preview']:
|
if props['auto_3d_preview']:
|
||||||
log.info('Enabling 3D Previews from "%s"', self.name)
|
|
||||||
self.trigger_action('show_preview')
|
self.trigger_action('show_preview')
|
||||||
self.cache__data_was_unlinked = False
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
from . import sim_domain
|
||||||
|
|
||||||
# from . import sim_grid
|
# from . import sim_grid
|
||||||
# from . import sim_grid_axes
|
# from . import sim_grid_axes
|
||||||
from . import fdtd_sim, sim_domain
|
|
||||||
|
from . import fdtd_sim
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
*sim_domain.BL_REGISTER,
|
*sim_domain.BL_REGISTER,
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import sympy as sp
|
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
|
import sympy as sp
|
||||||
|
import sympy.physics.units as spu
|
||||||
|
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from ... import sockets
|
from ... import sockets
|
||||||
from .. import base, events
|
from .. import base
|
||||||
|
|
||||||
|
|
||||||
class FDTDSimNode(base.MaxwellSimNode):
|
class FDTDSimNode(base.MaxwellSimNode):
|
||||||
|
@ -33,7 +34,7 @@ class FDTDSimNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - Output Socket Computation
|
# - Output Socket Computation
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@base.computes_output_socket(
|
||||||
'FDTD Sim',
|
'FDTD Sim',
|
||||||
kind=ct.DataFlowKind.Value,
|
kind=ct.DataFlowKind.Value,
|
||||||
input_sockets={'Sources', 'Structures', 'Domain', 'BCs', 'Monitors'},
|
input_sockets={'Sources', 'Structures', 'Domain', 'BCs', 'Monitors'},
|
||||||
|
|
|
@ -1,102 +1,124 @@
|
||||||
import typing as typ
|
import bpy
|
||||||
|
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
import sympy.physics.units as spu
|
import sympy.physics.units as spu
|
||||||
|
import scipy as sc
|
||||||
|
|
||||||
from .....assets.import_geonodes import GeoNodes, import_geonodes
|
from .....utils import analyze_geonodes
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from ... import managed_objs, sockets
|
from ... import sockets
|
||||||
from .. import base, events
|
from .. import base
|
||||||
|
from ... import managed_objs
|
||||||
|
|
||||||
|
GEONODES_DOMAIN_BOX = 'simdomain_box'
|
||||||
|
|
||||||
|
|
||||||
class SimDomainNode(base.MaxwellSimNode):
|
class SimDomainNode(base.MaxwellSimNode):
|
||||||
node_type = ct.NodeType.SimDomain
|
node_type = ct.NodeType.SimDomain
|
||||||
bl_label = 'Sim Domain'
|
bl_label = 'Sim Domain'
|
||||||
use_sim_node_name = True
|
|
||||||
|
|
||||||
input_sockets: typ.ClassVar = {
|
input_sockets = {
|
||||||
'Duration': sockets.PhysicalTimeSocketDef(
|
'Duration': sockets.PhysicalTimeSocketDef(
|
||||||
default_value=5 * spu.ps,
|
default_value=5 * spu.ps,
|
||||||
default_unit=spu.ps,
|
default_unit=spu.ps,
|
||||||
),
|
),
|
||||||
'Center': sockets.PhysicalPoint3DSocketDef(),
|
'Center': sockets.PhysicalSize3DSocketDef(),
|
||||||
'Size': sockets.PhysicalSize3DSocketDef(),
|
'Size': sockets.PhysicalSize3DSocketDef(),
|
||||||
'Grid': sockets.MaxwellSimGridSocketDef(),
|
'Grid': sockets.MaxwellSimGridSocketDef(),
|
||||||
'Ambient Medium': sockets.MaxwellMediumSocketDef(),
|
'Ambient Medium': sockets.MaxwellMediumSocketDef(),
|
||||||
}
|
}
|
||||||
output_sockets: typ.ClassVar = {
|
output_sockets = {
|
||||||
'Domain': sockets.MaxwellSimDomainSocketDef(),
|
'Domain': sockets.MaxwellSimDomainSocketDef(),
|
||||||
}
|
}
|
||||||
|
|
||||||
managed_obj_defs: typ.ClassVar = {
|
managed_obj_defs = {
|
||||||
'mesh': ct.schemas.ManagedObjDef(
|
'domain_box': ct.schemas.ManagedObjDef(
|
||||||
mk=lambda name: managed_objs.ManagedBLMesh(name),
|
mk=lambda name: managed_objs.ManagedBLObject(name),
|
||||||
name_prefix='',
|
name_prefix='',
|
||||||
),
|
)
|
||||||
'modifier': ct.schemas.ManagedObjDef(
|
|
||||||
mk=lambda name: managed_objs.ManagedBLModifier(name),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Event Methods
|
# - Callbacks
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@base.computes_output_socket(
|
||||||
'Domain',
|
'Domain',
|
||||||
input_sockets={'Duration', 'Center', 'Size', 'Grid', 'Ambient Medium'},
|
input_sockets={'Duration', 'Center', 'Size', 'Grid', 'Ambient Medium'},
|
||||||
unit_systems={'Tidy3DUnits': ct.UNITS_TIDY3D},
|
|
||||||
scale_input_sockets={
|
|
||||||
'Duration': 'Tidy3DUnits',
|
|
||||||
'Center': 'Tidy3DUnits',
|
|
||||||
'Size': 'Tidy3DUnits',
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
def compute_output(self, input_sockets: dict) -> sp.Expr:
|
def compute_sim_domain(self, input_sockets: dict) -> sp.Expr:
|
||||||
return {
|
if all(
|
||||||
'run_time': input_sockets['Duration'],
|
[
|
||||||
'center': input_sockets['Center'],
|
(_duration := input_sockets['Duration']),
|
||||||
'size': input_sockets['Size'],
|
(_center := input_sockets['Center']),
|
||||||
'grid_spec': input_sockets['Grid'],
|
(_size := input_sockets['Size']),
|
||||||
'medium': input_sockets['Ambient Medium'],
|
(grid := input_sockets['Grid']),
|
||||||
}
|
(medium := input_sockets['Ambient Medium']),
|
||||||
|
]
|
||||||
@events.on_value_changed(
|
|
||||||
socket_name={'Center', 'Size'},
|
|
||||||
prop_name='preview_active',
|
|
||||||
props={'preview_active'},
|
|
||||||
input_sockets={'Center', 'Size'},
|
|
||||||
managed_objs={'mesh', 'modifier'},
|
|
||||||
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
|
||||||
scale_input_sockets={
|
|
||||||
'Center': 'BlenderUnits',
|
|
||||||
},
|
|
||||||
)
|
|
||||||
def on_input_changed(
|
|
||||||
self,
|
|
||||||
props: dict,
|
|
||||||
managed_objs: dict[str, ct.schemas.ManagedObj],
|
|
||||||
input_sockets: dict,
|
|
||||||
unit_systems: dict,
|
|
||||||
):
|
):
|
||||||
# Push Input Values to GeoNodes Modifier
|
duration = spu.convert_to(_duration, spu.second) / spu.second
|
||||||
managed_objs['modifier'].bl_modifier(
|
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
|
||||||
managed_objs['mesh'].bl_object(location=input_sockets['Center']),
|
size = tuple(spu.convert_to(_size, spu.um) / spu.um)
|
||||||
'NODES',
|
return dict(
|
||||||
{
|
run_time=duration,
|
||||||
'node_group': import_geonodes(GeoNodes.PrimitiveBox, 'link'),
|
center=center,
|
||||||
'unit_system': unit_systems['BlenderUnits'],
|
size=size,
|
||||||
'inputs': {
|
grid_spec=grid,
|
||||||
'Size': input_sockets['Size'],
|
medium=medium,
|
||||||
},
|
)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Preview
|
||||||
|
####################
|
||||||
|
@base.on_value_changed(
|
||||||
|
socket_name={'Center', 'Size'},
|
||||||
|
input_sockets={'Center', 'Size'},
|
||||||
|
managed_objs={'domain_box'},
|
||||||
|
)
|
||||||
|
def on_value_changed__center_size(
|
||||||
|
self,
|
||||||
|
input_sockets: dict,
|
||||||
|
managed_objs: dict[str, ct.schemas.ManagedObj],
|
||||||
|
):
|
||||||
|
_center = input_sockets['Center']
|
||||||
|
center = tuple(
|
||||||
|
[float(el) for el in spu.convert_to(_center, spu.um) / spu.um]
|
||||||
|
)
|
||||||
|
|
||||||
|
_size = input_sockets['Size']
|
||||||
|
size = tuple(
|
||||||
|
[float(el) for el in spu.convert_to(_size, spu.um) / spu.um]
|
||||||
|
)
|
||||||
|
## TODO: Preview unit system?? Presume um for now
|
||||||
|
|
||||||
|
# Retrieve Hard-Coded GeoNodes and Analyze Input
|
||||||
|
geo_nodes = bpy.data.node_groups[GEONODES_DOMAIN_BOX]
|
||||||
|
geonodes_interface = analyze_geonodes.interface(
|
||||||
|
geo_nodes, direc='INPUT'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Sync Modifier Inputs
|
||||||
|
managed_objs['domain_box'].sync_geonodes_modifier(
|
||||||
|
geonodes_node_group=geo_nodes,
|
||||||
|
geonodes_identifier_to_value={
|
||||||
|
geonodes_interface['Size'].identifier: size,
|
||||||
|
## TODO: Use 'bl_socket_map.value_to_bl`!
|
||||||
|
## - This accounts for auto-conversion, unit systems, etc. .
|
||||||
|
## - We could keep it in the node base class...
|
||||||
|
## - ...But it needs aligning with Blender, too. Hmm.
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
# Push Preview State
|
|
||||||
if props['preview_active']:
|
|
||||||
managed_objs['mesh'].show_preview()
|
|
||||||
|
|
||||||
@events.on_init()
|
# Sync Object Position
|
||||||
def on_init(self):
|
managed_objs['domain_box'].bl_object('MESH').location = center
|
||||||
self.on_input_change()
|
|
||||||
|
@base.on_show_preview(
|
||||||
|
managed_objs={'domain_box'},
|
||||||
|
)
|
||||||
|
def on_show_preview(
|
||||||
|
self,
|
||||||
|
managed_objs: dict[str, ct.schemas.ManagedObj],
|
||||||
|
):
|
||||||
|
managed_objs['domain_box'].show_preview('MESH')
|
||||||
|
self.on_value_changed__center_size()
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
from . import (
|
from . import automatic_sim_grid_axis
|
||||||
array_sim_grid_axis,
|
from . import manual_sim_grid_axis
|
||||||
automatic_sim_grid_axis,
|
from . import uniform_sim_grid_axis
|
||||||
manual_sim_grid_axis,
|
from . import array_sim_grid_axis
|
||||||
uniform_sim_grid_axis,
|
|
||||||
)
|
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
*automatic_sim_grid_axis.BL_REGISTER,
|
*automatic_sim_grid_axis.BL_REGISTER,
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
# from . import uniform_current_source
|
from . import temporal_shapes
|
||||||
from . import plane_wave_source, point_dipole_source, temporal_shapes
|
|
||||||
|
|
||||||
|
from . import point_dipole_source
|
||||||
|
|
||||||
|
# from . import uniform_current_source
|
||||||
|
from . import plane_wave_source
|
||||||
# from . import gaussian_beam_source
|
# from . import gaussian_beam_source
|
||||||
# from . import astigmatic_gaussian_beam_source
|
# from . import astigmatic_gaussian_beam_source
|
||||||
# from . import tfsf_source
|
# from . import tfsf_source
|
||||||
|
|
|
@ -1,12 +1,19 @@
|
||||||
|
import typing_extensions as typx
|
||||||
import math
|
import math
|
||||||
import typing as typ
|
|
||||||
|
|
||||||
import sympy as sp
|
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
|
import sympy as sp
|
||||||
|
import sympy.physics.units as spu
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
from .....utils import analyze_geonodes
|
||||||
|
from ... import managed_objs
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from ... import managed_objs, sockets
|
from ... import sockets
|
||||||
from .. import base, events
|
from .. import base
|
||||||
|
|
||||||
|
GEONODES_PLANE_WAVE = 'source_plane_wave'
|
||||||
|
|
||||||
|
|
||||||
def convert_vector_to_spherical(
|
def convert_vector_to_spherical(
|
||||||
|
@ -46,17 +53,19 @@ class PlaneWaveSourceNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - Sockets
|
# - Sockets
|
||||||
####################
|
####################
|
||||||
input_sockets: typ.ClassVar = {
|
input_sockets = {
|
||||||
'Temporal Shape': sockets.MaxwellTemporalShapeSocketDef(),
|
'Temporal Shape': sockets.MaxwellTemporalShapeSocketDef(),
|
||||||
'Center': sockets.PhysicalPoint3DSocketDef(),
|
'Center': sockets.PhysicalPoint3DSocketDef(),
|
||||||
'Direction': sockets.Real3DVectorSocketDef(default_value=sp.Matrix([0, 0, -1])),
|
'Direction': sockets.Real3DVectorSocketDef(
|
||||||
|
default_value=sp.Matrix([0, 0, -1])
|
||||||
|
),
|
||||||
'Pol Angle': sockets.PhysicalAngleSocketDef(),
|
'Pol Angle': sockets.PhysicalAngleSocketDef(),
|
||||||
}
|
}
|
||||||
output_sockets: typ.ClassVar = {
|
output_sockets = {
|
||||||
'Source': sockets.MaxwellSourceSocketDef(),
|
'Source': sockets.MaxwellSourceSocketDef(),
|
||||||
}
|
}
|
||||||
|
|
||||||
managed_obj_defs: typ.ClassVar = {
|
managed_obj_defs = {
|
||||||
'plane_wave_source': ct.schemas.ManagedObjDef(
|
'plane_wave_source': ct.schemas.ManagedObjDef(
|
||||||
mk=lambda name: managed_objs.ManagedBLObject(name),
|
mk=lambda name: managed_objs.ManagedBLObject(name),
|
||||||
name_prefix='',
|
name_prefix='',
|
||||||
|
@ -66,20 +75,18 @@ class PlaneWaveSourceNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - Output Socket Computation
|
# - Output Socket Computation
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@base.computes_output_socket(
|
||||||
'Source',
|
'Source',
|
||||||
input_sockets={'Temporal Shape', 'Center', 'Direction', 'Pol Angle'},
|
input_sockets={'Temporal Shape', 'Center', 'Direction', 'Pol Angle'},
|
||||||
unit_systems={'Tidy3DUnits': ct.UNITS_TIDY3D},
|
|
||||||
scale_input_sockets={
|
|
||||||
'Center': 'Tidy3DUnits',
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
def compute_source(self, input_sockets: dict):
|
def compute_source(self, input_sockets: dict):
|
||||||
|
temporal_shape = input_sockets['Temporal Shape']
|
||||||
|
_center = input_sockets['Center']
|
||||||
direction = input_sockets['Direction']
|
direction = input_sockets['Direction']
|
||||||
pol_angle = input_sockets['Pol Angle']
|
pol_angle = input_sockets['Pol Angle']
|
||||||
|
|
||||||
injection_axis, dir_sgn, theta, phi = convert_vector_to_spherical(
|
injection_axis, dir_sgn, theta, phi = convert_vector_to_spherical(
|
||||||
input_sockets['Direction']
|
direction
|
||||||
)
|
)
|
||||||
|
|
||||||
size = {
|
size = {
|
||||||
|
@ -87,11 +94,12 @@ class PlaneWaveSourceNode(base.MaxwellSimNode):
|
||||||
'y': (math.inf, 0, math.inf),
|
'y': (math.inf, 0, math.inf),
|
||||||
'z': (math.inf, math.inf, 0),
|
'z': (math.inf, math.inf, 0),
|
||||||
}[injection_axis]
|
}[injection_axis]
|
||||||
|
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
|
||||||
|
|
||||||
# Display the results
|
# Display the results
|
||||||
return td.PlaneWave(
|
return td.PlaneWave(
|
||||||
center=input_sockets['Center'],
|
center=center,
|
||||||
source_time=input_sockets['Temporal Shape'],
|
source_time=temporal_shape,
|
||||||
size=size,
|
size=size,
|
||||||
direction=dir_sgn,
|
direction=dir_sgn,
|
||||||
angle_theta=theta,
|
angle_theta=theta,
|
||||||
|
@ -99,54 +107,58 @@ class PlaneWaveSourceNode(base.MaxwellSimNode):
|
||||||
pol_angle=pol_angle,
|
pol_angle=pol_angle,
|
||||||
)
|
)
|
||||||
|
|
||||||
#####################
|
####################
|
||||||
## - Preview
|
# - Preview
|
||||||
#####################
|
####################
|
||||||
# @events.on_value_changed(
|
@base.on_value_changed(
|
||||||
# socket_name={'Center', 'Direction'},
|
socket_name={'Center', 'Direction'},
|
||||||
# input_sockets={'Center', 'Direction'},
|
input_sockets={'Center', 'Direction'},
|
||||||
# managed_objs={'plane_wave_source'},
|
managed_objs={'plane_wave_source'},
|
||||||
# )
|
)
|
||||||
# def on_value_changed__center_direction(
|
def on_value_changed__center_direction(
|
||||||
# self,
|
self,
|
||||||
# input_sockets: dict,
|
input_sockets: dict,
|
||||||
# managed_objs: dict[str, ct.schemas.ManagedObj],
|
managed_objs: dict[str, ct.schemas.ManagedObj],
|
||||||
# ):
|
):
|
||||||
# _center = input_sockets['Center']
|
_center = input_sockets['Center']
|
||||||
# center = tuple([float(el) for el in spu.convert_to(_center, spu.um) / spu.um])
|
center = tuple(
|
||||||
|
[float(el) for el in spu.convert_to(_center, spu.um) / spu.um]
|
||||||
|
)
|
||||||
|
|
||||||
# _direction = input_sockets['Direction']
|
_direction = input_sockets['Direction']
|
||||||
# direction = tuple([float(el) for el in _direction])
|
direction = tuple([float(el) for el in _direction])
|
||||||
# ## TODO: Preview unit system?? Presume um for now
|
## TODO: Preview unit system?? Presume um for now
|
||||||
|
|
||||||
# # Retrieve Hard-Coded GeoNodes and Analyze Input
|
# Retrieve Hard-Coded GeoNodes and Analyze Input
|
||||||
# geo_nodes = bpy.data.node_groups[GEONODES_PLANE_WAVE]
|
geo_nodes = bpy.data.node_groups[GEONODES_PLANE_WAVE]
|
||||||
# geonodes_interface = analyze_geonodes.interface(geo_nodes, direc='INPUT')
|
geonodes_interface = analyze_geonodes.interface(
|
||||||
|
geo_nodes, direc='INPUT'
|
||||||
|
)
|
||||||
|
|
||||||
# # Sync Modifier Inputs
|
# Sync Modifier Inputs
|
||||||
# managed_objs['plane_wave_source'].sync_geonodes_modifier(
|
managed_objs['plane_wave_source'].sync_geonodes_modifier(
|
||||||
# geonodes_node_group=geo_nodes,
|
geonodes_node_group=geo_nodes,
|
||||||
# geonodes_identifier_to_value={
|
geonodes_identifier_to_value={
|
||||||
# geonodes_interface['Direction'].identifier: direction,
|
geonodes_interface['Direction'].identifier: direction,
|
||||||
# ## TODO: Use 'bl_socket_map.value_to_bl`!
|
## TODO: Use 'bl_socket_map.value_to_bl`!
|
||||||
# ## - This accounts for auto-conversion, unit systems, etc. .
|
## - This accounts for auto-conversion, unit systems, etc. .
|
||||||
# ## - We could keep it in the node base class...
|
## - We could keep it in the node base class...
|
||||||
# ## - ...But it needs aligning with Blender, too. Hmm.
|
## - ...But it needs aligning with Blender, too. Hmm.
|
||||||
# },
|
},
|
||||||
# )
|
)
|
||||||
|
|
||||||
# # Sync Object Position
|
# Sync Object Position
|
||||||
# managed_objs['plane_wave_source'].bl_object('MESH').location = center
|
managed_objs['plane_wave_source'].bl_object('MESH').location = center
|
||||||
|
|
||||||
# @events.on_show_preview(
|
@base.on_show_preview(
|
||||||
# managed_objs={'plane_wave_source'},
|
managed_objs={'plane_wave_source'},
|
||||||
# )
|
)
|
||||||
# def on_show_preview(
|
def on_show_preview(
|
||||||
# self,
|
self,
|
||||||
# managed_objs: dict[str, ct.schemas.ManagedObj],
|
managed_objs: dict[str, ct.schemas.ManagedObj],
|
||||||
# ):
|
):
|
||||||
# managed_objs['plane_wave_source'].show_preview('MESH')
|
managed_objs['plane_wave_source'].show_preview('MESH')
|
||||||
# self.on_value_changed__center_direction()
|
self.on_value_changed__center_direction()
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
import tidy3d as td
|
||||||
|
import sympy as sp
|
||||||
|
import sympy.physics.units as spu
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import sympy.physics.units as spu
|
|
||||||
import tidy3d as td
|
|
||||||
|
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from ... import managed_objs, sockets
|
from ... import sockets
|
||||||
from .. import base, events
|
from .. import base
|
||||||
|
from ... import managed_objs
|
||||||
|
|
||||||
|
|
||||||
class PointDipoleSourceNode(base.MaxwellSimNode):
|
class PointDipoleSourceNode(base.MaxwellSimNode):
|
||||||
|
@ -64,14 +66,10 @@ class PointDipoleSourceNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - Output Socket Computation
|
# - Output Socket Computation
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@base.computes_output_socket(
|
||||||
'Source',
|
'Source',
|
||||||
input_sockets={'Temporal Shape', 'Center', 'Interpolate'},
|
input_sockets={'Temporal Shape', 'Center', 'Interpolate'},
|
||||||
props={'pol_axis'},
|
props={'pol_axis'},
|
||||||
unit_systems={'Tidy3DUnits': ct.UNITS_TIDY3D},
|
|
||||||
scale_input_sockets={
|
|
||||||
'Center': 'Tidy3DUnits',
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
def compute_source(
|
def compute_source(
|
||||||
self, input_sockets: dict[str, typ.Any], props: dict[str, typ.Any]
|
self, input_sockets: dict[str, typ.Any], props: dict[str, typ.Any]
|
||||||
|
@ -82,46 +80,55 @@ class PointDipoleSourceNode(base.MaxwellSimNode):
|
||||||
'EZ': 'Ez',
|
'EZ': 'Ez',
|
||||||
}[props['pol_axis']]
|
}[props['pol_axis']]
|
||||||
|
|
||||||
return td.PointDipole(
|
temporal_shape = input_sockets['Temporal Shape']
|
||||||
center=input_sockets['Center'],
|
_center = input_sockets['Center']
|
||||||
source_time=input_sockets['Temporal Shape'],
|
interpolate = input_sockets['Interpolate']
|
||||||
interpolate=input_sockets['Interpolate'],
|
|
||||||
|
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
|
||||||
|
|
||||||
|
_res = td.PointDipole(
|
||||||
|
center=center,
|
||||||
|
source_time=temporal_shape,
|
||||||
|
interpolate=interpolate,
|
||||||
polarization=pol_axis,
|
polarization=pol_axis,
|
||||||
)
|
)
|
||||||
|
return _res
|
||||||
|
|
||||||
#####################
|
####################
|
||||||
## - Preview
|
# - Preview
|
||||||
#####################
|
####################
|
||||||
# @events.on_value_changed(
|
@base.on_value_changed(
|
||||||
# socket_name='Center',
|
socket_name='Center',
|
||||||
# input_sockets={'Center'},
|
input_sockets={'Center'},
|
||||||
# managed_objs={'sphere_empty'},
|
managed_objs={'sphere_empty'},
|
||||||
# )
|
)
|
||||||
# def on_value_changed__center(
|
def on_value_changed__center(
|
||||||
# self,
|
self,
|
||||||
# input_sockets: dict,
|
input_sockets: dict,
|
||||||
# managed_objs: dict[str, ct.schemas.ManagedObj],
|
managed_objs: dict[str, ct.schemas.ManagedObj],
|
||||||
# ):
|
):
|
||||||
# _center = input_sockets['Center']
|
_center = input_sockets['Center']
|
||||||
# center = tuple(spu.convert_to(_center, spu.um) / spu.um)
|
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
|
||||||
# ## TODO: Preview unit system?? Presume um for now
|
## TODO: Preview unit system?? Presume um for now
|
||||||
|
|
||||||
# mobj = managed_objs['sphere_empty']
|
mobj = managed_objs['sphere_empty']
|
||||||
# bl_object = mobj.bl_object('EMPTY')
|
bl_object = mobj.bl_object('EMPTY')
|
||||||
# bl_object.location = center # tuple([float(el) for el in center])
|
bl_object.location = center # tuple([float(el) for el in center])
|
||||||
|
|
||||||
# @events.on_show_preview(
|
@base.on_show_preview(
|
||||||
# managed_objs={'sphere_empty'},
|
managed_objs={'sphere_empty'},
|
||||||
# )
|
)
|
||||||
# def on_show_preview(
|
def on_show_preview(
|
||||||
# self,
|
self,
|
||||||
# managed_objs: dict[str, ct.schemas.ManagedObj],
|
managed_objs: dict[str, ct.schemas.ManagedObj],
|
||||||
# ):
|
):
|
||||||
# managed_objs['sphere_empty'].show_preview(
|
managed_objs['sphere_empty'].show_preview(
|
||||||
# 'EMPTY',
|
'EMPTY',
|
||||||
# empty_display_type='SPHERE',
|
empty_display_type='SPHERE',
|
||||||
# )
|
)
|
||||||
# managed_objs['sphere_empty'].bl_object('EMPTY').empty_display_size = 0.2
|
managed_objs['sphere_empty'].bl_object(
|
||||||
|
'EMPTY'
|
||||||
|
).empty_display_size = 0.2
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -130,4 +137,6 @@ class PointDipoleSourceNode(base.MaxwellSimNode):
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
PointDipoleSourceNode,
|
PointDipoleSourceNode,
|
||||||
]
|
]
|
||||||
BL_NODES = {ct.NodeType.PointDipoleSource: (ct.NodeCategory.MAXWELLSIM_SOURCES)}
|
BL_NODES = {
|
||||||
|
ct.NodeType.PointDipoleSource: (ct.NodeCategory.MAXWELLSIM_SOURCES)
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
from . import gaussian_pulse_temporal_shape
|
from . import gaussian_pulse_temporal_shape
|
||||||
|
|
||||||
# from . import continuous_wave_temporal_shape
|
# from . import continuous_wave_temporal_shape
|
||||||
# from . import array_temporal_shape
|
# from . import array_temporal_shape
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import sympy.physics.units as spu
|
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
|
import sympy as sp
|
||||||
|
import sympy.physics.units as spu
|
||||||
|
|
||||||
from .... import contracts, sockets
|
from .... import contracts
|
||||||
from ... import base, events
|
from .... import sockets
|
||||||
|
from ... import base
|
||||||
|
|
||||||
|
|
||||||
class ContinuousWaveTemporalShapeNode(base.MaxwellSimTreeNode):
|
class ContinuousWaveTemporalShapeNode(base.MaxwellSimTreeNode):
|
||||||
|
@ -41,7 +43,7 @@ class ContinuousWaveTemporalShapeNode(base.MaxwellSimTreeNode):
|
||||||
####################
|
####################
|
||||||
# - Output Socket Computation
|
# - Output Socket Computation
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket('temporal_shape')
|
@base.computes_output_socket('temporal_shape')
|
||||||
def compute_source(self: contracts.NodeTypeProtocol) -> td.PointDipole:
|
def compute_source(self: contracts.NodeTypeProtocol) -> td.PointDipole:
|
||||||
_phase = self.compute_input('phase')
|
_phase = self.compute_input('phase')
|
||||||
_freq_center = self.compute_input('freq_center')
|
_freq_center = self.compute_input('freq_center')
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
|
||||||
import bpy
|
|
||||||
import numpy as np
|
|
||||||
import sympy.physics.units as spu
|
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
|
import numpy as np
|
||||||
|
import sympy as sp
|
||||||
|
import sympy.physics.units as spu
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
|
||||||
from ......utils import extra_sympy_units as spuex
|
from ......utils import extra_sympy_units as spuex
|
||||||
from .... import contracts as ct
|
from .... import contracts as ct
|
||||||
from .... import managed_objs, sockets
|
from .... import sockets
|
||||||
from ... import base, events
|
from .... import managed_objs
|
||||||
|
from ... import base
|
||||||
|
|
||||||
|
|
||||||
class GaussianPulseTemporalShapeNode(base.MaxwellSimNode):
|
class GaussianPulseTemporalShapeNode(base.MaxwellSimNode):
|
||||||
|
@ -55,13 +58,17 @@ class GaussianPulseTemporalShapeNode(base.MaxwellSimNode):
|
||||||
name='Plot Time Start (ps)',
|
name='Plot Time Start (ps)',
|
||||||
description='The instance ID of a particular MaxwellSimNode instance, used to index caches',
|
description='The instance ID of a particular MaxwellSimNode instance, used to index caches',
|
||||||
default=0.0,
|
default=0.0,
|
||||||
update=(lambda self, context: self.sync_prop('plot_time_start', context)),
|
update=(
|
||||||
|
lambda self, context: self.sync_prop('plot_time_start', context)
|
||||||
|
),
|
||||||
)
|
)
|
||||||
plot_time_end: bpy.props.FloatProperty(
|
plot_time_end: bpy.props.FloatProperty(
|
||||||
name='Plot Time End (ps)',
|
name='Plot Time End (ps)',
|
||||||
description='The instance ID of a particular MaxwellSimNode instance, used to index caches',
|
description='The instance ID of a particular MaxwellSimNode instance, used to index caches',
|
||||||
default=5,
|
default=5,
|
||||||
update=(lambda self, context: self.sync_prop('plot_time_start', context)),
|
update=(
|
||||||
|
lambda self, context: self.sync_prop('plot_time_start', context)
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -81,7 +88,7 @@ class GaussianPulseTemporalShapeNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - Output Socket Computation
|
# - Output Socket Computation
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@base.computes_output_socket(
|
||||||
'Temporal Shape',
|
'Temporal Shape',
|
||||||
input_sockets={
|
input_sockets={
|
||||||
'Freq Center',
|
'Freq Center',
|
||||||
|
@ -96,7 +103,8 @@ class GaussianPulseTemporalShapeNode(base.MaxwellSimNode):
|
||||||
(_freq_center := input_sockets['Freq Center']) is None
|
(_freq_center := input_sockets['Freq Center']) is None
|
||||||
or (_freq_std := input_sockets['Freq Std.']) is None
|
or (_freq_std := input_sockets['Freq Std.']) is None
|
||||||
or (_phase := input_sockets['Phase']) is None
|
or (_phase := input_sockets['Phase']) is None
|
||||||
or (time_delay_rel_ang_freq := input_sockets['Delay rel. AngFreq']) 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
|
or (remove_dc_component := input_sockets['Remove DC']) is None
|
||||||
):
|
):
|
||||||
raise ValueError('Inputs not defined')
|
raise ValueError('Inputs not defined')
|
||||||
|
@ -115,7 +123,7 @@ class GaussianPulseTemporalShapeNode(base.MaxwellSimNode):
|
||||||
remove_dc_component=remove_dc_component,
|
remove_dc_component=remove_dc_component,
|
||||||
)
|
)
|
||||||
|
|
||||||
@events.on_show_plot(
|
@base.on_show_plot(
|
||||||
managed_objs={'amp_time'},
|
managed_objs={'amp_time'},
|
||||||
props={'plot_time_start', 'plot_time_end'},
|
props={'plot_time_start', 'plot_time_end'},
|
||||||
output_sockets={'Temporal Shape'},
|
output_sockets={'Temporal Shape'},
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
# from . import object_structure
|
# from . import object_structure
|
||||||
from . import geonodes_structure, primitives
|
from . import geonodes_structure
|
||||||
|
|
||||||
|
from . import primitives
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
# *object_structure.BL_REGISTER,
|
# *object_structure.BL_REGISTER,
|
||||||
|
|
|
@ -1,65 +1,69 @@
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
|
import numpy as np
|
||||||
|
import sympy as sp
|
||||||
|
import sympy.physics.units as spu
|
||||||
|
|
||||||
from .....utils import analyze_geonodes, logger
|
import bpy
|
||||||
from ... import bl_socket_map, managed_objs, sockets
|
from bpy_types import bpy_types
|
||||||
|
import bmesh
|
||||||
|
|
||||||
|
from .....utils import analyze_geonodes
|
||||||
|
from ... import bl_socket_map
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from .. import base, events
|
from ... import sockets
|
||||||
|
from .. import base
|
||||||
log = logger.get(__name__)
|
from ... import managed_objs
|
||||||
|
|
||||||
|
|
||||||
class GeoNodesStructureNode(base.MaxwellSimNode):
|
class GeoNodesStructureNode(base.MaxwellSimNode):
|
||||||
node_type = ct.NodeType.GeoNodesStructure
|
node_type = ct.NodeType.GeoNodesStructure
|
||||||
bl_label = 'GeoNodes Structure'
|
bl_label = 'GeoNodes Structure'
|
||||||
use_sim_node_name = True
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Sockets
|
# - Sockets
|
||||||
####################
|
####################
|
||||||
input_sockets: typ.ClassVar = {
|
input_sockets = {
|
||||||
|
'Unit System': sockets.PhysicalUnitSystemSocketDef(),
|
||||||
'Medium': sockets.MaxwellMediumSocketDef(),
|
'Medium': sockets.MaxwellMediumSocketDef(),
|
||||||
'Center': sockets.PhysicalPoint3DSocketDef(),
|
|
||||||
'GeoNodes': sockets.BlenderGeoNodesSocketDef(),
|
'GeoNodes': sockets.BlenderGeoNodesSocketDef(),
|
||||||
}
|
}
|
||||||
output_sockets: typ.ClassVar = {
|
output_sockets = {
|
||||||
'Structure': sockets.MaxwellStructureSocketDef(),
|
'Structure': sockets.MaxwellStructureSocketDef(),
|
||||||
}
|
}
|
||||||
|
|
||||||
managed_obj_defs: typ.ClassVar = {
|
managed_obj_defs = {
|
||||||
'mesh': ct.schemas.ManagedObjDef(
|
'geometry': ct.schemas.ManagedObjDef(
|
||||||
mk=lambda name: managed_objs.ManagedBLMesh(name),
|
mk=lambda name: managed_objs.ManagedBLObject(name),
|
||||||
),
|
name_prefix='',
|
||||||
'modifier': ct.schemas.ManagedObjDef(
|
)
|
||||||
mk=lambda name: managed_objs.ManagedBLModifier(name),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Event Methods
|
# - Output Socket Computation
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@base.computes_output_socket(
|
||||||
'Structure',
|
'Structure',
|
||||||
input_sockets={'Medium'},
|
input_sockets={'Medium'},
|
||||||
managed_objs={'geometry'},
|
managed_objs={'geometry'},
|
||||||
)
|
)
|
||||||
def compute_output(
|
def compute_structure(
|
||||||
self,
|
self,
|
||||||
input_sockets: dict[str, typ.Any],
|
input_sockets: dict[str, typ.Any],
|
||||||
managed_objs: dict[str, ct.schemas.ManagedObj],
|
managed_objs: dict[str, ct.schemas.ManagedObj],
|
||||||
) -> td.Structure:
|
) -> td.Structure:
|
||||||
# Simulate Input Value Change
|
# Extract the Managed Blender Object
|
||||||
## This ensures that the mesh has been re-computed.
|
mobj = managed_objs['geometry']
|
||||||
self.on_input_changed()
|
|
||||||
|
|
||||||
## TODO: mesh_as_arrays might not take the Center into account.
|
# Extract Geometry as Arrays
|
||||||
## - Alternatively, Tidy3D might have a way to transform?
|
geometry_as_arrays = mobj.mesh_as_arrays
|
||||||
mesh_as_arrays = managed_objs['mesh'].mesh_as_arrays
|
|
||||||
|
# Return TriMesh Structure
|
||||||
return td.Structure(
|
return td.Structure(
|
||||||
geometry=td.TriangleMesh.from_vertices_faces(
|
geometry=td.TriangleMesh.from_vertices_faces(
|
||||||
mesh_as_arrays['verts'],
|
geometry_as_arrays['verts'],
|
||||||
mesh_as_arrays['faces'],
|
geometry_as_arrays['faces'],
|
||||||
),
|
),
|
||||||
medium=input_sockets['Medium'],
|
medium=input_sockets['Medium'],
|
||||||
)
|
)
|
||||||
|
@ -67,96 +71,112 @@ class GeoNodesStructureNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - Event Methods
|
# - Event Methods
|
||||||
####################
|
####################
|
||||||
@events.on_value_changed(
|
@base.on_value_changed(
|
||||||
socket_name='GeoNodes',
|
socket_name='GeoNodes',
|
||||||
prop_name='preview_active',
|
managed_objs={'geometry'},
|
||||||
any_loose_input_socket=True,
|
input_sockets={'GeoNodes'},
|
||||||
props={'preview_active'},
|
|
||||||
managed_objs={'mesh', 'modifier'},
|
|
||||||
input_sockets={'Center', 'GeoNodes'},
|
|
||||||
all_loose_input_sockets=True,
|
|
||||||
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
|
||||||
)
|
)
|
||||||
def on_input_changed(
|
def on_value_changed__geonodes(
|
||||||
self,
|
self,
|
||||||
props: dict,
|
|
||||||
managed_objs: dict[str, ct.schemas.ManagedObj],
|
managed_objs: dict[str, ct.schemas.ManagedObj],
|
||||||
input_sockets: dict,
|
input_sockets: dict[str, typ.Any],
|
||||||
loose_input_sockets: dict,
|
|
||||||
unit_systems: dict,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
# No GeoNodes: Remove Modifier (if any)
|
"""Called whenever the GeoNodes socket is changed.
|
||||||
if (geonodes := input_sockets['GeoNodes']) is None:
|
|
||||||
if (
|
|
||||||
managed_objs['modifier'].name
|
|
||||||
in managed_objs['mesh'].bl_object().modifiers
|
|
||||||
):
|
|
||||||
log.info(
|
|
||||||
'Removing Modifier "%s" from BLObject "%s"',
|
|
||||||
managed_objs['modifier'].name,
|
|
||||||
managed_objs['mesh'].name,
|
|
||||||
)
|
|
||||||
managed_objs['mesh'].bl_object().modifiers.remove(
|
|
||||||
managed_objs['modifier'].name
|
|
||||||
)
|
|
||||||
|
|
||||||
# Reset Loose Input Sockets
|
Refreshes the Loose Input Sockets, which map directly to the GeoNodes tree input sockets.
|
||||||
|
"""
|
||||||
|
if not (geo_nodes := input_sockets['GeoNodes']):
|
||||||
|
managed_objs['geometry'].free()
|
||||||
self.loose_input_sockets = {}
|
self.loose_input_sockets = {}
|
||||||
return
|
return
|
||||||
|
|
||||||
# No Loose Input Sockets: Create from GeoNodes Interface
|
# Analyze GeoNodes
|
||||||
## TODO: Other reasons to trigger re-filling loose_input_sockets.
|
## Extract Valid Inputs (via GeoNodes Tree "Interface")
|
||||||
if not loose_input_sockets:
|
|
||||||
# Retrieve the GeoNodes Interface
|
|
||||||
geonodes_interface = analyze_geonodes.interface(
|
geonodes_interface = analyze_geonodes.interface(
|
||||||
input_sockets['GeoNodes'], direc='INPUT'
|
geo_nodes, direc='INPUT'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Fill the Loose Input Sockets
|
# Set Loose Input Sockets
|
||||||
log.info(
|
## Retrieve the appropriate SocketDef for the Blender Interface Socket
|
||||||
'Initializing GeoNodes Structure Node "%s" from GeoNodes Group "%s"',
|
|
||||||
self.bl_label,
|
|
||||||
str(geonodes),
|
|
||||||
)
|
|
||||||
self.loose_input_sockets = {
|
self.loose_input_sockets = {
|
||||||
socket_name: bl_socket_map.socket_def_from_bl_socket(iface_socket)()
|
socket_name: bl_socket_map.socket_def_from_bl_interface_socket(
|
||||||
for socket_name, iface_socket in geonodes_interface.items()
|
bl_interface_socket
|
||||||
|
)() ## === <SocketType>SocketDef(), but with dynamic SocketDef
|
||||||
|
for socket_name, bl_interface_socket in geonodes_interface.items()
|
||||||
}
|
}
|
||||||
|
|
||||||
# Set Loose Input Sockets to Interface (Default) Values
|
## Set Loose `socket.value` from Interface `default_value`
|
||||||
## Changing socket.value invokes recursion of this function.
|
|
||||||
## The else: below ensures that only one push occurs.
|
|
||||||
## (well, one push per .value set, which simplifies to one push)
|
|
||||||
log.info(
|
|
||||||
'Setting Loose Input Sockets of "%s" to GeoNodes Defaults',
|
|
||||||
self.bl_label,
|
|
||||||
)
|
|
||||||
for socket_name in self.loose_input_sockets:
|
for socket_name in self.loose_input_sockets:
|
||||||
socket = self.inputs[socket_name]
|
socket = self.inputs[socket_name]
|
||||||
socket.value = bl_socket_map.read_bl_socket_default_value(
|
bl_interface_socket = geonodes_interface[socket_name]
|
||||||
geonodes_interface[socket_name],
|
|
||||||
unit_systems['BlenderUnits'],
|
socket.value = bl_socket_map.value_from_bl(bl_interface_socket)
|
||||||
allow_unit_not_in_unit_system=True,
|
|
||||||
|
## Implicitly triggers the loose-input `on_value_changed` for each.
|
||||||
|
|
||||||
|
@base.on_value_changed(
|
||||||
|
any_loose_input_socket=True,
|
||||||
|
managed_objs={'geometry'},
|
||||||
|
input_sockets={'Unit System', 'GeoNodes'},
|
||||||
)
|
)
|
||||||
log.info(
|
def on_value_changed__loose_inputs(
|
||||||
'Set Loose Input Sockets of "%s" to: %s',
|
self,
|
||||||
self.bl_label,
|
managed_objs: dict[str, ct.schemas.ManagedObj],
|
||||||
str(self.loose_input_sockets),
|
input_sockets: dict[str, typ.Any],
|
||||||
|
loose_input_sockets: dict[str, typ.Any],
|
||||||
|
):
|
||||||
|
"""Called whenever a Loose Input Socket is altered.
|
||||||
|
|
||||||
|
Synchronizes the change to the actual GeoNodes modifier, so that the change is immediately visible.
|
||||||
|
"""
|
||||||
|
# Retrieve Data
|
||||||
|
unit_system = input_sockets['Unit System']
|
||||||
|
mobj = managed_objs['geometry']
|
||||||
|
|
||||||
|
if not (geo_nodes := input_sockets['GeoNodes']):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Analyze GeoNodes Interface (input direction)
|
||||||
|
## This retrieves NodeTreeSocketInterface elements
|
||||||
|
geonodes_interface = analyze_geonodes.interface(
|
||||||
|
geo_nodes, direc='INPUT'
|
||||||
|
)
|
||||||
|
|
||||||
|
## TODO: Check that Loose Sockets matches the Interface
|
||||||
|
## - If the user deletes an interface socket, bad things will happen.
|
||||||
|
## - We will try to set an identifier that doesn't exist!
|
||||||
|
## - Instead, this should update the loose input sockets.
|
||||||
|
|
||||||
|
## Push Values to the GeoNodes Modifier
|
||||||
|
mobj.sync_geonodes_modifier(
|
||||||
|
geonodes_node_group=geo_nodes,
|
||||||
|
geonodes_identifier_to_value={
|
||||||
|
bl_interface_socket.identifier: bl_socket_map.value_to_bl(
|
||||||
|
bl_interface_socket,
|
||||||
|
loose_input_sockets[socket_name],
|
||||||
|
unit_system,
|
||||||
|
)
|
||||||
|
for socket_name, bl_interface_socket in (
|
||||||
|
geonodes_interface.items()
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
# Push Loose Input Values to GeoNodes Modifier
|
|
||||||
managed_objs['modifier'].bl_modifier(
|
|
||||||
managed_objs['mesh'].bl_object(location=input_sockets['Center']),
|
|
||||||
'NODES',
|
|
||||||
{
|
|
||||||
'node_group': input_sockets['GeoNodes'],
|
|
||||||
'unit_system': unit_systems['BlenderUnits'],
|
|
||||||
'inputs': loose_input_sockets,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
# Push Preview State
|
|
||||||
if props['preview_active']:
|
####################
|
||||||
managed_objs['mesh'].show_preview()
|
# - Event Methods
|
||||||
|
####################
|
||||||
|
@base.on_show_preview(
|
||||||
|
managed_objs={'geometry'},
|
||||||
|
)
|
||||||
|
def on_show_preview(
|
||||||
|
self,
|
||||||
|
managed_objs: dict[str, ct.schemas.ManagedObj],
|
||||||
|
):
|
||||||
|
"""Called whenever a Loose Input Socket is altered.
|
||||||
|
|
||||||
|
Synchronizes the change to the actual GeoNodes modifier, so that the change is immediately visible.
|
||||||
|
"""
|
||||||
|
managed_objs['geometry'].show_preview('MESH')
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -165,4 +185,6 @@ class GeoNodesStructureNode(base.MaxwellSimNode):
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
GeoNodesStructureNode,
|
GeoNodesStructureNode,
|
||||||
]
|
]
|
||||||
BL_NODES = {ct.NodeType.GeoNodesStructure: (ct.NodeCategory.MAXWELLSIM_STRUCTURES)}
|
BL_NODES = {
|
||||||
|
ct.NodeType.GeoNodesStructure: (ct.NodeCategory.MAXWELLSIM_STRUCTURES)
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
import bmesh
|
|
||||||
import bpy
|
|
||||||
import numpy as np
|
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
|
import numpy as np
|
||||||
|
import sympy as sp
|
||||||
|
import sympy.physics.units as spu
|
||||||
|
|
||||||
from ... import contracts, sockets
|
import bpy
|
||||||
from .. import base, events
|
import bmesh
|
||||||
|
|
||||||
|
from ... import contracts
|
||||||
|
from ... import sockets
|
||||||
|
from .. import base
|
||||||
|
|
||||||
|
|
||||||
class ObjectStructureNode(base.MaxwellSimTreeNode):
|
class ObjectStructureNode(base.MaxwellSimTreeNode):
|
||||||
|
@ -32,7 +36,7 @@ class ObjectStructureNode(base.MaxwellSimTreeNode):
|
||||||
####################
|
####################
|
||||||
# - Output Socket Computation
|
# - Output Socket Computation
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket('structure')
|
@base.computes_output_socket('structure')
|
||||||
def compute_structure(self: contracts.NodeTypeProtocol) -> td.Structure:
|
def compute_structure(self: contracts.NodeTypeProtocol) -> td.Structure:
|
||||||
# Extract the Blender Object
|
# Extract the Blender Object
|
||||||
bl_object = self.compute_input('object')
|
bl_object = self.compute_input('object')
|
||||||
|
@ -54,7 +58,9 @@ class ObjectStructureNode(base.MaxwellSimTreeNode):
|
||||||
|
|
||||||
# Extract Vertices and Faces
|
# Extract Vertices and Faces
|
||||||
vertices = np.array([vert.co for vert in mesh.vertices])
|
vertices = np.array([vert.co for vert in mesh.vertices])
|
||||||
faces = np.array([[vert for vert in poly.vertices] for poly in mesh.polygons])
|
faces = np.array(
|
||||||
|
[[vert for vert in poly.vertices] for poly in mesh.polygons]
|
||||||
|
)
|
||||||
|
|
||||||
# Remove Temporary Mesh
|
# Remove Temporary Mesh
|
||||||
bpy.data.meshes.remove(mesh)
|
bpy.data.meshes.remove(mesh)
|
||||||
|
@ -72,5 +78,7 @@ BL_REGISTER = [
|
||||||
ObjectStructureNode,
|
ObjectStructureNode,
|
||||||
]
|
]
|
||||||
BL_NODES = {
|
BL_NODES = {
|
||||||
contracts.NodeType.ObjectStructure: (contracts.NodeCategory.MAXWELLSIM_STRUCTURES)
|
contracts.NodeType.ObjectStructure: (
|
||||||
|
contracts.NodeCategory.MAXWELLSIM_STRUCTURES
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
from . import box_structure
|
||||||
|
|
||||||
# from . import cylinder_structure
|
# from . import cylinder_structure
|
||||||
from . import box_structure, sphere_structure
|
from . import sphere_structure
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
*box_structure.BL_REGISTER,
|
*box_structure.BL_REGISTER,
|
||||||
|
|
|
@ -1,101 +1,123 @@
|
||||||
import typing as typ
|
import tidy3d as td
|
||||||
|
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
import sympy.physics.units as spu
|
import sympy.physics.units as spu
|
||||||
import tidy3d as td
|
|
||||||
|
|
||||||
from ......assets.import_geonodes import GeoNodes, import_geonodes
|
import bpy
|
||||||
|
|
||||||
|
from ......utils import analyze_geonodes
|
||||||
from .... import contracts as ct
|
from .... import contracts as ct
|
||||||
from .... import managed_objs, sockets
|
from .... import sockets
|
||||||
from ... import base, events
|
from .... import managed_objs
|
||||||
|
from ... import base
|
||||||
|
|
||||||
|
GEONODES_STRUCTURE_BOX = 'structure_box'
|
||||||
|
|
||||||
|
|
||||||
class BoxStructureNode(base.MaxwellSimNode):
|
class BoxStructureNode(base.MaxwellSimNode):
|
||||||
node_type = ct.NodeType.BoxStructure
|
node_type = ct.NodeType.BoxStructure
|
||||||
bl_label = 'Box Structure'
|
bl_label = 'Box Structure'
|
||||||
use_sim_node_name = True
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Sockets
|
# - Sockets
|
||||||
####################
|
####################
|
||||||
input_sockets: typ.ClassVar = {
|
input_sockets = {
|
||||||
'Medium': sockets.MaxwellMediumSocketDef(),
|
'Medium': sockets.MaxwellMediumSocketDef(),
|
||||||
'Center': sockets.PhysicalPoint3DSocketDef(),
|
'Center': sockets.PhysicalPoint3DSocketDef(),
|
||||||
'Size': sockets.PhysicalSize3DSocketDef(
|
'Size': sockets.PhysicalSize3DSocketDef(
|
||||||
default_value=sp.Matrix([500, 500, 500]) * spu.nm
|
default_value=sp.Matrix([500, 500, 500]) * spu.nm
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
output_sockets: typ.ClassVar = {
|
output_sockets = {
|
||||||
'Structure': sockets.MaxwellStructureSocketDef(),
|
'Structure': sockets.MaxwellStructureSocketDef(),
|
||||||
}
|
}
|
||||||
|
|
||||||
managed_obj_defs: typ.ClassVar = {
|
managed_obj_defs = {
|
||||||
'mesh': ct.schemas.ManagedObjDef(
|
'structure_box': ct.schemas.ManagedObjDef(
|
||||||
mk=lambda name: managed_objs.ManagedBLMesh(name),
|
mk=lambda name: managed_objs.ManagedBLObject(name),
|
||||||
),
|
name_prefix='',
|
||||||
'modifier': ct.schemas.ManagedObjDef(
|
)
|
||||||
mk=lambda name: managed_objs.ManagedBLModifier(name),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Event Methods
|
# - Output Socket Computation
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@base.computes_output_socket(
|
||||||
'Structure',
|
'Structure',
|
||||||
input_sockets={'Medium', 'Center', 'Size'},
|
input_sockets={'Medium', 'Center', 'Size'},
|
||||||
unit_systems={'Tidy3DUnits': ct.UNITS_TIDY3D},
|
|
||||||
scale_input_sockets={
|
|
||||||
'Center': 'Tidy3DUnits',
|
|
||||||
'Size': 'Tidy3DUnits',
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
def compute_output(self, input_sockets: dict, unit_systems: dict) -> td.Box:
|
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)
|
||||||
|
|
||||||
return td.Structure(
|
return td.Structure(
|
||||||
geometry=td.Box(
|
geometry=td.Box(
|
||||||
center=input_sockets['Center'],
|
center=center,
|
||||||
size=input_sockets['Size'],
|
size=size,
|
||||||
),
|
),
|
||||||
medium=input_sockets['Medium'],
|
medium=medium,
|
||||||
)
|
)
|
||||||
|
|
||||||
@events.on_value_changed(
|
####################
|
||||||
|
# - Preview - Changes to Input Sockets
|
||||||
|
####################
|
||||||
|
@base.on_value_changed(
|
||||||
socket_name={'Center', 'Size'},
|
socket_name={'Center', 'Size'},
|
||||||
prop_name='preview_active',
|
|
||||||
props={'preview_active'},
|
|
||||||
input_sockets={'Center', 'Size'},
|
input_sockets={'Center', 'Size'},
|
||||||
managed_objs={'mesh', 'modifier'},
|
managed_objs={'structure_box'},
|
||||||
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
|
||||||
scale_input_sockets={
|
|
||||||
'Center': 'BlenderUnits',
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
def on_inputs_changed(
|
def on_value_changed__center_size(
|
||||||
self,
|
self,
|
||||||
props: dict,
|
|
||||||
managed_objs: dict[str, ct.schemas.ManagedObj],
|
|
||||||
input_sockets: dict,
|
input_sockets: dict,
|
||||||
unit_systems: dict,
|
managed_objs: dict[str, ct.schemas.ManagedObj],
|
||||||
):
|
):
|
||||||
# Push Input Values to GeoNodes Modifier
|
_center = input_sockets['Center']
|
||||||
managed_objs['modifier'].bl_modifier(
|
center = tuple(
|
||||||
managed_objs['mesh'].bl_object(location=input_sockets['Center']),
|
[float(el) for el in spu.convert_to(_center, spu.um) / spu.um]
|
||||||
'NODES',
|
)
|
||||||
{
|
|
||||||
'node_group': import_geonodes(GeoNodes.PrimitiveBox, 'link'),
|
_size = input_sockets['Size']
|
||||||
'unit_system': unit_systems['BlenderUnits'],
|
size = tuple(
|
||||||
'inputs': {
|
[float(el) for el in spu.convert_to(_size, spu.um) / spu.um]
|
||||||
'Size': input_sockets['Size'],
|
)
|
||||||
},
|
## TODO: Preview unit system?? Presume um for now
|
||||||
|
|
||||||
|
# Retrieve Hard-Coded GeoNodes and Analyze Input
|
||||||
|
geo_nodes = bpy.data.node_groups[GEONODES_STRUCTURE_BOX]
|
||||||
|
geonodes_interface = analyze_geonodes.interface(
|
||||||
|
geo_nodes, direc='INPUT'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Sync Modifier Inputs
|
||||||
|
managed_objs['structure_box'].sync_geonodes_modifier(
|
||||||
|
geonodes_node_group=geo_nodes,
|
||||||
|
geonodes_identifier_to_value={
|
||||||
|
geonodes_interface['Size'].identifier: size,
|
||||||
|
## TODO: Use 'bl_socket_map.value_to_bl`!
|
||||||
|
## - This accounts for auto-conversion, unit systems, etc. .
|
||||||
|
## - We could keep it in the node base class...
|
||||||
|
## - ...But it needs aligning with Blender, too. Hmm.
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
# Push Preview State
|
|
||||||
if props['preview_active']:
|
|
||||||
managed_objs['mesh'].show_preview()
|
|
||||||
|
|
||||||
@events.on_init()
|
# Sync Object Position
|
||||||
def on_init(self):
|
managed_objs['structure_box'].bl_object('MESH').location = center
|
||||||
self.on_inputs_changed()
|
|
||||||
|
####################
|
||||||
|
# - Preview - Show Preview
|
||||||
|
####################
|
||||||
|
@base.on_show_preview(
|
||||||
|
managed_objs={'structure_box'},
|
||||||
|
)
|
||||||
|
def on_show_preview(
|
||||||
|
self,
|
||||||
|
managed_objs: dict[str, ct.schemas.ManagedObj],
|
||||||
|
):
|
||||||
|
managed_objs['structure_box'].show_preview('MESH')
|
||||||
|
self.on_value_changed__center_size()
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -105,5 +127,7 @@ BL_REGISTER = [
|
||||||
BoxStructureNode,
|
BoxStructureNode,
|
||||||
]
|
]
|
||||||
BL_NODES = {
|
BL_NODES = {
|
||||||
ct.NodeType.BoxStructure: (ct.NodeCategory.MAXWELLSIM_STRUCTURES_PRIMITIVES)
|
ct.NodeType.BoxStructure: (
|
||||||
|
ct.NodeCategory.MAXWELLSIM_STRUCTURES_PRIMITIVES
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import sympy.physics.units as spu
|
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
|
import sympy as sp
|
||||||
|
import sympy.physics.units as spu
|
||||||
|
|
||||||
from .... import contracts, sockets
|
from .... import contracts
|
||||||
from ... import base, events
|
from .... import sockets
|
||||||
|
from ... import base
|
||||||
|
|
||||||
|
|
||||||
class CylinderStructureNode(base.MaxwellSimTreeNode):
|
class CylinderStructureNode(base.MaxwellSimTreeNode):
|
||||||
|
@ -36,7 +38,7 @@ class CylinderStructureNode(base.MaxwellSimTreeNode):
|
||||||
####################
|
####################
|
||||||
# - Output Socket Computation
|
# - Output Socket Computation
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket('structure')
|
@base.computes_output_socket('structure')
|
||||||
def compute_simulation(self: contracts.NodeTypeProtocol) -> td.Box:
|
def compute_simulation(self: contracts.NodeTypeProtocol) -> td.Box:
|
||||||
medium = self.compute_input('medium')
|
medium = self.compute_input('medium')
|
||||||
_center = self.compute_input('center')
|
_center = self.compute_input('center')
|
||||||
|
|
|
@ -1,104 +1,121 @@
|
||||||
import typing as typ
|
|
||||||
|
|
||||||
import sympy.physics.units as spu
|
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
|
import sympy as sp
|
||||||
|
import sympy.physics.units as spu
|
||||||
|
|
||||||
from ......assets.import_geonodes import GeoNodes, import_geonodes
|
import bpy
|
||||||
|
|
||||||
|
from ......utils import analyze_geonodes
|
||||||
from .... import contracts as ct
|
from .... import contracts as ct
|
||||||
from .... import managed_objs, sockets
|
from .... import sockets
|
||||||
from ... import base, events
|
from .... import managed_objs
|
||||||
|
from ... import base
|
||||||
|
|
||||||
|
GEONODES_STRUCTURE_SPHERE = 'structure_sphere'
|
||||||
|
|
||||||
|
|
||||||
class SphereStructureNode(base.MaxwellSimNode):
|
class SphereStructureNode(base.MaxwellSimNode):
|
||||||
node_type = ct.NodeType.SphereStructure
|
node_type = ct.NodeType.SphereStructure
|
||||||
bl_label = 'Sphere Structure'
|
bl_label = 'Sphere Structure'
|
||||||
use_sim_node_name = True
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Sockets
|
# - Sockets
|
||||||
####################
|
####################
|
||||||
input_sockets: typ.ClassVar = {
|
input_sockets = {
|
||||||
'Center': sockets.PhysicalPoint3DSocketDef(),
|
'Center': sockets.PhysicalPoint3DSocketDef(),
|
||||||
'Radius': sockets.PhysicalLengthSocketDef(
|
'Radius': sockets.PhysicalLengthSocketDef(
|
||||||
default_value=150 * spu.nm,
|
default_value=150 * spu.nm,
|
||||||
),
|
),
|
||||||
'Medium': sockets.MaxwellMediumSocketDef(),
|
'Medium': sockets.MaxwellMediumSocketDef(),
|
||||||
}
|
}
|
||||||
output_sockets: typ.ClassVar = {
|
output_sockets = {
|
||||||
'Structure': sockets.MaxwellStructureSocketDef(),
|
'Structure': sockets.MaxwellStructureSocketDef(),
|
||||||
}
|
}
|
||||||
|
|
||||||
managed_obj_defs: typ.ClassVar = {
|
managed_obj_defs = {
|
||||||
'mesh': ct.schemas.ManagedObjDef(
|
'structure_sphere': ct.schemas.ManagedObjDef(
|
||||||
mk=lambda name: managed_objs.ManagedBLMesh(name),
|
mk=lambda name: managed_objs.ManagedBLObject(name),
|
||||||
),
|
name_prefix='',
|
||||||
'modifier': ct.schemas.ManagedObjDef(
|
)
|
||||||
mk=lambda name: managed_objs.ManagedBLModifier(name),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Output Socket Computation
|
# - Output Socket Computation
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@base.computes_output_socket(
|
||||||
'Structure',
|
'Structure',
|
||||||
input_sockets={'Center', 'Radius', 'Medium'},
|
input_sockets={'Center', 'Radius', 'Medium'},
|
||||||
unit_systems={'Tidy3DUnits': ct.UNITS_TIDY3D},
|
|
||||||
scale_input_sockets={
|
|
||||||
'Center': 'Tidy3DUnits',
|
|
||||||
'Radius': 'Tidy3DUnits',
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
def compute_structure(self, input_sockets: dict) -> td.Box:
|
def compute_structure(self, input_sockets: dict) -> td.Box:
|
||||||
|
medium = input_sockets['Medium']
|
||||||
|
_center = input_sockets['Center']
|
||||||
|
_radius = input_sockets['Radius']
|
||||||
|
|
||||||
|
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
|
||||||
|
radius = spu.convert_to(_radius, spu.um) / spu.um
|
||||||
|
|
||||||
return td.Structure(
|
return td.Structure(
|
||||||
geometry=td.Sphere(
|
geometry=td.Sphere(
|
||||||
radius=input_sockets['Radius'],
|
radius=radius,
|
||||||
center=input_sockets['Center'],
|
center=center,
|
||||||
),
|
),
|
||||||
medium=input_sockets['Medium'],
|
medium=medium,
|
||||||
)
|
)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Preview - Changes to Input Sockets
|
# - Preview - Changes to Input Sockets
|
||||||
####################
|
####################
|
||||||
@events.on_value_changed(
|
@base.on_value_changed(
|
||||||
socket_name={'Center', 'Radius'},
|
socket_name={'Center', 'Radius'},
|
||||||
prop_name='preview_active',
|
|
||||||
props={'preview_active'},
|
|
||||||
input_sockets={'Center', 'Radius'},
|
input_sockets={'Center', 'Radius'},
|
||||||
managed_objs={'mesh', 'modifier'},
|
managed_objs={'structure_sphere'},
|
||||||
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
|
||||||
scale_input_sockets={
|
|
||||||
'Center': 'Tidy3DUnits',
|
|
||||||
'Radius': 'Tidy3DUnits',
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
def on_inputs_changed(
|
def on_value_changed__center_radius(
|
||||||
self,
|
self,
|
||||||
props: dict,
|
|
||||||
managed_objs: dict[str, ct.schemas.ManagedObj],
|
|
||||||
input_sockets: dict,
|
input_sockets: dict,
|
||||||
unit_systems: dict,
|
managed_objs: dict[str, ct.schemas.ManagedObj],
|
||||||
):
|
):
|
||||||
# Push Input Values to GeoNodes Modifier
|
_center = input_sockets['Center']
|
||||||
managed_objs['modifier'].bl_modifier(
|
center = tuple(
|
||||||
managed_objs['mesh'].bl_object(location=input_sockets['Center']),
|
[float(el) for el in spu.convert_to(_center, spu.um) / spu.um]
|
||||||
'NODES',
|
)
|
||||||
{
|
|
||||||
'node_group': import_geonodes(GeoNodes.PrimitiveSphere, 'link'),
|
_radius = input_sockets['Radius']
|
||||||
'unit_system': unit_systems['BlenderUnits'],
|
radius = float(spu.convert_to(_radius, spu.um) / spu.um)
|
||||||
'inputs': {
|
## TODO: Preview unit system?? Presume um for now
|
||||||
'Radius': input_sockets['Radius'],
|
|
||||||
},
|
# Retrieve Hard-Coded GeoNodes and Analyze Input
|
||||||
|
geo_nodes = bpy.data.node_groups[GEONODES_STRUCTURE_SPHERE]
|
||||||
|
geonodes_interface = analyze_geonodes.interface(
|
||||||
|
geo_nodes, direc='INPUT'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Sync Modifier Inputs
|
||||||
|
managed_objs['structure_sphere'].sync_geonodes_modifier(
|
||||||
|
geonodes_node_group=geo_nodes,
|
||||||
|
geonodes_identifier_to_value={
|
||||||
|
geonodes_interface['Radius'].identifier: radius,
|
||||||
|
## TODO: Use 'bl_socket_map.value_to_bl`!
|
||||||
|
## - This accounts for auto-conversion, unit systems, etc. .
|
||||||
|
## - We could keep it in the node base class...
|
||||||
|
## - ...But it needs aligning with Blender, too. Hmm.
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
# Push Preview State
|
|
||||||
if props['preview_active']:
|
|
||||||
managed_objs['mesh'].show_preview()
|
|
||||||
|
|
||||||
@events.on_init()
|
# Sync Object Position
|
||||||
def on_init(self):
|
managed_objs['structure_sphere'].bl_object('MESH').location = center
|
||||||
self.on_inputs_changed()
|
|
||||||
|
####################
|
||||||
|
# - Preview - Show Preview
|
||||||
|
####################
|
||||||
|
@base.on_show_preview(
|
||||||
|
managed_objs={'structure_sphere'},
|
||||||
|
)
|
||||||
|
def on_show_preview(
|
||||||
|
self,
|
||||||
|
managed_objs: dict[str, ct.schemas.ManagedObj],
|
||||||
|
):
|
||||||
|
managed_objs['structure_sphere'].show_preview('MESH')
|
||||||
|
self.on_value_changed__center_radius()
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -108,5 +125,7 @@ BL_REGISTER = [
|
||||||
SphereStructureNode,
|
SphereStructureNode,
|
||||||
]
|
]
|
||||||
BL_NODES = {
|
BL_NODES = {
|
||||||
ct.NodeType.SphereStructure: (ct.NodeCategory.MAXWELLSIM_STRUCTURES_PRIMITIVES)
|
ct.NodeType.SphereStructure: (
|
||||||
|
ct.NodeCategory.MAXWELLSIM_STRUCTURES_PRIMITIVES
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
# from . import math
|
# from . import math
|
||||||
from . import combine
|
from . import combine
|
||||||
|
|
||||||
# from . import separate
|
# from . import separate
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import bpy
|
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
|
import sympy.physics.units as spu
|
||||||
|
import scipy as sc
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from ... import sockets
|
from ... import sockets
|
||||||
from .. import base, events
|
from .. import base
|
||||||
|
|
||||||
MAX_AMOUNT = 20
|
MAX_AMOUNT = 20
|
||||||
|
|
||||||
|
@ -20,7 +23,9 @@ class CombineNode(base.MaxwellSimNode):
|
||||||
'Maxwell Sources': {},
|
'Maxwell Sources': {},
|
||||||
'Maxwell Structures': {},
|
'Maxwell Structures': {},
|
||||||
'Maxwell Monitors': {},
|
'Maxwell Monitors': {},
|
||||||
'Real 3D Vector': {f'x_{i}': sockets.RealNumberSocketDef() for i in range(3)},
|
'Real 3D Vector': {
|
||||||
|
f'x_{i}': sockets.RealNumberSocketDef() for i in range(3)
|
||||||
|
},
|
||||||
# "Point 3D": {
|
# "Point 3D": {
|
||||||
# axis: sockets.PhysicalLengthSocketDef()
|
# axis: sockets.PhysicalLengthSocketDef()
|
||||||
# for i, axis in zip(
|
# for i, axis in zip(
|
||||||
|
@ -82,13 +87,13 @@ class CombineNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - Output Socket Computation
|
# - Output Socket Computation
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@base.computes_output_socket(
|
||||||
'Real 3D Vector', input_sockets={'x_0', 'x_1', 'x_2'}
|
'Real 3D Vector', input_sockets={'x_0', 'x_1', 'x_2'}
|
||||||
)
|
)
|
||||||
def compute_real_3d_vector(self, input_sockets) -> sp.Expr:
|
def compute_real_3d_vector(self, input_sockets) -> sp.Expr:
|
||||||
return sp.Matrix([input_sockets[f'x_{i}'] for i in range(3)])
|
return sp.Matrix([input_sockets[f'x_{i}'] for i in range(3)])
|
||||||
|
|
||||||
@events.computes_output_socket(
|
@base.computes_output_socket(
|
||||||
'Sources',
|
'Sources',
|
||||||
input_sockets={f'Source #{i}' for i in range(MAX_AMOUNT)},
|
input_sockets={f'Source #{i}' for i in range(MAX_AMOUNT)},
|
||||||
props={'amount'},
|
props={'amount'},
|
||||||
|
@ -96,15 +101,17 @@ class CombineNode(base.MaxwellSimNode):
|
||||||
def compute_sources(self, input_sockets, props) -> sp.Expr:
|
def compute_sources(self, input_sockets, props) -> sp.Expr:
|
||||||
return [input_sockets[f'Source #{i}'] for i in range(props['amount'])]
|
return [input_sockets[f'Source #{i}'] for i in range(props['amount'])]
|
||||||
|
|
||||||
@events.computes_output_socket(
|
@base.computes_output_socket(
|
||||||
'Structures',
|
'Structures',
|
||||||
input_sockets={f'Structure #{i}' for i in range(MAX_AMOUNT)},
|
input_sockets={f'Structure #{i}' for i in range(MAX_AMOUNT)},
|
||||||
props={'amount'},
|
props={'amount'},
|
||||||
)
|
)
|
||||||
def compute_structures(self, input_sockets, props) -> sp.Expr:
|
def compute_structures(self, input_sockets, props) -> sp.Expr:
|
||||||
return [input_sockets[f'Structure #{i}'] for i in range(props['amount'])]
|
return [
|
||||||
|
input_sockets[f'Structure #{i}'] for i in range(props['amount'])
|
||||||
|
]
|
||||||
|
|
||||||
@events.computes_output_socket(
|
@base.computes_output_socket(
|
||||||
'Monitors',
|
'Monitors',
|
||||||
input_sockets={f'Monitor #{i}' for i in range(MAX_AMOUNT)},
|
input_sockets={f'Monitor #{i}' for i in range(MAX_AMOUNT)},
|
||||||
props={'amount'},
|
props={'amount'},
|
||||||
|
@ -115,7 +122,7 @@ class CombineNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - Input Socket Compilation
|
# - Input Socket Compilation
|
||||||
####################
|
####################
|
||||||
@events.on_value_changed(
|
@base.on_value_changed(
|
||||||
prop_name='active_socket_set',
|
prop_name='active_socket_set',
|
||||||
props={'active_socket_set', 'amount'},
|
props={'active_socket_set', 'amount'},
|
||||||
)
|
)
|
||||||
|
@ -138,13 +145,13 @@ class CombineNode(base.MaxwellSimNode):
|
||||||
else:
|
else:
|
||||||
self.loose_input_sockets = {}
|
self.loose_input_sockets = {}
|
||||||
|
|
||||||
@events.on_value_changed(
|
@base.on_value_changed(
|
||||||
prop_name='amount',
|
prop_name='amount',
|
||||||
)
|
)
|
||||||
def on_value_changed__amount(self):
|
def on_value_changed__amount(self):
|
||||||
self.on_value_changed__active_socket_set()
|
self.on_value_changed__active_socket_set()
|
||||||
|
|
||||||
@events.on_init()
|
@base.on_init()
|
||||||
def on_init(self):
|
def on_init(self):
|
||||||
self.on_value_changed__active_socket_set()
|
self.on_value_changed__active_socket_set()
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import scipy as sc
|
import tidy3d as td
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
import sympy.physics.units as spu
|
import sympy.physics.units as spu
|
||||||
|
import scipy as sc
|
||||||
|
|
||||||
from .... import contracts, sockets
|
from .... import contracts
|
||||||
from ... import base, events
|
from .... import sockets
|
||||||
|
from ... import base
|
||||||
|
|
||||||
|
|
||||||
class WaveConverterNode(base.MaxwellSimTreeNode):
|
class WaveConverterNode(base.MaxwellSimTreeNode):
|
||||||
|
@ -44,9 +46,11 @@ class WaveConverterNode(base.MaxwellSimTreeNode):
|
||||||
####################
|
####################
|
||||||
# - Output Socket Computation
|
# - Output Socket Computation
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket('freq')
|
@base.computes_output_socket('freq')
|
||||||
def compute_freq(self: contracts.NodeTypeProtocol) -> sp.Expr:
|
def compute_freq(self: contracts.NodeTypeProtocol) -> sp.Expr:
|
||||||
vac_speed_of_light = sc.constants.speed_of_light * spu.meter / spu.second
|
vac_speed_of_light = (
|
||||||
|
sc.constants.speed_of_light * spu.meter / spu.second
|
||||||
|
)
|
||||||
|
|
||||||
vacwl = self.compute_input('vacwl')
|
vacwl = self.compute_input('vacwl')
|
||||||
|
|
||||||
|
@ -55,9 +59,11 @@ class WaveConverterNode(base.MaxwellSimTreeNode):
|
||||||
spu.hertz,
|
spu.hertz,
|
||||||
)
|
)
|
||||||
|
|
||||||
@events.computes_output_socket('vacwl')
|
@base.computes_output_socket('vacwl')
|
||||||
def compute_vacwl(self: contracts.NodeTypeProtocol) -> sp.Expr:
|
def compute_vacwl(self: contracts.NodeTypeProtocol) -> sp.Expr:
|
||||||
vac_speed_of_light = sc.constants.speed_of_light * spu.meter / spu.second
|
vac_speed_of_light = (
|
||||||
|
sc.constants.speed_of_light * spu.meter / spu.second
|
||||||
|
)
|
||||||
|
|
||||||
freq = self.compute_input('freq')
|
freq = self.compute_input('freq')
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,18 @@
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
|
||||||
|
import tidy3d as td
|
||||||
|
import numpy as np
|
||||||
|
import sympy as sp
|
||||||
|
import sympy.physics.units as spu
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
|
from .....utils import analyze_geonodes
|
||||||
|
from ... import bl_socket_map
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from ... import managed_objs, sockets
|
from ... import sockets
|
||||||
from .. import base, events
|
from .. import base
|
||||||
|
from ... import managed_objs
|
||||||
|
|
||||||
CACHE = {}
|
CACHE = {}
|
||||||
|
|
||||||
|
@ -16,12 +24,12 @@ class FDTDSimDataVizNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - Sockets
|
# - Sockets
|
||||||
####################
|
####################
|
||||||
input_sockets: typ.ClassVar = {
|
input_sockets = {
|
||||||
'FDTD Sim Data': sockets.MaxwellFDTDSimDataSocketDef(),
|
'FDTD Sim Data': sockets.MaxwellFDTDSimDataSocketDef(),
|
||||||
}
|
}
|
||||||
output_sockets: typ.ClassVar = {'Preview': sockets.AnySocketDef()}
|
output_sockets = {'Preview': sockets.AnySocketDef()}
|
||||||
|
|
||||||
managed_obj_defs: typ.ClassVar = {
|
managed_obj_defs = {
|
||||||
'viz_plot': ct.schemas.ManagedObjDef(
|
'viz_plot': ct.schemas.ManagedObjDef(
|
||||||
mk=lambda name: managed_objs.ManagedBLImage(name),
|
mk=lambda name: managed_objs.ManagedBLImage(name),
|
||||||
name_prefix='',
|
name_prefix='',
|
||||||
|
@ -63,7 +71,9 @@ class FDTDSimDataVizNode(base.MaxwellSimNode):
|
||||||
# ("Hz", "Hz", "Hz"),
|
# ("Hz", "Hz", "Hz"),
|
||||||
],
|
],
|
||||||
default='E',
|
default='E',
|
||||||
update=lambda self, context: self.sync_prop('field_viz_component', context),
|
update=lambda self, context: self.sync_prop(
|
||||||
|
'field_viz_component', context
|
||||||
|
),
|
||||||
)
|
)
|
||||||
field_viz_part: bpy.props.EnumProperty(
|
field_viz_part: bpy.props.EnumProperty(
|
||||||
name='Field Part',
|
name='Field Part',
|
||||||
|
@ -86,7 +96,9 @@ class FDTDSimDataVizNode(base.MaxwellSimNode):
|
||||||
('dB', 'Log (dB)', 'Logarithmic (dB) Scale'),
|
('dB', 'Log (dB)', 'Logarithmic (dB) Scale'),
|
||||||
],
|
],
|
||||||
default='lin',
|
default='lin',
|
||||||
update=lambda self, context: self.sync_prop('field_viz_scale', context),
|
update=lambda self, context: self.sync_prop(
|
||||||
|
'field_viz_scale', context
|
||||||
|
),
|
||||||
)
|
)
|
||||||
field_viz_structure_visibility: bpy.props.FloatProperty(
|
field_viz_structure_visibility: bpy.props.FloatProperty(
|
||||||
name='Field Viz Plot: Structure Visibility',
|
name='Field Viz Plot: Structure Visibility',
|
||||||
|
@ -94,57 +106,75 @@ class FDTDSimDataVizNode(base.MaxwellSimNode):
|
||||||
default=0.2,
|
default=0.2,
|
||||||
min=0.0,
|
min=0.0,
|
||||||
max=1.0,
|
max=1.0,
|
||||||
update=lambda self, context: self.sync_prop('field_viz_plot_fixed_f', context),
|
update=lambda self, context: self.sync_prop(
|
||||||
|
'field_viz_plot_fixed_f', context
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
field_viz_plot_fix_x: bpy.props.BoolProperty(
|
field_viz_plot_fix_x: bpy.props.BoolProperty(
|
||||||
name='Field Viz Plot: Fix X',
|
name='Field Viz Plot: Fix X',
|
||||||
description='Fix the x-coordinate on the plot',
|
description='Fix the x-coordinate on the plot',
|
||||||
default=False,
|
default=False,
|
||||||
update=lambda self, context: self.sync_prop('field_viz_plot_fix_x', context),
|
update=lambda self, context: self.sync_prop(
|
||||||
|
'field_viz_plot_fix_x', context
|
||||||
|
),
|
||||||
)
|
)
|
||||||
field_viz_plot_fix_y: bpy.props.BoolProperty(
|
field_viz_plot_fix_y: bpy.props.BoolProperty(
|
||||||
name='Field Viz Plot: Fix Y',
|
name='Field Viz Plot: Fix Y',
|
||||||
description='Fix the y coordinate on the plot',
|
description='Fix the y coordinate on the plot',
|
||||||
default=False,
|
default=False,
|
||||||
update=lambda self, context: self.sync_prop('field_viz_plot_fix_y', context),
|
update=lambda self, context: self.sync_prop(
|
||||||
|
'field_viz_plot_fix_y', context
|
||||||
|
),
|
||||||
)
|
)
|
||||||
field_viz_plot_fix_z: bpy.props.BoolProperty(
|
field_viz_plot_fix_z: bpy.props.BoolProperty(
|
||||||
name='Field Viz Plot: Fix Z',
|
name='Field Viz Plot: Fix Z',
|
||||||
description='Fix the z coordinate on the plot',
|
description='Fix the z coordinate on the plot',
|
||||||
default=False,
|
default=False,
|
||||||
update=lambda self, context: self.sync_prop('field_viz_plot_fix_z', context),
|
update=lambda self, context: self.sync_prop(
|
||||||
|
'field_viz_plot_fix_z', context
|
||||||
|
),
|
||||||
)
|
)
|
||||||
field_viz_plot_fix_f: bpy.props.BoolProperty(
|
field_viz_plot_fix_f: bpy.props.BoolProperty(
|
||||||
name='Field Viz Plot: Fix Freq',
|
name='Field Viz Plot: Fix Freq',
|
||||||
description='Fix the frequency coordinate on the plot',
|
description='Fix the frequency coordinate on the plot',
|
||||||
default=False,
|
default=False,
|
||||||
update=lambda self, context: self.sync_prop('field_viz_plot_fix_f', context),
|
update=lambda self, context: self.sync_prop(
|
||||||
|
'field_viz_plot_fix_f', context
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
field_viz_plot_fixed_x: bpy.props.FloatProperty(
|
field_viz_plot_fixed_x: bpy.props.FloatProperty(
|
||||||
name='Field Viz Plot: Fix X',
|
name='Field Viz Plot: Fix X',
|
||||||
description='Fix the x-coordinate on the plot',
|
description='Fix the x-coordinate on the plot',
|
||||||
default=0.0,
|
default=0.0,
|
||||||
update=lambda self, context: self.sync_prop('field_viz_plot_fixed_x', context),
|
update=lambda self, context: self.sync_prop(
|
||||||
|
'field_viz_plot_fixed_x', context
|
||||||
|
),
|
||||||
)
|
)
|
||||||
field_viz_plot_fixed_y: bpy.props.FloatProperty(
|
field_viz_plot_fixed_y: bpy.props.FloatProperty(
|
||||||
name='Field Viz Plot: Fixed Y',
|
name='Field Viz Plot: Fixed Y',
|
||||||
description='Fix the y coordinate on the plot',
|
description='Fix the y coordinate on the plot',
|
||||||
default=0.0,
|
default=0.0,
|
||||||
update=lambda self, context: self.sync_prop('field_viz_plot_fixed_y', context),
|
update=lambda self, context: self.sync_prop(
|
||||||
|
'field_viz_plot_fixed_y', context
|
||||||
|
),
|
||||||
)
|
)
|
||||||
field_viz_plot_fixed_z: bpy.props.FloatProperty(
|
field_viz_plot_fixed_z: bpy.props.FloatProperty(
|
||||||
name='Field Viz Plot: Fixed Z',
|
name='Field Viz Plot: Fixed Z',
|
||||||
description='Fix the z coordinate on the plot',
|
description='Fix the z coordinate on the plot',
|
||||||
default=0.0,
|
default=0.0,
|
||||||
update=lambda self, context: self.sync_prop('field_viz_plot_fixed_z', context),
|
update=lambda self, context: self.sync_prop(
|
||||||
|
'field_viz_plot_fixed_z', context
|
||||||
|
),
|
||||||
)
|
)
|
||||||
field_viz_plot_fixed_f: bpy.props.FloatProperty(
|
field_viz_plot_fixed_f: bpy.props.FloatProperty(
|
||||||
name='Field Viz Plot: Fixed Freq (Thz)',
|
name='Field Viz Plot: Fixed Freq (Thz)',
|
||||||
description='Fix the frequency coordinate on the plot',
|
description='Fix the frequency coordinate on the plot',
|
||||||
default=0.0,
|
default=0.0,
|
||||||
update=lambda self, context: self.sync_prop('field_viz_plot_fixed_f', context),
|
update=lambda self, context: self.sync_prop(
|
||||||
|
'field_viz_plot_fixed_f', context
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -154,7 +184,9 @@ class FDTDSimDataVizNode(base.MaxwellSimNode):
|
||||||
if (sim_data := self._compute_input('FDTD Sim Data')) is None:
|
if (sim_data := self._compute_input('FDTD Sim Data')) is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.cache_viz_monitor_type = sim_data.monitor_data[self.viz_monitor_name].type
|
self.cache_viz_monitor_type = sim_data.monitor_data[
|
||||||
|
self.viz_monitor_name
|
||||||
|
].type
|
||||||
self.sync_prop('viz_monitor_name', context)
|
self.sync_prop('viz_monitor_name', context)
|
||||||
|
|
||||||
def retrieve_monitors(self, context) -> list[tuple]:
|
def retrieve_monitors(self, context) -> list[tuple]:
|
||||||
|
@ -220,7 +252,7 @@ class FDTDSimDataVizNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - On Value Changed Methods
|
# - On Value Changed Methods
|
||||||
####################
|
####################
|
||||||
@events.on_value_changed(
|
@base.on_value_changed(
|
||||||
socket_name='FDTD Sim Data',
|
socket_name='FDTD Sim Data',
|
||||||
managed_objs={'viz_object'},
|
managed_objs={'viz_object'},
|
||||||
input_sockets={'FDTD Sim Data'},
|
input_sockets={'FDTD Sim Data'},
|
||||||
|
@ -236,12 +268,14 @@ class FDTDSimDataVizNode(base.MaxwellSimNode):
|
||||||
CACHE.pop(self.instance_id, None)
|
CACHE.pop(self.instance_id, None)
|
||||||
return
|
return
|
||||||
|
|
||||||
CACHE[self.instance_id] = {'monitors': list(sim_data.monitor_data.keys())}
|
CACHE[self.instance_id] = {
|
||||||
|
'monitors': list(sim_data.monitor_data.keys())
|
||||||
|
}
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Plotting
|
# - Plotting
|
||||||
####################
|
####################
|
||||||
@events.on_show_plot(
|
@base.on_show_plot(
|
||||||
managed_objs={'viz_plot'},
|
managed_objs={'viz_plot'},
|
||||||
props={
|
props={
|
||||||
'viz_monitor_name',
|
'viz_monitor_name',
|
||||||
|
@ -296,7 +330,7 @@ class FDTDSimDataVizNode(base.MaxwellSimNode):
|
||||||
bl_select=True,
|
bl_select=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# @events.on_show_preview(
|
# @base.on_show_preview(
|
||||||
# managed_objs={"viz_object"},
|
# managed_objs={"viz_object"},
|
||||||
# )
|
# )
|
||||||
# def on_show_preview(
|
# def on_show_preview(
|
||||||
|
|
|
@ -1,35 +1,72 @@
|
||||||
from ....utils import logger
|
from . import base
|
||||||
from .. import contracts as ct
|
|
||||||
from . import basic, blender, maxwell, number, physical, tidy3d, vector
|
|
||||||
from .scan_socket_defs import scan_for_socket_defs
|
|
||||||
|
|
||||||
log = logger.get(__name__)
|
from . import basic
|
||||||
sockets_modules = [basic, number, vector, physical, blender, maxwell, tidy3d]
|
|
||||||
|
|
||||||
####################
|
AnySocketDef = basic.AnySocketDef
|
||||||
# - Scan for SocketDefs
|
BoolSocketDef = basic.BoolSocketDef
|
||||||
####################
|
StringSocketDef = basic.StringSocketDef
|
||||||
SOCKET_DEFS = {}
|
FilePathSocketDef = basic.FilePathSocketDef
|
||||||
for sockets_module in sockets_modules:
|
|
||||||
SOCKET_DEFS |= scan_for_socket_defs(sockets_module)
|
|
||||||
|
|
||||||
# Set Global Names from SOCKET_DEFS
|
from . import number
|
||||||
## SOCKET_DEFS values are the classes themselves, which always have a __name__.
|
|
||||||
for socket_def_type in SOCKET_DEFS.values():
|
|
||||||
globals()[socket_def_type.__name__] = socket_def_type
|
|
||||||
|
|
||||||
# Validate SocketType -> SocketDef
|
IntegerNumberSocketDef = number.IntegerNumberSocketDef
|
||||||
## All SocketTypes should have a SocketDef
|
RationalNumberSocketDef = number.RationalNumberSocketDef
|
||||||
for socket_type in ct.SocketType:
|
RealNumberSocketDef = number.RealNumberSocketDef
|
||||||
if (
|
ComplexNumberSocketDef = number.ComplexNumberSocketDef
|
||||||
globals().get(socket_type.value.removesuffix('SocketType') + 'SocketDef')
|
|
||||||
is None
|
from . import vector
|
||||||
):
|
|
||||||
log.warning('Missing SocketDef for %s', socket_type.value)
|
Real2DVectorSocketDef = vector.Real2DVectorSocketDef
|
||||||
|
Complex2DVectorSocketDef = vector.Complex2DVectorSocketDef
|
||||||
|
Integer3DVectorSocketDef = vector.Integer3DVectorSocketDef
|
||||||
|
Real3DVectorSocketDef = vector.Real3DVectorSocketDef
|
||||||
|
Complex3DVectorSocketDef = vector.Complex3DVectorSocketDef
|
||||||
|
|
||||||
|
from . import physical
|
||||||
|
|
||||||
|
PhysicalUnitSystemSocketDef = physical.PhysicalUnitSystemSocketDef
|
||||||
|
PhysicalTimeSocketDef = physical.PhysicalTimeSocketDef
|
||||||
|
PhysicalAngleSocketDef = physical.PhysicalAngleSocketDef
|
||||||
|
PhysicalLengthSocketDef = physical.PhysicalLengthSocketDef
|
||||||
|
PhysicalAreaSocketDef = physical.PhysicalAreaSocketDef
|
||||||
|
PhysicalVolumeSocketDef = physical.PhysicalVolumeSocketDef
|
||||||
|
PhysicalPoint3DSocketDef = physical.PhysicalPoint3DSocketDef
|
||||||
|
PhysicalSize3DSocketDef = physical.PhysicalSize3DSocketDef
|
||||||
|
PhysicalMassSocketDef = physical.PhysicalMassSocketDef
|
||||||
|
PhysicalSpeedSocketDef = physical.PhysicalSpeedSocketDef
|
||||||
|
PhysicalAccelScalarSocketDef = physical.PhysicalAccelScalarSocketDef
|
||||||
|
PhysicalForceScalarSocketDef = physical.PhysicalForceScalarSocketDef
|
||||||
|
PhysicalPolSocketDef = physical.PhysicalPolSocketDef
|
||||||
|
PhysicalFreqSocketDef = physical.PhysicalFreqSocketDef
|
||||||
|
|
||||||
|
from . import blender
|
||||||
|
|
||||||
|
BlenderObjectSocketDef = blender.BlenderObjectSocketDef
|
||||||
|
BlenderCollectionSocketDef = blender.BlenderCollectionSocketDef
|
||||||
|
BlenderImageSocketDef = blender.BlenderImageSocketDef
|
||||||
|
BlenderGeoNodesSocketDef = blender.BlenderGeoNodesSocketDef
|
||||||
|
BlenderTextSocketDef = blender.BlenderTextSocketDef
|
||||||
|
|
||||||
|
from . import maxwell
|
||||||
|
|
||||||
|
MaxwellBoundCondSocketDef = maxwell.MaxwellBoundCondSocketDef
|
||||||
|
MaxwellBoundCondsSocketDef = maxwell.MaxwellBoundCondsSocketDef
|
||||||
|
MaxwellMediumSocketDef = maxwell.MaxwellMediumSocketDef
|
||||||
|
MaxwellMediumNonLinearitySocketDef = maxwell.MaxwellMediumNonLinearitySocketDef
|
||||||
|
MaxwellSourceSocketDef = maxwell.MaxwellSourceSocketDef
|
||||||
|
MaxwellTemporalShapeSocketDef = maxwell.MaxwellTemporalShapeSocketDef
|
||||||
|
MaxwellStructureSocketDef = maxwell.MaxwellStructureSocketDef
|
||||||
|
MaxwellMonitorSocketDef = maxwell.MaxwellMonitorSocketDef
|
||||||
|
MaxwellFDTDSimSocketDef = maxwell.MaxwellFDTDSimSocketDef
|
||||||
|
MaxwellFDTDSimDataSocketDef = maxwell.MaxwellFDTDSimDataSocketDef
|
||||||
|
MaxwellSimGridSocketDef = maxwell.MaxwellSimGridSocketDef
|
||||||
|
MaxwellSimGridAxisSocketDef = maxwell.MaxwellSimGridAxisSocketDef
|
||||||
|
MaxwellSimDomainSocketDef = maxwell.MaxwellSimDomainSocketDef
|
||||||
|
|
||||||
|
from . import tidy3d
|
||||||
|
|
||||||
|
Tidy3DCloudTaskSocketDef = tidy3d.Tidy3DCloudTaskSocketDef
|
||||||
|
|
||||||
####################
|
|
||||||
# - Exports
|
|
||||||
####################
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
*basic.BL_REGISTER,
|
*basic.BL_REGISTER,
|
||||||
*number.BL_REGISTER,
|
*number.BL_REGISTER,
|
||||||
|
@ -39,13 +76,3 @@ BL_REGISTER = [
|
||||||
*maxwell.BL_REGISTER,
|
*maxwell.BL_REGISTER,
|
||||||
*tidy3d.BL_REGISTER,
|
*tidy3d.BL_REGISTER,
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'basic',
|
|
||||||
'number',
|
|
||||||
'vector',
|
|
||||||
'physical',
|
|
||||||
'blender',
|
|
||||||
'maxwell',
|
|
||||||
'tidy3d',
|
|
||||||
] + [socket_def_type.__name__ for socket_def_type in SOCKET_DEFS.values()]
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import functools
|
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
import typing_extensions as typx
|
||||||
|
import functools
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
|
import pydantic as pyd
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
import sympy.physics.units as spu
|
import sympy.physics.units as spu
|
||||||
import typing_extensions as typx
|
|
||||||
|
|
||||||
from .. import contracts as ct
|
from .. import contracts as ct
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,7 +43,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
||||||
# - Initialization
|
# - Initialization
|
||||||
####################
|
####################
|
||||||
def __init_subclass__(cls, **kwargs: typ.Any):
|
def __init_subclass__(cls, **kwargs: typ.Any):
|
||||||
super().__init_subclass__(**kwargs)
|
super().__init_subclass__(**kwargs) ## Yucky superclass setup.
|
||||||
|
|
||||||
# Setup Blender ID for Node
|
# Setup Blender ID for Node
|
||||||
if not hasattr(cls, 'socket_type'):
|
if not hasattr(cls, 'socket_type'):
|
||||||
|
@ -180,7 +181,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
||||||
if self.locked:
|
if self.locked:
|
||||||
return False
|
return False
|
||||||
if self.is_output:
|
if self.is_output:
|
||||||
msg = "Tried to sync 'link add' on output socket"
|
msg = f"Tried to sync 'link add' on output socket"
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
self.trigger_action('value_changed')
|
self.trigger_action('value_changed')
|
||||||
|
@ -195,7 +196,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
||||||
if self.locked:
|
if self.locked:
|
||||||
return False
|
return False
|
||||||
if self.is_output:
|
if self.is_output:
|
||||||
msg = "Tried to sync 'link add' on output socket"
|
msg = f"Tried to sync 'link add' on output socket"
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
self.trigger_action('value_changed')
|
self.trigger_action('value_changed')
|
||||||
|
@ -266,12 +267,15 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
||||||
if kind == ct.DataFlowKind.Value:
|
if kind == ct.DataFlowKind.Value:
|
||||||
if self.is_list:
|
if self.is_list:
|
||||||
return self.value_list
|
return self.value_list
|
||||||
|
else:
|
||||||
return self.value
|
return self.value
|
||||||
if kind == ct.DataFlowKind.LazyValue:
|
elif kind == ct.DataFlowKind.LazyValue:
|
||||||
if self.is_list:
|
if self.is_list:
|
||||||
return self.lazy_value_list
|
return self.lazy_value_list
|
||||||
|
else:
|
||||||
return self.lazy_value
|
return self.lazy_value
|
||||||
if kind == ct.DataFlowKind.Capabilities:
|
return self.lazy_value
|
||||||
|
elif kind == ct.DataFlowKind.Capabilities:
|
||||||
return self.capabilities
|
return self.capabilities
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
@ -299,7 +303,9 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
||||||
return self._compute_data(kind)
|
return self._compute_data(kind)
|
||||||
|
|
||||||
## Linked: Compute Output of Linked Sockets
|
## Linked: Compute Output of Linked Sockets
|
||||||
linked_values = [link.from_socket.compute_data(kind) for link in self.links]
|
linked_values = [
|
||||||
|
link.from_socket.compute_data(kind) for link in self.links
|
||||||
|
]
|
||||||
|
|
||||||
## Return Single Value / List of Values
|
## Return Single Value / List of Values
|
||||||
if len(linked_values) == 1:
|
if len(linked_values) == 1:
|
||||||
|
@ -356,6 +362,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
||||||
|
|
||||||
Can be overridden if more specific logic is required.
|
Can be overridden if more specific logic is required.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
prev_value = self.value / self.unit * self.prev_unit
|
prev_value = self.value / self.unit * self.prev_unit
|
||||||
## After changing units, self.value is expressed in the wrong unit.
|
## After changing units, self.value is expressed in the wrong unit.
|
||||||
## - Therefore, we removing the new unit, and re-add the prev unit.
|
## - Therefore, we removing the new unit, and re-add the prev unit.
|
||||||
|
@ -394,6 +401,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
||||||
text: str,
|
text: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Called by Blender to draw the socket UI."""
|
"""Called by Blender to draw the socket UI."""
|
||||||
|
|
||||||
if self.is_output:
|
if self.is_output:
|
||||||
self.draw_output(context, layout, node, text)
|
self.draw_output(context, layout, node, text)
|
||||||
else:
|
else:
|
||||||
|
@ -450,7 +458,8 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
||||||
if self.locked:
|
if self.locked:
|
||||||
row = col.row(align=False)
|
row = col.row(align=False)
|
||||||
row.enabled = False
|
row.enabled = False
|
||||||
elif self.locked:
|
else:
|
||||||
|
if self.locked:
|
||||||
row.enabled = False
|
row.enabled = False
|
||||||
|
|
||||||
# Value Column(s)
|
# Value Column(s)
|
||||||
|
@ -489,9 +498,11 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
||||||
|
|
||||||
Can be overridden.
|
Can be overridden.
|
||||||
"""
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
def draw_value_list(self, col: bpy.types.UILayout) -> None:
|
def draw_value_list(self, col: bpy.types.UILayout) -> None:
|
||||||
"""Called to draw the value list column in unlinked input sockets.
|
"""Called to draw the value list column in unlinked input sockets.
|
||||||
|
|
||||||
Can be overridden.
|
Can be overridden.
|
||||||
"""
|
"""
|
||||||
|
pass
|
||||||
|
|
|
@ -1,12 +1,19 @@
|
||||||
from . import any as any_socket
|
from . import any as any_socket
|
||||||
from . import bool as bool_socket
|
|
||||||
from . import file_path, string
|
|
||||||
|
|
||||||
AnySocketDef = any_socket.AnySocketDef
|
AnySocketDef = any_socket.AnySocketDef
|
||||||
|
|
||||||
|
from . import bool as bool_socket
|
||||||
|
|
||||||
BoolSocketDef = bool_socket.BoolSocketDef
|
BoolSocketDef = bool_socket.BoolSocketDef
|
||||||
FilePathSocketDef = file_path.FilePathSocketDef
|
|
||||||
|
from . import string
|
||||||
|
|
||||||
StringSocketDef = string.StringSocketDef
|
StringSocketDef = string.StringSocketDef
|
||||||
|
|
||||||
|
from . import file_path
|
||||||
|
|
||||||
|
FilePathSocketDef = file_path.FilePathSocketDef
|
||||||
|
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
*any_socket.BL_REGISTER,
|
*any_socket.BL_REGISTER,
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import sympy as sp
|
||||||
import pydantic as pyd
|
import pydantic as pyd
|
||||||
|
|
||||||
from ... import contracts as ct
|
|
||||||
from .. import base
|
from .. import base
|
||||||
|
from ... import contracts as ct
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
import sympy as sp
|
||||||
import pydantic as pyd
|
import pydantic as pyd
|
||||||
|
|
||||||
from ... import contracts as ct
|
|
||||||
from .. import base
|
from .. import base
|
||||||
|
from ... import contracts as ct
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -25,7 +28,9 @@ class BoolBLSocket(base.MaxwellSimSocket):
|
||||||
####################
|
####################
|
||||||
# - Socket UI
|
# - Socket UI
|
||||||
####################
|
####################
|
||||||
def draw_label_row(self, label_col_row: bpy.types.UILayout, text: str) -> None:
|
def draw_label_row(
|
||||||
|
self, label_col_row: bpy.types.UILayout, text: str
|
||||||
|
) -> None:
|
||||||
label_col_row.label(text=text)
|
label_col_row.label(text=text)
|
||||||
label_col_row.prop(self, 'raw_value', text='')
|
label_col_row.prop(self, 'raw_value', text='')
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
|
import typing as typ
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
import sympy as sp
|
||||||
import pydantic as pyd
|
import pydantic as pyd
|
||||||
|
|
||||||
from ... import contracts as ct
|
|
||||||
from .. import base
|
from .. import base
|
||||||
|
from ... import contracts as ct
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -49,7 +51,7 @@ class FilePathBLSocket(base.MaxwellSimSocket):
|
||||||
class FilePathSocketDef(pyd.BaseModel):
|
class FilePathSocketDef(pyd.BaseModel):
|
||||||
socket_type: ct.SocketType = ct.SocketType.FilePath
|
socket_type: ct.SocketType = ct.SocketType.FilePath
|
||||||
|
|
||||||
default_path: Path = Path()
|
default_path: Path = Path('')
|
||||||
|
|
||||||
def init(self, bl_socket: FilePathBLSocket) -> None:
|
def init(self, bl_socket: FilePathBLSocket) -> None:
|
||||||
bl_socket.value = self.default_path
|
bl_socket.value = self.default_path
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
import sympy as sp
|
||||||
import pydantic as pyd
|
import pydantic as pyd
|
||||||
|
|
||||||
from ... import contracts as ct
|
|
||||||
from .. import base
|
from .. import base
|
||||||
|
from ... import contracts as ct
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -25,7 +28,9 @@ class StringBLSocket(base.MaxwellSimSocket):
|
||||||
####################
|
####################
|
||||||
# - Socket UI
|
# - Socket UI
|
||||||
####################
|
####################
|
||||||
def draw_label_row(self, label_col_row: bpy.types.UILayout, text: str) -> None:
|
def draw_label_row(
|
||||||
|
self, label_col_row: bpy.types.UILayout, text: str
|
||||||
|
) -> None:
|
||||||
label_col_row.prop(self, 'raw_value', text=text)
|
label_col_row.prop(self, 'raw_value', text=text)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
from . import collection, material
|
|
||||||
from . import object as object_socket
|
from . import object as object_socket
|
||||||
|
from . import collection
|
||||||
|
|
||||||
BlenderMaterialSocketDef = material.BlenderMaterialSocketDef
|
|
||||||
BlenderObjectSocketDef = object_socket.BlenderObjectSocketDef
|
BlenderObjectSocketDef = object_socket.BlenderObjectSocketDef
|
||||||
BlenderCollectionSocketDef = collection.BlenderCollectionSocketDef
|
BlenderCollectionSocketDef = collection.BlenderCollectionSocketDef
|
||||||
|
|
||||||
|
@ -9,13 +8,13 @@ from . import image
|
||||||
|
|
||||||
BlenderImageSocketDef = image.BlenderImageSocketDef
|
BlenderImageSocketDef = image.BlenderImageSocketDef
|
||||||
|
|
||||||
from . import geonodes, text
|
from . import geonodes
|
||||||
|
from . import text
|
||||||
|
|
||||||
BlenderGeoNodesSocketDef = geonodes.BlenderGeoNodesSocketDef
|
BlenderGeoNodesSocketDef = geonodes.BlenderGeoNodesSocketDef
|
||||||
BlenderTextSocketDef = text.BlenderTextSocketDef
|
BlenderTextSocketDef = text.BlenderTextSocketDef
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
*material.BL_REGISTER,
|
|
||||||
*object_socket.BL_REGISTER,
|
*object_socket.BL_REGISTER,
|
||||||
*collection.BL_REGISTER,
|
*collection.BL_REGISTER,
|
||||||
*text.BL_REGISTER,
|
*text.BL_REGISTER,
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import pydantic as pyd
|
import pydantic as pyd
|
||||||
|
|
||||||
from ... import contracts as ct
|
|
||||||
from .. import base
|
from .. import base
|
||||||
|
from ... import contracts as ct
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import pydantic as pyd
|
import pydantic as pyd
|
||||||
|
|
||||||
from ... import contracts as ct
|
|
||||||
from .. import base
|
from .. import base
|
||||||
|
from ... import contracts as ct
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import pydantic as pyd
|
import pydantic as pyd
|
||||||
|
|
||||||
from ... import contracts as ct
|
|
||||||
from .. import base
|
from .. import base
|
||||||
|
from ... import contracts as ct
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
import bpy
|
|
||||||
import pydantic as pyd
|
|
||||||
|
|
||||||
from ... import contracts as ct
|
|
||||||
from .. import base
|
|
||||||
|
|
||||||
|
|
||||||
class BlenderMaterialBLSocket(base.MaxwellSimSocket):
|
|
||||||
socket_type = ct.SocketType.BlenderMaterial
|
|
||||||
bl_label = 'Blender Material'
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Properties
|
|
||||||
####################
|
|
||||||
raw_value: bpy.props.PointerProperty(
|
|
||||||
name='Blender Material',
|
|
||||||
description='Represents a Blender material',
|
|
||||||
type=bpy.types.Material,
|
|
||||||
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 value(self) -> bpy.types.Material | None:
|
|
||||||
return self.raw_value
|
|
||||||
|
|
||||||
@value.setter
|
|
||||||
def value(self, value: bpy.types.Material) -> None:
|
|
||||||
self.raw_value = value
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Socket Configuration
|
|
||||||
####################
|
|
||||||
class BlenderMaterialSocketDef(pyd.BaseModel):
|
|
||||||
socket_type: ct.SocketType = ct.SocketType.BlenderMaterial
|
|
||||||
|
|
||||||
def init(self, bl_socket: BlenderMaterialBLSocket) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Blender Registration
|
|
||||||
####################
|
|
||||||
BL_REGISTER = [
|
|
||||||
BlenderMaterialBLSocket,
|
|
||||||
]
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import pydantic as pyd
|
import pydantic as pyd
|
||||||
|
|
||||||
from ... import contracts as ct
|
|
||||||
from .. import base
|
from .. import base
|
||||||
|
from ... import contracts as ct
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import pydantic as pyd
|
import pydantic as pyd
|
||||||
|
|
||||||
from ... import contracts as ct
|
|
||||||
from .. import base
|
from .. import base
|
||||||
|
from ... import contracts as ct
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
from . import bound_cond, bound_conds
|
from . import bound_cond
|
||||||
|
from . import bound_conds
|
||||||
|
|
||||||
MaxwellBoundCondSocketDef = bound_cond.MaxwellBoundCondSocketDef
|
MaxwellBoundCondSocketDef = bound_cond.MaxwellBoundCondSocketDef
|
||||||
MaxwellBoundCondsSocketDef = bound_conds.MaxwellBoundCondsSocketDef
|
MaxwellBoundCondsSocketDef = bound_conds.MaxwellBoundCondsSocketDef
|
||||||
|
|
||||||
from . import medium, medium_non_linearity
|
from . import medium
|
||||||
|
from . import medium_non_linearity
|
||||||
|
|
||||||
MaxwellMediumSocketDef = medium.MaxwellMediumSocketDef
|
MaxwellMediumSocketDef = medium.MaxwellMediumSocketDef
|
||||||
MaxwellMediumNonLinearitySocketDef = (
|
MaxwellMediumNonLinearitySocketDef = (
|
||||||
medium_non_linearity.MaxwellMediumNonLinearitySocketDef
|
medium_non_linearity.MaxwellMediumNonLinearitySocketDef
|
||||||
)
|
)
|
||||||
|
|
||||||
from . import source, temporal_shape
|
from . import source
|
||||||
|
from . import temporal_shape
|
||||||
|
|
||||||
MaxwellSourceSocketDef = source.MaxwellSourceSocketDef
|
MaxwellSourceSocketDef = source.MaxwellSourceSocketDef
|
||||||
MaxwellTemporalShapeSocketDef = temporal_shape.MaxwellTemporalShapeSocketDef
|
MaxwellTemporalShapeSocketDef = temporal_shape.MaxwellTemporalShapeSocketDef
|
||||||
|
@ -23,7 +26,11 @@ from . import monitor
|
||||||
|
|
||||||
MaxwellMonitorSocketDef = monitor.MaxwellMonitorSocketDef
|
MaxwellMonitorSocketDef = monitor.MaxwellMonitorSocketDef
|
||||||
|
|
||||||
from . import fdtd_sim, fdtd_sim_data, sim_domain, sim_grid, sim_grid_axis
|
from . import fdtd_sim
|
||||||
|
from . import fdtd_sim_data
|
||||||
|
from . import sim_grid
|
||||||
|
from . import sim_grid_axis
|
||||||
|
from . import sim_domain
|
||||||
|
|
||||||
MaxwellFDTDSimSocketDef = fdtd_sim.MaxwellFDTDSimSocketDef
|
MaxwellFDTDSimSocketDef = fdtd_sim.MaxwellFDTDSimSocketDef
|
||||||
MaxwellFDTDSimDataSocketDef = fdtd_sim_data.MaxwellFDTDSimDataSocketDef
|
MaxwellFDTDSimDataSocketDef = fdtd_sim_data.MaxwellFDTDSimDataSocketDef
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
|
import typing as typ
|
||||||
|
import typing_extensions as typx
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import pydantic as pyd
|
import pydantic as pyd
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
import typing_extensions as typx
|
|
||||||
|
|
||||||
from ... import contracts as ct
|
|
||||||
from .. import base
|
from .. import base
|
||||||
|
from ... import contracts as ct
|
||||||
|
|
||||||
|
|
||||||
class MaxwellBoundCondBLSocket(base.MaxwellSimSocket):
|
class MaxwellBoundCondBLSocket(base.MaxwellSimSocket):
|
||||||
|
@ -24,7 +26,9 @@ class MaxwellBoundCondBLSocket(base.MaxwellSimSocket):
|
||||||
('PERIODIC', 'Periodic', 'Infinitely periodic layer'),
|
('PERIODIC', 'Periodic', 'Infinitely periodic layer'),
|
||||||
],
|
],
|
||||||
default='PML',
|
default='PML',
|
||||||
update=(lambda self, context: self.sync_prop('default_choice', context)),
|
update=(
|
||||||
|
lambda self, context: self.sync_prop('default_choice', context)
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -46,7 +50,9 @@ class MaxwellBoundCondBLSocket(base.MaxwellSimSocket):
|
||||||
}[self.default_choice]
|
}[self.default_choice]
|
||||||
|
|
||||||
@value.setter
|
@value.setter
|
||||||
def value(self, value: typx.Literal['PML', 'PEC', 'PMC', 'PERIODIC']) -> None:
|
def value(
|
||||||
|
self, value: typx.Literal['PML', 'PEC', 'PMC', 'PERIODIC']
|
||||||
|
) -> None:
|
||||||
self.default_choice = value
|
self.default_choice = value
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import pydantic as pyd
|
import pydantic as pyd
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
|
|
||||||
from ... import contracts as ct
|
|
||||||
from .. import base
|
from .. import base
|
||||||
|
from ... import contracts as ct
|
||||||
|
|
||||||
BOUND_FACE_ITEMS = [
|
BOUND_FACE_ITEMS = [
|
||||||
('PML', 'PML', 'Perfectly matched layer'),
|
('PML', 'PML', 'Perfectly matched layer'),
|
||||||
|
@ -30,7 +32,9 @@ class MaxwellBoundCondsBLSocket(base.MaxwellSimSocket):
|
||||||
name='Show Bounds Definition',
|
name='Show Bounds Definition',
|
||||||
description='Toggle to show bound faces',
|
description='Toggle to show bound faces',
|
||||||
default=False,
|
default=False,
|
||||||
update=(lambda self, context: self.sync_prop('show_definition', context)),
|
update=(
|
||||||
|
lambda self, context: self.sync_prop('show_definition', context)
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
x_pos: bpy.props.EnumProperty(
|
x_pos: bpy.props.EnumProperty(
|
||||||
|
@ -81,7 +85,9 @@ class MaxwellBoundCondsBLSocket(base.MaxwellSimSocket):
|
||||||
####################
|
####################
|
||||||
def draw_label_row(self, row: bpy.types.UILayout, text) -> None:
|
def draw_label_row(self, row: bpy.types.UILayout, text) -> None:
|
||||||
row.label(text=text)
|
row.label(text=text)
|
||||||
row.prop(self, 'show_definition', toggle=True, text='', icon='MOD_LENGTH')
|
row.prop(
|
||||||
|
self, 'show_definition', toggle=True, text='', icon='MOD_LENGTH'
|
||||||
|
)
|
||||||
|
|
||||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||||
if not self.show_definition:
|
if not self.show_definition:
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue