refactor: Far more well-functioning baseline.
parent
b592ea4b10
commit
b78dd8dd56
|
@ -1,11 +1,11 @@
|
||||||
|
from . import sockets
|
||||||
|
from . import node_tree
|
||||||
from . import nodes
|
from . import nodes
|
||||||
from . import categories
|
from . import categories
|
||||||
from . import socket_types
|
|
||||||
from . import tree
|
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
*tree.BL_REGISTER,
|
*sockets.BL_REGISTER,
|
||||||
*socket_types.BL_REGISTER,
|
*node_tree.BL_REGISTER,
|
||||||
*nodes.BL_REGISTER,
|
*nodes.BL_REGISTER,
|
||||||
*categories.BL_REGISTER,
|
*categories.BL_REGISTER,
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,18 +1,10 @@
|
||||||
|
## TODO: Refactor this whole horrible module.
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import nodeitems_utils
|
import nodeitems_utils
|
||||||
from . import types
|
from . import contracts
|
||||||
from .nodes import BL_NODES
|
from .nodes import BL_NODES
|
||||||
|
|
||||||
####################
|
|
||||||
# - Assembly of Node Categories
|
|
||||||
####################
|
|
||||||
class MaxwellSimNodeCategory(nodeitems_utils.NodeCategory):
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
"""Constrain node category availability to within a MaxwellSimTree."""
|
|
||||||
|
|
||||||
return context.space_data.tree_type == types.TreeType.MaxwellSim.value
|
|
||||||
|
|
||||||
DYNAMIC_SUBMENU_REGISTRATIONS = []
|
DYNAMIC_SUBMENU_REGISTRATIONS = []
|
||||||
def mk_node_categories(
|
def mk_node_categories(
|
||||||
tree,
|
tree,
|
||||||
|
@ -23,7 +15,7 @@ def mk_node_categories(
|
||||||
items = []
|
items = []
|
||||||
|
|
||||||
# Add Node Items
|
# Add Node Items
|
||||||
base_category = types.NodeCategory["_".join(syllable_prefix)]
|
base_category = contracts.NodeCategory["_".join(syllable_prefix)]
|
||||||
for node_type, node_category in BL_NODES.items():
|
for node_type, node_category in BL_NODES.items():
|
||||||
if node_category == base_category:
|
if node_category == base_category:
|
||||||
items.append(nodeitems_utils.NodeItem(node_type.value))
|
items.append(nodeitems_utils.NodeItem(node_type.value))
|
||||||
|
@ -31,7 +23,7 @@ def mk_node_categories(
|
||||||
# Add Node Sub-Menus
|
# Add Node Sub-Menus
|
||||||
for syllable, sub_tree in tree.items():
|
for syllable, sub_tree in tree.items():
|
||||||
current_syllable_path = syllable_prefix + [syllable]
|
current_syllable_path = syllable_prefix + [syllable]
|
||||||
current_category = types.NodeCategory[
|
current_category = contracts.NodeCategory[
|
||||||
"_".join(current_syllable_path)
|
"_".join(current_syllable_path)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -51,10 +43,12 @@ def mk_node_categories(
|
||||||
nodeitems_utils.NodeItem,
|
nodeitems_utils.NodeItem,
|
||||||
):
|
):
|
||||||
nodeitem = nodeitem_or_submenu
|
nodeitem = nodeitem_or_submenu
|
||||||
self.layout.operator(
|
op_add_node_cfg = self.layout.operator(
|
||||||
"node.add_node",
|
"node.add_node",
|
||||||
text=nodeitem.label,
|
text=nodeitem.label,
|
||||||
).type = nodeitem.nodetype
|
)
|
||||||
|
op_add_node_cfg.type = nodeitem.nodetype
|
||||||
|
op_add_node_cfg.use_transform = True
|
||||||
elif isinstance(nodeitem_or_submenu, str):
|
elif isinstance(nodeitem_or_submenu, str):
|
||||||
submenu_id = nodeitem_or_submenu
|
submenu_id = nodeitem_or_submenu
|
||||||
self.layout.menu(submenu_id)
|
self.layout.menu(submenu_id)
|
||||||
|
@ -62,7 +56,7 @@ def mk_node_categories(
|
||||||
|
|
||||||
menu_class = type(current_category.value, (bpy.types.Menu,), {
|
menu_class = type(current_category.value, (bpy.types.Menu,), {
|
||||||
'bl_idname': current_category.value,
|
'bl_idname': current_category.value,
|
||||||
'bl_label': types.NodeCategory_to_category_label[current_category],
|
'bl_label': contracts.NodeCategory_to_category_label[current_category],
|
||||||
'draw': draw_factory(tuple(subitems)),
|
'draw': draw_factory(tuple(subitems)),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -78,7 +72,7 @@ def mk_node_categories(
|
||||||
# - Blender Registration
|
# - Blender Registration
|
||||||
####################
|
####################
|
||||||
BL_NODE_CATEGORIES = mk_node_categories(
|
BL_NODE_CATEGORIES = mk_node_categories(
|
||||||
types.NodeCategory.get_tree()["MAXWELL"]["SIM"],
|
contracts.NodeCategory.get_tree()["MAXWELL"]["SIM"],
|
||||||
syllable_prefix = ["MAXWELL", "SIM"],
|
syllable_prefix = ["MAXWELL", "SIM"],
|
||||||
)
|
)
|
||||||
## TODO: refractor, this has a big code smell
|
## TODO: refractor, this has a big code smell
|
||||||
|
@ -88,7 +82,7 @@ BL_REGISTER = [
|
||||||
|
|
||||||
## TEST - TODO this is a big code smell
|
## TEST - TODO this is a big code smell
|
||||||
def menu_draw(self, context):
|
def menu_draw(self, context):
|
||||||
if context.space_data.tree_type == types.TreeType.MaxwellSim.value:
|
if context.space_data.tree_type == contracts.TreeType.MaxwellSim.value:
|
||||||
for nodeitem_or_submenu in BL_NODE_CATEGORIES:
|
for nodeitem_or_submenu in BL_NODE_CATEGORIES:
|
||||||
if isinstance(nodeitem_or_submenu, str):
|
if isinstance(nodeitem_or_submenu, str):
|
||||||
submenu_id = nodeitem_or_submenu
|
submenu_id = nodeitem_or_submenu
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
####################
|
|
||||||
# - Colors
|
|
||||||
####################
|
|
||||||
COLOR_SOCKET_SOURCE = (0.4, 0.4, 0.9, 1.0)
|
|
||||||
COLOR_SOCKET_MEDIUM = (1.0, 0.4, 0.2, 1.0)
|
|
||||||
COLOR_SOCKET_STRUCTURE = (0.2, 0.4, 0.8, 1.0)
|
|
||||||
COLOR_SOCKET_BOUND = (0.7, 0.8, 0.7, 1.0)
|
|
||||||
COLOR_SOCKET_FDTDSIM = (0.9, 0.9, 0.9, 1.0)
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Icons
|
|
||||||
####################
|
|
||||||
ICON_SIM = 'MOD_SIMPLEDEFORM'
|
|
||||||
|
|
||||||
ICON_SIM_SOURCE = 'FORCE_CHARGE'
|
|
||||||
ICON_SIM_MEDIUM = 'MATSHADERBALL'
|
|
||||||
ICON_SIM_STRUCTURE = 'OUTLINER_DATA_MESH'
|
|
||||||
ICON_SIM_BOUND = 'MOD_MESHDEFORM'
|
|
||||||
ICON_SIM_SIMULATION = ICON_SIM
|
|
|
@ -1,10 +1,72 @@
|
||||||
import bpy
|
import typing as typ
|
||||||
|
import typing_extensions as pytypes_ext
|
||||||
import enum
|
import enum
|
||||||
|
|
||||||
|
import sympy as sp
|
||||||
|
import sympy.physics.units as spu
|
||||||
|
import pydantic as pyd
|
||||||
|
import bpy
|
||||||
|
|
||||||
from ...utils.blender_type_enum import (
|
from ...utils.blender_type_enum import (
|
||||||
BlenderTypeEnum, append_cls_name_to_values
|
BlenderTypeEnum, append_cls_name_to_values
|
||||||
)
|
)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - String Types
|
||||||
|
####################
|
||||||
|
BlenderColorRGB = tuple[float, float, float, float]
|
||||||
|
BlenderID = pytypes_ext.Annotated[str, pyd.StringConstraints(
|
||||||
|
pattern=r'^[A-Z_]+$',
|
||||||
|
)]
|
||||||
|
|
||||||
|
# Socket ID
|
||||||
|
SocketName = pytypes_ext.Annotated[str, pyd.StringConstraints(
|
||||||
|
pattern=r'^[a-zA-Z0-9_]+$',
|
||||||
|
)]
|
||||||
|
BLSocketName = pytypes_ext.Annotated[str, pyd.StringConstraints(
|
||||||
|
pattern=r'^[a-zA-Z0-9_]+$',
|
||||||
|
)]
|
||||||
|
|
||||||
|
# Socket ID
|
||||||
|
PresetID = pytypes_ext.Annotated[str, pyd.StringConstraints(
|
||||||
|
pattern=r'^[A-Z_]+$',
|
||||||
|
)]
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Generic Types
|
||||||
|
####################
|
||||||
|
SocketReturnType = typ.TypeVar('SocketReturnType', covariant=True)
|
||||||
|
## - Covariance: If B subtypes A, then Container[B] subtypes Container[A].
|
||||||
|
## - This is absolutely what we want here.
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Sympy Expression Typing
|
||||||
|
####################
|
||||||
|
ALL_UNIT_SYMBOLS = {
|
||||||
|
unit
|
||||||
|
for unit in spu.__dict__.values()
|
||||||
|
if isinstance(unit, spu.Quantity)
|
||||||
|
}
|
||||||
|
def has_units(expr: sp.Expr):
|
||||||
|
return any(
|
||||||
|
symbol in ALL_UNIT_SYMBOLS
|
||||||
|
for symbol in expr.atoms(sp.Symbol)
|
||||||
|
)
|
||||||
|
def is_exactly_expressed_as_unit(expr: sp.Expr, unit) -> bool:
|
||||||
|
#try:
|
||||||
|
converted_expr = expr / unit
|
||||||
|
|
||||||
|
return (
|
||||||
|
converted_expr.is_number
|
||||||
|
and not converted_expr.has(spu.Quantity)
|
||||||
|
)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Icon Types
|
||||||
|
####################
|
||||||
|
class Icon(BlenderTypeEnum):
|
||||||
|
MaxwellSimTree = "MOD_SIMPLEDEFORM"
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Tree Types
|
# - Tree Types
|
||||||
####################
|
####################
|
||||||
|
@ -17,6 +79,17 @@ class TreeType(BlenderTypeEnum):
|
||||||
####################
|
####################
|
||||||
@append_cls_name_to_values
|
@append_cls_name_to_values
|
||||||
class SocketType(BlenderTypeEnum):
|
class SocketType(BlenderTypeEnum):
|
||||||
|
Any = enum.auto()
|
||||||
|
Text = enum.auto()
|
||||||
|
FilePath = enum.auto()
|
||||||
|
|
||||||
|
RationalNumber = enum.auto()
|
||||||
|
RealNumber = enum.auto()
|
||||||
|
ComplexNumber = enum.auto()
|
||||||
|
|
||||||
|
PhysicalLength = enum.auto()
|
||||||
|
PhysicalArea = enum.auto()
|
||||||
|
|
||||||
MaxwellSource = enum.auto()
|
MaxwellSource = enum.auto()
|
||||||
MaxwellMedium = enum.auto()
|
MaxwellMedium = enum.auto()
|
||||||
MaxwellStructure = enum.auto()
|
MaxwellStructure = enum.auto()
|
||||||
|
@ -132,7 +205,7 @@ class NodeType(BlenderTypeEnum):
|
||||||
KSpaceNearFieldProjectionMonitor = enum.auto()
|
KSpaceNearFieldProjectionMonitor = enum.auto()
|
||||||
|
|
||||||
# Simulations
|
# Simulations
|
||||||
FDTDSimulation = enum.auto()
|
FDTDSim = enum.auto()
|
||||||
|
|
||||||
SimulationGridDiscretization = enum.auto()
|
SimulationGridDiscretization = enum.auto()
|
||||||
|
|
||||||
|
@ -155,8 +228,6 @@ class NodeType(BlenderTypeEnum):
|
||||||
####################
|
####################
|
||||||
# - Node Category Types
|
# - Node Category Types
|
||||||
####################
|
####################
|
||||||
#@append_cls_name_to_values
|
|
||||||
#@append_cls_name_to_values
|
|
||||||
class NodeCategory(BlenderTypeEnum):
|
class NodeCategory(BlenderTypeEnum):
|
||||||
MAXWELL_SIM = enum.auto()
|
MAXWELL_SIM = enum.auto()
|
||||||
|
|
||||||
|
@ -219,6 +290,7 @@ class NodeCategory(BlenderTypeEnum):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_tree(cls):
|
def get_tree(cls):
|
||||||
|
## TODO: Refactor
|
||||||
syllable_categories = [
|
syllable_categories = [
|
||||||
node_category.value.split("_")
|
node_category.value.split("_")
|
||||||
for node_category in cls
|
for node_category in cls
|
||||||
|
@ -296,3 +368,104 @@ NodeCategory_to_category_label = {
|
||||||
NodeCategory.MAXWELL_SIM_UTILITIES_MATH: "Math",
|
NodeCategory.MAXWELL_SIM_UTILITIES_MATH: "Math",
|
||||||
NodeCategory.MAXWELL_SIM_UTILITIES_FIELDMATH: "Field Math",
|
NodeCategory.MAXWELL_SIM_UTILITIES_FIELDMATH: "Field Math",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Protocols
|
||||||
|
####################
|
||||||
|
class SocketDefProtocol(typ.Protocol):
|
||||||
|
socket_type: SocketType
|
||||||
|
label: str
|
||||||
|
|
||||||
|
def init(self, bl_socket: bpy.types.NodeSocket) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
class PresetDef(pyd.BaseModel):
|
||||||
|
label: str
|
||||||
|
description: str
|
||||||
|
values: dict[SocketName, typ.Any]
|
||||||
|
|
||||||
|
@typ.runtime_checkable
|
||||||
|
#class BLSocketProtocol(typ.Protocol):
|
||||||
|
# socket_type: SocketType
|
||||||
|
# socket_color: BlenderColorRGB
|
||||||
|
#
|
||||||
|
# bl_label: str
|
||||||
|
#
|
||||||
|
# compatible_types: dict[typ.Type, set[typ.Callable[[typ.Any], bool]]]
|
||||||
|
#
|
||||||
|
# def draw(
|
||||||
|
# self,
|
||||||
|
# context: bpy.types.Context,
|
||||||
|
# layout: bpy.types.UILayout,
|
||||||
|
# node: bpy.types.Node,
|
||||||
|
# text: str,
|
||||||
|
# ) -> None:
|
||||||
|
# ...
|
||||||
|
#
|
||||||
|
# @property
|
||||||
|
# def default_value(self) -> typ.Any:
|
||||||
|
# ...
|
||||||
|
# @default_value.setter
|
||||||
|
# def default_value(self, value: typ.Any) -> typ.Any:
|
||||||
|
# ...
|
||||||
|
#
|
||||||
|
|
||||||
|
@typ.runtime_checkable
|
||||||
|
class NodeTypeProtocol(typ.Protocol):
|
||||||
|
node_type: NodeType
|
||||||
|
|
||||||
|
bl_label: str
|
||||||
|
|
||||||
|
input_sockets: dict[SocketName, SocketDefProtocol]
|
||||||
|
output_sockets: dict[SocketName, SocketDefProtocol]
|
||||||
|
presets: dict[PresetID, PresetDef] | None
|
||||||
|
|
||||||
|
# Built-In Blender Methods
|
||||||
|
def init(self, context: bpy.types.Context) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
def draw_buttons(
|
||||||
|
self,
|
||||||
|
context: bpy.types.Context,
|
||||||
|
layout: bpy.types.UILayout,
|
||||||
|
) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, ntree: bpy.types.NodeTree) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
# Socket Getters
|
||||||
|
def g_input_bl_socket(
|
||||||
|
self,
|
||||||
|
input_socket_name: SocketName,
|
||||||
|
) -> bpy.types.NodeSocket:
|
||||||
|
...
|
||||||
|
|
||||||
|
def g_output_bl_socket(
|
||||||
|
self,
|
||||||
|
output_socket_name: SocketName,
|
||||||
|
) -> bpy.types.NodeSocket:
|
||||||
|
...
|
||||||
|
|
||||||
|
# Socket Methods
|
||||||
|
def s_input_value(
|
||||||
|
self,
|
||||||
|
input_socket_name: SocketName,
|
||||||
|
value: typ.Any
|
||||||
|
) -> typ.Any:
|
||||||
|
...
|
||||||
|
|
||||||
|
# Data-Flow Methods
|
||||||
|
def compute_input(
|
||||||
|
self,
|
||||||
|
input_socket_name: SocketName,
|
||||||
|
) -> typ.Any:
|
||||||
|
...
|
||||||
|
def compute_output(
|
||||||
|
self,
|
||||||
|
output_socket_name: SocketName,
|
||||||
|
) -> typ.Any:
|
||||||
|
...
|
|
@ -0,0 +1,57 @@
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
from . import contracts
|
||||||
|
|
||||||
|
ICON_SIM_TREE = 'MOD_SIMPLEDEFORM'
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Node Tree Definition
|
||||||
|
####################
|
||||||
|
class MaxwellSimTree(bpy.types.NodeTree):
|
||||||
|
bl_idname = contracts.TreeType.MaxwellSim
|
||||||
|
bl_label = "Maxwell Sim Editor"
|
||||||
|
bl_icon = contracts.Icon.MaxwellSimTree
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Registration
|
||||||
|
####################
|
||||||
|
BL_REGISTER = [
|
||||||
|
MaxwellSimTree,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Red Edges on Error
|
||||||
|
####################
|
||||||
|
## TODO: Refactor
|
||||||
|
#def link_callback_new(context):
|
||||||
|
# print("A THING HAPPENED")
|
||||||
|
# node_tree_type = contracts.TreeType.MaxwellSim.value
|
||||||
|
# link = context.link
|
||||||
|
#
|
||||||
|
# if not (
|
||||||
|
# link.from_node.node_tree.bl_idname == node_tree_type
|
||||||
|
# and link.to_node.node_tree.bl_idname == node_tree_type
|
||||||
|
# ):
|
||||||
|
# return
|
||||||
|
#
|
||||||
|
# source_node = link.from_node
|
||||||
|
#
|
||||||
|
# source_socket_name = source_node.g_output_socket_name(
|
||||||
|
# link.from_socket.name
|
||||||
|
# )
|
||||||
|
# link_data = source_node.compute_output(source_socket_name)
|
||||||
|
#
|
||||||
|
# destination_socket = link.to_socket
|
||||||
|
# link.is_valid = destination_socket.is_compatible(link_data)
|
||||||
|
#
|
||||||
|
# print(source_node, destination_socket, link.is_valid)
|
||||||
|
#
|
||||||
|
#bpy.msgbus.subscribe_rna(
|
||||||
|
# key=("active", "node_tree"),
|
||||||
|
# owner=MaxwellSimTree,
|
||||||
|
# args=(bpy.context,),
|
||||||
|
# notify=link_callback_new,
|
||||||
|
# options={'PERSISTENT'}
|
||||||
|
#)
|
|
@ -1,8 +1,23 @@
|
||||||
|
from . import inputs
|
||||||
|
from . import outputs
|
||||||
|
from . import sources
|
||||||
from . import mediums
|
from . import mediums
|
||||||
|
from . import simulations
|
||||||
|
from . import structures
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
|
*inputs.BL_REGISTER,
|
||||||
|
*outputs.BL_REGISTER,
|
||||||
*mediums.BL_REGISTER,
|
*mediums.BL_REGISTER,
|
||||||
|
*sources.BL_REGISTER,
|
||||||
|
*simulations.BL_REGISTER,
|
||||||
|
*structures.BL_REGISTER,
|
||||||
]
|
]
|
||||||
BL_NODES = {
|
BL_NODES = {
|
||||||
|
**inputs.BL_NODES,
|
||||||
|
**outputs.BL_NODES,
|
||||||
|
**sources.BL_NODES,
|
||||||
**mediums.BL_NODES,
|
**mediums.BL_NODES,
|
||||||
|
**simulations.BL_NODES,
|
||||||
|
**structures.BL_NODES,
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,412 @@
|
||||||
|
import typing as typ
|
||||||
|
import typing_extensions as pytypes_ext
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import pydantic as pyd
|
||||||
|
|
||||||
|
from .. import contracts
|
||||||
|
from .. import sockets
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Decorator: Output Socket Computation
|
||||||
|
####################
|
||||||
|
@typ.runtime_checkable
|
||||||
|
class ComputeOutputSocketFunc(typ.Protocol[contracts.SocketReturnType]):
|
||||||
|
"""Protocol describing a function that computes the value of an
|
||||||
|
output socket.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __call__(
|
||||||
|
_self,
|
||||||
|
self: contracts.NodeTypeProtocol,
|
||||||
|
) -> contracts.SocketReturnType:
|
||||||
|
"""Describes the function signature of all functions that compute
|
||||||
|
the value of an output socket.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
node: A node in the tree, passed via the 'self' attribute of the
|
||||||
|
node.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The value of the output socket, as the relevant type.
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
|
class PydanticProtocolMeta(type(pyd.BaseModel), type(typ.Protocol)): pass
|
||||||
|
|
||||||
|
class FuncOutputSocket(
|
||||||
|
pyd.BaseModel,
|
||||||
|
typ.Generic[contracts.SocketReturnType],
|
||||||
|
ComputeOutputSocketFunc[contracts.SocketReturnType],
|
||||||
|
metaclass=PydanticProtocolMeta,
|
||||||
|
):
|
||||||
|
"""Defines a function (-like object) that defines an attachment from
|
||||||
|
an output socket name, to the original method that computes the value of
|
||||||
|
an output socket.
|
||||||
|
|
||||||
|
Conforms to the protocol `ComputeOutputSocketFunc`.
|
||||||
|
Validation is provided by subtyping `pydantic.BaseModel`.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
output_socket_func: The original method computing the value of an
|
||||||
|
output socket.
|
||||||
|
output_socket_name: The SocketName of the output socket for which
|
||||||
|
this function should be called to compute.
|
||||||
|
"""
|
||||||
|
|
||||||
|
output_socket_func: typ.Callable[
|
||||||
|
[contracts.NodeTypeProtocol],
|
||||||
|
contracts.SocketReturnType,
|
||||||
|
]
|
||||||
|
output_socket_name: contracts.SocketName
|
||||||
|
|
||||||
|
def __call__(
|
||||||
|
self,
|
||||||
|
node: contracts.NodeTypeProtocol
|
||||||
|
) -> contracts.SocketReturnType:
|
||||||
|
"""Computes the value of an output socket.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
node: A node in the tree, passed via the 'self' attribute of the
|
||||||
|
node.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The value of the output socket, as the relevant type.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.output_socket_func(node)
|
||||||
|
|
||||||
|
# Define Factory Function & Decorator
|
||||||
|
def computes_output_socket(
|
||||||
|
output_socket_name: contracts.SocketName,
|
||||||
|
return_type: typ.Generic[contracts.SocketReturnType],
|
||||||
|
) -> typ.Callable[
|
||||||
|
[ComputeOutputSocketFunc[contracts.SocketReturnType]],
|
||||||
|
FuncOutputSocket[contracts.SocketReturnType],
|
||||||
|
]:
|
||||||
|
"""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.
|
||||||
|
|
||||||
|
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`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(
|
||||||
|
output_socket_func: ComputeOutputSocketFunc[contracts.SocketReturnType]
|
||||||
|
) -> FuncOutputSocket[contracts.SocketReturnType]:
|
||||||
|
return FuncOutputSocket(
|
||||||
|
output_socket_func=output_socket_func,
|
||||||
|
output_socket_name=output_socket_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Node Callbacks
|
||||||
|
####################
|
||||||
|
def sync_selected_preset(node: contracts.NodeTypeProtocol) -> None:
|
||||||
|
"""Whenever a preset is set in a NodeTypeProtocol, this function
|
||||||
|
should be called to overwrite the `default_value`s of the input sockets
|
||||||
|
with the actual preset values.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
node: The node for which input socket `default_value`s should be
|
||||||
|
set to the values defined within the currently selected preset.
|
||||||
|
"""
|
||||||
|
if node.preset is None:
|
||||||
|
msg = f"Node {node} has no preset EnumProperty"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
if node.presets is None:
|
||||||
|
msg = f"Node {node} has preset EnumProperty, but no defined presets."
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
# Set Input Sockets to Preset Values
|
||||||
|
preset_def = node.presets[node.preset]
|
||||||
|
for input_socket_name, value in preset_def.values.items():
|
||||||
|
node.s_input_value(input_socket_name, value)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Node Superclass Definition
|
||||||
|
####################
|
||||||
|
class MaxwellSimTreeNode(bpy.types.Node):
|
||||||
|
"""A base type for nodes that greatly simplifies the implementation of
|
||||||
|
reliable, powerful nodes.
|
||||||
|
|
||||||
|
Should be used together with `contracts.NodeTypeProtocol`.
|
||||||
|
"""
|
||||||
|
def __init_subclass__(cls, **kwargs: typ.Any):
|
||||||
|
super().__init_subclass__(**kwargs) ## Yucky superclass setup.
|
||||||
|
|
||||||
|
# Set bl_idname
|
||||||
|
cls.bl_idname = cls.node_type.value
|
||||||
|
|
||||||
|
# Declare Node Property: 'preset' EnumProperty
|
||||||
|
if hasattr(cls, "presets"):
|
||||||
|
first_preset = list(cls.presets.keys())[0]
|
||||||
|
cls.__annotations__["preset"] = bpy.props.EnumProperty(
|
||||||
|
name="Presets",
|
||||||
|
description="Select a preset",
|
||||||
|
items=[
|
||||||
|
(
|
||||||
|
preset_name,
|
||||||
|
preset_def.label,
|
||||||
|
preset_def.description,
|
||||||
|
)
|
||||||
|
for preset_name, preset_def in cls.presets.items()
|
||||||
|
],
|
||||||
|
default=first_preset, ## 1st is Default
|
||||||
|
update=(lambda self, context: sync_selected_preset(self)),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
cls.preset = None
|
||||||
|
cls.presets = None
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Init / Constraints
|
||||||
|
####################
|
||||||
|
def init(self, context: bpy.types.Context):
|
||||||
|
"""Declares input and output sockets as described by the
|
||||||
|
`NodeTypeProtocol` specification, and initializes each as described
|
||||||
|
by user-provided `SocketDefProtocol`s.
|
||||||
|
"""
|
||||||
|
# Initialize Input Sockets
|
||||||
|
for socket_name, socket_def in self.input_sockets.items():
|
||||||
|
self.inputs.new(
|
||||||
|
socket_def.socket_type.value, ## strenum.value => a real str
|
||||||
|
socket_def.label,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Retrieve the Blender Socket (bpy.types.NodeSocket)
|
||||||
|
## We could use self.g_input_bl_socket()...
|
||||||
|
## ...but that would rely on implicit semi-initialized state.
|
||||||
|
bl_socket = self.inputs[
|
||||||
|
self.input_sockets[socket_name].label
|
||||||
|
]
|
||||||
|
|
||||||
|
# Initialize the Socket from the Socket Definition
|
||||||
|
## `bl_socket` knows whether it's an input or output socket...
|
||||||
|
## ...via its `.is_output` attribute.
|
||||||
|
socket_def.init(bl_socket)
|
||||||
|
|
||||||
|
# Initialize Output Sockets
|
||||||
|
for socket_name, socket_def in self.output_sockets.items():
|
||||||
|
self.outputs.new(
|
||||||
|
socket_def.socket_type.value,
|
||||||
|
socket_def.label,
|
||||||
|
)
|
||||||
|
|
||||||
|
bl_socket = self.outputs[
|
||||||
|
self.output_sockets[socket_name].label
|
||||||
|
]
|
||||||
|
socket_def.init(bl_socket)
|
||||||
|
|
||||||
|
# Sync Default Preset to Input Socket Values
|
||||||
|
if self.preset is not None:
|
||||||
|
sync_selected_preset(self)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, ntree: bpy.types.NodeTree) -> bool:
|
||||||
|
"""This class method controls whether a node can be instantiated
|
||||||
|
in a given node tree.
|
||||||
|
|
||||||
|
In our case, we restrict node instantiation to within a
|
||||||
|
MaxwellSimTree.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ntree: The node tree within which the user is currently working.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Whether or not the user should be able to instantiate the node.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
return ntree.bl_idname == contracts.TreeType.MaxwellSim.value
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - UI Methods
|
||||||
|
####################
|
||||||
|
def draw_buttons(
|
||||||
|
self,
|
||||||
|
context: bpy.types.Context,
|
||||||
|
layout: bpy.types.UILayout,
|
||||||
|
) -> None:
|
||||||
|
"""This method draws the UI of the node itself.
|
||||||
|
|
||||||
|
Specifically, it is used to expose the Presets dropdown.
|
||||||
|
"""
|
||||||
|
if self.preset is not None:
|
||||||
|
layout.prop(self, "preset", text="")
|
||||||
|
|
||||||
|
if hasattr(self, "draw_operators"):
|
||||||
|
self.draw_operators(context, layout)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Socket Getters
|
||||||
|
####################
|
||||||
|
def g_input_bl_socket(
|
||||||
|
self,
|
||||||
|
input_socket_name: contracts.SocketName,
|
||||||
|
) -> bpy.types.NodeSocket:
|
||||||
|
"""Returns the `bpy.types.NodeSocket` of an input socket by name.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_socket_name: The name of the input socket, as defined in
|
||||||
|
`self.input_sockets`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Blender's own node socket object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# (Guard) Socket Exists
|
||||||
|
if input_socket_name not in self.input_sockets:
|
||||||
|
msg = f"Input socket with name {input_socket_name} does not exist"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
return self.inputs[self.input_sockets[input_socket_name].label]
|
||||||
|
|
||||||
|
def g_output_bl_socket(
|
||||||
|
self,
|
||||||
|
output_socket_name: contracts.SocketName,
|
||||||
|
) -> bpy.types.NodeSocket:
|
||||||
|
"""Returns the `bpy.types.NodeSocket` of an output socket by name.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
output_socket_name: The name of the output socket, as defined in
|
||||||
|
`self.output_sockets`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Blender's own node socket object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# (Guard) Socket Exists
|
||||||
|
if output_socket_name not in self.output_sockets:
|
||||||
|
msg = f"Input socket with name {output_socket_name} does not exist"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
return self.outputs[self.output_sockets[output_socket_name].label]
|
||||||
|
|
||||||
|
def g_output_socket_name(
|
||||||
|
self,
|
||||||
|
output_bl_socket_name: contracts.BLSocketName,
|
||||||
|
) -> contracts.SocketName:
|
||||||
|
return next(
|
||||||
|
output_socket_name
|
||||||
|
for output_socket_name in self.output_sockets.keys()
|
||||||
|
if self.output_sockets[
|
||||||
|
output_socket_name
|
||||||
|
].label == output_bl_socket_name
|
||||||
|
)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Socket Setters
|
||||||
|
####################
|
||||||
|
def s_input_value(
|
||||||
|
self,
|
||||||
|
input_socket_name: contracts.SocketName,
|
||||||
|
value: typ.Any,
|
||||||
|
) -> None:
|
||||||
|
"""Sets the value of an input socket, if the value is compatible with
|
||||||
|
the socket.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_socket_name: The name of the input socket.
|
||||||
|
value: The value to set, which must be compatible with the
|
||||||
|
socket.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If the value is incompatible with the socket, for
|
||||||
|
example due to incompatible types, then a ValueError will be
|
||||||
|
raised.
|
||||||
|
"""
|
||||||
|
bl_socket = self.g_input_bl_socket(input_socket_name)
|
||||||
|
|
||||||
|
# Set the Value
|
||||||
|
bl_socket.default_value = value
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Socket Computation
|
||||||
|
####################
|
||||||
|
def compute_input(
|
||||||
|
self,
|
||||||
|
input_socket_name: contracts.SocketName,
|
||||||
|
) -> typ.Any:
|
||||||
|
"""Computes the value of an input socket, by its name. Will
|
||||||
|
automatically compute the output socket value of any linked
|
||||||
|
nodes.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_socket_name: The name of the input socket, as defined in
|
||||||
|
`self.input_sockets`.
|
||||||
|
"""
|
||||||
|
bl_socket = self.g_input_bl_socket(input_socket_name)
|
||||||
|
|
||||||
|
# Linked: Compute Output of Linked Socket
|
||||||
|
if bl_socket.is_linked:
|
||||||
|
linked_node = bl_socket.links[0].from_node
|
||||||
|
|
||||||
|
# Compute the Linked Socket Name
|
||||||
|
linked_bl_socket_name: contracts.BLSocketName = bl_socket.links[0].from_socket.name
|
||||||
|
linked_socket_name = linked_node.g_output_socket_name(
|
||||||
|
linked_bl_socket_name
|
||||||
|
)
|
||||||
|
|
||||||
|
# Compute the Linked Socket Value
|
||||||
|
linked_socket_value = linked_node.compute_output(
|
||||||
|
linked_socket_name
|
||||||
|
)
|
||||||
|
|
||||||
|
# (Guard) Check the Compatibility of the Linked Socket Value
|
||||||
|
if not bl_socket.is_compatible(linked_socket_value):
|
||||||
|
msg = f"Tried setting socket ({input_socket_name}) to incompatible value ({linked_socket_value}) of type {type(linked_socket_value)}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
return linked_socket_value
|
||||||
|
|
||||||
|
# Unlinked: Simply Retrieve Socket Value
|
||||||
|
return bl_socket.default_value
|
||||||
|
|
||||||
|
def compute_output(
|
||||||
|
self,
|
||||||
|
output_socket_name: contracts.SocketName,
|
||||||
|
) -> typ.Any:
|
||||||
|
"""Computes the value of an output socket name, from its socket name.
|
||||||
|
|
||||||
|
Searches for methods decorated with `@computes_output_socket("name")`,
|
||||||
|
which describe the computation that occurs to actually compute the
|
||||||
|
value of an output socket from ex. input sockets and node properties.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
output_socket_name: The name declaring the output socket,
|
||||||
|
for which this method computes the output.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The value of the output socket, as computed by the dedicated method
|
||||||
|
registered using the `@computes_output_socket` decorator.
|
||||||
|
"""
|
||||||
|
# Lookup the Function that Computes the Output Socket
|
||||||
|
## The decorator ALWAYS produces a FuncOutputSocket.
|
||||||
|
## Thus, we merely need to find a FuncOutputSocket
|
||||||
|
output_socket_func = next(
|
||||||
|
method.output_socket_func
|
||||||
|
for attr_name in dir(self) ## Lookup self.*
|
||||||
|
if isinstance(
|
||||||
|
method := getattr(self, attr_name),
|
||||||
|
FuncOutputSocket,
|
||||||
|
)
|
||||||
|
if method.output_socket_name == output_socket_name
|
||||||
|
)
|
||||||
|
|
||||||
|
return output_socket_func(self)
|
|
@ -0,0 +1,8 @@
|
||||||
|
from . import constants
|
||||||
|
|
||||||
|
BL_REGISTER = [
|
||||||
|
*constants.BL_REGISTER,
|
||||||
|
]
|
||||||
|
BL_NODES = {
|
||||||
|
**constants.BL_NODES,
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
from . import complex_constant
|
||||||
|
|
||||||
|
BL_REGISTER = [
|
||||||
|
*complex_constant.BL_REGISTER,
|
||||||
|
]
|
||||||
|
BL_NODES = {
|
||||||
|
**complex_constant.BL_NODES,
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
import bpy
|
||||||
|
import sympy as sp
|
||||||
|
|
||||||
|
from .... import contracts
|
||||||
|
from .... import sockets
|
||||||
|
from ... import base
|
||||||
|
|
||||||
|
class ComplexConstantNode(base.MaxwellSimTreeNode):
|
||||||
|
node_type = contracts.NodeType.ComplexConstant
|
||||||
|
|
||||||
|
bl_label = "Complex Constant"
|
||||||
|
#bl_icon = constants.ICON_SIM_INPUT
|
||||||
|
|
||||||
|
input_sockets = {
|
||||||
|
"value": sockets.ComplexNumberSocketDef(
|
||||||
|
label="Complex",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
output_sockets = {
|
||||||
|
"value": sockets.ComplexNumberSocketDef(
|
||||||
|
label="Complex",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Callbacks
|
||||||
|
####################
|
||||||
|
@base.computes_output_socket("value", sp.Expr)
|
||||||
|
def compute_value(self: contracts.NodeTypeProtocol) -> sp.Expr:
|
||||||
|
return self.compute_input("value")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Registration
|
||||||
|
####################
|
||||||
|
BL_REGISTER = [
|
||||||
|
ComplexConstantNode,
|
||||||
|
]
|
||||||
|
BL_NODES = {
|
||||||
|
contracts.NodeType.ComplexConstant: (
|
||||||
|
contracts.NodeCategory.MAXWELL_SIM_INPUTS_CONSTANTS
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
import bpy
|
||||||
|
import gpu
|
||||||
|
import gpu_extras
|
||||||
|
import sympy.physics.units as spu
|
||||||
|
from .... import types, constants
|
||||||
|
from ... import node_base
|
||||||
|
|
||||||
|
class TripleSellmeierMediumNode(node_base.MaxwellSimTreeNode):
|
||||||
|
bl_idname = types.NodeType.TripleSellmeierMedium.value
|
||||||
|
bl_label = "Triple Sellmeier Medium"
|
||||||
|
bl_icon = constants.ICON_SIM_MEDIUM
|
||||||
|
|
||||||
|
input_sockets = {
|
||||||
|
"B1": (types.SocketType.RealNumber, "B1"),
|
||||||
|
"B2": (types.SocketType.RealNumber, "B2"),
|
||||||
|
"B3": (types.SocketType.RealNumber, "B3"),
|
||||||
|
"C1": (types.SocketType.DimenArea, "C1"),
|
||||||
|
"C2": (types.SocketType.DimenArea, "C2"),
|
||||||
|
"C3": (types.SocketType.DimenArea, "C3"),
|
||||||
|
}
|
||||||
|
output_sockets = {
|
||||||
|
"medium": (types.SocketType.MaxwellMedium, "Medium")
|
||||||
|
}
|
||||||
|
|
||||||
|
input_unit_defaults = {
|
||||||
|
"B1": None,
|
||||||
|
"B2": None,
|
||||||
|
"B3": None,
|
||||||
|
"C1": spu.um**2,
|
||||||
|
"C2": spu.um**2,
|
||||||
|
"C3": spu.um**2,
|
||||||
|
}
|
||||||
|
socket_presets = {
|
||||||
|
"_description": [
|
||||||
|
('BK7', "BK7 Glass", "Borosilicate crown glass (known as BK7)"),
|
||||||
|
('FUSED_SILICA', "Fused Silica", "Fused silica aka. SiO2"),
|
||||||
|
],
|
||||||
|
"_default": "BK7",
|
||||||
|
"_values": {
|
||||||
|
"BK7": {
|
||||||
|
"B1": 1.03961212,
|
||||||
|
"B2": 0.231792344,
|
||||||
|
"B3": 1.01046945,
|
||||||
|
"C1": 6.00069867e-3 * spu.um**2,
|
||||||
|
"C2": 2.00179144e-2 * spu.um**2,
|
||||||
|
"C3": 103.560653 * spu.um**2,
|
||||||
|
},
|
||||||
|
"FUSED_SILICA": {
|
||||||
|
"B1": 0.696166300,
|
||||||
|
"B2": 0.407942600,
|
||||||
|
"B3": 0.897479400,
|
||||||
|
"C1": 4.67914826e-3 * spu.um**2,
|
||||||
|
"C2": 1.35120631e-2 * spu.um**2,
|
||||||
|
"C3": 97.9340025 * spu.um**2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Properties
|
||||||
|
####################
|
||||||
|
def draw_buttons(self, context, layout):
|
||||||
|
layout.prop(self, 'preset', text="")
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Callbacks
|
||||||
|
####################
|
||||||
|
@node_base.output_socket_cb("medium")
|
||||||
|
def compute_medium(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Registration
|
||||||
|
####################
|
||||||
|
BL_REGISTER = [
|
||||||
|
TripleSellmeierMediumNode,
|
||||||
|
]
|
||||||
|
BL_NODES = {
|
||||||
|
types.NodeType.TripleSellmeierMedium: (
|
||||||
|
types.NodeCategory.MAXWELL_SIM_MEDIUMS_LINEARMEDIUMS
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,54 +1,90 @@
|
||||||
import bpy
|
import tidy3d as td
|
||||||
from .... import types, constants
|
import sympy as sp
|
||||||
from ... import node_base
|
import sympy.physics.units as spu
|
||||||
|
|
||||||
class TripleSellmeierMediumNode(node_base.MaxwellSimTreeNode):
|
from .... import contracts
|
||||||
bl_idname = types.NodeType.TripleSellmeierMedium.value
|
from .... import sockets
|
||||||
bl_label = "Triple Sellmeier Medium"
|
from ... import base
|
||||||
bl_icon = constants.ICON_SIM_MEDIUM
|
|
||||||
|
|
||||||
|
class TripleSellmeierMediumNode(base.MaxwellSimTreeNode):
|
||||||
|
node_type = contracts.NodeType.TripleSellmeierMedium
|
||||||
|
|
||||||
|
bl_label = "Three-Parameter Sellmeier Medium"
|
||||||
|
#bl_icon = ...
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Sockets
|
||||||
|
####################
|
||||||
input_sockets = {
|
input_sockets = {
|
||||||
"B1": ("NodeSocketFloat", "B1"),
|
f"B{i}": sockets.RealNumberSocketDef(
|
||||||
"B2": ("NodeSocketFloat", "B2"),
|
label=f"B{i}",
|
||||||
"B3": ("NodeSocketFloat", "B3"),
|
)
|
||||||
"C1": ("NodeSocketFloat", "C1 (um^2)"),
|
for i in [1, 2, 3]
|
||||||
"C2": ("NodeSocketFloat", "C2 (um^2)"),
|
} | {
|
||||||
"C3": ("NodeSocketFloat", "C3 (um^2)"),
|
f"C{i}": sockets.PhysicalAreaSocketDef(
|
||||||
|
label=f"C{i}",
|
||||||
|
default_unit="UM_SQ"
|
||||||
|
)
|
||||||
|
for i in [1, 2, 3]
|
||||||
}
|
}
|
||||||
output_sockets = {
|
output_sockets = {
|
||||||
"medium": (types.SocketType.MaxwellMedium, "Medium")
|
"medium": sockets.MaxwellMediumSocketDef(
|
||||||
}
|
label="Medium"
|
||||||
socket_presets = {
|
),
|
||||||
"_description": [
|
|
||||||
('BK7', "BK7 Glass", "Borosilicate crown glass (known as BK7)"),
|
|
||||||
('FUSED_SILICA', "Fused Silica", "Fused silica aka. SiO2"),
|
|
||||||
],
|
|
||||||
"_default": "BK7",
|
|
||||||
"_values": {
|
|
||||||
"BK7": {
|
|
||||||
"B1": 1.03961212,
|
|
||||||
"B2": 0.231792344,
|
|
||||||
"B3": 1.01046945,
|
|
||||||
"C1": 6.00069867e-3,
|
|
||||||
"C2": 2.00179144e-2,
|
|
||||||
"C3": 103.560653,
|
|
||||||
},
|
|
||||||
"FUSED_SILICA": {
|
|
||||||
"B1": 0.696166300,
|
|
||||||
"B2": 0.407942600,
|
|
||||||
"B3": 0.897479400,
|
|
||||||
"C1": 4.67914826e-3,
|
|
||||||
"C2": 1.35120631e-2,
|
|
||||||
"C3": 97.9340025,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Properties
|
# - Presets
|
||||||
####################
|
####################
|
||||||
def draw_buttons(self, context, layout):
|
presets = {
|
||||||
layout.prop(self, 'preset', text="")
|
"BK7": contracts.PresetDef(
|
||||||
|
label="BK7 Glass",
|
||||||
|
description="Borosilicate crown glass (known as BK7)",
|
||||||
|
values={
|
||||||
|
"B1": 1.03961212,
|
||||||
|
"B2": 0.231792344,
|
||||||
|
"B3": 1.01046945,
|
||||||
|
"C1": 6.00069867e-3 * spu.um**2,
|
||||||
|
"C2": 2.00179144e-2 * spu.um**2,
|
||||||
|
"C3": 103.560653 * spu.um**2,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"FUSED_SILICA": contracts.PresetDef(
|
||||||
|
label="Fused Silica",
|
||||||
|
description="Fused silica aka. SiO2",
|
||||||
|
values={
|
||||||
|
"B1": 0.696166300,
|
||||||
|
"B2": 0.407942600,
|
||||||
|
"B3": 0.897479400,
|
||||||
|
"C1": 4.67914826e-3 * spu.um**2,
|
||||||
|
"C2": 1.35120631e-2 * spu.um**2,
|
||||||
|
"C3": 97.9340025 * spu.um**2,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Output Socket Computation
|
||||||
|
####################
|
||||||
|
@base.computes_output_socket("medium", td.Sellmeier)
|
||||||
|
def compute_medium(self: contracts.NodeTypeProtocol) -> td.Sellmeier:
|
||||||
|
## Retrieval
|
||||||
|
#B1 = self.compute_input("B1")
|
||||||
|
#C1_with_units = self.compute_input("C1")
|
||||||
|
#
|
||||||
|
## Processing
|
||||||
|
#C1 = spu.convert_to(C1_with_units, spu.um**2) / spu.um**2
|
||||||
|
|
||||||
|
return td.Sellmeier(coeffs = [
|
||||||
|
(
|
||||||
|
self.compute_input(f"B{i}"),
|
||||||
|
spu.convert_to(
|
||||||
|
self.compute_input(f"C{i}"),
|
||||||
|
spu.um**2,
|
||||||
|
) / spu.um**2
|
||||||
|
)
|
||||||
|
for i in [1, 2, 3]
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -59,7 +95,7 @@ BL_REGISTER = [
|
||||||
TripleSellmeierMediumNode,
|
TripleSellmeierMediumNode,
|
||||||
]
|
]
|
||||||
BL_NODES = {
|
BL_NODES = {
|
||||||
types.NodeType.TripleSellmeierMedium: (
|
contracts.NodeType.TripleSellmeierMedium: (
|
||||||
types.NodeCategory.MAXWELL_SIM_MEDIUMS_LINEARMEDIUMS
|
contracts.NodeCategory.MAXWELL_SIM_MEDIUMS_LINEARMEDIUMS
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,140 +0,0 @@
|
||||||
import numpy as np
|
|
||||||
import bpy
|
|
||||||
import nodeitems_utils
|
|
||||||
|
|
||||||
from .. import types
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Decorator: Output Socket
|
|
||||||
####################
|
|
||||||
def output_socket_cb(name):
|
|
||||||
def decorator(func):
|
|
||||||
func._output_socket_name = name # Set a marker attribute
|
|
||||||
return func
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Socket Type Casters
|
|
||||||
####################
|
|
||||||
SOCKET_CAST_MAP = {
|
|
||||||
"NodeSocketBool": bool,
|
|
||||||
"NodeSocketFloat": float,
|
|
||||||
"NodeSocketFloatAngle": float,
|
|
||||||
"NodeSocketFloatDistance": float,
|
|
||||||
"NodeSocketFloatFactor": float,
|
|
||||||
"NodeSocketFloatPercentage": float,
|
|
||||||
"NodeSocketFloatTime": float,
|
|
||||||
"NodeSocketFloatTimeAbsolute": float,
|
|
||||||
"NodeSocketFloatUnsigned": float,
|
|
||||||
"NodeSocketFloatInt": int,
|
|
||||||
"NodeSocketFloatIntFactor": int,
|
|
||||||
"NodeSocketFloatIntPercentage": int,
|
|
||||||
"NodeSocketFloatIntUnsigned": int,
|
|
||||||
"NodeSocketString": str,
|
|
||||||
"NodeSocketVector": np.array,
|
|
||||||
"NodeSocketVectorAcceleration": np.array,
|
|
||||||
"NodeSocketVectorDirection": np.array,
|
|
||||||
"NodeSocketVectorTranslation": np.array,
|
|
||||||
"NodeSocketVectorVelocity": np.array,
|
|
||||||
"NodeSocketVectorXYZ": np.array,
|
|
||||||
}
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Node Superclass
|
|
||||||
####################
|
|
||||||
def set_preset(self, context):
|
|
||||||
for preset_name, preset_dict in self.socket_presets["_values"].items():
|
|
||||||
if self.preset == preset_name:
|
|
||||||
for input_socket_name, value in preset_dict.items():
|
|
||||||
self.inputs[
|
|
||||||
self.input_sockets[input_socket_name][1]
|
|
||||||
].default_value = value
|
|
||||||
|
|
||||||
break
|
|
||||||
|
|
||||||
class MaxwellSimTreeNode(bpy.types.Node):
|
|
||||||
def __init_subclass__(cls, **kwargs):
|
|
||||||
super().__init_subclass__(**kwargs)
|
|
||||||
required_attrs = [
|
|
||||||
'bl_idname',
|
|
||||||
'bl_label',
|
|
||||||
'bl_icon',
|
|
||||||
'input_sockets',
|
|
||||||
'output_sockets',
|
|
||||||
]
|
|
||||||
for attr in required_attrs:
|
|
||||||
if getattr(cls, attr, None) is None:
|
|
||||||
raise TypeError(
|
|
||||||
f"class {cls.__name__} is missing required '{attr}' attribute"
|
|
||||||
)
|
|
||||||
|
|
||||||
if hasattr(cls, 'socket_presets'):
|
|
||||||
cls.__annotations__["preset"] = bpy.props.EnumProperty(
|
|
||||||
name="Presets",
|
|
||||||
description="Select a preset",
|
|
||||||
items=cls.socket_presets["_description"],
|
|
||||||
default=cls.socket_presets["_default"],
|
|
||||||
update=set_preset,
|
|
||||||
)
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Node Initialization
|
|
||||||
####################
|
|
||||||
def init(self, context):
|
|
||||||
for input_socket_name in self.input_sockets:
|
|
||||||
self.inputs.new(*self.input_sockets[input_socket_name][:2])
|
|
||||||
|
|
||||||
for output_socket_name in self.output_sockets:
|
|
||||||
self.outputs.new(*self.output_sockets[output_socket_name][:2])
|
|
||||||
|
|
||||||
set_preset(self, context)
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Node Computation
|
|
||||||
####################
|
|
||||||
def compute_input(self, input_socket_name: str):
|
|
||||||
"""Computes the value of an input socket name.
|
|
||||||
"""
|
|
||||||
bl_socket_type = self.input_sockets[input_socket_name][0]
|
|
||||||
bl_socket = self.inputs[self.input_sockets[input_socket_name][1]]
|
|
||||||
|
|
||||||
if bl_socket.is_linked:
|
|
||||||
linked_node = bl_socket.links[0].from_node
|
|
||||||
linked_bl_socket_name = bl_socket.links[0].from_socket.name
|
|
||||||
result = linked_node.compute_output(linked_bl_socket_name)
|
|
||||||
else:
|
|
||||||
result = bl_socket.default_value
|
|
||||||
|
|
||||||
if bl_socket_type in SOCKET_CAST_MAP:
|
|
||||||
return SOCKET_CAST_MAP[bl_socket_type](result)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def compute_output(self, output_bl_socket_name: str):
|
|
||||||
"""Computes the value of an output socket name, from its Blender display name.
|
|
||||||
"""
|
|
||||||
output_socket_name = next(
|
|
||||||
output_socket_name
|
|
||||||
for output_socket_name in self.output_sockets.keys()
|
|
||||||
if self.output_sockets[output_socket_name][1] == output_bl_socket_name
|
|
||||||
)
|
|
||||||
|
|
||||||
output_socket_name_to_cb = {
|
|
||||||
getattr(attr, '_output_socket_name'): attr
|
|
||||||
for attr_name in dir(self)
|
|
||||||
if (
|
|
||||||
callable(attr := getattr(self, attr_name))
|
|
||||||
and hasattr(attr, '_output_socket_name')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return output_socket_name_to_cb[output_socket_name]()
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Blender Configuration
|
|
||||||
####################
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, ntree):
|
|
||||||
"""Constrain node instantiation to within a MaxwellSimTree."""
|
|
||||||
|
|
||||||
return ntree.bl_idname == types.TreeType.MaxwellSim
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
from . import exporters
|
||||||
|
|
||||||
|
BL_REGISTER = [
|
||||||
|
*exporters.BL_REGISTER,
|
||||||
|
]
|
||||||
|
BL_NODES = {
|
||||||
|
**exporters.BL_NODES,
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
from . import json_file_exporter
|
||||||
|
|
||||||
|
BL_REGISTER = [
|
||||||
|
*json_file_exporter.BL_REGISTER,
|
||||||
|
]
|
||||||
|
BL_NODES = {
|
||||||
|
**json_file_exporter.BL_NODES,
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
import typing as typ
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import sympy as sp
|
||||||
|
import pydantic as pyd
|
||||||
|
|
||||||
|
from .... import contracts
|
||||||
|
from .... import sockets
|
||||||
|
from ... import base
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Operators
|
||||||
|
####################
|
||||||
|
class JSONFileExporterPrintJSON(bpy.types.Operator):
|
||||||
|
bl_idname = "blender_maxwell.json_file_exporter_print_json"
|
||||||
|
bl_label = "Print the JSON of what's linked into a JSONFileExporterNode."
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
node = context.node
|
||||||
|
print(node.linked_data_as_json())
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
class JSONFileExporterSaveJSON(bpy.types.Operator):
|
||||||
|
bl_idname = "blender_maxwell.json_file_exporter_save_json"
|
||||||
|
bl_label = "Save the JSON of what's linked into a JSONFileExporterNode."
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
node = context.node
|
||||||
|
node.export_data_as_json()
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Node
|
||||||
|
####################
|
||||||
|
class JSONFileExporterNode(base.MaxwellSimTreeNode):
|
||||||
|
node_type = contracts.NodeType.JSONFileExporter
|
||||||
|
|
||||||
|
bl_label = "JSON File Exporter"
|
||||||
|
#bl_icon = constants.ICON_SIM_INPUT
|
||||||
|
|
||||||
|
input_sockets = {
|
||||||
|
"json_path": sockets.FilePathSocketDef(
|
||||||
|
label="JSON Path",
|
||||||
|
default_path="simulation.json"
|
||||||
|
),
|
||||||
|
"data": sockets.AnySocketDef(
|
||||||
|
label="Data",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
output_sockets = {}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - UI Layout
|
||||||
|
####################
|
||||||
|
def draw_operators(
|
||||||
|
self,
|
||||||
|
context: bpy.types.Context,
|
||||||
|
layout: bpy.types.UILayout,
|
||||||
|
) -> None:
|
||||||
|
layout.operator(JSONFileExporterPrintJSON.bl_idname, text="Print")
|
||||||
|
layout.operator(JSONFileExporterSaveJSON.bl_idname, text="Save")
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Methods
|
||||||
|
####################
|
||||||
|
def linked_data_as_json(self) -> str | None:
|
||||||
|
if self.g_input_bl_socket("data").is_linked:
|
||||||
|
data: typ.Any = self.compute_input("data")
|
||||||
|
|
||||||
|
# Tidy3D Objects: Call .json()
|
||||||
|
if hasattr(data, "json"):
|
||||||
|
return data.json()
|
||||||
|
|
||||||
|
# Pydantic Models: Call .model_dump_json()
|
||||||
|
elif isinstance(data, pyd.BaseModel):
|
||||||
|
return data.model_dump_json()
|
||||||
|
|
||||||
|
# Finally: Try json.dumps (might fail)
|
||||||
|
else:
|
||||||
|
json.dumps(data)
|
||||||
|
|
||||||
|
def export_data_as_json(self) -> None:
|
||||||
|
if (data := self.linked_data_as_json()):
|
||||||
|
data_dict = json.loads(data)
|
||||||
|
with self.compute_input("json_path").open("w") as f:
|
||||||
|
json.dump(data_dict, f, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Registration
|
||||||
|
####################
|
||||||
|
BL_REGISTER = [
|
||||||
|
JSONFileExporterPrintJSON,
|
||||||
|
JSONFileExporterNode,
|
||||||
|
JSONFileExporterSaveJSON,
|
||||||
|
]
|
||||||
|
BL_NODES = {
|
||||||
|
contracts.NodeType.JSONFileExporter: (
|
||||||
|
contracts.NodeCategory.MAXWELL_SIM_OUTPUTS_EXPORTERS
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
from . import fdtd_simulation
|
||||||
|
|
||||||
|
BL_REGISTER = [
|
||||||
|
*fdtd_simulation.BL_REGISTER,
|
||||||
|
]
|
||||||
|
BL_NODES = {
|
||||||
|
**fdtd_simulation.BL_NODES,
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
import tidy3d as td
|
||||||
|
import sympy as sp
|
||||||
|
import sympy.physics.units as spu
|
||||||
|
|
||||||
|
from ... import contracts
|
||||||
|
from ... import sockets
|
||||||
|
from .. import base
|
||||||
|
|
||||||
|
class FDTDSimNode(base.MaxwellSimTreeNode):
|
||||||
|
node_type = contracts.NodeType.FDTDSim
|
||||||
|
|
||||||
|
bl_label = "FDTD Simulation"
|
||||||
|
#bl_icon = ...
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Sockets
|
||||||
|
####################
|
||||||
|
input_sockets = {
|
||||||
|
"run_time": sockets.RealNumberSocketDef(
|
||||||
|
label="Run Time",
|
||||||
|
default_value=1.0,
|
||||||
|
),
|
||||||
|
"size_x": sockets.RealNumberSocketDef(
|
||||||
|
label="Size X",
|
||||||
|
default_value=1.0,
|
||||||
|
),
|
||||||
|
"size_y": sockets.RealNumberSocketDef(
|
||||||
|
label="Size Y",
|
||||||
|
default_value=1.0,
|
||||||
|
),
|
||||||
|
"size_z": sockets.RealNumberSocketDef(
|
||||||
|
label="Size Z",
|
||||||
|
default_value=1.0,
|
||||||
|
),
|
||||||
|
"ambient_medium": sockets.MaxwellMediumSocketDef(
|
||||||
|
label="Ambient Medium",
|
||||||
|
),
|
||||||
|
"source": sockets.MaxwellSourceSocketDef(
|
||||||
|
label="Source",
|
||||||
|
),
|
||||||
|
"structure": sockets.MaxwellStructureSocketDef(
|
||||||
|
label="Structure",
|
||||||
|
),
|
||||||
|
"bound": sockets.MaxwellBoundSocketDef(
|
||||||
|
label="Bound",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
output_sockets = {
|
||||||
|
"fdtd_sim": sockets.MaxwellFDTDSimSocketDef(
|
||||||
|
label="Medium",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Output Socket Computation
|
||||||
|
####################
|
||||||
|
@base.computes_output_socket("fdtd_sim", td.Sellmeier)
|
||||||
|
def compute_simulation(self: contracts.NodeTypeProtocol) -> td.Simulation:
|
||||||
|
run_time = self.compute_input("run_time")
|
||||||
|
ambient_medium = self.compute_input("ambient_medium")
|
||||||
|
structures = [self.compute_input("structure")]
|
||||||
|
sources = [self.compute_input("source")]
|
||||||
|
bound = self.compute_input("bound")
|
||||||
|
|
||||||
|
size = (
|
||||||
|
self.compute_input("size_x"),
|
||||||
|
self.compute_input("size_y"),
|
||||||
|
self.compute_input("size_z"),
|
||||||
|
)
|
||||||
|
|
||||||
|
return td.Simulation(
|
||||||
|
size=size,
|
||||||
|
medium=ambient_medium,
|
||||||
|
structures=structures,
|
||||||
|
sources=sources,
|
||||||
|
boundary_spec=bound,
|
||||||
|
run_time=run_time,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Registration
|
||||||
|
####################
|
||||||
|
BL_REGISTER = [
|
||||||
|
FDTDSimNode,
|
||||||
|
]
|
||||||
|
BL_NODES = {
|
||||||
|
contracts.NodeType.FDTDSim: (
|
||||||
|
contracts.NodeCategory.MAXWELL_SIM_SIMULATIONS
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
from . import modelled
|
||||||
|
|
||||||
|
BL_REGISTER = [
|
||||||
|
*modelled.BL_REGISTER,
|
||||||
|
]
|
||||||
|
BL_NODES = {
|
||||||
|
**modelled.BL_NODES,
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
from . import point_dipole_source
|
||||||
|
|
||||||
|
BL_REGISTER = [
|
||||||
|
*point_dipole_source.BL_REGISTER,
|
||||||
|
]
|
||||||
|
BL_NODES = {
|
||||||
|
**point_dipole_source.BL_NODES,
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
import tidy3d as td
|
||||||
|
import sympy as sp
|
||||||
|
import sympy.physics.units as spu
|
||||||
|
|
||||||
|
from .... import contracts
|
||||||
|
from .... import sockets
|
||||||
|
from ... import base
|
||||||
|
|
||||||
|
class PointDipoleSourceNode(base.MaxwellSimTreeNode):
|
||||||
|
node_type = contracts.NodeType.PointDipoleSource
|
||||||
|
|
||||||
|
bl_label = "Point Dipole Source"
|
||||||
|
#bl_icon = ...
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Sockets
|
||||||
|
####################
|
||||||
|
input_sockets = {
|
||||||
|
"center_x": sockets.RealNumberSocketDef(
|
||||||
|
label="Center X",
|
||||||
|
default_value=0.0,
|
||||||
|
),
|
||||||
|
"center_y": sockets.RealNumberSocketDef(
|
||||||
|
label="Center Y",
|
||||||
|
default_value=0.0,
|
||||||
|
),
|
||||||
|
"center_z": sockets.RealNumberSocketDef(
|
||||||
|
label="Center Z",
|
||||||
|
default_value=0.0,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
output_sockets = {
|
||||||
|
"source": sockets.MaxwellSourceSocketDef(
|
||||||
|
label="Source",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Output Socket Computation
|
||||||
|
####################
|
||||||
|
@base.computes_output_socket("source", td.PointDipole)
|
||||||
|
def compute_source(self: contracts.NodeTypeProtocol) -> td.PointDipole:
|
||||||
|
center = (
|
||||||
|
self.compute_input("center_x"),
|
||||||
|
self.compute_input("center_y"),
|
||||||
|
self.compute_input("center_z"),
|
||||||
|
)
|
||||||
|
|
||||||
|
cheating_pulse = td.GaussianPulse(freq0=200e12, fwidth=20e12)
|
||||||
|
|
||||||
|
return td.PointDipole(
|
||||||
|
center=center,
|
||||||
|
source_time=cheating_pulse,
|
||||||
|
interpolate=True,
|
||||||
|
polarization="Ex",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Registration
|
||||||
|
####################
|
||||||
|
BL_REGISTER = [
|
||||||
|
PointDipoleSourceNode,
|
||||||
|
]
|
||||||
|
BL_NODES = {
|
||||||
|
contracts.NodeType.PointDipoleSource: (
|
||||||
|
contracts.NodeCategory.MAXWELL_SIM_SOURCES_MODELLED
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
from . import primitives
|
||||||
|
|
||||||
|
BL_REGISTER = [
|
||||||
|
*primitives.BL_REGISTER,
|
||||||
|
]
|
||||||
|
BL_NODES = {
|
||||||
|
**primitives.BL_NODES,
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
from . import box_structure
|
||||||
|
|
||||||
|
BL_REGISTER = [
|
||||||
|
*box_structure.BL_REGISTER,
|
||||||
|
]
|
||||||
|
BL_NODES = {
|
||||||
|
**box_structure.BL_NODES,
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
import tidy3d as td
|
||||||
|
import sympy as sp
|
||||||
|
import sympy.physics.units as spu
|
||||||
|
|
||||||
|
from .... import contracts
|
||||||
|
from .... import sockets
|
||||||
|
from ... import base
|
||||||
|
|
||||||
|
class BoxStructureNode(base.MaxwellSimTreeNode):
|
||||||
|
node_type = contracts.NodeType.BoxStructure
|
||||||
|
|
||||||
|
bl_label = "Box Structure"
|
||||||
|
#bl_icon = ...
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Sockets
|
||||||
|
####################
|
||||||
|
input_sockets = {
|
||||||
|
"medium": sockets.MaxwellMediumSocketDef(
|
||||||
|
label="Medium",
|
||||||
|
),
|
||||||
|
"center_x": sockets.RealNumberSocketDef(
|
||||||
|
label="Center X",
|
||||||
|
default_value=0.0,
|
||||||
|
),
|
||||||
|
"center_y": sockets.RealNumberSocketDef(
|
||||||
|
label="Center Y",
|
||||||
|
default_value=0.0,
|
||||||
|
),
|
||||||
|
"center_z": sockets.RealNumberSocketDef(
|
||||||
|
label="Center Z",
|
||||||
|
default_value=0.0,
|
||||||
|
),
|
||||||
|
"size_x": sockets.RealNumberSocketDef(
|
||||||
|
label="Size X",
|
||||||
|
default_value=1.0,
|
||||||
|
),
|
||||||
|
"size_y": sockets.RealNumberSocketDef(
|
||||||
|
label="Size Y",
|
||||||
|
default_value=1.0,
|
||||||
|
),
|
||||||
|
"size_z": sockets.RealNumberSocketDef(
|
||||||
|
label="Size Z",
|
||||||
|
default_value=1.0,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
output_sockets = {
|
||||||
|
"structure": sockets.MaxwellStructureSocketDef(
|
||||||
|
label="Structure",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Output Socket Computation
|
||||||
|
####################
|
||||||
|
@base.computes_output_socket("structure", td.Box)
|
||||||
|
def compute_simulation(self: contracts.NodeTypeProtocol) -> td.Box:
|
||||||
|
medium = self.compute_input("medium")
|
||||||
|
center = (
|
||||||
|
self.compute_input("center_x"),
|
||||||
|
self.compute_input("center_y"),
|
||||||
|
self.compute_input("center_z"),
|
||||||
|
)
|
||||||
|
size = (
|
||||||
|
self.compute_input("size_x"),
|
||||||
|
self.compute_input("size_y"),
|
||||||
|
self.compute_input("size_z"),
|
||||||
|
)
|
||||||
|
|
||||||
|
return td.Structure(
|
||||||
|
geometry=td.Box(
|
||||||
|
center=center,
|
||||||
|
size=size,
|
||||||
|
),
|
||||||
|
medium=medium,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Registration
|
||||||
|
####################
|
||||||
|
BL_REGISTER = [
|
||||||
|
BoxStructureNode,
|
||||||
|
]
|
||||||
|
BL_NODES = {
|
||||||
|
contracts.NodeType.BoxStructure: (
|
||||||
|
contracts.NodeCategory.MAXWELL_SIM_STRUCTURES_PRIMITIES
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,72 +0,0 @@
|
||||||
import bpy
|
|
||||||
|
|
||||||
from . import constants, types
|
|
||||||
|
|
||||||
class MaxwellSourceSocket(bpy.types.NodeSocket):
|
|
||||||
bl_idname = types.SocketType.MaxwellSource
|
|
||||||
bl_label = "Maxwell Source"
|
|
||||||
|
|
||||||
def draw(self, context, layout, node, text):
|
|
||||||
layout.label(text=text)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def draw_color_simple(cls):
|
|
||||||
return constants.COLOR_SOCKET_SOURCE
|
|
||||||
|
|
||||||
|
|
||||||
class MaxwellMediumSocket(bpy.types.NodeSocket):
|
|
||||||
bl_idname = types.SocketType.MaxwellMedium
|
|
||||||
bl_label = "Maxwell Medium"
|
|
||||||
|
|
||||||
def draw(self, context, layout, node, text):
|
|
||||||
layout.label(text=text)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def draw_color_simple(cls):
|
|
||||||
return constants.COLOR_SOCKET_MEDIUM
|
|
||||||
|
|
||||||
class MaxwellStructureSocket(bpy.types.NodeSocket):
|
|
||||||
bl_idname = types.SocketType.MaxwellStructure
|
|
||||||
bl_label = "Maxwell Structure"
|
|
||||||
|
|
||||||
def draw(self, context, layout, node, text):
|
|
||||||
layout.label(text=text)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def draw_color_simple(cls):
|
|
||||||
return constants.COLOR_SOCKET_STRUCTURE
|
|
||||||
|
|
||||||
class MaxwellBoundSocket(bpy.types.NodeSocket):
|
|
||||||
bl_idname = types.SocketType.MaxwellBound
|
|
||||||
bl_label = "Maxwell Bound"
|
|
||||||
|
|
||||||
def draw(self, context, layout, node, text):
|
|
||||||
layout.label(text=text)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def draw_color_simple(cls):
|
|
||||||
return constants.COLOR_SOCKET_BOUND
|
|
||||||
|
|
||||||
class MaxwellFDTDSimSocket(bpy.types.NodeSocket):
|
|
||||||
bl_idname = types.SocketType.MaxwellFDTDSim
|
|
||||||
bl_label = "Maxwell FDTD Simulation"
|
|
||||||
|
|
||||||
def draw(self, context, layout, node, text):
|
|
||||||
layout.label(text=text)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def draw_color_simple(cls):
|
|
||||||
return constants.COLOR_SOCKET_FDTDSIM
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Blender Registration
|
|
||||||
####################
|
|
||||||
BL_REGISTER = [
|
|
||||||
MaxwellSourceSocket,
|
|
||||||
MaxwellMediumSocket,
|
|
||||||
MaxwellStructureSocket,
|
|
||||||
MaxwellBoundSocket,
|
|
||||||
MaxwellFDTDSimSocket,
|
|
||||||
]
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
from . import basic
|
||||||
|
AnySocketDef = basic.AnySocketDef
|
||||||
|
TextSocketDef = basic.TextSocketDef
|
||||||
|
FilePathSocketDef = basic.FilePathSocketDef
|
||||||
|
|
||||||
|
from . import number
|
||||||
|
RealNumberSocketDef = number.RealNumberSocketDef
|
||||||
|
ComplexNumberSocketDef = number.ComplexNumberSocketDef
|
||||||
|
|
||||||
|
from . import physical
|
||||||
|
PhysicalAreaSocketDef = physical.PhysicalAreaSocketDef
|
||||||
|
|
||||||
|
from . import maxwell
|
||||||
|
MaxwellBoundSocketDef = maxwell.MaxwellBoundSocketDef
|
||||||
|
MaxwellFDTDSimSocketDef = maxwell.MaxwellFDTDSimSocketDef
|
||||||
|
MaxwellMediumSocketDef = maxwell.MaxwellMediumSocketDef
|
||||||
|
MaxwellSourceSocketDef = maxwell.MaxwellSourceSocketDef
|
||||||
|
MaxwellStructureSocketDef = maxwell.MaxwellStructureSocketDef
|
||||||
|
|
||||||
|
BL_REGISTER = [
|
||||||
|
*basic.BL_REGISTER,
|
||||||
|
*number.BL_REGISTER,
|
||||||
|
*physical.BL_REGISTER,
|
||||||
|
*maxwell.BL_REGISTER,
|
||||||
|
]
|
|
@ -0,0 +1,105 @@
|
||||||
|
import typing as typ
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
from .. import contracts
|
||||||
|
|
||||||
|
class BLSocket(bpy.types.NodeSocket):
|
||||||
|
"""A base type for nodes that greatly simplifies the implementation of
|
||||||
|
reliable, powerful nodes.
|
||||||
|
|
||||||
|
Should be used together with `contracts.BLSocketProtocol`.
|
||||||
|
"""
|
||||||
|
def __init_subclass__(cls, **kwargs: typ.Any):
|
||||||
|
super().__init_subclass__(**kwargs) ## Yucky superclass setup.
|
||||||
|
|
||||||
|
# Set bl_idname
|
||||||
|
cls.bl_idname = cls.socket_type.value
|
||||||
|
|
||||||
|
# Declare Node Property: 'preset' EnumProperty
|
||||||
|
if hasattr(cls, "draw_preview"):
|
||||||
|
cls.__annotations__["preview_active"] = bpy.props.BoolProperty(
|
||||||
|
name="Preview",
|
||||||
|
description="Preview the socket value",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Methods
|
||||||
|
####################
|
||||||
|
def is_compatible(self, value: typ.Any) -> bool:
|
||||||
|
for compatible_type, checks in self.compatible_types.items():
|
||||||
|
if (
|
||||||
|
compatible_type is typ.Any or
|
||||||
|
isinstance(value, compatible_type)
|
||||||
|
):
|
||||||
|
return all(check(value) for check in checks)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - UI
|
||||||
|
####################
|
||||||
|
@classmethod
|
||||||
|
def draw_color_simple(cls) -> contracts.BlenderColorRGB:
|
||||||
|
return cls.socket_color
|
||||||
|
|
||||||
|
def draw(
|
||||||
|
self,
|
||||||
|
context: bpy.types.Context,
|
||||||
|
layout: bpy.types.UILayout,
|
||||||
|
node: bpy.types.Node,
|
||||||
|
text: str,
|
||||||
|
) -> None:
|
||||||
|
if self.is_output:
|
||||||
|
self.draw_output(context, layout, node, text)
|
||||||
|
else:
|
||||||
|
self.draw_input(context, layout, node, text)
|
||||||
|
|
||||||
|
def draw_input(
|
||||||
|
self,
|
||||||
|
context: bpy.types.Context,
|
||||||
|
layout: bpy.types.UILayout,
|
||||||
|
node: bpy.types.Node,
|
||||||
|
text: str,
|
||||||
|
) -> None:
|
||||||
|
if self.is_linked:
|
||||||
|
layout.label(text=text)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Column
|
||||||
|
col = layout.column(align=True)
|
||||||
|
|
||||||
|
# Row: Label & Preview Toggle
|
||||||
|
label_col_row = col.row(align=True)
|
||||||
|
if hasattr(self, "draw_label_row"):
|
||||||
|
self.draw_label_row(label_col_row, text)
|
||||||
|
else:
|
||||||
|
label_col_row.label(text=text)
|
||||||
|
|
||||||
|
if hasattr(self, "draw_preview"):
|
||||||
|
label_col_row.prop(
|
||||||
|
self,
|
||||||
|
"preview_active",
|
||||||
|
toggle=True,
|
||||||
|
text="",
|
||||||
|
icon="SEQ_PREVIEW",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Row: Preview (in Box)
|
||||||
|
if hasattr(self, "draw_preview"):
|
||||||
|
if self.preview_active:
|
||||||
|
col_box = col.box()
|
||||||
|
self.draw_preview(col_box)
|
||||||
|
|
||||||
|
# Row(s): Value
|
||||||
|
if hasattr(self, "draw_value"):
|
||||||
|
self.draw_value(col)
|
||||||
|
|
||||||
|
def draw_output(
|
||||||
|
self,
|
||||||
|
context: bpy.types.Context,
|
||||||
|
layout: bpy.types.UILayout,
|
||||||
|
node: bpy.types.Node,
|
||||||
|
text: str,
|
||||||
|
) -> None:
|
||||||
|
layout.label(text=text)
|
|
@ -0,0 +1,15 @@
|
||||||
|
from . import any_socket
|
||||||
|
AnySocketDef = any_socket.AnySocketDef
|
||||||
|
|
||||||
|
from . import text_socket
|
||||||
|
TextSocketDef = text_socket.TextSocketDef
|
||||||
|
|
||||||
|
from . import file_path_socket
|
||||||
|
FilePathSocketDef = file_path_socket.FilePathSocketDef
|
||||||
|
|
||||||
|
|
||||||
|
BL_REGISTER = [
|
||||||
|
*any_socket.BL_REGISTER,
|
||||||
|
*text_socket.BL_REGISTER,
|
||||||
|
*file_path_socket.BL_REGISTER,
|
||||||
|
]
|
|
@ -0,0 +1,57 @@
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import sympy as sp
|
||||||
|
import pydantic as pyd
|
||||||
|
|
||||||
|
from .. import base
|
||||||
|
from ... import contracts
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Socket
|
||||||
|
####################
|
||||||
|
class AnyBLSocket(base.BLSocket):
|
||||||
|
socket_type = contracts.SocketType.Any
|
||||||
|
socket_color = (0.0, 0.0, 0.0, 1.0)
|
||||||
|
|
||||||
|
bl_label = "Any"
|
||||||
|
|
||||||
|
compatible_types = {
|
||||||
|
typ.Any: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Socket UI
|
||||||
|
####################
|
||||||
|
def draw_label_row(self, label_col_row: bpy.types.UILayout, text: str) -> None:
|
||||||
|
"""Draw the value of the real number.
|
||||||
|
"""
|
||||||
|
label_col_row.label(text=text)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Computation of Default Value
|
||||||
|
####################
|
||||||
|
@property
|
||||||
|
def default_value(self) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@default_value.setter
|
||||||
|
def default_value(self, value: typ.Any) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Socket Configuration
|
||||||
|
####################
|
||||||
|
class AnySocketDef(pyd.BaseModel):
|
||||||
|
socket_type: contracts.SocketType = contracts.SocketType.Any
|
||||||
|
label: str
|
||||||
|
|
||||||
|
def init(self, bl_socket: AnyBLSocket) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Registration
|
||||||
|
####################
|
||||||
|
BL_REGISTER = [
|
||||||
|
AnyBLSocket,
|
||||||
|
]
|
|
@ -0,0 +1,84 @@
|
||||||
|
import typing as typ
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import sympy as sp
|
||||||
|
import pydantic as pyd
|
||||||
|
|
||||||
|
from .. import base
|
||||||
|
from ... import contracts
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Socket
|
||||||
|
####################
|
||||||
|
class FilePathBLSocket(base.BLSocket):
|
||||||
|
socket_type = contracts.SocketType.FilePath
|
||||||
|
socket_color = (0.2, 0.2, 0.2, 1.0)
|
||||||
|
|
||||||
|
bl_label = "File Path"
|
||||||
|
|
||||||
|
compatible_types = {
|
||||||
|
Path: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Properties
|
||||||
|
####################
|
||||||
|
raw_value: bpy.props.StringProperty(
|
||||||
|
name="File Path",
|
||||||
|
description="Represents the path to a file",
|
||||||
|
#default="",
|
||||||
|
subtype="FILE_PATH",
|
||||||
|
)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Socket UI
|
||||||
|
####################
|
||||||
|
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||||
|
col_row = col.row(align=True)
|
||||||
|
col_row.prop(self, "raw_value", text="")
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Computation of Default Value
|
||||||
|
####################
|
||||||
|
@property
|
||||||
|
def default_value(self) -> Path:
|
||||||
|
"""Return the text.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The text as a string.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return Path(str(self.raw_value))
|
||||||
|
|
||||||
|
@default_value.setter
|
||||||
|
def default_value(self, value: typ.Any) -> None:
|
||||||
|
"""Set the real number from some compatible type, namely
|
||||||
|
real sympy expressions with no symbols, or floats.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# (Guard) Value Compatibility
|
||||||
|
if not self.is_compatible(value):
|
||||||
|
msg = f"Tried setting socket ({self}) to incompatible value ({value}) of type {type(value)}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
self.raw_value = str(Path(value))
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Socket Configuration
|
||||||
|
####################
|
||||||
|
class FilePathSocketDef(pyd.BaseModel):
|
||||||
|
socket_type: contracts.SocketType = contracts.SocketType.FilePath
|
||||||
|
label: str
|
||||||
|
|
||||||
|
default_path: Path
|
||||||
|
|
||||||
|
def init(self, bl_socket: FilePathBLSocket) -> None:
|
||||||
|
bl_socket.default_value = self.default_path
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Registration
|
||||||
|
####################
|
||||||
|
BL_REGISTER = [
|
||||||
|
FilePathBLSocket,
|
||||||
|
]
|
|
@ -0,0 +1,81 @@
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import sympy as sp
|
||||||
|
import pydantic as pyd
|
||||||
|
|
||||||
|
from .. import base
|
||||||
|
from ... import contracts
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Socket
|
||||||
|
####################
|
||||||
|
class TextBLSocket(base.BLSocket):
|
||||||
|
socket_type = contracts.SocketType.Text
|
||||||
|
socket_color = (0.2, 0.2, 0.2, 1.0)
|
||||||
|
|
||||||
|
bl_label = "Text"
|
||||||
|
|
||||||
|
compatible_types = {
|
||||||
|
str: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Properties
|
||||||
|
####################
|
||||||
|
raw_value: bpy.props.StringProperty(
|
||||||
|
name="Text",
|
||||||
|
description="Represents some text",
|
||||||
|
default="",
|
||||||
|
)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Socket UI
|
||||||
|
####################
|
||||||
|
def draw_label_row(self, label_col_row: bpy.types.UILayout, text: str) -> None:
|
||||||
|
"""Draw the value of the real number.
|
||||||
|
"""
|
||||||
|
label_col_row.prop(self, "raw_value", text=text)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Computation of Default Value
|
||||||
|
####################
|
||||||
|
@property
|
||||||
|
def default_value(self) -> str:
|
||||||
|
"""Return the text.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The text as a string.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.raw_value
|
||||||
|
|
||||||
|
@default_value.setter
|
||||||
|
def default_value(self, value: typ.Any) -> None:
|
||||||
|
"""Set the real number from some compatible type, namely
|
||||||
|
real sympy expressions with no symbols, or floats.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# (Guard) Value Compatibility
|
||||||
|
if not self.is_compatible(value):
|
||||||
|
msg = f"Tried setting socket ({self}) to incompatible value ({value}) of type {type(value)}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
self.raw_value = str(value)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Socket Configuration
|
||||||
|
####################
|
||||||
|
class TextSocketDef(pyd.BaseModel):
|
||||||
|
socket_type: contracts.SocketType = contracts.SocketType.Text
|
||||||
|
label: str
|
||||||
|
|
||||||
|
def init(self, bl_socket: TextBLSocket) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Registration
|
||||||
|
####################
|
||||||
|
BL_REGISTER = [
|
||||||
|
TextBLSocket,
|
||||||
|
]
|
|
@ -0,0 +1,23 @@
|
||||||
|
from . import maxwell_bound_socket
|
||||||
|
MaxwellBoundSocketDef = maxwell_bound_socket.MaxwellBoundSocketDef
|
||||||
|
|
||||||
|
from . import maxwell_fdtd_sim_socket
|
||||||
|
MaxwellFDTDSimSocketDef = maxwell_fdtd_sim_socket.MaxwellFDTDSimSocketDef
|
||||||
|
|
||||||
|
from . import maxwell_medium_socket
|
||||||
|
MaxwellMediumSocketDef = maxwell_medium_socket.MaxwellMediumSocketDef
|
||||||
|
|
||||||
|
from . import maxwell_source_socket
|
||||||
|
MaxwellSourceSocketDef = maxwell_source_socket.MaxwellSourceSocketDef
|
||||||
|
|
||||||
|
from . import maxwell_structure_socket
|
||||||
|
MaxwellStructureSocketDef = maxwell_structure_socket.MaxwellStructureSocketDef
|
||||||
|
|
||||||
|
|
||||||
|
BL_REGISTER = [
|
||||||
|
*maxwell_bound_socket.BL_REGISTER,
|
||||||
|
*maxwell_fdtd_sim_socket.BL_REGISTER,
|
||||||
|
*maxwell_medium_socket.BL_REGISTER,
|
||||||
|
*maxwell_source_socket.BL_REGISTER,
|
||||||
|
*maxwell_structure_socket.BL_REGISTER,
|
||||||
|
]
|
|
@ -0,0 +1,46 @@
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import pydantic as pyd
|
||||||
|
import tidy3d as td
|
||||||
|
|
||||||
|
from .. import base
|
||||||
|
from ... import contracts
|
||||||
|
|
||||||
|
class MaxwellBoundBLSocket(base.BLSocket):
|
||||||
|
socket_type = contracts.SocketType.MaxwellBound
|
||||||
|
socket_color = (0.8, 0.8, 0.4, 1.0)
|
||||||
|
|
||||||
|
bl_label = "Maxwell Bound"
|
||||||
|
|
||||||
|
compatible_types = {
|
||||||
|
td.BoundarySpec: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Computation of Default Value
|
||||||
|
####################
|
||||||
|
@property
|
||||||
|
def default_value(self) -> td.BoundarySpec:
|
||||||
|
return td.BoundarySpec()
|
||||||
|
|
||||||
|
@default_value.setter
|
||||||
|
def default_value(self, value: typ.Any) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Socket Configuration
|
||||||
|
####################
|
||||||
|
class MaxwellBoundSocketDef(pyd.BaseModel):
|
||||||
|
socket_type: contracts.SocketType = contracts.SocketType.MaxwellBound
|
||||||
|
label: str
|
||||||
|
|
||||||
|
def init(self, bl_socket: MaxwellBoundBLSocket) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Registration
|
||||||
|
####################
|
||||||
|
BL_REGISTER = [
|
||||||
|
MaxwellBoundBLSocket,
|
||||||
|
]
|
|
@ -0,0 +1,46 @@
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import pydantic as pyd
|
||||||
|
import tidy3d as td
|
||||||
|
|
||||||
|
from .. import base
|
||||||
|
from ... import contracts
|
||||||
|
|
||||||
|
class MaxwellFDTDSimBLSocket(base.BLSocket):
|
||||||
|
socket_type = contracts.SocketType.MaxwellFDTDSim
|
||||||
|
socket_color = (0.8, 0.8, 0.4, 1.0)
|
||||||
|
|
||||||
|
bl_label = "Maxwell Source"
|
||||||
|
|
||||||
|
compatible_types = {
|
||||||
|
td.Simulation: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Computation of Default Value
|
||||||
|
####################
|
||||||
|
@property
|
||||||
|
def default_value(self) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@default_value.setter
|
||||||
|
def default_value(self, value: typ.Any) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Socket Configuration
|
||||||
|
####################
|
||||||
|
class MaxwellFDTDSimSocketDef(pyd.BaseModel):
|
||||||
|
socket_type: contracts.SocketType = contracts.SocketType.MaxwellFDTDSim
|
||||||
|
label: str
|
||||||
|
|
||||||
|
def init(self, bl_socket: MaxwellFDTDSimBLSocket) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Registration
|
||||||
|
####################
|
||||||
|
BL_REGISTER = [
|
||||||
|
MaxwellFDTDSimBLSocket,
|
||||||
|
]
|
|
@ -0,0 +1,89 @@
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import pydantic as pyd
|
||||||
|
import tidy3d as td
|
||||||
|
|
||||||
|
from .. import base
|
||||||
|
from ... import contracts
|
||||||
|
|
||||||
|
class MaxwellMediumBLSocket(base.BLSocket):
|
||||||
|
socket_type = contracts.SocketType.MaxwellMedium
|
||||||
|
socket_color = (0.8, 0.8, 0.4, 1.0)
|
||||||
|
|
||||||
|
bl_label = "Maxwell Medium"
|
||||||
|
|
||||||
|
compatible_types = {
|
||||||
|
td.components.medium.AbstractMedium: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Properties
|
||||||
|
####################
|
||||||
|
rel_permittivity: bpy.props.FloatProperty(
|
||||||
|
name="Permittivity",
|
||||||
|
description="Represents a simple, real permittivity.",
|
||||||
|
default=0.0,
|
||||||
|
precision=6,
|
||||||
|
)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Socket UI
|
||||||
|
####################
|
||||||
|
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||||
|
"""Draw the value of the area, including a toggle for
|
||||||
|
specifying the active unit.
|
||||||
|
"""
|
||||||
|
col_row = col.row(align=True)
|
||||||
|
col_row.prop(self, "rel_permittivity", text="Re(eps_r)")
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Computation of Default Value
|
||||||
|
####################
|
||||||
|
@property
|
||||||
|
def default_value(self) -> td.Medium:
|
||||||
|
"""Return the built-in medium representation as a `tidy3d` object,
|
||||||
|
ready to use in the simulation.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A completely normal medium with permittivity set.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return td.Medium(
|
||||||
|
permittivity=self.rel_permittivity,
|
||||||
|
)
|
||||||
|
|
||||||
|
@default_value.setter
|
||||||
|
def default_value(self, value: typ.Any) -> None:
|
||||||
|
"""Set the built-in medium representation by adjusting the
|
||||||
|
permittivity, ONLY.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: Must be a tidy3d.Medium, or similar subclass.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# ONLY Allow td.Medium
|
||||||
|
if isinstance(value, td.Medium):
|
||||||
|
self.rel_permittivity = value.permittivity
|
||||||
|
|
||||||
|
msg = f"Tried setting MaxwellMedium socket ({self}) to something that isn't a simple `tidy3d.Medium`"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Socket Configuration
|
||||||
|
####################
|
||||||
|
class MaxwellMediumSocketDef(pyd.BaseModel):
|
||||||
|
socket_type: contracts.SocketType = contracts.SocketType.MaxwellMedium
|
||||||
|
label: str
|
||||||
|
|
||||||
|
rel_permittivity: float = 1.0
|
||||||
|
|
||||||
|
def init(self, bl_socket: MaxwellMediumBLSocket) -> None:
|
||||||
|
bl_socket.rel_permittivity = self.rel_permittivity
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Registration
|
||||||
|
####################
|
||||||
|
BL_REGISTER = [
|
||||||
|
MaxwellMediumBLSocket,
|
||||||
|
]
|
|
@ -0,0 +1,46 @@
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import pydantic as pyd
|
||||||
|
import tidy3d as td
|
||||||
|
|
||||||
|
from .. import base
|
||||||
|
from ... import contracts
|
||||||
|
|
||||||
|
class MaxwellSourceBLSocket(base.BLSocket):
|
||||||
|
socket_type = contracts.SocketType.MaxwellSource
|
||||||
|
socket_color = (0.8, 0.8, 0.4, 1.0)
|
||||||
|
|
||||||
|
bl_label = "Maxwell Source"
|
||||||
|
|
||||||
|
compatible_types = {
|
||||||
|
td.components.base_sim.source.AbstractSource: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Computation of Default Value
|
||||||
|
####################
|
||||||
|
@property
|
||||||
|
def default_value(self) -> td.Medium:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@default_value.setter
|
||||||
|
def default_value(self, value: typ.Any) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Socket Configuration
|
||||||
|
####################
|
||||||
|
class MaxwellSourceSocketDef(pyd.BaseModel):
|
||||||
|
socket_type: contracts.SocketType = contracts.SocketType.MaxwellSource
|
||||||
|
label: str
|
||||||
|
|
||||||
|
def init(self, bl_socket: MaxwellSourceBLSocket) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Registration
|
||||||
|
####################
|
||||||
|
BL_REGISTER = [
|
||||||
|
MaxwellSourceBLSocket,
|
||||||
|
]
|
|
@ -0,0 +1,46 @@
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import pydantic as pyd
|
||||||
|
import tidy3d as td
|
||||||
|
|
||||||
|
from .. import base
|
||||||
|
from ... import contracts
|
||||||
|
|
||||||
|
class MaxwellStructureBLSocket(base.BLSocket):
|
||||||
|
socket_type = contracts.SocketType.MaxwellStructure
|
||||||
|
socket_color = (0.8, 0.8, 0.4, 1.0)
|
||||||
|
|
||||||
|
bl_label = "Maxwell Structure"
|
||||||
|
|
||||||
|
compatible_types = {
|
||||||
|
td.components.structure.AbstractStructure: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Computation of Default Value
|
||||||
|
####################
|
||||||
|
@property
|
||||||
|
def default_value(self) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@default_value.setter
|
||||||
|
def default_value(self, value: typ.Any) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Socket Configuration
|
||||||
|
####################
|
||||||
|
class MaxwellStructureSocketDef(pyd.BaseModel):
|
||||||
|
socket_type: contracts.SocketType = contracts.SocketType.MaxwellStructure
|
||||||
|
label: str
|
||||||
|
|
||||||
|
def init(self, bl_socket: MaxwellStructureBLSocket) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Registration
|
||||||
|
####################
|
||||||
|
BL_REGISTER = [
|
||||||
|
MaxwellStructureBLSocket,
|
||||||
|
]
|
|
@ -0,0 +1,11 @@
|
||||||
|
from . import real_number_socket
|
||||||
|
RealNumberSocketDef = real_number_socket.RealNumberSocketDef
|
||||||
|
|
||||||
|
from . import complex_number_socket
|
||||||
|
ComplexNumberSocketDef = complex_number_socket.ComplexNumberSocketDef
|
||||||
|
|
||||||
|
|
||||||
|
BL_REGISTER = [
|
||||||
|
*real_number_socket.BL_REGISTER,
|
||||||
|
*complex_number_socket.BL_REGISTER,
|
||||||
|
]
|
|
@ -0,0 +1,160 @@
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import sympy as sp
|
||||||
|
import pydantic as pyd
|
||||||
|
|
||||||
|
from .. import base
|
||||||
|
from ... import contracts
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Socket
|
||||||
|
####################
|
||||||
|
class ComplexNumberBLSocket(base.BLSocket):
|
||||||
|
socket_type = contracts.SocketType.ComplexNumber
|
||||||
|
socket_color = (0.6, 0.6, 0.6, 1.0)
|
||||||
|
|
||||||
|
bl_label = "Complex Number"
|
||||||
|
|
||||||
|
compatible_types = {
|
||||||
|
complex: {},
|
||||||
|
sp.Expr: {
|
||||||
|
lambda v: v.is_complex,
|
||||||
|
lambda v: len(v.free_symbols) == 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Properties
|
||||||
|
####################
|
||||||
|
raw_value: bpy.props.FloatVectorProperty(
|
||||||
|
name="Complex Number",
|
||||||
|
description="Represents a complex number (real, imaginary)",
|
||||||
|
size=2,
|
||||||
|
default=(0.0, 0.0),
|
||||||
|
subtype='NONE'
|
||||||
|
)
|
||||||
|
coord_sys: bpy.props.EnumProperty(
|
||||||
|
name="Coordinate System",
|
||||||
|
description="Choose between cartesian and polar form",
|
||||||
|
items=[
|
||||||
|
("CARTESIAN", "Cartesian", "Use Cartesian Coordinates", "EMPTY_AXIS", 0),
|
||||||
|
("POLAR", "Polar", "Use Polar Coordinates", "DRIVER_ROTATIONAL_DIFFERENCE", 1),
|
||||||
|
],
|
||||||
|
default="CARTESIAN",
|
||||||
|
update=lambda self, context: self._update_coord_sys(),
|
||||||
|
)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Socket UI
|
||||||
|
####################
|
||||||
|
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||||
|
"""Draw the value of the complex number, including a toggle for
|
||||||
|
specifying the active coordinate system.
|
||||||
|
"""
|
||||||
|
col_row = col.row()
|
||||||
|
col_row.prop(self, "raw_value", text="")
|
||||||
|
col.prop(self, "coord_sys", text="")
|
||||||
|
|
||||||
|
def draw_preview(self, col_box: bpy.types.UILayout) -> None:
|
||||||
|
"""Draw a live-preview value for the complex number, into the
|
||||||
|
given preview box.
|
||||||
|
|
||||||
|
- Cartesian: a,b -> a + ib
|
||||||
|
- Polar: r,t -> re^(it)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The sympy expression representing the complex number.
|
||||||
|
"""
|
||||||
|
if self.coord_sys == "CARTESIAN":
|
||||||
|
text = f"= {self.default_value.n(2)}"
|
||||||
|
|
||||||
|
elif self.coord_sys == "POLAR":
|
||||||
|
r = sp.Abs(self.default_value).n(2)
|
||||||
|
theta_rad = sp.arg(self.default_value).n(2)
|
||||||
|
text = f"= {r*sp.exp(sp.I*theta_rad)}"
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise RuntimeError("Invalid coordinate system for complex number")
|
||||||
|
|
||||||
|
col_box.label(text=text)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Computation of Default Value
|
||||||
|
####################
|
||||||
|
@property
|
||||||
|
def default_value(self) -> sp.Expr:
|
||||||
|
"""Return the complex number as a sympy expression, of a form
|
||||||
|
determined by the coordinate system.
|
||||||
|
|
||||||
|
- Cartesian: a,b -> a + ib
|
||||||
|
- Polar: r,t -> re^(it)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The sympy expression representing the complex number.
|
||||||
|
"""
|
||||||
|
|
||||||
|
v1, v2 = self.raw_value
|
||||||
|
|
||||||
|
return {
|
||||||
|
"CARTESIAN": v1 + sp.I*v2,
|
||||||
|
"POLAR": v1 * sp.exp(sp.I*v2),
|
||||||
|
}[self.coord_sys]
|
||||||
|
|
||||||
|
@default_value.setter
|
||||||
|
def default_value(self, value: typ.Any) -> None:
|
||||||
|
"""Set the complex number from a sympy expression, using an internal
|
||||||
|
representation determined by the coordinate system.
|
||||||
|
|
||||||
|
- Cartesian: a,b -> a + ib
|
||||||
|
- Polar: r,t -> re^(it)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# (Guard) Value Compatibility
|
||||||
|
if not self.is_compatible(value):
|
||||||
|
msg = f"Tried setting socket ({self}) to incompatible value ({value}) of type {type(value)}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
self.raw_value = {
|
||||||
|
"CARTESIAN": (sp.re(value), sp.im(value)),
|
||||||
|
"POLAR": (sp.Abs(value), sp.arg(value)),
|
||||||
|
}[self.coord_sys]
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Internal Update Methods
|
||||||
|
####################
|
||||||
|
def _update_coord_sys(self):
|
||||||
|
if self.coord_sys == "CARTESIAN":
|
||||||
|
r, theta_rad = self.raw_value
|
||||||
|
self.raw_value = (
|
||||||
|
r * sp.cos(theta_rad),
|
||||||
|
r * sp.sin(theta_rad),
|
||||||
|
)
|
||||||
|
elif self.coord_sys == "POLAR":
|
||||||
|
x, y = self.raw_value
|
||||||
|
cart_value = x + sp.I*y
|
||||||
|
self.raw_value = (
|
||||||
|
sp.Abs(cart_value),
|
||||||
|
sp.arg(cart_value) if y != 0 else 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Socket Configuration
|
||||||
|
####################
|
||||||
|
class ComplexNumberSocketDef(pyd.BaseModel):
|
||||||
|
socket_type: contracts.SocketType = contracts.SocketType.ComplexNumber
|
||||||
|
label: str
|
||||||
|
|
||||||
|
preview: bool = False
|
||||||
|
coord_sys: typ.Literal["CARTESIAN", "POLAR"] = "CARTESIAN"
|
||||||
|
|
||||||
|
def init(self, bl_socket: ComplexNumberBLSocket) -> None:
|
||||||
|
bl_socket.preview_active = self.preview
|
||||||
|
bl_socket.coord_sys = self.coord_sys
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Registration
|
||||||
|
####################
|
||||||
|
BL_REGISTER = [
|
||||||
|
ComplexNumberBLSocket,
|
||||||
|
]
|
|
@ -0,0 +1,88 @@
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import sympy as sp
|
||||||
|
import pydantic as pyd
|
||||||
|
|
||||||
|
from .. import base
|
||||||
|
from ... import contracts
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Socket
|
||||||
|
####################
|
||||||
|
class RealNumberBLSocket(base.BLSocket):
|
||||||
|
socket_type = contracts.SocketType.RealNumber
|
||||||
|
socket_color = (0.6, 0.6, 0.6, 1.0)
|
||||||
|
|
||||||
|
bl_label = "Real Number"
|
||||||
|
|
||||||
|
compatible_types = {
|
||||||
|
float: {},
|
||||||
|
sp.Expr: {
|
||||||
|
lambda v: v.is_real,
|
||||||
|
lambda v: len(v.free_symbols) == 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Properties
|
||||||
|
####################
|
||||||
|
raw_value: bpy.props.FloatProperty(
|
||||||
|
name="Real Number",
|
||||||
|
description="Represents a real number",
|
||||||
|
default=0.0,
|
||||||
|
precision=6,
|
||||||
|
)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Socket UI
|
||||||
|
####################
|
||||||
|
def draw_label_row(self, label_col_row: bpy.types.UILayout, text: str) -> None:
|
||||||
|
"""Draw the value of the real number.
|
||||||
|
"""
|
||||||
|
label_col_row.prop(self, "raw_value", text=text)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Computation of Default Value
|
||||||
|
####################
|
||||||
|
@property
|
||||||
|
def default_value(self) -> float:
|
||||||
|
"""Return the real number.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The real number as a float.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.raw_value
|
||||||
|
|
||||||
|
@default_value.setter
|
||||||
|
def default_value(self, value: typ.Any) -> None:
|
||||||
|
"""Set the real number from some compatible type, namely
|
||||||
|
real sympy expressions with no symbols, or floats.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# (Guard) Value Compatibility
|
||||||
|
if not self.is_compatible(value):
|
||||||
|
msg = f"Tried setting socket ({self}) to incompatible value ({value}) of type {type(value)}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
self.raw_value = float(value)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Socket Configuration
|
||||||
|
####################
|
||||||
|
class RealNumberSocketDef(pyd.BaseModel):
|
||||||
|
socket_type: contracts.SocketType = contracts.SocketType.RealNumber
|
||||||
|
label: str
|
||||||
|
|
||||||
|
default_value: float = 0.0
|
||||||
|
|
||||||
|
def init(self, bl_socket: RealNumberBLSocket) -> None:
|
||||||
|
bl_socket.default_value = self.default_value
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Registration
|
||||||
|
####################
|
||||||
|
BL_REGISTER = [
|
||||||
|
RealNumberBLSocket,
|
||||||
|
]
|
|
@ -0,0 +1,7 @@
|
||||||
|
from . import physical_area_socket
|
||||||
|
PhysicalAreaSocketDef = physical_area_socket.PhysicalAreaSocketDef
|
||||||
|
|
||||||
|
|
||||||
|
BL_REGISTER = [
|
||||||
|
*physical_area_socket.BL_REGISTER,
|
||||||
|
]
|
|
@ -0,0 +1,147 @@
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import sympy as sp
|
||||||
|
import sympy.physics.units as spu
|
||||||
|
import pydantic as pyd
|
||||||
|
|
||||||
|
sp.printing.str.StrPrinter._default_settings['abbrev'] = True
|
||||||
|
## When we str() a unit expression, use abbrevied units.
|
||||||
|
|
||||||
|
from .. import base
|
||||||
|
from ... import contracts
|
||||||
|
|
||||||
|
UNITS = {
|
||||||
|
"PM_SQ": spu.pm**2,
|
||||||
|
"A_SQ": spu.angstrom**2,
|
||||||
|
"NM_SQ": spu.nm**2,
|
||||||
|
"UM_SQ": spu.um**2,
|
||||||
|
"MM_SQ": spu.mm**2,
|
||||||
|
"CM_SQ": spu.cm**2,
|
||||||
|
"M_SQ": spu.m**2,
|
||||||
|
}
|
||||||
|
DEFAULT_UNIT = "UM_SQ"
|
||||||
|
|
||||||
|
class PhysicalAreaBLSocket(base.BLSocket):
|
||||||
|
socket_type = contracts.SocketType.PhysicalArea
|
||||||
|
socket_color = (0.8, 0.5, 0.5, 1.0)
|
||||||
|
|
||||||
|
bl_label = "Physical Area"
|
||||||
|
|
||||||
|
compatible_types = {
|
||||||
|
sp.Expr: {
|
||||||
|
lambda v: v.is_real,
|
||||||
|
lambda v: len(v.free_symbols) == 0,
|
||||||
|
lambda v: any(
|
||||||
|
contracts.is_exactly_expressed_as_unit(v, unit)
|
||||||
|
for unit in UNITS.values()
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Properties
|
||||||
|
####################
|
||||||
|
raw_value: bpy.props.FloatProperty(
|
||||||
|
name="Unitless Area",
|
||||||
|
description="Represents the unitless part of the area",
|
||||||
|
default=0.0,
|
||||||
|
precision=6,
|
||||||
|
)
|
||||||
|
unit: bpy.props.EnumProperty(
|
||||||
|
name="Unit",
|
||||||
|
description="Choose between area units",
|
||||||
|
items=[
|
||||||
|
(unit_name, str(unit_value), str(unit_value))
|
||||||
|
for unit_name, unit_value in UNITS.items()
|
||||||
|
],
|
||||||
|
default=DEFAULT_UNIT,
|
||||||
|
update=lambda self, context: self._update_unit(),
|
||||||
|
)
|
||||||
|
unit_previous: bpy.props.StringProperty(default=DEFAULT_UNIT)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Socket UI
|
||||||
|
####################
|
||||||
|
def draw_label_row(self, label_col_row: bpy.types.UILayout, text: str) -> None:
|
||||||
|
"""Draw the value of the area, including a toggle for
|
||||||
|
specifying the active unit.
|
||||||
|
"""
|
||||||
|
label_col_row.label(text=text)
|
||||||
|
#label_col_row.prop(self, "raw_value", text="")
|
||||||
|
label_col_row.prop(self, "unit", text="")
|
||||||
|
|
||||||
|
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||||
|
col_row = col.row(align=True)
|
||||||
|
col_row.prop(self, "raw_value", text="")
|
||||||
|
#col_row.prop(self, "unit", text="")
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Computation of Default Value
|
||||||
|
####################
|
||||||
|
@property
|
||||||
|
def default_value(self) -> sp.Expr:
|
||||||
|
"""Return the area as a sympy expression, which is a pure real
|
||||||
|
number perfectly expressed as the active unit.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The area as a sympy expression (with units).
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.raw_value * UNITS[self.unit]
|
||||||
|
|
||||||
|
@default_value.setter
|
||||||
|
def default_value(self, value: typ.Any) -> None:
|
||||||
|
"""Set the area from a sympy expression, including any required
|
||||||
|
unit conversions to normalize the input value to the selected
|
||||||
|
units.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# (Guard) Value Compatibility
|
||||||
|
if not self.is_compatible(value):
|
||||||
|
msg = f"Tried setting socket ({self}) to incompatible value ({value}) of type {type(value)}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
self.raw_value = spu.convert_to(
|
||||||
|
value, UNITS[self.unit]
|
||||||
|
) / UNITS[self.unit]
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Internal Update Methods
|
||||||
|
####################
|
||||||
|
def _update_unit(self):
|
||||||
|
old_unit = UNITS[self.unit_previous]
|
||||||
|
new_unit = UNITS[self.unit]
|
||||||
|
|
||||||
|
self.raw_value = spu.convert_to(
|
||||||
|
self.raw_value * old_unit,
|
||||||
|
new_unit,
|
||||||
|
) / new_unit
|
||||||
|
self.unit_previous = self.unit
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Socket Configuration
|
||||||
|
####################
|
||||||
|
class PhysicalAreaSocketDef(pyd.BaseModel):
|
||||||
|
socket_type: contracts.SocketType = contracts.SocketType.PhysicalArea
|
||||||
|
label: str
|
||||||
|
|
||||||
|
default_unit: typ.Literal[
|
||||||
|
"PM_SQ",
|
||||||
|
"A_SQ",
|
||||||
|
"NM_SQ",
|
||||||
|
"UM_SQ",
|
||||||
|
"MM_SQ",
|
||||||
|
"CM_SQ",
|
||||||
|
"M_SQ",
|
||||||
|
]
|
||||||
|
|
||||||
|
def init(self, bl_socket: PhysicalAreaBLSocket) -> None:
|
||||||
|
bl_socket.unit = self.default_unit
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Registration
|
||||||
|
####################
|
||||||
|
BL_REGISTER = [
|
||||||
|
PhysicalAreaBLSocket,
|
||||||
|
]
|
|
@ -1,17 +0,0 @@
|
||||||
import bpy
|
|
||||||
|
|
||||||
from . import types, constants
|
|
||||||
|
|
||||||
class MaxwellSimTree(bpy.types.NodeTree):
|
|
||||||
bl_idname = types.TreeType.MaxwellSim
|
|
||||||
bl_label = "Maxwell Sim Editor"
|
|
||||||
bl_icon = constants.ICON_SIM ## Icon ID
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Blender Registration
|
|
||||||
####################
|
|
||||||
BL_REGISTER = [
|
|
||||||
MaxwellSimTree,
|
|
||||||
]
|
|
|
@ -1,2 +1,3 @@
|
||||||
tidy3d==2.5.2
|
tidy3d==2.5.2
|
||||||
pydantic==2.6.0
|
pydantic==2.6.0
|
||||||
|
sympy==1.12
|
||||||
|
|
BIN
code/demo.blend (Stored with Git LFS)
BIN
code/demo.blend (Stored with Git LFS)
Binary file not shown.
|
@ -2,5 +2,10 @@
|
||||||
|
|
||||||
blender --python run.py
|
blender --python run.py
|
||||||
if [ $? -eq 42 ]; then
|
if [ $? -eq 42 ]; then
|
||||||
|
echo
|
||||||
|
echo
|
||||||
|
echo
|
||||||
|
echo
|
||||||
|
echo
|
||||||
blender --python run.py
|
blender --python run.py
|
||||||
fi
|
fi
|
||||||
|
|
34
code/test.py
34
code/test.py
|
@ -1,34 +0,0 @@
|
||||||
import enum
|
|
||||||
|
|
||||||
class BlenderTypeEnum(str, enum.Enum):
|
|
||||||
def _generate_next_value_(name, start, count, last_values):
|
|
||||||
return name
|
|
||||||
|
|
||||||
|
|
||||||
def blender_type_enum(cls):
|
|
||||||
# Construct Set w/Modified Member Names
|
|
||||||
new_members = {name: f"{name}{cls.__name__}" for name, member in cls.__members__.items()}
|
|
||||||
|
|
||||||
# Dynamically Declare New Enum Class w/Modified Members
|
|
||||||
new_cls = enum.Enum(cls.__name__, new_members, type=BlenderTypeEnum)
|
|
||||||
new_cls.__module__ = cls.__module__
|
|
||||||
|
|
||||||
# Return New (Replacing) Enum Class
|
|
||||||
return new_cls
|
|
||||||
|
|
||||||
@blender_type_enum
|
|
||||||
class TreeType(enum.Enum):
|
|
||||||
MaxwellSim = enum.auto()
|
|
||||||
|
|
||||||
@blender_type_enum
|
|
||||||
class SocketType(enum.Enum):
|
|
||||||
MaxwellSource = enum.auto()
|
|
||||||
MaxwellMedium = enum.auto()
|
|
||||||
MaxwellStructure = enum.auto()
|
|
||||||
MaxwellBound = enum.auto()
|
|
||||||
MaxwellFDTDSim = enum.auto()
|
|
||||||
|
|
||||||
# Demonstration
|
|
||||||
print(TreeType.MaxwellSim.value) # Should print "MaxwellSimTreeType"
|
|
||||||
print(SocketType.MaxwellSource.value) # Should print "MaxwellSourceSocketType"
|
|
||||||
|
|
Loading…
Reference in New Issue