feat: More sockets, nodes, fixes.

We're starting to have a very advanced socket-based language
for defining nodes. It's very practical already.

The priorities are
1. All the nodes!
2. Sockets, including default values, as needed.
3. Library constants, mediums.
4. Output socket previews w/geonodes, geonodes structures.
5. Utilities including arrays (and selected array multi-input)

The code is still very spaghetti. This is very much still the
"first, make it run" part of the system design.
blender-plugin-mvp
Sofus Albert Høgsbro Rose 2024-02-19 18:36:16 +01:00
parent 586d6fa74b
commit 7344913c0e
Signed by: so-rose
GPG Key ID: AD901CB0F3701434
31 changed files with 753 additions and 206 deletions

View File

@ -79,6 +79,7 @@ class TreeType(BlenderTypeEnum):
class SocketType(BlenderTypeEnum):
# Base
Any = enum.auto()
Bool = enum.auto()
Text = enum.auto()
FilePath = enum.auto()
@ -104,11 +105,19 @@ class SocketType(BlenderTypeEnum):
PhysicalArea = enum.auto()
PhysicalVolume = enum.auto()
PhysicalPoint2D = enum.auto()
PhysicalPoint3D = enum.auto()
PhysicalSize2D = enum.auto()
PhysicalSize3D = enum.auto()
PhysicalMass = enum.auto()
PhysicalSpeed = enum.auto()
PhysicalAccel = enum.auto()
PhysicalForce = enum.auto()
PhysicalAccelScalar = enum.auto()
PhysicalForceScalar = enum.auto()
PhysicalAccel3DVector = enum.auto()
PhysicalForce3DVector = enum.auto()
PhysicalPol = enum.auto()
@ -220,6 +229,72 @@ SocketType_to_units = {
},
},
SocketType.PhysicalPoint2D: {
"default": "UM",
"values": {
"PM": spu.picometer,
"A": spu.angstrom,
"NM": spu.nanometer,
"UM": spu.micrometer,
"MM": spu.millimeter,
"CM": spu.centimeter,
"M": spu.meter,
"INCH": spu.inch,
"FOOT": spu.foot,
"YARD": spu.yard,
"MILE": spu.mile,
},
},
SocketType.PhysicalPoint3D: {
"default": "UM",
"values": {
"PM": spu.picometer,
"A": spu.angstrom,
"NM": spu.nanometer,
"UM": spu.micrometer,
"MM": spu.millimeter,
"CM": spu.centimeter,
"M": spu.meter,
"INCH": spu.inch,
"FOOT": spu.foot,
"YARD": spu.yard,
"MILE": spu.mile,
},
},
SocketType.PhysicalSize2D: {
"default": "UM",
"values": {
"PM": spu.picometer,
"A": spu.angstrom,
"NM": spu.nanometer,
"UM": spu.micrometer,
"MM": spu.millimeter,
"CM": spu.centimeter,
"M": spu.meter,
"INCH": spu.inch,
"FOOT": spu.foot,
"YARD": spu.yard,
"MILE": spu.mile,
},
},
SocketType.PhysicalSize3D: {
"default": "UM",
"values": {
"PM": spu.picometer,
"A": spu.angstrom,
"NM": spu.nanometer,
"UM": spu.micrometer,
"MM": spu.millimeter,
"CM": spu.centimeter,
"M": spu.meter,
"INCH": spu.inch,
"FOOT": spu.foot,
"YARD": spu.yard,
"MILE": spu.mile,
},
},
SocketType.PhysicalMass: {
"default": "UG",
"values": {
@ -247,7 +322,7 @@ SocketType_to_units = {
"MI_H": spu.mile / spu.hour,
},
},
SocketType.PhysicalAccel: {
SocketType.PhysicalAccelScalar: {
"default": "UM_S_SQ",
"values": {
"PM_S_SQ": spu.picometer / spu.second**2,
@ -259,7 +334,29 @@ SocketType_to_units = {
"FT_S_SQ": spu.feet / spu.second**2,
},
},
SocketType.PhysicalForce: {
SocketType.PhysicalForceScalar: {
"default": "UNEWT",
"values": {
"KG_M_S_SQ": spu.kg * spu.m/spu.second**2,
"NNEWT": spuex.nanonewton,
"UNEWT": spuex.micronewton,
"MNEWT": spuex.millinewton,
"NEWT": spu.newton,
},
},
SocketType.PhysicalAccel3DVector: {
"default": "UM_S_SQ",
"values": {
"PM_S_SQ": spu.picometer / spu.second**2,
"NM_S_SQ": spu.nanometer / spu.second**2,
"UM_S_SQ": spu.micrometer / spu.second**2,
"MM_S_SQ": spu.millimeter / spu.second**2,
"M_S_SQ": spu.meter / spu.second**2,
"KM_S_SQ": spu.kilometer / spu.second**2,
"FT_S_SQ": spu.feet / spu.second**2,
},
},
SocketType.PhysicalForce3DVector: {
"default": "UNEWT",
"values": {
"KG_M_S_SQ": spu.kg * spu.m/spu.second**2,
@ -294,6 +391,7 @@ SocketType_to_units = {
SocketType_to_color = {
# Basic
SocketType.Any: (0.8, 0.8, 0.8, 1.0), # Light Grey
SocketType.Bool: (0.7, 0.7, 0.7, 1.0), # Medium Light Grey
SocketType.Text: (0.7, 0.7, 0.7, 1.0), # Medium Light Grey
SocketType.FilePath: (0.6, 0.6, 0.6, 1.0), # Medium Grey
@ -315,10 +413,16 @@ SocketType_to_color = {
SocketType.PhysicalLength: (0.8, 0.4, 0.4, 1.0), # Medium Red
SocketType.PhysicalArea: (0.7, 0.35, 0.35, 1.0), # Medium Dark Red
SocketType.PhysicalVolume: (0.6, 0.3, 0.3, 1.0), # Dark Red
SocketType.PhysicalPoint2D: (0.7, 0.35, 0.35, 1.0), # Medium Dark Red
SocketType.PhysicalPoint3D: (0.6, 0.3, 0.3, 1.0), # Dark Red
SocketType.PhysicalSize2D: (0.7, 0.35, 0.35, 1.0), # Medium Dark Red
SocketType.PhysicalSize3D: (0.6, 0.3, 0.3, 1.0), # Dark Red
SocketType.PhysicalMass: (0.9, 0.6, 0.4, 1.0), # Light Orange
SocketType.PhysicalSpeed: (0.8, 0.55, 0.35, 1.0), # Medium Light Orange
SocketType.PhysicalAccel: (0.7, 0.5, 0.3, 1.0), # Medium Orange
SocketType.PhysicalForce: (0.6, 0.45, 0.25, 1.0), # Medium Dark Orange
SocketType.PhysicalAccelScalar: (0.7, 0.5, 0.3, 1.0), # Medium Orange
SocketType.PhysicalForceScalar: (0.6, 0.45, 0.25, 1.0), # Medium Dark Orange
SocketType.PhysicalAccel3DVector: (0.7, 0.5, 0.3, 1.0), # Medium Orange
SocketType.PhysicalForce3DVector: (0.6, 0.45, 0.25, 1.0), # Medium Dark Orange
SocketType.PhysicalPol: (0.5, 0.4, 0.2, 1.0), # Dark Orange
SocketType.PhysicalFreq: (1.0, 0.7, 0.5, 1.0), # Light Peach
SocketType.PhysicalSpecPowerDist: (0.9, 0.65, 0.45, 1.0), # Medium Light Peach

View File

@ -371,13 +371,24 @@ class MaxwellSimTreeNode(bpy.types.Node):
Blender's own node socket object.
"""
# (Guard) Socket Exists
if input_socket_name in self.input_sockets:
# Check Nicely that it 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]
elif hasattr(self, "input_socket_sets"):
# 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
)]
def g_output_bl_socket(
self,
output_socket_name: contracts.SocketName,
@ -392,6 +403,8 @@ class MaxwellSimTreeNode(bpy.types.Node):
Blender's own node socket object.
"""
if output_socket_name in self.output_sockets:
# (Guard) Socket Exists
if output_socket_name not in self.output_sockets:
msg = f"Input socket with name {output_socket_name} does not exist"
@ -399,10 +412,26 @@ class MaxwellSimTreeNode(bpy.types.Node):
return self.outputs[self.output_sockets[output_socket_name].label]
elif hasattr(self, "input_socket_sets"):
return self.outputs[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 == output_socket_name
)]
def g_output_socket_name(
self,
output_bl_socket_name: contracts.BLSocketName,
) -> contracts.SocketName:
if hasattr(self, "output_socket_sets"):
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_name
for output_socket_name in self.output_sockets.keys()

View File

@ -11,16 +11,21 @@ class NumberConstantNode(base.MaxwellSimTreeNode):
bl_label = "Numerical Constant"
#bl_icon = constants.ICON_SIM_INPUT
input_sockets = {
input_sockets = {}
input_socket_sets = {
"real": {
"value": sockets.RealNumberSocketDef(
label="Real",
),
},
"complex": {
"value": sockets.ComplexNumberSocketDef(
label="Complex",
), ## TODO: Dynamic number socket!
}
output_sockets = {
"value": sockets.ComplexNumberSocketDef(
label="Complex",
), ## TODO: Dynamic number socket!
),
},
}
output_sockets = {}
output_socket_sets = input_socket_sets
####################
# - Callbacks

View File

@ -1,5 +1,75 @@
import bpy
import sympy as sp
from .... import contracts
from .... import sockets
from ... import base
class PhysicalConstantNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.PhysicalConstant
bl_label = "Physical Constant"
#bl_icon = constants.ICON_SIM_INPUT
input_sockets = {}
input_socket_sets = {
"time": {
"value": sockets.PhysicalTimeSocketDef(
label="Time",
),
},
"angle": {
"value": sockets.PhysicalAngleSocketDef(
label="Angle",
),
},
"length": {
"value": sockets.PhysicalLengthSocketDef(
label="Length",
),
},
"area": {
"value": sockets.PhysicalAreaSocketDef(
label="Area",
),
},
"volume": {
"value": sockets.PhysicalVolumeSocketDef(
label="Volume",
),
},
"point_3d": {
"value": sockets.PhysicalPoint3DSocketDef(
label="3D Point",
),
},
"size_3d": {
"value": sockets.PhysicalSize3DSocketDef(
label="3D Size",
),
},
## I got bored so maybe the rest later
}
output_sockets = {}
output_socket_sets = input_socket_sets
####################
# - Callbacks
####################
@base.computes_output_socket("value")
def compute_value(self: contracts.NodeTypeProtocol) -> sp.Expr:
return self.compute_input("value")
####################
# - Blender Registration
####################
BL_REGISTER = []
BL_NODES = {}
BL_REGISTER = [
PhysicalConstantNode,
]
BL_NODES = {
contracts.NodeType.PhysicalConstant: (
contracts.NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS
)
}

View File

@ -19,6 +19,7 @@ class KitchenSinkNode(base.MaxwellSimTreeNode):
input_socket_sets = {
"basic": {
"basic_any": sockets.AnySocketDef(label="Any"),
"basic_bool": sockets.BoolSocketDef(label="Bool"),
"basic_filepath": sockets.FilePathSocketDef(label="FilePath"),
"basic_text": sockets.TextSocketDef(label="Text"),
},
@ -36,14 +37,20 @@ class KitchenSinkNode(base.MaxwellSimTreeNode):
},
"physical": {
"physical_time": sockets.PhysicalTimeSocketDef(label="PhysicalTime"),
#"physical_point_2d": sockets.PhysicalPoint2DSocketDef(label="PhysicalPoint2D"),
"physical_angle": sockets.PhysicalAngleSocketDef(label="PhysicalAngle"),
"physical_length": sockets.PhysicalLengthSocketDef(label="PhysicalLength"),
"physical_area": sockets.PhysicalAreaSocketDef(label="PhysicalArea"),
"physical_volume": sockets.PhysicalVolumeSocketDef(label="PhysicalVolume"),
"physical_point_3d": sockets.PhysicalPoint3DSocketDef(label="PhysicalPoint3D"),
#"physical_size_2d": sockets.PhysicalSize2DSocketDef(label="PhysicalSize2D"),
"physical_size_3d": sockets.PhysicalSize3DSocketDef(label="PhysicalSize3D"),
"physical_mass": sockets.PhysicalMassSocketDef(label="PhysicalMass"),
"physical_speed": sockets.PhysicalSpeedSocketDef(label="PhysicalSpeed"),
"physical_accel": sockets.PhysicalAccelSocketDef(label="PhysicalAccel"),
"physical_force": sockets.PhysicalForceSocketDef(label="PhysicalForce"),
"physical_accel_scalar": sockets.PhysicalAccelScalarSocketDef(label="PhysicalAccelScalar"),
"physical_force_scalar": sockets.PhysicalForceScalarSocketDef(label="PhysicalForceScalar"),
#"physical_accel_3dvector": sockets.PhysicalAccel3DVectorSocketDef(label="PhysicalAccel3DVector"),
#"physical_force_3dvector": sockets.PhysicalForce3DVectorSocketDef(label="PhysicalForce3DVector"),
"physical_pol": sockets.PhysicalPolSocketDef(label="PhysicalPol"),
"physical_freq": sockets.PhysicalFreqSocketDef(label="PhysicalFreq"),
"physical_spec_power_dist": sockets.PhysicalSpecPowerDistSocketDef(label="PhysicalSpecPowerDist"),
@ -62,18 +69,21 @@ class KitchenSinkNode(base.MaxwellSimTreeNode):
"maxwell_temporal_shape": sockets.MaxwellTemporalShapeSocketDef(label="MaxwellTemporalShape"),
"maxwell_medium": sockets.MaxwellMediumSocketDef(label="MaxwellMedium"),
#"maxwell_medium_nonlinearity": sockets.MaxwellMediumNonLinearitySocketDef(label="MaxwellMediumNonLinearity"),
"maxwell_structure": sockets.MaxwellMediumSocketDef(label="MaxwellMedium"),
"maxwell_structure": sockets.MaxwellStructureSocketDef(label="MaxwellMedium"),
"maxwell_bound_box": sockets.MaxwellBoundBoxSocketDef(label="MaxwellBoundBox"),
"maxwell_bound_face": sockets.MaxwellBoundFaceSocketDef(label="MaxwellBoundFace"),
"maxwell_monitor": sockets.MaxwellMonitorSocketDef(label="MaxwellMonitor"),
"maxwell_monitor": sockets.MaxwellFDTDSimSocketDef(label="MaxwellFDTDSim"),
"maxwell_monitor": sockets.MaxwellSimGridSocketDef(label="MaxwellSimGrid"),
"maxwell_monitor": sockets.MaxwellSimGridAxisSocketDef(label="MaxwellSimGridAxis"),
"maxwell_fdtd_sim": sockets.MaxwellFDTDSimSocketDef(label="MaxwellFDTDSim"),
"maxwell_sim_grid": sockets.MaxwellSimGridSocketDef(label="MaxwellSimGrid"),
"maxwell_sim_grid_axis": sockets.MaxwellSimGridAxisSocketDef(label="MaxwellSimGridAxis"),
},
}
output_sockets = {}
output_socket_sets = input_socket_sets
output_socket_sets = {
k + " Output": v
for k, v in input_socket_sets.items()
}

View File

@ -16,21 +16,11 @@ class FDTDSimNode(base.MaxwellSimTreeNode):
# - Sockets
####################
input_sockets = {
"run_time": sockets.RealNumberSocketDef(
"run_time": sockets.PhysicalTimeSocketDef(
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,
"size": sockets.PhysicalSize3DSocketDef(
label="Size",
),
"ambient_medium": sockets.MaxwellMediumSocketDef(
label="Ambient Medium",
@ -56,17 +46,15 @@ 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")
_run_time = self.compute_input("run_time")
_size = self.compute_input("size")
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"),
)
run_time = spu.convert_to(_run_time, spu.second) / spu.second
size = tuple(spu.convert_to(_size, spu.um) / spu.um)
return td.Simulation(
size=size,

View File

@ -16,17 +16,14 @@ class PointDipoleSourceNode(base.MaxwellSimTreeNode):
# - Sockets
####################
input_sockets = {
"center_x": sockets.RealNumberSocketDef(
label="Center X",
default_value=0.0,
#"polarization": sockets.PhysicalPolSocketDef(
# label="Polarization",
#), ## TODO: Exactly how to go about this...
"temporal_shape": sockets.MaxwellTemporalShapeSocketDef(
label="Temporal Shape",
),
"center_y": sockets.RealNumberSocketDef(
label="Center Y",
default_value=0.0,
),
"center_z": sockets.RealNumberSocketDef(
label="Center Z",
default_value=0.0,
"center": sockets.PhysicalPoint3DSocketDef(
label="Center",
),
}
output_sockets = {
@ -40,19 +37,19 @@ class PointDipoleSourceNode(base.MaxwellSimTreeNode):
####################
@base.computes_output_socket("source")
def compute_source(self: contracts.NodeTypeProtocol) -> td.PointDipole:
center = (
self.compute_input("center_x"),
self.compute_input("center_y"),
self.compute_input("center_z"),
)
temporal_shape = self.compute_input("temporal_shape")
cheating_pulse = td.GaussianPulse(freq0=200e12, fwidth=20e12)
_center = self.compute_input("center")
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
cheating_pol = "Ex"
## TODO: Fix
return td.PointDipole(
center=center,
source_time=cheating_pulse,
source_time=temporal_shape,
interpolate=True,
polarization="Ex",
polarization=cheating_pol,
)

View File

@ -1,6 +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 GaussianPulseTemporalShapeNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.GaussianPulseTemporalShape
bl_label = "Gaussian Pulse 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",
),
"remove_dc_component": sockets.BoolSocketDef(
label="Remove DC",
default_value=True,
),
}
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")
remove_dc_component = self.compute_input("remove_dc_component")
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.GaussianPulse(
amplitude=cheating_amplitude,
phase=phase,
freq0=freq_center,
fwidth=freq_std,
offset=time_delay_rel_ang_freq,
remove_dc_component=remove_dc_component,
)
####################
# - Blender Registration
####################
BL_REGISTER = []
BL_NODES = {}
BL_REGISTER = [
GaussianPulseTemporalShapeNode,
]
BL_NODES = {
contracts.NodeType.GaussianPulseTemporalShape: (
contracts.NodeCategory.MAXWELLSIM_SOURCES_TEMPORALSHAPES
)
}

View File

@ -18,29 +18,11 @@ class BoxStructureNode(base.MaxwellSimTreeNode):
"medium": sockets.MaxwellMediumSocketDef(
label="Medium",
),
"center_x": sockets.RealNumberSocketDef(
label="Center X",
default_value=0.0,
"center": sockets.PhysicalPoint3DSocketDef(
label="Center",
),
"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,
"size": sockets.PhysicalSize3DSocketDef(
label="Size",
),
}
output_sockets = {
@ -55,16 +37,11 @@ class BoxStructureNode(base.MaxwellSimTreeNode):
@base.computes_output_socket("structure")
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"),
)
_center = self.compute_input("center")
_size = self.compute_input("size")
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
size = tuple(spu.convert_to(_size, spu.um) / spu.um)
return td.Structure(
geometry=td.Box(

View File

@ -1,5 +1,6 @@
from . import basic
AnySocketDef = basic.AnySocketDef
BoolSocketDef = basic.BoolSocketDef
TextSocketDef = basic.TextSocketDef
FilePathSocketDef = basic.FilePathSocketDef
@ -21,10 +22,12 @@ PhysicalAngleSocketDef = physical.PhysicalAngleSocketDef
PhysicalLengthSocketDef = physical.PhysicalLengthSocketDef
PhysicalAreaSocketDef = physical.PhysicalAreaSocketDef
PhysicalVolumeSocketDef = physical.PhysicalVolumeSocketDef
PhysicalPoint3DSocketDef = physical.PhysicalPoint3DSocketDef
PhysicalSize3DSocketDef = physical.PhysicalSize3DSocketDef
PhysicalMassSocketDef = physical.PhysicalMassSocketDef
PhysicalSpeedSocketDef = physical.PhysicalSpeedSocketDef
PhysicalAccelSocketDef = physical.PhysicalAccelSocketDef
PhysicalForceSocketDef = physical.PhysicalForceSocketDef
PhysicalAccelScalarSocketDef = physical.PhysicalAccelScalarSocketDef
PhysicalForceScalarSocketDef = physical.PhysicalForceScalarSocketDef
PhysicalPolSocketDef = physical.PhysicalPolSocketDef
PhysicalFreqSocketDef = physical.PhysicalFreqSocketDef
PhysicalSpecRelPermDistSocketDef = physical.PhysicalSpecRelPermDistSocketDef

View File

@ -127,6 +127,12 @@ class BLSocket(bpy.types.NodeSocket):
the value with the new unit.
"""
if hasattr(self, "raw_value") and hasattr(self, "unit"):
if hasattr(self.raw_value, "__getitem__"):
self.raw_value = tuple(spu.convert_to(
sp.Matrix(tuple(self.raw_value)) * self._unit_previous,
self.unit,
) / self.unit)
else:
self.raw_value = spu.convert_to(
self.raw_value * self._unit_previous,
self.unit,
@ -187,6 +193,9 @@ class BLSocket(bpy.types.NodeSocket):
label_col_row = col.row(align=True)
if hasattr(self, "draw_label_row"):
self.draw_label_row(label_col_row, text)
elif hasattr(self, "raw_unit"):
label_col_row.label(text=text)
label_col_row.prop(self, "raw_unit", text="")
else:
label_col_row.label(text=text)
@ -208,6 +217,9 @@ class BLSocket(bpy.types.NodeSocket):
# Row(s): Value
if hasattr(self, "draw_value"):
self.draw_value(col)
elif hasattr(self, "raw_value"):
#col_row = col.row(align=True)
col.prop(self, "raw_value", text="")
def draw_output(
self,

View File

@ -1,6 +1,9 @@
from . import any_socket
AnySocketDef = any_socket.AnySocketDef
from . import bool_socket
BoolSocketDef = bool_socket.BoolSocketDef
from . import text_socket
TextSocketDef = text_socket.TextSocketDef
@ -10,6 +13,7 @@ FilePathSocketDef = file_path_socket.FilePathSocketDef
BL_REGISTER = [
*any_socket.BL_REGISTER,
*bool_socket.BL_REGISTER,
*text_socket.BL_REGISTER,
*file_path_socket.BL_REGISTER,
]

View File

@ -0,0 +1,73 @@
import typing as typ
import bpy
import sympy as sp
import pydantic as pyd
from .. import base
from ... import contracts
####################
# - Blender Socket
####################
class BoolBLSocket(base.BLSocket):
socket_type = contracts.SocketType.Bool
bl_label = "Bool"
compatible_types = {
bool: {},
}
####################
# - Properties
####################
raw_value: bpy.props.BoolProperty(
name="Boolean",
description="Represents a boolean",
default=False,
)
####################
# - Socket UI
####################
def draw_label_row(self, label_col_row: bpy.types.UILayout, text: str) -> None:
label_col_row.label(text=text)
label_col_row.prop(self, "raw_value", text="")
def draw_value(self, label_col_row: bpy.types.UILayout) -> None:
pass
####################
# - Computation of Default Value
####################
@property
def default_value(self) -> str:
return self.raw_value
@default_value.setter
def default_value(self, value: typ.Any) -> None:
# (Guard) Value Compatibility
if not self.is_compatible(value):
msg = f"Tried setting socket ({self}) to incompatible value ({value}) of type {type(value)}"
raise ValueError(msg)
self.raw_value = bool(value)
####################
# - Socket Configuration
####################
class BoolSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.Bool
label: str
default_value: bool = False
def init(self, bl_socket: BoolBLSocket) -> None:
bl_socket.raw_value = self.default_value
####################
# - Blender Registration
####################
BL_REGISTER = [
BoolBLSocket,
]

View File

@ -37,6 +37,9 @@ class TextBLSocket(base.BLSocket):
"""
label_col_row.prop(self, "raw_value", text=text)
def draw_value(self, label_col_row: bpy.types.UILayout) -> None:
pass
####################
# - Computation of Default Value
####################

View File

@ -32,6 +32,7 @@ BL_REGISTER = [
*source_socket.BL_REGISTER,
*temporal_shape_socket.BL_REGISTER,
*structure_socket.BL_REGISTER,
*monitor_socket.BL_REGISTER,
*fdtd_sim_socket.BL_REGISTER,
*sim_grid_socket.BL_REGISTER,
*sim_grid_axis_socket.BL_REGISTER,

View File

@ -22,7 +22,7 @@ class MaxwellMediumBLSocket(base.BLSocket):
name="Permittivity",
description="Represents a simple, real permittivity.",
default=0.0,
precision=6,
precision=4,
)
####################
@ -33,7 +33,7 @@ class MaxwellMediumBLSocket(base.BLSocket):
specifying the active unit.
"""
col_row = col.row(align=True)
col_row.prop(self, "rel_permittivity", text="Re(eps_r)")
col_row.prop(self, "rel_permittivity", text="ϵr")
####################
# - Computation of Default Value

View File

@ -32,14 +32,6 @@ class RealNumberBLSocket(base.BLSocket):
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
####################

View File

@ -11,15 +11,21 @@ PhysicalLengthSocketDef = length_socket.PhysicalLengthSocketDef
PhysicalAreaSocketDef = area_socket.PhysicalAreaSocketDef
PhysicalVolumeSocketDef = volume_socket.PhysicalVolumeSocketDef
from . import point_3d_socket
PhysicalPoint3DSocketDef = point_3d_socket.PhysicalPoint3DSocketDef
from . import size_3d_socket
PhysicalSize3DSocketDef = size_3d_socket.PhysicalSize3DSocketDef
from . import mass_socket
PhysicalMassSocketDef = mass_socket.PhysicalMassSocketDef
from . import speed_socket
from . import accel_socket
from . import force_socket
from . import accel_scalar_socket
from . import force_scalar_socket
PhysicalSpeedSocketDef = speed_socket.PhysicalSpeedSocketDef
PhysicalAccelSocketDef = accel_socket.PhysicalAccelSocketDef
PhysicalForceSocketDef = force_socket.PhysicalForceSocketDef
PhysicalAccelScalarSocketDef = accel_scalar_socket.PhysicalAccelScalarSocketDef
PhysicalForceScalarSocketDef = force_scalar_socket.PhysicalForceScalarSocketDef
from . import pol_socket
PhysicalPolSocketDef = pol_socket.PhysicalPolSocketDef
@ -42,11 +48,15 @@ BL_REGISTER = [
*area_socket.BL_REGISTER,
*volume_socket.BL_REGISTER,
*point_3d_socket.BL_REGISTER,
*size_3d_socket.BL_REGISTER,
*mass_socket.BL_REGISTER,
*speed_socket.BL_REGISTER,
*accel_socket.BL_REGISTER,
*force_socket.BL_REGISTER,
*accel_scalar_socket.BL_REGISTER,
*force_scalar_socket.BL_REGISTER,
*pol_socket.BL_REGISTER,

View File

@ -9,8 +9,8 @@ from ... import contracts
####################
# - Blender Socket
####################
class PhysicalAccelBLSocket(base.BLSocket):
socket_type = contracts.SocketType.PhysicalAccel
class PhysicalAccelScalarBLSocket(base.BLSocket):
socket_type = contracts.SocketType.PhysicalAccelScalar
bl_label = "PhysicalAccel"
use_units = True
@ -24,17 +24,6 @@ class PhysicalAccelBLSocket(base.BLSocket):
precision=6,
)
####################
# - Socket UI
####################
def draw_label_row(self, label_col_row: bpy.types.UILayout, text: str) -> None:
label_col_row.label(text=text)
label_col_row.prop(self, "raw_unit", text="")
def draw_value(self, col: bpy.types.UILayout) -> None:
col_row = col.row(align=True)
col_row.prop(self, "raw_value", text="")
####################
# - Default Value
####################
@ -49,16 +38,19 @@ class PhysicalAccelBLSocket(base.BLSocket):
####################
# - Socket Configuration
####################
class PhysicalAccelSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.PhysicalAccel
class PhysicalAccelScalarSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.PhysicalAccelScalar
label: str
def init(self, bl_socket: PhysicalAccelBLSocket) -> None:
pass
default_unit: typ.Any | None = None
def init(self, bl_socket: PhysicalAccelScalarBLSocket) -> None:
if self.default_unit:
bl_socket.unit = self.default_unit
####################
# - Blender Registration
####################
BL_REGISTER = [
PhysicalAccelBLSocket,
PhysicalAccelScalarBLSocket,
]

View File

@ -1,5 +1,6 @@
import typing as typ
import bpy
import pydantic as pyd
from .. import base
@ -11,17 +12,28 @@ from ... import contracts
class PhysicalAngleBLSocket(base.BLSocket):
socket_type = contracts.SocketType.PhysicalAngle
bl_label = "PhysicalAngle"
use_units = True
####################
# - Properties
####################
raw_value: bpy.props.FloatProperty(
name="Unitless Acceleration",
description="Represents the unitless part of the acceleration",
default=0.0,
precision=4,
)
####################
# - Default Value
####################
@property
def default_value(self) -> None:
pass
return self.raw_value * self.unit
@default_value.setter
def default_value(self, value: typ.Any) -> None:
pass
self.raw_value = self.value_as_unit(value)
####################
# - Socket Configuration
@ -30,8 +42,11 @@ class PhysicalAngleSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.PhysicalAngle
label: str
default_unit: typ.Any | None = None
def init(self, bl_socket: PhysicalAngleBLSocket) -> None:
pass
if self.default_unit:
bl_socket.unit = self.default_unit
####################
# - Blender Registration

View File

@ -38,9 +38,6 @@ class PhysicalAreaBLSocket(base.BLSocket):
# - 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_unit", text="")

View File

@ -0,0 +1,56 @@
import typing as typ
import bpy
import pydantic as pyd
from .. import base
from ... import contracts
####################
# - Blender Socket
####################
class PhysicalForceScalarBLSocket(base.BLSocket):
socket_type = contracts.SocketType.PhysicalForceScalar
bl_label = "PhysicalForceScalar"
use_units = True
####################
# - Properties
####################
raw_value: bpy.props.FloatProperty(
name="Unitless Force",
description="Represents the unitless part of the force",
default=0.0,
precision=6,
)
####################
# - 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 PhysicalForceScalarSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.PhysicalForceScalar
label: str
default_unit: typ.Any | None = None
def init(self, bl_socket: PhysicalForceScalarBLSocket) -> None:
if self.default_unit:
bl_socket.unit = self.default_unit
####################
# - Blender Registration
####################
BL_REGISTER = [
PhysicalForceScalarBLSocket,
]

View File

@ -1,41 +0,0 @@
import typing as typ
import pydantic as pyd
from .. import base
from ... import contracts
####################
# - Blender Socket
####################
class PhysicalForceBLSocket(base.BLSocket):
socket_type = contracts.SocketType.PhysicalForce
bl_label = "PhysicalForce"
####################
# - Default Value
####################
@property
def default_value(self) -> None:
pass
@default_value.setter
def default_value(self, value: typ.Any) -> None:
pass
####################
# - Socket Configuration
####################
class PhysicalForceSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.PhysicalForce
label: str
def init(self, bl_socket: PhysicalForceBLSocket) -> None:
pass
####################
# - Blender Registration
####################
BL_REGISTER = [
PhysicalForceBLSocket,
]

View File

@ -1,5 +1,6 @@
import typing as typ
import bpy
import pydantic as pyd
from .. import base
@ -11,17 +12,28 @@ from ... import contracts
class PhysicalFreqBLSocket(base.BLSocket):
socket_type = contracts.SocketType.PhysicalFreq
bl_label = "PhysicalFreq"
use_units = True
####################
# - Properties
####################
raw_value: bpy.props.FloatProperty(
name="Unitless Frequency",
description="Represents the unitless part of the frequency",
default=0.0,
precision=6,
)
####################
# - Default Value
####################
@property
def default_value(self) -> None:
pass
return self.raw_value * self.unit
@default_value.setter
def default_value(self, value: typ.Any) -> None:
pass
self.raw_value = self.value_as_unit(value)
####################
# - Socket Configuration

View File

@ -1,5 +1,6 @@
import typing as typ
import bpy
import pydantic as pyd
from .. import base
@ -11,17 +12,28 @@ from ... import contracts
class PhysicalLengthBLSocket(base.BLSocket):
socket_type = contracts.SocketType.PhysicalLength
bl_label = "PhysicalLength"
use_units = True
####################
# - Properties
####################
raw_value: bpy.props.FloatProperty(
name="Unitless Force",
description="Represents the unitless part of the force",
default=0.0,
precision=6,
)
####################
# - Default Value
####################
@property
def default_value(self) -> None:
pass
return self.raw_value * self.unit
@default_value.setter
def default_value(self, value: typ.Any) -> None:
pass
self.raw_value = self.value_as_unit(value)
####################
# - Socket Configuration
@ -30,8 +42,11 @@ class PhysicalLengthSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.PhysicalLength
label: str
default_unit: typ.Any | None = None
def init(self, bl_socket: PhysicalLengthBLSocket) -> None:
pass
if self.default_unit:
bl_socket.unit = self.default_unit
####################
# - Blender Registration

View File

@ -0,0 +1,67 @@
import typing as typ
import bpy
import sympy as sp
import sympy.physics.units as spu
import pydantic as pyd
from .. import base
from ... import contracts
class PhysicalPoint3DBLSocket(base.BLSocket):
socket_type = contracts.SocketType.PhysicalPoint3D
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
####################
raw_value: bpy.props.FloatVectorProperty(
name="Unitless 3D Point (global coordinate system)",
description="Represents the unitless part of the 3D point",
size=3,
default=(0.0, 0.0, 0.0),
precision=4,
)
####################
# - Computation of Default Value
####################
@property
def default_value(self) -> sp.Expr:
return sp.Matrix(tuple(self.raw_value)) * self.unit
@default_value.setter
def default_value(self, value: typ.Any) -> None:
self.raw_value = self.value_as_unit(value)
####################
# - Socket Configuration
####################
class PhysicalPoint3DSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.PhysicalPoint3D
label: str
default_unit: typ.Any | None = None
def init(self, bl_socket: PhysicalPoint3DBLSocket) -> None:
if self.default_unit:
bl_socket.unit = self.default_unit
####################
# - Blender Registration
####################
BL_REGISTER = [
PhysicalPoint3DBLSocket,
]

View File

@ -0,0 +1,67 @@
import typing as typ
import bpy
import sympy as sp
import sympy.physics.units as spu
import pydantic as pyd
from .. import base
from ... import contracts
class PhysicalSize3DBLSocket(base.BLSocket):
socket_type = contracts.SocketType.PhysicalSize3D
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
####################
raw_value: bpy.props.FloatVectorProperty(
name="Unitless 3D Size",
description="Represents the unitless part of the 3D size",
size=3,
default=(1.0, 1.0, 1.0),
precision=4,
)
####################
# - Computation of Default Value
####################
@property
def default_value(self) -> sp.Expr:
return sp.Matrix(tuple(self.raw_value)) * self.unit
@default_value.setter
def default_value(self, value: typ.Any) -> None:
self.raw_value = self.value_as_unit(value)
####################
# - Socket Configuration
####################
class PhysicalSize3DSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.PhysicalSize3D
label: str
default_unit: typ.Any | None = None
def init(self, bl_socket: PhysicalSize3DBLSocket) -> None:
if self.default_unit:
bl_socket.unit = self.default_unit
####################
# - Blender Registration
####################
BL_REGISTER = [
PhysicalSize3DBLSocket,
]

View File

@ -1,5 +1,6 @@
import typing as typ
import bpy
import pydantic as pyd
from .. import base
@ -11,17 +12,28 @@ from ... import contracts
class PhysicalTimeBLSocket(base.BLSocket):
socket_type = contracts.SocketType.PhysicalTime
bl_label = "PhysicalTime"
use_units = True
####################
# - Properties
####################
raw_value: bpy.props.FloatProperty(
name="Unitless Force",
description="Represents the unitless part of the force",
default=0.0,
precision=6,
)
####################
# - Default Value
####################
@property
def default_value(self) -> None:
pass
return self.raw_value * self.unit
@default_value.setter
def default_value(self, value: typ.Any) -> None:
pass
self.raw_value = self.value_as_unit(value)
####################
# - Socket Configuration
@ -30,8 +42,11 @@ class PhysicalTimeSocketDef(pyd.BaseModel):
socket_type: contracts.SocketType = contracts.SocketType.PhysicalTime
label: str
default_unit: typ.Any | None = None
def init(self, bl_socket: PhysicalTimeBLSocket) -> None:
pass
if self.default_unit:
bl_socket.unit = self.default_unit
####################
# - Blender Registration

View File

@ -5,9 +5,6 @@ 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

View File

@ -1,3 +1,4 @@
tidy3d==2.5.2
pydantic==2.6.0
sympy==1.12
scipy==1.12.0

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

Binary file not shown.