193 lines
5.1 KiB
Python
193 lines
5.1 KiB
Python
import typing as typ
|
|
|
|
import tidy3d as td
|
|
import numpy as np
|
|
import sympy as sp
|
|
import sympy.physics.units as spu
|
|
|
|
import bpy
|
|
from bpy_types import bpy_types
|
|
import bmesh
|
|
|
|
from .....utils import analyze_geonodes
|
|
from ... import bl_socket_map
|
|
from ... import contracts as ct
|
|
from ... import sockets
|
|
from .. import base
|
|
from ... import managed_objs
|
|
|
|
class GeoNodesStructureNode(base.MaxwellSimNode):
|
|
node_type = ct.NodeType.GeoNodesStructure
|
|
bl_label = "GeoNodes Structure"
|
|
|
|
####################
|
|
# - Sockets
|
|
####################
|
|
input_sockets = {
|
|
"Unit System": sockets.PhysicalUnitSystemSocketDef(),
|
|
"Medium": sockets.MaxwellMediumSocketDef(),
|
|
"GeoNodes": sockets.BlenderGeoNodesSocketDef(),
|
|
}
|
|
output_sockets = {
|
|
"Structure": sockets.MaxwellStructureSocketDef(),
|
|
}
|
|
|
|
managed_obj_defs = {
|
|
"geometry": ct.schemas.ManagedObjDef(
|
|
mk=lambda name: managed_objs.ManagedBLObject(name),
|
|
name_prefix="geonodes_",
|
|
)
|
|
}
|
|
|
|
####################
|
|
# - Output Socket Computation
|
|
####################
|
|
@base.computes_output_socket(
|
|
"Structure",
|
|
input_sockets={"Medium"},
|
|
managed_objs={"geometry"},
|
|
)
|
|
def compute_structure(
|
|
self,
|
|
input_sockets: dict[str, typ.Any],
|
|
managed_objs: dict[str, ct.schemas.ManagedObj],
|
|
) -> td.Structure:
|
|
# Extract the Managed Blender Object
|
|
mobj = managed_objs["geometry"]
|
|
|
|
# Extract Geometry as Arrays
|
|
geometry_as_arrays = mobj.mesh_as_arrays
|
|
|
|
# Return TriMesh Structure
|
|
return td.Structure(
|
|
geometry=td.TriangleMesh.from_vertices_faces(
|
|
geometry_as_arrays["verts"],
|
|
geometry_as_arrays["faces"],
|
|
),
|
|
medium=input_sockets["Medium"],
|
|
)
|
|
|
|
####################
|
|
# - Event Methods
|
|
####################
|
|
@base.on_value_changed(
|
|
socket_name="GeoNodes",
|
|
|
|
managed_objs={"geometry"},
|
|
input_sockets={"GeoNodes"},
|
|
)
|
|
def on_value_changed__geonodes(
|
|
self,
|
|
managed_objs: dict[str, ct.schemas.ManagedObj],
|
|
input_sockets: dict[str, typ.Any],
|
|
) -> None:
|
|
"""Called whenever the GeoNodes socket is changed.
|
|
|
|
Refreshes the Loose Input Sockets, which map directly to the GeoNodes tree input sockets.
|
|
"""
|
|
if not (geo_nodes := input_sockets["GeoNodes"]):
|
|
managed_objs["geometry"].free()
|
|
self.loose_input_sockets = {}
|
|
return
|
|
|
|
# Analyze GeoNodes
|
|
## Extract Valid Inputs (via GeoNodes Tree "Interface")
|
|
geonodes_interface = analyze_geonodes.interface(
|
|
geo_nodes, direc="INPUT"
|
|
)
|
|
|
|
# Set Loose Input Sockets
|
|
## Retrieve the appropriate SocketDef for the Blender Interface Socket
|
|
self.loose_input_sockets = {
|
|
socket_name: bl_socket_map.socket_def_from_bl_interface_socket(
|
|
bl_interface_socket
|
|
)() ## === <SocketType>SocketDef(), but with dynamic SocketDef
|
|
for socket_name, bl_interface_socket in geonodes_interface.items()
|
|
}
|
|
|
|
## Set Loose `socket.value` from Interface `default_value`
|
|
for socket_name in self.loose_input_sockets:
|
|
socket = self.inputs[socket_name]
|
|
bl_interface_socket = geonodes_interface[socket_name]
|
|
|
|
socket.value = bl_socket_map.value_from_bl(bl_interface_socket)
|
|
|
|
## Implicitly triggers the loose-input `on_value_changed` for each.
|
|
|
|
@base.on_value_changed(
|
|
any_loose_input_socket=True,
|
|
|
|
managed_objs={"geometry"},
|
|
input_sockets={"Unit System", "GeoNodes"},
|
|
)
|
|
def on_value_changed__loose_inputs(
|
|
self,
|
|
managed_objs: dict[str, ct.schemas.ManagedObj],
|
|
input_sockets: dict[str, typ.Any],
|
|
loose_input_sockets: dict[str, typ.Any],
|
|
):
|
|
"""Called whenever a Loose Input Socket is altered.
|
|
|
|
Synchronizes the change to the actual GeoNodes modifier, so that the change is immediately visible.
|
|
"""
|
|
# Retrieve Data
|
|
unit_system = input_sockets["Unit System"]
|
|
mobj = managed_objs["geometry"]
|
|
|
|
if not (geo_nodes := input_sockets["GeoNodes"]): return
|
|
|
|
# Analyze GeoNodes Interface (input direction)
|
|
## This retrieves NodeTreeSocketInterface elements
|
|
geonodes_interface = analyze_geonodes.interface(
|
|
geo_nodes, direc="INPUT"
|
|
)
|
|
|
|
## TODO: Check that Loose Sockets matches the Interface
|
|
## - If the user deletes an interface socket, bad things will happen.
|
|
## - We will try to set an identifier that doesn't exist!
|
|
## - Instead, this should update the loose input sockets.
|
|
|
|
## Push Values to the GeoNodes Modifier
|
|
mobj.sync_geonodes_modifier(
|
|
geonodes_node_group=geo_nodes,
|
|
geonodes_identifier_to_value={
|
|
bl_interface_socket.identifier: bl_socket_map.value_to_bl(
|
|
bl_interface_socket,
|
|
loose_input_sockets[socket_name],
|
|
unit_system,
|
|
)
|
|
for socket_name, bl_interface_socket in (
|
|
geonodes_interface.items()
|
|
)
|
|
}
|
|
)
|
|
|
|
####################
|
|
# - Event Methods
|
|
####################
|
|
@base.on_show_preview(
|
|
managed_objs={"geometry"},
|
|
)
|
|
def on_show_preview(
|
|
self,
|
|
managed_objs: dict[str, ct.schemas.ManagedObj],
|
|
):
|
|
"""Called whenever a Loose Input Socket is altered.
|
|
|
|
Synchronizes the change to the actual GeoNodes modifier, so that the change is immediately visible.
|
|
"""
|
|
managed_objs["geometry"].show_preview("MESH")
|
|
|
|
|
|
####################
|
|
# - Blender Registration
|
|
####################
|
|
BL_REGISTER = [
|
|
GeoNodesStructureNode,
|
|
]
|
|
BL_NODES = {
|
|
ct.NodeType.GeoNodesStructure: (
|
|
ct.NodeCategory.MAXWELLSIM_STRUCTURES
|
|
)
|
|
}
|