diff --git a/.gitignore b/.gitignore index 267ace0..6bd773e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,11 @@ # - Standard Ignores #################### dev +.cached-dependencies +__pycache__ +*.blend1 +*.blend2 +*.blend3 #################### diff --git a/code/blender_maxwell/__init__.py b/code/blender_maxwell/__init__.py new file mode 100644 index 0000000..e75a821 --- /dev/null +++ b/code/blender_maxwell/__init__.py @@ -0,0 +1,72 @@ +bl_info = { + "name": "Maxwell Simulation and Visualization", + "blender": (4, 0, 2), + "category": "Node", + "description": "Custom node trees for defining and visualizing Maxwell simulation.", + "author": "Sofus Albert Høgsbro Rose", + "version": (0, 1), + "wiki_url": "https://git.sofus.io/dtu-courses/bsc_thesis", + "tracker_url": "https://git.sofus.io/dtu-courses/bsc_thesis/issues", +} + +#################### +# - sys.path Library Inclusion +#################### +import sys +sys.path.insert(0, "/home/sofus/src/college/bsc_ge/thesis/code/.cached-dependencies") +## ^^ Placeholder + +#################### +# - Module Import +#################### +if "bpy" not in locals(): + import bpy + import nodeitems_utils + try: + from . import node_trees + from . import operators + from . import preferences + except ImportError: + import sys + sys.path.insert(0, "/home/sofus/src/college/bsc_ge/thesis/code/blender-maxwell") + import node_trees + import operators + import preferences +else: + import importlib + + importlib.reload(node_trees) + + +#################### +# - Registration +#################### +BL_REGISTER = [ + *node_trees.BL_REGISTER, + *operators.BL_REGISTER, + *preferences.BL_REGISTER, +] +BL_NODE_CATEGORIES = [ + *node_trees.BL_NODE_CATEGORIES, +] + +def register(): + for cls in BL_REGISTER: + bpy.utils.register_class(cls) + + for bl_node_category in BL_NODE_CATEGORIES: + nodeitems_utils.register_node_categories(*bl_node_category) + +def unregister(): + for bl_node_category in reversed(BL_NODE_CATEGORIES): + try: + nodeitems_utils.unregister_node_categories(bl_node_category[0]) + except: pass + + for cls in reversed(BL_REGISTER): + try: + bpy.utils.unregister_class(cls) + except: pass + +if __name__ == "__main__": + register() diff --git a/code/blender_maxwell/node_trees/__init__.py b/code/blender_maxwell/node_trees/__init__.py new file mode 100644 index 0000000..80f25bd --- /dev/null +++ b/code/blender_maxwell/node_trees/__init__.py @@ -0,0 +1,9 @@ +from . import maxwell_sim_nodes + +BL_REGISTER = [ + *maxwell_sim_nodes.BL_REGISTER, +] + +BL_NODE_CATEGORIES = [ + *maxwell_sim_nodes.BL_NODE_CATEGORIES, +] diff --git a/code/blender_maxwell/node_trees/maxwell_sim_nodes/__init__.py b/code/blender_maxwell/node_trees/maxwell_sim_nodes/__init__.py new file mode 100644 index 0000000..5c906d5 --- /dev/null +++ b/code/blender_maxwell/node_trees/maxwell_sim_nodes/__init__.py @@ -0,0 +1,11 @@ +from . import tree, socket_types, nodes + +BL_REGISTER = [ + *tree.BL_REGISTER, + *socket_types.BL_REGISTER, + *nodes.BL_REGISTER, +] + +BL_NODE_CATEGORIES = [ + *nodes.BL_NODE_CATEGORIES, +] diff --git a/code/blender_maxwell/node_trees/maxwell_sim_nodes/constants.py b/code/blender_maxwell/node_trees/maxwell_sim_nodes/constants.py new file mode 100644 index 0000000..3355e8d --- /dev/null +++ b/code/blender_maxwell/node_trees/maxwell_sim_nodes/constants.py @@ -0,0 +1,20 @@ +#################### +# - Colors +#################### +COLOR_SOCKET_SOURCE = (0.4, 0.4, 0.9, 1.0) +COLOR_SOCKET_MEDIUM = (1.0, 0.4, 0.2, 1.0) +COLOR_SOCKET_STRUCTURE = (0.2, 0.4, 0.8, 1.0) +COLOR_SOCKET_BOUND = (0.7, 0.8, 0.7, 1.0) +COLOR_SOCKET_FDTDSIM = (0.9, 0.9, 0.9, 1.0) + + +#################### +# - Icons +#################### +ICON_SIM = 'MOD_SIMPLEDEFORM' + +ICON_SIM_SOURCE = 'FORCE_CHARGE' +ICON_SIM_MEDIUM = 'MATSHADERBALL' +ICON_SIM_STRUCTURE = 'OUTLINER_DATA_MESH' +ICON_SIM_BOUND = 'MOD_MESHDEFORM' +ICON_SIM_SIMULATION = ICON_SIM diff --git a/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/__init__.py b/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/__init__.py new file mode 100644 index 0000000..93d120e --- /dev/null +++ b/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/__init__.py @@ -0,0 +1,9 @@ +from . import definitions, categories + +BL_REGISTER = [ + *definitions.BL_REGISTER, +] + +BL_NODE_CATEGORIES = [ + *categories.BL_NODE_CATEGORIES, +] diff --git a/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/categories.py b/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/categories.py new file mode 100644 index 0000000..2012d78 --- /dev/null +++ b/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/categories.py @@ -0,0 +1,58 @@ +import nodeitems_utils +from . import types + +#################### +# - Node Category Types +#################### +CATEGORIES_MAXWELL_SIM = 'MAXWELL_SIM' + +CATEGORY_MAXWELL_SIM_DEBUG = 'MAXWELL_SIM_DEBUG' +CATEGORY_MAXWELL_SIM_SOURCES = 'MAXWELL_SIM_SOURCES' +CATEGORY_MAXWELL_SIM_MEDIUMS = 'MAXWELL_SIM_MEDIUMS' +CATEGORY_MAXWELL_SIM_STRUCTURES = 'MAXWELL_SIM_STRUCTURES' +CATEGORY_MAXWELL_SIM_BOUNDS = 'MAXWELL_SIM_BOUNDS' +CATEGORY_MAXWELL_SIM_SIMULATIONS = 'MAXWELL_SIM_SIMULATIONS' + +#################### +# - Node Category Class +#################### +class MaxwellSimNodeCategory(nodeitems_utils.NodeCategory): + @classmethod + def poll(cls, context): + """Constrain node category availability to within a MaxwellSimTree.""" + + return context.space_data.tree_type == types.tree_types.MaxwellSimTreeType + + +#################### +# - Node Category Definition +#################### +CATEGORIES_MaxwellSimTree = [ + MaxwellSimNodeCategory(CATEGORY_MAXWELL_SIM_DEBUG, "Debug", items=[ + nodeitems_utils.NodeItem(types.DebugPrinterNodeType), + ]), + MaxwellSimNodeCategory(CATEGORY_MAXWELL_SIM_SOURCES, "Sources", items=[ + nodeitems_utils.NodeItem(types.PointDipoleMaxwellSourceNodeType), + ]), + MaxwellSimNodeCategory(CATEGORY_MAXWELL_SIM_MEDIUMS, "Mediums", items=[ + nodeitems_utils.NodeItem(types.SellmeierMaxwellMediumNodeType), + ]), + MaxwellSimNodeCategory(CATEGORY_MAXWELL_SIM_STRUCTURES, "Structures", items=[ + nodeitems_utils.NodeItem(types.TriMeshMaxwellStructureNodeType), + ]), + MaxwellSimNodeCategory(CATEGORY_MAXWELL_SIM_BOUNDS, "Bounds", items=[ + nodeitems_utils.NodeItem(types.PMLMaxwellBoundNodeType), + ]), + MaxwellSimNodeCategory(CATEGORY_MAXWELL_SIM_SIMULATIONS, "Simulation", items=[ + nodeitems_utils.NodeItem(types.FDTDMaxwellSimulationNodeType), + ]), +] + + + +#################### +# - Blender Registration +#################### +BL_NODE_CATEGORIES = [ + (CATEGORIES_MAXWELL_SIM, CATEGORIES_MaxwellSimTree) +] diff --git a/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/constants.py b/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/constants.py new file mode 100644 index 0000000..c766047 --- /dev/null +++ b/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/constants.py @@ -0,0 +1 @@ +from .. import constants as tree_constants diff --git a/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/definitions/__init__.py b/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/definitions/__init__.py new file mode 100644 index 0000000..c79f642 --- /dev/null +++ b/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/definitions/__init__.py @@ -0,0 +1,16 @@ +#from . import bounds, mediums, simulations, sources, structures +from . import debug +from . import sources +#from . import mediums +#from . import structures +#from . import bounds +from . import simulations + +BL_REGISTER = [ + *debug.BL_REGISTER, + #*bounds.BL_REGISTER, + #*mediums.BL_REGISTER, + *sources.BL_REGISTER, + #*structures.BL_REGISTER, + *simulations.BL_REGISTER, +] diff --git a/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/definitions/bounds.py b/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/definitions/bounds.py new file mode 100644 index 0000000..7a80f20 --- /dev/null +++ b/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/definitions/bounds.py @@ -0,0 +1 @@ +BL_REGISTER = [] diff --git a/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/definitions/debug.py b/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/definitions/debug.py new file mode 100644 index 0000000..417e20f --- /dev/null +++ b/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/definitions/debug.py @@ -0,0 +1,53 @@ +import bpy +from .. import types, constants + +class DebugPrinterNodeOperator(bpy.types.Operator): + """Print, to the console, the object retrieved by the calling + DebugPrinterNode. + """ + + bl_idname = "blender_maxwell.debug_printer_node_operator" + bl_label = "Print the object linked into a DebugPrinterNode." + + @classmethod + def poll(cls, context): + return True + + def execute(self, context): + node = context.node + node.print_linked_data() + return {'FINISHED'} + +class DebugPrinterNode(types.MaxwellSimTreeNode): + bl_idname = types.DebugPrinterNodeType + bl_label = "Debug Printer" + bl_icon = constants.tree_constants.ICON_SIM + + input_sockets = { + "data": ("NodeSocketVirtual", "Data", lambda v: v), + } + output_sockets = {} + + #################### + # - Setup and Computation + #################### + def print_linked_data(self): + if self.inputs[self.input_sockets["data"][1]].is_linked: + print(self.compute_input("data")) + + + #################### + # - Node UI and Layout + #################### + def draw_buttons(self, context, layout): + layout.operator(DebugPrinterNodeOperator.bl_idname, text="Print Debug") + + + +#################### +# - Blender Registration +#################### +BL_REGISTER = [ + DebugPrinterNodeOperator, + DebugPrinterNode, +] diff --git a/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/definitions/mediums.py b/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/definitions/mediums.py new file mode 100644 index 0000000..b87bcb2 --- /dev/null +++ b/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/definitions/mediums.py @@ -0,0 +1,88 @@ +from dataclasses import dataclass +import bpy +from .. import types, constants + +#################### +# - Utilities (to move out) +#################### +def mk_set_preset(preset_dataclass): + def set_preset(self, context): + if self.preset in preset_dataclass.values: + for parameter, value in preset_dataclass.values.items(): + self.inputs[parameter].default_value = value + + return set_preset + + +#################### +# - Medium: Sellmeier +#################### +@dataclass +class _Presets_SellmeierMaxwellMediumNode: + descriptions = [ + ('BK7', "BK7 Glass", "Borosilicate crown glass (known as BK7)"), + ('FUSED_SILICA', "Fused Silica", "Fused silica aka. SiO2"), + ] + values = { + "BK7": { + "B1": 1.03961212, + "B2": 0.231792344, + "B3": 1.01046945, + "C1 (um^2)": 6.00069867e-3, + "C2 (um^2)": 2.00179144e-2, + "C3 (um^2)": 103.560653, + }, + "FUSED_SILICA": { + "B1": 0.696166300, + "B2": 0.407942600, + "B3": 0.897479400, + "C1 (um^2)": 4.67914826e-3, + "C2 (um^2)": 1.35120631e-2, + "C3 (um^2)": 97.9340025, + }, + } + + def mk_set_preset(self): + return mk_set_preset(self) + +Presets_SellmeierMaxwellMediumNode = _Presets_SellmeierMaxwellMediumNode() + +class SellmeierMaxwellMediumNode(types.MaxwellSimTreeNode, bpy.types.Node): + bl_idname = types.SellmeierMaxwellMediumNodeType + bl_label = "Sellmeier" + bl_icon = constants.tree_constants.ICON_SIM_MEDIUM + + preset: bpy.props.EnumProperty( + name="Presets", + description="Select a preset", + items=Presets_SellmeierMaxwellMediumNode.descriptions, + default='BK7', + update=Presets_SellmeierMaxwellMediumNode.mk_set_preset(), + ) + + def init(self, context): + # Declare Node Inputs + self.inputs.new('NodeSocketFloat', "B1") + self.inputs.new('NodeSocketFloat', "B2") + self.inputs.new('NodeSocketFloat', "B3") + self.inputs.new('NodeSocketFloat', "C1 (um^2)") + self.inputs.new('NodeSocketFloat', "C2 (um^2)") + self.inputs.new('NodeSocketFloat', "C3 (um^2)") + + # Declare Node Outputs + self.outputs.new(types.tree_types.MaxwellMediumSocketType, "Medium") + + # Set Preset Values + Presets_SellmeierMaxwellMediumNode.mk_set_preset()(self, context) + + def draw_buttons(self, context, layout): + layout.prop(self, 'sellmeier_presets', text="") + + + +#################### +# - Blender Registration +#################### +BL_REGISTER = [ + SellmeierMaxwellMediumNode, +] diff --git a/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/definitions/simulations.py b/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/definitions/simulations.py new file mode 100644 index 0000000..076e620 --- /dev/null +++ b/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/definitions/simulations.py @@ -0,0 +1,49 @@ +import bpy +import tidy3d as td + +from .. import types, constants + +class FDTDMaxwellSimulationNode(types.MaxwellSimTreeNode, bpy.types.Node): + bl_idname = types.FDTDMaxwellSimulationNodeType + bl_label = "FDTD" + bl_icon = constants.tree_constants.ICON_SIM_SIMULATION + + input_sockets = { + "run_time": ("NodeSocketFloatTime", "Run Time"), + "ambient_medium": (types.tree_types.MaxwellMediumSocketType, "Ambient Medium"), + "source": (types.tree_types.MaxwellSourceSocketType, "Source"), + "structure": (types.tree_types.MaxwellStructureSocketType, "Structure"), + "bound": (types.tree_types.MaxwellBoundSocketType, "Bound"), + } + output_sockets = { + "fdtd_sim": (types.tree_types.MaxwellFDTDSimSocketType, "FDTD Sim") + } + + #################### + # - Socket Properties + #################### + @types.output_socket_cb("fdtd_sim") + def output_source(self): + return td.Simulation( + #center= ## Default: 0,0,0 + size=(1, 1, 1), ## PLACEHOLDER + run_time=self.compute_input("run_time"), + #structures=[], + #symmetry=, + sources=[self.compute_input("source")], + #boundary_spec=, ## Default: PML + monitors=[], + #grid_spec=, ## Default: Autogrid + #shutoff=, ## Default: 1e-05 + #subpixel=, ## Default: True + #normalize_index=, ## Default: 0 + #courant=, ## Default: 0.99 + ) + + +#################### +# - Blender Registration +#################### +BL_REGISTER = [ + FDTDMaxwellSimulationNode, +] diff --git a/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/definitions/sources.py b/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/definitions/sources.py new file mode 100644 index 0000000..4bd94a6 --- /dev/null +++ b/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/definitions/sources.py @@ -0,0 +1,63 @@ +import bpy +from .. import types, constants + +import tidy3d as td + +class PointDipoleMaxwellSourceNode(types.MaxwellSimTreeNode, bpy.types.Node): + bl_idname = types.PointDipoleMaxwellSourceNodeType + bl_label = "Point Dipole" + bl_icon = constants.tree_constants.ICON_SIM_SOURCE + + input_sockets = { + "center": ("NodeSocketVector", "Center"), + "interpolate": ("NodeSocketBool", "Interpolate"), + } + output_sockets = { + "source": (types.tree_types.MaxwellSourceSocketType, "Source") + } + + #################### + # - Properties + #################### + polarization: bpy.props.EnumProperty( + name="Polarization", + description="Polarization of the generated point dipole field", + items=[ + ("Ex", "Ex", "x-component of E-field"), + ("Ey", "Ey", "y-component of E-field"), + ("Ez", "Ez", "z-component of E-field"), + ("Hx", "Hx", "x-component of H-field"), + ("Hy", "Hy", "y-component of H-field"), + ("Hz", "Hz", "z-component of H-field"), + ], + default="Ex", + ) + + #################### + # - Node UI and Layout + #################### + def draw_buttons(self, context, layout): + layout.prop(self, 'polarization', text="") + + #################### + # - Socket Properties + #################### + @types.output_socket_cb("source") + def output_source(self): + return td.PointDipole( + center=tuple(self.compute_input("center")), + size=(0, 0, 0), + source_time=td.GaussianPulse(freq0=200e12, fwidth=200e12), + ## ^ Placeholder + interpolate=self.compute_input("interpolate"), + polarization=str(self.polarization), + ) + + + +#################### +# - Blender Registration +#################### +BL_REGISTER = [ + PointDipoleMaxwellSourceNode, +] diff --git a/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/definitions/structures.py b/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/definitions/structures.py new file mode 100644 index 0000000..2dcae8e --- /dev/null +++ b/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/definitions/structures.py @@ -0,0 +1,25 @@ +import bpy +from .. import types, constants + +class TriMeshMaxwellStructureNode(types.MaxwellSimTreeNode, bpy.types.Node): + bl_idname = types.TriMeshMaxwellStructureNodeType + bl_label = "TriMesh" + bl_icon = constants.tree_constants.ICON_SIM_STRUCTURE + + # Initialization - Called on Node Creation + def init(self, context): + # Declare Node Inputs + self.inputs.new('NodeSocketObject', "Object") + self.inputs.new(types.tree_types.MaxwellMediumSocketType, "Medium") + + # Declare Node Outputs + self.outputs.new(types.tree_types.MaxwellStructureSocketType, "Structure") + + + +#################### +# - Blender Registration +#################### +BL_REGISTER = [ + TriMeshMaxwellStructureNode, +] diff --git a/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/types.py b/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/types.py new file mode 100644 index 0000000..058010b --- /dev/null +++ b/code/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/types.py @@ -0,0 +1,125 @@ +import bpy +import numpy as np +from .. import types as tree_types + +#################### +# - Blender Types +#################### +DebugPrinterNodeType = 'DebugPrinterNodeType' + +PointDipoleMaxwellSourceNodeType = 'PointDipoleMaxwellSourceNodeType' + +SellmeierMaxwellMediumNodeType = 'SellmeierMaxwellMediumNodeType' + +TriMeshMaxwellStructureNodeType = 'TriMeshMaxwellStructureNodeType' + +PMLMaxwellBoundNodeType = 'PMLMaxwellBoundNodeType' + +FDTDMaxwellSimulationNodeType = 'FDTDMaxwellSimulationNodeType' + +#################### +# - Node Superclass +#################### +def output_socket_cb(name): + def decorator(func): + func._output_socket_name = name # Set a marker attribute + return func + return decorator + +SOCKET_CAST_MAP = { + "NodeSocketBool": bool, + "NodeSocketFloat": float, + "NodeSocketFloatAngle": float, + "NodeSocketFloatDistance": float, + "NodeSocketFloatFactor": float, + "NodeSocketFloatPercentage": float, + "NodeSocketFloatTime": float, + "NodeSocketFloatTimeAbsolute": float, + "NodeSocketFloatUnsigned": float, + "NodeSocketFloatInt": int, + "NodeSocketFloatIntFactor": int, + "NodeSocketFloatIntPercentage": int, + "NodeSocketFloatIntUnsigned": int, + "NodeSocketString": str, + "NodeSocketVector": np.array, + "NodeSocketVectorAcceleration": np.array, + "NodeSocketVectorDirection": np.array, + "NodeSocketVectorTranslation": np.array, + "NodeSocketVectorVelocity": np.array, + "NodeSocketVectorXYZ": np.array, +} +class MaxwellSimTreeNode(bpy.types.Node): + def __init_subclass__(cls, **kwargs): + super().__init_subclass__(**kwargs) + required_attrs = [ + 'bl_idname', + 'bl_label', + 'bl_icon', + 'input_sockets', + 'output_sockets', + ] + for attr in required_attrs: + if getattr(cls, attr, None) is None: + raise TypeError( + f"class {cls.__name__} is missing required '{attr}' attribute" + ) + + #################### + # - Node Initialization + #################### + def init(self, context): + for input_socket_name in self.input_sockets: + self.inputs.new(*self.input_sockets[input_socket_name][:2]) + + for output_socket_name in self.output_sockets: + self.outputs.new(*self.output_sockets[output_socket_name][:2]) + + #################### + # - Node Computation + #################### + def compute_input(self, input_socket_name: str): + """Computes the value of an input socket name. + """ + bl_socket_type = self.input_sockets[input_socket_name][0] + bl_socket = self.inputs[self.input_sockets[input_socket_name][1]] + + if bl_socket.is_linked: + linked_node = bl_socket.links[0].from_node + linked_bl_socket_name = bl_socket.links[0].from_socket.name + result = linked_node.compute_output(linked_bl_socket_name) + else: + result = bl_socket.default_value + + if bl_socket_type in SOCKET_CAST_MAP: + return SOCKET_CAST_MAP[bl_socket_type](result) + + return result + + def compute_output(self, output_bl_socket_name: str): + """Computes the value of an output socket name, from its Blender display name. + """ + output_socket_name = next( + output_socket_name + for output_socket_name in self.output_sockets.keys() + if self.output_sockets[output_socket_name][1] == output_bl_socket_name + ) + + output_socket_name_to_cb = { + getattr(attr, '_output_socket_name'): attr + for attr_name in dir(self) + if ( + callable(attr := getattr(self, attr_name)) + and hasattr(attr, '_output_socket_name') + ) + } + + return output_socket_name_to_cb[output_socket_name]() + + #################### + # - Blender Configuration + #################### + @classmethod + def poll(cls, ntree): + """Constrain node instantiation to within a MaxwellSimTree.""" + + return ntree.bl_idname == tree_types.MaxwellSimTreeType diff --git a/code/blender_maxwell/node_trees/maxwell_sim_nodes/socket_types.py b/code/blender_maxwell/node_trees/maxwell_sim_nodes/socket_types.py new file mode 100644 index 0000000..bb52809 --- /dev/null +++ b/code/blender_maxwell/node_trees/maxwell_sim_nodes/socket_types.py @@ -0,0 +1,71 @@ +import bpy + +from . import constants, types + +class MaxwellSourceSocket(bpy.types.NodeSocket): + bl_idname = types.MaxwellSourceSocketType + bl_label = "Maxwell Source" + + def draw(self, context, layout, node, text): + layout.label(text=text) + + @classmethod + def draw_color_simple(cls): + return constants.COLOR_SOCKET_SOURCE + + +class MaxwellMediumSocket(bpy.types.NodeSocket): + bl_idname = types.MaxwellMediumSocketType + bl_label = "Maxwell Medium" + + def draw(self, context, layout, node, text): + layout.label(text=text) + + @classmethod + def draw_color_simple(cls): + return constants.COLOR_SOCKET_MEDIUM + +class MaxwellStructureSocket(bpy.types.NodeSocket): + bl_idname = types.MaxwellStructureSocketType + bl_label = "Maxwell Structure" + + def draw(self, context, layout, node, text): + layout.label(text=text) + + @classmethod + def draw_color_simple(cls): + return constants.COLOR_SOCKET_STRUCTURE + +class MaxwellBoundSocket(bpy.types.NodeSocket): + bl_idname = types.MaxwellBoundSocketType + bl_label = "Maxwell Bound" + + def draw(self, context, layout, node, text): + layout.label(text=text) + + @classmethod + def draw_color_simple(cls): + return constants.COLOR_SOCKET_BOUND + +class MaxwellFDTDSimSocket(bpy.types.NodeSocket): + bl_idname = types.MaxwellFDTDSimSocketType + bl_label = "Maxwell Bound" + + def draw(self, context, layout, node, text): + layout.label(text=text) + + @classmethod + def draw_color_simple(cls): + return constants.COLOR_SOCKET_FDTDSIM + + + +#################### +# - Blender Registration +#################### +BL_REGISTER = [ + MaxwellSourceSocket, + MaxwellMediumSocket, + MaxwellStructureSocket, + MaxwellBoundSocket, +] diff --git a/code/blender_maxwell/node_trees/maxwell_sim_nodes/tree.py b/code/blender_maxwell/node_trees/maxwell_sim_nodes/tree.py new file mode 100644 index 0000000..1f52441 --- /dev/null +++ b/code/blender_maxwell/node_trees/maxwell_sim_nodes/tree.py @@ -0,0 +1,17 @@ +import bpy + +from . import types, constants + +class MaxwellSimTree(bpy.types.NodeTree): + bl_idname = types.MaxwellSimTreeType + bl_label = "Maxwell Sim Editor" + bl_icon = constants.ICON_SIM ## Icon ID + + + +#################### +# - Blender Registration +#################### +BL_REGISTER = [ + MaxwellSimTree, +] diff --git a/code/blender_maxwell/node_trees/maxwell_sim_nodes/types.py b/code/blender_maxwell/node_trees/maxwell_sim_nodes/types.py new file mode 100644 index 0000000..66113f6 --- /dev/null +++ b/code/blender_maxwell/node_trees/maxwell_sim_nodes/types.py @@ -0,0 +1,12 @@ +import bpy + +#################### +# - Blender Types +#################### +MaxwellSimTreeType = 'MaxwellSimTreeType' + +MaxwellSourceSocketType = 'MaxwellSourceSocketType' +MaxwellMediumSocketType = 'MaxwellMediumSocketType' +MaxwellStructureSocketType = 'MaxwellStructureSocketType' +MaxwellBoundSocketType = 'MaxwellBoundSocketType' +MaxwellFDTDSimSocketType = 'MaxwellFDTDSimSocketType' diff --git a/code/blender_maxwell/node_trees/maxwell_viz_nodes/__init__.py b/code/blender_maxwell/node_trees/maxwell_viz_nodes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/code/blender_maxwell/operators/__init__.py b/code/blender_maxwell/operators/__init__.py new file mode 100644 index 0000000..76a0da7 --- /dev/null +++ b/code/blender_maxwell/operators/__init__.py @@ -0,0 +1,7 @@ +from . import install_deps +from . import uninstall_deps + +BL_REGISTER = [ + *install_deps.BL_REGISTER, + *uninstall_deps.BL_REGISTER, +] diff --git a/code/blender_maxwell/operators/install_deps.py b/code/blender_maxwell/operators/install_deps.py new file mode 100644 index 0000000..47c913b --- /dev/null +++ b/code/blender_maxwell/operators/install_deps.py @@ -0,0 +1,60 @@ +import sys +import subprocess +from pathlib import Path + +import bpy + +from . import types + +class BlenderMaxwellInstallDependenciesOperator(bpy.types.Operator): + bl_idname = types.BlenderMaxwellInstallDependencies + bl_label = "Install Dependencies for Blender Maxwell Addon" + + def execute(self, context): + addon_dir = Path(__file__).parent.parent + requirements_path = addon_dir / 'requirements.txt' + #addon_specific_folder = addon_dir / '.dependencies' + addon_specific_folder = Path("/home/sofus/src/college/bsc_ge/thesis/code/.cached-dependencies") + + # Create the Addon-Specific Folder + addon_specific_folder.mkdir(parents=True, exist_ok=True) + + # Determine Path to Blender's Bundled Python + python_exec = Path(sys.executable) + ## bpy.app.binary_path_python was deprecated in 2.91. + ## sys.executable points to the correct bundled Python. + ## See + + # Install Dependencies w/Bundled pip + try: + subprocess.check_call([ + str(python_exec), '-m', + 'pip', 'install', + '-r', str(requirements_path), + '--target', str(addon_specific_folder), + ]) + self.report( + {'INFO'}, + "Dependencies for 'blender_maxwell' installed successfully." + ) + except subprocess.CalledProcessError as e: + self.report( + {'ERROR'}, + f"Failed to install dependencies: {str(e)}" + ) + return {'CANCELLED'} + + # Install Dependencies w/Bundled pip + if str(addon_specific_folder) not in sys.path: + sys.path.insert(0, str(addon_specific_folder)) + + return {'FINISHED'} + + + +#################### +# - Blender Registration +#################### +BL_REGISTER = [ + BlenderMaxwellInstallDependenciesOperator, +] diff --git a/code/blender_maxwell/operators/types.py b/code/blender_maxwell/operators/types.py new file mode 100644 index 0000000..1ad0607 --- /dev/null +++ b/code/blender_maxwell/operators/types.py @@ -0,0 +1,7 @@ +import bpy + +#################### +# - Blender Types +#################### +BlenderMaxwellInstallDependencies = "blender_maxwell.install_dependencies" +BlenderMaxwellUninstallDependencies = "blender_maxwell.uninstall_dependencies" diff --git a/code/blender_maxwell/operators/uninstall_deps.py b/code/blender_maxwell/operators/uninstall_deps.py new file mode 100644 index 0000000..5f8043b --- /dev/null +++ b/code/blender_maxwell/operators/uninstall_deps.py @@ -0,0 +1,30 @@ +import sys +import shutil +import subprocess +from pathlib import Path + +import bpy + +from . import types + +class BlenderMaxwellUninstallDependenciesOperator(bpy.types.Operator): + bl_idname = types.BlenderMaxwellUninstallDependencies + bl_label = "Uninstall Dependencies for Blender Maxwell Addon" + + def execute(self, context): + addon_dir = Path(__file__).parent.parent + #addon_specific_folder = addon_dir / '.dependencies' + addon_specific_folder = Path("/home/sofus/src/college/bsc_ge/thesis/code/.cached-dependencies") + + shutil.rmtree(addon_specific_folder) + + return {'FINISHED'} + + + +#################### +# - Blender Registration +#################### +BL_REGISTER = [ + BlenderMaxwellUninstallDependenciesOperator, +] diff --git a/code/blender_maxwell/preferences.py b/code/blender_maxwell/preferences.py new file mode 100644 index 0000000..d5647bb --- /dev/null +++ b/code/blender_maxwell/preferences.py @@ -0,0 +1,18 @@ +import bpy + +from .operators import types as operators_types + +class BlenderMaxwellAddonPreferences(bpy.types.AddonPreferences): + bl_idname = "blender_maxwell" + + def draw(self, context): + layout = self.layout + layout.operator(operators_types.BlenderMaxwellInstallDependencies, text="Install Dependencies") + layout.operator(operators_types.BlenderMaxwellUninstallDependencies, text="Uninstall Dependencies") + +#################### +# - Blender Registration +#################### +BL_REGISTER = [ + BlenderMaxwellAddonPreferences, +] diff --git a/code/blender_maxwell/requirements.txt b/code/blender_maxwell/requirements.txt new file mode 100644 index 0000000..e06b29a --- /dev/null +++ b/code/blender_maxwell/requirements.txt @@ -0,0 +1,2 @@ +tidy3d==2.5.2 +pydantic==2.6.0 diff --git a/code/blender_maxwell/utils/__init__.py b/code/blender_maxwell/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/code/demo.blend b/code/demo.blend new file mode 100644 index 0000000..c608c90 --- /dev/null +++ b/code/demo.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7355e105639a37ff0bb862e27ce482f233e63aa1162d280c7ed8e7a1a3eeceaf +size 742424 diff --git a/code/run.py b/code/run.py new file mode 100644 index 0000000..7f69c58 --- /dev/null +++ b/code/run.py @@ -0,0 +1,108 @@ +import zipfile +import contextlib +import shutil +import sys +from pathlib import Path + +import bpy +import addon_utils + +PATH_ROOT = Path(__file__).resolve().parent + +#################### +# - Defined Constants +#################### +ADDON_NAME = "blender_maxwell" +PATH_BLEND = PATH_ROOT / "demo.blend" +PATH_ADDON_DEPS = PATH_ROOT / ".cached-dependencies" + +#################### +# - Computed Constants +#################### +PATH_ADDON = PATH_ROOT / ADDON_NAME +PATH_ADDON_ZIP = PATH_ROOT / (ADDON_NAME + ".zip") + +#################### +# - Utilities +#################### +@contextlib.contextmanager +def zipped_directory(path_dir: Path, path_zip: Path): + """Context manager that exposes a zipped version of a directory, + then deletes the .zip file afterwards. + """ + # Delete Existing ZIP file (if exists) + if path_zip.is_file(): path_zip.unlink() + + # Create a (new) ZIP file of the addon directory + with zipfile.ZipFile(path_zip, 'w', zipfile.ZIP_DEFLATED) as f_zip: + for file_to_zip in path_dir.rglob('*'): + f_zip.write(file_to_zip, file_to_zip.relative_to(path_dir.parent)) + + # Delete the ZIP + try: + yield path_zip + finally: + path_zip.unlink() + +#################### +# - main() +#################### +if __name__ == "__main__": + # Check and uninstall the addon if it's enabled + is_loaded_by_default, is_loaded_now = addon_utils.check(ADDON_NAME) + if is_loaded_now: + # Disable the Addon + addon_utils.disable(ADDON_NAME, default_set=True, handle_error=None) + + # Completey Delete the Addon + for mod in addon_utils.modules(): + if mod.__name__ == ADDON_NAME: + # Delete Addon from Blender Python Tree + shutil.rmtree(Path(mod.__file__).parent) + + # Reset All Addons + addon_utils.reset_all() + + # Save User Preferences & Break + bpy.ops.wm.save_userpref() + break + + # Quit Blender (hard-flush Python environment) + ## - Python environments are not made to be partially flushed. + ## - This is the only truly reliable way to avoid all bugs. + ## - See https://github.com/JacquesLucke/blender_vscode + bpy.ops.wm.quit_blender() + try: + raise RuntimeError + except: + sys.exit(42) + + with zipped_directory(PATH_ADDON, PATH_ADDON_ZIP) as path_zipped: + # Install the ZIPped Addon + bpy.ops.preferences.addon_install(filepath=str(path_zipped)) + + # Enable the Addon + addon_utils.enable( + ADDON_NAME, + default_set=True, + persistent=True, + handle_error=None, + ) + + # Save User Preferences + bpy.ops.wm.save_userpref() + + # Load the .blend + bpy.ops.wm.open_mainfile(filepath=str(PATH_BLEND)) + + # Ensure Addon-Specific Dependency Cache is Importable + ## - In distribution, the addon keeps this folder in the Blender script tree. + ## - For testing, we need to hack sys.path here. + ## - This avoids having to install all deps with every reload. + if str(PATH_ADDON_DEPS) not in sys.path: + sys.path.insert(0, str(PATH_ADDON_DEPS)) + + # Modify any specific settings, if needed + # Example: bpy.context.preferences.addons[addon_name].preferences.your_setting = "your_value" + + diff --git a/code/run.sh b/code/run.sh new file mode 100644 index 0000000..2167806 --- /dev/null +++ b/code/run.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +blender --python run.py +if [ $? -eq 42 ]; then + blender --python run.py +fi diff --git a/code/template.blend b/code/template.blend new file mode 100644 index 0000000..6397a7c --- /dev/null +++ b/code/template.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6475230422328c74d45fc9f4db25ca3d1d0ec49f6cc3f539a4ced68fd3a533fd +size 741696