enhance: Streamlined GN asset system
parent
4e1eb19a88
commit
0599eed2b2
|
@ -108,7 +108,7 @@ quartodoc:
|
||||||
desc: Blender assets bundled w/Blender Maxwell
|
desc: Blender assets bundled w/Blender Maxwell
|
||||||
contents:
|
contents:
|
||||||
- assets
|
- assets
|
||||||
- assets.import_geonodes
|
- assets.geonodes
|
||||||
|
|
||||||
- subtitle: "`bl_maxwell.nodeps`"
|
- subtitle: "`bl_maxwell.nodeps`"
|
||||||
desc: No-Dependency
|
desc: No-Dependency
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
from . import import_geonodes
|
from . import geonodes
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
*import_geonodes.BL_REGISTER,
|
*geonodes.BL_REGISTER,
|
||||||
]
|
]
|
||||||
|
|
||||||
BL_HOTKEYS = [
|
BL_HOTKEYS = [
|
||||||
*import_geonodes.BL_HOTKEYS,
|
*geonodes.BL_HOTKEYS,
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|
BIN
src/blender_maxwell/assets/assets.blend (Stored with Git LFS)
BIN
src/blender_maxwell/assets/assets.blend (Stored with Git LFS)
Binary file not shown.
|
@ -1,10 +0,0 @@
|
||||||
# This is an Asset Catalog Definition file for Blender.
|
|
||||||
#
|
|
||||||
# Empty lines and lines starting with `#` will be ignored.
|
|
||||||
# The first non-ignored line should be the version indicator.
|
|
||||||
# Other lines are of the format "UUID:catalog/path/for/assets:simple catalog name"
|
|
||||||
|
|
||||||
VERSION 1
|
|
||||||
|
|
||||||
783a7efe-c424-42b1-9771-42c862515891:Structures:Structures
|
|
||||||
ccb80eec-7e20-453d-89fb-0486b7abf7d4:Structures/Primitives:Structures-Primitives
|
|
|
@ -0,0 +1,574 @@
|
||||||
|
"""Provides for the linking and/or appending of geometry nodes trees from vendored libraries included in Blender maxwell."""
|
||||||
|
|
||||||
|
import enum
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
from blender_maxwell import contracts as ct
|
||||||
|
from blender_maxwell.utils import logger
|
||||||
|
|
||||||
|
log = logger.get(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - GeoNodes Specification
|
||||||
|
####################
|
||||||
|
# GeoNodes Paths
|
||||||
|
## Internal
|
||||||
|
GN_INTERNAL_PATH = ct.addon.PATH_ASSETS / 'internal'
|
||||||
|
GN_INTERNAL_INPUTS_PATH = GN_INTERNAL_PATH / 'input'
|
||||||
|
GN_INTERNAL_SOURCES_PATH = GN_INTERNAL_PATH / 'source'
|
||||||
|
GN_INTERNAL_STRUCTURES_PATH = GN_INTERNAL_PATH / 'structure'
|
||||||
|
GN_INTERNAL_MONITORS_PATH = GN_INTERNAL_PATH / 'monitor'
|
||||||
|
GN_INTERNAL_SIMULATIONS_PATH = GN_INTERNAL_PATH / 'simulation'
|
||||||
|
|
||||||
|
## Structures
|
||||||
|
GN_STRUCTURES_PATH = ct.addon.PATH_ASSETS / 'structures'
|
||||||
|
GN_STRUCTURES_PRIMITIVES_PATH = GN_STRUCTURES_PATH / 'primitives'
|
||||||
|
GN_STRUCTURES_ARRAYS_PATH = GN_STRUCTURES_PATH / 'arrays'
|
||||||
|
|
||||||
|
|
||||||
|
class GeoNodes(enum.StrEnum):
|
||||||
|
"""Defines the names of available GeoNodes groups vendored as part of Blender Maxwell.
|
||||||
|
|
||||||
|
The values define both name of the .blend file containing the GeoNodes group, and the GeoNodes group itself.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Node Previews
|
||||||
|
## Input
|
||||||
|
InputConstantPhysicalPol = '_input_constant_physical_pol'
|
||||||
|
## Source
|
||||||
|
SourcePointDipole = '_source_point_dipole'
|
||||||
|
SourcePlaneWave = '_source_plane_wave'
|
||||||
|
SourceUniformCurrent = '_source_uniform_current'
|
||||||
|
SourceTFSF = '_source_tfsf'
|
||||||
|
SourceGaussianBeam = '_source_gaussian_beam'
|
||||||
|
SourceAstigmaticGaussianBeam = '_source_astigmatic_gaussian_beam'
|
||||||
|
SourceMode = '_source_mode'
|
||||||
|
SourceEHArray = '_source_eh_array'
|
||||||
|
SourceEHEquivArray = '_source_eh_equiv_array'
|
||||||
|
## Structure
|
||||||
|
StructurePrimitivePlane = '_structure_primitive_plane'
|
||||||
|
StructurePrimitiveBox = '_structure_primitive_box'
|
||||||
|
StructurePrimitiveSphere = '_structure_primitive_sphere'
|
||||||
|
StructurePrimitiveCylinder = '_structure_primitive_cylinder'
|
||||||
|
StructurePrimitiveRing = '_structure_primitive_ring'
|
||||||
|
StructurePrimitiveCapsule = '_structure_primitive_capsule'
|
||||||
|
StructurePrimitiveCone = '_structure_primitive_cone'
|
||||||
|
## Monitor
|
||||||
|
MonitorEHField = '_monitor_eh_field'
|
||||||
|
MonitorPowerFlux = '_monitor_power_flux'
|
||||||
|
MonitorEpsTensor = '_monitor_eps_tensor'
|
||||||
|
MonitorDiffraction = '_monitor_diffraction'
|
||||||
|
MonitorProjCartEHField = '_monitor_proj_eh_field'
|
||||||
|
MonitorProjAngEHField = '_monitor_proj_ang_eh_field'
|
||||||
|
MonitorProjKSpaceEHField = '_monitor_proj_k_space_eh_field'
|
||||||
|
## Simulation
|
||||||
|
SimulationSimDomain = '_simulation_sim_domain'
|
||||||
|
SimulationBoundConds = '_simulation_bound_conds'
|
||||||
|
SimulationBoundCondPML = '_simulation_bound_cond_pml'
|
||||||
|
SimulationBoundCondPEC = '_simulation_bound_cond_pec'
|
||||||
|
SimulationBoundCondPMC = '_simulation_bound_cond_pmc'
|
||||||
|
SimulationBoundCondBloch = '_simulation_bound_cond_bloch'
|
||||||
|
SimulationBoundCondPeriodic = '_simulation_bound_cond_periodic'
|
||||||
|
SimulationBoundCondAbsorbing = '_simulation_bound_cond_absorbing'
|
||||||
|
SimulationSimGrid = '_simulation_sim_grid'
|
||||||
|
SimulationSimGridAxisAuto = '_simulation_sim_grid_axis_auto'
|
||||||
|
SimulationSimGridAxisManual = '_simulation_sim_grid_axis_manual'
|
||||||
|
SimulationSimGridAxisUniform = '_simulation_sim_grid_axis_uniform'
|
||||||
|
SimulationSimGridAxisArray = '_simulation_sim_grid_axis_array'
|
||||||
|
|
||||||
|
# Structures
|
||||||
|
## Primitives
|
||||||
|
PrimitiveBox = 'box'
|
||||||
|
PrimitiveSphere = 'sphere'
|
||||||
|
PrimitiveCylinder = 'cylinder'
|
||||||
|
PrimitiveRing = 'ring'
|
||||||
|
## Arrays
|
||||||
|
ArrayRing = 'array_ring'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dedicated_node_type(self) -> ct.BLImportMethod:
|
||||||
|
"""Deduces the denode type that implements a vendored GeoNodes tree (usually just "GeoNodes Structure").
|
||||||
|
|
||||||
|
Generally, "GeoNodes Structure' is the generic triangle-mesh node that can do everything.
|
||||||
|
Indeed, one could make perfectly useful simulations exclusively using triangle meshes.
|
||||||
|
|
||||||
|
However, when nodes that use geometry directly supported by `Tidy3D` might be more performant, and support features (ex. differentiable parameters) critical to some simulations.
|
||||||
|
|
||||||
|
To bridge the gap, this method provides a regularized hard-coded method of getting whichever node type is most appropriate for the given structure.
|
||||||
|
|
||||||
|
Warnings:
|
||||||
|
**The strings be manually checked, statically**.
|
||||||
|
Since we don't have easy access to the `contracts` within the sim node tree, these are just plain strings.
|
||||||
|
|
||||||
|
It's not ideal, definitely a lazy workaround, but hey.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The node type (as a string) that should be created to expose the GeoNodes group.
|
||||||
|
"""
|
||||||
|
dedicated_node_map = {
|
||||||
|
GeoNodes.PrimitiveBox: 'BoxStructureNodeType',
|
||||||
|
GeoNodes.PrimitiveSphere: 'SphereStructureNodeType',
|
||||||
|
GeoNodes.PrimitiveCylinder: 'CylinderStructureNodeType',
|
||||||
|
}
|
||||||
|
|
||||||
|
if dedicated_node_map.get(self) is None:
|
||||||
|
return 'GeoNodesStructureNodeType'
|
||||||
|
|
||||||
|
return dedicated_node_map[self]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def import_method(self) -> ct.BLImportMethod:
|
||||||
|
"""Deduces whether a vendored GeoNodes tree should be linked or appended.
|
||||||
|
|
||||||
|
Currently, everything is linked.
|
||||||
|
If the user wants to modify a group, they can always make a local copy of a linked group and just use that.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Either 'link' or 'append'.
|
||||||
|
"""
|
||||||
|
return 'link'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parent_path(self) -> Path:
|
||||||
|
"""Deduces the parent path of a vendored GeoNodes tree.
|
||||||
|
|
||||||
|
Warnings:
|
||||||
|
The guarantee of same-named `.blend` and tree name are not explicitly checked.
|
||||||
|
Also, the validity of GeoNodes tree interface parameters is also unchecked; this must be manually coordinated between the driving node, and the imported tree.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The parent path of the given GeoNodes tree.
|
||||||
|
|
||||||
|
A `.blend` file of the same name as the value of the enum must be **guaranteed** to exist as a direct child of the returned path, and must be **guaranteed** to contain a GeoNodes tree of the same name.
|
||||||
|
"""
|
||||||
|
GN = GeoNodes
|
||||||
|
return {
|
||||||
|
# Node Previews
|
||||||
|
## Input
|
||||||
|
GN.InputConstantPhysicalPol: GN_INTERNAL_INPUTS_PATH,
|
||||||
|
## Source
|
||||||
|
GN.SourcePointDipole: GN_INTERNAL_SOURCES_PATH,
|
||||||
|
GN.SourcePlaneWave: GN_INTERNAL_SOURCES_PATH,
|
||||||
|
GN.SourceUniformCurrent: GN_INTERNAL_SOURCES_PATH,
|
||||||
|
GN.SourceTFSF: GN_INTERNAL_SOURCES_PATH,
|
||||||
|
GN.SourceGaussianBeam: GN_INTERNAL_SOURCES_PATH,
|
||||||
|
GN.SourceAstigmaticGaussianBeam: GN_INTERNAL_SOURCES_PATH,
|
||||||
|
GN.SourceMode: GN_INTERNAL_SOURCES_PATH,
|
||||||
|
GN.SourceEHArray: GN_INTERNAL_SOURCES_PATH,
|
||||||
|
GN.SourceEHEquivArray: GN_INTERNAL_SOURCES_PATH,
|
||||||
|
## Structure
|
||||||
|
GN.StructurePrimitivePlane: GN_INTERNAL_STRUCTURES_PATH,
|
||||||
|
GN.StructurePrimitiveBox: GN_INTERNAL_STRUCTURES_PATH,
|
||||||
|
GN.StructurePrimitiveSphere: GN_INTERNAL_STRUCTURES_PATH,
|
||||||
|
GN.StructurePrimitiveCylinder: GN_INTERNAL_STRUCTURES_PATH,
|
||||||
|
GN.StructurePrimitiveRing: GN_INTERNAL_STRUCTURES_PATH,
|
||||||
|
GN.StructurePrimitiveCapsule: GN_INTERNAL_STRUCTURES_PATH,
|
||||||
|
GN.StructurePrimitiveCone: GN_INTERNAL_STRUCTURES_PATH,
|
||||||
|
## Monitor
|
||||||
|
GN.MonitorEHField: GN_INTERNAL_STRUCTURES_PATH,
|
||||||
|
GN.MonitorPowerFlux: GN_INTERNAL_STRUCTURES_PATH,
|
||||||
|
GN.MonitorEpsTensor: GN_INTERNAL_STRUCTURES_PATH,
|
||||||
|
GN.MonitorDiffraction: GN_INTERNAL_STRUCTURES_PATH,
|
||||||
|
GN.MonitorProjCartEHField: GN_INTERNAL_STRUCTURES_PATH,
|
||||||
|
GN.MonitorProjAngEHField: GN_INTERNAL_STRUCTURES_PATH,
|
||||||
|
GN.MonitorProjKSpaceEHField: GN_INTERNAL_STRUCTURES_PATH,
|
||||||
|
## Simulation
|
||||||
|
GN.SimulationSimDomain: GN_INTERNAL_SIMULATIONS_PATH,
|
||||||
|
GN.SimulationBoundConds: GN_INTERNAL_SIMULATIONS_PATH,
|
||||||
|
GN.SimulationBoundCondPML: GN_INTERNAL_SIMULATIONS_PATH,
|
||||||
|
GN.SimulationBoundCondPEC: GN_INTERNAL_SIMULATIONS_PATH,
|
||||||
|
GN.SimulationBoundCondPMC: GN_INTERNAL_SIMULATIONS_PATH,
|
||||||
|
GN.SimulationBoundCondBloch: GN_INTERNAL_SIMULATIONS_PATH,
|
||||||
|
GN.SimulationBoundCondPeriodic: GN_INTERNAL_SIMULATIONS_PATH,
|
||||||
|
GN.SimulationBoundCondAbsorbing: GN_INTERNAL_SIMULATIONS_PATH,
|
||||||
|
GN.SimulationSimGrid: GN_INTERNAL_SIMULATIONS_PATH,
|
||||||
|
GN.SimulationSimGridAxisAuto: GN_INTERNAL_SIMULATIONS_PATH,
|
||||||
|
GN.SimulationSimGridAxisManual: GN_INTERNAL_SIMULATIONS_PATH,
|
||||||
|
GN.SimulationSimGridAxisUniform: GN_INTERNAL_SIMULATIONS_PATH,
|
||||||
|
GN.SimulationSimGridAxisArray: GN_INTERNAL_SIMULATIONS_PATH,
|
||||||
|
# Structures
|
||||||
|
## Primitives
|
||||||
|
GN.PrimitiveBox: GN_STRUCTURES_PRIMITIVES_PATH,
|
||||||
|
GN.PrimitiveRing: GN_STRUCTURES_PRIMITIVES_PATH,
|
||||||
|
GN.PrimitiveSphere: GN_STRUCTURES_PRIMITIVES_PATH,
|
||||||
|
## Arrays
|
||||||
|
GN.ArrayRing: GN_STRUCTURES_ARRAYS_PATH,
|
||||||
|
}[self]
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Import GeoNodes (Link/Append)
|
||||||
|
####################
|
||||||
|
def import_geonodes(
|
||||||
|
_geonodes: GeoNodes,
|
||||||
|
force_append: bool = False,
|
||||||
|
) -> bpy.types.GeometryNodeGroup:
|
||||||
|
"""Given vendored GeoNodes tree link/append and return the local datablock.
|
||||||
|
|
||||||
|
- `GeoNodes.import_method` is used to determine whether it should be linked or imported.
|
||||||
|
- `GeoNodes.parent_path` is used to determine
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
geonodes: The (name of the) GeoNodes group, which ships with Blender Maxwell.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A GeoNodes group available in the current .blend file, which can ex. be attached to a 'GeoNodes Structure' node.
|
||||||
|
"""
|
||||||
|
# Parse Input
|
||||||
|
geonodes = GeoNodes(_geonodes)
|
||||||
|
import_method = geonodes.import_method
|
||||||
|
|
||||||
|
# Linked: Don't Re-Link
|
||||||
|
if import_method == 'link' and geonodes in bpy.data.node_groups:
|
||||||
|
return bpy.data.node_groups[geonodes]
|
||||||
|
|
||||||
|
filename = geonodes
|
||||||
|
filepath = str(geonodes.parent_path / (geonodes + '.blend') / 'NodeTree' / geonodes)
|
||||||
|
directory = filepath.removesuffix(geonodes)
|
||||||
|
log.info(
|
||||||
|
'%s GeoNodes (filename=%s, directory=%s, filepath=%s)',
|
||||||
|
'Linking' if import_method == 'link' else 'Appending',
|
||||||
|
filename,
|
||||||
|
directory,
|
||||||
|
filepath,
|
||||||
|
)
|
||||||
|
bpy.ops.wm.append(
|
||||||
|
filepath=filepath,
|
||||||
|
directory=directory,
|
||||||
|
filename=filename,
|
||||||
|
check_existing=False,
|
||||||
|
set_fake=True,
|
||||||
|
link=import_method == 'link',
|
||||||
|
)
|
||||||
|
|
||||||
|
return bpy.data.node_groups[geonodes]
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - GeoNodes Asset Shelf Panel for MaxwellSimTree
|
||||||
|
####################
|
||||||
|
ASSET_PANEL_NAME: str = 'blender_maxwell__node_asset_shelf'
|
||||||
|
WM_ACTIVE_NODE_ASSETS: str = 'blender_maxwell__active_node_asset_list'
|
||||||
|
WM_ACTIVE_ASSET_IDX: str = 'blender_maxwell__active_node_asset_index'
|
||||||
|
|
||||||
|
|
||||||
|
class NodeAssetPanel(bpy.types.Panel):
|
||||||
|
"""Provides a panel that displays vendored, usable GeoNodes trees, and enables drag-and-drop support to easily drag them into the simulation node editor."""
|
||||||
|
|
||||||
|
## TODO: Provide an option that forces appending? So that users can modify from a baseline. Just watch out - dealing with overlaps isn't trivial.
|
||||||
|
|
||||||
|
bl_idname = ct.PanelType.NodeAssetPanel
|
||||||
|
bl_label = 'Node GeoNodes Asset Panel'
|
||||||
|
bl_space_type = 'NODE_EDITOR'
|
||||||
|
bl_region_type = 'UI'
|
||||||
|
bl_category = 'Assets'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context: bpy.types.Context) -> bool:
|
||||||
|
"""Require window manager properties to show node assets.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
Run by Blender when trying to show a panel.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Whether the panel can show.
|
||||||
|
"""
|
||||||
|
## TODO: Check that we're in a MaxwellSim node tree as well.
|
||||||
|
wm = context.window_manager
|
||||||
|
return hasattr(wm, WM_ACTIVE_NODE_ASSETS) and hasattr(wm, WM_ACTIVE_ASSET_IDX)
|
||||||
|
|
||||||
|
def draw(self, context: bpy.types.Context) -> None:
|
||||||
|
"""Draw the asset library w/drag support.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
Run by Blender when the panel needs to be displayed.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
context: The Blender context object.
|
||||||
|
Must contain `context.window_manager` and `context.workspace`.
|
||||||
|
"""
|
||||||
|
layout = self.layout
|
||||||
|
|
||||||
|
# Draw the Asset Panel w/Drag Support
|
||||||
|
_activate_op_props, _drag_op_props = layout.template_asset_view(
|
||||||
|
# list_id
|
||||||
|
## Identifies this particular UI-bound asset list.
|
||||||
|
## Must be unique, otherwise weird things happen if invoked shown twice.
|
||||||
|
## We use a custom name, presuming only one tree is shown at a time.
|
||||||
|
## TODO: Allow several trees to show at once.
|
||||||
|
ASSET_PANEL_NAME,
|
||||||
|
# asset_library_[dataptr|propname]
|
||||||
|
## Where to find the asset library to pull assets from.
|
||||||
|
## We pull it from a global
|
||||||
|
context.workspace,
|
||||||
|
'asset_library_reference',
|
||||||
|
# assets_[dataptr|propname]
|
||||||
|
## The actual assets to provide user access to.
|
||||||
|
context.window_manager,
|
||||||
|
WM_ACTIVE_NODE_ASSETS,
|
||||||
|
# assets_[dataptr|propname]
|
||||||
|
## The currently selected asset to highlight.
|
||||||
|
context.window_manager,
|
||||||
|
WM_ACTIVE_ASSET_IDX,
|
||||||
|
# draw_operator
|
||||||
|
## The operator to invoke() whenever the user **starts** dragging an asset.
|
||||||
|
drag_operator=ct.OperatorType.GeoNodesToStructureNode,
|
||||||
|
# Other Options
|
||||||
|
## The currently selected asset to highlight.
|
||||||
|
# display_options={'NO_LIBRARY'},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Append GeoNodes Operator
|
||||||
|
####################
|
||||||
|
def get_view_location(
|
||||||
|
region: bpy.types.Region, coords: tuple[float, float], ui_scale: float
|
||||||
|
) -> tuple[float, float]:
|
||||||
|
"""Given a pair of coordinates defined on a Blender region, project the coordinates to the corresponding `bpy.types.View2D`.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
region: The region within which the coordinates are defined.
|
||||||
|
coords: The coordinates within the region, ex. produced during a mouse click event.
|
||||||
|
ui_scale: Scaling factor applied to pixels, which compensates for screens with differing DPIs.
|
||||||
|
We must divide the coordinates we receive by this float to get the "real" $x,y$ coordinates.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The $x,y$ coordinates within the region's `bpy.types.View2D`, correctly scaled with the current interface size/dpi.
|
||||||
|
"""
|
||||||
|
x, y = region.view2d.region_to_view(*coords)
|
||||||
|
return x / ui_scale, y / ui_scale
|
||||||
|
|
||||||
|
|
||||||
|
class GeoNodesToStructureNode(bpy.types.Operator):
|
||||||
|
"""Operator allowing the user to append a vendored GeoNodes tree for use in a simulation."""
|
||||||
|
|
||||||
|
bl_idname = ct.OperatorType.GeoNodesToStructureNode
|
||||||
|
bl_label = 'GeoNodes to Structure Node'
|
||||||
|
bl_description = 'Drag-and-drop operator'
|
||||||
|
bl_options = frozenset({'REGISTER'})
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context: bpy.types.Context) -> bool:
|
||||||
|
"""Defines when the operator can be run.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Whether the operator can be run.
|
||||||
|
"""
|
||||||
|
return context.asset is not None
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Properties
|
||||||
|
####################
|
||||||
|
_asset: bpy.types.AssetRepresentation | None = None
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Execution
|
||||||
|
####################
|
||||||
|
def invoke(
|
||||||
|
self, context: bpy.types.Context, _: bpy.types.Event
|
||||||
|
) -> set[ct.BLOperatorStatus]:
|
||||||
|
"""Commences the drag-and-drop of a GeoNodes asset.
|
||||||
|
|
||||||
|
- Starts the modal timer, which listens for a mouse-release event.
|
||||||
|
- Changes the mouse cursor to communicate the "dragging" state to the user.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
Run via `NodeAssetPanel` when the user starts dragging a vendored GeoNodes asset.
|
||||||
|
|
||||||
|
The asset being dragged can be found in `context.asset`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Indication that there is a modal running.
|
||||||
|
"""
|
||||||
|
self._asset = context.asset ## Guaranteed non-None by poll()
|
||||||
|
|
||||||
|
# Register Modal Operator
|
||||||
|
context.window_manager.modal_handler_add(self)
|
||||||
|
context.area.tag_redraw()
|
||||||
|
|
||||||
|
# Set Mouse Cursor
|
||||||
|
context.window.cursor_modal_set('CROSS')
|
||||||
|
|
||||||
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
|
def modal(
|
||||||
|
self, context: bpy.types.Context, event: bpy.types.Event
|
||||||
|
) -> ct.BLOperatorStatus:
|
||||||
|
"""When LMB is released, creates a GeoNodes Structure node.
|
||||||
|
|
||||||
|
Runs in response to events in the node editor while dragging an asset from the side panel.
|
||||||
|
"""
|
||||||
|
# No Asset: Do Nothing
|
||||||
|
asset = self._asset
|
||||||
|
if asset is None:
|
||||||
|
return {'PASS_THROUGH'}
|
||||||
|
|
||||||
|
# Released LMB: Add Structure Node
|
||||||
|
## The user 'dropped' the asset!
|
||||||
|
## Now, we create an appropriate "Structure" node corresponding to the dropped asset.
|
||||||
|
if event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
|
||||||
|
log.debug('Dropped Asset: %s', asset.name)
|
||||||
|
|
||||||
|
# Load Editor Region
|
||||||
|
area = context.area
|
||||||
|
editor_window_regions = [
|
||||||
|
region for region in area.regions.values() if region.type == 'WINDOW'
|
||||||
|
]
|
||||||
|
if editor_window_regions:
|
||||||
|
log.debug(
|
||||||
|
'Selected Node Editor Region out of %s Options',
|
||||||
|
str(len(editor_window_regions)),
|
||||||
|
)
|
||||||
|
editor_region = editor_window_regions[0]
|
||||||
|
else:
|
||||||
|
msg = 'No valid editor region in context where asset was dropped'
|
||||||
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
|
# Check if Mouse Coordinates are:
|
||||||
|
## - INSIDE of Node Editor
|
||||||
|
## - INSIDE of Node Editor's WINDOW Region
|
||||||
|
# Check Mouse Coordinates against Node Editor
|
||||||
|
## The asset must be dropped inside the Maxwell Sim node editor.
|
||||||
|
if (
|
||||||
|
## Check: Mouse in Area (contextual)
|
||||||
|
(event.mouse_x >= area.x and event.mouse_x < area.x + area.width)
|
||||||
|
and (event.mouse_y >= area.y and event.mouse_y < area.y + area.height)
|
||||||
|
) and (
|
||||||
|
## Check: Mouse in Node Editor Region
|
||||||
|
(
|
||||||
|
event.mouse_x >= editor_region.x
|
||||||
|
and event.mouse_x < editor_region.x + editor_region.width
|
||||||
|
)
|
||||||
|
and (
|
||||||
|
event.mouse_y >= editor_region.y
|
||||||
|
and event.mouse_y < editor_region.y + editor_region.height
|
||||||
|
)
|
||||||
|
):
|
||||||
|
space = context.space_data
|
||||||
|
node_tree = space.node_tree
|
||||||
|
|
||||||
|
# Computing GeoNodes View Location
|
||||||
|
## 1. node_tree.cursor_location gives clicked loc, not released.
|
||||||
|
## 2. event.mouse_region_* has inverted x wrt. event.mouse_*.
|
||||||
|
## -> View2D.region_to_view expects the event.mouse_* order.
|
||||||
|
## -> Is it a bug? Who knows!
|
||||||
|
## 3. We compute it manually, to avoid the jank.
|
||||||
|
node_location = get_view_location(
|
||||||
|
editor_region,
|
||||||
|
[
|
||||||
|
event.mouse_x - editor_region.x,
|
||||||
|
event.mouse_y - editor_region.y,
|
||||||
|
],
|
||||||
|
context.preferences.system.ui_scale,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create GeoNodes Structure Node
|
||||||
|
## 1. Deselect other nodes
|
||||||
|
## 2. Select the new one
|
||||||
|
## 3. Move it into place
|
||||||
|
## 4. Redraw (so we see the new node right away)
|
||||||
|
geonodes = GeoNodes(asset.name)
|
||||||
|
|
||||||
|
log.info(
|
||||||
|
'Creating Node "%s" at (%d, %d)',
|
||||||
|
geonodes.dedicated_node_type,
|
||||||
|
*tuple(node_location),
|
||||||
|
)
|
||||||
|
bpy.ops.node.select_all(action='DESELECT')
|
||||||
|
node = node_tree.nodes.new(geonodes.dedicated_node_type)
|
||||||
|
node.select = True
|
||||||
|
node.location.x = node_location[0]
|
||||||
|
node.location.y = node_location[1]
|
||||||
|
context.area.tag_redraw()
|
||||||
|
|
||||||
|
# GeoNodes Structure Specific Setup
|
||||||
|
## Since the node doesn't itself handle the structure, we must.
|
||||||
|
## We just import the GN tree, then attach the data block to the node.
|
||||||
|
if geonodes.dedicated_node_type == 'GeoNodesStructureNodeType':
|
||||||
|
geonodes_data = import_geonodes(asset.name)
|
||||||
|
node.inputs['GeoNodes'].value = geonodes_data
|
||||||
|
## TODO: Is this too presumptuous? Or a fine little hack?
|
||||||
|
|
||||||
|
# Restore the Pre-Modal Mouse Cursor Shape
|
||||||
|
context.window.cursor_modal_restore()
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Registration
|
||||||
|
####################
|
||||||
|
ASSET_LIB_POSTFIX: str = ' | BLMaxwell'
|
||||||
|
ASSET_LIB_SPECS: dict[str, Path] = {
|
||||||
|
'Primitives': GN_STRUCTURES_PRIMITIVES_PATH,
|
||||||
|
'Arrays': GN_STRUCTURES_ARRAYS_PATH,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@bpy.app.handlers.persistent
|
||||||
|
def initialize_asset_libraries(_: bpy.types.Scene):
|
||||||
|
"""Before loading a `.blend` file, ensure that the WindowManager properties relied on by NodeAssetPanel are available.
|
||||||
|
|
||||||
|
- Several asset libraries, defined under the global `ASSET_LIB_SPECS`, are added/replaced such that the name:path map is respected.
|
||||||
|
- Existing asset libraries are left entirely alone.
|
||||||
|
- The names attached to `bpy.types.WindowManager` are specifically the globals `WM_ACTIVE_NODE_ASSETS` and `WM_ACTIVE_ASSET_IDX`.
|
||||||
|
|
||||||
|
Warnings:
|
||||||
|
**Changing the name of an asset library will leave it dangling on all `.blend` files**.
|
||||||
|
|
||||||
|
Therefore, an addon-specific postfix `ASSET_LIB_POSTFIX` is appended to all asset library names.
|
||||||
|
**This postfix must never, ever change, across any and all versions of the addon**.
|
||||||
|
|
||||||
|
Again, if it does, **all Blender installations having ever used the addon will have a dangling asset library until the user manually notices + removes it**.
|
||||||
|
"""
|
||||||
|
asset_libraries = bpy.context.preferences.filepaths.asset_libraries
|
||||||
|
|
||||||
|
# Guarantee All Asset Libraries
|
||||||
|
for _asset_lib_name, asset_lib_path in ASSET_LIB_SPECS.items():
|
||||||
|
asset_lib_name = _asset_lib_name + ASSET_LIB_POSTFIX
|
||||||
|
|
||||||
|
# Remove Existing Asset Library
|
||||||
|
asset_library_idx = asset_libraries.find(asset_lib_name)
|
||||||
|
if asset_library_idx != -1 and asset_libraries[asset_lib_name].path != str(
|
||||||
|
asset_lib_path
|
||||||
|
):
|
||||||
|
log.debug('Removing Asset Library: %s', asset_lib_name)
|
||||||
|
bpy.ops.preferences.asset_library_remove(asset_library_idx)
|
||||||
|
|
||||||
|
# Add Asset Library
|
||||||
|
if asset_lib_name not in asset_libraries:
|
||||||
|
log.debug('Add Asset Library: %s', asset_lib_name)
|
||||||
|
|
||||||
|
bpy.ops.preferences.asset_library_add()
|
||||||
|
asset_library = asset_libraries[-1] ## Was added to the end
|
||||||
|
asset_library.name = asset_lib_name
|
||||||
|
asset_library.path = str(asset_lib_path)
|
||||||
|
|
||||||
|
# Set WindowManager Props
|
||||||
|
## Set Active Assets Collection
|
||||||
|
setattr(
|
||||||
|
bpy.types.WindowManager,
|
||||||
|
WM_ACTIVE_NODE_ASSETS,
|
||||||
|
bpy.props.CollectionProperty(type=bpy.types.AssetHandle),
|
||||||
|
)
|
||||||
|
## Set Active Assets Collection
|
||||||
|
setattr(
|
||||||
|
bpy.types.WindowManager,
|
||||||
|
WM_ACTIVE_ASSET_IDX,
|
||||||
|
bpy.props.IntProperty(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
bpy.app.handlers.load_pre.append(initialize_asset_libraries)
|
||||||
|
|
||||||
|
BL_REGISTER = [
|
||||||
|
NodeAssetPanel,
|
||||||
|
GeoNodesToStructureNode,
|
||||||
|
]
|
||||||
|
|
||||||
|
BL_HOTKEYS = []
|
|
@ -1,410 +0,0 @@
|
||||||
"""Provides for the linking and/or appending of geometry nodes trees from vendored libraries included in Blender maxwell."""
|
|
||||||
|
|
||||||
import enum
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import bpy
|
|
||||||
|
|
||||||
from blender_maxwell import contracts as ct
|
|
||||||
from blender_maxwell.utils import logger
|
|
||||||
|
|
||||||
log = logger.get(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - GeoNodes Specification
|
|
||||||
####################
|
|
||||||
class GeoNodes(enum.StrEnum):
|
|
||||||
"""Defines available GeoNodes groups vendored as part of Blender Maxwell.
|
|
||||||
|
|
||||||
The value of this StrEnum is both the name of the .blend file containing the GeoNodes group, and of the GeoNodes group itself.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Node Previews
|
|
||||||
## Input
|
|
||||||
InputConstantPhysicalPol = '_input_constant_physical_pol'
|
|
||||||
## Source
|
|
||||||
SourcePointDipole = '_source_point_dipole'
|
|
||||||
SourcePlaneWave = '_source_plane_wave'
|
|
||||||
SourceUniformCurrent = '_source_uniform_current'
|
|
||||||
SourceTFSF = '_source_tfsf'
|
|
||||||
SourceGaussianBeam = '_source_gaussian_beam'
|
|
||||||
SourceAstigmaticGaussianBeam = '_source_astigmatic_gaussian_beam'
|
|
||||||
SourceMode = '_source_mode'
|
|
||||||
SourceEHArray = '_source_eh_array'
|
|
||||||
SourceEHEquivArray = '_source_eh_equiv_array'
|
|
||||||
## Structure
|
|
||||||
StructurePrimitivePlane = '_structure_primitive_plane'
|
|
||||||
StructurePrimitiveBox = '_structure_primitive_box'
|
|
||||||
StructurePrimitiveSphere = '_structure_primitive_sphere'
|
|
||||||
StructurePrimitiveCylinder = '_structure_primitive_cylinder'
|
|
||||||
StructurePrimitiveRing = '_structure_primitive_ring'
|
|
||||||
StructurePrimitiveCapsule = '_structure_primitive_capsule'
|
|
||||||
StructurePrimitiveCone = '_structure_primitive_cone'
|
|
||||||
## Monitor
|
|
||||||
MonitorEHField = '_monitor_eh_field'
|
|
||||||
MonitorPowerFlux = '_monitor_power_flux'
|
|
||||||
MonitorEpsTensor = '_monitor_eps_tensor'
|
|
||||||
MonitorDiffraction = '_monitor_diffraction'
|
|
||||||
MonitorProjCartEHField = '_monitor_proj_eh_field'
|
|
||||||
MonitorProjAngEHField = '_monitor_proj_ang_eh_field'
|
|
||||||
MonitorProjKSpaceEHField = '_monitor_proj_k_space_eh_field'
|
|
||||||
## Simulation
|
|
||||||
SimulationSimDomain = '_simulation_sim_domain'
|
|
||||||
SimulationBoundConds = '_simulation_bound_conds'
|
|
||||||
SimulationBoundCondPML = '_simulation_bound_cond_pml'
|
|
||||||
SimulationBoundCondPEC = '_simulation_bound_cond_pec'
|
|
||||||
SimulationBoundCondPMC = '_simulation_bound_cond_pmc'
|
|
||||||
SimulationBoundCondBloch = '_simulation_bound_cond_bloch'
|
|
||||||
SimulationBoundCondPeriodic = '_simulation_bound_cond_periodic'
|
|
||||||
SimulationBoundCondAbsorbing = '_simulation_bound_cond_absorbing'
|
|
||||||
SimulationSimGrid = '_simulation_sim_grid'
|
|
||||||
SimulationSimGridAxisAuto = '_simulation_sim_grid_axis_auto'
|
|
||||||
SimulationSimGridAxisManual = '_simulation_sim_grid_axis_manual'
|
|
||||||
SimulationSimGridAxisUniform = '_simulation_sim_grid_axis_uniform'
|
|
||||||
SimulationSimGridAxisArray = '_simulation_sim_grid_axis_array'
|
|
||||||
|
|
||||||
# Structures
|
|
||||||
## Primitives
|
|
||||||
PrimitiveBox = 'box'
|
|
||||||
PrimitiveRing = 'ring'
|
|
||||||
PrimitiveSphere = 'sphere'
|
|
||||||
|
|
||||||
|
|
||||||
# GeoNodes Paths
|
|
||||||
## Internal
|
|
||||||
GN_INTERNAL_PATH = ct.addon.PATH_ASSETS / 'internal' / 'primitives'
|
|
||||||
GN_INTERNAL_INPUTS_PATH = GN_INTERNAL_PATH / 'input'
|
|
||||||
GN_INTERNAL_SOURCES_PATH = GN_INTERNAL_PATH / 'source'
|
|
||||||
GN_INTERNAL_STRUCTURES_PATH = GN_INTERNAL_PATH / 'structure'
|
|
||||||
GN_INTERNAL_MONITORS_PATH = GN_INTERNAL_PATH / 'monitor'
|
|
||||||
GN_INTERNAL_SIMULATIONS_PATH = GN_INTERNAL_PATH / 'simulation'
|
|
||||||
|
|
||||||
## Structures
|
|
||||||
GN_STRUCTURES_PATH = ct.addon.PATH_ASSETS / 'structures'
|
|
||||||
GN_STRUCTURES_PRIMITIVES_PATH = GN_STRUCTURES_PATH / 'primitives'
|
|
||||||
|
|
||||||
GN_PARENT_PATHS: dict[GeoNodes, Path] = {
|
|
||||||
# Node Previews
|
|
||||||
## Input
|
|
||||||
GeoNodes.InputConstantPhysicalPol: GN_INTERNAL_INPUTS_PATH,
|
|
||||||
## Source
|
|
||||||
GeoNodes.SourcePointDipole: GN_INTERNAL_SOURCES_PATH,
|
|
||||||
GeoNodes.SourcePlaneWave: GN_INTERNAL_SOURCES_PATH,
|
|
||||||
GeoNodes.SourceUniformCurrent: GN_INTERNAL_SOURCES_PATH,
|
|
||||||
GeoNodes.SourceTFSF: GN_INTERNAL_SOURCES_PATH,
|
|
||||||
GeoNodes.SourceGaussianBeam: GN_INTERNAL_SOURCES_PATH,
|
|
||||||
GeoNodes.SourceAstigmaticGaussianBeam: GN_INTERNAL_SOURCES_PATH,
|
|
||||||
GeoNodes.SourceMode: GN_INTERNAL_SOURCES_PATH,
|
|
||||||
GeoNodes.SourceEHArray: GN_INTERNAL_SOURCES_PATH,
|
|
||||||
GeoNodes.SourceEHEquivArray: GN_INTERNAL_SOURCES_PATH,
|
|
||||||
## Structure
|
|
||||||
GeoNodes.StructurePrimitivePlane: GN_INTERNAL_STRUCTURES_PATH,
|
|
||||||
GeoNodes.StructurePrimitiveBox: GN_INTERNAL_STRUCTURES_PATH,
|
|
||||||
GeoNodes.StructurePrimitiveSphere: GN_INTERNAL_STRUCTURES_PATH,
|
|
||||||
GeoNodes.StructurePrimitiveCylinder: GN_INTERNAL_STRUCTURES_PATH,
|
|
||||||
GeoNodes.StructurePrimitiveRing: GN_INTERNAL_STRUCTURES_PATH,
|
|
||||||
GeoNodes.StructurePrimitiveCapsule: GN_INTERNAL_STRUCTURES_PATH,
|
|
||||||
GeoNodes.StructurePrimitiveCone: GN_INTERNAL_STRUCTURES_PATH,
|
|
||||||
## Monitor
|
|
||||||
GeoNodes.MonitorEHField: GN_INTERNAL_STRUCTURES_PATH,
|
|
||||||
GeoNodes.MonitorPowerFlux: GN_INTERNAL_STRUCTURES_PATH,
|
|
||||||
GeoNodes.MonitorEpsTensor: GN_INTERNAL_STRUCTURES_PATH,
|
|
||||||
GeoNodes.MonitorDiffraction: GN_INTERNAL_STRUCTURES_PATH,
|
|
||||||
GeoNodes.MonitorProjCartEHField: GN_INTERNAL_STRUCTURES_PATH,
|
|
||||||
GeoNodes.MonitorProjAngEHField: GN_INTERNAL_STRUCTURES_PATH,
|
|
||||||
GeoNodes.MonitorProjKSpaceEHField: GN_INTERNAL_STRUCTURES_PATH,
|
|
||||||
## Simulation
|
|
||||||
GeoNodes.SimulationSimDomain: GN_INTERNAL_SIMULATIONS_PATH,
|
|
||||||
GeoNodes.SimulationBoundConds: GN_INTERNAL_SIMULATIONS_PATH,
|
|
||||||
GeoNodes.SimulationBoundCondPML: GN_INTERNAL_SIMULATIONS_PATH,
|
|
||||||
GeoNodes.SimulationBoundCondPEC: GN_INTERNAL_SIMULATIONS_PATH,
|
|
||||||
GeoNodes.SimulationBoundCondPMC: GN_INTERNAL_SIMULATIONS_PATH,
|
|
||||||
GeoNodes.SimulationBoundCondBloch: GN_INTERNAL_SIMULATIONS_PATH,
|
|
||||||
GeoNodes.SimulationBoundCondPeriodic: GN_INTERNAL_SIMULATIONS_PATH,
|
|
||||||
GeoNodes.SimulationBoundCondAbsorbing: GN_INTERNAL_SIMULATIONS_PATH,
|
|
||||||
GeoNodes.SimulationSimGrid: GN_INTERNAL_SIMULATIONS_PATH,
|
|
||||||
GeoNodes.SimulationSimGridAxisAuto: GN_INTERNAL_SIMULATIONS_PATH,
|
|
||||||
GeoNodes.SimulationSimGridAxisManual: GN_INTERNAL_SIMULATIONS_PATH,
|
|
||||||
GeoNodes.SimulationSimGridAxisUniform: GN_INTERNAL_SIMULATIONS_PATH,
|
|
||||||
GeoNodes.SimulationSimGridAxisArray: GN_INTERNAL_SIMULATIONS_PATH,
|
|
||||||
# Structures
|
|
||||||
GeoNodes.PrimitiveBox: GN_STRUCTURES_PRIMITIVES_PATH,
|
|
||||||
GeoNodes.PrimitiveRing: GN_STRUCTURES_PRIMITIVES_PATH,
|
|
||||||
GeoNodes.PrimitiveSphere: GN_STRUCTURES_PRIMITIVES_PATH,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Import GeoNodes (Link/Append)
|
|
||||||
####################
|
|
||||||
def import_geonodes(
|
|
||||||
geonodes: GeoNodes,
|
|
||||||
import_method: ct.BLImportMethod,
|
|
||||||
) -> bpy.types.GeometryNodeGroup:
|
|
||||||
"""Given a (name of a) GeoNodes group packaged with Blender Maxwell, link/append it to the current file, and return the node group.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
geonodes: The (name of the) GeoNodes group, which ships with Blender Maxwell.
|
|
||||||
import_method: Whether to link or append the GeoNodes group.
|
|
||||||
When 'link', repeated calls will not link a new group; the existing group will simply be returned.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A GeoNodes group available in the current .blend file, which can ex. be attached to a 'GeoNodes Structure' node.
|
|
||||||
"""
|
|
||||||
if import_method == 'link' and geonodes in bpy.data.node_groups:
|
|
||||||
return bpy.data.node_groups[geonodes]
|
|
||||||
|
|
||||||
filename = geonodes
|
|
||||||
filepath = str(
|
|
||||||
GN_PARENT_PATHS[geonodes] / (geonodes + '.blend') / 'NodeTree' / geonodes
|
|
||||||
)
|
|
||||||
directory = filepath.removesuffix(geonodes)
|
|
||||||
log.info(
|
|
||||||
'%s GeoNodes (filename=%s, directory=%s, filepath=%s)',
|
|
||||||
'Linking' if import_method == 'link' else 'Appending',
|
|
||||||
filename,
|
|
||||||
directory,
|
|
||||||
filepath,
|
|
||||||
)
|
|
||||||
bpy.ops.wm.append(
|
|
||||||
filepath=filepath,
|
|
||||||
directory=directory,
|
|
||||||
filename=filename,
|
|
||||||
check_existing=False,
|
|
||||||
set_fake=True,
|
|
||||||
link=import_method == 'link',
|
|
||||||
)
|
|
||||||
|
|
||||||
return bpy.data.node_groups[geonodes]
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - GeoNodes Asset Shelf Panel for MaxwellSimTree
|
|
||||||
####################
|
|
||||||
class NodeAssetPanel(bpy.types.Panel):
|
|
||||||
bl_idname = ct.PanelType.NodeAssetPanel
|
|
||||||
bl_label = 'Node GeoNodes Asset Panel'
|
|
||||||
bl_space_type = 'NODE_EDITOR'
|
|
||||||
bl_region_type = 'UI'
|
|
||||||
bl_category = 'Assets'
|
|
||||||
|
|
||||||
# @classmethod
|
|
||||||
# def poll(cls, context):
|
|
||||||
# return (
|
|
||||||
# (space := context.get('space_data')) is not None
|
|
||||||
# and (node_tree := space.get('node_tree')) is not None
|
|
||||||
# and (node_tree.bl_idname == 'MaxwellSimTreeType')
|
|
||||||
# )
|
|
||||||
|
|
||||||
def draw(self, context):
|
|
||||||
layout = self.layout
|
|
||||||
workspace = context.workspace
|
|
||||||
wm = context.window_manager
|
|
||||||
|
|
||||||
# list_id must be unique otherwise behaviour gets weird when the template_asset_view is shown twice
|
|
||||||
# (drag operator stops working in AssetPanelDrag, clickable area of all Assets in AssetPanelNoDrag gets
|
|
||||||
# reduced to below the Asset name and clickable area of Current File Assets in AssetPanelDrag gets
|
|
||||||
# reduced as if it didn't have a drag operator)
|
|
||||||
_activate_op_props, _drag_op_props = layout.template_asset_view(
|
|
||||||
'geo_nodes_asset_shelf',
|
|
||||||
workspace,
|
|
||||||
'asset_library_reference',
|
|
||||||
wm,
|
|
||||||
'active_asset_list',
|
|
||||||
wm,
|
|
||||||
'active_asset_index',
|
|
||||||
drag_operator=AppendGeoNodes.bl_idname,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Append GeoNodes Operator
|
|
||||||
####################
|
|
||||||
def get_view_location(region, coords, ui_scale):
|
|
||||||
x, y = region.view2d.region_to_view(*coords)
|
|
||||||
return x / ui_scale, y / ui_scale
|
|
||||||
|
|
||||||
|
|
||||||
class AppendGeoNodes(bpy.types.Operator):
|
|
||||||
"""Operator allowing the user to append a vendored GeoNodes tree for use in a simulation."""
|
|
||||||
|
|
||||||
bl_idname = 'blender_maxwell.blends__import_geo_nodes'
|
|
||||||
bl_label = 'Import GeoNode Tree'
|
|
||||||
bl_description = 'Append a geometry node tree from the Blender Maxwell plugin, either via linking or appending'
|
|
||||||
bl_options = frozenset({'REGISTER'})
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Properties
|
|
||||||
####################
|
|
||||||
_asset: bpy.types.AssetRepresentation | None = None
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - UI
|
|
||||||
####################
|
|
||||||
def draw(self, _: bpy.types.Context) -> None:
|
|
||||||
"""Draws the UI of the operator."""
|
|
||||||
layout = self.layout
|
|
||||||
col = layout.column()
|
|
||||||
col.prop(self, 'geonodes_to_append', expand=True)
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Execution
|
|
||||||
####################
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context: bpy.types.Context) -> bool:
|
|
||||||
"""Defines when the operator can be run.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Whether the operator can be run.
|
|
||||||
"""
|
|
||||||
return context.asset is not None
|
|
||||||
|
|
||||||
def invoke(self, context: bpy.types.Context, _):
|
|
||||||
return self.execute(context)
|
|
||||||
|
|
||||||
def execute(self, context: bpy.types.Context) -> ct.BLOperatorStatus:
|
|
||||||
"""Initializes the while-dragging modal handler, which executes custom logic when the mouse button is released.
|
|
||||||
|
|
||||||
Runs in response to drag_handler of a `UILayout.template_asset_view`.
|
|
||||||
"""
|
|
||||||
asset: bpy.types.AssetRepresentation = context.asset
|
|
||||||
log.debug('Dragging Asset: %s', asset.name)
|
|
||||||
|
|
||||||
# Store Asset for Modal & Drag Start
|
|
||||||
self._asset = context.asset
|
|
||||||
|
|
||||||
# Register Modal Operator & Tag Area for Redraw
|
|
||||||
context.window_manager.modal_handler_add(self)
|
|
||||||
context.area.tag_redraw()
|
|
||||||
|
|
||||||
# Set Modal Cursor
|
|
||||||
context.window.cursor_modal_set('CROSS')
|
|
||||||
|
|
||||||
# Return Status of Running Modal
|
|
||||||
return {'RUNNING_MODAL'}
|
|
||||||
|
|
||||||
def modal(
|
|
||||||
self, context: bpy.types.Context, event: bpy.types.Event
|
|
||||||
) -> ct.BLOperatorStatus:
|
|
||||||
"""When LMB is released, creates a GeoNodes Structure node.
|
|
||||||
|
|
||||||
Runs in response to events in the node editor while dragging an asset from the side panel.
|
|
||||||
"""
|
|
||||||
if (asset := self._asset) is None:
|
|
||||||
return {'PASS_THROUGH'}
|
|
||||||
|
|
||||||
if event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
|
|
||||||
log.debug('Released Dragged Asset: %s', asset.name)
|
|
||||||
area = context.area
|
|
||||||
editor_region = next(
|
|
||||||
region for region in area.regions.values() if region.type == 'WINDOW'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check if Mouse Coordinates are:
|
|
||||||
## - INSIDE of Node Editor
|
|
||||||
## - INSIDE of Node Editor's WINDOW Region
|
|
||||||
if (
|
|
||||||
(event.mouse_x >= area.x and event.mouse_x < area.x + area.width)
|
|
||||||
and (event.mouse_y >= area.y and event.mouse_y < area.y + area.height)
|
|
||||||
) and (
|
|
||||||
(
|
|
||||||
event.mouse_x >= editor_region.x
|
|
||||||
and event.mouse_x < editor_region.x + editor_region.width
|
|
||||||
)
|
|
||||||
and (
|
|
||||||
event.mouse_y >= editor_region.y
|
|
||||||
and event.mouse_y < editor_region.y + editor_region.height
|
|
||||||
)
|
|
||||||
):
|
|
||||||
log.info(
|
|
||||||
'Asset "%s" Released in Main Window of Node Editor', asset.name
|
|
||||||
)
|
|
||||||
space = context.space_data
|
|
||||||
node_tree = space.node_tree
|
|
||||||
|
|
||||||
# Computing GeoNodes View Location
|
|
||||||
## 1. node_tree.cursor_location gives clicked loc, not released.
|
|
||||||
## 2. event.mouse_region_* has inverted x wrt. event.mouse_*.
|
|
||||||
## - View2D.region_to_view expects the event.mouse_* order.
|
|
||||||
## - Is it a bug? Who knows!
|
|
||||||
## 3. We compute it manually, to avoid the jank.
|
|
||||||
node_location = get_view_location(
|
|
||||||
editor_region,
|
|
||||||
[
|
|
||||||
event.mouse_x - editor_region.x,
|
|
||||||
event.mouse_y - editor_region.y,
|
|
||||||
],
|
|
||||||
context.preferences.system.ui_scale,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create GeoNodes Structure Node
|
|
||||||
## 1. Deselect other nodes
|
|
||||||
## 2. Select the new one
|
|
||||||
## 3. Move it into place
|
|
||||||
## 4. Redraw (so we see the new node right away)
|
|
||||||
log.info(
|
|
||||||
'Creating GeoNodes Structure Node at (%d, %d)',
|
|
||||||
*tuple(node_location),
|
|
||||||
)
|
|
||||||
bpy.ops.node.select_all(action='DESELECT')
|
|
||||||
node = node_tree.nodes.new('GeoNodesStructureNodeType')
|
|
||||||
node.select = True
|
|
||||||
node.location.x = node_location[0]
|
|
||||||
node.location.y = node_location[1]
|
|
||||||
context.area.tag_redraw()
|
|
||||||
|
|
||||||
# Import & Attach the GeoNodes Tree to the Node
|
|
||||||
geonodes = import_geonodes(asset.name, 'append')
|
|
||||||
node.inputs['GeoNodes'].value = geonodes
|
|
||||||
|
|
||||||
# Restore the Pre-Modal Mouse Cursor Shape
|
|
||||||
context.window.cursor_modal_restore()
|
|
||||||
return {'FINISHED'}
|
|
||||||
|
|
||||||
return {'RUNNING_MODAL'}
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Blender Registration
|
|
||||||
####################
|
|
||||||
# def initialize_asset_libraries(_: bpy.types.Scene):
|
|
||||||
# bpy.app.handlers.load_post.append(initialize_asset_libraries)
|
|
||||||
## TODO: Move to top-level registration.
|
|
||||||
|
|
||||||
asset_libraries = bpy.context.preferences.filepaths.asset_libraries
|
|
||||||
if (
|
|
||||||
asset_library_idx := asset_libraries.find('Blender Maxwell')
|
|
||||||
) != -1 and asset_libraries['Blender Maxwell'].path != str(ct.addon.PATH_ASSETS):
|
|
||||||
bpy.ops.preferences.asset_library_remove(asset_library_idx)
|
|
||||||
|
|
||||||
if 'Blender Maxwell' not in asset_libraries:
|
|
||||||
bpy.ops.preferences.asset_library_add()
|
|
||||||
asset_library = asset_libraries[-1] ## Since the operator adds to the end
|
|
||||||
asset_library.name = 'Blender Maxwell'
|
|
||||||
asset_library.path = str(ct.addon.PATH_ASSETS)
|
|
||||||
|
|
||||||
bpy.types.WindowManager.active_asset_list = bpy.props.CollectionProperty(
|
|
||||||
type=bpy.types.AssetHandle
|
|
||||||
)
|
|
||||||
bpy.types.WindowManager.active_asset_index = bpy.props.IntProperty()
|
|
||||||
## TODO: Do something differently
|
|
||||||
|
|
||||||
BL_REGISTER = [
|
|
||||||
# GeoNodesAssetShelf,
|
|
||||||
NodeAssetPanel,
|
|
||||||
AppendGeoNodes,
|
|
||||||
]
|
|
||||||
|
|
||||||
BL_HOTKEYS = [
|
|
||||||
# {
|
|
||||||
# '_': [
|
|
||||||
# AppendGeoNodes.bl_idname,
|
|
||||||
# 'LEFTMOUSE',
|
|
||||||
# 'CLICK_DRAG',
|
|
||||||
# ],
|
|
||||||
# 'ctrl': False,
|
|
||||||
# 'shift': False,
|
|
||||||
# 'alt': False,
|
|
||||||
# }
|
|
||||||
]
|
|
BIN
src/blender_maxwell/assets/internal/monitor/_monitor_eh_field.blend (Stored with Git LFS)
100644
BIN
src/blender_maxwell/assets/internal/monitor/_monitor_eh_field.blend (Stored with Git LFS)
100644
Binary file not shown.
BIN
src/blender_maxwell/assets/internal/monitor/_monitor_power_flux.blend (Stored with Git LFS)
100644
BIN
src/blender_maxwell/assets/internal/monitor/_monitor_power_flux.blend (Stored with Git LFS)
100644
Binary file not shown.
BIN
src/blender_maxwell/assets/internal/simulation/_simulation_sim_domain.blend (Stored with Git LFS)
100644
BIN
src/blender_maxwell/assets/internal/simulation/_simulation_sim_domain.blend (Stored with Git LFS)
100644
Binary file not shown.
BIN
src/blender_maxwell/assets/internal/source/_source_plane_wave.blend (Stored with Git LFS)
100644
BIN
src/blender_maxwell/assets/internal/source/_source_plane_wave.blend (Stored with Git LFS)
100644
Binary file not shown.
BIN
src/blender_maxwell/assets/internal/structure/_structure_primitive_box.blend (Stored with Git LFS)
100644
BIN
src/blender_maxwell/assets/internal/structure/_structure_primitive_box.blend (Stored with Git LFS)
100644
Binary file not shown.
BIN
src/blender_maxwell/assets/internal/structure/_structure_primitive_ring.blend (Stored with Git LFS)
100644
BIN
src/blender_maxwell/assets/internal/structure/_structure_primitive_ring.blend (Stored with Git LFS)
100644
Binary file not shown.
BIN
src/blender_maxwell/assets/internal/structure/_structure_primitive_sphere.blend (Stored with Git LFS)
100644
BIN
src/blender_maxwell/assets/internal/structure/_structure_primitive_sphere.blend (Stored with Git LFS)
100644
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/blender_maxwell/assets/structures/primitives/sphere.blend (Stored with Git LFS)
BIN
src/blender_maxwell/assets/structures/primitives/sphere.blend (Stored with Git LFS)
Binary file not shown.
|
@ -16,6 +16,8 @@ class OperatorType(enum.StrEnum):
|
||||||
|
|
||||||
ConnectViewerNode = enum.auto()
|
ConnectViewerNode = enum.auto()
|
||||||
|
|
||||||
|
GeoNodesToStructureNode = enum.auto()
|
||||||
|
|
||||||
# Socket: Tidy3DCloudTask
|
# Socket: Tidy3DCloudTask
|
||||||
SocketCloudAuthenticate = enum.auto()
|
SocketCloudAuthenticate = enum.auto()
|
||||||
SocketReloadCloudFolderList = enum.auto()
|
SocketReloadCloudFolderList = enum.auto()
|
||||||
|
|
|
@ -82,7 +82,7 @@ class NodeType(blender_type_enum.BlenderTypeEnum):
|
||||||
## Structures / Primitives
|
## Structures / Primitives
|
||||||
BoxStructure = enum.auto()
|
BoxStructure = enum.auto()
|
||||||
SphereStructure = enum.auto()
|
SphereStructure = enum.auto()
|
||||||
# CylinderStructure = enum.auto()
|
CylinderStructure = enum.auto()
|
||||||
|
|
||||||
# Bounds
|
# Bounds
|
||||||
BoundConds = enum.auto()
|
BoundConds = enum.auto()
|
||||||
|
|
|
@ -3,10 +3,10 @@ import typing as typ
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
|
|
||||||
|
from blender_maxwell.assets.geonodes import GeoNodes, import_geonodes
|
||||||
from blender_maxwell.utils import extra_sympy_units as spux
|
from blender_maxwell.utils import extra_sympy_units as spux
|
||||||
from blender_maxwell.utils import logger
|
from blender_maxwell.utils import logger
|
||||||
|
|
||||||
from .....assets.import_geonodes import GeoNodes, import_geonodes
|
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from ... import managed_objs, sockets
|
from ... import managed_objs, sockets
|
||||||
from .. import base, events
|
from .. import base, events
|
||||||
|
@ -123,7 +123,7 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
|
||||||
managed_objs['mesh'].bl_object(location=input_sockets['Center']),
|
managed_objs['mesh'].bl_object(location=input_sockets['Center']),
|
||||||
'NODES',
|
'NODES',
|
||||||
{
|
{
|
||||||
'node_group': import_geonodes(GeoNodes.PrimitiveBox, 'link'),
|
'node_group': import_geonodes(GeoNodes.MonitorEHField),
|
||||||
'unit_system': unit_systems['BlenderUnits'],
|
'unit_system': unit_systems['BlenderUnits'],
|
||||||
'inputs': {
|
'inputs': {
|
||||||
'Size': input_sockets['Size'],
|
'Size': input_sockets['Size'],
|
||||||
|
|
|
@ -3,10 +3,10 @@ import typing as typ
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
|
|
||||||
|
from blender_maxwell.assets.geonodes import GeoNodes, import_geonodes
|
||||||
from blender_maxwell.utils import extra_sympy_units as spux
|
from blender_maxwell.utils import extra_sympy_units as spux
|
||||||
from blender_maxwell.utils import logger
|
from blender_maxwell.utils import logger
|
||||||
|
|
||||||
from .....assets.import_geonodes import GeoNodes, import_geonodes
|
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from ... import managed_objs, sockets
|
from ... import managed_objs, sockets
|
||||||
from .. import base, events
|
from .. import base, events
|
||||||
|
@ -124,7 +124,7 @@ class PowerFluxMonitorNode(base.MaxwellSimNode):
|
||||||
managed_objs['mesh'].bl_object(location=input_sockets['Center']),
|
managed_objs['mesh'].bl_object(location=input_sockets['Center']),
|
||||||
'NODES',
|
'NODES',
|
||||||
{
|
{
|
||||||
'node_group': import_geonodes(GeoNodes.PrimitiveBox, 'link'),
|
'node_group': import_geonodes(GeoNodes.MonitorPowerFlux),
|
||||||
'unit_system': unit_systems['BlenderUnits'],
|
'unit_system': unit_systems['BlenderUnits'],
|
||||||
'inputs': {
|
'inputs': {
|
||||||
'Size': input_sockets['Size'],
|
'Size': input_sockets['Size'],
|
||||||
|
|
|
@ -3,7 +3,8 @@ import typing as typ
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
import sympy.physics.units as spu
|
import sympy.physics.units as spu
|
||||||
|
|
||||||
from .....assets.import_geonodes import GeoNodes, import_geonodes
|
from blender_maxwell.assets.geonodes import GeoNodes, import_geonodes
|
||||||
|
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from ... import managed_objs, sockets
|
from ... import managed_objs, sockets
|
||||||
from .. import base, events
|
from .. import base, events
|
||||||
|
@ -79,7 +80,7 @@ class SimDomainNode(base.MaxwellSimNode):
|
||||||
managed_objs['mesh'].bl_object(location=input_sockets['Center']),
|
managed_objs['mesh'].bl_object(location=input_sockets['Center']),
|
||||||
'NODES',
|
'NODES',
|
||||||
{
|
{
|
||||||
'node_group': import_geonodes(GeoNodes.PrimitiveBox, 'link'),
|
'node_group': import_geonodes(GeoNodes.SimulationSimDomain),
|
||||||
'unit_system': unit_systems['BlenderUnits'],
|
'unit_system': unit_systems['BlenderUnits'],
|
||||||
'inputs': {
|
'inputs': {
|
||||||
'Size': input_sockets['Size'],
|
'Size': input_sockets['Size'],
|
||||||
|
|
|
@ -4,7 +4,8 @@ import sympy as sp
|
||||||
import sympy.physics.units as spu
|
import sympy.physics.units as spu
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
|
|
||||||
from ......assets.import_geonodes import GeoNodes, import_geonodes
|
from blender_maxwell.assets.geonodes import GeoNodes, import_geonodes
|
||||||
|
|
||||||
from .... import contracts as ct
|
from .... import contracts as ct
|
||||||
from .... import managed_objs, sockets
|
from .... import managed_objs, sockets
|
||||||
from ... import base, events
|
from ... import base, events
|
||||||
|
@ -79,7 +80,7 @@ class BoxStructureNode(base.MaxwellSimNode):
|
||||||
managed_objs['mesh'].bl_object(location=input_sockets['Center']),
|
managed_objs['mesh'].bl_object(location=input_sockets['Center']),
|
||||||
'NODES',
|
'NODES',
|
||||||
{
|
{
|
||||||
'node_group': import_geonodes(GeoNodes.PrimitiveBox, 'link'),
|
'node_group': import_geonodes(GeoNodes.StructurePrimitiveBox),
|
||||||
'unit_system': unit_systems['BlenderUnits'],
|
'unit_system': unit_systems['BlenderUnits'],
|
||||||
'inputs': {
|
'inputs': {
|
||||||
'Size': input_sockets['Size'],
|
'Size': input_sockets['Size'],
|
||||||
|
|
|
@ -3,7 +3,8 @@ import typing as typ
|
||||||
import sympy.physics.units as spu
|
import sympy.physics.units as spu
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
|
|
||||||
from ......assets.import_geonodes import GeoNodes, import_geonodes
|
from blender_maxwell.assets.geonodes import GeoNodes, import_geonodes
|
||||||
|
|
||||||
from .... import contracts as ct
|
from .... import contracts as ct
|
||||||
from .... import managed_objs, sockets
|
from .... import managed_objs, sockets
|
||||||
from ... import base, events
|
from ... import base, events
|
||||||
|
@ -18,11 +19,11 @@ class SphereStructureNode(base.MaxwellSimNode):
|
||||||
# - Sockets
|
# - Sockets
|
||||||
####################
|
####################
|
||||||
input_sockets: typ.ClassVar = {
|
input_sockets: typ.ClassVar = {
|
||||||
|
'Medium': sockets.MaxwellMediumSocketDef(),
|
||||||
'Center': sockets.PhysicalPoint3DSocketDef(),
|
'Center': sockets.PhysicalPoint3DSocketDef(),
|
||||||
'Radius': sockets.PhysicalLengthSocketDef(
|
'Radius': sockets.PhysicalLengthSocketDef(
|
||||||
default_value=150 * spu.nm,
|
default_value=150 * spu.nm,
|
||||||
),
|
),
|
||||||
'Medium': sockets.MaxwellMediumSocketDef(),
|
|
||||||
}
|
}
|
||||||
output_sockets: typ.ClassVar = {
|
output_sockets: typ.ClassVar = {
|
||||||
'Structure': sockets.MaxwellStructureSocketDef(),
|
'Structure': sockets.MaxwellStructureSocketDef(),
|
||||||
|
@ -82,7 +83,7 @@ class SphereStructureNode(base.MaxwellSimNode):
|
||||||
managed_objs['mesh'].bl_object(location=input_sockets['Center']),
|
managed_objs['mesh'].bl_object(location=input_sockets['Center']),
|
||||||
'NODES',
|
'NODES',
|
||||||
{
|
{
|
||||||
'node_group': import_geonodes(GeoNodes.PrimitiveSphere, 'link'),
|
'node_group': import_geonodes(GeoNodes.StructurePrimitiveSphere),
|
||||||
'unit_system': unit_systems['BlenderUnits'],
|
'unit_system': unit_systems['BlenderUnits'],
|
||||||
'inputs': {
|
'inputs': {
|
||||||
'Radius': input_sockets['Radius'],
|
'Radius': input_sockets['Radius'],
|
||||||
|
|
|
@ -10,24 +10,16 @@ from .. import base
|
||||||
|
|
||||||
VAC_SPEED_OF_LIGHT = sc.constants.speed_of_light * spu.meter / spu.second
|
VAC_SPEED_OF_LIGHT = sc.constants.speed_of_light * spu.meter / spu.second
|
||||||
|
|
||||||
|
FIXED_WL = 500 * spu.nm
|
||||||
|
|
||||||
|
|
||||||
class MaxwellMediumBLSocket(base.MaxwellSimSocket):
|
class MaxwellMediumBLSocket(base.MaxwellSimSocket):
|
||||||
socket_type = ct.SocketType.MaxwellMedium
|
socket_type = ct.SocketType.MaxwellMedium
|
||||||
bl_label = 'Maxwell Medium'
|
bl_label = 'Maxwell Medium'
|
||||||
use_units = True
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Properties
|
# - Properties
|
||||||
####################
|
####################
|
||||||
wl: bpy.props.FloatProperty(
|
|
||||||
name='WL',
|
|
||||||
description='WL to evaluate conductivity at',
|
|
||||||
default=500.0,
|
|
||||||
precision=4,
|
|
||||||
step=50,
|
|
||||||
update=(lambda self, context: self.on_prop_changed('wl', context)),
|
|
||||||
)
|
|
||||||
|
|
||||||
rel_permittivity: bpy.props.FloatVectorProperty(
|
rel_permittivity: bpy.props.FloatVectorProperty(
|
||||||
name='Relative Permittivity',
|
name='Relative Permittivity',
|
||||||
description='Represents a simple, complex permittivity',
|
description='Represents a simple, complex permittivity',
|
||||||
|
@ -40,28 +32,13 @@ class MaxwellMediumBLSocket(base.MaxwellSimSocket):
|
||||||
)
|
)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Socket UI
|
# - FlowKinds
|
||||||
####################
|
|
||||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
|
||||||
col.prop(self, 'wl', text='λ')
|
|
||||||
col.separator(factor=1.0)
|
|
||||||
|
|
||||||
split = col.split(factor=0.35, align=False)
|
|
||||||
|
|
||||||
col = split.column(align=True)
|
|
||||||
col.label(text='ϵ_r (ℂ)')
|
|
||||||
|
|
||||||
col = split.column(align=True)
|
|
||||||
col.prop(self, 'rel_permittivity', text='')
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Computation of Default Value
|
|
||||||
####################
|
####################
|
||||||
@property
|
@property
|
||||||
def value(self) -> td.Medium:
|
def value(self) -> td.Medium:
|
||||||
freq = (
|
freq = (
|
||||||
spu.convert_to(
|
spu.convert_to(
|
||||||
VAC_SPEED_OF_LIGHT / (self.wl * self.unit),
|
VAC_SPEED_OF_LIGHT / FIXED_WL,
|
||||||
spu.hertz,
|
spu.hertz,
|
||||||
)
|
)
|
||||||
/ spu.hertz
|
/ spu.hertz
|
||||||
|
@ -76,25 +53,21 @@ class MaxwellMediumBLSocket(base.MaxwellSimSocket):
|
||||||
def value(
|
def value(
|
||||||
self, value: tuple[spux.ConstrSympyExpr(allow_variables=False), complex]
|
self, value: tuple[spux.ConstrSympyExpr(allow_variables=False), complex]
|
||||||
) -> None:
|
) -> None:
|
||||||
_wl, rel_permittivity = value
|
rel_permittivity = value
|
||||||
|
|
||||||
wl = float(
|
|
||||||
spu.convert_to(
|
|
||||||
_wl,
|
|
||||||
self.unit,
|
|
||||||
)
|
|
||||||
/ self.unit
|
|
||||||
)
|
|
||||||
self.wl = wl
|
|
||||||
self.rel_permittivity = (rel_permittivity.real, rel_permittivity.imag)
|
self.rel_permittivity = (rel_permittivity.real, rel_permittivity.imag)
|
||||||
|
|
||||||
def sync_unit_change(self):
|
####################
|
||||||
"""Override unit change to only alter frequency unit."""
|
# - UI
|
||||||
self.value = (
|
####################
|
||||||
self.wl * self.prev_unit,
|
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||||
complex(*self.rel_permittivity),
|
split = col.split(factor=0.35, align=False)
|
||||||
)
|
|
||||||
self.prev_active_unit = self.active_unit
|
col = split.column(align=True)
|
||||||
|
col.label(text='ϵ_r (ℂ)')
|
||||||
|
|
||||||
|
col = split.column(align=True)
|
||||||
|
col.prop(self, 'rel_permittivity', text='')
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
Loading…
Reference in New Issue