feat: Various features (some very prototype).

It's very prototype-y. Cleanup pending.
blender-plugin-mvp
Sofus Albert Høgsbro Rose 2024-02-26 16:16:06 +01:00
parent 74d5a5daf8
commit d95210dc34
Signed by: so-rose
GPG Key ID: AD901CB0F3701434
51 changed files with 2483 additions and 199 deletions

View File

@ -97,6 +97,7 @@ class SocketType(BlenderTypeEnum):
Complex3DVector = enum.auto()
# Physical
PhysicalUnitSystem = enum.auto()
PhysicalTime = enum.auto()
PhysicalAngle = enum.auto()
@ -122,6 +123,7 @@ class SocketType(BlenderTypeEnum):
PhysicalPol = enum.auto()
PhysicalFreq = enum.auto()
PhysicalVacWL = enum.auto()
PhysicalSpecPowerDist = enum.auto()
PhysicalSpecRelPermDist = enum.auto()
@ -135,6 +137,8 @@ class SocketType(BlenderTypeEnum):
BlenderGeoNodes = enum.auto()
BlenderText = enum.auto()
BlenderPreviewTarget = enum.auto()
# Maxwell
MaxwellSource = enum.auto()
MaxwellTemporalShape = enum.auto()
@ -376,14 +380,19 @@ SocketType_to_units = {
"GHZ": spuex.gigahertz,
"THZ": spuex.terahertz,
"PHZ": spuex.petahertz,
#"EHZ": spu.exahertz,
"VAC_PM": spu.picometer, ## c(vac) = wl*freq
"VAC_A": spu.angstrom,
"VAC_NM": spu.nanometer,
"VAC_UM": spu.micrometer,
"VAC_MM": spu.millimeter,
"VAC_CM": spu.centimeter,
"VAC_M": spu.meter,
"EHZ": spuex.exahertz,
},
},
SocketType.PhysicalVacWL: {
"default": "NM",
"values": {
"PM": spu.picometer, ## c(vac) = wl*freq
"A": spu.angstrom,
"NM": spu.nanometer,
"UM": spu.micrometer,
"MM": spu.millimeter,
"CM": spu.centimeter,
"M": spu.meter,
},
},
}
@ -408,6 +417,7 @@ SocketType_to_color = {
SocketType.Complex3DVector: (0.2, 0.7, 0.2, 1.0), # Dark Green
# Physical
SocketType.PhysicalUnitSystem: (1.0, 0.5, 0.5, 1.0), # Light Red
SocketType.PhysicalTime: (1.0, 0.5, 0.5, 1.0), # Light Red
SocketType.PhysicalAngle: (0.9, 0.45, 0.45, 1.0), # Medium Light Red
SocketType.PhysicalLength: (0.8, 0.4, 0.4, 1.0), # Medium Red
@ -425,6 +435,7 @@ SocketType_to_color = {
SocketType.PhysicalForce3DVector: (0.6, 0.45, 0.25, 1.0), # Medium Dark Orange
SocketType.PhysicalPol: (0.5, 0.4, 0.2, 1.0), # Dark Orange
SocketType.PhysicalFreq: (1.0, 0.7, 0.5, 1.0), # Light Peach
SocketType.PhysicalVacWL: (1.0, 0.7, 0.5, 1.0), # Light Peach
SocketType.PhysicalSpecPowerDist: (0.9, 0.65, 0.45, 1.0), # Medium Light Peach
SocketType.PhysicalSpecRelPermDist: (0.8, 0.6, 0.4, 1.0), # Medium Peach
@ -435,6 +446,7 @@ SocketType_to_color = {
SocketType.BlenderVolume: (0.4, 0.35, 0.7, 1.0), # Medium Dark Purple
SocketType.BlenderGeoNodes: (0.3, 0.3, 0.6, 1.0), # Dark Purple
SocketType.BlenderText: (0.5, 0.5, 0.75, 1.0), # Light Lavender
SocketType.BlenderPreviewTarget: (0.5, 0.5, 0.75, 1.0), # Light Lavender
# Maxwell
SocketType.MaxwellSource: (1.0, 1.0, 0.5, 1.0), # Light Yellow
@ -451,30 +463,35 @@ SocketType_to_color = {
}
BLNodeSocket_to_SocketType = {
1: {
"NodeSocketStandard": SocketType.Any,
"NodeSocketVirtual": SocketType.Any,
"NodeSocketGeometry": SocketType.Any,
"NodeSocketTexture": SocketType.Any,
"NodeSocketShader": SocketType.Any,
"NodeSocketMaterial": SocketType.Any,
"NodeSocketString": SocketType.Text,
"NodeSocketBool": SocketType.Bool,
"NodeSocketCollection": SocketType.BlenderCollection,
"NodeSocketColor": SocketType.Real3DVector,
"NodeSocketImage": SocketType.BlenderImage,
"NodeSocketObject": SocketType.BlenderObject,
"NodeSocketFloat": SocketType.RealNumber,
"NodeSocketFloatAngle": SocketType.RealNumber,
"NodeSocketFloatDistance": SocketType.RealNumber,
"NodeSocketFloatAngle": SocketType.PhysicalAngle,
"NodeSocketFloatDistance": SocketType.PhysicalLength,
"NodeSocketFloatFactor": SocketType.RealNumber,
"NodeSocketFloatPercentage": SocketType.RealNumber,
"NodeSocketFloatTime": SocketType.RealNumber,
"NodeSocketFloatTime": SocketType.PhysicalTime,
"NodeSocketFloatTimeAbsolute": SocketType.RealNumber,
"NodeSocketFloatUnsigned": SocketType.RealNumber,
"NodeSocketGeometry": SocketType.Any,
"NodeSocketImage": SocketType.BlenderImage,
"NodeSocketInt": SocketType.IntegerNumber,
"NodeSocketIntFactor": SocketType.IntegerNumber,
"NodeSocketIntPercentage": SocketType.IntegerNumber,
"NodeSocketIntUnsigned": SocketType.IntegerNumber,
"NodeSocketMaterial": SocketType.Any,
"NodeSocketObject": SocketType.BlenderObject,
"NodeSocketRotation": SocketType.Real3DVector,
"NodeSocketShader": SocketType.Any,
"NodeSocketStandard": SocketType.Any,
"NodeSocketString": SocketType.Text,
"NodeSocketTexture": SocketType.Any,
},
2: {
"NodeSocketVector": SocketType.Real3DVector,
"NodeSocketVectorAcceleration": SocketType.Real3DVector,
"NodeSocketVectorDirection": SocketType.Real3DVector,
@ -482,9 +499,68 @@ BLNodeSocket_to_SocketType = {
"NodeSocketVectorTranslation": SocketType.Real3DVector,
"NodeSocketVectorVelocity": SocketType.Real3DVector,
"NodeSocketVectorXYZ": SocketType.Real3DVector,
"NodeSocketVirtual": SocketType.Any,
#"NodeSocketVector": SocketType.Real2DVector,
#"NodeSocketVectorAcceleration": SocketType.PhysicalAccel2D,
#"NodeSocketVectorDirection": SocketType.PhysicalDir2D,
#"NodeSocketVectorEuler": SocketType.PhysicalEuler2D,
#"NodeSocketVectorTranslation": SocketType.PhysicalDispl2D,
#"NodeSocketVectorVelocity": SocketType.PhysicalVel2D,
#"NodeSocketVectorXYZ": SocketType.Real2DPoint,
},
3: {
"NodeSocketRotation": SocketType.Real3DVector,
"NodeSocketColor": SocketType.Any,
"NodeSocketVector": SocketType.Real3DVector,
#"NodeSocketVectorAcceleration": SocketType.PhysicalAccel3D,
#"NodeSocketVectorDirection": SocketType.PhysicalDir3D,
#"NodeSocketVectorEuler": SocketType.PhysicalEuler3D,
#"NodeSocketVectorTranslation": SocketType.PhysicalDispl3D,
"NodeSocketVectorTranslation": SocketType.PhysicalPoint3D,
#"NodeSocketVectorVelocity": SocketType.PhysicalVel3D,
"NodeSocketVectorXYZ": SocketType.PhysicalPoint3D,
},
}
BLNodeSocket_to_SocketType_by_desc = {
1: {
"Angle": SocketType.PhysicalAngle,
"Length": SocketType.PhysicalLength,
"Area": SocketType.PhysicalArea,
"Volume": SocketType.PhysicalVolume,
"Mass": SocketType.PhysicalMass,
"Speed": SocketType.PhysicalSpeed,
"Accel": SocketType.PhysicalAccelScalar,
"Force": SocketType.PhysicalForceScalar,
"Freq": SocketType.PhysicalFreq,
},
2: {
#"2DCount": SocketType.Int2DVector,
#"2DPoint": SocketType.PhysicalPoint2D,
#"2DSize": SocketType.PhysicalSize2D,
#"2DPol": SocketType.PhysicalPol,
"2DPoint": SocketType.PhysicalPoint3D,
"2DSize": SocketType.PhysicalSize3D,
},
3: {
#"Count": SocketType.Int3DVector,
"Point": SocketType.PhysicalPoint3D,
"Size": SocketType.PhysicalSize3D,
#"Force": SocketType.PhysicalForce3D,
"Freq": SocketType.PhysicalSize3D,
},
}
####################
# - Node Types
####################
@ -493,15 +569,17 @@ class NodeType(BlenderTypeEnum):
KitchenSink = enum.auto()
# Inputs
UnitSystem = enum.auto()
## Inputs / Scene
Time = enum.auto()
UnitSystem = enum.auto()
## Inputs / Parameters
NumberParameter = enum.auto()
PhysicalParameter = enum.auto()
## Inputs / Constants
WaveConstant = enum.auto()
ScientificConstant = enum.auto()
NumberConstant = enum.auto()
PhysicalConstant = enum.auto()
@ -517,6 +595,7 @@ class NodeType(BlenderTypeEnum):
# Outputs
## Outputs / Viewers
Viewer3D = enum.auto()
ValueViewer = enum.auto()
ConsoleViewer = enum.auto()
@ -612,8 +691,13 @@ class NodeType(BlenderTypeEnum):
# Utilities
Combine = enum.auto()
Separate = enum.auto()
Math = enum.auto()
## Utilities / Converters
WaveConverter = enum.auto()
## Utilities / Operations
ArrayOperation = enum.auto()
@ -663,6 +747,7 @@ class NodeCategory(BlenderTypeEnum):
# Utilities/
MAXWELLSIM_UTILITIES = enum.auto()
MAXWELLSIM_UTILITIES_CONVERTERS = enum.auto()
MAXWELLSIM_UTILITIES_OPERATIONS = enum.auto()
@classmethod
@ -727,6 +812,7 @@ NodeCategory_to_category_label = {
# Utilities/
NodeCategory.MAXWELLSIM_UTILITIES: "Utilities",
NodeCategory.MAXWELLSIM_UTILITIES_CONVERTERS: "Converters",
NodeCategory.MAXWELLSIM_UTILITIES_OPERATIONS: "Operations",
}

View File

@ -4,6 +4,25 @@ from . import contracts
ICON_SIM_TREE = 'MOD_SIMPLEDEFORM'
class BLENDER_MAXWELL_PT_MaxwellSimTreePanel(bpy.types.Panel):
bl_label = "Node Tree Custom Prop"
bl_idname = "NODE_PT_custom_prop"
bl_space_type = 'NODE_EDITOR'
bl_region_type = 'UI'
bl_category = 'Item'
@classmethod
def poll(cls, context):
return context.space_data.tree_type == contracts.TreeType.MaxwellSim.value
def draw(self, context):
layout = self.layout
node_tree = context.space_data.node_tree
layout.prop(node_tree, "preview_collection")
layout.prop(node_tree, "non_preview_collection")
####################
# - Node Tree Definition
####################
@ -12,6 +31,22 @@ class MaxwellSimTree(bpy.types.NodeTree):
bl_label = "Maxwell Sim Editor"
bl_icon = contracts.Icon.MaxwellSimTree
preview_collection: bpy.props.PointerProperty(
name="Preview Collection",
description="Collection of Blender objects that will be previewed",
type=bpy.types.Collection,
update=(lambda self, context: self.trigger_updates())
)
non_preview_collection: bpy.props.PointerProperty(
name="Non-Preview Collection",
description="Collection of Blender objects that will NOT be previewed",
type=bpy.types.Collection,
update=(lambda self, context: self.trigger_updates())
)
def trigger_updates(self):
pass
####################
# - Blender Registration
####################

View File

@ -165,14 +165,19 @@ class MaxwellSimTreeNode(bpy.types.Node):
if output_socket_set_key not in socket_set_keys
]
def labeller(socket_set_key):
return " ".join(
word.capitalize()
for word in socket_set_key.split("_")
)
cls.__annotations__["socket_set"] = bpy.props.EnumProperty(
name="",
description="Select a node socket configuration",
items=[
(
socket_set_key,
socket_set_key.capitalize(),
socket_set_key.capitalize(),
labeller(socket_set_key),
labeller(socket_set_key),
)
for socket_set_key in socket_set_keys
],
@ -217,32 +222,28 @@ class MaxwellSimTreeNode(bpy.types.Node):
`NodeTypeProtocol` specification, and initializes each as described
by user-provided `SocketDefProtocol`s.
"""
# Initialize Input Sockets
# Create 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.socket_type.value,
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
# Create Output Sockets
for socket_name, socket_def in self.output_sockets.items():
self.outputs.new(
socket_def.socket_type.value,
socket_def.label,
)
# Initialize Sockets
for socket_name, socket_def in self.input_sockets.items():
bl_socket = self.inputs[
self.input_sockets[socket_name].label
]
socket_def.init(bl_socket)
for socket_name, socket_def in self.output_sockets.items():
bl_socket = self.outputs[
self.output_sockets[socket_name].label
]
@ -274,6 +275,9 @@ class MaxwellSimTreeNode(bpy.types.Node):
if self.preset is not None:
sync_selected_preset(self)
if hasattr(self, "init_cb"):
self.init_cb()
@classmethod
def poll(cls, ntree: bpy.types.NodeTree) -> bool:
"""This class method controls whether a node can be instantiated
@ -366,6 +370,12 @@ class MaxwellSimTreeNode(bpy.types.Node):
if hasattr(self, "draw_operators"):
self.draw_operators(context, layout)
if hasattr(self, "draw_props"):
self.draw_props(context, layout)
if hasattr(self, "draw_info"):
self.draw_info(context, layout)
####################
# - Socket Getters
####################
@ -392,13 +402,12 @@ class MaxwellSimTreeNode(bpy.types.Node):
return self.inputs[self.input_sockets[input_socket_name].label]
elif hasattr(self, "socket_set"):
# You're on your own, chump
return self.inputs[next(
socket_def.label
for socket_set, socket_dict in self.input_socket_sets.items()
for socket_name, socket_def in socket_dict.items()
if socket_name == input_socket_name
if socket_set == self.socket_set
)]
def g_output_bl_socket(
@ -436,21 +445,21 @@ class MaxwellSimTreeNode(bpy.types.Node):
self,
output_bl_socket_name: contracts.BLSocketName,
) -> contracts.SocketName:
if hasattr(self, "socket_set"):
return next(
socket_name
for socket_set, socket_dict in self.output_socket_sets.items()
for socket_name, socket_def in socket_dict.items()
if socket_def.label == output_bl_socket_name
)
else:
return next(
output_socket_names = [
output_socket_name
for output_socket_name in self.output_sockets.keys()
if self.output_sockets[
output_socket_name
].label == output_bl_socket_name
)
]
if hasattr(self, "socket_set"):
output_socket_names += [
socket_name
for socket_set, socket_dict in self.output_socket_sets.items()
for socket_name, socket_def in socket_dict.items()
if socket_def.label == output_bl_socket_name
]
return output_socket_names[0]
####################
# - Socket Setters

View File

@ -1,5 +1,82 @@
import tidy3d as td
import sympy as sp
import sympy.physics.units as spu
from ... import contracts
from ... import sockets
from .. import base
class BoundBoxNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.BoundBox
bl_label = "Bound Box"
#bl_icon = ...
####################
# - Sockets
####################
input_sockets = {
"x_pos": sockets.MaxwellBoundFaceSocketDef(
label="+x",
),
"x_neg": sockets.MaxwellBoundFaceSocketDef(
label="-x",
),
"y_pos": sockets.MaxwellBoundFaceSocketDef(
label="+y",
),
"y_neg": sockets.MaxwellBoundFaceSocketDef(
label="-y",
),
"z_pos": sockets.MaxwellBoundFaceSocketDef(
label="+z",
),
"z_neg": sockets.MaxwellBoundFaceSocketDef(
label="-z",
),
}
output_sockets = {
"bound": sockets.MaxwellBoundBoxSocketDef(
label="Bound",
),
}
####################
# - Output Socket Computation
####################
@base.computes_output_socket("bound")
def compute_simulation(self: contracts.NodeTypeProtocol) -> td.BoundarySpec:
x_pos = self.compute_input("x_pos")
x_neg = self.compute_input("x_neg")
y_pos = self.compute_input("x_pos")
y_neg = self.compute_input("x_neg")
z_pos = self.compute_input("x_pos")
z_neg = self.compute_input("x_neg")
return td.BoundarySpec(
x=td.Boundary(
plus=x_pos,
minus=x_neg,
),
y=td.Boundary(
plus=y_pos,
minus=y_neg,
),
z=td.Boundary(
plus=z_pos,
minus=z_neg,
),
)
####################
# - Blender Registration
####################
BL_REGISTER = []
BL_NODES = {}
BL_REGISTER = [
BoundBoxNode,
]
BL_NODES = {
contracts.NodeType.BoundBox: (
contracts.NodeCategory.MAXWELLSIM_BOUNDS
)
}

View File

@ -1,17 +1,20 @@
from . import unit_system
from . import constants
from . import lists
from . import parameters
from . import scene
BL_REGISTER = [
*unit_system.BL_REGISTER,
*scene.BL_REGISTER,
*constants.BL_REGISTER,
*parameters.BL_REGISTER,
*lists.BL_REGISTER,
]
BL_NODES = {
**unit_system.BL_NODES,
**scene.BL_NODES,
**constants.BL_NODES,
**parameters.BL_NODES,
**lists.BL_NODES,
}

View File

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

View File

@ -1,5 +1,68 @@
import typing as typ
from .... import contracts
from .... import sockets
from ... import base
class BlenderConstantNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.BlenderConstant
bl_label = "Blender Constant"
#bl_icon = constants.ICON_SIM_INPUT
input_sockets = {}
input_socket_sets = {
"object": {
"value": sockets.BlenderObjectSocketDef(
label="Object",
),
},
"collection": {
"value": sockets.BlenderCollectionSocketDef(
label="Collection",
),
},
"image": {
"value": sockets.BlenderImageSocketDef(
label="Image",
),
},
"volume": {
"value": sockets.BlenderVolumeSocketDef(
label="Volume",
),
},
"text": {
"value": sockets.BlenderTextSocketDef(
label="Text",
),
},
"geonodes": {
"value": sockets.BlenderGeoNodesSocketDef(
label="GeoNodes",
),
},
}
output_sockets = {}
output_socket_sets = input_socket_sets
####################
# - Callbacks
####################
@base.computes_output_socket("value")
def compute_value(self: contracts.NodeTypeProtocol) -> typ.Any:
return self.compute_input("value")
####################
# - Blender Registration
####################
BL_REGISTER = []
BL_NODES = {}
BL_REGISTER = [
BlenderConstantNode,
]
BL_NODES = {
contracts.NodeType.BlenderConstant: (
contracts.NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS
)
}

View File

@ -13,6 +13,11 @@ class NumberConstantNode(base.MaxwellSimTreeNode):
input_sockets = {}
input_socket_sets = {
"integer": {
"value": sockets.IntegerNumberSocketDef(
label="Integer",
),
},
"real": {
"value": sockets.RealNumberSocketDef(
label="Real",

View File

@ -0,0 +1,86 @@
import bpy
import sympy as sp
import sympy.physics.units as spu
import scipy as sc
from .... import contracts
from .... import sockets
from ... import base
vac_speed_of_light = (
sc.constants.speed_of_light
* spu.meter/spu.second
)
class WaveConstantNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.WaveConstant
bl_label = "Wave Constant"
input_sockets = {}
input_socket_sets = {
"vac_wl": {
"vac_wl": sockets.PhysicalVacWLSocketDef(
label="Vac WL",
),
},
"freq": {
"freq": sockets.PhysicalFreqSocketDef(
label="Freq",
),
},
}
output_sockets = {
"vac_wl": sockets.PhysicalVacWLSocketDef(
label="Vac WL",
),
"freq": sockets.PhysicalVacWLSocketDef(
label="Freq",
),
}
output_socket_sets = {}
####################
# - Callbacks
####################
@base.computes_output_socket("vac_wl")
def compute_vac_wl(self: contracts.NodeTypeProtocol) -> sp.Expr:
if self.socket_set == "vac_wl":
return self.compute_input("vac_wl")
elif self.socket_set == "freq":
freq = self.compute_input("freq")
return spu.convert_to(
vac_speed_of_light / freq,
spu.meter,
)
raise ValueError("No valid socket set.")
@base.computes_output_socket("freq")
def compute_freq(self: contracts.NodeTypeProtocol) -> sp.Expr:
if self.socket_set == "vac_wl":
vac_wl = self.compute_input("vac_wl")
return spu.convert_to(
vac_speed_of_light / vac_wl,
spu.hertz,
)
elif self.socket_set == "freq":
return self.compute_input("freq")
raise ValueError("No valid socket set.")
####################
# - Blender Registration
####################
BL_REGISTER = [
WaveConstantNode,
]
BL_NODES = {
contracts.NodeType.WaveConstant: (
contracts.NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS
)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,161 @@
import bpy
import tidy3d as td
import sympy as sp
import sympy.physics.units as spu
import numpy as np
import scipy as sc
from .....utils import extra_sympy_units as spuex
from ... import contracts
from ... import sockets
from .. import base
class ExperimentOperator00(bpy.types.Operator):
bl_idname = "blender_maxwell.experiment_operator_00"
bl_label = "exp"
@classmethod
def poll(cls, context):
return True
def execute(self, context):
node = context.node
node.invoke_matplotlib_and_update_image()
return {'FINISHED'}
class LibraryMediumNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.LibraryMedium
bl_label = "Library Medium"
#bl_icon = ...
####################
# - Sockets
####################
input_sockets = {}
output_sockets = {
"medium": sockets.MaxwellMediumSocketDef(
label="Medium"
),
}
####################
# - Properties
####################
material: bpy.props.EnumProperty(
name="",
description="",
#icon="NODE_MATERIAL",
items=[
(
mat_key,
td.material_library[mat_key].name,
", ".join([
ref.journal
for ref in td.material_library[mat_key].variants[
td.material_library[mat_key].default
].reference
])
)
for mat_key in td.material_library
if mat_key != "graphene" ## For some reason, it's unique...
],
default="Au",
update=(lambda self,context: self.update()),
)
####################
# - UI
####################
def draw_props(self, context, layout):
layout.prop(self, "material", text="")
def draw_info(self, context, layout):
layout.operator(ExperimentOperator00.bl_idname, text="Experiment")
vac_speed_of_light = sc.constants.speed_of_light * spu.meter/spu.second
mat = td.material_library[self.material]
freq_range = [
spu.convert_to(
val * spu.hertz,
spuex.terahertz,
) / spuex.terahertz
for val in mat.medium.frequency_range
]
nm_range = [
spu.convert_to(
vac_speed_of_light / (val * spu.hertz),
spu.nanometer,
) / spu.nanometer
for val in mat.medium.frequency_range
]
layout.label(text=f"nm: [{nm_range[1].n(2)}, {nm_range[0].n(2)}]")
layout.label(text=f"THz: [{freq_range[0].n(2)}, {freq_range[1].n(2)}]")
####################
# - Output Socket Computation
####################
@base.computes_output_socket("medium")
def compute_medium(self: contracts.NodeTypeProtocol) -> td.AbstractMedium:
return td.material_library[self.material].medium
####################
# - Experiment
####################
def invoke_matplotlib_and_update_image(self):
import matplotlib.pyplot as plt
mat = td.material_library[self.material]
aspect_ratio = 1.0
for area in bpy.context.screen.areas:
if area.type == 'IMAGE_EDITOR':
width = area.width
height = area.height
aspect_ratio = width / height
# Generate a plot with matplotlib
fig_width = 6
fig_height = fig_width / aspect_ratio
fig, ax = plt.subplots(figsize=(fig_width, fig_height))
ax.set_aspect(aspect_ratio)
mat.medium.plot(
np.linspace(*mat.medium.frequency_range[:2], 50),
ax=ax,
)
# Save the plot to a temporary file
temp_plot_file = bpy.path.abspath('//temp_plot.png')
fig.savefig(temp_plot_file, bbox_inches='tight')
plt.close(fig) # Close the figure to free up memory
# Load or reload the image in Blender
if "matplotlib_plot" in bpy.data.images:
image = bpy.data.images["matplotlib_plot"]
image.reload()
else:
image = bpy.data.images.load(temp_plot_file)
image.name = "matplotlib_plot"
# Write the plot to an image datablock in Blender
for area in bpy.context.screen.areas:
if area.type == 'IMAGE_EDITOR':
for space in area.spaces:
if space.type == 'IMAGE_EDITOR':
space.image = image
return True
####################
# - Blender Registration
####################
BL_REGISTER = []
BL_NODES = {}
BL_REGISTER = [
ExperimentOperator00,
LibraryMediumNode,
]
BL_NODES = {
contracts.NodeType.LibraryMedium: (
contracts.NodeCategory.MAXWELLSIM_MEDIUMS
)
}

View File

@ -5,6 +5,7 @@ from pathlib import Path
import bpy
import sympy as sp
import pydantic as pyd
import tidy3d as td
from .... import contracts
from .... import sockets
@ -26,6 +27,19 @@ class JSONFileExporterPrintJSON(bpy.types.Operator):
print(node.linked_data_as_json())
return {'FINISHED'}
class JSONFileExporterMeshData(bpy.types.Operator):
bl_idname = "blender_maxwell.json_file_exporter_mesh_data"
bl_label = "Print any mesh data linked into a JSONFileExporterNode."
@classmethod
def poll(cls, context):
return True
def execute(self, context):
node = context.node
print(node.linked_mesh_data())
return {'FINISHED'}
class JSONFileExporterSaveJSON(bpy.types.Operator):
bl_idname = "blender_maxwell.json_file_exporter_save_json"
bl_label = "Save the JSON of what's linked into a JSONFileExporterNode."
@ -69,6 +83,7 @@ class JSONFileExporterNode(base.MaxwellSimTreeNode):
) -> None:
layout.operator(JSONFileExporterPrintJSON.bl_idname, text="Print")
layout.operator(JSONFileExporterSaveJSON.bl_idname, text="Save")
layout.operator(JSONFileExporterMeshData.bl_idname, text="Mesh Info")
####################
# - Methods
@ -85,10 +100,16 @@ class JSONFileExporterNode(base.MaxwellSimTreeNode):
elif isinstance(data, pyd.BaseModel):
return data.model_dump_json()
# Finally: Try json.dumps (might fail)
else:
json.dumps(data)
def linked_mesh_data(self) -> str | None:
if self.g_input_bl_socket("data").is_linked:
data: typ.Any = self.compute_input("data")
if isinstance(data, td.Structure):
return data.geometry
def export_data_as_json(self) -> None:
if (data := self.linked_data_as_json()):
data_dict = json.loads(data)
@ -101,8 +122,9 @@ class JSONFileExporterNode(base.MaxwellSimTreeNode):
####################
BL_REGISTER = [
JSONFileExporterPrintJSON,
JSONFileExporterNode,
JSONFileExporterMeshData,
JSONFileExporterSaveJSON,
JSONFileExporterNode,
]
BL_NODES = {
contracts.NodeType.JSONFileExporter: (

View File

@ -1,11 +1,14 @@
from . import viewer_3d
from . import value_viewer
from . import console_viewer
BL_REGISTER = [
*viewer_3d.BL_REGISTER,
*value_viewer.BL_REGISTER,
*console_viewer.BL_REGISTER,
]
BL_NODES = {
**viewer_3d.BL_NODES,
**value_viewer.BL_NODES,
**console_viewer.BL_NODES,
}

View File

@ -0,0 +1,50 @@
import typing as typ
import json
from pathlib import Path
import bpy
import sympy as sp
import pydantic as pyd
import tidy3d as td
from .... import contracts
from .... import sockets
from ... import base
INTERNAL_GEONODES = {
}
####################
# - Node
####################
class Viewer3DNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.Viewer3D
bl_label = "3D Viewer"
input_sockets = {
"data": sockets.AnySocketDef(
label="Data",
),
}
output_sockets = {}
####################
# - Update
####################
def update_cb(self):
pass
####################
# - Blender Registration
####################
BL_REGISTER = [
Viewer3DNode,
]
BL_NODES = {
contracts.NodeType.Viewer3D: (
contracts.NodeCategory.MAXWELLSIM_OUTPUTS_VIEWERS
)
}

View File

@ -19,8 +19,8 @@ class FDTDSimNode(base.MaxwellSimTreeNode):
"run_time": sockets.PhysicalTimeSocketDef(
label="Run Time",
),
"size": sockets.PhysicalSize3DSocketDef(
label="Size",
"domain": sockets.PhysicalSize3DSocketDef(
label="Domain",
),
"ambient_medium": sockets.MaxwellMediumSocketDef(
label="Ambient Medium",
@ -37,7 +37,7 @@ class FDTDSimNode(base.MaxwellSimTreeNode):
}
output_sockets = {
"fdtd_sim": sockets.MaxwellFDTDSimSocketDef(
label="Medium",
label="FDTD Sim",
),
}
@ -47,17 +47,17 @@ class FDTDSimNode(base.MaxwellSimTreeNode):
@base.computes_output_socket("fdtd_sim")
def compute_simulation(self: contracts.NodeTypeProtocol) -> td.Simulation:
_run_time = self.compute_input("run_time")
_size = self.compute_input("size")
_domain = self.compute_input("domain")
ambient_medium = self.compute_input("ambient_medium")
structures = [self.compute_input("structure")]
sources = [self.compute_input("source")]
bound = self.compute_input("bound")
run_time = spu.convert_to(_run_time, spu.second) / spu.second
size = tuple(spu.convert_to(_size, spu.um) / spu.um)
domain = tuple(spu.convert_to(_domain, spu.um) / spu.um)
return td.Simulation(
size=size,
size=domain,
medium=ambient_medium,
structures=structures,
sources=sources,

View File

@ -1,6 +1,94 @@
import math
import tidy3d as td
import sympy as sp
import sympy.physics.units as spu
from ... import contracts
from ... import sockets
from .. import base
class PlaneWaveSourceNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.PlaneWaveSource
bl_label = "Plane Wave Source"
#bl_icon = ...
####################
# - Sockets
####################
input_sockets = {
"temporal_shape": sockets.MaxwellTemporalShapeSocketDef(
label="Temporal Shape",
),
"center": sockets.PhysicalPoint3DSocketDef(
label="Center",
),
"size": sockets.PhysicalSize3DSocketDef(
label="Size",
),
"direction": sockets.BoolSocketDef(
label="+ Direction?",
default_value=True,
),
"angle_theta": sockets.PhysicalAngleSocketDef(
label="θ",
),
"angle_phi": sockets.PhysicalAngleSocketDef(
label="φ",
),
"angle_pol": sockets.PhysicalAngleSocketDef(
label="Pol Angle",
),
}
output_sockets = {
"source": sockets.MaxwellSourceSocketDef(
label="Source",
),
}
####################
# - Output Socket Computation
####################
@base.computes_output_socket("source")
def compute_source(self: contracts.NodeTypeProtocol) -> td.PointDipole:
temporal_shape = self.compute_input("temporal_shape")
_center = self.compute_input("center")
_size = self.compute_input("size")
_direction = self.compute_input("direction")
_angle_theta = self.compute_input("angle_theta")
_angle_phi = self.compute_input("angle_phi")
_angle_pol = self.compute_input("angle_pol")
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
size = tuple(
0 if val == 1.0 else math.inf
for val in spu.convert_to(_size, spu.um) / spu.um
)
angle_theta = spu.convert_to(_angle_theta, spu.rad) / spu.rad
angle_phi = spu.convert_to(_angle_phi, spu.rad) / spu.rad
angle_pol = spu.convert_to(_angle_pol, spu.rad) / spu.rad
return td.PlaneWave(
center=center,
size=size,
source_time=temporal_shape,
direction="+" if _direction else "-",
angle_theta=angle_theta,
angle_phi=angle_phi,
pol_angle=angle_pol,
)
####################
# - Blender Registration
####################
BL_REGISTER = []
BL_NODES = {}
BL_REGISTER = [
PlaneWaveSourceNode,
]
BL_NODES = {
contracts.NodeType.PlaneWaveSource: (
contracts.NodeCategory.MAXWELLSIM_SOURCES
)
}

View File

@ -16,15 +16,19 @@ class PointDipoleSourceNode(base.MaxwellSimTreeNode):
# - Sockets
####################
input_sockets = {
#"polarization": sockets.PhysicalPolSocketDef(
# label="Polarization",
#), ## TODO: Exactly how to go about this...
"polarization": sockets.PhysicalPolSocketDef(
label="Polarization",
),
"temporal_shape": sockets.MaxwellTemporalShapeSocketDef(
label="Temporal Shape",
),
"center": sockets.PhysicalPoint3DSocketDef(
label="Center",
),
"interpolate": sockets.BoolSocketDef(
label="Interpolate",
default_value=True,
),
}
output_sockets = {
"source": sockets.MaxwellSourceSocketDef(
@ -37,19 +41,18 @@ class PointDipoleSourceNode(base.MaxwellSimTreeNode):
####################
@base.computes_output_socket("source")
def compute_source(self: contracts.NodeTypeProtocol) -> td.PointDipole:
polarization = self.compute_input("polarization")
temporal_shape = self.compute_input("temporal_shape")
_center = self.compute_input("center")
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
interpolate = self.compute_input("interpolate")
cheating_pol = "Ex"
## TODO: Fix
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
return td.PointDipole(
center=center,
source_time=temporal_shape,
interpolate=True,
polarization=cheating_pol,
interpolate=interpolate,
polarization=polarization,
)

View File

@ -1,6 +1,77 @@
import tidy3d as td
import sympy as sp
import sympy.physics.units as spu
from .... import contracts
from .... import sockets
from ... import base
class ContinuousWaveTemporalShapeNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.ContinuousWaveTemporalShape
bl_label = "Continuous Wave Temporal Shape"
#bl_icon = ...
####################
# - Sockets
####################
input_sockets = {
#"amplitude": sockets.RealNumberSocketDef(
# label="Temporal Shape",
#), ## Should have a unit of some kind...
"phase": sockets.PhysicalAngleSocketDef(
label="Phase",
),
"freq_center": sockets.PhysicalFreqSocketDef(
label="Freq Center",
),
"freq_std": sockets.PhysicalFreqSocketDef(
label="Freq STD",
),
"time_delay_rel_ang_freq": sockets.RealNumberSocketDef(
label="Time Delay rel. Ang. Freq",
default_value=5.0,
),
}
output_sockets = {
"temporal_shape": sockets.MaxwellTemporalShapeSocketDef(
label="Temporal Shape",
),
}
####################
# - Output Socket Computation
####################
@base.computes_output_socket("temporal_shape")
def compute_source(self: contracts.NodeTypeProtocol) -> td.PointDipole:
_phase = self.compute_input("phase")
_freq_center = self.compute_input("freq_center")
_freq_std = self.compute_input("freq_std")
time_delay_rel_ang_freq = self.compute_input("time_delay_rel_ang_freq")
cheating_amplitude = 1.0
phase = spu.convert_to(_phase, spu.radian) / spu.radian
freq_center = spu.convert_to(_freq_center, spu.hertz) / spu.hertz
freq_std = spu.convert_to(_freq_std, spu.hertz) / spu.hertz
return td.ContinuousWave(
amplitude=cheating_amplitude,
phase=phase,
freq0=freq_center,
fwidth=freq_std,
offset=time_delay_rel_ang_freq,
)
####################
# - Blender Registration
####################
BL_REGISTER = []
BL_NODES = {}
BL_REGISTER = [
ContinuousWaveTemporalShapeNode,
]
BL_NODES = {
contracts.NodeType.ContinuousWaveTemporalShape: (
contracts.NodeCategory.MAXWELLSIM_SOURCES_TEMPORALSHAPES
)
}

View File

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

View File

@ -30,6 +30,7 @@ class GaussianPulseTemporalShapeNode(base.MaxwellSimTreeNode):
),
"time_delay_rel_ang_freq": sockets.RealNumberSocketDef(
label="Time Delay rel. Ang. Freq",
default_value=5.0,
),
"remove_dc_component": sockets.BoolSocketDef(
label="Remove DC",

View File

@ -4,6 +4,7 @@ import sympy as sp
import sympy.physics.units as spu
import bpy
from bpy_types import bpy_types
import bmesh
from ... import contracts
@ -12,6 +13,26 @@ from .. import base
GEONODES_MODIFIER_NAME = "BLMaxwell_GeoNodes"
# Monkey-Patch Sympy Types
## TODO: This needs to be a more generic thing, this isn't the only place we're setting blender interface values.
def parse_scalar(scalar):
if isinstance(scalar, sp.Integer):
return int(scalar)
elif isinstance(scalar, sp.Float):
return float(scalar)
elif isinstance(scalar, sp.Rational):
return float(scalar)
elif isinstance(scalar, sp.Expr):
return float(scalar.n())
return scalar
def parse_bl_to_sp(scalar):
if isinstance(scalar, bpy_types.bpy_prop_array):
return sp.Matrix(tuple(scalar))
return scalar
class GeoNodesStructureNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.GeoNodesStructure
bl_label = "GeoNodes Structure"
@ -21,12 +42,15 @@ class GeoNodesStructureNode(base.MaxwellSimTreeNode):
# - Sockets
####################
input_sockets = {
"preview_target": sockets.BlenderPreviewTargetSocketDef(
label="Preview Target",
),
"blender_unit_system": sockets.PhysicalUnitSystemSocketDef(
label="Blender Units",
),
"medium": sockets.MaxwellMediumSocketDef(
label="Medium",
),
"object": sockets.BlenderObjectSocketDef(
label="Object",
),
"geo_nodes": sockets.BlenderGeoNodesSocketDef(
label="GeoNodes",
),
@ -50,7 +74,10 @@ class GeoNodesStructureNode(base.MaxwellSimTreeNode):
# Triangulate Object Mesh
bmesh_mesh = bmesh.new()
bmesh_mesh.from_mesh(bl_object.data)
bmesh_mesh.from_object(
bl_object,
bpy.context.evaluated_depsgraph_get(),
)
bmesh.ops.triangulate(bmesh_mesh, faces=bmesh_mesh.faces)
mesh = bpy.data.meshes.new(name="TriangulatedMesh")
@ -75,8 +102,13 @@ class GeoNodesStructureNode(base.MaxwellSimTreeNode):
####################
# - Update Function
####################
def free(self) -> None:
bl_socket = self.g_input_bl_socket("preview_target")
bl_socket.free()
def update_cb(self) -> None:
bl_object = self.compute_input("object")
bl_object = self.compute_input("preview_target")
if bl_object is None: return
geo_nodes = self.compute_input("geo_nodes")
@ -91,9 +123,12 @@ class GeoNodesStructureNode(base.MaxwellSimTreeNode):
):
if idx == 0: continue ## Always-on "Geometry" Input (from Object)
# Retrieve Input Socket
bl_socket = self.inputs[
interface_item.name
]
# Retrieve Linked/Unlinked Input Socket Value
if bl_socket.is_linked:
linked_bl_socket = bl_socket.links[0].from_socket
linked_bl_node = bl_socket.links[0].from_node
@ -103,13 +138,33 @@ class GeoNodesStructureNode(base.MaxwellSimTreeNode):
)
) ## What a bunch of spaghetti
else:
val = self.inputs[
interface_item.name
].default_value
val = bl_socket.default_value
# Retrieve Unit-System Corrected Modifier Value
bl_unit_system = self.compute_input("blender_unit_system")
socket_type = contracts.SocketType[
bl_socket.bl_idname.removesuffix("SocketType")
]
if socket_type in bl_unit_system:
unitless_val = spu.convert_to(
val,
bl_unit_system[socket_type],
) / bl_unit_system[socket_type]
else:
unitless_val = val
if isinstance(unitless_val, sp.matrices.MatrixBase):
unitless_val = tuple(
parse_scalar(scalar)
for scalar in unitless_val
)
else:
unitless_val = parse_scalar(unitless_val)
# Conservatively Set Differing Values
if bl_modifier[interface_item.identifier] != val:
bl_modifier[interface_item.identifier] = val
if bl_modifier[interface_item.identifier] != unitless_val:
bl_modifier[interface_item.identifier] = unitless_val
# Update DepGraph
bl_object.data.update()
@ -134,24 +189,78 @@ class GeoNodesStructureNode(base.MaxwellSimTreeNode):
self.inputs.remove(bl_socket)
# Query for Blender Object / Geo Nodes
bl_object = self.compute_input("object")
bl_object = self.compute_input("preview_target")
if bl_object is None: return
## TODO: Make object? Gray out geonodes if object not defined?
# Remove Existing GeoNodes Modifier
if GEONODES_MODIFIER_NAME in bl_object.modifiers:
modifier_to_remove = bl_object.modifiers[GEONODES_MODIFIER_NAME]
bl_object.modifiers.remove(modifier_to_remove)
# Retrieve GeoNodes Tree
geo_nodes = self.compute_input("geo_nodes")
if geo_nodes is None: return
# Add Non-Static Sockets from GeoNodes
for bl_socket_name, bl_socket in geo_nodes.interface.items_tree.items():
# For now, don't allow Geometry inputs.
if bl_socket.socket_type == "NodeSocketGeometry": continue
# Establish Dimensions of GeoNodes Input Sockets
if (
bl_socket.description.startswith("2D")
):
dimensions = 2
elif (
bl_socket.socket_type.startswith("NodeSocketVector")
or bl_socket.socket_type.startswith("NodeSocketColor")
or bl_socket.socket_type.startswith("NodeSocketRotation")
):
dimensions = 3
else:
dimensions = 1
# Choose Socket via. Description Hint (if exists)
if (
":" in bl_socket.description
and "(" in (desc_hint := bl_socket.description.split(":")[0])
and ")" in desc_hint
):
for tag in contracts.BLNodeSocket_to_SocketType_by_desc[
dimensions
]:
if desc_hint.startswith(tag):
self.inputs.new(
contracts.BLNodeSocket_to_SocketType[bl_socket.socket_type],
contracts.BLNodeSocket_to_SocketType_by_desc[
dimensions
][tag],
bl_socket_name,
)
if len([
(unit := _unit)
for _unit in contracts.SocketType_to_units[
contracts.SocketType[
self.inputs[bl_socket_name].bl_idname.removesuffix("SocketType")
]
]["values"].values()
if desc_hint[
desc_hint.find("(")+1 : desc_hint.find(")")
] == str(_unit)
]) > 0:
self.inputs[bl_socket_name].unit = unit
elif bl_socket.socket_type in contracts.BLNodeSocket_to_SocketType[
dimensions
]:
self.inputs.new(
contracts.BLNodeSocket_to_SocketType[
dimensions
][bl_socket.socket_type],
bl_socket_name,
)
# Create New GeoNodes Modifier
if GEONODES_MODIFIER_NAME not in bl_object.modifiers:
modifier = bl_object.modifiers.new(
@ -160,7 +269,27 @@ class GeoNodesStructureNode(base.MaxwellSimTreeNode):
)
modifier.node_group = geo_nodes
self.update()
# Set Default Values
for interface_item in geo_nodes.interface.items_tree.values():
if (
interface_item.name in self.inputs
and hasattr(interface_item, "default_value")
):
bl_socket = self.inputs[
interface_item.name
]
if hasattr(bl_socket, "use_units"):
bl_unit_system = self.compute_input("blender_unit_system")
socket_type = contracts.SocketType[
bl_socket.bl_idname.removesuffix("SocketType")
]
bl_socket.default_value = (
parse_bl_to_sp(interface_item.default_value)
* bl_unit_system[socket_type]
)
else:
bl_socket.default_value = parse_bl_to_sp(interface_item.default_value)

View File

@ -45,7 +45,10 @@ class ObjectStructureNode(base.MaxwellSimTreeNode):
# Triangulate Object Mesh
bmesh_mesh = bmesh.new()
bmesh_mesh.from_mesh(bl_object.data)
bmesh_mesh.from_object(
bl_object,
bpy.context.evaluated_depsgraph_get(),
)
bmesh.ops.triangulate(bmesh_mesh, faces=bmesh_mesh.faces)
mesh = bpy.data.meshes.new(name="TriangulatedMesh")
@ -62,7 +65,6 @@ class ObjectStructureNode(base.MaxwellSimTreeNode):
# Remove Temporary Mesh
bpy.data.meshes.remove(mesh)
print(vertices)
return td.Structure(
geometry=td.TriangleMesh.from_vertices_faces(vertices, faces),
medium=self.compute_input("medium")

View File

@ -61,6 +61,6 @@ BL_REGISTER = [
]
BL_NODES = {
contracts.NodeType.BoxStructure: (
contracts.NodeCategory.MAXWELLSIM_STRUCTURES
contracts.NodeCategory.MAXWELLSIM_STRUCTURES_PRIMITIVES
)
}

View File

@ -1,6 +1,72 @@
import tidy3d as td
import sympy as sp
import sympy.physics.units as spu
from .... import contracts
from .... import sockets
from ... import base
class CylinderStructureNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.CylinderStructure
bl_label = "Cylinder Structure"
#bl_icon = ...
####################
# - Sockets
####################
input_sockets = {
"medium": sockets.MaxwellMediumSocketDef(
label="Medium",
),
"center": sockets.PhysicalPoint3DSocketDef(
label="Center",
),
"radius": sockets.PhysicalLengthSocketDef(
label="Radius",
),
"height": sockets.PhysicalLengthSocketDef(
label="Height",
),
}
output_sockets = {
"structure": sockets.MaxwellStructureSocketDef(
label="Structure",
),
}
####################
# - Output Socket Computation
####################
@base.computes_output_socket("structure")
def compute_simulation(self: contracts.NodeTypeProtocol) -> td.Box:
medium = self.compute_input("medium")
_center = self.compute_input("center")
_radius = self.compute_input("radius")
_height = self.compute_input("height")
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
radius = spu.convert_to(_radius, spu.um) / spu.um
height = spu.convert_to(_height, spu.um) / spu.um
return td.Structure(
geometry=td.Cylinder(
radius=radius,
center=center,
length=height,
),
medium=medium,
)
####################
# - Blender Registration
####################
BL_REGISTER = []
BL_NODES = {}
BL_REGISTER = [
CylinderStructureNode,
]
BL_NODES = {
contracts.NodeType.CylinderStructure: (
contracts.NodeCategory.MAXWELLSIM_STRUCTURES_PRIMITIVES
)
}

View File

@ -1,6 +1,66 @@
import tidy3d as td
import sympy as sp
import sympy.physics.units as spu
from .... import contracts
from .... import sockets
from ... import base
class SphereStructureNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.SphereStructure
bl_label = "Sphere Structure"
#bl_icon = ...
####################
# - Sockets
####################
input_sockets = {
"medium": sockets.MaxwellMediumSocketDef(
label="Medium",
),
"center": sockets.PhysicalPoint3DSocketDef(
label="Center",
),
"radius": sockets.PhysicalLengthSocketDef(
label="Radius",
),
}
output_sockets = {
"structure": sockets.MaxwellStructureSocketDef(
label="Structure",
),
}
####################
# - Output Socket Computation
####################
@base.computes_output_socket("structure")
def compute_simulation(self: contracts.NodeTypeProtocol) -> td.Box:
medium = self.compute_input("medium")
_center = self.compute_input("center")
_radius = self.compute_input("radius")
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
radius = spu.convert_to(_radius, spu.um) / spu.um
return td.Structure(
geometry=td.Sphere(
radius=radius,
center=center,
),
medium=medium,
)
####################
# - Blender Registration
####################
BL_REGISTER = []
BL_NODES = {}
BL_REGISTER = [
SphereStructureNode,
]
BL_NODES = {
contracts.NodeType.SphereStructure: (
contracts.NodeCategory.MAXWELLSIM_STRUCTURES_PRIMITIVES
)
}

View File

@ -1,11 +1,23 @@
from . import combine
#from . import separate
from . import math
from . import operations
from . import converter
BL_REGISTER = [
*combine.BL_REGISTER,
#*separate.BL_REGISTER,
*converter.BL_REGISTER,
*math.BL_REGISTER,
*operations.BL_REGISTER,
]
BL_NODES = {
**combine.BL_NODES,
#**separate.BL_NODES,
**converter.BL_NODES,
**math.BL_NODES,
**operations.BL_NODES,
}

View File

@ -0,0 +1,114 @@
import sympy as sp
import sympy.physics.units as spu
import scipy as sc
from ... import contracts
from ... import sockets
from .. import base
class CombineNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.Combine
bl_label = "Combine"
#bl_icon = ...
####################
# - Sockets
####################
input_sockets = {}
input_socket_sets = {
"real_3d_vector": {
f"x_{i}": sockets.RealNumberSocketDef(
label=f"x_{i}"
)
for i in range(3)
},
"point_3d": {
axis: sockets.PhysicalLengthSocketDef(
label=axis
)
for i, axis in zip(
range(3),
["x", "y", "z"]
)
},
"size_3d": {
axis_key: sockets.PhysicalLengthSocketDef(
label=axis_label
)
for i, axis_key, axis_label in zip(
range(3),
["x_size", "y_size", "z_size"],
["X Size", "Y Size", "Z Size"],
)
},
}
output_sockets = {}
output_socket_sets = {
"real_3d_vector": {
"real_3d_vector": sockets.Real3DVectorSocketDef(
label="Real 3D Vector",
),
},
"point_3d": {
"point_3d": sockets.PhysicalPoint3DSocketDef(
label="3D Point",
),
},
"size_3d": {
"size_3d": sockets.PhysicalSize3DSocketDef(
label="3D Size",
),
},
}
####################
# - Output Socket Computation
####################
@base.computes_output_socket("real_3d_vector")
def compute_real_3d_vector(self: contracts.NodeTypeProtocol) -> sp.Expr:
x1, x2, x3 = [
self.compute_input(f"x_{i}")
for i in range(3)
]
return (x1, x2, x3)
@base.computes_output_socket("point_3d")
def compute_point_3d(self: contracts.NodeTypeProtocol) -> sp.Expr:
x, y, z = [
self.compute_input(axis)
#spu.convert_to(
# self.compute_input(axis),
# spu.meter,
#) / spu.meter
for axis in ["x", "y", "z"]
]
return sp.Matrix([x, y, z])# * spu.meter
@base.computes_output_socket("size_3d")
def compute_size_3d(self: contracts.NodeTypeProtocol) -> sp.Expr:
x_size, y_size, z_size = [
self.compute_input(axis)
#spu.convert_to(
# self.compute_input(axis),
# spu.meter,
#) / spu.meter
for axis in ["x_size", "y_size", "z_size"]
]
return sp.Matrix([x_size, y_size, z_size])# * spu.meter
####################
# - Blender Registration
####################
BL_REGISTER = [
CombineNode,
]
BL_NODES = {
contracts.NodeType.Combine: (
contracts.NodeCategory.MAXWELLSIM_UTILITIES
)
}

View File

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

View File

@ -0,0 +1,82 @@
import tidy3d as td
import sympy as sp
import sympy.physics.units as spu
import scipy as sc
from .... import contracts
from .... import sockets
from ... import base
class WaveConverterNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.WaveConverter
bl_label = "Wave Converter"
#bl_icon = ...
####################
# - Sockets
####################
input_sockets = {}
input_socket_sets = {
"freq_to_vacwl": {
"freq": sockets.PhysicalFreqSocketDef(
label="Freq",
),
},
"vacwl_to_freq": {
"vacwl": sockets.PhysicalVacWLSocketDef(
label="Vac WL",
),
},
}
output_sockets = {}
output_socket_sets = {
"freq_to_vacwl": {
"vacwl": sockets.PhysicalVacWLSocketDef(
label="Vac WL",
),
},
"vacwl_to_freq": {
"freq": sockets.PhysicalFreqSocketDef(
label="Freq",
),
},
}
####################
# - Output Socket Computation
####################
@base.computes_output_socket("freq")
def compute_freq(self: contracts.NodeTypeProtocol) -> sp.Expr:
vac_speed_of_light = sc.constants.speed_of_light * spu.meter/spu.second
vacwl = self.compute_input("vacwl")
return spu.convert_to(
vac_speed_of_light / vacwl,
spu.hertz,
)
@base.computes_output_socket("vacwl")
def compute_vacwl(self: contracts.NodeTypeProtocol) -> sp.Expr:
vac_speed_of_light = sc.constants.speed_of_light * spu.meter/spu.second
freq = self.compute_input("freq")
return spu.convert_to(
vac_speed_of_light / freq,
spu.meter,
)
####################
# - Blender Registration
####################
BL_REGISTER = [
WaveConverterNode,
]
BL_NODES = {
contracts.NodeType.WaveConverter: (
contracts.NodeCategory.MAXWELLSIM_UTILITIES_CONVERTERS
)
}

View File

@ -0,0 +1,82 @@
import tidy3d as td
import sympy as sp
import sympy.physics.units as spu
import scipy as sc
from .... import contracts
from .... import sockets
from ... import base
class WaveConverterNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.WaveConverter
bl_label = "Wave Converter"
#bl_icon = ...
####################
# - Sockets
####################
input_sockets = {}
input_socket_sets = {
"freq_to_vacwl": {
"freq": sockets.PhysicalFreqSocketDef(
label="Freq",
),
},
"vacwl_to_freq": {
"vacwl": sockets.PhysicalVacWLSocketDef(
label="Vac WL",
),
},
}
output_sockets = {}
output_socket_sets = {
"freq_to_vacwl": {
"vacwl": sockets.PhysicalVacWLSocketDef(
label="Vac WL",
),
},
"vacwl_to_freq": {
"freq": sockets.PhysicalFreqSocketDef(
label="Freq",
),
},
}
####################
# - Output Socket Computation
####################
@base.computes_output_socket("freq")
def compute_freq(self: contracts.NodeTypeProtocol) -> sp.Expr:
vac_speed_of_light = sc.constants.speed_of_light * spu.meter/spu.second
vacwl = self.compute_input("vacwl")
return spu.convert_to(
vac_speed_of_light / vacwl,
spu.hertz,
)
@base.computes_output_socket("vacwl")
def compute_vacwl(self: contracts.NodeTypeProtocol) -> sp.Expr:
vac_speed_of_light = sc.constants.speed_of_light * spu.meter/spu.second
freq = self.compute_input("freq")
return spu.convert_to(
vac_speed_of_light / freq,
spu.meter,
)
####################
# - Blender Registration
####################
BL_REGISTER = [
WaveConverterNode,
]
BL_NODES = {
contracts.NodeType.WaveConverter: (
contracts.NodeCategory.MAXWELLSIM_UTILITIES_CONVERTERS
)
}

View File

@ -17,6 +17,7 @@ Real3DVectorSocketDef = vector.Real3DVectorSocketDef
Complex3DVectorSocketDef = vector.Complex3DVectorSocketDef
from . import physical
PhysicalUnitSystemSocketDef = physical.PhysicalUnitSystemSocketDef
PhysicalTimeSocketDef = physical.PhysicalTimeSocketDef
PhysicalAngleSocketDef = physical.PhysicalAngleSocketDef
PhysicalLengthSocketDef = physical.PhysicalLengthSocketDef
@ -30,6 +31,7 @@ PhysicalAccelScalarSocketDef = physical.PhysicalAccelScalarSocketDef
PhysicalForceScalarSocketDef = physical.PhysicalForceScalarSocketDef
PhysicalPolSocketDef = physical.PhysicalPolSocketDef
PhysicalFreqSocketDef = physical.PhysicalFreqSocketDef
PhysicalVacWLSocketDef = physical.PhysicalVacWLSocketDef
PhysicalSpecRelPermDistSocketDef = physical.PhysicalSpecRelPermDistSocketDef
PhysicalSpecPowerDistSocketDef = physical.PhysicalSpecPowerDistSocketDef
@ -40,6 +42,7 @@ BlenderImageSocketDef = blender.BlenderImageSocketDef
BlenderVolumeSocketDef = blender.BlenderVolumeSocketDef
BlenderGeoNodesSocketDef = blender.BlenderGeoNodesSocketDef
BlenderTextSocketDef = blender.BlenderTextSocketDef
BlenderPreviewTargetSocketDef = blender.BlenderPreviewTargetSocketDef
from . import maxwell
MaxwellBoundBoxSocketDef = maxwell.MaxwellBoundBoxSocketDef

View File

@ -235,4 +235,23 @@ class BLSocket(bpy.types.NodeSocket):
node: bpy.types.Node,
text: str,
) -> None:
layout.label(text=text)
col = layout.column()
row_col = col.row()
row_col.alignment = "RIGHT"
# Row: Label & Preview Toggle
if hasattr(self, "draw_preview"):
row_col.prop(
self,
"preview_active",
toggle=True,
text="",
icon="SEQ_PREVIEW",
)
row_col.label(text=text)
# Row: Preview (in box)
if hasattr(self, "draw_preview"):
if self.preview_active:
col_box = col.box()
self.draw_preview(col_box)

View File

@ -13,6 +13,8 @@ from . import text_socket
BlenderGeoNodesSocketDef = geonodes_socket.BlenderGeoNodesSocketDef
BlenderTextSocketDef = text_socket.BlenderTextSocketDef
from . import target_socket
BlenderPreviewTargetSocketDef = target_socket.BlenderPreviewTargetSocketDef
BL_REGISTER = [
*object_socket.BL_REGISTER,
@ -20,6 +22,7 @@ BL_REGISTER = [
*image_socket.BL_REGISTER,
*volume_socket.BL_REGISTER,
*target_socket.BL_REGISTER,
*geonodes_socket.BL_REGISTER,
*text_socket.BL_REGISTER,

View File

@ -6,6 +6,19 @@ import pydantic as pyd
from .. import base
from ... import contracts
####################
# - Operators
####################
class BlenderMaxwellResetGeoNodesSocket(bpy.types.Operator):
bl_idname = "blender_maxwell.reset_geo_nodes_socket"
bl_label = "Reset GeoNodes Socket"
def execute(self, context):
context.socket.update_geonodes_node()
return {'FINISHED'}
####################
# - Blender Socket
####################
@ -33,6 +46,18 @@ class BlenderGeoNodesBLSocket(base.BLSocket):
update=(lambda self, context: self.update_geonodes_node()),
)
####################
# - UI
####################
def draw_label_row(self, label_col_row, text):
label_col_row.label(text=text)
if self.raw_value:
label_col_row.operator(
"blender_maxwell.reset_geo_nodes_socket",
text="",
icon="FILE_REFRESH",
)
####################
# - Default Value
####################
@ -58,5 +83,6 @@ class BlenderGeoNodesSocketDef(pyd.BaseModel):
# - Blender Registration
####################
BL_REGISTER = [
BlenderGeoNodesBLSocket
BlenderMaxwellResetGeoNodesSocket,
BlenderGeoNodesBLSocket,
]

View File

@ -6,6 +6,29 @@ import pydantic as pyd
from .. import base
from ... import contracts
####################
# - Blender Socket
####################
class BlenderMaxwellCreateAndAssignBLObject(bpy.types.Operator):
bl_idname = "blender_maxwell.create_and_assign_bl_object"
bl_label = "Create and Assign BL Object"
def execute(self, context):
mesh = bpy.data.meshes.new("GenMesh")
new_bl_object = bpy.data.objects.new("GenObj", mesh)
context.collection.objects.link(new_bl_object)
node = context.node
for bl_socket_name, bl_socket in node.inputs.items():
if isinstance(bl_socket, BlenderObjectBLSocket):
bl_socket.default_value = new_bl_object
if hasattr(node, "update_sockets_from_geonodes"):
node.update_sockets_from_geonodes()
return {'FINISHED'}
####################
# - Blender Socket
####################
@ -23,6 +46,17 @@ class BlenderObjectBLSocket(base.BLSocket):
update=(lambda self, context: self.trigger_updates()),
)
####################
# - UI
####################
def draw_label_row(self, label_col_row, text):
label_col_row.label(text=text)
label_col_row.operator(
"blender_maxwell.create_and_assign_bl_object",
text="",
icon="ADD",
)
####################
# - Default Value
####################
@ -48,5 +82,6 @@ class BlenderObjectSocketDef(pyd.BaseModel):
# - Blender Registration
####################
BL_REGISTER = [
BlenderObjectBLSocket
BlenderMaxwellCreateAndAssignBLObject,
BlenderObjectBLSocket,
]

View File

@ -0,0 +1,268 @@
import typing as typ
import bpy
import sympy as sp
import pydantic as pyd
from .. import base
from ... import contracts
def mk_and_assign_target_bl_obj(bl_socket, node, node_tree):
# Create Mesh and Object
mesh = bpy.data.meshes.new("Mesh" + bl_socket.node.name)
new_bl_object = bpy.data.objects.new(bl_socket.node.name, mesh)
# Create Preview Collection and Object
if bl_socket.show_preview:
#if not node_tree.preview_collection:
# new_collection = bpy.data.collections.new("BLMaxwellPreview")
# node_tree.preview_collection = new_collection
#
# bpy.context.scene.collection.children.link(new_collection)
node_tree.preview_collection.objects.link(new_bl_object)
# Create Non-Preview Collection and Object
else:
#if not node_tree.non_preview_collection:
# new_collection = bpy.data.collections.new("BLMaxwellNonPreview")
# node_tree.non_preview_collection = new_collection
#
# bpy.context.scene.collection.children.link(new_collection)
node_tree.non_preview_collection.objects.link(new_bl_object)
bl_socket.local_target_object = new_bl_object
if hasattr(node, "update_sockets_from_geonodes"):
node.update_sockets_from_geonodes()
class BlenderMaxwellCreateAndAssignTargetBLObject(bpy.types.Operator):
bl_idname = "blender_maxwell.create_and_assign_target_bl_object"
bl_label = "Create and Assign Target BL Object"
def execute(self, context):
bl_socket = context.socket
node = bl_socket.node
node_tree = node.id_data
mk_and_assign_target_bl_obj(bl_socket, node, node_tree)
return {'FINISHED'}
####################
# - Blender Socket
####################
class BlenderPreviewTargetBLSocket(base.BLSocket):
socket_type = contracts.SocketType.BlenderPreviewTarget
bl_label = "BlenderPreviewTarget"
####################
# - Properties
####################
show_preview: bpy.props.BoolProperty(
name="Target Object Included in Preview",
description="Whether or not Blender will preview the target object",
default=True,
update=(lambda self, context: self.update_preview()),
)
show_definition: bpy.props.BoolProperty(
name="Show Unit System Definition",
description="Toggle to show unit system definition",
default=False,
update=(lambda self, context: self.trigger_updates()),
)
target_object_pinned: bpy.props.BoolProperty(
name="Target Object Pinned",
description="Whether or not Blender will manage the target object",
default=True,
)
preview_collection_pinned: bpy.props.BoolProperty(
name="Global Preview Collection Pinned",
description="Whether or not Blender will use the global preview collection",
default=True,
)
non_preview_collection_pinned: bpy.props.BoolProperty(
name="Global Non-Preview Collection Pinned",
description="Whether or not Blender will use the global non-preview collection",
default=True,
)
local_target_object: bpy.props.PointerProperty(
name="Local Target Blender Object",
description="Represents a Blender object to apply a preview to",
type=bpy.types.Object,
update=(lambda self, context: self.trigger_updates()),
)
local_preview_collection: bpy.props.PointerProperty(
name="Local Preview Collection",
description="Collection of Blender objects that will be previewed",
type=bpy.types.Collection,
update=(lambda self, context: self.trigger_updates())
)
local_non_preview_collection: bpy.props.PointerProperty(
name="Local Non-Preview Collection",
description="Collection of Blender objects that will NOT be previewed",
type=bpy.types.Collection,
update=(lambda self, context: self.trigger_updates())
)
####################
# - Methods
####################
def update_preview(self):
node_tree = self.node.id_data
# Target Object Pinned
if (
self.show_preview
and self.local_target_object
and self.target_object_pinned
):
node_tree.non_preview_collection.objects.unlink(self.local_target_object)
node_tree.preview_collection.objects.link(self.local_target_object)
elif (
not self.show_preview
and self.local_target_object
and self.target_object_pinned
):
node_tree.preview_collection.objects.unlink(self.local_target_object)
node_tree.non_preview_collection.objects.link(self.local_target_object)
# Target Object Not Pinned
if (
self.show_preview
and self.local_target_object
and not self.target_object_pinned
and self.local_target_object.name in (
node_tree.non_preview_collection.objects.keys()
)
):
node_tree.non_preview_collection.objects.unlink(self.local_target_object)
node_tree.preview_collection.objects.link(self.local_target_object)
elif (
not self.show_preview
and self.local_target_object
and not self.target_object_pinned
and self.local_target_object.name in (
node_tree.preview_collection.objects.keys()
)
):
node_tree.preview_collection.objects.unlink(self.local_target_object)
node_tree.non_preview_collection.objects.link(self.local_target_object)
self.trigger_updates()
####################
# - UI
####################
def draw_label_row(self, label_col_row: bpy.types.UILayout, text) -> None:
label_col_row.label(text=text)
label_col_row.prop(self, "show_preview", toggle=True, text="", icon="SEQ_PREVIEW")
label_col_row.prop(self, "show_definition", toggle=True, text="", icon="MOD_LENGTH")
def draw_value(self, col: bpy.types.UILayout) -> None:
node_tree = self.node.id_data
if self.show_definition:
col_row = col.row(align=True)
col_row.alignment = "EXPAND"
col_row.label(text="Target", icon="OBJECT_DATA")
col_row.prop(
self,
"target_object_pinned",
toggle=True,
icon="EVENT_A",
icon_only=True,
)
#col_row.operator(
# "blender_maxwell.create_and_assign_target_bl_object",
# text="",
# icon="ADD",
#)
col_row=col.row(align=True)
col_row.alignment = "EXPAND"
if not self.target_object_pinned:
col_row.prop(self, "local_target_object", text="")
# Non-Preview Collection
col_row=col.row(align=True)
col_row.alignment = "EXPAND"
col_row.label(text="Enabled", icon="COLLECTION_COLOR_04")
col_row.prop(
self,
"preview_collection_pinned",
toggle=True,
icon="PINNED",
icon_only=True,
)
col_row=col.row(align=True)
col_row.alignment = "EXPAND"
if not self.preview_collection_pinned:
col_row.prop(self, "local_preview_collection", text="")
# Non-Preview Collection
col_row=col.row(align=True)
col_row.alignment = "EXPAND"
col_row.label(text="Disabled", icon="COLLECTION_COLOR_01")
col_row.prop(
self,
"non_preview_collection_pinned",
toggle=True,
icon="PINNED",
icon_only=True,
)
col_row=col.row(align=True)
col_row.alignment = "EXPAND"
if not self.non_preview_collection_pinned:
col_row.prop(self, "local_non_preview_collection", text="")
####################
# - Default Value
####################
@property
def default_value(self) -> bpy.types.Object:
node_tree = self.node.id_data
if not self.local_target_object and self.target_object_pinned:
mk_and_assign_target_bl_obj(self, self.node, node_tree)
return self.local_target_object
return self.local_target_object
@default_value.setter
def default_value(self, value: typ.Any) -> None:
pass
####################
# - Cleanup
####################
def free(self) -> None:
if self.local_target_object:
bpy.data.meshes.remove(self.local_target_object.data, do_unlink=True)
####################
# - Socket Configuration
####################
class BlenderPreviewTargetSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.BlenderPreviewTarget
label: str
show_preview: bool = True
def init(self, bl_socket: BlenderPreviewTargetBLSocket) -> None:
pass
#bl_socket.show_preview = self.show_preview
####################
# - Blender Registration
####################
BL_REGISTER = [
BlenderMaxwellCreateAndAssignTargetBLObject,
BlenderPreviewTargetBLSocket,
]

View File

@ -7,6 +7,13 @@ import tidy3d as td
from .. import base
from ... import contracts
BOUND_FACE_ITEMS = [
("PML", "PML", "Perfectly matched layer"),
("PEC", "PEC", "Perfect electrical conductor"),
("PMC", "PMC", "Perfect magnetic conductor"),
("PERIODIC", "Periodic", "Infinitely periodic layer"),
]
class MaxwellBoundBoxBLSocket(base.BLSocket):
socket_type = contracts.SocketType.MaxwellBoundBox
bl_label = "Maxwell Bound Box"
@ -15,6 +22,72 @@ class MaxwellBoundBoxBLSocket(base.BLSocket):
td.BoundarySpec: {}
}
####################
# - Properties
####################
x_pos: bpy.props.EnumProperty(
name="+x Bound Face",
description="+x choice of default boundary face",
items=BOUND_FACE_ITEMS,
default="PML",
update=(lambda self, context: self.trigger_updates()),
)
x_neg: bpy.props.EnumProperty(
name="-x Bound Face",
description="-x choice of default boundary face",
items=BOUND_FACE_ITEMS,
default="PML",
update=(lambda self, context: self.trigger_updates()),
)
y_pos: bpy.props.EnumProperty(
name="+y Bound Face",
description="+y choice of default boundary face",
items=BOUND_FACE_ITEMS,
default="PML",
update=(lambda self, context: self.trigger_updates()),
)
y_neg: bpy.props.EnumProperty(
name="-y Bound Face",
description="-y choice of default boundary face",
items=BOUND_FACE_ITEMS,
default="PML",
update=(lambda self, context: self.trigger_updates()),
)
z_pos: bpy.props.EnumProperty(
name="+z Bound Face",
description="+z choice of default boundary face",
items=BOUND_FACE_ITEMS,
default="PML",
update=(lambda self, context: self.trigger_updates()),
)
z_neg: bpy.props.EnumProperty(
name="-z Bound Face",
description="-z choice of default boundary face",
items=BOUND_FACE_ITEMS,
default="PML",
update=(lambda self, context: self.trigger_updates()),
)
####################
# - UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.label(text="-/+ x")
col_row = col.row(align=True)
col_row.prop(self, "x_neg", text="")
col_row.prop(self, "x_pos", text="")
col.label(text="-/+ y")
col_row = col.row(align=True)
col_row.prop(self, "y_neg", text="")
col_row.prop(self, "y_pos", text="")
col.label(text="-/+ z")
col_row = col.row(align=True)
col_row.prop(self, "z_neg", text="")
col_row.prop(self, "z_pos", text="")
####################
# - Computation of Default Value
####################

View File

@ -11,16 +11,40 @@ class MaxwellBoundFaceBLSocket(base.BLSocket):
socket_type = contracts.SocketType.MaxwellBoundFace
bl_label = "Maxwell Bound Face"
compatible_types = {
td.BoundarySpec: {}
}
####################
# - Properties
####################
default_choice: bpy.props.EnumProperty(
name="Bound Face",
description="A choice of default boundary face",
items=[
("PML", "PML", "Perfectly matched layer"),
("PEC", "PEC", "Perfect electrical conductor"),
("PMC", "PMC", "Perfect magnetic conductor"),
("PERIODIC", "Periodic", "Infinitely periodic layer"),
],
default="PML",
update=(lambda self, context: self.trigger_updates()),
)
####################
# - UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col_row = col.row(align=True)
col_row.prop(self, "default_choice", text="")
####################
# - Computation of Default Value
####################
@property
def default_value(self) -> td.BoundarySpec:
return td.BoundarySpec()
return {
"PML": td.PML(num_layers=12),
"PEC": td.PECBoundary(),
"PMC": td.PMCBoundary(),
"PERIODIC": td.Periodic(),
}[self.default_choice]
@default_value.setter
def default_value(self, value: typ.Any) -> None:
@ -33,8 +57,10 @@ class MaxwellBoundFaceSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.MaxwellBoundFace
label: str
default_choice: str = "PML"
def init(self, bl_socket: MaxwellBoundFaceBLSocket) -> None:
pass
bl_socket.default_choice = self.default_choice
####################
# - Blender Registration

View File

@ -1,5 +1,6 @@
import typing as typ
import bpy
import pydantic as pyd
from .. import base
@ -12,16 +13,26 @@ class IntegerNumberBLSocket(base.BLSocket):
socket_type = contracts.SocketType.IntegerNumber
bl_label = "IntegerNumber"
####################
# - Properties
####################
raw_value: bpy.props.IntProperty(
name="Integer",
description="Represents an integer",
default=0,
update=(lambda self, context: self.trigger_updates()),
)
####################
# - Default Value
####################
@property
def default_value(self) -> None:
pass
return self.raw_value
@default_value.setter
def default_value(self, value: typ.Any) -> None:
pass
self.raw_value = int(value)
####################
# - Socket Configuration
@ -30,8 +41,10 @@ class IntegerNumberSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.IntegerNumber
label: str
default_value: int = 0
def init(self, bl_socket: IntegerNumberBLSocket) -> None:
pass
bl_socket.raw_value = self.default_value
####################
# - Blender Registration

View File

@ -1,3 +1,6 @@
from . import unit_system_socket
PhysicalUnitSystemSocketDef = unit_system_socket.PhysicalUnitSystemSocketDef
from . import time_socket
PhysicalTimeSocketDef = time_socket.PhysicalTimeSocketDef
@ -31,7 +34,9 @@ from . import pol_socket
PhysicalPolSocketDef = pol_socket.PhysicalPolSocketDef
from . import freq_socket
from . import vac_wl_socket
PhysicalFreqSocketDef = freq_socket.PhysicalFreqSocketDef
PhysicalVacWLSocketDef = vac_wl_socket.PhysicalVacWLSocketDef
from . import spec_rel_permit_dist_socket
from . import spec_power_dist_socket
@ -40,6 +45,8 @@ PhysicalSpecPowerDistSocketDef = spec_power_dist_socket.PhysicalSpecPowerDistSoc
BL_REGISTER = [
*unit_system_socket.BL_REGISTER,
*time_socket.BL_REGISTER,
*angle_socket.BL_REGISTER,
@ -61,6 +68,7 @@ BL_REGISTER = [
*pol_socket.BL_REGISTER,
*freq_socket.BL_REGISTER,
*vac_wl_socket.BL_REGISTER,
*spec_rel_permit_dist_socket.BL_REGISTER,
*spec_power_dist_socket.BL_REGISTER,
]

View File

@ -1,5 +1,6 @@
import typing as typ
import bpy
import pydantic as pyd
from .. import base
@ -12,12 +13,44 @@ class PhysicalPolBLSocket(base.BLSocket):
socket_type = contracts.SocketType.PhysicalPol
bl_label = "PhysicalPol"
####################
# - Properties
####################
default_choice: bpy.props.EnumProperty(
name="Bound Face",
description="A choice of default boundary face",
items=[
("EX", "Ex", "Linear x-pol of E field"),
("EY", "Ey", "Linear y-pol of E field"),
("EZ", "Ez", "Linear z-pol of E field"),
("HX", "Hx", "Linear x-pol of H field"),
("HY", "Hy", "Linear x-pol of H field"),
("HZ", "Hz", "Linear x-pol of H field"),
],
default="EX",
update=(lambda self, context: self.trigger_updates()),
)
####################
# - UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col_row = col.row(align=True)
col_row.prop(self, "default_choice", text="")
####################
# - Default Value
####################
@property
def default_value(self) -> None:
pass
def default_value(self) -> str:
return {
"EX": "Ex",
"EY": "Ey",
"EZ": "Ez",
"HX": "Hx",
"HY": "Hy",
"HZ": "Hz",
}[self.default_choice]
@default_value.setter
def default_value(self, value: typ.Any) -> None:

View File

@ -13,17 +13,6 @@ class PhysicalSize3DBLSocket(base.BLSocket):
bl_label = "Physical Volume"
use_units = True
compatible_types = {
sp.Expr: {
lambda self, v: v.is_real,
lambda self, v: len(v.free_symbols) == 0,
lambda self, v: any(
contracts.is_exactly_expressed_as_unit(v, unit)
for unit in self.units.values()
)
},
}
####################
# - Properties
####################

View File

@ -0,0 +1,296 @@
import typing as typ
import bpy
import sympy as sp
import pydantic as pyd
from .. import base
from ... import contracts
def contract_units_to_items(socket_type):
return [
(
unit_key,
str(unit),
f"{socket_type}-compatible unit",
)
for unit_key, unit in contracts.SocketType_to_units[
socket_type
]["values"].items()
]
def default_unit_key_for(socket_type):
return contracts.SocketType_to_units[
socket_type
]["default"]
####################
# - Blender Socket
####################
class PhysicalUnitSystemBLSocket(base.BLSocket):
socket_type = contracts.SocketType.PhysicalUnitSystem
bl_label = "PhysicalUnitSystem"
####################
# - Properties
####################
show_definition: bpy.props.BoolProperty(
name="Show Unit System Definition",
description="Toggle to show unit system definition",
default=False,
update=(lambda self, context: self.trigger_updates()),
)
unit_time: bpy.props.EnumProperty(
name="Time Unit",
description="Unit of time",
items=contract_units_to_items(contracts.SocketType.PhysicalTime),
default=default_unit_key_for(contracts.SocketType.PhysicalTime),
update=(lambda self, context: self.trigger_updates()),
)
unit_angle: bpy.props.EnumProperty(
name="Angle Unit",
description="Unit of angle",
items=contract_units_to_items(contracts.SocketType.PhysicalAngle),
default=default_unit_key_for(contracts.SocketType.PhysicalAngle),
update=(lambda self, context: self.trigger_updates()),
)
unit_length: bpy.props.EnumProperty(
name="Length Unit",
description="Unit of length",
items=contract_units_to_items(contracts.SocketType.PhysicalLength),
default=default_unit_key_for(contracts.SocketType.PhysicalLength),
update=(lambda self, context: self.trigger_updates()),
)
unit_area: bpy.props.EnumProperty(
name="Area Unit",
description="Unit of area",
items=contract_units_to_items(contracts.SocketType.PhysicalArea),
default=default_unit_key_for(contracts.SocketType.PhysicalArea),
update=(lambda self, context: self.trigger_updates()),
)
unit_volume: bpy.props.EnumProperty(
name="Volume Unit",
description="Unit of time",
items=contract_units_to_items(contracts.SocketType.PhysicalVolume),
default=default_unit_key_for(contracts.SocketType.PhysicalVolume),
update=(lambda self, context: self.trigger_updates()),
)
unit_point_2d: bpy.props.EnumProperty(
name="Point2D Unit",
description="Unit of 2D points",
items=contract_units_to_items(contracts.SocketType.PhysicalPoint2D),
default=default_unit_key_for(contracts.SocketType.PhysicalPoint2D),
update=(lambda self, context: self.trigger_updates()),
)
unit_point_3d: bpy.props.EnumProperty(
name="Point3D Unit",
description="Unit of 3D points",
items=contract_units_to_items(contracts.SocketType.PhysicalPoint3D),
default=default_unit_key_for(contracts.SocketType.PhysicalPoint3D),
update=(lambda self, context: self.trigger_updates()),
)
unit_size_2d: bpy.props.EnumProperty(
name="Size2D Unit",
description="Unit of 2D sizes",
items=contract_units_to_items(contracts.SocketType.PhysicalSize2D),
default=default_unit_key_for(contracts.SocketType.PhysicalSize2D),
update=(lambda self, context: self.trigger_updates()),
)
unit_size_3d: bpy.props.EnumProperty(
name="Size3D Unit",
description="Unit of 3D sizes",
items=contract_units_to_items(contracts.SocketType.PhysicalSize3D),
default=default_unit_key_for(contracts.SocketType.PhysicalSize3D),
update=(lambda self, context: self.trigger_updates()),
)
unit_mass: bpy.props.EnumProperty(
name="Mass Unit",
description="Unit of mass",
items=contract_units_to_items(contracts.SocketType.PhysicalMass),
default=default_unit_key_for(contracts.SocketType.PhysicalMass),
update=(lambda self, context: self.trigger_updates()),
)
unit_speed: bpy.props.EnumProperty(
name="Speed Unit",
description="Unit of speed",
items=contract_units_to_items(contracts.SocketType.PhysicalSpeed),
default=default_unit_key_for(contracts.SocketType.PhysicalSpeed),
update=(lambda self, context: self.trigger_updates()),
)
unit_accel_scalar: bpy.props.EnumProperty(
name="Accel Unit",
description="Unit of acceleration",
items=contract_units_to_items(contracts.SocketType.PhysicalAccelScalar),
default=default_unit_key_for(contracts.SocketType.PhysicalAccelScalar),
update=(lambda self, context: self.trigger_updates()),
)
unit_force_scalar: bpy.props.EnumProperty(
name="Force Scalar Unit",
description="Unit of scalar force",
items=contract_units_to_items(contracts.SocketType.PhysicalForceScalar),
default=default_unit_key_for(contracts.SocketType.PhysicalForceScalar),
update=(lambda self, context: self.trigger_updates()),
)
unit_accel_3d_vector: bpy.props.EnumProperty(
name="Accel3D Unit",
description="Unit of 3D vector acceleration",
items=contract_units_to_items(contracts.SocketType.PhysicalAccel3DVector),
default=default_unit_key_for(contracts.SocketType.PhysicalAccel3DVector),
update=(lambda self, context: self.trigger_updates()),
)
unit_force_3d_vector: bpy.props.EnumProperty(
name="Force3D Unit",
description="Unit of 3D vector force",
items=contract_units_to_items(contracts.SocketType.PhysicalForce3DVector),
default=default_unit_key_for(contracts.SocketType.PhysicalForce3DVector),
update=(lambda self, context: self.trigger_updates()),
)
unit_freq: bpy.props.EnumProperty(
name="Freq Unit",
description="Unit of frequency",
items=contract_units_to_items(contracts.SocketType.PhysicalFreq),
default=default_unit_key_for(contracts.SocketType.PhysicalFreq),
update=(lambda self, context: self.trigger_updates()),
)
unit_vac_wl: bpy.props.EnumProperty(
name="VacWL Unit",
description="Unit of vacuum wavelength",
items=contract_units_to_items(contracts.SocketType.PhysicalVacWL),
default=default_unit_key_for(contracts.SocketType.PhysicalVacWL),
update=(lambda self, context: self.trigger_updates()),
)
####################
# - UI
####################
def draw_label_row(self, label_col_row: bpy.types.UILayout, text) -> None:
label_col_row.label(text=text)
label_col_row.prop(self, "show_definition", toggle=True, text="", icon="MOD_LENGTH")
def draw_value(self, col: bpy.types.UILayout) -> None:
if self.show_definition:
col_row=col.row(align=True)
col_row.alignment = "EXPAND"
col_row.prop(self, "unit_time", text="")
col_row.prop(self, "unit_angle", text="")
col_row=col.row(align=True)
col_row.alignment = "EXPAND"
col_row.prop(self, "unit_length", text="")
col_row.prop(self, "unit_area", text="")
col_row.prop(self, "unit_volume", text="")
col_row=col.row(align=True)
col_row.alignment = "EXPAND"
col_row.label(text="Point")
col_row.prop(self, "unit_point_2d", text="")
col_row.prop(self, "unit_point_3d", text="")
col_row=col.row(align=True)
col_row.alignment = "EXPAND"
col_row.label(text="Size")
col_row.prop(self, "unit_size_2d", text="")
col_row.prop(self, "unit_size_3d", text="")
col_row=col.row(align=True)
col_row.alignment = "EXPAND"
col_row.label(text="Mass")
col_row.prop(self, "unit_mass", text="")
col_row=col.row(align=True)
col_row.alignment = "EXPAND"
col_row.label(text="Vel")
col_row.prop(self, "unit_speed", text="")
#col_row.prop(self, "unit_vel_2d_vector", text="")
#col_row.prop(self, "unit_vel_3d_vector", text="")
col_row=col.row(align=True)
col_row.label(text="Accel")
col_row.prop(self, "unit_accel_scalar", text="")
#col_row.prop(self, "unit_accel_2d_vector", text="")
col_row.prop(self, "unit_accel_3d_vector", text="")
col_row=col.row(align=True)
col_row.label(text="Force")
col_row.prop(self, "unit_force_scalar", text="")
#col_row.prop(self, "unit_force_2d_vector", text="")
col_row.prop(self, "unit_force_3d_vector", text="")
col_row=col.row(align=True)
col_row.alignment = "EXPAND"
col_row.label(text="Freq")
col_row.prop(self, "unit_freq", text="")
col_row=col.row(align=True)
col_row.alignment = "EXPAND"
col_row.label(text="Vac WL")
col_row.prop(self, "unit_vac_wl", text="")
####################
# - Default Value
####################
@property
def default_value(self) -> sp.Expr:
ST = contracts.SocketType
SM = lambda socket_type: contracts.SocketType_to_units[
socket_type
]["values"]
return {
socket_type: SM(socket_type)[socket_unit_prop]
for socket_type, socket_unit_prop in [
(ST.PhysicalTime, self.unit_time),
(ST.PhysicalAngle, self.unit_angle),
(ST.PhysicalLength, self.unit_length),
(ST.PhysicalArea, self.unit_area),
(ST.PhysicalVolume, self.unit_volume),
(ST.PhysicalPoint2D, self.unit_point_2d),
(ST.PhysicalPoint3D, self.unit_point_3d),
(ST.PhysicalSize2D, self.unit_size_2d),
(ST.PhysicalSize3D, self.unit_size_3d),
(ST.PhysicalMass, self.unit_mass),
(ST.PhysicalSpeed, self.unit_speed),
(ST.PhysicalAccelScalar, self.unit_accel_scalar),
(ST.PhysicalForceScalar, self.unit_force_scalar),
(ST.PhysicalAccel3DVector, self.unit_accel_3d_vector),
(ST.PhysicalForce3DVector, self.unit_force_3d_vector),
(ST.PhysicalFreq, self.unit_freq),
(ST.PhysicalVacWL, self.unit_vac_wl),
]
}
@default_value.setter
def default_value(self, value: typ.Any) -> None:
pass
####################
# - Socket Configuration
####################
class PhysicalUnitSystemSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.PhysicalUnitSystem
label: str
show_by_default: bool = False
def init(self, bl_socket: PhysicalUnitSystemBLSocket) -> None:
bl_socket.show_definition = self.show_by_default
####################
# - Blender Registration
####################
BL_REGISTER = [
PhysicalUnitSystemBLSocket,
]

View File

@ -0,0 +1,54 @@
import typing as typ
import bpy
import pydantic as pyd
from .. import base
from ... import contracts
####################
# - Blender Socket
####################
class PhysicalVacWLBLSocket(base.BLSocket):
socket_type = contracts.SocketType.PhysicalVacWL
bl_label = "PhysicalVacWL"
use_units = True
####################
# - Properties
####################
raw_value: bpy.props.FloatProperty(
name="Unitless Vacuum Wavelength",
description="Represents the unitless part of the vacuum wavelength",
default=0.0,
precision=6,
update=(lambda self, context: self.trigger_updates()),
)
####################
# - Default Value
####################
@property
def default_value(self) -> None:
return self.raw_value * self.unit
@default_value.setter
def default_value(self, value: typ.Any) -> None:
self.raw_value = self.value_as_unit(value)
####################
# - Socket Configuration
####################
class PhysicalVacWLSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.PhysicalVacWL
label: str
def init(self, bl_socket: PhysicalVacWLBLSocket) -> None:
pass
####################
# - Blender Registration
####################
BL_REGISTER = [
PhysicalVacWLBLSocket,
]

View File

@ -1,5 +1,7 @@
import typing as typ
import bpy
import sympy as sp
import pydantic as pyd
from .. import base
@ -13,15 +15,27 @@ class Real2DVectorBLSocket(base.BLSocket):
bl_label = "Real2DVector"
####################
# - Default Value
# - Properties
####################
raw_value: bpy.props.FloatVectorProperty(
name="Unitless 2D Vector (global coordinate system)",
description="Represents a real 2D (coordinate) vector",
size=2,
default=(0.0, 0.0),
precision=4,
update=(lambda self, context: self.trigger_updates()),
)
####################
# - Computation of Default Value
####################
@property
def default_value(self) -> None:
pass
def default_value(self) -> sp.Expr:
return tuple(self.raw_value)
@default_value.setter
def default_value(self, value: typ.Any) -> None:
pass
self.raw_value = tuple(value)
####################
# - Socket Configuration

View File

@ -1,5 +1,7 @@
import typing as typ
import bpy
import sympy as sp
import pydantic as pyd
from .. import base
@ -13,15 +15,27 @@ class Real3DVectorBLSocket(base.BLSocket):
bl_label = "Real3DVector"
####################
# - Default Value
# - Properties
####################
raw_value: bpy.props.FloatVectorProperty(
name="Unitless 3D Vector (global coordinate system)",
description="Represents a real 3D (coordinate) vector",
size=3,
default=(0.0, 0.0, 0.0),
precision=4,
update=(lambda self, context: self.trigger_updates()),
)
####################
# - Computation of Default Value
####################
@property
def default_value(self) -> None:
pass
def default_value(self) -> sp.Expr:
return tuple(self.raw_value)
@default_value.setter
def default_value(self, value: typ.Any) -> None:
pass
self.raw_value = tuple(value)
####################
# - Socket Configuration

BIN
code/demo.blend (Stored with Git LFS)

Binary file not shown.