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 categories
|
||||
from . import socket_types
|
||||
from . import tree
|
||||
|
||||
BL_REGISTER = [
|
||||
*tree.BL_REGISTER,
|
||||
*socket_types.BL_REGISTER,
|
||||
*sockets.BL_REGISTER,
|
||||
*node_tree.BL_REGISTER,
|
||||
*nodes.BL_REGISTER,
|
||||
*categories.BL_REGISTER,
|
||||
]
|
||||
|
|
|
@ -1,18 +1,10 @@
|
|||
## TODO: Refactor this whole horrible module.
|
||||
|
||||
import bpy
|
||||
import nodeitems_utils
|
||||
from . import types
|
||||
from . import contracts
|
||||
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 = []
|
||||
def mk_node_categories(
|
||||
tree,
|
||||
|
@ -23,7 +15,7 @@ def mk_node_categories(
|
|||
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():
|
||||
if node_category == base_category:
|
||||
items.append(nodeitems_utils.NodeItem(node_type.value))
|
||||
|
@ -31,7 +23,7 @@ def mk_node_categories(
|
|||
# Add Node Sub-Menus
|
||||
for syllable, sub_tree in tree.items():
|
||||
current_syllable_path = syllable_prefix + [syllable]
|
||||
current_category = types.NodeCategory[
|
||||
current_category = contracts.NodeCategory[
|
||||
"_".join(current_syllable_path)
|
||||
]
|
||||
|
||||
|
@ -51,10 +43,12 @@ def mk_node_categories(
|
|||
nodeitems_utils.NodeItem,
|
||||
):
|
||||
nodeitem = nodeitem_or_submenu
|
||||
self.layout.operator(
|
||||
op_add_node_cfg = self.layout.operator(
|
||||
"node.add_node",
|
||||
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):
|
||||
submenu_id = nodeitem_or_submenu
|
||||
self.layout.menu(submenu_id)
|
||||
|
@ -62,7 +56,7 @@ def mk_node_categories(
|
|||
|
||||
menu_class = type(current_category.value, (bpy.types.Menu,), {
|
||||
'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)),
|
||||
})
|
||||
|
||||
|
@ -78,7 +72,7 @@ def mk_node_categories(
|
|||
# - Blender Registration
|
||||
####################
|
||||
BL_NODE_CATEGORIES = mk_node_categories(
|
||||
types.NodeCategory.get_tree()["MAXWELL"]["SIM"],
|
||||
contracts.NodeCategory.get_tree()["MAXWELL"]["SIM"],
|
||||
syllable_prefix = ["MAXWELL", "SIM"],
|
||||
)
|
||||
## TODO: refractor, this has a big code smell
|
||||
|
@ -88,7 +82,7 @@ BL_REGISTER = [
|
|||
|
||||
## TEST - TODO this is a big code smell
|
||||
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:
|
||||
if isinstance(nodeitem_or_submenu, str):
|
||||
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 sympy as sp
|
||||
import sympy.physics.units as spu
|
||||
import pydantic as pyd
|
||||
import bpy
|
||||
|
||||
from ...utils.blender_type_enum import (
|
||||
BlenderTypeEnum, append_cls_name_to_values
|
||||
)
|
||||
|
||||
####################
|
||||
# - String Types
|
||||
####################
|
||||
BlenderColorRGB = tuple[float, float, float, float]
|
||||
BlenderID = pytypes_ext.Annotated[str, pyd.StringConstraints(
|
||||
pattern=r'^[A-Z_]+$',
|
||||
)]
|
||||
|
||||
# Socket ID
|
||||
SocketName = pytypes_ext.Annotated[str, pyd.StringConstraints(
|
||||
pattern=r'^[a-zA-Z0-9_]+$',
|
||||
)]
|
||||
BLSocketName = pytypes_ext.Annotated[str, pyd.StringConstraints(
|
||||
pattern=r'^[a-zA-Z0-9_]+$',
|
||||
)]
|
||||
|
||||
# Socket ID
|
||||
PresetID = pytypes_ext.Annotated[str, pyd.StringConstraints(
|
||||
pattern=r'^[A-Z_]+$',
|
||||
)]
|
||||
|
||||
####################
|
||||
# - Generic Types
|
||||
####################
|
||||
SocketReturnType = typ.TypeVar('SocketReturnType', covariant=True)
|
||||
## - Covariance: If B subtypes A, then Container[B] subtypes Container[A].
|
||||
## - This is absolutely what we want here.
|
||||
|
||||
####################
|
||||
# - Sympy Expression Typing
|
||||
####################
|
||||
ALL_UNIT_SYMBOLS = {
|
||||
unit
|
||||
for unit in spu.__dict__.values()
|
||||
if isinstance(unit, spu.Quantity)
|
||||
}
|
||||
def has_units(expr: sp.Expr):
|
||||
return any(
|
||||
symbol in ALL_UNIT_SYMBOLS
|
||||
for symbol in expr.atoms(sp.Symbol)
|
||||
)
|
||||
def is_exactly_expressed_as_unit(expr: sp.Expr, unit) -> bool:
|
||||
#try:
|
||||
converted_expr = expr / unit
|
||||
|
||||
return (
|
||||
converted_expr.is_number
|
||||
and not converted_expr.has(spu.Quantity)
|
||||
)
|
||||
|
||||
####################
|
||||
# - Icon Types
|
||||
####################
|
||||
class Icon(BlenderTypeEnum):
|
||||
MaxwellSimTree = "MOD_SIMPLEDEFORM"
|
||||
|
||||
####################
|
||||
# - Tree Types
|
||||
####################
|
||||
|
@ -17,6 +79,17 @@ class TreeType(BlenderTypeEnum):
|
|||
####################
|
||||
@append_cls_name_to_values
|
||||
class SocketType(BlenderTypeEnum):
|
||||
Any = enum.auto()
|
||||
Text = enum.auto()
|
||||
FilePath = enum.auto()
|
||||
|
||||
RationalNumber = enum.auto()
|
||||
RealNumber = enum.auto()
|
||||
ComplexNumber = enum.auto()
|
||||
|
||||
PhysicalLength = enum.auto()
|
||||
PhysicalArea = enum.auto()
|
||||
|
||||
MaxwellSource = enum.auto()
|
||||
MaxwellMedium = enum.auto()
|
||||
MaxwellStructure = enum.auto()
|
||||
|
@ -132,7 +205,7 @@ class NodeType(BlenderTypeEnum):
|
|||
KSpaceNearFieldProjectionMonitor = enum.auto()
|
||||
|
||||
# Simulations
|
||||
FDTDSimulation = enum.auto()
|
||||
FDTDSim = enum.auto()
|
||||
|
||||
SimulationGridDiscretization = enum.auto()
|
||||
|
||||
|
@ -155,8 +228,6 @@ class NodeType(BlenderTypeEnum):
|
|||
####################
|
||||
# - Node Category Types
|
||||
####################
|
||||
#@append_cls_name_to_values
|
||||
#@append_cls_name_to_values
|
||||
class NodeCategory(BlenderTypeEnum):
|
||||
MAXWELL_SIM = enum.auto()
|
||||
|
||||
|
@ -219,6 +290,7 @@ class NodeCategory(BlenderTypeEnum):
|
|||
|
||||
@classmethod
|
||||
def get_tree(cls):
|
||||
## TODO: Refactor
|
||||
syllable_categories = [
|
||||
node_category.value.split("_")
|
||||
for node_category in cls
|
||||
|
@ -296,3 +368,104 @@ NodeCategory_to_category_label = {
|
|||
NodeCategory.MAXWELL_SIM_UTILITIES_MATH: "Math",
|
||||
NodeCategory.MAXWELL_SIM_UTILITIES_FIELDMATH: "Field Math",
|
||||
}
|
||||
|
||||
|
||||
|
||||
####################
|
||||
# - Protocols
|
||||
####################
|
||||
class SocketDefProtocol(typ.Protocol):
|
||||
socket_type: SocketType
|
||||
label: str
|
||||
|
||||
def init(self, bl_socket: bpy.types.NodeSocket) -> None:
|
||||
...
|
||||
|
||||
class PresetDef(pyd.BaseModel):
|
||||
label: str
|
||||
description: str
|
||||
values: dict[SocketName, typ.Any]
|
||||
|
||||
@typ.runtime_checkable
|
||||
#class BLSocketProtocol(typ.Protocol):
|
||||
# socket_type: SocketType
|
||||
# socket_color: BlenderColorRGB
|
||||
#
|
||||
# bl_label: str
|
||||
#
|
||||
# compatible_types: dict[typ.Type, set[typ.Callable[[typ.Any], bool]]]
|
||||
#
|
||||
# def draw(
|
||||
# self,
|
||||
# context: bpy.types.Context,
|
||||
# layout: bpy.types.UILayout,
|
||||
# node: bpy.types.Node,
|
||||
# text: str,
|
||||
# ) -> None:
|
||||
# ...
|
||||
#
|
||||
# @property
|
||||
# def default_value(self) -> typ.Any:
|
||||
# ...
|
||||
# @default_value.setter
|
||||
# def default_value(self, value: typ.Any) -> typ.Any:
|
||||
# ...
|
||||
#
|
||||
|
||||
@typ.runtime_checkable
|
||||
class NodeTypeProtocol(typ.Protocol):
|
||||
node_type: NodeType
|
||||
|
||||
bl_label: str
|
||||
|
||||
input_sockets: dict[SocketName, SocketDefProtocol]
|
||||
output_sockets: dict[SocketName, SocketDefProtocol]
|
||||
presets: dict[PresetID, PresetDef] | None
|
||||
|
||||
# Built-In Blender Methods
|
||||
def init(self, context: bpy.types.Context) -> None:
|
||||
...
|
||||
|
||||
def draw_buttons(
|
||||
self,
|
||||
context: bpy.types.Context,
|
||||
layout: bpy.types.UILayout,
|
||||
) -> None:
|
||||
...
|
||||
|
||||
@classmethod
|
||||
def poll(cls, ntree: bpy.types.NodeTree) -> None:
|
||||
...
|
||||
|
||||
# Socket Getters
|
||||
def g_input_bl_socket(
|
||||
self,
|
||||
input_socket_name: SocketName,
|
||||
) -> bpy.types.NodeSocket:
|
||||
...
|
||||
|
||||
def g_output_bl_socket(
|
||||
self,
|
||||
output_socket_name: SocketName,
|
||||
) -> bpy.types.NodeSocket:
|
||||
...
|
||||
|
||||
# Socket Methods
|
||||
def s_input_value(
|
||||
self,
|
||||
input_socket_name: SocketName,
|
||||
value: typ.Any
|
||||
) -> typ.Any:
|
||||
...
|
||||
|
||||
# Data-Flow Methods
|
||||
def compute_input(
|
||||
self,
|
||||
input_socket_name: SocketName,
|
||||
) -> typ.Any:
|
||||
...
|
||||
def compute_output(
|
||||
self,
|
||||
output_socket_name: SocketName,
|
||||
) -> typ.Any:
|
||||
...
|
|
@ -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 simulations
|
||||
from . import structures
|
||||
|
||||
BL_REGISTER = [
|
||||
*inputs.BL_REGISTER,
|
||||
*outputs.BL_REGISTER,
|
||||
*mediums.BL_REGISTER,
|
||||
*sources.BL_REGISTER,
|
||||
*simulations.BL_REGISTER,
|
||||
*structures.BL_REGISTER,
|
||||
]
|
||||
BL_NODES = {
|
||||
**inputs.BL_NODES,
|
||||
**outputs.BL_NODES,
|
||||
**sources.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
|
||||
from .... import types, constants
|
||||
from ... import node_base
|
||||
import tidy3d as td
|
||||
import sympy as sp
|
||||
import sympy.physics.units as spu
|
||||
|
||||
class TripleSellmeierMediumNode(node_base.MaxwellSimTreeNode):
|
||||
bl_idname = types.NodeType.TripleSellmeierMedium.value
|
||||
bl_label = "Triple Sellmeier Medium"
|
||||
bl_icon = constants.ICON_SIM_MEDIUM
|
||||
from .... import contracts
|
||||
from .... import sockets
|
||||
from ... import base
|
||||
|
||||
class TripleSellmeierMediumNode(base.MaxwellSimTreeNode):
|
||||
node_type = contracts.NodeType.TripleSellmeierMedium
|
||||
|
||||
bl_label = "Three-Parameter Sellmeier Medium"
|
||||
#bl_icon = ...
|
||||
|
||||
####################
|
||||
# - Sockets
|
||||
####################
|
||||
input_sockets = {
|
||||
"B1": ("NodeSocketFloat", "B1"),
|
||||
"B2": ("NodeSocketFloat", "B2"),
|
||||
"B3": ("NodeSocketFloat", "B3"),
|
||||
"C1": ("NodeSocketFloat", "C1 (um^2)"),
|
||||
"C2": ("NodeSocketFloat", "C2 (um^2)"),
|
||||
"C3": ("NodeSocketFloat", "C3 (um^2)"),
|
||||
f"B{i}": sockets.RealNumberSocketDef(
|
||||
label=f"B{i}",
|
||||
)
|
||||
for i in [1, 2, 3]
|
||||
} | {
|
||||
f"C{i}": sockets.PhysicalAreaSocketDef(
|
||||
label=f"C{i}",
|
||||
default_unit="UM_SQ"
|
||||
)
|
||||
for i in [1, 2, 3]
|
||||
}
|
||||
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": {
|
||||
|
||||
####################
|
||||
# - Presets
|
||||
####################
|
||||
presets = {
|
||||
"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,
|
||||
"C2": 2.00179144e-2,
|
||||
"C3": 103.560653,
|
||||
},
|
||||
"FUSED_SILICA": {
|
||||
"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,
|
||||
"C2": 1.35120631e-2,
|
||||
"C3": 97.9340025,
|
||||
},
|
||||
"C1": 4.67914826e-3 * spu.um**2,
|
||||
"C2": 1.35120631e-2 * spu.um**2,
|
||||
"C3": 97.9340025 * spu.um**2,
|
||||
}
|
||||
),
|
||||
}
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
# - Output Socket Computation
|
||||
####################
|
||||
def draw_buttons(self, context, layout):
|
||||
layout.prop(self, 'preset', text="")
|
||||
@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,
|
||||
]
|
||||
BL_NODES = {
|
||||
types.NodeType.TripleSellmeierMedium: (
|
||||
types.NodeCategory.MAXWELL_SIM_MEDIUMS_LINEARMEDIUMS
|
||||
contracts.NodeType.TripleSellmeierMedium: (
|
||||
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
|
||||
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
|
||||
if [ $? -eq 42 ]; then
|
||||
echo
|
||||
echo
|
||||
echo
|
||||
echo
|
||||
echo
|
||||
blender --python run.py
|
||||
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