Compare commits

...

2 Commits

174 changed files with 5658 additions and 4643 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
dev
build
*.blend[0-9]
.cached-dependencies

View File

@ -1 +1 @@
3.10.13
3.11.8

View File

@ -6,16 +6,25 @@ authors = [
{ name = "Sofus Albert Høgsbro Rose", email = "blender-maxwell@sofusrose.com" }
]
dependencies = [
"tidy3d>=2.6.1",
"pydantic>=2.6.4",
"sympy>=1.12",
"scipy>=1.12.0",
"trimesh>=4.2.0",
"networkx>=3.2.1",
"rtree>=1.2.0",
"tidy3d~=2.6.1",
"pydantic~=2.6.4",
"sympy~=1.12",
"scipy~=1.12.0",
"trimesh~=4.2.0",
"networkx~=3.2.1",
"rtree~=1.2.0",
# Pin Blender 4.1.0-Compatible Versions
## The dependency resolver will report if anything is wonky.
"urllib3==1.26.8",
"requests==2.27.1",
"numpy==1.24.3",
"idna==3.3",
"charset-normalizer==2.0.10",
"certifi==2021.10.8",
]
readme = "README.md"
requires-python = "~= 3.10"
requires-python = "~= 3.11"
license = { text = "AGPL-3.0-or-later" }
####################
@ -26,13 +35,18 @@ managed = true
virtual = true
dev-dependencies = [
"ruff>=0.3.2",
"fake-bpy-module-4-0>=20231118", ## TODO: Update to Blender 4.1.0
]
[tool.rye.scripts]
dev = "python ./scripts/run.py"
####################
# - Tooling: Ruff
####################
[tool.ruff]
target-version = "py312"
target-version = "py311"
line-length = 79
[tool.ruff.lint]
@ -77,14 +91,15 @@ select = [
"PT", # flake8-pytest-style ## pytest-Specific Checks
]
ignore = [
"B008", # FastAPI uses this for Depends(), Security(), etc. .
"E701", # class foo(Parent): pass or if simple: return are perfectly elegant
"COM812", # Conflicts w/Formatter
"ISC001", # Conflicts w/Formatter
"Q000", # Conflicts w/Formatter
"Q001", # Conflicts w/Formatter
"Q002", # Conflicts w/Formatter
"Q003", # Conflicts w/Formatter
"B008", # FastAPI uses this for Depends(), Security(), etc. .
"E701", # class foo(Parent): pass or if simple: return are perfectly elegant
"ERA001", # 'Commented-out code' seems to be just about anything to ruff
]
####################

View File

@ -14,9 +14,9 @@ boto3==1.23.1
botocore==1.26.10
# via boto3
# via s3transfer
certifi==2024.2.2
certifi==2021.10.8
# via requests
charset-normalizer==3.3.2
charset-normalizer==2.0.10
# via requests
click==8.0.3
# via dask
@ -31,6 +31,7 @@ cycler==0.12.1
# via matplotlib
dask==2023.10.1
# via tidy3d
fake-bpy-module-4-0==20231118
fonttools==4.49.0
# via matplotlib
fsspec==2024.2.0
@ -40,7 +41,7 @@ h5netcdf==1.0.2
h5py==3.10.0
# via h5netcdf
# via tidy3d
idna==3.6
idna==3.3
# via requests
importlib-metadata==6.11.0
# via dask
@ -57,11 +58,10 @@ matplotlib==3.8.3
mpmath==1.3.0
# via sympy
networkx==3.2.1
numpy==1.26.4
numpy==1.24.3
# via contourpy
# via h5py
# via matplotlib
# via pandas
# via scipy
# via shapely
# via trimesh
@ -99,10 +99,10 @@ pyyaml==6.0.1
# via dask
# via responses
# via tidy3d
requests==2.31.0
requests==2.27.1
# via responses
# via tidy3d
responses==0.25.0
responses==0.23.1
# via tidy3d
rich==12.5.1
# via tidy3d
@ -124,12 +124,14 @@ toolz==0.12.1
# via dask
# via partd
trimesh==4.2.0
types-pyyaml==6.0.12.20240311
# via responses
typing-extensions==4.10.0
# via pydantic
# via pydantic-core
tzdata==2024.1
# via pandas
urllib3==1.26.18
urllib3==1.26.8
# via botocore
# via requests
# via responses

View File

@ -14,9 +14,9 @@ boto3==1.23.1
botocore==1.26.10
# via boto3
# via s3transfer
certifi==2024.2.2
certifi==2021.10.8
# via requests
charset-normalizer==3.3.2
charset-normalizer==2.0.10
# via requests
click==8.0.3
# via dask
@ -40,7 +40,7 @@ h5netcdf==1.0.2
h5py==3.10.0
# via h5netcdf
# via tidy3d
idna==3.6
idna==3.3
# via requests
importlib-metadata==6.11.0
# via dask
@ -57,11 +57,10 @@ matplotlib==3.8.3
mpmath==1.3.0
# via sympy
networkx==3.2.1
numpy==1.26.4
numpy==1.24.3
# via contourpy
# via h5py
# via matplotlib
# via pandas
# via scipy
# via shapely
# via trimesh
@ -99,10 +98,10 @@ pyyaml==6.0.1
# via dask
# via responses
# via tidy3d
requests==2.31.0
requests==2.27.1
# via responses
# via tidy3d
responses==0.25.0
responses==0.23.1
# via tidy3d
rich==12.5.1
# via tidy3d
@ -123,12 +122,14 @@ toolz==0.12.1
# via dask
# via partd
trimesh==4.2.0
types-pyyaml==6.0.12.20240311
# via responses
typing-extensions==4.10.0
# via pydantic
# via pydantic-core
tzdata==2024.1
# via pandas
urllib3==1.26.18
urllib3==1.26.8
# via botocore
# via requests
# via responses

108
run.py
View File

@ -1,108 +0,0 @@
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"

11
run.sh
View File

@ -1,11 +0,0 @@
#!/bin/bash
blender --python run.py
if [ $? -eq 42 ]; then
echo
echo
echo
echo
echo
blender --python run.py
fi

View File

155
scripts/bl_run.py 100644
View File

@ -0,0 +1,155 @@
"""Blender startup script ensuring correct addon installation.
See <https://github.com/dfelinto/blender/blob/master/release/scripts/modules/addon_utils.py>
"""
import shutil
import sys
import traceback
from pathlib import Path
import bpy
sys.path.insert(0, str(Path(__file__).resolve().parent))
import info
import pack
## TODO: Preferences item that allows using BLMaxwell 'starter.blend' as Blender's default starter blendfile.
####################
# - Addon Functions
####################
def delete_addon_if_loaded(addon_name: str) -> None:
"""Strongly inspired by Blender's addon_utils.py."""
should_restart_blender = False
# Check if Python Module is Loaded
mod = sys.modules.get(addon_name)
# if (mod := sys.modules.get(addon_name)) is None:
# ## It could still be loaded-by-default; then, it's in the prefs list
# is_loaded_now = False
# loads_by_default = addon_name in bpy.context.preferences.addons
# else:
# ## BL sets __addon_enabled__ on module of enabled addons.
# ## BL sets __addon_persistent__ on module of load-by-default addons.
# is_loaded_now = getattr(mod, '__addon_enabled__', False)
# loads_by_default = getattr(mod, '__addon_persistent__', False)
# Unregister Modules and Mark Disabled & Non-Persistent
## This effectively disables it
if mod is not None:
mod.__addon_enabled__ = False
mod.__addon_persistent__ = False
try:
mod.unregister()
except BaseException:
traceback.print_exc()
should_restart_blender = True
# Remove Addon
## Remove Addon from Preferences
## - Unsure why addon_utils has a while, but let's trust the process...
while addon_name in bpy.context.preferences.addons:
addon = bpy.context.preferences.addons.get(addon_name)
if addon:
bpy.context.preferences.addons.remove(addon)
## Physically Excise Addon Code
for addons_path in bpy.utils.script_paths(subdir='addons'):
addon_path = Path(addons_path) / addon_name
if addon_path.exists():
shutil.rmtree(addon_path)
should_restart_blender = True
## Save User Preferences
bpy.ops.wm.save_userpref()
# Quit (Restart) 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>
## - By passing STATUS_UNINSTALLED_ADDON, we report that it's clean now.
if should_restart_blender:
bpy.ops.wm.quit_blender()
sys.exit(info.STATUS_UNINSTALLED_ADDON)
def install_addon(addon_name: str, addon_zip: Path) -> None:
"""Strongly inspired by Blender's addon_utils.py."""
# Check if Addon is Installable
if any(
[
(mod := sys.modules.get(addon_name)) is not None,
addon_name in bpy.context.preferences.addons,
any(
(Path(addon_path) / addon_name).exists()
for addon_path in bpy.utils.script_paths(subdir='addons')
),
]
):
## TODO: Check if addon file path exists?
in_pref_addons = addon_name in bpy.context.preferences.addons
existing_files_found = {
addon_path: (Path(addon_path) / addon_name).exists()
for addon_path in bpy.utils.script_paths(subdir='addons')
if (Path(addon_path) / addon_name).exists()
}
msg = f"Addon (module = '{mod}') is not installable (in preferences.addons: {in_pref_addons}) (existing files found: {existing_files_found})"
raise ValueError(msg)
# Install Addon
bpy.ops.preferences.addon_install(filepath=str(addon_zip))
if not any(
(Path(addon_path) / addon_name).exists()
for addon_path in bpy.utils.script_paths(subdir='addons')
):
msg = f"Couldn't install addon {addon_name}"
raise RuntimeError(msg)
# Enable Addon
bpy.ops.preferences.addon_enable(module=addon_name)
if addon_name not in bpy.context.preferences.addons:
msg = f"Couldn't enable addon {addon_name}"
raise RuntimeError(msg)
# Set Dev Path for Addon Dependencies
addon_prefs = bpy.context.preferences.addons[addon_name].preferences
addon_prefs.use_default_path_addon_pydeps = False
addon_prefs.path_addon_pydeps = info.PATH_ADDON_DEV_DEPS
# Save User Preferences
bpy.ops.wm.save_userpref()
####################
# - Entrypoint
####################
if __name__ == '__main__':
# Delete Addon (maybe; possibly restart)
delete_addon_if_loaded(info.ADDON_NAME)
# Signal that Live-Printing can Start
print(info.SIGNAL_START_CLEAN_BLENDER) # noqa: T201
# Install and Enable Addon
install_failed = False
with pack.zipped_addon(
info.PATH_ADDON_PKG,
info.PATH_ADDON_ZIP,
info.PATH_ROOT / 'pyproject.toml',
info.PATH_ROOT / 'requirements.lock',
) as path_zipped:
try:
install_addon(info.ADDON_NAME, path_zipped)
except Exception as exe:
traceback.print_exc()
install_failed = True
# Load Development .blend
## TODO: We need a better (also final-deployed-compatible) solution for what happens when a user opened a .blend file without installing dependencies!
if not install_failed:
bpy.ops.wm.open_mainfile(filepath=str(info.PATH_ADDON_DEV_BLEND))
else:
bpy.ops.wm.quit_blender()
sys.exit(info.STATUS_NOINSTALL_ADDON)

51
scripts/info.py 100644
View File

@ -0,0 +1,51 @@
import tomllib
from pathlib import Path
PATH_ROOT = Path(__file__).resolve().parent.parent
PATH_RUN = PATH_ROOT / 'scripts' / 'run.py'
PATH_BL_RUN = PATH_ROOT / 'scripts' / 'bl_run.py'
PATH_BUILD = PATH_ROOT / 'build'
PATH_BUILD.mkdir(exist_ok=True)
PATH_DEV = PATH_ROOT / 'dev'
PATH_DEV.mkdir(exist_ok=True)
####################
# - BL_RUN stdout Signals
####################
SIGNAL_START_CLEAN_BLENDER = 'SIGNAL__blender_is_clean'
####################
# - BL_RUN Exit Codes
####################
STATUS_UNINSTALLED_ADDON = 42
STATUS_NOINSTALL_ADDON = 68
####################
# - Addon Information
####################
with (PATH_ROOT / 'pyproject.toml').open('rb') as f:
PROJ_SPEC = tomllib.load(f)
ADDON_NAME = PROJ_SPEC['project']['name']
ADDON_VERSION = PROJ_SPEC['project']['version']
####################
# - Packaging Information
####################
PATH_ADDON_PKG = PATH_ROOT / 'src' / ADDON_NAME
PATH_ADDON_ZIP = (
PATH_ROOT / 'build' / (ADDON_NAME + '__' + ADDON_VERSION + '.zip')
)
PATH_ADDON_BLEND_STARTER = PATH_ADDON_PKG / 'blenders' / 'starter.blend'
# Install the ZIPped Addon
####################
# - Development Information
####################
PATH_ADDON_DEV_BLEND = PATH_DEV / 'demo.blend'
PATH_ADDON_DEV_DEPS = PATH_DEV / '.cached-dev-dependencies'
PATH_ADDON_DEV_DEPS.mkdir(exist_ok=True)

93
scripts/pack.py 100644
View File

@ -0,0 +1,93 @@
import contextlib
import tempfile
import typing as typ
import zipfile
from pathlib import Path
import info
_PROJ_VERSION_STR = str(
tuple(int(el) for el in info.PROJ_SPEC['project']['version'].split('.'))
)
_PROJ_DESC_STR = info.PROJ_SPEC['project']['description']
BL_INFO_REPLACEMENTS = {
"'version': (0, 0, 0),": f"'version': {_PROJ_VERSION_STR},",
"'description': 'Placeholder',": f"'description': '{_PROJ_DESC_STR}',",
}
@contextlib.contextmanager
def zipped_addon(
path_addon_pkg: Path,
path_addon_zip: Path,
path_pyproject_toml: Path,
path_requirements_lock: Path,
replace_if_exists: bool = False,
) -> typ.Iterator[Path]:
"""Context manager exposing a folder as a (temporary) zip file.
The .zip file is deleted afterwards.
"""
# Delete Existing ZIP (maybe)
if path_addon_zip.is_file():
if replace_if_exists:
msg = 'File already exists where ZIP would be made'
raise ValueError(msg)
path_addon_zip.unlink()
# Create New ZIP file of the addon directory
with zipfile.ZipFile(path_addon_zip, 'w', zipfile.ZIP_DEFLATED) as f_zip:
# Install Addon Files @ /*
for file_to_zip in path_addon_pkg.rglob('*'):
# Dynamically Alter 'bl_info' in __init__.py
## This is the only way to propagate ex. version information
if str(file_to_zip.relative_to(path_addon_pkg)) == '__init__.py':
with (
file_to_zip.open('r') as f_init,
tempfile.NamedTemporaryFile(mode='w') as f_tmp,
):
initpy = f_init.read()
for (
to_replace,
replacement,
) in BL_INFO_REPLACEMENTS.items():
initpy = initpy.replace(to_replace, replacement)
f_tmp.write(initpy)
# Write to ZIP
f_zip.writestr(
str(file_to_zip.relative_to(path_addon_pkg.parent)),
initpy,
)
# Write File to Zip
else:
f_zip.write(
file_to_zip, file_to_zip.relative_to(path_addon_pkg.parent)
)
# Install pyproject.toml @ /pyproject.toml of Addon
f_zip.write(
path_pyproject_toml,
str(
(Path(path_addon_pkg.name) / Path(path_pyproject_toml.name))
.with_suffix('')
.with_suffix('.toml')
),
)
# Install requirements.lock @ /requirements.txt of Addon
f_zip.write(
path_requirements_lock,
str(
(Path(path_addon_pkg.name) / Path(path_requirements_lock.name))
.with_suffix('')
.with_suffix('.txt')
),
)
# Delete the ZIP
try:
yield path_addon_zip
finally:
path_addon_zip.unlink()

54
scripts/run.py 100644
View File

@ -0,0 +1,54 @@
import os
import subprocess
from pathlib import Path
import info
####################
# - Blender Runner
####################
def run_blender(py_script: Path, print_live: bool = False):
process = subprocess.Popen(
['blender', '--python', str(py_script)],
env=os.environ | {'PYTHONUNBUFFERED': '1'},
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
)
output = []
printing_live = print_live
# Process Real-Time Output
for line in iter(process.stdout.readline, b''):
if not line:
break
if printing_live:
print(line, end='') # noqa: T201
elif (
info.SIGNAL_START_CLEAN_BLENDER in line
# or 'Traceback (most recent call last)' in line
):
printing_live = True
print(''.join(output)) # noqa: T201
else:
output.append(line)
# Wait for the process to finish and get the exit code
process.wait()
return process.returncode, output
####################
# - Run Blender w/Clean Addon Reinstall
####################
if __name__ == '__main__':
return_code, output = run_blender(info.PATH_BL_RUN, print_live=False)
if return_code == info.STATUS_UNINSTALLED_ADDON:
return_code, output = run_blender(info.PATH_BL_RUN, print_live=True)
if return_code == info.STATUS_NOINSTALL_ADDON:
msg = f"Couldn't install addon {info.ADDON_NAME}"
raise ValueError(msg)
elif return_code != 0:
print(''.join(output)) # noqa: T201

View File

@ -1,84 +1,97 @@
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",
}
import tomllib
from pathlib import Path
####################
# - 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)
from . import operators_nodeps, preferences, registration
from .utils import pydeps
from .utils import logger as _logger
log = _logger.get()
PATH_ADDON_ROOT = Path(__file__).resolve().parent
with (PATH_ADDON_ROOT / 'pyproject.toml').open('rb') as f:
PROJ_SPEC = tomllib.load(f)
####################
# - Addon Information
####################
# The following parameters are replaced when packing the addon ZIP
## - description
## - version
bl_info = {
'name': 'Maxwell PDE Sim and Viz',
'blender': (4, 1, 0),
'category': 'Node',
'description': 'Placeholder',
'author': 'Sofus Albert Høgsbro Rose',
'version': (0, 0, 0),
'wiki_url': 'https://git.sofus.io/dtu-courses/bsc_thesis',
'tracker_url': 'https://git.sofus.io/dtu-courses/bsc_thesis/issues',
}
## bl_info MUST readable via. ast.parse
## See scripts/pack.py::BL_INFO_REPLACEMENTS for active replacements
## The mechanism is a 'dumb' - output of 'ruff fmt' MUST be basis for replacing
def ADDON_PREFS():
return bpy.context.preferences.addons[
PROJ_SPEC['project']['name']
].preferences
####################
# - Load and Register Addon
####################
BL_REGISTER__BEFORE_DEPS = [
*operators_nodeps.BL_REGISTER,
*preferences.BL_REGISTER,
]
def BL_REGISTER__AFTER_DEPS(path_deps: Path):
with pydeps.importable_addon_deps(path_deps):
from . import node_trees, operators
return [
*operators.BL_REGISTER,
*node_trees.BL_REGISTER,
]
def BL_KEYMAP_ITEM_DEFS(path_deps: Path):
with pydeps.importable_addon_deps(path_deps):
from . import operators
return [
*operators.BL_KMI_REGISTER,
]
####################
# - Registration
####################
BL_REGISTER = [
*node_trees.BL_REGISTER,
*operators.BL_REGISTER,
*preferences.BL_REGISTER,
]
BL_KMI_REGISTER = [
*operators.BL_KMI_REGISTER,
]
BL_NODE_CATEGORIES = [
*node_trees.BL_NODE_CATEGORIES,
]
km = bpy.context.window_manager.keyconfigs.addon.keymaps.new(
name='Node Editor',
space_type="NODE_EDITOR",
)
REGISTERED_KEYMAPS = []
def register():
global REGISTERED_KEYMAPS
# Register Barebones Addon for Dependency Installation
registration.register_classes(BL_REGISTER__BEFORE_DEPS)
for cls in BL_REGISTER:
bpy.utils.register_class(cls)
# Retrieve PyDeps Path from Addon Preferences
addon_prefs = ADDON_PREFS()
path_pydeps = addon_prefs.path_addon_pydeps
for kmi_def in BL_KMI_REGISTER:
kmi = km.keymap_items.new(
*kmi_def["_"],
ctrl=kmi_def["ctrl"],
shift=kmi_def["shift"],
alt=kmi_def["alt"],
# If Dependencies are Satisfied, Register Everything
if pydeps.check_pydeps(path_pydeps):
registration.register_classes(BL_REGISTER__AFTER_DEPS())
registration.register_keymap_items(BL_KEYMAP_ITEM_DEFS())
else:
# Delay Registration
registration.delay_registration(
registration.EVENT__DEPS_SATISFIED,
classes_cb=BL_REGISTER__AFTER_DEPS,
keymap_item_defs_cb=BL_KEYMAP_ITEM_DEFS,
)
REGISTERED_KEYMAPS.append(kmi)
# TODO: A popup before the addon fully loads or something like that?
## TODO: Communicate that deps must be installed and all that?
def unregister():
for cls in reversed(BL_REGISTER):
bpy.utils.unregister_class(cls)
for kmi in REGISTERED_KEYMAPS:
km.keymap_items.remove(kmi)
if __name__ == "__main__":
register()
registration.unregister_classes()
registration.unregister_keymap_items()

BIN
src/blender_maxwell/blends/starter.blend (Stored with Git LFS) 100644

Binary file not shown.

View File

@ -1,4 +1,5 @@
import sympy as sp
sp.printing.str.StrPrinter._default_settings['abbrev'] = True
## In this tree, all Sympy unit printing must be abbreviated.
## By configuring this in __init__.py, we guarantee it for all subimports.

View File

@ -26,36 +26,35 @@ Unit = typ.Any ## Type of a valid unit
SOCKET_DEFS = {
socket_type: getattr(
sck,
socket_type.value.removesuffix("SocketType") + "SocketDef",
socket_type.value.removesuffix('SocketType') + 'SocketDef',
)
for socket_type in ST
if hasattr(
sck,
socket_type.value.removesuffix("SocketType") + "SocketDef"
)
if hasattr(sck, socket_type.value.removesuffix('SocketType') + 'SocketDef')
}
## TODO: Bit of a hack. Is it robust enough?
for socket_type in ST:
if not hasattr(
sck,
socket_type.value.removesuffix("SocketType") + "SocketDef",
socket_type.value.removesuffix('SocketType') + 'SocketDef',
):
print("Missing SocketDef for", socket_type.value)
print('Missing SocketDef for', socket_type.value)
####################
# - BL Socket Size Parser
####################
BL_SOCKET_3D_TYPE_PREFIXES = {
"NodeSocketVector",
"NodeSocketRotation",
'NodeSocketVector',
'NodeSocketRotation',
}
BL_SOCKET_4D_TYPE_PREFIXES = {
"NodeSocketColor",
'NodeSocketColor',
}
def size_from_bl_interface_socket(
bl_interface_socket: bpy.types.NodeTreeInterfaceSocket
bl_interface_socket: bpy.types.NodeTreeInterfaceSocket,
) -> typx.Literal[1, 2, 3, 4]:
"""Parses the `size`, aka. number of elements, contained within the `default_value` of a Blender interface socket.
@ -66,7 +65,8 @@ def size_from_bl_interface_socket(
- For 3D sockets, a hard-coded list of Blender node socket types is used.
- Else, it is a 1D socket type.
"""
if bl_interface_socket.description.startswith("2D"): return 2
if bl_interface_socket.description.startswith('2D'):
return 2
if any(
bl_interface_socket.socket_type.startswith(bl_socket_3d_type_prefix)
for bl_socket_3d_type_prefix in BL_SOCKET_3D_TYPE_PREFIXES
@ -114,7 +114,7 @@ def parse_bl_interface_socket(
# Parse Description for Socket Type
tokens = (
_tokens
if (_tokens := bl_interface_socket.description.split(" "))[0] != "2D"
if (_tokens := bl_interface_socket.description.split(' '))[0] != '2D'
else _tokens[1:]
) ## Don't include the "2D" token, if defined.
if (
@ -122,25 +122,36 @@ def parse_bl_interface_socket(
(tokens[0], bl_interface_socket.socket_type, size)
)
) is None:
return (direct_socket_type, None) ## Description doesn't map to anything
return (
direct_socket_type,
None,
) ## Description doesn't map to anything
# Determine Socket Unit (to use instead of "unit system")
## This is entirely OPTIONAL
socket_unit = None
if socket_type in ct.SOCKET_UNITS:
## Case: Unit is User-Defined
if len(tokens) > 1 and "(" in tokens[1] and ")" in tokens[1]:
if len(tokens) > 1 and '(' in tokens[1] and ')' in tokens[1]:
# Compute (<unit_str>) as Unit Token
unit_token = tokens[1].removeprefix("(").removesuffix(")")
unit_token = tokens[1].removeprefix('(').removesuffix(')')
# Compare Unit Token to Valid Sympy-Printed Units
socket_unit = _socket_unit if (_socket_unit := [
socket_unit = (
_socket_unit
if (
_socket_unit := [
unit
for unit in ct.SOCKET_UNITS[socket_type]["values"].values()
for unit in ct.SOCKET_UNITS[socket_type][
'values'
].values()
if str(unit) == unit_token
]) else ct.SOCKET_UNITS[socket_type]["values"][
ct.SOCKET_UNITS[socket_type]["default"]
]
)
else ct.SOCKET_UNITS[socket_type]['values'][
ct.SOCKET_UNITS[socket_type]['default']
]
)
## TODO: Enforce abbreviated sympy printing here, not globally
return (socket_type, socket_unit)
@ -152,11 +163,8 @@ def parse_bl_interface_socket(
def socket_def_from_bl_interface_socket(
bl_interface_socket: bpy.types.NodeTreeInterfaceSocket,
):
"""Computes an appropriate (no-arg) SocketDef from the given `bl_interface_socket`, by parsing it.
"""
return SOCKET_DEFS[
parse_bl_interface_socket(bl_interface_socket)[0]
]
"""Computes an appropriate (no-arg) SocketDef from the given `bl_interface_socket`, by parsing it."""
return SOCKET_DEFS[parse_bl_interface_socket(bl_interface_socket)[0]]
####################
@ -190,6 +198,7 @@ def value_from_bl(
return parsed_bl_socket_value
####################
# - Convert to Blender-Compatible Value
####################
@ -211,6 +220,7 @@ def make_scalar_bl_compat(scalar: typ.Any) -> typ.Any:
return scalar
def value_to_bl(
bl_interface_socket: bpy.types.NodeSocket,
value: typ.Any,
@ -221,31 +231,29 @@ def value_to_bl(
# Set Socket
if unit is not None:
bl_socket_value = spu.convert_to(value, unit) / unit
elif (
unit_system is not None
and socket_type in unit_system
):
bl_socket_value = spu.convert_to(
value, unit_system[socket_type]
) / unit_system[socket_type]
elif unit_system is not None and socket_type in unit_system:
bl_socket_value = (
spu.convert_to(value, unit_system[socket_type])
/ unit_system[socket_type]
)
else:
bl_socket_value = value
return {
1: lambda: make_scalar_bl_compat(bl_socket_value),
2: lambda: tuple([
2: lambda: tuple(
[
make_scalar_bl_compat(bl_socket_value[0]),
make_scalar_bl_compat(bl_socket_value[1]),
bl_interface_socket.default_value[2]
bl_interface_socket.default_value[2],
## Don't touch (unused) 3rd bl_socket coordinate
]),
3: lambda: tuple([
make_scalar_bl_compat(el)
for el in bl_socket_value
]),
4: lambda: tuple([
make_scalar_bl_compat(el)
for el in bl_socket_value
]),
]
),
3: lambda: tuple(
[make_scalar_bl_compat(el) for el in bl_socket_value]
),
4: lambda: tuple(
[make_scalar_bl_compat(el) for el in bl_socket_value]
),
}[size_from_bl_interface_socket(bl_interface_socket)]()
## The 'lambda' delays construction until size is determined

View File

@ -6,6 +6,8 @@ from . import contracts as ct
from .nodes import BL_NODES
DYNAMIC_SUBMENU_REGISTRATIONS = []
def mk_node_categories(
tree,
syllable_prefix=[],
@ -15,7 +17,7 @@ def mk_node_categories(
items = []
# Add Node Items
base_category = ct.NodeCategory["_".join(syllable_prefix)]
base_category = ct.NodeCategory['_'.join(syllable_prefix)]
for node_type, node_category in BL_NODES.items():
if node_category == base_category:
items.append(nodeitems_utils.NodeItem(node_type.value))
@ -23,16 +25,15 @@ def mk_node_categories(
# Add Node Sub-Menus
for syllable, sub_tree in tree.items():
current_syllable_path = syllable_prefix + [syllable]
current_category = ct.NodeCategory[
"_".join(current_syllable_path)
]
current_category = ct.NodeCategory['_'.join(current_syllable_path)]
# Build Items for Sub-Categories
subitems = mk_node_categories(
sub_tree,
current_syllable_path,
)
if len(subitems) == 0: continue
if len(subitems) == 0:
continue
# Define Dynamic Node Submenu
def draw_factory(items):
@ -44,7 +45,7 @@ def mk_node_categories(
):
nodeitem = nodeitem_or_submenu
op_add_node_cfg = self.layout.operator(
"node.add_node",
'node.add_node',
text=nodeitem.label,
)
op_add_node_cfg.type = nodeitem.nodetype
@ -52,13 +53,18 @@ def mk_node_categories(
elif isinstance(nodeitem_or_submenu, str):
submenu_id = nodeitem_or_submenu
self.layout.menu(submenu_id)
return draw
menu_class = type(str(current_category.value), (bpy.types.Menu,), {
menu_class = type(
str(current_category.value),
(bpy.types.Menu,),
{
'bl_idname': current_category.value,
'bl_label': ct.NODE_CAT_LABELS[current_category],
'draw': draw_factory(tuple(subitems)),
})
},
)
# Report to Items and Registration List
items.append(current_category.value)
@ -67,19 +73,19 @@ def mk_node_categories(
return items
####################
# - Blender Registration
####################
BL_NODE_CATEGORIES = mk_node_categories(
ct.NodeCategory.get_tree()["MAXWELLSIM"],
syllable_prefix = ["MAXWELLSIM"],
ct.NodeCategory.get_tree()['MAXWELLSIM'],
syllable_prefix=['MAXWELLSIM'],
)
## TODO: refactor, this has a big code smell
BL_REGISTER = [
*DYNAMIC_SUBMENU_REGISTRATIONS
] ## Must be run after, right now.
## TEST - TODO this is a big code smell
def menu_draw(self, context):
if context.space_data.tree_type == ct.TreeType.MaxwellSim.value:
@ -88,4 +94,5 @@ def menu_draw(self, context):
submenu_id = nodeitem_or_submenu
self.layout.menu(submenu_id)
bpy.types.NODE_MT_add.append(menu_draw)

View File

@ -7,20 +7,32 @@ import bpy
####################
# - Pure BL Types
####################
BLEnumID = pytypes_ext.Annotated[str, pyd.StringConstraints(
BLEnumID = pytypes_ext.Annotated[
str,
pyd.StringConstraints(
pattern=r'^[A-Z_]+$',
)]
SocketName = pytypes_ext.Annotated[str, pyd.StringConstraints(
),
]
SocketName = pytypes_ext.Annotated[
str,
pyd.StringConstraints(
pattern=r'^[a-zA-Z0-9_]+$',
)]
PresetName = pytypes_ext.Annotated[str, pyd.StringConstraints(
),
]
PresetName = pytypes_ext.Annotated[
str,
pyd.StringConstraints(
pattern=r'^[a-zA-Z0-9_]+$',
)]
),
]
BLColorRGBA = tuple[float, float, float, float]
####################
# - Shared-With-BL Types
####################
ManagedObjName = pytypes_ext.Annotated[str, pyd.StringConstraints(
ManagedObjName = pytypes_ext.Annotated[
str,
pyd.StringConstraints(
pattern=r'^[a-z_]+$',
)]
),
]

View File

@ -2,6 +2,7 @@ import enum
from ....utils.blender_type_enum import BlenderTypeEnum
class DataFlowKind(BlenderTypeEnum):
"""Defines a shape/kind of data that may flow through a node tree.

View File

@ -1,4 +1,5 @@
from ....utils.blender_type_enum import BlenderTypeEnum
class Icon(BlenderTypeEnum):
SimNodeEditor = "MOD_SIMPLEDEFORM"
SimNodeEditor = 'MOD_SIMPLEDEFORM'

View File

@ -1,8 +1,7 @@
import enum
from ....utils.blender_type_enum import (
BlenderTypeEnum
)
from ....utils.blender_type_enum import BlenderTypeEnum
class ManagedObjType(BlenderTypeEnum):
ManagedBLObject = enum.auto()

View File

@ -2,48 +2,39 @@ from .node_cats import NodeCategory as NC
NODE_CAT_LABELS = {
# Inputs/
NC.MAXWELLSIM_INPUTS: "Inputs",
NC.MAXWELLSIM_INPUTS_IMPORTERS: "Importers",
NC.MAXWELLSIM_INPUTS_SCENE: "Scene",
NC.MAXWELLSIM_INPUTS_PARAMETERS: "Parameters",
NC.MAXWELLSIM_INPUTS_CONSTANTS: "Constants",
NC.MAXWELLSIM_INPUTS_LISTS: "Lists",
NC.MAXWELLSIM_INPUTS: 'Inputs',
NC.MAXWELLSIM_INPUTS_IMPORTERS: 'Importers',
NC.MAXWELLSIM_INPUTS_SCENE: 'Scene',
NC.MAXWELLSIM_INPUTS_PARAMETERS: 'Parameters',
NC.MAXWELLSIM_INPUTS_CONSTANTS: 'Constants',
NC.MAXWELLSIM_INPUTS_LISTS: 'Lists',
# Outputs/
NC.MAXWELLSIM_OUTPUTS: "Outputs",
NC.MAXWELLSIM_OUTPUTS_VIEWERS: "Viewers",
NC.MAXWELLSIM_OUTPUTS_EXPORTERS: "Exporters",
NC.MAXWELLSIM_OUTPUTS_PLOTTERS: "Plotters",
NC.MAXWELLSIM_OUTPUTS: 'Outputs',
NC.MAXWELLSIM_OUTPUTS_VIEWERS: 'Viewers',
NC.MAXWELLSIM_OUTPUTS_EXPORTERS: 'Exporters',
NC.MAXWELLSIM_OUTPUTS_PLOTTERS: 'Plotters',
# Sources/
NC.MAXWELLSIM_SOURCES: "Sources",
NC.MAXWELLSIM_SOURCES_TEMPORALSHAPES: "Temporal Shapes",
NC.MAXWELLSIM_SOURCES: 'Sources',
NC.MAXWELLSIM_SOURCES_TEMPORALSHAPES: 'Temporal Shapes',
# Mediums/
NC.MAXWELLSIM_MEDIUMS: "Mediums",
NC.MAXWELLSIM_MEDIUMS_NONLINEARITIES: "Non-Linearities",
NC.MAXWELLSIM_MEDIUMS: 'Mediums',
NC.MAXWELLSIM_MEDIUMS_NONLINEARITIES: 'Non-Linearities',
# Structures/
NC.MAXWELLSIM_STRUCTURES: "Structures",
NC.MAXWELLSIM_STRUCTURES_PRIMITIVES: "Primitives",
NC.MAXWELLSIM_STRUCTURES: 'Structures',
NC.MAXWELLSIM_STRUCTURES_PRIMITIVES: 'Primitives',
# Bounds/
NC.MAXWELLSIM_BOUNDS: "Bounds",
NC.MAXWELLSIM_BOUNDS_BOUNDCONDS: "Bound Conds",
NC.MAXWELLSIM_BOUNDS: 'Bounds',
NC.MAXWELLSIM_BOUNDS_BOUNDCONDS: 'Bound Conds',
# Monitors/
NC.MAXWELLSIM_MONITORS: "Monitors",
NC.MAXWELLSIM_MONITORS_NEARFIELDPROJECTIONS: "Near-Field Projections",
NC.MAXWELLSIM_MONITORS: 'Monitors',
NC.MAXWELLSIM_MONITORS_NEARFIELDPROJECTIONS: 'Near-Field Projections',
# Simulations/
NC.MAXWELLSIM_SIMS: "Simulations",
NC.MAXWELLSIM_SIMGRIDAXES: "Sim Grid Axes",
NC.MAXWELLSIM_SIMS: 'Simulations',
NC.MAXWELLSIM_SIMGRIDAXES: 'Sim Grid Axes',
# Utilities/
NC.MAXWELLSIM_UTILITIES: "Utilities",
NC.MAXWELLSIM_UTILITIES_CONVERTERS: "Converters",
NC.MAXWELLSIM_UTILITIES_OPERATIONS: "Operations",
NC.MAXWELLSIM_UTILITIES: 'Utilities',
NC.MAXWELLSIM_UTILITIES_CONVERTERS: 'Converters',
NC.MAXWELLSIM_UTILITIES_OPERATIONS: 'Operations',
# Viz/
NC.MAXWELLSIM_VIZ: "Viz",
NC.MAXWELLSIM_VIZ: 'Viz',
}

View File

@ -1,8 +1,7 @@
import enum
from ....utils.blender_type_enum import (
BlenderTypeEnum, wrap_values_in_MT
)
from ....utils.blender_type_enum import BlenderTypeEnum, wrap_values_in_MT
@wrap_values_in_MT
class NodeCategory(BlenderTypeEnum):
@ -58,9 +57,9 @@ class NodeCategory(BlenderTypeEnum):
def get_tree(cls):
## TODO: Refactor
syllable_categories = [
str(node_category.value).split("_")
str(node_category.value).split('_')
for node_category in cls
if node_category.value != "MAXWELLSIM"
if node_category.value != 'MAXWELLSIM'
]
category_tree = {}

View File

@ -1,9 +1,11 @@
import enum
from ....utils.blender_type_enum import (
BlenderTypeEnum, append_cls_name_to_values
BlenderTypeEnum,
append_cls_name_to_values,
)
@append_cls_name_to_values
class NodeType(BlenderTypeEnum):
KitchenSink = enum.auto()
@ -35,7 +37,6 @@ class NodeType(BlenderTypeEnum):
## Inputs /
InputFile = enum.auto()
# Outputs
## Outputs / Viewers
Viewer = enum.auto()
@ -46,7 +47,6 @@ class NodeType(BlenderTypeEnum):
JSONFileExporter = enum.auto()
Tidy3DWebExporter = enum.auto()
# Sources
## Sources / Temporal Shapes
GaussianPulseTemporalShape = enum.auto()
@ -95,7 +95,6 @@ class NodeType(BlenderTypeEnum):
SphereStructure = enum.auto()
CylinderStructure = enum.auto()
# Bounds
BoundConds = enum.auto()
@ -108,7 +107,6 @@ class NodeType(BlenderTypeEnum):
PeriodicBoundCond = enum.auto()
AbsorbingBoundCond = enum.auto()
# Monitors
EHFieldMonitor = enum.auto()
FieldPowerFluxMonitor = enum.auto()
@ -120,7 +118,6 @@ class NodeType(BlenderTypeEnum):
ObservationAngleNearFieldProjectionMonitor = enum.auto()
KSpaceNearFieldProjectionMonitor = enum.auto()
# Sims
SimDomain = enum.auto()
SimGrid = enum.auto()
@ -134,7 +131,6 @@ class NodeType(BlenderTypeEnum):
## Sim /
FDTDSim = enum.auto()
# Utilities
Combine = enum.auto()
Separate = enum.auto()
@ -146,7 +142,5 @@ class NodeType(BlenderTypeEnum):
## Utilities / Operations
ArrayOperation = enum.auto()
# Viz
FDTDSimDataViz = enum.auto()

View File

@ -1,33 +1,26 @@
import typing as typ
import typing as typx
import pydantic as pyd
import bpy
from ..bl import ManagedObjName, SocketName
from ..bl import ManagedObjName
from ..managed_obj_type import ManagedObjType
class ManagedObj(typ.Protocol):
managed_obj_type: ManagedObjType
def __init__(
self,
name: ManagedObjName,
):
...
): ...
@property
def name(self) -> str: ...
@name.setter
def name(self, value: str): ...
def free(self):
...
def free(self): ...
def bl_select(self):
"""If this is a managed Blender object, and the operation "select this in Blender" makes sense, then do so.
Else, do nothing.
"""
pass

View File

@ -6,6 +6,7 @@ import pydantic as pyd
from ..bl import PresetName, SocketName, BLEnumID
from .managed_obj import ManagedObj
class ManagedObjDef(pyd.BaseModel):
mk: typ.Callable[[str], ManagedObj]
name_prefix: str = ""
name_prefix: str = ''

View File

@ -4,6 +4,7 @@ import pydantic as pyd
from ..bl import PresetName, SocketName, BLEnumID
class PresetDef(pyd.BaseModel):
label: PresetName
description: str

View File

@ -4,9 +4,9 @@ import bpy
from ..socket_types import SocketType
@typ.runtime_checkable
class SocketDef(typ.Protocol):
socket_type: SocketType
def init(self, bl_socket: bpy.types.NodeSocket) -> None:
...
def init(self, bl_socket: bpy.types.NodeSocket) -> None: ...

View File

@ -10,13 +10,11 @@ SOCKET_COLORS = {
ST.Bool: (0.7, 0.7, 0.7, 1.0), # Medium Light Grey
ST.String: (0.7, 0.7, 0.7, 1.0), # Medium Light Grey
ST.FilePath: (0.6, 0.6, 0.6, 1.0), # Medium Grey
# Number
ST.IntegerNumber: (0.5, 0.5, 1.0, 1.0), # Light Blue
ST.RationalNumber: (0.4, 0.4, 0.9, 1.0), # Medium Light Blue
ST.RealNumber: (0.3, 0.3, 0.8, 1.0), # Medium Blue
ST.ComplexNumber: (0.2, 0.2, 0.7, 1.0), # Dark Blue
# Vector
ST.Integer2DVector: (0.5, 1.0, 0.5, 1.0), # Light Green
ST.Real2DVector: (0.5, 1.0, 0.5, 1.0), # Light Green
@ -24,7 +22,6 @@ SOCKET_COLORS = {
ST.Integer3DVector: (0.3, 0.8, 0.3, 1.0), # Medium Green
ST.Real3DVector: (0.3, 0.8, 0.3, 1.0), # Medium Green
ST.Complex3DVector: (0.2, 0.7, 0.2, 1.0), # Dark Green
# Physical
ST.PhysicalUnitSystem: (1.0, 0.5, 0.5, 1.0), # Light Red
ST.PhysicalTime: (1.0, 0.5, 0.5, 1.0), # Light Red
@ -44,14 +41,12 @@ SOCKET_COLORS = {
ST.PhysicalForce3D: (0.6, 0.45, 0.25, 1.0), # Medium Dark Orange
ST.PhysicalPol: (0.5, 0.4, 0.2, 1.0), # Dark Orange
ST.PhysicalFreq: (1.0, 0.7, 0.5, 1.0), # Light Peach
# Blender
ST.BlenderObject: (0.7, 0.5, 1.0, 1.0), # Light Purple
ST.BlenderCollection: (0.6, 0.45, 0.9, 1.0), # Medium Light Purple
ST.BlenderImage: (0.5, 0.4, 0.8, 1.0), # Medium Purple
ST.BlenderGeoNodes: (0.3, 0.3, 0.6, 1.0), # Dark Purple
ST.BlenderText: (0.5, 0.5, 0.75, 1.0), # Light Lavender
# Maxwell
ST.MaxwellSource: (1.0, 1.0, 0.5, 1.0), # Light Yellow
ST.MaxwellTemporalShape: (0.9, 0.9, 0.45, 1.0), # Medium Light Yellow
@ -66,8 +61,6 @@ SOCKET_COLORS = {
ST.MaxwellSimGrid: (0.5, 0.4, 0.3, 1.0), # Dark Gold
ST.MaxwellSimGridAxis: (0.4, 0.3, 0.25, 1.0), # Darkest Gold
ST.MaxwellSimDomain: (0.4, 0.3, 0.25, 1.0), # Darkest Gold
# Tidy3D
ST.Tidy3DCloudTask: (0.4, 0.3, 0.25, 1.0), # Darkest Gold
}

View File

@ -1,78 +1,62 @@
from .socket_types import SocketType as ST
BL_SOCKET_DESCR_ANNOT_STRING = ":: "
BL_SOCKET_DESCR_ANNOT_STRING = ':: '
BL_SOCKET_DESCR_TYPE_MAP = {
("Time", "NodeSocketFloat", 1): ST.PhysicalTime,
("Angle", "NodeSocketFloat", 1): ST.PhysicalAngle,
("SolidAngle", "NodeSocketFloat", 1): ST.PhysicalSolidAngle,
("Rotation", "NodeSocketVector", 2): ST.PhysicalRot2D,
("Rotation", "NodeSocketVector", 3): ST.PhysicalRot3D,
("Freq", "NodeSocketFloat", 1): ST.PhysicalFreq,
("AngFreq", "NodeSocketFloat", 1): ST.PhysicalAngFreq,
('Time', 'NodeSocketFloat', 1): ST.PhysicalTime,
('Angle', 'NodeSocketFloat', 1): ST.PhysicalAngle,
('SolidAngle', 'NodeSocketFloat', 1): ST.PhysicalSolidAngle,
('Rotation', 'NodeSocketVector', 2): ST.PhysicalRot2D,
('Rotation', 'NodeSocketVector', 3): ST.PhysicalRot3D,
('Freq', 'NodeSocketFloat', 1): ST.PhysicalFreq,
('AngFreq', 'NodeSocketFloat', 1): ST.PhysicalAngFreq,
## Cartesian
("Length", "NodeSocketFloat", 1): ST.PhysicalLength,
("Area", "NodeSocketFloat", 1): ST.PhysicalArea,
("Volume", "NodeSocketFloat", 1): ST.PhysicalVolume,
("Disp", "NodeSocketVector", 2): ST.PhysicalDisp2D,
("Disp", "NodeSocketVector", 3): ST.PhysicalDisp3D,
("Point", "NodeSocketFloat", 1): ST.PhysicalPoint1D,
("Point", "NodeSocketVector", 2): ST.PhysicalPoint2D,
("Point", "NodeSocketVector", 3): ST.PhysicalPoint3D,
("Size", "NodeSocketVector", 2): ST.PhysicalSize2D,
("Size", "NodeSocketVector", 3): ST.PhysicalSize3D,
('Length', 'NodeSocketFloat', 1): ST.PhysicalLength,
('Area', 'NodeSocketFloat', 1): ST.PhysicalArea,
('Volume', 'NodeSocketFloat', 1): ST.PhysicalVolume,
('Disp', 'NodeSocketVector', 2): ST.PhysicalDisp2D,
('Disp', 'NodeSocketVector', 3): ST.PhysicalDisp3D,
('Point', 'NodeSocketFloat', 1): ST.PhysicalPoint1D,
('Point', 'NodeSocketVector', 2): ST.PhysicalPoint2D,
('Point', 'NodeSocketVector', 3): ST.PhysicalPoint3D,
('Size', 'NodeSocketVector', 2): ST.PhysicalSize2D,
('Size', 'NodeSocketVector', 3): ST.PhysicalSize3D,
## Mechanical
("Mass", "NodeSocketFloat", 1): ST.PhysicalMass,
("Speed", "NodeSocketFloat", 1): ST.PhysicalSpeed,
("Vel", "NodeSocketVector", 2): ST.PhysicalVel2D,
("Vel", "NodeSocketVector", 3): ST.PhysicalVel3D,
("Accel", "NodeSocketFloat", 1): ST.PhysicalAccelScalar,
("Accel", "NodeSocketVector", 2): ST.PhysicalAccel2D,
("Accel", "NodeSocketVector", 3): ST.PhysicalAccel3D,
("Force", "NodeSocketFloat", 1): ST.PhysicalForceScalar,
("Force", "NodeSocketVector", 2): ST.PhysicalForce2D,
("Force", "NodeSocketVector", 3): ST.PhysicalForce3D,
("Pressure", "NodeSocketFloat", 1): ST.PhysicalPressure,
('Mass', 'NodeSocketFloat', 1): ST.PhysicalMass,
('Speed', 'NodeSocketFloat', 1): ST.PhysicalSpeed,
('Vel', 'NodeSocketVector', 2): ST.PhysicalVel2D,
('Vel', 'NodeSocketVector', 3): ST.PhysicalVel3D,
('Accel', 'NodeSocketFloat', 1): ST.PhysicalAccelScalar,
('Accel', 'NodeSocketVector', 2): ST.PhysicalAccel2D,
('Accel', 'NodeSocketVector', 3): ST.PhysicalAccel3D,
('Force', 'NodeSocketFloat', 1): ST.PhysicalForceScalar,
('Force', 'NodeSocketVector', 2): ST.PhysicalForce2D,
('Force', 'NodeSocketVector', 3): ST.PhysicalForce3D,
('Pressure', 'NodeSocketFloat', 1): ST.PhysicalPressure,
## Energetic
("Energy", "NodeSocketFloat", 1): ST.PhysicalEnergy,
("Power", "NodeSocketFloat", 1): ST.PhysicalPower,
("Temp", "NodeSocketFloat", 1): ST.PhysicalTemp,
('Energy', 'NodeSocketFloat', 1): ST.PhysicalEnergy,
('Power', 'NodeSocketFloat', 1): ST.PhysicalPower,
('Temp', 'NodeSocketFloat', 1): ST.PhysicalTemp,
## ELectrodynamical
("Curr", "NodeSocketFloat", 1): ST.PhysicalCurr,
("CurrDens", "NodeSocketVector", 2): ST.PhysicalCurrDens2D,
("CurrDens", "NodeSocketVector", 3): ST.PhysicalCurrDens3D,
("Charge", "NodeSocketFloat", 1): ST.PhysicalCharge,
("Voltage", "NodeSocketFloat", 1): ST.PhysicalVoltage,
("Capacitance", "NodeSocketFloat", 1): ST.PhysicalCapacitance,
("Resistance", "NodeSocketFloat", 1): ST.PhysicalResistance,
("Conductance", "NodeSocketFloat", 1): ST.PhysicalConductance,
("MagFlux", "NodeSocketFloat", 1): ST.PhysicalMagFlux,
("MagFluxDens", "NodeSocketFloat", 1): ST.PhysicalMagFluxDens,
("Inductance", "NodeSocketFloat", 1): ST.PhysicalInductance,
("EField", "NodeSocketFloat", 2): ST.PhysicalEField3D,
("EField", "NodeSocketFloat", 3): ST.PhysicalEField2D,
("HField", "NodeSocketFloat", 2): ST.PhysicalHField3D,
("HField", "NodeSocketFloat", 3): ST.PhysicalHField2D,
('Curr', 'NodeSocketFloat', 1): ST.PhysicalCurr,
('CurrDens', 'NodeSocketVector', 2): ST.PhysicalCurrDens2D,
('CurrDens', 'NodeSocketVector', 3): ST.PhysicalCurrDens3D,
('Charge', 'NodeSocketFloat', 1): ST.PhysicalCharge,
('Voltage', 'NodeSocketFloat', 1): ST.PhysicalVoltage,
('Capacitance', 'NodeSocketFloat', 1): ST.PhysicalCapacitance,
('Resistance', 'NodeSocketFloat', 1): ST.PhysicalResistance,
('Conductance', 'NodeSocketFloat', 1): ST.PhysicalConductance,
('MagFlux', 'NodeSocketFloat', 1): ST.PhysicalMagFlux,
('MagFluxDens', 'NodeSocketFloat', 1): ST.PhysicalMagFluxDens,
('Inductance', 'NodeSocketFloat', 1): ST.PhysicalInductance,
('EField', 'NodeSocketFloat', 2): ST.PhysicalEField3D,
('EField', 'NodeSocketFloat', 3): ST.PhysicalEField2D,
('HField', 'NodeSocketFloat', 2): ST.PhysicalHField3D,
('HField', 'NodeSocketFloat', 3): ST.PhysicalHField2D,
## Luminal
("LumIntensity", "NodeSocketFloat", 1): ST.PhysicalLumIntensity,
("LumFlux", "NodeSocketFloat", 1): ST.PhysicalLumFlux,
("Illuminance", "NodeSocketFloat", 1): ST.PhysicalIlluminance,
('LumIntensity', 'NodeSocketFloat', 1): ST.PhysicalLumIntensity,
('LumFlux', 'NodeSocketFloat', 1): ST.PhysicalLumFlux,
('Illuminance', 'NodeSocketFloat', 1): ST.PhysicalIlluminance,
## Optical
("PolJones", "NodeSocketFloat", 2): ST.PhysicalPolJones,
("Pol", "NodeSocketFloat", 4): ST.PhysicalPol,
('PolJones', 'NodeSocketFloat', 2): ST.PhysicalPolJones,
('Pol', 'NodeSocketFloat', 4): ST.PhysicalPol,
}

View File

@ -1,35 +1,32 @@
from .socket_types import SocketType as ST
BL_SOCKET_DIRECT_TYPE_MAP = {
("NodeSocketString", 1): ST.String,
("NodeSocketBool", 1): ST.Bool,
("NodeSocketCollection", 1): ST.BlenderCollection,
("NodeSocketImage", 1): ST.BlenderImage,
("NodeSocketObject", 1): ST.BlenderObject,
("NodeSocketFloat", 1): ST.RealNumber,
('NodeSocketString', 1): ST.String,
('NodeSocketBool', 1): ST.Bool,
('NodeSocketCollection', 1): ST.BlenderCollection,
('NodeSocketImage', 1): ST.BlenderImage,
('NodeSocketObject', 1): ST.BlenderObject,
('NodeSocketFloat', 1): ST.RealNumber,
# ("NodeSocketFloatAngle", 1): ST.PhysicalAngle,
# ("NodeSocketFloatDistance", 1): ST.PhysicalLength,
("NodeSocketFloatFactor", 1): ST.RealNumber,
("NodeSocketFloatPercentage", 1): ST.RealNumber,
('NodeSocketFloatFactor', 1): ST.RealNumber,
('NodeSocketFloatPercentage', 1): ST.RealNumber,
# ("NodeSocketFloatTime", 1): ST.PhysicalTime,
# ("NodeSocketFloatTimeAbsolute", 1): ST.PhysicalTime,
("NodeSocketInt", 1): ST.IntegerNumber,
("NodeSocketIntFactor", 1): ST.IntegerNumber,
("NodeSocketIntPercentage", 1): ST.IntegerNumber,
("NodeSocketIntUnsigned", 1): ST.IntegerNumber,
("NodeSocketRotation", 2): ST.PhysicalRot2D,
("NodeSocketColor", 3): ST.Color,
("NodeSocketVector", 2): ST.Real2DVector,
("NodeSocketVector", 3): ST.Real3DVector,
('NodeSocketInt', 1): ST.IntegerNumber,
('NodeSocketIntFactor', 1): ST.IntegerNumber,
('NodeSocketIntPercentage', 1): ST.IntegerNumber,
('NodeSocketIntUnsigned', 1): ST.IntegerNumber,
('NodeSocketRotation', 2): ST.PhysicalRot2D,
('NodeSocketColor', 3): ST.Color,
('NodeSocketVector', 2): ST.Real2DVector,
('NodeSocketVector', 3): ST.Real3DVector,
# ("NodeSocketVectorAcceleration", 2): ST.PhysicalAccel2D,
# ("NodeSocketVectorAcceleration", 3): ST.PhysicalAccel3D,
# ("NodeSocketVectorDirection", 2): ST.Real2DVectorDir,
# ("NodeSocketVectorDirection", 3): ST.Real3DVectorDir,
("NodeSocketVectorEuler", 2): ST.PhysicalRot2D,
("NodeSocketVectorEuler", 3): ST.PhysicalRot3D,
('NodeSocketVectorEuler', 2): ST.PhysicalRot2D,
('NodeSocketVectorEuler', 3): ST.PhysicalRot3D,
# ("NodeSocketVectorTranslation", 3): ST.PhysicalDisp3D,
# ("NodeSocketVectorVelocity", 3): ST.PhysicalVel3D,
# ("NodeSocketVectorXYZ", 3): ST.PhysicalPoint3D,

View File

@ -2,67 +2,61 @@ from .socket_types import SocketType as ST
SOCKET_SHAPES = {
# Basic
ST.Any: "CIRCLE",
ST.Bool: "CIRCLE",
ST.String: "CIRCLE",
ST.FilePath: "CIRCLE",
ST.Any: 'CIRCLE',
ST.Bool: 'CIRCLE',
ST.String: 'CIRCLE',
ST.FilePath: 'CIRCLE',
# Number
ST.IntegerNumber: "CIRCLE",
ST.RationalNumber: "CIRCLE",
ST.RealNumber: "CIRCLE",
ST.ComplexNumber: "CIRCLE",
ST.IntegerNumber: 'CIRCLE',
ST.RationalNumber: 'CIRCLE',
ST.RealNumber: 'CIRCLE',
ST.ComplexNumber: 'CIRCLE',
# Vector
ST.Integer2DVector: "CIRCLE",
ST.Real2DVector: "CIRCLE",
ST.Complex2DVector: "CIRCLE",
ST.Integer3DVector: "CIRCLE",
ST.Real3DVector: "CIRCLE",
ST.Complex3DVector: "CIRCLE",
ST.Integer2DVector: 'CIRCLE',
ST.Real2DVector: 'CIRCLE',
ST.Complex2DVector: 'CIRCLE',
ST.Integer3DVector: 'CIRCLE',
ST.Real3DVector: 'CIRCLE',
ST.Complex3DVector: 'CIRCLE',
# Physical
ST.PhysicalUnitSystem: "CIRCLE",
ST.PhysicalTime: "CIRCLE",
ST.PhysicalAngle: "CIRCLE",
ST.PhysicalLength: "CIRCLE",
ST.PhysicalArea: "CIRCLE",
ST.PhysicalVolume: "CIRCLE",
ST.PhysicalPoint2D: "CIRCLE",
ST.PhysicalPoint3D: "CIRCLE",
ST.PhysicalSize2D: "CIRCLE",
ST.PhysicalSize3D: "CIRCLE",
ST.PhysicalMass: "CIRCLE",
ST.PhysicalSpeed: "CIRCLE",
ST.PhysicalAccelScalar: "CIRCLE",
ST.PhysicalForceScalar: "CIRCLE",
ST.PhysicalAccel3D: "CIRCLE",
ST.PhysicalForce3D: "CIRCLE",
ST.PhysicalPol: "CIRCLE",
ST.PhysicalFreq: "CIRCLE",
ST.PhysicalUnitSystem: 'CIRCLE',
ST.PhysicalTime: 'CIRCLE',
ST.PhysicalAngle: 'CIRCLE',
ST.PhysicalLength: 'CIRCLE',
ST.PhysicalArea: 'CIRCLE',
ST.PhysicalVolume: 'CIRCLE',
ST.PhysicalPoint2D: 'CIRCLE',
ST.PhysicalPoint3D: 'CIRCLE',
ST.PhysicalSize2D: 'CIRCLE',
ST.PhysicalSize3D: 'CIRCLE',
ST.PhysicalMass: 'CIRCLE',
ST.PhysicalSpeed: 'CIRCLE',
ST.PhysicalAccelScalar: 'CIRCLE',
ST.PhysicalForceScalar: 'CIRCLE',
ST.PhysicalAccel3D: 'CIRCLE',
ST.PhysicalForce3D: 'CIRCLE',
ST.PhysicalPol: 'CIRCLE',
ST.PhysicalFreq: 'CIRCLE',
# Blender
ST.BlenderObject: "DIAMOND",
ST.BlenderCollection: "DIAMOND",
ST.BlenderImage: "DIAMOND",
ST.BlenderGeoNodes: "DIAMOND",
ST.BlenderText: "DIAMOND",
ST.BlenderObject: 'DIAMOND',
ST.BlenderCollection: 'DIAMOND',
ST.BlenderImage: 'DIAMOND',
ST.BlenderGeoNodes: 'DIAMOND',
ST.BlenderText: 'DIAMOND',
# Maxwell
ST.MaxwellSource: "CIRCLE",
ST.MaxwellTemporalShape: "CIRCLE",
ST.MaxwellMedium: "CIRCLE",
ST.MaxwellMediumNonLinearity: "CIRCLE",
ST.MaxwellStructure: "CIRCLE",
ST.MaxwellBoundConds: "CIRCLE",
ST.MaxwellBoundCond: "CIRCLE",
ST.MaxwellMonitor: "CIRCLE",
ST.MaxwellFDTDSim: "CIRCLE",
ST.MaxwellFDTDSimData: "CIRCLE",
ST.MaxwellSimGrid: "CIRCLE",
ST.MaxwellSimGridAxis: "CIRCLE",
ST.MaxwellSimDomain: "CIRCLE",
ST.MaxwellSource: 'CIRCLE',
ST.MaxwellTemporalShape: 'CIRCLE',
ST.MaxwellMedium: 'CIRCLE',
ST.MaxwellMediumNonLinearity: 'CIRCLE',
ST.MaxwellStructure: 'CIRCLE',
ST.MaxwellBoundConds: 'CIRCLE',
ST.MaxwellBoundCond: 'CIRCLE',
ST.MaxwellMonitor: 'CIRCLE',
ST.MaxwellFDTDSim: 'CIRCLE',
ST.MaxwellFDTDSimData: 'CIRCLE',
ST.MaxwellSimGrid: 'CIRCLE',
ST.MaxwellSimGridAxis: 'CIRCLE',
ST.MaxwellSimDomain: 'CIRCLE',
# Tidy3D
ST.Tidy3DCloudTask: "DIAMOND",
ST.Tidy3DCloudTask: 'DIAMOND',
}

View File

@ -1,9 +1,12 @@
import enum
from ....utils.blender_type_enum import (
BlenderTypeEnum, append_cls_name_to_values, wrap_values_in_MT
BlenderTypeEnum,
append_cls_name_to_values,
wrap_values_in_MT,
)
@append_cls_name_to_values
class SocketType(BlenderTypeEnum):
# Base

View File

@ -5,262 +5,255 @@ from .socket_types import SocketType as ST
SOCKET_UNITS = {
ST.PhysicalTime: {
"default": "PS",
"values": {
"FS": spux.femtosecond,
"PS": spu.picosecond,
"NS": spu.nanosecond,
"MS": spu.microsecond,
"MLSEC": spu.millisecond,
"SEC": spu.second,
"MIN": spu.minute,
"HOUR": spu.hour,
"DAY": spu.day,
'default': 'PS',
'values': {
'FS': spux.femtosecond,
'PS': spu.picosecond,
'NS': spu.nanosecond,
'MS': spu.microsecond,
'MLSEC': spu.millisecond,
'SEC': spu.second,
'MIN': spu.minute,
'HOUR': spu.hour,
'DAY': spu.day,
},
},
ST.PhysicalAngle: {
"default": "RADIAN",
"values": {
"RADIAN": spu.radian,
"DEGREE": spu.degree,
"STERAD": spu.steradian,
"ANGMIL": spu.angular_mil,
'default': 'RADIAN',
'values': {
'RADIAN': spu.radian,
'DEGREE': spu.degree,
'STERAD': spu.steradian,
'ANGMIL': spu.angular_mil,
},
},
ST.PhysicalLength: {
"default": "UM",
"values": {
"PM": spu.picometer,
"A": spu.angstrom,
"NM": spu.nanometer,
"UM": spu.micrometer,
"MM": spu.millimeter,
"CM": spu.centimeter,
"M": spu.meter,
"INCH": spu.inch,
"FOOT": spu.foot,
"YARD": spu.yard,
"MILE": spu.mile,
'default': 'UM',
'values': {
'PM': spu.picometer,
'A': spu.angstrom,
'NM': spu.nanometer,
'UM': spu.micrometer,
'MM': spu.millimeter,
'CM': spu.centimeter,
'M': spu.meter,
'INCH': spu.inch,
'FOOT': spu.foot,
'YARD': spu.yard,
'MILE': spu.mile,
},
},
ST.PhysicalArea: {
"default": "UM_SQ",
"values": {
"PM_SQ": spu.picometer**2,
"A_SQ": spu.angstrom**2,
"NM_SQ": spu.nanometer**2,
"UM_SQ": spu.micrometer**2,
"MM_SQ": spu.millimeter**2,
"CM_SQ": spu.centimeter**2,
"M_SQ": spu.meter**2,
"INCH_SQ": spu.inch**2,
"FOOT_SQ": spu.foot**2,
"YARD_SQ": spu.yard**2,
"MILE_SQ": spu.mile**2,
'default': 'UM_SQ',
'values': {
'PM_SQ': spu.picometer**2,
'A_SQ': spu.angstrom**2,
'NM_SQ': spu.nanometer**2,
'UM_SQ': spu.micrometer**2,
'MM_SQ': spu.millimeter**2,
'CM_SQ': spu.centimeter**2,
'M_SQ': spu.meter**2,
'INCH_SQ': spu.inch**2,
'FOOT_SQ': spu.foot**2,
'YARD_SQ': spu.yard**2,
'MILE_SQ': spu.mile**2,
},
},
ST.PhysicalVolume: {
"default": "UM_CB",
"values": {
"PM_CB": spu.picometer**3,
"A_CB": spu.angstrom**3,
"NM_CB": spu.nanometer**3,
"UM_CB": spu.micrometer**3,
"MM_CB": spu.millimeter**3,
"CM_CB": spu.centimeter**3,
"M_CB": spu.meter**3,
"ML": spu.milliliter,
"L": spu.liter,
"INCH_CB": spu.inch**3,
"FOOT_CB": spu.foot**3,
"YARD_CB": spu.yard**3,
"MILE_CB": spu.mile**3,
'default': 'UM_CB',
'values': {
'PM_CB': spu.picometer**3,
'A_CB': spu.angstrom**3,
'NM_CB': spu.nanometer**3,
'UM_CB': spu.micrometer**3,
'MM_CB': spu.millimeter**3,
'CM_CB': spu.centimeter**3,
'M_CB': spu.meter**3,
'ML': spu.milliliter,
'L': spu.liter,
'INCH_CB': spu.inch**3,
'FOOT_CB': spu.foot**3,
'YARD_CB': spu.yard**3,
'MILE_CB': spu.mile**3,
},
},
ST.PhysicalPoint2D: {
"default": "UM",
"values": {
"PM": spu.picometer,
"A": spu.angstrom,
"NM": spu.nanometer,
"UM": spu.micrometer,
"MM": spu.millimeter,
"CM": spu.centimeter,
"M": spu.meter,
"INCH": spu.inch,
"FOOT": spu.foot,
"YARD": spu.yard,
"MILE": spu.mile,
'default': 'UM',
'values': {
'PM': spu.picometer,
'A': spu.angstrom,
'NM': spu.nanometer,
'UM': spu.micrometer,
'MM': spu.millimeter,
'CM': spu.centimeter,
'M': spu.meter,
'INCH': spu.inch,
'FOOT': spu.foot,
'YARD': spu.yard,
'MILE': spu.mile,
},
},
ST.PhysicalPoint3D: {
"default": "UM",
"values": {
"PM": spu.picometer,
"A": spu.angstrom,
"NM": spu.nanometer,
"UM": spu.micrometer,
"MM": spu.millimeter,
"CM": spu.centimeter,
"M": spu.meter,
"INCH": spu.inch,
"FOOT": spu.foot,
"YARD": spu.yard,
"MILE": spu.mile,
'default': 'UM',
'values': {
'PM': spu.picometer,
'A': spu.angstrom,
'NM': spu.nanometer,
'UM': spu.micrometer,
'MM': spu.millimeter,
'CM': spu.centimeter,
'M': spu.meter,
'INCH': spu.inch,
'FOOT': spu.foot,
'YARD': spu.yard,
'MILE': spu.mile,
},
},
ST.PhysicalSize2D: {
"default": "UM",
"values": {
"PM": spu.picometer,
"A": spu.angstrom,
"NM": spu.nanometer,
"UM": spu.micrometer,
"MM": spu.millimeter,
"CM": spu.centimeter,
"M": spu.meter,
"INCH": spu.inch,
"FOOT": spu.foot,
"YARD": spu.yard,
"MILE": spu.mile,
'default': 'UM',
'values': {
'PM': spu.picometer,
'A': spu.angstrom,
'NM': spu.nanometer,
'UM': spu.micrometer,
'MM': spu.millimeter,
'CM': spu.centimeter,
'M': spu.meter,
'INCH': spu.inch,
'FOOT': spu.foot,
'YARD': spu.yard,
'MILE': spu.mile,
},
},
ST.PhysicalSize3D: {
"default": "UM",
"values": {
"PM": spu.picometer,
"A": spu.angstrom,
"NM": spu.nanometer,
"UM": spu.micrometer,
"MM": spu.millimeter,
"CM": spu.centimeter,
"M": spu.meter,
"INCH": spu.inch,
"FOOT": spu.foot,
"YARD": spu.yard,
"MILE": spu.mile,
'default': 'UM',
'values': {
'PM': spu.picometer,
'A': spu.angstrom,
'NM': spu.nanometer,
'UM': spu.micrometer,
'MM': spu.millimeter,
'CM': spu.centimeter,
'M': spu.meter,
'INCH': spu.inch,
'FOOT': spu.foot,
'YARD': spu.yard,
'MILE': spu.mile,
},
},
ST.PhysicalMass: {
"default": "UG",
"values": {
"E_REST": spu.electron_rest_mass,
"DAL": spu.dalton,
"UG": spu.microgram,
"MG": spu.milligram,
"G": spu.gram,
"KG": spu.kilogram,
"TON": spu.metric_ton,
'default': 'UG',
'values': {
'E_REST': spu.electron_rest_mass,
'DAL': spu.dalton,
'UG': spu.microgram,
'MG': spu.milligram,
'G': spu.gram,
'KG': spu.kilogram,
'TON': spu.metric_ton,
},
},
ST.PhysicalSpeed: {
"default": "UM_S",
"values": {
"PM_S": spu.picometer / spu.second,
"NM_S": spu.nanometer / spu.second,
"UM_S": spu.micrometer / spu.second,
"MM_S": spu.millimeter / spu.second,
"M_S": spu.meter / spu.second,
"KM_S": spu.kilometer / spu.second,
"KM_H": spu.kilometer / spu.hour,
"FT_S": spu.feet / spu.second,
"MI_H": spu.mile / spu.hour,
'default': 'UM_S',
'values': {
'PM_S': spu.picometer / spu.second,
'NM_S': spu.nanometer / spu.second,
'UM_S': spu.micrometer / spu.second,
'MM_S': spu.millimeter / spu.second,
'M_S': spu.meter / spu.second,
'KM_S': spu.kilometer / spu.second,
'KM_H': spu.kilometer / spu.hour,
'FT_S': spu.feet / spu.second,
'MI_H': spu.mile / spu.hour,
},
},
ST.PhysicalAccelScalar: {
"default": "UM_S_SQ",
"values": {
"PM_S_SQ": spu.picometer / spu.second**2,
"NM_S_SQ": spu.nanometer / spu.second**2,
"UM_S_SQ": spu.micrometer / spu.second**2,
"MM_S_SQ": spu.millimeter / spu.second**2,
"M_S_SQ": spu.meter / spu.second**2,
"KM_S_SQ": spu.kilometer / spu.second**2,
"FT_S_SQ": spu.feet / spu.second**2,
'default': 'UM_S_SQ',
'values': {
'PM_S_SQ': spu.picometer / spu.second**2,
'NM_S_SQ': spu.nanometer / spu.second**2,
'UM_S_SQ': spu.micrometer / spu.second**2,
'MM_S_SQ': spu.millimeter / spu.second**2,
'M_S_SQ': spu.meter / spu.second**2,
'KM_S_SQ': spu.kilometer / spu.second**2,
'FT_S_SQ': spu.feet / spu.second**2,
},
},
ST.PhysicalForceScalar: {
"default": "UNEWT",
"values": {
"KG_M_S_SQ": spu.kg * spu.m/spu.second**2,
"NNEWT": spux.nanonewton,
"UNEWT": spux.micronewton,
"MNEWT": spux.millinewton,
"NEWT": spu.newton,
'default': 'UNEWT',
'values': {
'KG_M_S_SQ': spu.kg * spu.m / spu.second**2,
'NNEWT': spux.nanonewton,
'UNEWT': spux.micronewton,
'MNEWT': spux.millinewton,
'NEWT': spu.newton,
},
},
ST.PhysicalAccel3D: {
"default": "UM_S_SQ",
"values": {
"PM_S_SQ": spu.picometer / spu.second**2,
"NM_S_SQ": spu.nanometer / spu.second**2,
"UM_S_SQ": spu.micrometer / spu.second**2,
"MM_S_SQ": spu.millimeter / spu.second**2,
"M_S_SQ": spu.meter / spu.second**2,
"KM_S_SQ": spu.kilometer / spu.second**2,
"FT_S_SQ": spu.feet / spu.second**2,
'default': 'UM_S_SQ',
'values': {
'PM_S_SQ': spu.picometer / spu.second**2,
'NM_S_SQ': spu.nanometer / spu.second**2,
'UM_S_SQ': spu.micrometer / spu.second**2,
'MM_S_SQ': spu.millimeter / spu.second**2,
'M_S_SQ': spu.meter / spu.second**2,
'KM_S_SQ': spu.kilometer / spu.second**2,
'FT_S_SQ': spu.feet / spu.second**2,
},
},
ST.PhysicalForce3D: {
"default": "UNEWT",
"values": {
"KG_M_S_SQ": spu.kg * spu.m/spu.second**2,
"NNEWT": spux.nanonewton,
"UNEWT": spux.micronewton,
"MNEWT": spux.millinewton,
"NEWT": spu.newton,
'default': 'UNEWT',
'values': {
'KG_M_S_SQ': spu.kg * spu.m / spu.second**2,
'NNEWT': spux.nanonewton,
'UNEWT': spux.micronewton,
'MNEWT': spux.millinewton,
'NEWT': spu.newton,
},
},
ST.PhysicalFreq: {
"default": "THZ",
"values": {
"HZ": spu.hertz,
"KHZ": spux.kilohertz,
"MHZ": spux.megahertz,
"GHZ": spux.gigahertz,
"THZ": spux.terahertz,
"PHZ": spux.petahertz,
"EHZ": spux.exahertz,
'default': 'THZ',
'values': {
'HZ': spu.hertz,
'KHZ': spux.kilohertz,
'MHZ': spux.megahertz,
'GHZ': spux.gigahertz,
'THZ': spux.terahertz,
'PHZ': spux.petahertz,
'EHZ': spux.exahertz,
},
},
ST.PhysicalPol: {
"default": "RADIAN",
"values": {
"RADIAN": spu.radian,
"DEGREE": spu.degree,
"STERAD": spu.steradian,
"ANGMIL": spu.angular_mil,
'default': 'RADIAN',
'values': {
'RADIAN': spu.radian,
'DEGREE': spu.degree,
'STERAD': spu.steradian,
'ANGMIL': spu.angular_mil,
},
},
ST.MaxwellMedium: {
"default": "NM",
"values": {
"PM": spu.picometer, ## c(vac) = wl*freq
"A": spu.angstrom,
"NM": spu.nanometer,
"UM": spu.micrometer,
"MM": spu.millimeter,
"CM": spu.centimeter,
"M": spu.meter,
'default': 'NM',
'values': {
'PM': spu.picometer, ## c(vac) = wl*freq
'A': spu.angstrom,
'NM': spu.nanometer,
'UM': spu.micrometer,
'MM': spu.millimeter,
'CM': spu.centimeter,
'M': spu.meter,
},
},
ST.MaxwellMonitor: {
"default": "NM",
"values": {
"PM": spu.picometer, ## c(vac) = wl*freq
"A": spu.angstrom,
"NM": spu.nanometer,
"UM": spu.micrometer,
"MM": spu.millimeter,
"CM": spu.centimeter,
"M": spu.meter,
'default': 'NM',
'values': {
'PM': spu.picometer, ## c(vac) = wl*freq
'A': spu.angstrom,
'NM': spu.nanometer,
'UM': spu.micrometer,
'MM': spu.millimeter,
'CM': spu.centimeter,
'M': spu.meter,
},
},
}

View File

@ -1,9 +1,11 @@
import enum
from ....utils.blender_type_enum import (
BlenderTypeEnum, append_cls_name_to_values
BlenderTypeEnum,
append_cls_name_to_values,
)
@append_cls_name_to_values
class TreeType(BlenderTypeEnum):
MaxwellSim = enum.auto()

View File

@ -10,8 +10,9 @@ import bpy
from .. import contracts as ct
AREA_TYPE = "IMAGE_EDITOR"
SPACE_TYPE = "IMAGE_EDITOR"
AREA_TYPE = 'IMAGE_EDITOR'
SPACE_TYPE = 'IMAGE_EDITOR'
class ManagedBLImage(ct.schemas.ManagedObj):
managed_obj_type = ct.ManagedObjType.ManagedBLImage
@ -36,7 +37,7 @@ class ManagedBLImage(ct.schemas.ManagedObj):
# ...AND Desired Image Name is Taken
else:
msg = f"Desired name {value} for BL image is taken"
msg = f'Desired name {value} for BL image is taken'
raise ValueError(msg)
# Object DOES Exist
@ -59,24 +60,21 @@ class ManagedBLImage(ct.schemas.ManagedObj):
self,
width_px: int,
height_px: int,
color_model: typx.Literal["RGB", "RGBA"],
dtype: typx.Literal["uint8", "float32"],
color_model: typx.Literal['RGB', 'RGBA'],
dtype: typx.Literal['uint8', 'float32'],
):
"""Returns the managed blender image.
If the requested image properties are different from the image's, then delete the old image make a new image with correct geometry.
"""
channels = 4 if color_model == "RGBA" else 3
channels = 4 if color_model == 'RGBA' else 3
# Remove Image (if mismatch)
if (
(bl_image := bpy.data.images.get(self.name))
and (
if (bl_image := bpy.data.images.get(self.name)) and (
bl_image.size[0] != width_px
or bl_image.size[1] != height_px
or bl_image.channels != channels
or bl_image.is_float ^ (dtype == "float32")
)
or bl_image.is_float ^ (dtype == 'float32')
):
self.free()
@ -99,9 +97,7 @@ class ManagedBLImage(ct.schemas.ManagedObj):
If none are valid, return None.
"""
valid_areas = [
area
for area in bpy.context.screen.areas
if area.type == AREA_TYPE
area for area in bpy.context.screen.areas if area.type == AREA_TYPE
]
if valid_areas:
return valid_areas[0]
@ -111,7 +107,7 @@ class ManagedBLImage(ct.schemas.ManagedObj):
"""Returns the visible preview space in the visible preview area of
the Blender UI
"""
if (preview_area := self.preview_area):
if preview_area := self.preview_area:
return next(
space
for space in preview_area.spaces
@ -125,7 +121,7 @@ class ManagedBLImage(ct.schemas.ManagedObj):
"""Synchronizes the managed object to the preview, by manipulating
relevant editors.
"""
if (bl_image := bpy.data.images.get(self.name)):
if bl_image := bpy.data.images.get(self.name):
self.preview_space.image = bl_image
####################
@ -142,7 +138,7 @@ class ManagedBLImage(ct.schemas.ManagedObj):
import matplotlib.pyplot as plt
# Compute Image Geometry
if (preview_area := self.preview_area):
if preview_area := self.preview_area:
# Retrieve DPI from Blender Preferences
_dpi = bpy.context.preferences.system.dpi
@ -165,7 +161,7 @@ class ManagedBLImage(ct.schemas.ManagedObj):
height_px = int(_height_inches * _dpi)
else:
msg = f"There must either be a preview area, or defined `width_inches`, `height_inches`, and `dpi`"
msg = f'There must either be a preview area, or defined `width_inches`, `height_inches`, and `dpi`'
raise ValueError(msg)
# Compute Plot Dimensions
@ -196,10 +192,9 @@ class ManagedBLImage(ct.schemas.ManagedObj):
plt.close(fig)
# Optimized Write to Blender Image
bl_image = self.bl_image(cmp_width_px, cmp_height_px, "RGBA", "uint8")
bl_image = self.bl_image(cmp_width_px, cmp_height_px, 'RGBA', 'uint8')
bl_image.pixels.foreach_set(image_data.ravel())
bl_image.update()
if bl_select:
self.bl_select()

View File

@ -13,13 +13,14 @@ import bmesh
from .. import contracts as ct
ModifierType = typx.Literal["NODES", "ARRAY"]
ModifierType = typx.Literal['NODES', 'ARRAY']
MODIFIER_NAMES = {
"NODES": "BLMaxwell_GeoNodes",
"ARRAY": "BLMaxwell_Array",
'NODES': 'BLMaxwell_GeoNodes',
'ARRAY': 'BLMaxwell_Array',
}
MANAGED_COLLECTION_NAME = "BLMaxwell"
PREVIEW_COLLECTION_NAME = "BLMaxwell Visible"
MANAGED_COLLECTION_NAME = 'BLMaxwell'
PREVIEW_COLLECTION_NAME = 'BLMaxwell Visible'
def bl_collection(
collection_name: str, view_layer_exclude: bool
@ -33,13 +34,16 @@ def bl_collection(
collection = bpy.data.collections[collection_name]
## Ensure synced View Layer exclusion
if (layer_collection := bpy.context.view_layer.layer_collection.children[
if (
layer_collection := bpy.context.view_layer.layer_collection.children[
collection_name
]).exclude != view_layer_exclude:
]
).exclude != view_layer_exclude:
layer_collection.exclude = view_layer_exclude
return collection
class ManagedBLObject(ct.schemas.ManagedObj):
managed_obj_type = ct.ManagedObjType.ManagedBLObject
_bl_object_name: str
@ -63,7 +67,7 @@ class ManagedBLObject(ct.schemas.ManagedObj):
# ...AND Desired Object Name is Taken
else:
msg = f"Desired name {value} for BL object is taken"
msg = f'Desired name {value} for BL object is taken'
raise ValueError(msg)
# Object DOES Exist
@ -88,14 +92,14 @@ class ManagedBLObject(ct.schemas.ManagedObj):
# Delete the Underlying Datablock
## This automatically deletes the object too
if bl_object.type == "MESH":
if bl_object.type == 'MESH':
bpy.data.meshes.remove(bl_object.data)
elif bl_object.type == "EMPTY":
elif bl_object.type == 'EMPTY':
bpy.data.meshes.remove(bl_object.data)
elif bl_object.type == "VOLUME":
elif bl_object.type == 'VOLUME':
bpy.data.volumes.remove(bl_object.data)
else:
msg = f"Type of to-delete `bl_object`, {bl_object.type}, is not valid"
msg = f'Type of to-delete `bl_object`, {bl_object.type}, is not valid'
raise ValueError(msg)
####################
@ -103,37 +107,54 @@ class ManagedBLObject(ct.schemas.ManagedObj):
####################
def show_preview(
self,
kind: typx.Literal["MESH", "EMPTY", "VOLUME"],
kind: typx.Literal['MESH', 'EMPTY', 'VOLUME'],
empty_display_type: typx.Literal[
"PLAIN_AXES", "ARROWS", "SINGLE_ARROW", "CIRCLE", "CUBE",
"SPHERE", "CONE", "IMAGE",
] | None = None,
'PLAIN_AXES',
'ARROWS',
'SINGLE_ARROW',
'CIRCLE',
'CUBE',
'SPHERE',
'CONE',
'IMAGE',
]
| None = None,
) -> None:
"""Moves the managed Blender object to the preview collection.
If it's already included, do nothing.
"""
bl_object = self.bl_object(kind)
if bl_object.name not in (preview_collection := bl_collection(
if (
bl_object.name
not in (
preview_collection := bl_collection(
PREVIEW_COLLECTION_NAME, view_layer_exclude=False
)).objects:
)
).objects
):
preview_collection.objects.link(bl_object)
if kind == "EMPTY" and empty_display_type is not None:
if kind == 'EMPTY' and empty_display_type is not None:
bl_object.empty_display_type = empty_display_type
def hide_preview(
self,
kind: typx.Literal["MESH", "EMPTY", "VOLUME"],
kind: typx.Literal['MESH', 'EMPTY', 'VOLUME'],
) -> None:
"""Removes the managed Blender object from the preview collection.
If it's already removed, do nothing.
"""
bl_object = self.bl_object(kind)
if bl_object.name not in (preview_collection := bl_collection(
if (
bl_object.name
not in (
preview_collection := bl_collection(
PREVIEW_COLLECTION_NAME, view_layer_exclude=False
)).objects:
)
).objects
):
preview_collection.objects.unlink(bl_object)
def bl_select(self) -> None:
@ -141,7 +162,7 @@ class ManagedBLObject(ct.schemas.ManagedObj):
outlined in the 3D viewport.
"""
if not (bl_object := bpy.data.objects.get(self.name)):
msg = "Managed BLObject does not exist"
msg = 'Managed BLObject does not exist'
raise ValueError(msg)
bpy.ops.object.select_all(action='DESELECT')
@ -152,7 +173,7 @@ class ManagedBLObject(ct.schemas.ManagedObj):
####################
def bl_object(
self,
kind: typx.Literal["MESH", "EMPTY", "VOLUME"],
kind: typx.Literal['MESH', 'EMPTY', 'VOLUME'],
):
"""Returns the managed blender object.
@ -161,21 +182,22 @@ class ManagedBLObject(ct.schemas.ManagedObj):
"""
# Remove Object (if mismatch)
if (
(bl_object := bpy.data.objects.get(self.name))
and bl_object.type != kind
):
bl_object := bpy.data.objects.get(self.name)
) and bl_object.type != kind:
self.free()
# Create Object w/Appropriate Data Block
if not (bl_object := bpy.data.objects.get(self.name)):
if kind == "MESH":
if kind == 'MESH':
bl_data = bpy.data.meshes.new(self.bl_mesh_name)
elif kind == "EMPTY":
elif kind == 'EMPTY':
bl_data = None
elif kind == "VOLUME":
elif kind == 'VOLUME':
raise NotImplementedError
else:
msg = f"Requested `bl_object` type {bl_object.type} is not valid"
msg = (
f'Requested `bl_object` type {bl_object.type} is not valid'
)
raise ValueError(msg)
bl_object = bpy.data.objects.new(self.name, bl_data)
@ -195,12 +217,11 @@ class ManagedBLObject(ct.schemas.ManagedObj):
Raises an error if the object has no mesh data.
"""
if (
(bl_object := bpy.data.objects.get(self.name))
and bl_object.type == "MESH"
):
bl_object := bpy.data.objects.get(self.name)
) and bl_object.type == 'MESH':
return bl_object.data
msg = f"Requested MESH data from `bl_object` of type {bl_object.type}"
msg = f'Requested MESH data from `bl_object` of type {bl_object.type}'
raise ValueError(msg)
@contextlib.contextmanager
@ -210,9 +231,8 @@ class ManagedBLObject(ct.schemas.ManagedObj):
triangulate: bool = False,
) -> bpy.types.Mesh:
if (
(bl_object := bpy.data.objects.get(self.name))
and bl_object.type == "MESH"
):
bl_object := bpy.data.objects.get(self.name)
) and bl_object.type == 'MESH':
bmesh_mesh = None
try:
bmesh_mesh = bmesh.new()
@ -230,10 +250,11 @@ class ManagedBLObject(ct.schemas.ManagedObj):
yield bmesh_mesh
finally:
if bmesh_mesh: bmesh_mesh.free()
if bmesh_mesh:
bmesh_mesh.free()
else:
msg = f"Requested BMesh from `bl_object` of type {bl_object.type}"
msg = f'Requested BMesh from `bl_object` of type {bl_object.type}'
raise ValueError(msg)
@property
@ -245,7 +266,7 @@ class ManagedBLObject(ct.schemas.ManagedObj):
## TODO: Must we?
# Compute Evaluted + Triangulated Mesh
_mesh = bpy.data.meshes.new(name="TemporaryMesh")
_mesh = bpy.data.meshes.new(name='TemporaryMesh')
with self.mesh_as_bmesh(evaluate=True, triangulate=True) as bmesh_mesh:
bmesh_mesh.to_mesh(_mesh)
@ -265,8 +286,8 @@ class ManagedBLObject(ct.schemas.ManagedObj):
bpy.data.meshes.remove(_mesh)
return {
"verts": verts,
"faces": faces,
'verts': verts,
'faces': faces,
}
####################
@ -299,11 +320,11 @@ class ManagedBLObject(ct.schemas.ManagedObj):
"""
bl_modifier = self.bl_modifier(modifier_type)
if modifier_type == "NODES":
if modifier_type == 'NODES':
return {
"node_group": bl_modifier.node_group,
'node_group': bl_modifier.node_group,
}
elif modifier_type == "ARRAY":
elif modifier_type == 'ARRAY':
raise NotImplementedError
def s_modifier_attrs(
@ -313,10 +334,10 @@ class ManagedBLObject(ct.schemas.ManagedObj):
):
bl_modifier = self.bl_modifier(modifier_type)
if modifier_type == "NODES":
if bl_modifier.node_group != modifier_attrs["node_group"]:
bl_modifier.node_group = modifier_attrs["node_group"]
elif modifier_type == "ARRAY":
if modifier_type == 'NODES':
if bl_modifier.node_group != modifier_attrs['node_group']:
bl_modifier.node_group = modifier_attrs['node_group']
elif modifier_type == 'ARRAY':
raise NotImplementedError
####################
@ -337,27 +358,27 @@ class ManagedBLObject(ct.schemas.ManagedObj):
If the GeoNodes node group doesn't match, it is changed.
Only differing interface values are actually changed.
"""
bl_object = self.bl_object("MESH")
bl_object = self.bl_object('MESH')
# Get (/make) a GeoModes Modifier
bl_modifier = self.bl_modifier("NODES")
bl_modifier = self.bl_modifier('NODES')
# Set GeoNodes Modifier Attributes (specifically, the 'node_group')
self.s_modifier_attrs("NODES", {"node_group": geonodes_node_group})
self.s_modifier_attrs('NODES', {'node_group': geonodes_node_group})
# Set GeoNodes Values
modifier_altered = False
for interface_identifier, value in (
geonodes_identifier_to_value.items()
):
for (
interface_identifier,
value,
) in geonodes_identifier_to_value.items():
if bl_modifier[interface_identifier] != value:
# Quickly Determine if IDPropertyArray is Equal
if hasattr(
bl_modifier[interface_identifier],
"to_list"
) and tuple(
bl_modifier[interface_identifier].to_list()
) == value:
if (
hasattr(bl_modifier[interface_identifier], 'to_list')
and tuple(bl_modifier[interface_identifier].to_list())
== value
):
continue
# Quickly Determine int/float Mismatch
@ -375,7 +396,6 @@ class ManagedBLObject(ct.schemas.ManagedObj):
if modifier_altered:
bl_object.data.update()
# @property
# def volume(self) -> bpy.types.Volume:
# """Returns the object's volume data.

View File

@ -9,10 +9,12 @@ from . import contracts as ct
####################
MemAddr = int
class DeltaNodeLinkCache(typ.TypedDict):
added: set[MemAddr]
removed: set[MemAddr]
class NodeLinkCache:
def __init__(self, node_tree: bpy.types.NodeTree):
# Initialize Parameters
@ -51,14 +53,15 @@ class NodeLinkCache:
self.link_ptrs_from_sockets[link_ptr] = link.from_socket
self.link_ptrs_to_sockets[link_ptr] = link.to_socket
return {"added": added_link_ptrs, "removed": removed_link_ptrs}
return {'added': added_link_ptrs, 'removed': removed_link_ptrs}
####################
# - Node Tree Definition
####################
class MaxwellSimTree(bpy.types.NodeTree):
bl_idname = ct.TreeType.MaxwellSim.value
bl_label = "Maxwell Sim Editor"
bl_label = 'Maxwell Sim Editor'
bl_icon = ct.Icon.SimNodeEditor.value
####################
@ -79,9 +82,11 @@ class MaxwellSimTree(bpy.types.NodeTree):
It's a bit of a "fake" function - in practicality, it's triggered on the first update() function.
"""
## TODO: Consider tying this to an "on_load" handler
if hasattr(self, '_node_link_cache'):
self._node_link_cache.regenerate()
else:
self._node_link_cache = NodeLinkCache(self)
####################
# - Update Methods
####################
@ -92,10 +97,9 @@ class MaxwellSimTree(bpy.types.NodeTree):
"""
for bl_socket in node.inputs.values():
# Retrieve Socket Links (if any)
self._node_link_cache.remove({
link.as_pointer()
for link in bl_socket.links
})
self._node_link_cache.remove(
{link.as_pointer() for link in bl_socket.links}
)
## ONLY Input Socket Links are Removed from the NodeLink Cache
## - update() handles link-removal from still-existing node just fine.
## - update() does NOT handle link-removal of non-existant nodes.
@ -105,23 +109,25 @@ class MaxwellSimTree(bpy.types.NodeTree):
Updates an internal node link cache, then updates sockets that just lost/gained an input link.
"""
if not hasattr(self, "_node_link_cache"):
if not hasattr(self, '_node_link_cache'):
self.on_load()
## We presume update() is run before the first link is altered.
## - Else, the first link of the session will not update caches.
## - We remain slightly unsure of the semantics.
## - More testing needed to prevent this 'first-link bug'.
## - Therefore, self.on_load() is also called as a load_post handler.
return
# Compute Changes to NodeLink Cache
delta_links = self._node_link_cache.regenerate()
link_alterations = {
"to_remove": [],
"to_add": [],
'to_remove': [],
'to_add': [],
}
for link_ptr in delta_links["removed"]:
from_socket = self._node_link_cache.link_ptrs_from_sockets[link_ptr]
for link_ptr in delta_links['removed']:
from_socket = self._node_link_cache.link_ptrs_from_sockets[
link_ptr
]
to_socket = self._node_link_cache.link_ptrs_to_sockets[link_ptr]
# Update Socket Caches
@ -134,51 +140,49 @@ class MaxwellSimTree(bpy.types.NodeTree):
consent_removal := to_socket.sync_link_removed(from_socket)
):
# Did Not Consent to Removal: Queue Add Link
link_alterations["to_add"].append((from_socket, to_socket))
link_alterations['to_add'].append((from_socket, to_socket))
for link_ptr in delta_links["added"]:
for link_ptr in delta_links['added']:
link = self._node_link_cache.link_ptrs_to_links.get(link_ptr)
if link is None: continue
if link is None:
continue
# Trigger Report Chain on Socket that Just Gained a Link
## Aka. Forward-Refresh Caches Relying on Linkage
if not (
consent_added := link.to_socket.sync_link_added(link)
):
if not (consent_added := link.to_socket.sync_link_added(link)):
# Did Not Consent to Addition: Queue Remove Link
link_alterations["to_remove"].append(link)
link_alterations['to_remove'].append(link)
# Execute Queued Operations
## - Especially undoing undesirable link changes.
## - This is important for locked graphs, whose links must not change.
for link in link_alterations["to_remove"]:
for link in link_alterations['to_remove']:
self.links.remove(link)
for from_socket, to_socket in link_alterations["to_add"]:
for from_socket, to_socket in link_alterations['to_add']:
self.links.new(from_socket, to_socket)
# If Queued Operations: Regenerate Cache
## - This prevents the next update() from picking up on alterations.
if link_alterations["to_remove"] or link_alterations["to_add"]:
if link_alterations['to_remove'] or link_alterations['to_add']:
self._node_link_cache.regenerate()
####################
# - Post-Load Handler
####################
def initialize_sim_tree_node_link_cache(scene: bpy.types.Scene):
"""Whenever a file is loaded, create/regenerate the NodeLinkCache in all trees.
"""
def initialize_sim_tree_node_link_cache(_: bpy.types.Scene):
"""Whenever a file is loaded, create/regenerate the NodeLinkCache in all trees."""
for node_tree in bpy.data.node_groups:
if node_tree.bl_idname == "MaxwellSimTree":
if not hasattr(node_tree, "_node_link_cache"):
node_tree._node_link_cache = NodeLinkCache(node_tree)
else:
node_tree._node_link_cache.regenerate()
if node_tree.bl_idname == 'MaxwellSimTree':
node_tree.on_load()
####################
# - Blender Registration
####################
bpy.app.handlers.load_post.append(initialize_sim_tree_node_link_cache)
## TODO: Move to top-level registration.
BL_REGISTER = [
MaxwellSimTree,

View File

@ -5,6 +5,7 @@ from . import outputs
from . import sources
from . import mediums
from . import structures
# from . import bounds
from . import monitors
from . import simulations

View File

@ -1,12 +1,11 @@
import uuid
import typing as typ
import typing_extensions as typx
import json
import inspect
import json
import typing as typ
import uuid
import bpy
import pydantic as pyd
import typing_extensions as typx
from .. import contracts as ct
from .. import sockets
@ -14,11 +13,14 @@ from .. import sockets
CACHE: dict[str, typ.Any] = {} ## By Instance UUID
## NOTE: CACHE does not persist between file loads.
_DEFAULT_LOOSE_SOCKET_SER = json.dumps({
"socket_names": [],
"socket_def_names": [],
"models": [],
})
_DEFAULT_LOOSE_SOCKET_SER = json.dumps(
{
'socket_names': [],
'socket_def_names': [],
'models': [],
}
)
class MaxwellSimNode(bpy.types.Node):
# Fundamentals
@ -29,7 +31,7 @@ class MaxwellSimNode(bpy.types.Node):
# draw_label(self) -> str: pass
# Style
bl_description: str = ""
bl_description: str = ''
# bl_width_default: float = 0.0
# bl_width_min: float = 0.0
@ -56,35 +58,35 @@ class MaxwellSimNode(bpy.types.Node):
super().__init_subclass__(**kwargs)
# Setup Blender ID for Node
if not hasattr(cls, "node_type"):
if not hasattr(cls, 'node_type'):
msg = f"Node class {cls} does not define 'node_type', or it is does not have the type {ct.NodeType}"
raise ValueError(msg)
cls.bl_idname = str(cls.node_type.value)
# Setup Instance ID for Node
cls.__annotations__["instance_id"] = bpy.props.StringProperty(
name="Instance ID",
description="The instance ID of a particular MaxwellSimNode instance, used to index caches",
default="",
cls.__annotations__['instance_id'] = bpy.props.StringProperty(
name='Instance ID',
description='The instance ID of a particular MaxwellSimNode instance, used to index caches',
default='',
)
# Setup Name Property for Node
cls.__annotations__["sim_node_name"] = bpy.props.StringProperty(
name="Sim Node Name",
description="The name of a particular MaxwellSimNode node, which can be used to help identify data managed by the node",
default="",
update=(lambda self, context: self.sync_sim_node_name(context))
cls.__annotations__['sim_node_name'] = bpy.props.StringProperty(
name='Sim Node Name',
description='The name of a particular MaxwellSimNode node, which can be used to help identify data managed by the node',
default='',
update=(lambda self, context: self.sync_sim_node_name(context)),
)
# Setup Locked Property for Node
cls.__annotations__["locked"] = bpy.props.BoolProperty(
name="Locked State",
cls.__annotations__['locked'] = bpy.props.BoolProperty(
name='Locked State',
description="The lock-state of a particular MaxwellSimNode instance, which determines the node's user editability",
default=False,
)
# Setup Blender Label for Node
if not hasattr(cls, "bl_label"):
if not hasattr(cls, 'bl_label'):
msg = f"Node class {cls} does not define 'bl_label'"
raise ValueError(msg)
@ -92,42 +94,32 @@ class MaxwellSimNode(bpy.types.Node):
cls._output_socket_methods = {
method._index_by: method
for attr_name in dir(cls)
if hasattr(
method := getattr(cls, attr_name),
"_callback_type"
) and method._callback_type == "computes_output_socket"
if hasattr(method := getattr(cls, attr_name), '_callback_type')
and method._callback_type == 'computes_output_socket'
}
cls._on_value_changed_methods = {
method
for attr_name in dir(cls)
if hasattr(
method := getattr(cls, attr_name),
"_callback_type"
) and method._callback_type == "on_value_changed"
if hasattr(method := getattr(cls, attr_name), '_callback_type')
and method._callback_type == 'on_value_changed'
}
cls._on_show_preview = {
method
for attr_name in dir(cls)
if hasattr(
method := getattr(cls, attr_name),
"_callback_type"
) and method._callback_type == "on_show_preview"
if hasattr(method := getattr(cls, attr_name), '_callback_type')
and method._callback_type == 'on_show_preview'
}
cls._on_show_plot = {
method
for attr_name in dir(cls)
if hasattr(
method := getattr(cls, attr_name),
"_callback_type"
) and method._callback_type == "on_show_plot"
if hasattr(method := getattr(cls, attr_name), '_callback_type')
and method._callback_type == 'on_show_plot'
}
cls._on_init = {
method
for attr_name in dir(cls)
if hasattr(
method := getattr(cls, attr_name),
"_callback_type"
) and method._callback_type == "on_init"
if hasattr(method := getattr(cls, attr_name), '_callback_type')
and method._callback_type == 'on_init'
}
# Setup Socket Set Dropdown
@ -136,23 +128,22 @@ class MaxwellSimNode(bpy.types.Node):
else:
## Add Active Socket Set Enum
socket_set_names = (
(_input_socket_set_names := list(cls.input_socket_sets.keys()))
+ [
_input_socket_set_names := list(cls.input_socket_sets.keys())
) + [
output_socket_set_name
for output_socket_set_name in cls.output_socket_sets.keys()
if output_socket_set_name not in _input_socket_set_names
]
)
socket_set_ids = [
socket_set_name.replace(" ", "_").upper()
socket_set_name.replace(' ', '_').upper()
for socket_set_name in socket_set_names
]
## TODO: Better deriv. of sock.set. ID, ex. ( is currently invalid.
## Add Active Socket Set Enum
cls.__annotations__["active_socket_set"] = bpy.props.EnumProperty(
name="Active Socket Set",
description="The active socket set",
cls.__annotations__['active_socket_set'] = bpy.props.EnumProperty(
name='Active Socket Set',
description='The active socket set',
items=[
(
socket_set_name,
@ -165,7 +156,9 @@ class MaxwellSimNode(bpy.types.Node):
)
],
default=socket_set_names[0],
update=lambda self, context: self.sync_active_socket_set(context),
update=lambda self, context: self.sync_active_socket_set(
context
),
)
# Setup Preset Dropdown
@ -173,9 +166,9 @@ class MaxwellSimNode(bpy.types.Node):
cls.active_preset = None
else:
## TODO: Check that presets are represented in a socket that is guaranteed to be always available, specifically either a static socket or ALL static socket sets.
cls.__annotations__["active_preset"] = bpy.props.EnumProperty(
name="Active Preset",
description="The active preset",
cls.__annotations__['active_preset'] = bpy.props.EnumProperty(
name='Active Preset',
description='The active preset',
items=[
(
preset_name,
@ -185,9 +178,7 @@ class MaxwellSimNode(bpy.types.Node):
for preset_name, preset_def in cls.presets.items()
],
default=list(cls.presets.keys())[0],
update=lambda self, context: (
self.sync_active_preset()()
),
update=lambda self, context: (self.sync_active_preset()()),
)
####################
@ -195,10 +186,10 @@ class MaxwellSimNode(bpy.types.Node):
####################
def sync_active_socket_set(self, context):
self.sync_sockets()
self.sync_prop("active_socket_set", context)
self.sync_prop('active_socket_set', context)
def sync_sim_node_name(self, context):
if (mobjs := CACHE[self.instance_id].get("managed_objs")) is None:
if (mobjs := CACHE[self.instance_id].get('managed_objs')) is None:
return
for mobj_id, mobj in mobjs.items():
@ -227,61 +218,60 @@ class MaxwellSimNode(bpy.types.Node):
## - We sync our 'sim_node_name' with all managed objects.
## - (There is also a class-defined 'name_prefix' to differentiate)
## - See the 'sim_node_name' w/its sync function.
if CACHE[self.instance_id].get("managed_objs") is None:
if CACHE[self.instance_id].get('managed_objs') is None:
# Initialize the Managed Object Instance Cache
CACHE[self.instance_id]["managed_objs"] = {}
CACHE[self.instance_id]['managed_objs'] = {}
# Fill w/Managed Objects by Name Socket
for mobj_id, mobj_def in self.managed_obj_defs.items():
name = mobj_def.name_prefix + self.sim_node_name
CACHE[self.instance_id]["managed_objs"][mobj_id] = (
mobj_def.mk(name)
CACHE[self.instance_id]['managed_objs'][mobj_id] = mobj_def.mk(
name
)
return CACHE[self.instance_id]["managed_objs"]
return CACHE[self.instance_id]['managed_objs']
return CACHE[self.instance_id]["managed_objs"]
return CACHE[self.instance_id]['managed_objs']
####################
# - Socket Properties
####################
def active_bl_sockets(self, direc: typx.Literal["input", "output"]):
return self.inputs if direc == "input" else self.outputs
def active_bl_sockets(self, direc: typx.Literal['input', 'output']):
return self.inputs if direc == 'input' else self.outputs
def active_socket_set_sockets(
self,
direc: typx.Literal["input", "output"],
direc: typx.Literal['input', 'output'],
) -> dict:
# No Active Socket Set: Return Nothing
if not self.active_socket_set: return {}
if not self.active_socket_set:
return {}
# Retrieve Active Socket Set Sockets
socket_sets = (
self.input_socket_sets
if direc == "input" else self.output_socket_sets
)
active_socket_set_sockets = socket_sets.get(
self.active_socket_set
if direc == 'input'
else self.output_socket_sets
)
active_socket_set_sockets = socket_sets.get(self.active_socket_set)
# Return Active Socket Set Sockets (if any)
if not active_socket_set_sockets: return {}
if not active_socket_set_sockets:
return {}
return active_socket_set_sockets
def active_sockets(self, direc: typx.Literal["input", "output"]):
def active_sockets(self, direc: typx.Literal['input', 'output']):
static_sockets = (
self.input_sockets
if direc == "input"
else self.output_sockets
self.input_sockets if direc == 'input' else self.output_sockets
)
socket_sets = (
self.input_socket_sets
if direc == "input"
if direc == 'input'
else self.output_socket_sets
)
loose_sockets = (
self.loose_input_sockets
if direc == "input"
if direc == 'input'
else self.loose_output_sockets
)
@ -297,42 +287,50 @@ class MaxwellSimNode(bpy.types.Node):
# Loose Sockets
## Only Blender props persist as instance data
ser_loose_input_sockets: bpy.props.StringProperty(
name="Serialized Loose Input Sockets",
description="JSON-serialized representation of loose input sockets.",
name='Serialized Loose Input Sockets',
description='JSON-serialized representation of loose input sockets.',
default=_DEFAULT_LOOSE_SOCKET_SER,
)
ser_loose_output_sockets: bpy.props.StringProperty(
name="Serialized Loose Input Sockets",
description="JSON-serialized representation of loose input sockets.",
name='Serialized Loose Input Sockets',
description='JSON-serialized representation of loose input sockets.',
default=_DEFAULT_LOOSE_SOCKET_SER,
)
## Internal Serialization/Deserialization Methods (yuck)
def _ser_loose_sockets(self, deser: dict[str, ct.schemas.SocketDef]) -> str:
if not all(isinstance(model, pyd.BaseModel) for model in deser.values()):
msg = "Trying to deserialize loose sockets with invalid SocketDefs (they must be `pydantic` BaseModels)."
def _ser_loose_sockets(
self, deser: dict[str, ct.schemas.SocketDef]
) -> str:
if not all(
isinstance(model, pyd.BaseModel) for model in deser.values()
):
msg = 'Trying to deserialize loose sockets with invalid SocketDefs (they must be `pydantic` BaseModels).'
raise ValueError(msg)
return json.dumps({
"socket_names": list(deser.keys()),
"socket_def_names": [
model.__class__.__name__
for model in deser.values()
return json.dumps(
{
'socket_names': list(deser.keys()),
'socket_def_names': [
model.__class__.__name__ for model in deser.values()
],
"models": [
'models': [
model.model_dump()
for model in deser.values()
if isinstance(model, pyd.BaseModel)
],
}) ## Big reliance on order-preservation of dicts here.)
def _deser_loose_sockets(self, ser: str) -> dict[str, ct.schemas.SocketDef]:
}
) ## Big reliance on order-preservation of dicts here.)
def _deser_loose_sockets(
self, ser: str
) -> dict[str, ct.schemas.SocketDef]:
semi_deser = json.loads(ser)
return {
socket_name: getattr(sockets, socket_def_name)(**model_kwargs)
for socket_name, socket_def_name, model_kwargs in zip(
semi_deser["socket_names"],
semi_deser["socket_def_names"],
semi_deser["models"],
semi_deser['socket_names'],
semi_deser['socket_def_names'],
semi_deser['models'],
)
if hasattr(sockets, socket_def_name)
}
@ -340,17 +338,22 @@ class MaxwellSimNode(bpy.types.Node):
@property
def loose_input_sockets(self) -> dict[str, ct.schemas.SocketDef]:
return self._deser_loose_sockets(self.ser_loose_input_sockets)
@property
def loose_output_sockets(self) -> dict[str, ct.schemas.SocketDef]:
return self._deser_loose_sockets(self.ser_loose_output_sockets)
## TODO: Some caching may play a role if this is all too slow.
@loose_input_sockets.setter
def loose_input_sockets(
self, value: dict[str, ct.schemas.SocketDef],
self,
value: dict[str, ct.schemas.SocketDef],
) -> None:
if not value: self.ser_loose_input_sockets = _DEFAULT_LOOSE_SOCKET_SER
else: self.ser_loose_input_sockets = self._ser_loose_sockets(value)
if not value:
self.ser_loose_input_sockets = _DEFAULT_LOOSE_SOCKET_SER
else:
self.ser_loose_input_sockets = self._ser_loose_sockets(value)
# Synchronize Sockets
self.sync_sockets()
@ -358,10 +361,13 @@ class MaxwellSimNode(bpy.types.Node):
@loose_output_sockets.setter
def loose_output_sockets(
self, value: dict[str, ct.schemas.SocketDef],
self,
value: dict[str, ct.schemas.SocketDef],
) -> None:
if not value: self.ser_loose_output_sockets = _DEFAULT_LOOSE_SOCKET_SER
else: self.ser_loose_output_sockets = self._ser_loose_sockets(value)
if not value:
self.ser_loose_output_sockets = _DEFAULT_LOOSE_SOCKET_SER
else:
self.ser_loose_output_sockets = self._ser_loose_sockets(value)
# Synchronize Sockets
self.sync_sockets()
@ -375,7 +381,7 @@ class MaxwellSimNode(bpy.types.Node):
**NOTE**: Socket names must be unique within direction, active socket set, and loose socket set.
"""
for direc in ["input", "output"]:
for direc in ['input', 'output']:
sockets = self.active_sockets(direc)
bl_sockets = self.active_bl_sockets(direc)
@ -395,7 +401,7 @@ class MaxwellSimNode(bpy.types.Node):
Existing sockets within the given direction are not re-created.
"""
for direc in ["input", "output"]:
for direc in ['input', 'output']:
sockets = self.active_sockets(direc)
bl_sockets = self.active_bl_sockets(direc)
@ -403,7 +409,8 @@ class MaxwellSimNode(bpy.types.Node):
created_sockets = {}
for socket_name, socket_def in sockets.items():
# Skip Existing Sockets
if socket_name in bl_sockets: continue
if socket_name in bl_sockets:
continue
# Create BL Socket from Socket
bl_socket = bl_sockets.new(
@ -441,12 +448,12 @@ class MaxwellSimNode(bpy.types.Node):
preset-defined input sockets.
"""
if not (preset_def := self.presets.get(self.active_preset)):
msg = f"Tried to apply active preset, but the active preset ({self.active_preset}) is not in presets ({self.presets})"
msg = f'Tried to apply active preset, but the active preset ({self.active_preset}) is not in presets ({self.presets})'
raise RuntimeError(msg)
for socket_name, socket_value in preset_def.values.items():
if not (bl_socket := self.inputs.get(socket_name)):
msg = f"Tried to set preset socket/value pair ({socket_name}={socket_value}), but socket is not in active input sockets ({self.inputs})"
msg = f'Tried to set preset socket/value pair ({socket_name}={socket_value}), but socket is not in active input sockets ({self.inputs})'
raise ValueError(msg)
bl_socket.value = socket_value
@ -460,20 +467,21 @@ class MaxwellSimNode(bpy.types.Node):
context: bpy.types.Context,
layout: bpy.types.UILayout,
) -> None:
if self.locked: layout.enabled = False
if self.locked:
layout.enabled = False
if self.active_preset:
layout.prop(self, "active_preset", text="")
layout.prop(self, 'active_preset', text='')
if self.active_socket_set:
layout.prop(self, "active_socket_set", text="")
layout.prop(self, 'active_socket_set', text='')
# Draw Name
col = layout.column(align=False)
if self.use_sim_node_name:
row = col.row(align=True)
row.label(text="", icon="FILE_TEXT")
row.prop(self, "sim_node_name", text="")
row.label(text='', icon='FILE_TEXT')
row.prop(self, 'sim_node_name', text='')
# Draw Name
self.draw_props(context, col)
@ -481,15 +489,23 @@ class MaxwellSimNode(bpy.types.Node):
self.draw_info(context, col)
## TODO: Managed Operators instead of this shit
def draw_props(self, context, layout): pass
def draw_operators(self, context, layout): pass
def draw_info(self, context, layout): pass
def draw_props(self, context, layout):
pass
def draw_operators(self, context, layout):
pass
def draw_info(self, context, layout):
pass
def draw_buttons_ext(self, context, layout):
pass
def draw_buttons_ext(self, context, layout): pass
## TODO: Side panel buttons for fanciness.
def draw_plot_settings(self, context, layout):
if self.locked: layout.enabled = False
if self.locked:
layout.enabled = False
####################
# - Data Flow
@ -536,7 +552,7 @@ class MaxwellSimNode(bpy.types.Node):
(output_socket_name, kind)
)
):
msg = f"No output method for ({output_socket_name}, {str(kind.value)}"
msg = f'No output method for ({output_socket_name}, {str(kind.value)}'
raise ValueError(msg)
return output_socket_method(self)
@ -545,17 +561,22 @@ class MaxwellSimNode(bpy.types.Node):
# - Action Chain
####################
def sync_prop(self, prop_name: str, context: bpy.types.Context):
"""Called when a property has been updated.
"""
"""Called when a property has been updated."""
if not hasattr(self, prop_name):
msg = f"Property {prop_name} not defined on socket {self}"
msg = f'Property {prop_name} not defined on socket {self}'
raise RuntimeError(msg)
self.trigger_action("value_changed", prop_name=prop_name)
self.trigger_action('value_changed', prop_name=prop_name)
def trigger_action(
self,
action: typx.Literal["enable_lock", "disable_lock", "value_changed", "show_preview", "show_plot"],
action: typx.Literal[
'enable_lock',
'disable_lock',
'value_changed',
'show_preview',
'show_plot',
],
socket_name: ct.SocketName | None = None,
prop_name: ct.SocketName | None = None,
) -> None:
@ -565,63 +586,69 @@ class MaxwellSimNode(bpy.types.Node):
output socket method that implicitly depends on this input socket.
"""
# Forwards Chains
if action == "value_changed":
if action == 'value_changed':
# Run User Callbacks
## Careful with these, they run BEFORE propagation...
## ...because later-chain methods may rely on the results of this.
for method in self._on_value_changed_methods:
if (
(
socket_name
and socket_name in method._extra_data.get("changed_sockets")
) or (
and socket_name
in method._extra_data.get('changed_sockets')
)
or (
prop_name
and prop_name in method._extra_data.get("changed_props")
) or (
and prop_name
in method._extra_data.get('changed_props')
)
or (
socket_name
and method._extra_data["changed_loose_input"]
and method._extra_data['changed_loose_input']
and socket_name in self.loose_input_sockets
)
):
method(self)
# Propagate via Output Sockets
for bl_socket in self.active_bl_sockets("output"):
for bl_socket in self.active_bl_sockets('output'):
bl_socket.trigger_action(action)
# Backwards Chains
elif action == "enable_lock":
elif action == 'enable_lock':
self.locked = True
## Propagate via Input Sockets
for bl_socket in self.active_bl_sockets("input"):
for bl_socket in self.active_bl_sockets('input'):
bl_socket.trigger_action(action)
elif action == "disable_lock":
elif action == 'disable_lock':
self.locked = False
## Propagate via Input Sockets
for bl_socket in self.active_bl_sockets("input"):
for bl_socket in self.active_bl_sockets('input'):
bl_socket.trigger_action(action)
elif action == "show_preview":
elif action == 'show_preview':
# Run User Callbacks
for method in self._on_show_preview:
method(self)
## Propagate via Input Sockets
for bl_socket in self.active_bl_sockets("input"):
for bl_socket in self.active_bl_sockets('input'):
bl_socket.trigger_action(action)
elif action == "show_plot":
elif action == 'show_plot':
# Run User Callbacks
## These shouldn't change any data, BUT...
## ...because they can stop propagation, they should go first.
for method in self._on_show_plot:
method(self)
if method._extra_data["stop_propagation"]:
if method._extra_data['stop_propagation']:
return
## Propagate via Input Sockets
for bl_socket in self.active_bl_sockets("input"):
for bl_socket in self.active_bl_sockets('input'):
bl_socket.trigger_action(action)
####################
@ -637,8 +664,7 @@ class MaxwellSimNode(bpy.types.Node):
return node_tree.bl_idname == ct.TreeType.MaxwellSim.value
def init(self, context: bpy.types.Context):
"""Run (by Blender) on node creation.
"""
"""Run (by Blender) on node creation."""
global CACHE
# Initialize Cache and Instance ID
@ -653,7 +679,8 @@ class MaxwellSimNode(bpy.types.Node):
self.sync_sockets()
# Apply Default Preset
if self.active_preset: self.sync_active_preset()
if self.active_preset:
self.sync_active_preset()
# Callbacks
for method in self._on_init:
@ -663,8 +690,7 @@ class MaxwellSimNode(bpy.types.Node):
pass
def free(self) -> None:
"""Run (by Blender) when deleting the node.
"""
"""Run (by Blender) when deleting the node."""
global CACHE
if not CACHE.get(self.instance_id):
CACHE[self.instance_id] = {}
@ -680,7 +706,7 @@ class MaxwellSimNode(bpy.types.Node):
bl_socket.is_linked and bl_socket.locked
for bl_socket in self.inputs.values()
):
self.trigger_action("disable_lock")
self.trigger_action('disable_lock')
# Free Managed Objects
for managed_obj in self.managed_objs.values():
@ -697,18 +723,16 @@ class MaxwellSimNode(bpy.types.Node):
del CACHE[self.instance_id]
def chain_event_decorator(
callback_type: typ.Literal[
"computes_output_socket",
"on_value_changed",
"on_show_preview",
"on_show_plot",
"on_init",
'computes_output_socket',
'on_value_changed',
'on_show_preview',
'on_show_plot',
'on_init',
],
index_by: typ.Any | None = None,
extra_data: dict[str, typ.Any] | None = None,
kind: ct.DataFlowKind = ct.DataFlowKind.Value,
input_sockets: set[str] = set(), ## For now, presume
output_sockets: set[str] = set(), ## For now, presume
@ -716,8 +740,7 @@ def chain_event_decorator(
loose_output_sockets: bool = False,
props: set[str] = set(),
managed_objs: set[str] = set(),
req_params: set[str] = set()
req_params: set[str] = set(),
):
def decorator(method: typ.Callable) -> typ.Callable:
# Check Function Signature Validity
@ -725,11 +748,11 @@ def chain_event_decorator(
## Too Little
if func_sig != req_params and func_sig.issubset(req_params):
msg = f"Decorated method {method.__name__} is missing arguments {req_params - func_sig}"
msg = f'Decorated method {method.__name__} is missing arguments {req_params - func_sig}'
## Too Much
if func_sig != req_params and func_sig.issuperset(req_params):
msg = f"Decorated method {method.__name__} has superfluous arguments {func_sig - req_params}"
msg = f'Decorated method {method.__name__} has superfluous arguments {func_sig - req_params}'
raise ValueError(msg)
## Just Right :)
@ -744,7 +767,9 @@ def chain_event_decorator(
## Add Input Sockets
if input_sockets:
_input_sockets = {
input_socket_name: node._compute_input(input_socket_name, kind)
input_socket_name: node._compute_input(
input_socket_name, kind
)
for input_socket_name in input_sockets
}
method_kw_args |= dict(input_sockets=_input_sockets)
@ -752,7 +777,9 @@ def chain_event_decorator(
## Add Output Sockets
if output_sockets:
_output_sockets = {
output_socket_name: node.compute_output(output_socket_name, kind)
output_socket_name: node.compute_output(
output_socket_name, kind
)
for output_socket_name in output_sockets
}
method_kw_args |= dict(output_sockets=_output_sockets)
@ -760,7 +787,9 @@ def chain_event_decorator(
## Add Loose Sockets
if loose_input_sockets:
_loose_input_sockets = {
input_socket_name: node._compute_input(input_socket_name, kind)
input_socket_name: node._compute_input(
input_socket_name, kind
)
for input_socket_name in node.loose_input_sockets
}
method_kw_args |= dict(
@ -768,7 +797,9 @@ def chain_event_decorator(
)
if loose_output_sockets:
_loose_output_sockets = {
output_socket_name: node.compute_output(output_socket_name, kind)
output_socket_name: node.compute_output(
output_socket_name, kind
)
for output_socket_name in node.loose_output_sockets
}
method_kw_args |= dict(
@ -778,8 +809,7 @@ def chain_event_decorator(
## Add Props
if props:
_props = {
prop_name: getattr(node, prop_name)
for prop_name in props
prop_name: getattr(node, prop_name) for prop_name in props
}
method_kw_args |= dict(props=_props)
@ -805,6 +835,7 @@ def chain_event_decorator(
decorated._extra_data = extra_data
return decorated
return decorator
@ -842,16 +873,15 @@ def computes_output_socket(
and returns a new output-socket-computing method, now annotated
and discoverable by the `MaxwellSimTreeNode`.
"""
req_params = {"self"} | (
{"input_sockets"} if input_sockets else set()
) | (
{"props"} if props else set()
) | (
{"managed_objs"} if managed_objs else set()
req_params = (
{'self'}
| ({'input_sockets'} if input_sockets else set())
| ({'props'} if props else set())
| ({'managed_objs'} if managed_objs else set())
)
return chain_event_decorator(
callback_type="computes_output_socket",
callback_type='computes_output_socket',
index_by=(output_socket_name, kind),
kind=kind,
input_sockets=input_sockets,
@ -861,7 +891,6 @@ def computes_output_socket(
)
####################
# - Decorator: On Show Preview
####################
@ -869,40 +898,42 @@ def on_value_changed(
socket_name: set[ct.SocketName] | ct.SocketName | None = None,
prop_name: set[str] | str | None = None,
any_loose_input_socket: bool = False,
kind: ct.DataFlowKind = ct.DataFlowKind.Value,
input_sockets: set[str] = set(),
props: set[str] = set(),
managed_objs: set[str] = set(),
):
if sum([
if (
sum(
[
int(socket_name is not None),
int(prop_name is not None),
int(any_loose_input_socket),
]) > 1:
msg = "Define only one of socket_name, prop_name or any_loose_input_socket"
]
)
> 1
):
msg = 'Define only one of socket_name, prop_name or any_loose_input_socket'
raise ValueError(msg)
req_params = {"self"} | (
{"input_sockets"} if input_sockets else set()
) | (
{"loose_input_sockets"} if any_loose_input_socket else set()
) | (
{"props"} if props else set()
) | (
{"managed_objs"} if managed_objs else set()
req_params = (
{'self'}
| ({'input_sockets'} if input_sockets else set())
| ({'loose_input_sockets'} if any_loose_input_socket else set())
| ({'props'} if props else set())
| ({'managed_objs'} if managed_objs else set())
)
return chain_event_decorator(
callback_type="on_value_changed",
callback_type='on_value_changed',
extra_data={
"changed_sockets": (
'changed_sockets': (
socket_name if isinstance(socket_name, set) else {socket_name}
),
"changed_props": (
'changed_props': (
prop_name if isinstance(prop_name, set) else {prop_name}
),
"changed_loose_input": any_loose_input_socket,
'changed_loose_input': any_loose_input_socket,
},
kind=kind,
input_sockets=input_sockets,
@ -912,6 +943,7 @@ def on_value_changed(
req_params=req_params,
)
def on_show_preview(
kind: ct.DataFlowKind = ct.DataFlowKind.Value,
input_sockets: set[str] = set(), ## For now, presume only same kind
@ -919,18 +951,16 @@ def on_show_preview(
props: set[str] = set(),
managed_objs: set[str] = set(),
):
req_params = {"self"} | (
{"input_sockets"} if input_sockets else set()
) | (
{"output_sockets"} if output_sockets else set()
) | (
{"props"} if props else set()
) | (
{"managed_objs"} if managed_objs else set()
req_params = (
{'self'}
| ({'input_sockets'} if input_sockets else set())
| ({'output_sockets'} if output_sockets else set())
| ({'props'} if props else set())
| ({'managed_objs'} if managed_objs else set())
)
return chain_event_decorator(
callback_type="on_show_preview",
callback_type='on_show_preview',
kind=kind,
input_sockets=input_sockets,
output_sockets=output_sockets,
@ -939,6 +969,7 @@ def on_show_preview(
req_params=req_params,
)
def on_show_plot(
kind: ct.DataFlowKind = ct.DataFlowKind.Value,
input_sockets: set[str] = set(),
@ -947,20 +978,18 @@ def on_show_plot(
managed_objs: set[str] = set(),
stop_propagation: bool = False,
):
req_params = {"self"} | (
{"input_sockets"} if input_sockets else set()
) | (
{"output_sockets"} if output_sockets else set()
) | (
{"props"} if props else set()
) | (
{"managed_objs"} if managed_objs else set()
req_params = (
{'self'}
| ({'input_sockets'} if input_sockets else set())
| ({'output_sockets'} if output_sockets else set())
| ({'props'} if props else set())
| ({'managed_objs'} if managed_objs else set())
)
return chain_event_decorator(
callback_type="on_show_plot",
callback_type='on_show_plot',
extra_data={
"stop_propagation": stop_propagation,
'stop_propagation': stop_propagation,
},
kind=kind,
input_sockets=input_sockets,
@ -970,6 +999,7 @@ def on_show_plot(
req_params=req_params,
)
def on_init(
kind: ct.DataFlowKind = ct.DataFlowKind.Value,
input_sockets: set[str] = set(),
@ -977,18 +1007,16 @@ def on_init(
props: set[str] = set(),
managed_objs: set[str] = set(),
):
req_params = {"self"} | (
{"input_sockets"} if input_sockets else set()
) | (
{"output_sockets"} if output_sockets else set()
) | (
{"props"} if props else set()
) | (
{"managed_objs"} if managed_objs else set()
req_params = (
{'self'}
| ({'input_sockets'} if input_sockets else set())
| ({'output_sockets'} if output_sockets else set())
| ({'props'} if props else set())
| ({'managed_objs'} if managed_objs else set())
)
return chain_event_decorator(
callback_type="on_init",
callback_type='on_init',
kind=kind,
input_sockets=input_sockets,
output_sockets=output_sockets,

View File

@ -6,40 +6,40 @@ from ... import contracts as ct
from ... import sockets
from .. import base
class BoundCondsNode(base.MaxwellSimNode):
node_type = ct.NodeType.BoundConds
bl_label = "Bound Box"
bl_label = 'Bound Box'
# bl_icon = ...
####################
# - Sockets
####################
input_sockets = {
"+X": sockets.MaxwellBoundCondSocketDef(),
"-X": sockets.MaxwellBoundCondSocketDef(),
"+Y": sockets.MaxwellBoundCondSocketDef(),
"-Y": sockets.MaxwellBoundCondSocketDef(),
"+Z": sockets.MaxwellBoundCondSocketDef(),
"-Z": sockets.MaxwellBoundCondSocketDef(),
'+X': sockets.MaxwellBoundCondSocketDef(),
'-X': sockets.MaxwellBoundCondSocketDef(),
'+Y': sockets.MaxwellBoundCondSocketDef(),
'-Y': sockets.MaxwellBoundCondSocketDef(),
'+Z': sockets.MaxwellBoundCondSocketDef(),
'-Z': sockets.MaxwellBoundCondSocketDef(),
}
output_sockets = {
"BCs": sockets.MaxwellBoundCondsSocketDef(),
'BCs': sockets.MaxwellBoundCondsSocketDef(),
}
####################
# - Output Socket Computation
####################
@base.computes_output_socket(
"BCs",
input_sockets={"+X", "-X", "+Y", "-Y", "+Z", "-Z"}
'BCs', input_sockets={'+X', '-X', '+Y', '-Y', '+Z', '-Z'}
)
def compute_simulation(self, input_sockets) -> td.BoundarySpec:
x_pos = input_sockets["+X"]
x_neg = input_sockets["-X"]
y_pos = input_sockets["+Y"]
y_neg = input_sockets["-Y"]
z_pos = input_sockets["+Z"]
z_neg = input_sockets["-Z"]
x_pos = input_sockets['+X']
x_neg = input_sockets['-X']
y_pos = input_sockets['+Y']
y_neg = input_sockets['-Y']
z_pos = input_sockets['+Z']
z_neg = input_sockets['-Z']
return td.BoundarySpec(
x=td.Boundary(
@ -57,15 +57,10 @@ class BoundCondsNode(base.MaxwellSimNode):
)
####################
# - Blender Registration
####################
BL_REGISTER = [
BoundCondsNode,
]
BL_NODES = {
ct.NodeType.BoundConds: (
ct.NodeCategory.MAXWELLSIM_BOUNDS
)
}
BL_NODES = {ct.NodeType.BoundConds: (ct.NodeCategory.MAXWELLSIM_BOUNDS)}

View File

@ -10,7 +10,6 @@ BL_REGISTER = [
*pml_bound_face.BL_REGISTER,
*pec_bound_face.BL_REGISTER,
*pmc_bound_face.BL_REGISTER,
*bloch_bound_face.BL_REGISTER,
*periodic_bound_face.BL_REGISTER,
*absorbing_bound_face.BL_REGISTER,
@ -19,7 +18,6 @@ BL_NODES = {
**pml_bound_face.BL_NODES,
**pec_bound_face.BL_NODES,
**pmc_bound_face.BL_NODES,
**bloch_bound_face.BL_NODES,
**periodic_bound_face.BL_NODES,
**absorbing_bound_face.BL_NODES,

View File

@ -9,18 +9,14 @@ from . import web_importers
BL_REGISTER = [
*wave_constant.BL_REGISTER,
# *unit_system.BL_REGISTER,
*constants.BL_REGISTER,
*web_importers.BL_REGISTER,
# *file_importers.BL_REGISTER,
]
BL_NODES = {
**wave_constant.BL_NODES,
# **unit_system.BL_NODES,
**constants.BL_NODES,
**web_importers.BL_NODES,
# *file_importers.BL_REGISTER,
}

View File

@ -1,5 +1,6 @@
# from . import scientific_constant
from . import number_constant
# from . import physical_constant
from . import blender_constant

View File

@ -4,25 +4,26 @@ from .... import contracts as ct
from .... import sockets
from ... import base
class BlenderConstantNode(base.MaxwellSimNode):
node_type = ct.NodeType.BlenderConstant
bl_label = "Blender Constant"
bl_label = 'Blender Constant'
input_socket_sets = {
"Object": {
"Value": sockets.BlenderObjectSocketDef(),
'Object': {
'Value': sockets.BlenderObjectSocketDef(),
},
"Collection": {
"Value": sockets.BlenderCollectionSocketDef(),
'Collection': {
'Value': sockets.BlenderCollectionSocketDef(),
},
"Text": {
"Value": sockets.BlenderTextSocketDef(),
'Text': {
'Value': sockets.BlenderTextSocketDef(),
},
"Image": {
"Value": sockets.BlenderImageSocketDef(),
'Image': {
'Value': sockets.BlenderImageSocketDef(),
},
"GeoNode Tree": {
"Value": sockets.BlenderGeoNodesSocketDef(),
'GeoNode Tree': {
'Value': sockets.BlenderGeoNodesSocketDef(),
},
}
output_socket_sets = input_socket_sets
@ -30,13 +31,9 @@ class BlenderConstantNode(base.MaxwellSimNode):
####################
# - Callbacks
####################
@base.computes_output_socket(
"Value",
input_sockets={"Value"}
)
@base.computes_output_socket('Value', input_sockets={'Value'})
def compute_value(self, input_sockets) -> typ.Any:
return input_sockets["Value"]
return input_sockets['Value']
####################
@ -46,7 +43,5 @@ BL_REGISTER = [
BlenderConstantNode,
]
BL_NODES = {
ct.NodeType.BlenderConstant: (
ct.NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS
)
ct.NodeType.BlenderConstant: (ct.NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS)
}

View File

@ -7,22 +7,23 @@ from .... import contracts as ct
from .... import sockets
from ... import base
class NumberConstantNode(base.MaxwellSimNode):
node_type = ct.NodeType.NumberConstant
bl_label = "Numerical Constant"
bl_label = 'Numerical Constant'
input_socket_sets = {
"Integer": {
"Value": sockets.IntegerNumberSocketDef(),
'Integer': {
'Value': sockets.IntegerNumberSocketDef(),
},
"Rational": {
"Value": sockets.RationalNumberSocketDef(),
'Rational': {
'Value': sockets.RationalNumberSocketDef(),
},
"Real": {
"Value": sockets.RealNumberSocketDef(),
'Real': {
'Value': sockets.RealNumberSocketDef(),
},
"Complex": {
"Value": sockets.ComplexNumberSocketDef(),
'Complex': {
'Value': sockets.ComplexNumberSocketDef(),
},
}
output_socket_sets = input_socket_sets
@ -30,13 +31,9 @@ class NumberConstantNode(base.MaxwellSimNode):
####################
# - Callbacks
####################
@base.computes_output_socket(
"Value",
input_sockets={"Value"}
)
@base.computes_output_socket('Value', input_sockets={'Value'})
def compute_value(self, input_sockets) -> typ.Any:
return input_sockets["Value"]
return input_sockets['Value']
####################
@ -46,7 +43,5 @@ BL_REGISTER = [
NumberConstantNode,
]
BL_NODES = {
ct.NodeType.NumberConstant: (
ct.NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS
)
ct.NodeType.NumberConstant: (ct.NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS)
}

View File

@ -5,47 +5,48 @@ from .... import contracts
from .... import sockets
from ... import base
class PhysicalConstantNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.PhysicalConstant
bl_label = "Physical Constant"
bl_label = 'Physical Constant'
# bl_icon = constants.ICON_SIM_INPUT
input_sockets = {}
input_socket_sets = {
"time": {
"value": sockets.PhysicalTimeSocketDef(
label="Time",
'time': {
'value': sockets.PhysicalTimeSocketDef(
label='Time',
),
},
"angle": {
"value": sockets.PhysicalAngleSocketDef(
label="Angle",
'angle': {
'value': sockets.PhysicalAngleSocketDef(
label='Angle',
),
},
"length": {
"value": sockets.PhysicalLengthSocketDef(
label="Length",
'length': {
'value': sockets.PhysicalLengthSocketDef(
label='Length',
),
},
"area": {
"value": sockets.PhysicalAreaSocketDef(
label="Area",
'area': {
'value': sockets.PhysicalAreaSocketDef(
label='Area',
),
},
"volume": {
"value": sockets.PhysicalVolumeSocketDef(
label="Volume",
'volume': {
'value': sockets.PhysicalVolumeSocketDef(
label='Volume',
),
},
"point_3d": {
"value": sockets.PhysicalPoint3DSocketDef(
label="3D Point",
'point_3d': {
'value': sockets.PhysicalPoint3DSocketDef(
label='3D Point',
),
},
"size_3d": {
"value": sockets.PhysicalSize3DSocketDef(
label="3D Size",
'size_3d': {
'value': sockets.PhysicalSize3DSocketDef(
label='3D Size',
),
},
## I got bored so maybe the rest later
@ -56,10 +57,9 @@ class PhysicalConstantNode(base.MaxwellSimTreeNode):
####################
# - Callbacks
####################
@base.computes_output_socket("value")
@base.computes_output_socket('value')
def compute_value(self: contracts.NodeTypeProtocol) -> sp.Expr:
return self.compute_input("value")
return self.compute_input('value')
####################

View File

@ -5,29 +5,29 @@ from ... import contracts as ct
from ... import sockets
from .. import base
class PhysicalUnitSystemNode(base.MaxwellSimNode):
node_type = ct.NodeType.UnitSystem
bl_label = "Unit System"
bl_label = 'Unit System'
input_sockets = {
"Unit System": sockets.PhysicalUnitSystemSocketDef(
'Unit System': sockets.PhysicalUnitSystemSocketDef(
show_by_default=True,
),
}
output_sockets = {
"Unit System": sockets.PhysicalUnitSystemSocketDef(),
'Unit System': sockets.PhysicalUnitSystemSocketDef(),
}
####################
# - Callbacks
####################
@base.computes_output_socket(
"Unit System",
input_sockets = {"Unit System"},
'Unit System',
input_sockets={'Unit System'},
)
def compute_value(self, input_sockets) -> dict:
return input_sockets["Unit System"]
return input_sockets['Unit System']
####################
@ -36,8 +36,4 @@ class PhysicalUnitSystemNode(base.MaxwellSimNode):
BL_REGISTER = [
PhysicalUnitSystemNode,
]
BL_NODES = {
ct.NodeType.UnitSystem: (
ct.NodeCategory.MAXWELLSIM_INPUTS
)
}
BL_NODES = {ct.NodeType.UnitSystem: (ct.NodeCategory.MAXWELLSIM_INPUTS)}

View File

@ -8,39 +8,36 @@ from ... import contracts as ct
from ... import sockets
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
class WaveConstantNode(base.MaxwellSimNode):
node_type = ct.NodeType.WaveConstant
bl_label = "Wave Constant"
bl_label = 'Wave Constant'
input_socket_sets = {
# Single
"Vacuum WL": {
"WL": sockets.PhysicalLengthSocketDef(
'Vacuum WL': {
'WL': sockets.PhysicalLengthSocketDef(
default_value=500 * spu.nm,
default_unit=spu.nm,
),
},
"Frequency": {
"Freq": sockets.PhysicalFreqSocketDef(
'Frequency': {
'Freq': sockets.PhysicalFreqSocketDef(
default_value=500 * spux.THz,
default_unit=spux.THz,
),
},
# Listy
"Vacuum WLs": {
"WLs": sockets.PhysicalLengthSocketDef(
'Vacuum WLs': {
'WLs': sockets.PhysicalLengthSocketDef(
is_list=True,
),
},
"Frequencies": {
"Freqs": sockets.PhysicalFreqSocketDef(
'Frequencies': {
'Freqs': sockets.PhysicalFreqSocketDef(
is_list=True,
),
},
@ -50,47 +47,47 @@ class WaveConstantNode(base.MaxwellSimNode):
# - Callbacks
####################
@base.computes_output_socket(
"WL",
input_sockets={"WL", "Freq"},
'WL',
input_sockets={'WL', 'Freq'},
)
def compute_vac_wl(self, input_sockets: dict) -> sp.Expr:
if (vac_wl := input_sockets["WL"]) is not None:
if (vac_wl := input_sockets['WL']) is not None:
return vac_wl
elif (freq := input_sockets["Freq"]) is not None:
elif (freq := input_sockets['Freq']) is not None:
return spu.convert_to(
VAC_SPEED_OF_LIGHT / freq,
spu.meter,
)
raise RuntimeError("Vac WL and Freq are both None")
raise RuntimeError('Vac WL and Freq are both None')
@base.computes_output_socket(
"Freq",
input_sockets={"WL", "Freq"},
'Freq',
input_sockets={'WL', 'Freq'},
)
def compute_freq(self, input_sockets: dict) -> sp.Expr:
if (vac_wl := input_sockets["WL"]) is not None:
if (vac_wl := input_sockets['WL']) is not None:
return spu.convert_to(
VAC_SPEED_OF_LIGHT / vac_wl,
spu.hertz,
)
elif (freq := input_sockets["Freq"]) is not None:
elif (freq := input_sockets['Freq']) is not None:
return freq
raise RuntimeError("Vac WL and Freq are both None")
raise RuntimeError('Vac WL and Freq are both None')
####################
# - Listy Callbacks
####################
@base.computes_output_socket(
"WLs",
input_sockets={"WLs", "Freqs"},
'WLs',
input_sockets={'WLs', 'Freqs'},
)
def compute_vac_wls(self, input_sockets: dict) -> sp.Expr:
if (vac_wls := input_sockets["WLs"]) is not None:
if (vac_wls := input_sockets['WLs']) is not None:
return vac_wls
elif (freqs := input_sockets["Freqs"]) is not None:
elif (freqs := input_sockets['Freqs']) is not None:
return [
spu.convert_to(
VAC_SPEED_OF_LIGHT / freq,
@ -99,14 +96,14 @@ class WaveConstantNode(base.MaxwellSimNode):
for freq in freqs
][::-1]
raise RuntimeError("Vac WLs and Freqs are both None")
raise RuntimeError('Vac WLs and Freqs are both None')
@base.computes_output_socket(
"Freqs",
input_sockets={"WLs", "Freqs"},
'Freqs',
input_sockets={'WLs', 'Freqs'},
)
def compute_freqs(self, input_sockets: dict) -> sp.Expr:
if (vac_wls := input_sockets["WLs"]) is not None:
if (vac_wls := input_sockets['WLs']) is not None:
return [
spu.convert_to(
VAC_SPEED_OF_LIGHT / vac_wl,
@ -114,33 +111,32 @@ class WaveConstantNode(base.MaxwellSimNode):
)
for vac_wl in vac_wls
][::-1]
elif (freqs := input_sockets["Freqs"]) is not None:
elif (freqs := input_sockets['Freqs']) is not None:
return freqs
raise RuntimeError("Vac WLs and Freqs are both None")
raise RuntimeError('Vac WLs and Freqs are both None')
####################
# - Callbacks
####################
@base.on_value_changed(
prop_name="active_socket_set",
props={"active_socket_set"}
prop_name='active_socket_set', props={'active_socket_set'}
)
def on_value_changed__active_socket_set(self, props: dict):
# Singular: Normal Output Sockets
if props["active_socket_set"] in {"Vacuum WL", "Frequency"}:
if props['active_socket_set'] in {'Vacuum WL', 'Frequency'}:
self.loose_output_sockets = {}
self.loose_output_sockets = {
"Freq": sockets.PhysicalFreqSocketDef(),
"WL": sockets.PhysicalLengthSocketDef(),
'Freq': sockets.PhysicalFreqSocketDef(),
'WL': sockets.PhysicalLengthSocketDef(),
}
# Plural: Listy Output Sockets
elif props["active_socket_set"] in {"Vacuum WLs", "Frequencies"}:
elif props['active_socket_set'] in {'Vacuum WLs', 'Frequencies'}:
self.loose_output_sockets = {}
self.loose_output_sockets = {
"Freqs": sockets.PhysicalFreqSocketDef(is_list=True),
"WLs": sockets.PhysicalLengthSocketDef(is_list=True),
'Freqs': sockets.PhysicalFreqSocketDef(is_list=True),
'WLs': sockets.PhysicalLengthSocketDef(is_list=True),
}
else:
@ -158,8 +154,4 @@ class WaveConstantNode(base.MaxwellSimNode):
BL_REGISTER = [
WaveConstantNode,
]
BL_NODES = {
ct.NodeType.WaveConstant: (
ct.NodeCategory.MAXWELLSIM_INPUTS
)
}
BL_NODES = {ct.NodeType.WaveConstant: (ct.NodeCategory.MAXWELLSIM_INPUTS)}

View File

@ -17,49 +17,50 @@ from ... import base
CACHE = {}
####################
# - Node
####################
class Tidy3DWebImporterNode(base.MaxwellSimNode):
node_type = ct.NodeType.Tidy3DWebImporter
bl_label = "Tidy3DWebImporter"
bl_label = 'Tidy3DWebImporter'
input_sockets = {
"Cloud Task": sockets.Tidy3DCloudTaskSocketDef(
'Cloud Task': sockets.Tidy3DCloudTaskSocketDef(
should_exist=True,
),
"Cache Path": sockets.FilePathSocketDef(
default_path=Path("loaded_simulation.hdf5")
)
'Cache Path': sockets.FilePathSocketDef(
default_path=Path('loaded_simulation.hdf5')
),
}
####################
# - Output Methods
####################
@base.computes_output_socket(
"FDTD Sim Data",
input_sockets={"Cloud Task", "Cache Path"},
'FDTD Sim Data',
input_sockets={'Cloud Task', 'Cache Path'},
)
def compute_fdtd_sim_data(self, input_sockets: dict) -> str:
global CACHE
if not CACHE.get(self.instance_id):
CACHE[self.instance_id] = {"fdtd_sim_data": None}
CACHE[self.instance_id] = {'fdtd_sim_data': None}
if CACHE[self.instance_id]["fdtd_sim_data"] is not None:
return CACHE[self.instance_id]["fdtd_sim_data"]
if CACHE[self.instance_id]['fdtd_sim_data'] is not None:
return CACHE[self.instance_id]['fdtd_sim_data']
if not (
(cloud_task := input_sockets["Cloud Task"]) is not None
(cloud_task := input_sockets['Cloud Task']) is not None
and isinstance(cloud_task, tdcloud.CloudTask)
and cloud_task.status == "success"
and cloud_task.status == 'success'
):
msg = "Won't attempt getting SimData"
raise RuntimeError(msg)
# Load the Simulation
cache_path = input_sockets["Cache Path"]
cache_path = input_sockets['Cache Path']
if cache_path is None:
print("CACHE PATH IS NONE WHY")
print('CACHE PATH IS NONE WHY')
return ## I guess?
if cache_path.is_file():
sim_data = td.SimulationData.from_file(str(cache_path))
@ -70,26 +71,25 @@ class Tidy3DWebImporterNode(base.MaxwellSimNode):
path=str(cache_path),
)
CACHE[self.instance_id]["fdtd_sim_data"] = sim_data
CACHE[self.instance_id]['fdtd_sim_data'] = sim_data
return sim_data
@base.computes_output_socket(
"FDTD Sim",
input_sockets={"Cloud Task"},
'FDTD Sim',
input_sockets={'Cloud Task'},
)
def compute_fdtd_sim(self, input_sockets: dict) -> str:
if not isinstance(
cloud_task := input_sockets["Cloud Task"],
tdcloud.CloudTask
cloud_task := input_sockets['Cloud Task'], tdcloud.CloudTask
):
msg ="Input cloud task does not exist"
msg = 'Input cloud task does not exist'
raise RuntimeError(msg)
# Load the Simulation
with tempfile.NamedTemporaryFile(delete=False) as f:
_path_tmp = Path(f.name)
_path_tmp.rename(f.name + ".json")
path_tmp = Path(f.name + ".json")
_path_tmp.rename(f.name + '.json')
path_tmp = Path(f.name + '.json')
sim = td_web.api.webapi.load_simulation(
cloud_task.task_id,
@ -103,18 +103,17 @@ class Tidy3DWebImporterNode(base.MaxwellSimNode):
# - Update
####################
@base.on_value_changed(
socket_name="Cloud Task",
input_sockets={"Cloud Task"}
socket_name='Cloud Task', input_sockets={'Cloud Task'}
)
def on_value_changed__cloud_task(self, input_sockets: dict):
if (
(cloud_task := input_sockets["Cloud Task"]) is not None
(cloud_task := input_sockets['Cloud Task']) is not None
and isinstance(cloud_task, tdcloud.CloudTask)
and cloud_task.status == "success"
and cloud_task.status == 'success'
):
self.loose_output_sockets = {
"FDTD Sim Data": sockets.MaxwellFDTDSimDataSocketDef(),
"FDTD Sim": sockets.MaxwellFDTDSimSocketDef(),
'FDTD Sim Data': sockets.MaxwellFDTDSimDataSocketDef(),
'FDTD Sim': sockets.MaxwellFDTDSimSocketDef(),
}
return

View File

@ -8,95 +8,91 @@ from .. import contracts as ct
from .. import sockets
from . import base
class KitchenSinkNode(base.MaxwellSimNode):
node_type = ct.NodeType.KitchenSink
bl_label = "Kitchen Sink"
bl_label = 'Kitchen Sink'
# bl_icon = ...
####################
# - Sockets
####################
input_sockets = {
"Static Data": sockets.AnySocketDef(),
'Static Data': sockets.AnySocketDef(),
}
input_socket_sets = {
"Basic": {
"Any": sockets.AnySocketDef(),
"Bool": sockets.BoolSocketDef(),
"FilePath": sockets.FilePathSocketDef(),
"Text": sockets.TextSocketDef(),
'Basic': {
'Any': sockets.AnySocketDef(),
'Bool': sockets.BoolSocketDef(),
'FilePath': sockets.FilePathSocketDef(),
'Text': sockets.TextSocketDef(),
},
"Number": {
"Integer": sockets.IntegerNumberSocketDef(),
"Rational": sockets.RationalNumberSocketDef(),
"Real": sockets.RealNumberSocketDef(),
"Complex": sockets.ComplexNumberSocketDef(),
'Number': {
'Integer': sockets.IntegerNumberSocketDef(),
'Rational': sockets.RationalNumberSocketDef(),
'Real': sockets.RealNumberSocketDef(),
'Complex': sockets.ComplexNumberSocketDef(),
},
"Vector": {
"Real 2D": sockets.Real2DVectorSocketDef(),
"Real 3D": sockets.Real3DVectorSocketDef(
'Vector': {
'Real 2D': sockets.Real2DVectorSocketDef(),
'Real 3D': sockets.Real3DVectorSocketDef(
default_value=sp.Matrix([0.0, 0.0, 0.0])
),
"Complex 2D": sockets.Complex2DVectorSocketDef(),
"Complex 3D": sockets.Complex3DVectorSocketDef(),
'Complex 2D': sockets.Complex2DVectorSocketDef(),
'Complex 3D': sockets.Complex3DVectorSocketDef(),
},
"Physical": {
"Time": sockets.PhysicalTimeSocketDef(),
'Physical': {
'Time': sockets.PhysicalTimeSocketDef(),
# "physical_point_2d": sockets.PhysicalPoint2DSocketDef(),
"Angle": sockets.PhysicalAngleSocketDef(),
"Length": sockets.PhysicalLengthSocketDef(),
"Area": sockets.PhysicalAreaSocketDef(),
"Volume": sockets.PhysicalVolumeSocketDef(),
"Point 3D": sockets.PhysicalPoint3DSocketDef(),
'Angle': sockets.PhysicalAngleSocketDef(),
'Length': sockets.PhysicalLengthSocketDef(),
'Area': sockets.PhysicalAreaSocketDef(),
'Volume': sockets.PhysicalVolumeSocketDef(),
'Point 3D': sockets.PhysicalPoint3DSocketDef(),
##"physical_size_2d": sockets.PhysicalSize2DSocketDef(),
"Size 3D": sockets.PhysicalSize3DSocketDef(),
"Mass": sockets.PhysicalMassSocketDef(),
"Speed": sockets.PhysicalSpeedSocketDef(),
"Accel Scalar": sockets.PhysicalAccelScalarSocketDef(),
"Force Scalar": sockets.PhysicalForceScalarSocketDef(),
'Size 3D': sockets.PhysicalSize3DSocketDef(),
'Mass': sockets.PhysicalMassSocketDef(),
'Speed': sockets.PhysicalSpeedSocketDef(),
'Accel Scalar': sockets.PhysicalAccelScalarSocketDef(),
'Force Scalar': sockets.PhysicalForceScalarSocketDef(),
# "physical_accel_3dvector": sockets.PhysicalAccel3DVectorSocketDef(),
##"physical_force_3dvector": sockets.PhysicalForce3DVectorSocketDef(),
"Pol": sockets.PhysicalPolSocketDef(),
"Freq": sockets.PhysicalFreqSocketDef(),
'Pol': sockets.PhysicalPolSocketDef(),
'Freq': sockets.PhysicalFreqSocketDef(),
},
"Blender": {
"Object": sockets.BlenderObjectSocketDef(),
"Collection": sockets.BlenderCollectionSocketDef(),
"Image": sockets.BlenderImageSocketDef(),
"GeoNodes": sockets.BlenderGeoNodesSocketDef(),
"Text": sockets.BlenderTextSocketDef(),
'Blender': {
'Object': sockets.BlenderObjectSocketDef(),
'Collection': sockets.BlenderCollectionSocketDef(),
'Image': sockets.BlenderImageSocketDef(),
'GeoNodes': sockets.BlenderGeoNodesSocketDef(),
'Text': sockets.BlenderTextSocketDef(),
},
"Maxwell": {
"Source": sockets.MaxwellSourceSocketDef(),
"Temporal Shape": sockets.MaxwellTemporalShapeSocketDef(),
"Medium": sockets.MaxwellMediumSocketDef(),
"Medium Non-Linearity": sockets.MaxwellMediumNonLinearitySocketDef(),
"Structure": sockets.MaxwellStructureSocketDef(),
"Bound Box": sockets.MaxwellBoundBoxSocketDef(),
"Bound Face": sockets.MaxwellBoundFaceSocketDef(),
"Monitor": sockets.MaxwellMonitorSocketDef(),
"FDTD Sim": sockets.MaxwellFDTDSimSocketDef(),
"Sim Grid": sockets.MaxwellSimGridSocketDef(),
"Sim Grid Axis": sockets.MaxwellSimGridAxisSocketDef(),
'Maxwell': {
'Source': sockets.MaxwellSourceSocketDef(),
'Temporal Shape': sockets.MaxwellTemporalShapeSocketDef(),
'Medium': sockets.MaxwellMediumSocketDef(),
'Medium Non-Linearity': sockets.MaxwellMediumNonLinearitySocketDef(),
'Structure': sockets.MaxwellStructureSocketDef(),
'Bound Box': sockets.MaxwellBoundBoxSocketDef(),
'Bound Face': sockets.MaxwellBoundFaceSocketDef(),
'Monitor': sockets.MaxwellMonitorSocketDef(),
'FDTD Sim': sockets.MaxwellFDTDSimSocketDef(),
'Sim Grid': sockets.MaxwellSimGridSocketDef(),
'Sim Grid Axis': sockets.MaxwellSimGridAxisSocketDef(),
},
}
output_sockets = {
"Static Data": sockets.AnySocketDef(),
'Static Data': sockets.AnySocketDef(),
}
output_socket_sets = input_socket_sets
####################
# - Blender Registration
####################
BL_REGISTER = [
KitchenSinkNode,
]
BL_NODES = {
ct.NodeType.KitchenSink: (
ct.NodeCategory.MAXWELLSIM_INPUTS
)
}
BL_NODES = {ct.NodeType.KitchenSink: (ct.NodeCategory.MAXWELLSIM_INPUTS)}

View File

@ -15,7 +15,6 @@ from . import library_medium
BL_REGISTER = [
*library_medium.BL_REGISTER,
# *pec_medium.BL_REGISTER,
# *isotropic_medium.BL_REGISTER,
# *anisotropic_medium.BL_REGISTER,
@ -31,7 +30,6 @@ BL_REGISTER = [
]
BL_NODES = {
**library_medium.BL_NODES,
# **pec_medium.BL_NODES,
# **isotropic_medium.BL_NODES,
# **anisotropic_medium.BL_NODES,

View File

@ -3,4 +3,3 @@
####################
BL_REGISTER = []
BL_NODES = {}

View File

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

View File

@ -3,4 +3,3 @@
####################
BL_REGISTER = []
BL_NODES = {}

View File

@ -14,27 +14,25 @@ from ... import sockets
from ... import managed_objs
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
class LibraryMediumNode(base.MaxwellSimNode):
node_type = ct.NodeType.LibraryMedium
bl_label = "Library Medium"
bl_label = 'Library Medium'
####################
# - Sockets
####################
input_sockets = {}
output_sockets = {
"Medium": sockets.MaxwellMediumSocketDef(),
'Medium': sockets.MaxwellMediumSocketDef(),
}
managed_obj_defs = {
"nk_plot": ct.schemas.ManagedObjDef(
'nk_plot': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLImage(name),
name_prefix="",
name_prefix='',
)
}
@ -42,25 +40,27 @@ class LibraryMediumNode(base.MaxwellSimNode):
# - Properties
####################
material: bpy.props.EnumProperty(
name="",
description="",
name='',
description='',
# icon="NODE_MATERIAL",
items=[
(
mat_key,
td.material_library[mat_key].name,
", ".join([
', '.join(
[
ref.journal
for ref in td.material_library[mat_key].variants[
td.material_library[mat_key].default
].reference
])
for ref in td.material_library[mat_key]
.variants[td.material_library[mat_key].default]
.reference
]
),
)
for mat_key in td.material_library
if mat_key != "graphene" ## For some reason, it's unique...
if mat_key != 'graphene' ## For some reason, it's unique...
],
default="Au",
update=(lambda self, context: self.sync_prop("material", context)),
default='Au',
update=(lambda self, context: self.sync_prop('material', context)),
)
@property
@ -71,12 +71,12 @@ class LibraryMediumNode(base.MaxwellSimNode):
spu.convert_to(
val * spu.hertz,
spuex.terahertz,
) / spuex.terahertz
)
/ spuex.terahertz
for val in mat.medium.frequency_range
]
return sp.pretty(
[freq_range[0].n(4), freq_range[1].n(4)],
use_unicode=True
[freq_range[0].n(4), freq_range[1].n(4)], use_unicode=True
)
@property
@ -87,38 +87,38 @@ class LibraryMediumNode(base.MaxwellSimNode):
spu.convert_to(
VAC_SPEED_OF_LIGHT / (val * spu.hertz),
spu.nanometer,
) / spu.nanometer
)
/ spu.nanometer
for val in reversed(mat.medium.frequency_range)
]
return sp.pretty(
[nm_range[0].n(4), nm_range[1].n(4)],
use_unicode=True
[nm_range[0].n(4), nm_range[1].n(4)], use_unicode=True
)
####################
# - UI
####################
def draw_props(self, context, layout):
layout.prop(self, "material", text="")
layout.prop(self, 'material', text='')
def draw_info(self, context, col):
# UI Drawing
split = col.split(factor=0.23, align=True)
_col = split.column(align=True)
_col.alignment = "LEFT"
_col.label(text="nm")
_col.label(text="THz")
_col.alignment = 'LEFT'
_col.label(text='nm')
_col.label(text='THz')
_col = split.column(align=True)
_col.alignment = "RIGHT"
_col.alignment = 'RIGHT'
_col.label(text=self.nm_range_str)
_col.label(text=self.freq_range_str)
####################
# - Output Sockets
####################
@base.computes_output_socket("Medium")
@base.computes_output_socket('Medium')
def compute_vac_wl(self) -> sp.Expr:
return td.material_library[self.material].medium
@ -126,8 +126,8 @@ class LibraryMediumNode(base.MaxwellSimNode):
# - Event Callbacks
####################
@base.on_show_plot(
managed_objs={"nk_plot"},
props={"material"},
managed_objs={'nk_plot'},
props={'material'},
stop_propagation=True, ## Plot only the first plottable node
)
def on_show_plot(
@ -135,28 +135,26 @@ class LibraryMediumNode(base.MaxwellSimNode):
managed_objs: dict[str, ct.schemas.ManagedObj],
props: dict[str, typ.Any],
):
medium = td.material_library[props["material"]].medium
medium = td.material_library[props['material']].medium
freq_range = [
spu.convert_to(
val * spu.hertz,
spuex.terahertz,
) / spu.hertz
)
/ spu.hertz
for val in medium.frequency_range
]
managed_objs["nk_plot"].mpl_plot_to_image(
managed_objs['nk_plot'].mpl_plot_to_image(
lambda ax: medium.plot(medium.frequency_range, ax=ax),
bl_select=True,
)
####################
# - Blender Registration
####################
BL_REGISTER = [
LibraryMediumNode,
]
BL_NODES = {
ct.NodeType.LibraryMedium: (
ct.NodeCategory.MAXWELLSIM_MEDIUMS
)
}
BL_NODES = {ct.NodeType.LibraryMedium: (ct.NodeCategory.MAXWELLSIM_MEDIUMS)}

View File

@ -3,4 +3,3 @@
####################
BL_REGISTER = []
BL_NODES = {}

View File

@ -3,4 +3,3 @@
####################
BL_REGISTER = []
BL_NODES = {}

View File

@ -3,4 +3,3 @@
####################
BL_REGISTER = []
BL_NODES = {}

View File

@ -6,67 +6,65 @@ from ... import contracts
from ... import sockets
from .. import base
class TripleSellmeierMediumNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.TripleSellmeierMedium
bl_label = "Three-Parameter Sellmeier Medium"
bl_label = 'Three-Parameter Sellmeier Medium'
# bl_icon = ...
####################
# - Sockets
####################
input_sockets = {
f"B{i}": sockets.RealNumberSocketDef(
label=f"B{i}",
f'B{i}': sockets.RealNumberSocketDef(
label=f'B{i}',
)
for i in [1, 2, 3]
} | {
f"C{i}": sockets.PhysicalAreaSocketDef(
label=f"C{i}",
default_unit=spu.um**2
f'C{i}': sockets.PhysicalAreaSocketDef(
label=f'C{i}', default_unit=spu.um**2
)
for i in [1, 2, 3]
}
output_sockets = {
"medium": sockets.MaxwellMediumSocketDef(
label="Medium"
),
'medium': sockets.MaxwellMediumSocketDef(label='Medium'),
}
####################
# - Presets
####################
presets = {
"BK7": contracts.PresetDef(
label="BK7 Glass",
description="Borosilicate crown glass (known as BK7)",
'BK7': contracts.PresetDef(
label='BK7 Glass',
description='Borosilicate crown glass (known as BK7)',
values={
"B1": 1.03961212,
"B2": 0.231792344,
"B3": 1.01046945,
"C1": 6.00069867e-3 * spu.um**2,
"C2": 2.00179144e-2 * spu.um**2,
"C3": 103.560653 * spu.um**2,
}
'B1': 1.03961212,
'B2': 0.231792344,
'B3': 1.01046945,
'C1': 6.00069867e-3 * spu.um**2,
'C2': 2.00179144e-2 * spu.um**2,
'C3': 103.560653 * spu.um**2,
},
),
"FUSED_SILICA": contracts.PresetDef(
label="Fused Silica",
description="Fused silica aka. SiO2",
'FUSED_SILICA': contracts.PresetDef(
label='Fused Silica',
description='Fused silica aka. SiO2',
values={
"B1": 0.696166300,
"B2": 0.407942600,
"B3": 0.897479400,
"C1": 4.67914826e-3 * spu.um**2,
"C2": 1.35120631e-2 * spu.um**2,
"C3": 97.9340025 * spu.um**2,
}
'B1': 0.696166300,
'B2': 0.407942600,
'B3': 0.897479400,
'C1': 4.67914826e-3 * spu.um**2,
'C2': 1.35120631e-2 * spu.um**2,
'C3': 97.9340025 * spu.um**2,
},
),
}
####################
# - Output Socket Computation
####################
@base.computes_output_socket("medium")
@base.computes_output_socket('medium')
def compute_medium(self: contracts.NodeTypeProtocol) -> td.Sellmeier:
## Retrieval
# B1 = self.compute_input("B1")
@ -75,17 +73,19 @@ class TripleSellmeierMediumNode(base.MaxwellSimTreeNode):
## Processing
# C1 = spu.convert_to(C1_with_units, spu.um**2) / spu.um**2
return td.Sellmeier(coeffs = [
return td.Sellmeier(
coeffs=[
(
self.compute_input(f"B{i}"),
self.compute_input(f'B{i}'),
spu.convert_to(
self.compute_input(f"C{i}"),
self.compute_input(f'C{i}'),
spu.um**2,
) / spu.um**2
)
/ spu.um**2,
)
for i in [1, 2, 3]
])
]
)
####################

View File

@ -3,4 +3,3 @@
####################
BL_REGISTER = []
BL_NODES = {}

View File

@ -15,47 +15,48 @@ from ... import sockets
from ... import managed_objs
from .. import base
GEONODES_MONITOR_BOX = "monitor_box"
GEONODES_MONITOR_BOX = 'monitor_box'
class EHFieldMonitorNode(base.MaxwellSimNode):
node_type = ct.NodeType.EHFieldMonitor
bl_label = "E/H Field Monitor"
bl_label = 'E/H Field Monitor'
use_sim_node_name = True
####################
# - Sockets
####################
input_sockets = {
"Center": sockets.PhysicalPoint3DSocketDef(),
"Size": sockets.PhysicalSize3DSocketDef(),
"Samples/Space": sockets.Integer3DVectorSocketDef(
'Center': sockets.PhysicalPoint3DSocketDef(),
'Size': sockets.PhysicalSize3DSocketDef(),
'Samples/Space': sockets.Integer3DVectorSocketDef(
default_value=sp.Matrix([10, 10, 10])
),
}
input_socket_sets = {
"Freq Domain": {
"Freqs": sockets.PhysicalFreqSocketDef(
'Freq Domain': {
'Freqs': sockets.PhysicalFreqSocketDef(
is_list=True,
),
},
"Time Domain": {
"Rec Start": sockets.PhysicalTimeSocketDef(),
"Rec Stop": sockets.PhysicalTimeSocketDef(
'Time Domain': {
'Rec Start': sockets.PhysicalTimeSocketDef(),
'Rec Stop': sockets.PhysicalTimeSocketDef(
default_value=200 * spux.fs
),
"Samples/Time": sockets.IntegerNumberSocketDef(
'Samples/Time': sockets.IntegerNumberSocketDef(
default_value=100,
),
},
}
output_sockets = {
"Monitor": sockets.MaxwellMonitorSocketDef(),
'Monitor': sockets.MaxwellMonitorSocketDef(),
}
managed_obj_defs = {
"monitor_box": ct.schemas.ManagedObjDef(
'monitor_box': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLObject(name),
name_prefix="",
name_prefix='',
)
}
@ -76,29 +77,36 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
# - Output Sockets
####################
@base.computes_output_socket(
"Monitor",
'Monitor',
input_sockets={
"Rec Start", "Rec Stop", "Center", "Size", "Samples/Space",
"Samples/Time", "Freqs",
'Rec Start',
'Rec Stop',
'Center',
'Size',
'Samples/Space',
'Samples/Time',
'Freqs',
},
props={"active_socket_set", "sim_node_name"}
props={'active_socket_set', 'sim_node_name'},
)
def compute_monitor(self, input_sockets: dict, props: dict) -> td.FieldTimeMonitor:
_center = input_sockets["Center"]
_size = input_sockets["Size"]
_samples_space = input_sockets["Samples/Space"]
def compute_monitor(
self, input_sockets: dict, props: dict
) -> td.FieldTimeMonitor:
_center = input_sockets['Center']
_size = input_sockets['Size']
_samples_space = input_sockets['Samples/Space']
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
size = tuple(spu.convert_to(_size, spu.um) / spu.um)
samples_space = tuple(_samples_space)
if props["active_socket_set"] == "Freq Domain":
freqs = input_sockets["Freqs"]
if props['active_socket_set'] == 'Freq Domain':
freqs = input_sockets['Freqs']
return td.FieldMonitor(
center=center,
size=size,
name=props["sim_node_name"],
name=props['sim_node_name'],
interval_space=samples_space,
freqs=[
float(spu.convert_to(freq, spu.hertz) / spu.hertz)
@ -106,9 +114,9 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
],
)
else: ## Time Domain
_rec_start = input_sockets["Rec Start"]
_rec_stop = input_sockets["Rec Stop"]
samples_time = input_sockets["Samples/Time"]
_rec_start = input_sockets['Rec Start']
_rec_stop = input_sockets['Rec Stop']
samples_time = input_sockets['Samples/Time']
rec_start = spu.convert_to(_rec_start, spu.second) / spu.second
rec_stop = spu.convert_to(_rec_stop, spu.second) / spu.second
@ -116,7 +124,7 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
return td.FieldTimeMonitor(
center=center,
size=size,
name=props["sim_node_name"],
name=props['sim_node_name'],
start=rec_start,
stop=rec_stop,
interval=samples_time,
@ -127,70 +135,65 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
# - Preview - Changes to Input Sockets
####################
@base.on_value_changed(
socket_name={"Center", "Size"},
input_sockets={"Center", "Size"},
managed_objs={"monitor_box"},
socket_name={'Center', 'Size'},
input_sockets={'Center', 'Size'},
managed_objs={'monitor_box'},
)
def on_value_changed__center_size(
self,
input_sockets: dict,
managed_objs: dict[str, ct.schemas.ManagedObj],
):
_center = input_sockets["Center"]
center = tuple([
float(el)
for el in spu.convert_to(_center, spu.um) / spu.um
])
_center = input_sockets['Center']
center = tuple(
[float(el) for el in spu.convert_to(_center, spu.um) / spu.um]
)
_size = input_sockets["Size"]
size = tuple([
float(el)
for el in spu.convert_to(_size, spu.um) / spu.um
])
_size = input_sockets['Size']
size = tuple(
[float(el) for el in spu.convert_to(_size, spu.um) / spu.um]
)
## TODO: Preview unit system?? Presume um for now
# Retrieve Hard-Coded GeoNodes and Analyze Input
geo_nodes = bpy.data.node_groups[GEONODES_MONITOR_BOX]
geonodes_interface = analyze_geonodes.interface(
geo_nodes, direc="INPUT"
geo_nodes, direc='INPUT'
)
# Sync Modifier Inputs
managed_objs["monitor_box"].sync_geonodes_modifier(
managed_objs['monitor_box'].sync_geonodes_modifier(
geonodes_node_group=geo_nodes,
geonodes_identifier_to_value={
geonodes_interface["Size"].identifier: size,
geonodes_interface['Size'].identifier: size,
## TODO: Use 'bl_socket_map.value_to_bl`!
## - This accounts for auto-conversion, unit systems, etc. .
## - We could keep it in the node base class...
## - ...But it needs aligning with Blender, too. Hmm.
}
},
)
# Sync Object Position
managed_objs["monitor_box"].bl_object("MESH").location = center
managed_objs['monitor_box'].bl_object('MESH').location = center
####################
# - Preview - Show Preview
####################
@base.on_show_preview(
managed_objs={"monitor_box"},
managed_objs={'monitor_box'},
)
def on_show_preview(
self,
managed_objs: dict[str, ct.schemas.ManagedObj],
):
managed_objs["monitor_box"].show_preview("MESH")
managed_objs['monitor_box'].show_preview('MESH')
self.on_value_changed__center_size()
####################
# - Blender Registration
####################
BL_REGISTER = [
EHFieldMonitorNode,
]
BL_NODES = {
ct.NodeType.EHFieldMonitor: (
ct.NodeCategory.MAXWELLSIM_MONITORS
)
}
BL_NODES = {ct.NodeType.EHFieldMonitor: (ct.NodeCategory.MAXWELLSIM_MONITORS)}

View File

@ -3,4 +3,3 @@
####################
BL_REGISTER = []
BL_NODES = {}

View File

@ -15,48 +15,49 @@ from ... import sockets
from ... import managed_objs
from .. import base
GEONODES_MONITOR_BOX = "monitor_flux_box"
GEONODES_MONITOR_BOX = 'monitor_flux_box'
class FieldPowerFluxMonitorNode(base.MaxwellSimNode):
node_type = ct.NodeType.FieldPowerFluxMonitor
bl_label = "Field Power Flux Monitor"
bl_label = 'Field Power Flux Monitor'
use_sim_node_name = True
####################
# - Sockets
####################
input_sockets = {
"Center": sockets.PhysicalPoint3DSocketDef(),
"Size": sockets.PhysicalSize3DSocketDef(),
"Samples/Space": sockets.Integer3DVectorSocketDef(
'Center': sockets.PhysicalPoint3DSocketDef(),
'Size': sockets.PhysicalSize3DSocketDef(),
'Samples/Space': sockets.Integer3DVectorSocketDef(
default_value=sp.Matrix([10, 10, 10])
),
"Direction": sockets.BoolSocketDef(),
'Direction': sockets.BoolSocketDef(),
}
input_socket_sets = {
"Freq Domain": {
"Freqs": sockets.PhysicalFreqSocketDef(
'Freq Domain': {
'Freqs': sockets.PhysicalFreqSocketDef(
is_list=True,
),
},
"Time Domain": {
"Rec Start": sockets.PhysicalTimeSocketDef(),
"Rec Stop": sockets.PhysicalTimeSocketDef(
'Time Domain': {
'Rec Start': sockets.PhysicalTimeSocketDef(),
'Rec Stop': sockets.PhysicalTimeSocketDef(
default_value=200 * spux.fs
),
"Samples/Time": sockets.IntegerNumberSocketDef(
'Samples/Time': sockets.IntegerNumberSocketDef(
default_value=100,
),
},
}
output_sockets = {
"Monitor": sockets.MaxwellMonitorSocketDef(),
'Monitor': sockets.MaxwellMonitorSocketDef(),
}
managed_obj_defs = {
"monitor_box": ct.schemas.ManagedObjDef(
'monitor_box': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLObject(name),
name_prefix="",
name_prefix='',
)
}
@ -77,31 +78,39 @@ class FieldPowerFluxMonitorNode(base.MaxwellSimNode):
# - Output Sockets
####################
@base.computes_output_socket(
"Monitor",
'Monitor',
input_sockets={
"Rec Start", "Rec Stop", "Center", "Size", "Samples/Space",
"Samples/Time", "Freqs", "Direction",
'Rec Start',
'Rec Stop',
'Center',
'Size',
'Samples/Space',
'Samples/Time',
'Freqs',
'Direction',
},
props={"active_socket_set", "sim_node_name"}
props={'active_socket_set', 'sim_node_name'},
)
def compute_monitor(self, input_sockets: dict, props: dict) -> td.FieldTimeMonitor:
_center = input_sockets["Center"]
_size = input_sockets["Size"]
_samples_space = input_sockets["Samples/Space"]
def compute_monitor(
self, input_sockets: dict, props: dict
) -> td.FieldTimeMonitor:
_center = input_sockets['Center']
_size = input_sockets['Size']
_samples_space = input_sockets['Samples/Space']
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
size = tuple(spu.convert_to(_size, spu.um) / spu.um)
samples_space = tuple(_samples_space)
direction = "+" if input_sockets["Direction"] else "-"
direction = '+' if input_sockets['Direction'] else '-'
if props["active_socket_set"] == "Freq Domain":
freqs = input_sockets["Freqs"]
if props['active_socket_set'] == 'Freq Domain':
freqs = input_sockets['Freqs']
return td.FluxMonitor(
center=center,
size=size,
name=props["sim_node_name"],
name=props['sim_node_name'],
interval_space=samples_space,
freqs=[
float(spu.convert_to(freq, spu.hertz) / spu.hertz)
@ -110,9 +119,9 @@ class FieldPowerFluxMonitorNode(base.MaxwellSimNode):
normal_dir=direction,
)
else: ## Time Domain
_rec_start = input_sockets["Rec Start"]
_rec_stop = input_sockets["Rec Stop"]
samples_time = input_sockets["Samples/Time"]
_rec_start = input_sockets['Rec Start']
_rec_stop = input_sockets['Rec Stop']
samples_time = input_sockets['Samples/Time']
rec_start = spu.convert_to(_rec_start, spu.second) / spu.second
rec_stop = spu.convert_to(_rec_stop, spu.second) / spu.second
@ -120,7 +129,7 @@ class FieldPowerFluxMonitorNode(base.MaxwellSimNode):
return td.FieldTimeMonitor(
center=center,
size=size,
name=props["sim_node_name"],
name=props['sim_node_name'],
start=rec_start,
stop=rec_stop,
interval=samples_time,
@ -131,63 +140,64 @@ class FieldPowerFluxMonitorNode(base.MaxwellSimNode):
# - Preview - Changes to Input Sockets
####################
@base.on_value_changed(
socket_name={"Center", "Size"},
input_sockets={"Center", "Size", "Direction"},
managed_objs={"monitor_box"},
socket_name={'Center', 'Size'},
input_sockets={'Center', 'Size', 'Direction'},
managed_objs={'monitor_box'},
)
def on_value_changed__center_size(
self,
input_sockets: dict,
managed_objs: dict[str, ct.schemas.ManagedObj],
):
_center = input_sockets["Center"]
center = tuple([
float(el)
for el in spu.convert_to(_center, spu.um) / spu.um
])
_center = input_sockets['Center']
center = tuple(
[float(el) for el in spu.convert_to(_center, spu.um) / spu.um]
)
_size = input_sockets["Size"]
size = tuple([
float(el)
for el in spu.convert_to(_size, spu.um) / spu.um
])
_size = input_sockets['Size']
size = tuple(
[float(el) for el in spu.convert_to(_size, spu.um) / spu.um]
)
## TODO: Preview unit system?? Presume um for now
# Retrieve Hard-Coded GeoNodes and Analyze Input
geo_nodes = bpy.data.node_groups[GEONODES_MONITOR_BOX]
geonodes_interface = analyze_geonodes.interface(
geo_nodes, direc="INPUT"
geo_nodes, direc='INPUT'
)
# Sync Modifier Inputs
managed_objs["monitor_box"].sync_geonodes_modifier(
managed_objs['monitor_box'].sync_geonodes_modifier(
geonodes_node_group=geo_nodes,
geonodes_identifier_to_value={
geonodes_interface["Size"].identifier: size,
geonodes_interface["Direction"].identifier: input_sockets["Direction"],
geonodes_interface['Size'].identifier: size,
geonodes_interface['Direction'].identifier: input_sockets[
'Direction'
],
## TODO: Use 'bl_socket_map.value_to_bl`!
## - This accounts for auto-conversion, unit systems, etc. .
## - We could keep it in the node base class...
## - ...But it needs aligning with Blender, too. Hmm.
}
},
)
# Sync Object Position
managed_objs["monitor_box"].bl_object("MESH").location = center
managed_objs['monitor_box'].bl_object('MESH').location = center
####################
# - Preview - Show Preview
####################
@base.on_show_preview(
managed_objs={"monitor_box"},
managed_objs={'monitor_box'},
)
def on_show_preview(
self,
managed_objs: dict[str, ct.schemas.ManagedObj],
):
managed_objs["monitor_box"].show_preview("MESH")
managed_objs['monitor_box'].show_preview('MESH')
self.on_value_changed__center_size()
####################
# - Blender Registration
####################
@ -195,7 +205,5 @@ BL_REGISTER = [
FieldPowerFluxMonitorNode,
]
BL_NODES = {
ct.NodeType.FieldPowerFluxMonitor: (
ct.NodeCategory.MAXWELLSIM_MONITORS
)
ct.NodeType.FieldPowerFluxMonitor: (ct.NodeCategory.MAXWELLSIM_MONITORS)
}

View File

@ -11,11 +11,12 @@ from .... import contracts as ct
from .... import sockets
from ... import base
####################
# - Operators
####################
class JSONFileExporterSaveJSON(bpy.types.Operator):
bl_idname = "blender_maxwell.json_file_exporter_save_json"
bl_idname = 'blender_maxwell.json_file_exporter_save_json'
bl_label = "Save the JSON of what's linked into a JSONFileExporterNode."
@classmethod
@ -27,26 +28,27 @@ class JSONFileExporterSaveJSON(bpy.types.Operator):
node.export_data_as_json()
return {'FINISHED'}
####################
# - Node
####################
class JSONFileExporterNode(base.MaxwellSimNode):
node_type = ct.NodeType.JSONFileExporter
bl_label = "JSON File Exporter"
bl_label = 'JSON File Exporter'
# bl_icon = constants.ICON_SIM_INPUT
input_sockets = {
"Data": sockets.AnySocketDef(),
"JSON Path": sockets.FilePathSocketDef(
default_path=Path("simulation.json")
'Data': sockets.AnySocketDef(),
'JSON Path': sockets.FilePathSocketDef(
default_path=Path('simulation.json')
),
"JSON Indent": sockets.IntegerNumberSocketDef(
'JSON Indent': sockets.IntegerNumberSocketDef(
default_value=4,
),
}
output_sockets = {
"JSON String": sockets.StringSocketDef(),
'JSON String': sockets.StringSocketDef(),
}
####################
@ -57,31 +59,33 @@ class JSONFileExporterNode(base.MaxwellSimNode):
context: bpy.types.Context,
layout: bpy.types.UILayout,
) -> None:
layout.operator(JSONFileExporterSaveJSON.bl_idname, text="Save JSON")
layout.operator(JSONFileExporterSaveJSON.bl_idname, text='Save JSON')
####################
# - Methods
####################
def export_data_as_json(self) -> None:
if (json_str := self.compute_output("JSON String")):
if json_str := self.compute_output('JSON String'):
data_dict = json.loads(json_str)
with self._compute_input("JSON Path").open("w") as f:
indent = self._compute_input("JSON Indent")
with self._compute_input('JSON Path').open('w') as f:
indent = self._compute_input('JSON Indent')
json.dump(data_dict, f, ensure_ascii=False, indent=indent)
####################
# - Output Sockets
####################
@base.computes_output_socket(
"JSON String",
input_sockets={"Data"},
'JSON String',
input_sockets={'Data'},
)
def compute_json_string(self, input_sockets: dict[str, typ.Any]) -> str | None:
if not (data := input_sockets["Data"]):
def compute_json_string(
self, input_sockets: dict[str, typ.Any]
) -> str | None:
if not (data := input_sockets['Data']):
return None
# Tidy3D Objects: Call .json()
if hasattr(data, "json"):
if hasattr(data, 'json'):
return data.json()
# Pydantic Models: Call .model_dump_json()

View File

@ -16,25 +16,25 @@ from .... import contracts as ct
from .... import sockets
from ... import base
####################
# - Web Uploader / Loader / Runner / Releaser
####################
class UploadSimulation(bpy.types.Operator):
bl_idname = "blender_maxwell.nodes__upload_simulation"
bl_label = "Upload Tidy3D Simulation"
bl_description = "Upload the attached (locked) simulation, such that it is ready to run on the Tidy3D cloud"
bl_idname = 'blender_maxwell.nodes__upload_simulation'
bl_label = 'Upload Tidy3D Simulation'
bl_description = 'Upload the attached (locked) simulation, such that it is ready to run on the Tidy3D cloud'
@classmethod
def poll(cls, context):
return (
hasattr(context, "node")
and hasattr(context.node, "node_type")
hasattr(context, 'node')
and hasattr(context.node, 'node_type')
and context.node.node_type == ct.NodeType.Tidy3DWebExporter
and context.node.lock_tree
and tdcloud.IS_AUTHENTICATED
and not context.node.tracked_task_id
and context.node.inputs["FDTD Sim"].is_linked
and context.node.inputs['FDTD Sim'].is_linked
)
def execute(self, context):
@ -42,24 +42,27 @@ class UploadSimulation(bpy.types.Operator):
node.upload_sim()
return {'FINISHED'}
class RunSimulation(bpy.types.Operator):
bl_idname = "blender_maxwell.nodes__run_simulation"
bl_label = "Run Tracked Tidy3D Sim"
bl_description = "Run the currently tracked simulation task"
bl_idname = 'blender_maxwell.nodes__run_simulation'
bl_label = 'Run Tracked Tidy3D Sim'
bl_description = 'Run the currently tracked simulation task'
@classmethod
def poll(cls, context):
return (
hasattr(context, "node")
and hasattr(context.node, "node_type")
hasattr(context, 'node')
and hasattr(context.node, 'node_type')
and context.node.node_type == ct.NodeType.Tidy3DWebExporter
and tdcloud.IS_AUTHENTICATED
and context.node.tracked_task_id
and (task_info := tdcloud.TidyCloudTasks.task_info(
and (
task_info := tdcloud.TidyCloudTasks.task_info(
context.node.tracked_task_id
)) is not None
and task_info.status == "draft"
)
)
is not None
and task_info.status == 'draft'
)
def execute(self, context):
@ -67,18 +70,18 @@ class RunSimulation(bpy.types.Operator):
node.run_tracked_task()
return {'FINISHED'}
class ReloadTrackedTask(bpy.types.Operator):
bl_idname = "blender_maxwell.nodes__reload_tracked_task"
bl_label = "Reload Tracked Tidy3D Cloud Task"
bl_description = "Reload the currently tracked simulation task"
bl_idname = 'blender_maxwell.nodes__reload_tracked_task'
bl_label = 'Reload Tracked Tidy3D Cloud Task'
bl_description = 'Reload the currently tracked simulation task'
@classmethod
def poll(cls, context):
return (
hasattr(context, "node")
and hasattr(context.node, "node_type")
hasattr(context, 'node')
and hasattr(context.node, 'node_type')
and context.node.node_type == ct.NodeType.Tidy3DWebExporter
and tdcloud.IS_AUTHENTICATED
and context.node.tracked_task_id
)
@ -94,18 +97,18 @@ class ReloadTrackedTask(bpy.types.Operator):
cloud_task = tdcloud.TidyCloudTasks.update_task(cloud_task)
return {'FINISHED'}
class EstCostTrackedTask(bpy.types.Operator):
bl_idname = "blender_maxwell.nodes__est_cost_tracked_task"
bl_label = "Est Cost of Tracked Tidy3D Cloud Task"
bl_description = "Reload the currently tracked simulation task"
bl_idname = 'blender_maxwell.nodes__est_cost_tracked_task'
bl_label = 'Est Cost of Tracked Tidy3D Cloud Task'
bl_description = 'Reload the currently tracked simulation task'
@classmethod
def poll(cls, context):
return (
hasattr(context, "node")
and hasattr(context.node, "node_type")
hasattr(context, 'node')
and hasattr(context.node, 'node_type')
and context.node.node_type == ct.NodeType.Tidy3DWebExporter
and tdcloud.IS_AUTHENTICATED
and context.node.tracked_task_id
)
@ -113,47 +116,50 @@ class EstCostTrackedTask(bpy.types.Operator):
def execute(self, context):
node = context.node
if (
task_info := tdcloud.TidyCloudTasks.task_info(context.node.tracked_task_id)
task_info := tdcloud.TidyCloudTasks.task_info(
context.node.tracked_task_id
)
) is None:
msg = "Tried to estimate cost of tracked task, but it doesn't exist"
msg = (
"Tried to estimate cost of tracked task, but it doesn't exist"
)
raise RuntimeError(msg)
node.cache_est_cost = task_info.cost_est()
return {'FINISHED'}
class ReleaseTrackedTask(bpy.types.Operator):
bl_idname = "blender_maxwell.nodes__release_tracked_task"
bl_label = "Release Tracked Tidy3D Cloud Task"
bl_description = "Release the currently tracked simulation task"
bl_idname = 'blender_maxwell.nodes__release_tracked_task'
bl_label = 'Release Tracked Tidy3D Cloud Task'
bl_description = 'Release the currently tracked simulation task'
@classmethod
def poll(cls, context):
return (
hasattr(context, "node")
and hasattr(context.node, "node_type")
hasattr(context, 'node')
and hasattr(context.node, 'node_type')
and context.node.node_type == ct.NodeType.Tidy3DWebExporter
# and tdcloud.IS_AUTHENTICATED
and context.node.tracked_task_id
)
def execute(self, context):
node = context.node
node.tracked_task_id = ""
node.tracked_task_id = ''
return {'FINISHED'}
####################
# - Node
####################
class Tidy3DWebExporterNode(base.MaxwellSimNode):
node_type = ct.NodeType.Tidy3DWebExporter
bl_label = "Tidy3D Web Exporter"
bl_label = 'Tidy3D Web Exporter'
input_sockets = {
"FDTD Sim": sockets.MaxwellFDTDSimSocketDef(),
"Cloud Task": sockets.Tidy3DCloudTaskSocketDef(
'FDTD Sim': sockets.MaxwellFDTDSimSocketDef(),
'Cloud Task': sockets.Tidy3DCloudTaskSocketDef(
should_exist=False,
),
}
@ -162,27 +168,27 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
# - Properties
####################
lock_tree: bpy.props.BoolProperty(
name="Whether to lock the attached tree",
description="Whether or not to lock the attached tree",
name='Whether to lock the attached tree',
description='Whether or not to lock the attached tree',
default=False,
update=lambda self, context: self.sync_lock_tree(context),
)
tracked_task_id: bpy.props.StringProperty(
name="Tracked Task ID",
description="The currently tracked task ID",
default="",
name='Tracked Task ID',
description='The currently tracked task ID',
default='',
update=lambda self, context: self.sync_tracked_task_id(context),
)
# Cache
cache_total_monitor_data: bpy.props.FloatProperty(
name="(Cached) Total Monitor Data",
description="Required storage space by all monitors",
name='(Cached) Total Monitor Data',
description='Required storage space by all monitors',
default=0.0,
)
cache_est_cost: bpy.props.FloatProperty(
name="(Cached) Estimated Total Cost",
description="Est. Cost in FlexCompute units",
name='(Cached) Estimated Total Cost',
description='Est. Cost in FlexCompute units',
default=-1.0,
)
@ -191,16 +197,17 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
####################
def sync_lock_tree(self, context):
if self.lock_tree:
self.trigger_action("enable_lock")
self.trigger_action('enable_lock')
self.locked = False
for bl_socket in self.inputs:
if bl_socket.name == "FDTD Sim": continue
if bl_socket.name == 'FDTD Sim':
continue
bl_socket.locked = False
else:
self.trigger_action("disable_lock")
self.trigger_action('disable_lock')
self.sync_prop("lock_tree", context)
self.sync_prop('lock_tree', context)
def sync_tracked_task_id(self, context):
# Select Tracked Task
@ -209,44 +216,43 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
task_info = tdcloud.TidyCloudTasks.task_info(self.tracked_task_id)
self.loose_output_sockets = {
"Cloud Task": sockets.Tidy3DCloudTaskSocketDef(
'Cloud Task': sockets.Tidy3DCloudTaskSocketDef(
should_exist=True,
),
}
self.inputs["Cloud Task"].locked = True
self.inputs['Cloud Task'].locked = True
# Release Tracked Task
else:
self.cache_est_cost = -1.0
self.loose_output_sockets = {}
self.inputs["Cloud Task"].sync_prepare_new_task()
self.inputs["Cloud Task"].locked = False
self.inputs['Cloud Task'].sync_prepare_new_task()
self.inputs['Cloud Task'].locked = False
self.sync_prop("tracked_task_id", context)
self.sync_prop('tracked_task_id', context)
####################
# - Output Socket Callbacks
####################
def validate_sim(self):
if (sim := self._compute_input("FDTD Sim")) is None:
msg = "Tried to validate simulation, but none is attached"
if (sim := self._compute_input('FDTD Sim')) is None:
msg = 'Tried to validate simulation, but none is attached'
raise ValueError(msg)
sim.validate_pre_upload(source_required=True)
def upload_sim(self):
if (sim := self._compute_input("FDTD Sim")) is None:
msg = "Tried to upload simulation, but none is attached"
if (sim := self._compute_input('FDTD Sim')) is None:
msg = 'Tried to upload simulation, but none is attached'
raise ValueError(msg)
if (
(new_task := self._compute_input("Cloud Task")) is None
or isinstance(
new_task := self._compute_input('Cloud Task')
) is None or isinstance(
new_task,
tdcloud.CloudTask,
)
):
msg = "Tried to upload simulation to new task, but existing task was selected"
msg = 'Tried to upload simulation to new task, but existing task was selected'
raise ValueError(msg)
# Create Cloud Task
@ -261,7 +267,7 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
# Declare to Cloud Task that it Exists Now
## This will change the UI to not allow free-text input.
## If the socket is linked, this errors.
self.inputs["Cloud Task"].sync_created_new_task(cloud_task)
self.inputs['Cloud Task'].sync_created_new_task(cloud_task)
# Track the Newly Uploaded Task ID
self.tracked_task_id = cloud_task.task_id
@ -274,7 +280,9 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
raise RuntimeError(msg)
cloud_task.submit()
tdcloud.TidyCloudTasks.update_task(cloud_task) ## TODO: Check that status is actually immediately updated.
tdcloud.TidyCloudTasks.update_task(
cloud_task
) ## TODO: Check that status is actually immediately updated.
####################
# - UI
@ -284,93 +292,94 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
row = layout.row(align=True)
row.operator(
UploadSimulation.bl_idname,
text="Upload",
text='Upload',
)
tree_lock_icon = "LOCKED" if self.lock_tree else "UNLOCKED"
row.prop(self, "lock_tree", toggle=True, icon=tree_lock_icon, text="")
tree_lock_icon = 'LOCKED' if self.lock_tree else 'UNLOCKED'
row.prop(self, 'lock_tree', toggle=True, icon=tree_lock_icon, text='')
# Row: Run Sim Buttons
row = layout.row(align=True)
row.operator(
RunSimulation.bl_idname,
text="Run",
text='Run',
)
if self.tracked_task_id:
tree_lock_icon = "LOOP_BACK"
tree_lock_icon = 'LOOP_BACK'
row.operator(
ReleaseTrackedTask.bl_idname,
icon="LOOP_BACK",
text="",
icon='LOOP_BACK',
text='',
)
def draw_info(self, context, layout):
# Connection Info
auth_icon = "CHECKBOX_HLT" if tdcloud.IS_AUTHENTICATED else "CHECKBOX_DEHLT"
conn_icon = "CHECKBOX_HLT" if tdcloud.IS_ONLINE else "CHECKBOX_DEHLT"
auth_icon = (
'CHECKBOX_HLT' if tdcloud.IS_AUTHENTICATED else 'CHECKBOX_DEHLT'
)
conn_icon = 'CHECKBOX_HLT' if tdcloud.IS_ONLINE else 'CHECKBOX_DEHLT'
row = layout.row()
row.alignment = "CENTER"
row.label(text="Cloud Status")
row.alignment = 'CENTER'
row.label(text='Cloud Status')
box = layout.box()
split = box.split(factor=0.85)
## Split: Left Column
col = split.column(align=False)
col.label(text="Authed")
col.label(text="Connected")
col.label(text='Authed')
col.label(text='Connected')
## Split: Right Column
col = split.column(align=False)
col.label(icon=auth_icon)
col.label(icon=conn_icon)
# Simulation Info
if self.inputs["FDTD Sim"].is_linked:
if self.inputs['FDTD Sim'].is_linked:
row = layout.row()
row.alignment = "CENTER"
row.label(text="Sim Info")
row.alignment = 'CENTER'
row.label(text='Sim Info')
box = layout.box()
split = box.split(factor=0.4)
## Split: Left Column
col = split.column(align=False)
col.label(text="𝝨 Output")
col.label(text='𝝨 Output')
## Split: Right Column
col = split.column(align=False)
col.alignment = "RIGHT"
col.label(text=f"{self.cache_total_monitor_data / 1_000_000:.2f}MB")
col.alignment = 'RIGHT'
col.label(
text=f'{self.cache_total_monitor_data / 1_000_000:.2f}MB'
)
# Cloud Task Info
if self.tracked_task_id and tdcloud.IS_AUTHENTICATED:
task_info = tdcloud.TidyCloudTasks.task_info(
self.tracked_task_id
)
if task_info is None: return
task_info = tdcloud.TidyCloudTasks.task_info(self.tracked_task_id)
if task_info is None:
return
## Header
row = layout.row()
row.alignment = "CENTER"
row.label(text="Task Info")
row.alignment = 'CENTER'
row.label(text='Task Info')
## Progress Bar
row = layout.row(align=True)
row.progress(
factor=0.0,
type="BAR",
text=f"Status: {task_info.status.capitalize()}",
type='BAR',
text=f'Status: {task_info.status.capitalize()}',
)
row.operator(
ReloadTrackedTask.bl_idname,
text="",
icon="FILE_REFRESH",
text='',
icon='FILE_REFRESH',
)
row.operator(
EstCostTrackedTask.bl_idname,
text="",
icon="SORTTIME",
text='',
icon='SORTTIME',
)
## Information
@ -379,19 +388,27 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
## Split: Left Column
col = split.column(align=False)
col.label(text="Status")
col.label(text="Est. Cost")
col.label(text="Real Cost")
col.label(text='Status')
col.label(text='Est. Cost')
col.label(text='Real Cost')
## Split: Right Column
cost_est = f"{self.cache_est_cost:.2f}" if self.cache_est_cost >= 0 else "TBD"
cost_real = f"{task_info.cost_real:.2f}" if task_info.cost_real is not None else "TBD"
cost_est = (
f'{self.cache_est_cost:.2f}'
if self.cache_est_cost >= 0
else 'TBD'
)
cost_real = (
f'{task_info.cost_real:.2f}'
if task_info.cost_real is not None
else 'TBD'
)
col = split.column(align=False)
col.alignment = "RIGHT"
col.alignment = 'RIGHT'
col.label(text=task_info.status.capitalize())
col.label(text=f"{cost_est} creds")
col.label(text=f"{cost_real} creds")
col.label(text=f'{cost_est} creds')
col.label(text=f'{cost_real} creds')
# Connection Information
@ -399,13 +416,14 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
# - Output Methods
####################
@base.computes_output_socket(
"Cloud Task",
input_sockets={"Cloud Task"},
'Cloud Task',
input_sockets={'Cloud Task'},
)
def compute_cloud_task(self, input_sockets: dict) -> tdcloud.CloudTask | None:
def compute_cloud_task(
self, input_sockets: dict
) -> tdcloud.CloudTask | None:
if isinstance(
cloud_task := input_sockets["Cloud Task"],
tdcloud.CloudTask
cloud_task := input_sockets['Cloud Task'], tdcloud.CloudTask
):
return cloud_task
@ -415,11 +433,11 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
# - Output Methods
####################
@base.on_value_changed(
socket_name="FDTD Sim",
input_sockets={"FDTD Sim"},
socket_name='FDTD Sim',
input_sockets={'FDTD Sim'},
)
def on_value_changed__fdtd_sim(self, input_sockets):
if (sim := self._compute_input("FDTD Sim")) is None:
if (sim := self._compute_input('FDTD Sim')) is None:
self.cache_total_monitor_data = 0
return

View File

@ -15,8 +15,8 @@ from ...managed_objs import managed_bl_object
class ConsoleViewOperator(bpy.types.Operator):
bl_idname = "blender_maxwell.console_view_operator"
bl_label = "View Plots"
bl_idname = 'blender_maxwell.console_view_operator'
bl_label = 'View Plots'
@classmethod
def poll(cls, context):
@ -27,9 +27,10 @@ class ConsoleViewOperator(bpy.types.Operator):
node.print_data_to_console()
return {'FINISHED'}
class RefreshPlotViewOperator(bpy.types.Operator):
bl_idname = "blender_maxwell.refresh_plot_view_operator"
bl_label = "Refresh Plots"
bl_idname = 'blender_maxwell.refresh_plot_view_operator'
bl_label = 'Refresh Plots'
@classmethod
def poll(cls, context):
@ -37,35 +38,38 @@ class RefreshPlotViewOperator(bpy.types.Operator):
def execute(self, context):
node = context.node
node.trigger_action("value_changed", "Data")
node.trigger_action('value_changed', 'Data')
return {'FINISHED'}
####################
# - Node
####################
class ViewerNode(base.MaxwellSimNode):
node_type = ct.NodeType.Viewer
bl_label = "Viewer"
bl_label = 'Viewer'
input_sockets = {
"Data": sockets.AnySocketDef(),
'Data': sockets.AnySocketDef(),
}
####################
# - Properties
####################
auto_plot: bpy.props.BoolProperty(
name="Auto-Plot",
description="Whether to auto-plot anything plugged into the viewer node",
name='Auto-Plot',
description='Whether to auto-plot anything plugged into the viewer node',
default=False,
update=lambda self, context: self.sync_prop("auto_plot", context),
update=lambda self, context: self.sync_prop('auto_plot', context),
)
auto_3d_preview: bpy.props.BoolProperty(
name="Auto 3D Preview",
name='Auto 3D Preview',
description="Whether to auto-preview anything 3D, that's plugged into the viewer node",
default=False,
update=lambda self, context: self.sync_prop("auto_3d_preview", context),
update=lambda self, context: self.sync_prop(
'auto_3d_preview', context
),
)
####################
@ -76,34 +80,34 @@ class ViewerNode(base.MaxwellSimNode):
# Split LHS
col = split.column(align=False)
col.label(text="Console")
col.label(text="Plot")
col.label(text="3D")
col.label(text='Console')
col.label(text='Plot')
col.label(text='3D')
# Split RHS
col = split.column(align=False)
## Console Options
col.operator(ConsoleViewOperator.bl_idname, text="Print")
col.operator(ConsoleViewOperator.bl_idname, text='Print')
## Plot Options
row = col.row(align=True)
row.prop(self, "auto_plot", text="Plot", toggle=True)
row.prop(self, 'auto_plot', text='Plot', toggle=True)
row.operator(
RefreshPlotViewOperator.bl_idname,
text="",
icon="FILE_REFRESH",
text='',
icon='FILE_REFRESH',
)
## 3D Preview Options
row = col.row(align=True)
row.prop(self, "auto_3d_preview", text="3D Preview", toggle=True)
row.prop(self, 'auto_3d_preview', text='3D Preview', toggle=True)
####################
# - Methods
####################
def print_data_to_console(self):
if not (data := self._compute_input("Data")):
if not (data := self._compute_input('Data')):
return
if isinstance(data, sp.Basic):
@ -115,14 +119,14 @@ class ViewerNode(base.MaxwellSimNode):
# - Updates
####################
@base.on_value_changed(
socket_name="Data",
props={"auto_3d_preview"},
socket_name='Data',
props={'auto_3d_preview'},
)
def on_value_changed__data(self, props):
# Show Plot
## Don't have to un-show other plots.
if self.auto_plot:
self.trigger_action("show_plot")
self.trigger_action('show_plot')
# Remove Anything Previewed
preview_collection = managed_bl_object.bl_collection(
@ -133,12 +137,12 @@ class ViewerNode(base.MaxwellSimNode):
preview_collection.objects.unlink(bl_object)
# Preview Anything that Should be Previewed (maybe)
if props["auto_3d_preview"]:
self.trigger_action("show_preview")
if props['auto_3d_preview']:
self.trigger_action('show_preview')
@base.on_value_changed(
prop_name="auto_3d_preview",
props={"auto_3d_preview"},
prop_name='auto_3d_preview',
props={'auto_3d_preview'},
)
def on_value_changed__auto_3d_preview(self, props):
# Remove Anything Previewed
@ -150,8 +154,8 @@ class ViewerNode(base.MaxwellSimNode):
preview_collection.objects.unlink(bl_object)
# Preview Anything that Should be Previewed (maybe)
if props["auto_3d_preview"]:
self.trigger_action("show_preview")
if props['auto_3d_preview']:
self.trigger_action('show_preview')
####################
@ -162,8 +166,4 @@ BL_REGISTER = [
RefreshPlotViewOperator,
ViewerNode,
]
BL_NODES = {
ct.NodeType.Viewer: (
ct.NodeCategory.MAXWELLSIM_OUTPUTS
)
}
BL_NODES = {ct.NodeType.Viewer: (ct.NodeCategory.MAXWELLSIM_OUTPUTS)}

View File

@ -6,46 +6,45 @@ from ... import contracts as ct
from ... import sockets
from .. import base
class FDTDSimNode(base.MaxwellSimNode):
node_type = ct.NodeType.FDTDSim
bl_label = "FDTD Simulation"
bl_label = 'FDTD Simulation'
####################
# - Sockets
####################
input_sockets = {
"Domain": sockets.MaxwellSimDomainSocketDef(),
"BCs": sockets.MaxwellBoundCondsSocketDef(),
"Sources": sockets.MaxwellSourceSocketDef(
'Domain': sockets.MaxwellSimDomainSocketDef(),
'BCs': sockets.MaxwellBoundCondsSocketDef(),
'Sources': sockets.MaxwellSourceSocketDef(
is_list=True,
),
"Structures": sockets.MaxwellStructureSocketDef(
'Structures': sockets.MaxwellStructureSocketDef(
is_list=True,
),
"Monitors": sockets.MaxwellMonitorSocketDef(
'Monitors': sockets.MaxwellMonitorSocketDef(
is_list=True,
),
}
output_sockets = {
"FDTD Sim": sockets.MaxwellFDTDSimSocketDef(),
'FDTD Sim': sockets.MaxwellFDTDSimSocketDef(),
}
####################
# - Output Socket Computation
####################
@base.computes_output_socket(
"FDTD Sim",
'FDTD Sim',
kind=ct.DataFlowKind.Value,
input_sockets={
"Sources", "Structures", "Domain", "BCs", "Monitors"
},
input_sockets={'Sources', 'Structures', 'Domain', 'BCs', 'Monitors'},
)
def compute_fdtd_sim(self, input_sockets: dict) -> sp.Expr:
sim_domain = input_sockets["Domain"]
sources = input_sockets["Sources"]
structures = input_sockets["Structures"]
bounds = input_sockets["BCs"]
monitors = input_sockets["Monitors"]
sim_domain = input_sockets['Domain']
sources = input_sockets['Sources']
structures = input_sockets['Structures']
bounds = input_sockets['BCs']
monitors = input_sockets['Monitors']
# if not isinstance(sources, list):
# sources = [sources]
@ -62,14 +61,11 @@ class FDTDSimNode(base.MaxwellSimNode):
boundary_spec=bounds,
)
####################
# - Blender Registration
####################
BL_REGISTER = [
FDTDSimNode,
]
BL_NODES = {
ct.NodeType.FDTDSim: (
ct.NodeCategory.MAXWELLSIM_SIMS
)
}
BL_NODES = {ct.NodeType.FDTDSim: (ct.NodeCategory.MAXWELLSIM_SIMS)}

View File

@ -9,30 +9,31 @@ from ... import sockets
from .. import base
from ... import managed_objs
GEONODES_DOMAIN_BOX = "simdomain_box"
GEONODES_DOMAIN_BOX = 'simdomain_box'
class SimDomainNode(base.MaxwellSimNode):
node_type = ct.NodeType.SimDomain
bl_label = "Sim Domain"
bl_label = 'Sim Domain'
input_sockets = {
"Duration": sockets.PhysicalTimeSocketDef(
'Duration': sockets.PhysicalTimeSocketDef(
default_value=5 * spu.ps,
default_unit=spu.ps,
),
"Center": sockets.PhysicalSize3DSocketDef(),
"Size": sockets.PhysicalSize3DSocketDef(),
"Grid": sockets.MaxwellSimGridSocketDef(),
"Ambient Medium": sockets.MaxwellMediumSocketDef(),
'Center': sockets.PhysicalSize3DSocketDef(),
'Size': sockets.PhysicalSize3DSocketDef(),
'Grid': sockets.MaxwellSimGridSocketDef(),
'Ambient Medium': sockets.MaxwellMediumSocketDef(),
}
output_sockets = {
"Domain": sockets.MaxwellSimDomainSocketDef(),
'Domain': sockets.MaxwellSimDomainSocketDef(),
}
managed_obj_defs = {
"domain_box": ct.schemas.ManagedObjDef(
'domain_box': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLObject(name),
name_prefix="",
name_prefix='',
)
}
@ -40,17 +41,19 @@ class SimDomainNode(base.MaxwellSimNode):
# - Callbacks
####################
@base.computes_output_socket(
"Domain",
input_sockets={"Duration", "Center", "Size", "Grid", "Ambient Medium"},
'Domain',
input_sockets={'Duration', 'Center', 'Size', 'Grid', 'Ambient Medium'},
)
def compute_sim_domain(self, input_sockets: dict) -> sp.Expr:
if all([
(_duration := input_sockets["Duration"]),
(_center := input_sockets["Center"]),
(_size := input_sockets["Size"]),
(grid := input_sockets["Grid"]),
(medium := input_sockets["Ambient Medium"]),
]):
if all(
[
(_duration := input_sockets['Duration']),
(_center := input_sockets['Center']),
(_size := input_sockets['Size']),
(grid := input_sockets['Grid']),
(medium := input_sockets['Ambient Medium']),
]
):
duration = spu.convert_to(_duration, spu.second) / spu.second
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
size = tuple(spu.convert_to(_size, spu.um) / spu.um)
@ -66,67 +69,62 @@ class SimDomainNode(base.MaxwellSimNode):
# - Preview
####################
@base.on_value_changed(
socket_name={"Center", "Size"},
input_sockets={"Center", "Size"},
managed_objs={"domain_box"},
socket_name={'Center', 'Size'},
input_sockets={'Center', 'Size'},
managed_objs={'domain_box'},
)
def on_value_changed__center_size(
self,
input_sockets: dict,
managed_objs: dict[str, ct.schemas.ManagedObj],
):
_center = input_sockets["Center"]
center = tuple([
float(el)
for el in spu.convert_to(_center, spu.um) / spu.um
])
_center = input_sockets['Center']
center = tuple(
[float(el) for el in spu.convert_to(_center, spu.um) / spu.um]
)
_size = input_sockets["Size"]
size = tuple([
float(el)
for el in spu.convert_to(_size, spu.um) / spu.um
])
_size = input_sockets['Size']
size = tuple(
[float(el) for el in spu.convert_to(_size, spu.um) / spu.um]
)
## TODO: Preview unit system?? Presume um for now
# Retrieve Hard-Coded GeoNodes and Analyze Input
geo_nodes = bpy.data.node_groups[GEONODES_DOMAIN_BOX]
geonodes_interface = analyze_geonodes.interface(
geo_nodes, direc="INPUT"
geo_nodes, direc='INPUT'
)
# Sync Modifier Inputs
managed_objs["domain_box"].sync_geonodes_modifier(
managed_objs['domain_box'].sync_geonodes_modifier(
geonodes_node_group=geo_nodes,
geonodes_identifier_to_value={
geonodes_interface["Size"].identifier: size,
geonodes_interface['Size'].identifier: size,
## TODO: Use 'bl_socket_map.value_to_bl`!
## - This accounts for auto-conversion, unit systems, etc. .
## - We could keep it in the node base class...
## - ...But it needs aligning with Blender, too. Hmm.
}
},
)
# Sync Object Position
managed_objs["domain_box"].bl_object("MESH").location = center
managed_objs['domain_box'].bl_object('MESH').location = center
@base.on_show_preview(
managed_objs={"domain_box"},
managed_objs={'domain_box'},
)
def on_show_preview(
self,
managed_objs: dict[str, ct.schemas.ManagedObj],
):
managed_objs["domain_box"].show_preview("MESH")
managed_objs['domain_box'].show_preview('MESH')
self.on_value_changed__center_size()
####################
# - Blender Registration
####################
BL_REGISTER = [
SimDomainNode,
]
BL_NODES = {
ct.NodeType.SimDomain: (
ct.NodeCategory.MAXWELLSIM_SIMS
)
}
BL_NODES = {ct.NodeType.SimDomain: (ct.NodeCategory.MAXWELLSIM_SIMS)}

View File

@ -3,4 +3,3 @@
####################
BL_REGISTER = []
BL_NODES = {}

View File

@ -3,4 +3,3 @@
####################
BL_REGISTER = []
BL_NODES = {}

View File

@ -3,4 +3,3 @@
####################
BL_REGISTER = []
BL_NODES = {}

View File

@ -3,4 +3,3 @@
####################
BL_REGISTER = []
BL_NODES = {}

View File

@ -1,6 +1,7 @@
from . import temporal_shapes
from . import point_dipole_source
# from . import uniform_current_source
from . import plane_wave_source
# from . import gaussian_beam_source

View File

@ -3,4 +3,3 @@
####################
BL_REGISTER = []
BL_NODES = {}

View File

@ -3,4 +3,3 @@
####################
BL_REGISTER = []
BL_NODES = {}

View File

@ -3,4 +3,3 @@
####################
BL_REGISTER = []
BL_NODES = {}

View File

@ -3,4 +3,3 @@
####################
BL_REGISTER = []
BL_NODES = {}

View File

@ -13,7 +13,8 @@ from ... import contracts as ct
from ... import sockets
from .. import base
GEONODES_PLANE_WAVE = "source_plane_wave"
GEONODES_PLANE_WAVE = 'source_plane_wave'
def convert_vector_to_spherical(
v: sp.MatrixBase,
@ -25,51 +26,49 @@ def convert_vector_to_spherical(
x, y, z = v
injection_axis = max(
('x', abs(x)),
('y', abs(y)),
('z', abs(z)),
key=lambda item: item[1]
('x', abs(x)), ('y', abs(y)), ('z', abs(z)), key=lambda item: item[1]
)[0]
## Select injection axis that minimizes 'theta'
if injection_axis == "x":
direction = "+" if x >= 0 else "-"
if injection_axis == 'x':
direction = '+' if x >= 0 else '-'
theta = sp.acos(x / sp.sqrt(x**2 + y**2 + z**2))
phi = sp.atan2(z, y)
elif injection_axis == "y":
direction = "+" if y >= 0 else "-"
elif injection_axis == 'y':
direction = '+' if y >= 0 else '-'
theta = sp.acos(y / sp.sqrt(x**2 + y**2 + z**2))
phi = sp.atan2(x, z)
else:
direction = "+" if z >= 0 else "-"
direction = '+' if z >= 0 else '-'
theta = sp.acos(z / sp.sqrt(x**2 + y**2 + z**2))
phi = sp.atan2(y, x)
return injection_axis, direction, theta, phi
class PlaneWaveSourceNode(base.MaxwellSimNode):
node_type = ct.NodeType.PlaneWaveSource
bl_label = "Plane Wave Source"
bl_label = 'Plane Wave Source'
####################
# - Sockets
####################
input_sockets = {
"Temporal Shape": sockets.MaxwellTemporalShapeSocketDef(),
"Center": sockets.PhysicalPoint3DSocketDef(),
"Direction": sockets.Real3DVectorSocketDef(
'Temporal Shape': sockets.MaxwellTemporalShapeSocketDef(),
'Center': sockets.PhysicalPoint3DSocketDef(),
'Direction': sockets.Real3DVectorSocketDef(
default_value=sp.Matrix([0, 0, -1])
),
"Pol Angle": sockets.PhysicalAngleSocketDef(),
'Pol Angle': sockets.PhysicalAngleSocketDef(),
}
output_sockets = {
"Source": sockets.MaxwellSourceSocketDef(),
'Source': sockets.MaxwellSourceSocketDef(),
}
managed_obj_defs = {
"plane_wave_source": ct.schemas.ManagedObjDef(
'plane_wave_source': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLObject(name),
name_prefix="",
name_prefix='',
)
}
@ -77,21 +76,23 @@ class PlaneWaveSourceNode(base.MaxwellSimNode):
# - Output Socket Computation
####################
@base.computes_output_socket(
"Source",
input_sockets={"Temporal Shape", "Center", "Direction", "Pol Angle"},
'Source',
input_sockets={'Temporal Shape', 'Center', 'Direction', 'Pol Angle'},
)
def compute_source(self, input_sockets: dict):
temporal_shape = input_sockets["Temporal Shape"]
_center = input_sockets["Center"]
direction = input_sockets["Direction"]
pol_angle = input_sockets["Pol Angle"]
temporal_shape = input_sockets['Temporal Shape']
_center = input_sockets['Center']
direction = input_sockets['Direction']
pol_angle = input_sockets['Pol Angle']
injection_axis, dir_sgn, theta, phi = convert_vector_to_spherical(direction)
injection_axis, dir_sgn, theta, phi = convert_vector_to_spherical(
direction
)
size = {
"x": (0, math.inf, math.inf),
"y": (math.inf, 0, math.inf),
"z": (math.inf, math.inf, 0),
'x': (0, math.inf, math.inf),
'y': (math.inf, 0, math.inf),
'z': (math.inf, math.inf, 0),
}[injection_axis]
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
@ -110,69 +111,60 @@ class PlaneWaveSourceNode(base.MaxwellSimNode):
# - Preview
####################
@base.on_value_changed(
socket_name={"Center", "Direction"},
input_sockets={"Center", "Direction"},
managed_objs={"plane_wave_source"},
socket_name={'Center', 'Direction'},
input_sockets={'Center', 'Direction'},
managed_objs={'plane_wave_source'},
)
def on_value_changed__center_direction(
self,
input_sockets: dict,
managed_objs: dict[str, ct.schemas.ManagedObj],
):
_center = input_sockets["Center"]
center = tuple([
float(el)
for el in spu.convert_to(_center, spu.um) / spu.um
])
_center = input_sockets['Center']
center = tuple(
[float(el) for el in spu.convert_to(_center, spu.um) / spu.um]
)
_direction = input_sockets["Direction"]
direction = tuple([
float(el)
for el in _direction
])
_direction = input_sockets['Direction']
direction = tuple([float(el) for el in _direction])
## TODO: Preview unit system?? Presume um for now
# Retrieve Hard-Coded GeoNodes and Analyze Input
geo_nodes = bpy.data.node_groups[GEONODES_PLANE_WAVE]
geonodes_interface = analyze_geonodes.interface(
geo_nodes, direc="INPUT"
geo_nodes, direc='INPUT'
)
# Sync Modifier Inputs
managed_objs["plane_wave_source"].sync_geonodes_modifier(
managed_objs['plane_wave_source'].sync_geonodes_modifier(
geonodes_node_group=geo_nodes,
geonodes_identifier_to_value={
geonodes_interface["Direction"].identifier: direction,
geonodes_interface['Direction'].identifier: direction,
## TODO: Use 'bl_socket_map.value_to_bl`!
## - This accounts for auto-conversion, unit systems, etc. .
## - We could keep it in the node base class...
## - ...But it needs aligning with Blender, too. Hmm.
}
},
)
# Sync Object Position
managed_objs["plane_wave_source"].bl_object("MESH").location = center
managed_objs['plane_wave_source'].bl_object('MESH').location = center
@base.on_show_preview(
managed_objs={"plane_wave_source"},
managed_objs={'plane_wave_source'},
)
def on_show_preview(
self,
managed_objs: dict[str, ct.schemas.ManagedObj],
):
managed_objs["plane_wave_source"].show_preview("MESH")
managed_objs['plane_wave_source'].show_preview('MESH')
self.on_value_changed__center_direction()
####################
# - Blender Registration
####################
BL_REGISTER = [
PlaneWaveSourceNode,
]
BL_NODES = {
ct.NodeType.PlaneWaveSource: (
ct.NodeCategory.MAXWELLSIM_SOURCES
)
}
BL_NODES = {ct.NodeType.PlaneWaveSource: (ct.NodeCategory.MAXWELLSIM_SOURCES)}

View File

@ -10,28 +10,29 @@ from ... import sockets
from .. import base
from ... import managed_objs
class PointDipoleSourceNode(base.MaxwellSimNode):
node_type = ct.NodeType.PointDipoleSource
bl_label = "Point Dipole Source"
bl_label = 'Point Dipole Source'
####################
# - Sockets
####################
input_sockets = {
"Temporal Shape": sockets.MaxwellTemporalShapeSocketDef(),
"Center": sockets.PhysicalPoint3DSocketDef(),
"Interpolate": sockets.BoolSocketDef(
'Temporal Shape': sockets.MaxwellTemporalShapeSocketDef(),
'Center': sockets.PhysicalPoint3DSocketDef(),
'Interpolate': sockets.BoolSocketDef(
default_value=True,
),
}
output_sockets = {
"Source": sockets.MaxwellSourceSocketDef(),
'Source': sockets.MaxwellSourceSocketDef(),
}
managed_obj_defs = {
"sphere_empty": ct.schemas.ManagedObjDef(
'sphere_empty': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLObject(name),
name_prefix="",
name_prefix='',
)
}
@ -39,15 +40,15 @@ class PointDipoleSourceNode(base.MaxwellSimNode):
# - Properties
####################
pol_axis: bpy.props.EnumProperty(
name="Polarization Axis",
description="Polarization Axis",
name='Polarization Axis',
description='Polarization Axis',
items=[
("EX", "Ex", "Electric field in x-dir"),
("EY", "Ey", "Electric field in y-dir"),
("EZ", "Ez", "Electric field in z-dir"),
('EX', 'Ex', 'Electric field in x-dir'),
('EY', 'Ey', 'Electric field in y-dir'),
('EZ', 'Ez', 'Electric field in z-dir'),
],
default="EX",
update=(lambda self, context: self.sync_prop("pol_axis", context)),
default='EX',
update=(lambda self, context: self.sync_prop('pol_axis', context)),
)
####################
@ -57,29 +58,31 @@ class PointDipoleSourceNode(base.MaxwellSimNode):
split = layout.split(factor=0.6)
col = split.column()
col.label(text="Pol Axis")
col.label(text='Pol Axis')
col = split.column()
col.prop(self, "pol_axis", text="")
col.prop(self, 'pol_axis', text='')
####################
# - Output Socket Computation
####################
@base.computes_output_socket(
"Source",
input_sockets={"Temporal Shape", "Center", "Interpolate"},
props={"pol_axis"},
'Source',
input_sockets={'Temporal Shape', 'Center', 'Interpolate'},
props={'pol_axis'},
)
def compute_source(self, input_sockets: dict[str, typ.Any], props: dict[str, typ.Any]) -> td.PointDipole:
def compute_source(
self, input_sockets: dict[str, typ.Any], props: dict[str, typ.Any]
) -> td.PointDipole:
pol_axis = {
"EX": "Ex",
"EY": "Ey",
"EZ": "Ez",
}[props["pol_axis"]]
'EX': 'Ex',
'EY': 'Ey',
'EZ': 'Ez',
}[props['pol_axis']]
temporal_shape = input_sockets["Temporal Shape"]
_center = input_sockets["Center"]
interpolate = input_sockets["Interpolate"]
temporal_shape = input_sockets['Temporal Shape']
_center = input_sockets['Center']
interpolate = input_sockets['Interpolate']
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
@ -95,36 +98,37 @@ class PointDipoleSourceNode(base.MaxwellSimNode):
# - Preview
####################
@base.on_value_changed(
socket_name="Center",
input_sockets={"Center"},
managed_objs={"sphere_empty"},
socket_name='Center',
input_sockets={'Center'},
managed_objs={'sphere_empty'},
)
def on_value_changed__center(
self,
input_sockets: dict,
managed_objs: dict[str, ct.schemas.ManagedObj],
):
_center = input_sockets["Center"]
_center = input_sockets['Center']
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
## TODO: Preview unit system?? Presume um for now
mobj = managed_objs["sphere_empty"]
bl_object = mobj.bl_object("EMPTY")
mobj = managed_objs['sphere_empty']
bl_object = mobj.bl_object('EMPTY')
bl_object.location = center # tuple([float(el) for el in center])
@base.on_show_preview(
managed_objs={"sphere_empty"},
managed_objs={'sphere_empty'},
)
def on_show_preview(
self,
managed_objs: dict[str, ct.schemas.ManagedObj],
):
managed_objs["sphere_empty"].show_preview(
"EMPTY",
empty_display_type="SPHERE",
managed_objs['sphere_empty'].show_preview(
'EMPTY',
empty_display_type='SPHERE',
)
managed_objs["sphere_empty"].bl_object("EMPTY").empty_display_size = 0.2
managed_objs['sphere_empty'].bl_object(
'EMPTY'
).empty_display_size = 0.2
####################
@ -134,7 +138,5 @@ BL_REGISTER = [
PointDipoleSourceNode,
]
BL_NODES = {
ct.NodeType.PointDipoleSource: (
ct.NodeCategory.MAXWELLSIM_SOURCES
)
ct.NodeType.PointDipoleSource: (ct.NodeCategory.MAXWELLSIM_SOURCES)
}

View File

@ -3,4 +3,3 @@
####################
BL_REGISTER = []
BL_NODES = {}

View File

@ -6,10 +6,11 @@ from .... import contracts
from .... import sockets
from ... import base
class ContinuousWaveTemporalShapeNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.ContinuousWaveTemporalShape
bl_label = "Continuous Wave Temporal Shape"
bl_label = 'Continuous Wave Temporal Shape'
# bl_icon = ...
####################
@ -19,35 +20,35 @@ class ContinuousWaveTemporalShapeNode(base.MaxwellSimTreeNode):
# "amplitude": sockets.RealNumberSocketDef(
# label="Temporal Shape",
# ), ## Should have a unit of some kind...
"phase": sockets.PhysicalAngleSocketDef(
label="Phase",
'phase': sockets.PhysicalAngleSocketDef(
label='Phase',
),
"freq_center": sockets.PhysicalFreqSocketDef(
label="Freq Center",
'freq_center': sockets.PhysicalFreqSocketDef(
label='Freq Center',
),
"freq_std": sockets.PhysicalFreqSocketDef(
label="Freq STD",
'freq_std': sockets.PhysicalFreqSocketDef(
label='Freq STD',
),
"time_delay_rel_ang_freq": sockets.RealNumberSocketDef(
label="Time Delay rel. Ang. Freq",
'time_delay_rel_ang_freq': sockets.RealNumberSocketDef(
label='Time Delay rel. Ang. Freq',
default_value=5.0,
),
}
output_sockets = {
"temporal_shape": sockets.MaxwellTemporalShapeSocketDef(
label="Temporal Shape",
'temporal_shape': sockets.MaxwellTemporalShapeSocketDef(
label='Temporal Shape',
),
}
####################
# - Output Socket Computation
####################
@base.computes_output_socket("temporal_shape")
@base.computes_output_socket('temporal_shape')
def compute_source(self: contracts.NodeTypeProtocol) -> td.PointDipole:
_phase = self.compute_input("phase")
_freq_center = self.compute_input("freq_center")
_freq_std = self.compute_input("freq_std")
time_delay_rel_ang_freq = self.compute_input("time_delay_rel_ang_freq")
_phase = self.compute_input('phase')
_freq_center = self.compute_input('freq_center')
_freq_std = self.compute_input('freq_std')
time_delay_rel_ang_freq = self.compute_input('time_delay_rel_ang_freq')
cheating_amplitude = 1.0
phase = spu.convert_to(_phase, spu.radian) / spu.radian
@ -63,7 +64,6 @@ class ContinuousWaveTemporalShapeNode(base.MaxwellSimTreeNode):
)
####################
# - Blender Registration
####################

View File

@ -13,9 +13,10 @@ from .... import sockets
from .... import managed_objs
from ... import base
class GaussianPulseTemporalShapeNode(base.MaxwellSimNode):
node_type = ct.NodeType.GaussianPulseTemporalShape
bl_label = "Gaussian Pulse Temporal Shape"
bl_label = 'Gaussian Pulse Temporal Shape'
# bl_icon = ...
####################
@ -25,28 +26,28 @@ class GaussianPulseTemporalShapeNode(base.MaxwellSimNode):
# "amplitude": sockets.RealNumberSocketDef(
# label="Temporal Shape",
# ), ## Should have a unit of some kind...
"Freq Center": sockets.PhysicalFreqSocketDef(
'Freq Center': sockets.PhysicalFreqSocketDef(
default_value=500 * spuex.terahertz,
),
"Freq Std.": sockets.PhysicalFreqSocketDef(
'Freq Std.': sockets.PhysicalFreqSocketDef(
default_value=200 * spuex.terahertz,
),
"Phase": sockets.PhysicalAngleSocketDef(),
"Delay rel. AngFreq": sockets.RealNumberSocketDef(
'Phase': sockets.PhysicalAngleSocketDef(),
'Delay rel. AngFreq': sockets.RealNumberSocketDef(
default_value=5.0,
),
"Remove DC": sockets.BoolSocketDef(
'Remove DC': sockets.BoolSocketDef(
default_value=True,
),
}
output_sockets = {
"Temporal Shape": sockets.MaxwellTemporalShapeSocketDef(),
'Temporal Shape': sockets.MaxwellTemporalShapeSocketDef(),
}
managed_obj_defs = {
"amp_time": ct.schemas.ManagedObjDef(
'amp_time': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLImage(name),
name_prefix="amp_time_",
name_prefix='amp_time_',
)
}
@ -54,51 +55,59 @@ class GaussianPulseTemporalShapeNode(base.MaxwellSimNode):
# - Properties
####################
plot_time_start: bpy.props.FloatProperty(
name="Plot Time Start (ps)",
description="The instance ID of a particular MaxwellSimNode instance, used to index caches",
name='Plot Time Start (ps)',
description='The instance ID of a particular MaxwellSimNode instance, used to index caches',
default=0.0,
update=(lambda self, context: self.sync_prop("plot_time_start", context)),
update=(
lambda self, context: self.sync_prop('plot_time_start', context)
),
)
plot_time_end: bpy.props.FloatProperty(
name="Plot Time End (ps)",
description="The instance ID of a particular MaxwellSimNode instance, used to index caches",
name='Plot Time End (ps)',
description='The instance ID of a particular MaxwellSimNode instance, used to index caches',
default=5,
update=(lambda self, context: self.sync_prop("plot_time_start", context)),
update=(
lambda self, context: self.sync_prop('plot_time_start', context)
),
)
####################
# - UI
####################
def draw_props(self, context, layout):
layout.label(text="Plot Settings")
layout.label(text='Plot Settings')
split = layout.split(factor=0.6)
col = split.column()
col.label(text="t-Range (ps)")
col.label(text='t-Range (ps)')
col = split.column()
col.prop(self, "plot_time_start", text="")
col.prop(self, "plot_time_end", text="")
col.prop(self, 'plot_time_start', text='')
col.prop(self, 'plot_time_end', text='')
####################
# - Output Socket Computation
####################
@base.computes_output_socket(
"Temporal Shape",
'Temporal Shape',
input_sockets={
"Freq Center", "Freq Std.", "Phase", "Delay rel. AngFreq",
"Remove DC",
}
'Freq Center',
'Freq Std.',
'Phase',
'Delay rel. AngFreq',
'Remove DC',
},
)
def compute_source(self, input_sockets: dict) -> td.GaussianPulse:
if (
(_freq_center := input_sockets["Freq Center"]) is None
or (_freq_std := input_sockets["Freq Std."]) is None
or (_phase := input_sockets["Phase"]) is None
or (time_delay_rel_ang_freq := input_sockets["Delay rel. AngFreq"]) is None
or (remove_dc_component := input_sockets["Remove DC"]) is None
(_freq_center := input_sockets['Freq Center']) is None
or (_freq_std := input_sockets['Freq Std.']) is None
or (_phase := input_sockets['Phase']) is None
or (time_delay_rel_ang_freq := input_sockets['Delay rel. AngFreq'])
is None
or (remove_dc_component := input_sockets['Remove DC']) is None
):
raise ValueError("Inputs not defined")
raise ValueError('Inputs not defined')
cheating_amplitude = 1.0
freq_center = spu.convert_to(_freq_center, spu.hertz) / spu.hertz
@ -115,9 +124,9 @@ class GaussianPulseTemporalShapeNode(base.MaxwellSimNode):
)
@base.on_show_plot(
managed_objs={"amp_time"},
props={"plot_time_start", "plot_time_end"},
output_sockets={"Temporal Shape"},
managed_objs={'amp_time'},
props={'plot_time_start', 'plot_time_end'},
output_sockets={'Temporal Shape'},
stop_propagation=True,
)
def on_show_plot(
@ -126,19 +135,18 @@ class GaussianPulseTemporalShapeNode(base.MaxwellSimNode):
output_sockets: dict[str, typ.Any],
props: dict[str, typ.Any],
):
temporal_shape = output_sockets["Temporal Shape"]
plot_time_start = props["plot_time_start"] * 1e-15
plot_time_end = props["plot_time_end"] * 1e-15
temporal_shape = output_sockets['Temporal Shape']
plot_time_start = props['plot_time_start'] * 1e-15
plot_time_end = props['plot_time_end'] * 1e-15
times = np.linspace(plot_time_start, plot_time_end)
managed_objs["amp_time"].mpl_plot_to_image(
managed_objs['amp_time'].mpl_plot_to_image(
lambda ax: temporal_shape.plot_spectrum(times, ax=ax),
bl_select=True,
)
####################
# - Blender Registration
####################

View File

@ -3,4 +3,3 @@
####################
BL_REGISTER = []
BL_NODES = {}

View File

@ -6,12 +6,10 @@ from . import primitives
BL_REGISTER = [
# *object_structure.BL_REGISTER,
*geonodes_structure.BL_REGISTER,
*primitives.BL_REGISTER,
]
BL_NODES = {
# **object_structure.BL_NODES,
**geonodes_structure.BL_NODES,
**primitives.BL_NODES,
}

View File

@ -16,26 +16,27 @@ from ... import sockets
from .. import base
from ... import managed_objs
class GeoNodesStructureNode(base.MaxwellSimNode):
node_type = ct.NodeType.GeoNodesStructure
bl_label = "GeoNodes Structure"
bl_label = 'GeoNodes Structure'
####################
# - Sockets
####################
input_sockets = {
"Unit System": sockets.PhysicalUnitSystemSocketDef(),
"Medium": sockets.MaxwellMediumSocketDef(),
"GeoNodes": sockets.BlenderGeoNodesSocketDef(),
'Unit System': sockets.PhysicalUnitSystemSocketDef(),
'Medium': sockets.MaxwellMediumSocketDef(),
'GeoNodes': sockets.BlenderGeoNodesSocketDef(),
}
output_sockets = {
"Structure": sockets.MaxwellStructureSocketDef(),
'Structure': sockets.MaxwellStructureSocketDef(),
}
managed_obj_defs = {
"geometry": ct.schemas.ManagedObjDef(
'geometry': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLObject(name),
name_prefix="",
name_prefix='',
)
}
@ -43,9 +44,9 @@ class GeoNodesStructureNode(base.MaxwellSimNode):
# - Output Socket Computation
####################
@base.computes_output_socket(
"Structure",
input_sockets={"Medium"},
managed_objs={"geometry"},
'Structure',
input_sockets={'Medium'},
managed_objs={'geometry'},
)
def compute_structure(
self,
@ -53,7 +54,7 @@ class GeoNodesStructureNode(base.MaxwellSimNode):
managed_objs: dict[str, ct.schemas.ManagedObj],
) -> td.Structure:
# Extract the Managed Blender Object
mobj = managed_objs["geometry"]
mobj = managed_objs['geometry']
# Extract Geometry as Arrays
geometry_as_arrays = mobj.mesh_as_arrays
@ -61,20 +62,19 @@ class GeoNodesStructureNode(base.MaxwellSimNode):
# Return TriMesh Structure
return td.Structure(
geometry=td.TriangleMesh.from_vertices_faces(
geometry_as_arrays["verts"],
geometry_as_arrays["faces"],
geometry_as_arrays['verts'],
geometry_as_arrays['faces'],
),
medium=input_sockets["Medium"],
medium=input_sockets['Medium'],
)
####################
# - Event Methods
####################
@base.on_value_changed(
socket_name="GeoNodes",
managed_objs={"geometry"},
input_sockets={"GeoNodes"},
socket_name='GeoNodes',
managed_objs={'geometry'},
input_sockets={'GeoNodes'},
)
def on_value_changed__geonodes(
self,
@ -85,15 +85,15 @@ class GeoNodesStructureNode(base.MaxwellSimNode):
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()
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"
geo_nodes, direc='INPUT'
)
# Set Loose Input Sockets
@ -116,9 +116,8 @@ class GeoNodesStructureNode(base.MaxwellSimNode):
@base.on_value_changed(
any_loose_input_socket=True,
managed_objs={"geometry"},
input_sockets={"Unit System", "GeoNodes"},
managed_objs={'geometry'},
input_sockets={'Unit System', 'GeoNodes'},
)
def on_value_changed__loose_inputs(
self,
@ -131,15 +130,16 @@ class GeoNodesStructureNode(base.MaxwellSimNode):
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"]
unit_system = input_sockets['Unit System']
mobj = managed_objs['geometry']
if not (geo_nodes := input_sockets["GeoNodes"]): return
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"
geo_nodes, direc='INPUT'
)
## TODO: Check that Loose Sockets matches the Interface
@ -159,14 +159,14 @@ class GeoNodesStructureNode(base.MaxwellSimNode):
for socket_name, bl_interface_socket in (
geonodes_interface.items()
)
}
},
)
####################
# - Event Methods
####################
@base.on_show_preview(
managed_objs={"geometry"},
managed_objs={'geometry'},
)
def on_show_preview(
self,
@ -176,7 +176,7 @@ class GeoNodesStructureNode(base.MaxwellSimNode):
Synchronizes the change to the actual GeoNodes modifier, so that the change is immediately visible.
"""
managed_objs["geometry"].show_preview("MESH")
managed_objs['geometry'].show_preview('MESH')
####################
@ -186,7 +186,5 @@ BL_REGISTER = [
GeoNodesStructureNode,
]
BL_NODES = {
ct.NodeType.GeoNodesStructure: (
ct.NodeCategory.MAXWELLSIM_STRUCTURES
)
ct.NodeType.GeoNodesStructure: (ct.NodeCategory.MAXWELLSIM_STRUCTURES)
}

View File

@ -10,35 +10,36 @@ from ... import contracts
from ... import sockets
from .. import base
class ObjectStructureNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.ObjectStructure
bl_label = "Object Structure"
bl_label = 'Object Structure'
# bl_icon = ...
####################
# - Sockets
####################
input_sockets = {
"medium": sockets.MaxwellMediumSocketDef(
label="Medium",
'medium': sockets.MaxwellMediumSocketDef(
label='Medium',
),
"object": sockets.BlenderObjectSocketDef(
label="Object",
'object': sockets.BlenderObjectSocketDef(
label='Object',
),
}
output_sockets = {
"structure": sockets.MaxwellStructureSocketDef(
label="Structure",
'structure': sockets.MaxwellStructureSocketDef(
label='Structure',
),
}
####################
# - Output Socket Computation
####################
@base.computes_output_socket("structure")
@base.computes_output_socket('structure')
def compute_structure(self: contracts.NodeTypeProtocol) -> td.Structure:
# Extract the Blender Object
bl_object = self.compute_input("object")
bl_object = self.compute_input('object')
# Ensure Updated Geometry
bpy.context.view_layer.update()
@ -51,27 +52,25 @@ class ObjectStructureNode(base.MaxwellSimTreeNode):
)
bmesh.ops.triangulate(bmesh_mesh, faces=bmesh_mesh.faces)
mesh = bpy.data.meshes.new(name="TriangulatedMesh")
mesh = bpy.data.meshes.new(name='TriangulatedMesh')
bmesh_mesh.to_mesh(mesh)
bmesh_mesh.free()
# Extract Vertices and Faces
vertices = np.array([vert.co for vert in mesh.vertices])
faces = np.array([
[vert for vert in poly.vertices]
for poly in mesh.polygons
])
faces = np.array(
[[vert for vert in poly.vertices] for poly in mesh.polygons]
)
# Remove Temporary Mesh
bpy.data.meshes.remove(mesh)
return td.Structure(
geometry=td.TriangleMesh.from_vertices_faces(vertices, faces),
medium=self.compute_input("medium")
medium=self.compute_input('medium'),
)
####################
# - Blender Registration
####################

View File

@ -1,4 +1,5 @@
from . import box_structure
# from . import cylinder_structure
from . import sphere_structure

View File

@ -10,30 +10,31 @@ from .... import sockets
from .... import managed_objs
from ... import base
GEONODES_STRUCTURE_BOX = "structure_box"
GEONODES_STRUCTURE_BOX = 'structure_box'
class BoxStructureNode(base.MaxwellSimNode):
node_type = ct.NodeType.BoxStructure
bl_label = "Box Structure"
bl_label = 'Box Structure'
####################
# - Sockets
####################
input_sockets = {
"Medium": sockets.MaxwellMediumSocketDef(),
"Center": sockets.PhysicalPoint3DSocketDef(),
"Size": sockets.PhysicalSize3DSocketDef(
'Medium': sockets.MaxwellMediumSocketDef(),
'Center': sockets.PhysicalPoint3DSocketDef(),
'Size': sockets.PhysicalSize3DSocketDef(
default_value=sp.Matrix([500, 500, 500]) * spu.nm
),
}
output_sockets = {
"Structure": sockets.MaxwellStructureSocketDef(),
'Structure': sockets.MaxwellStructureSocketDef(),
}
managed_obj_defs = {
"structure_box": ct.schemas.ManagedObjDef(
'structure_box': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLObject(name),
name_prefix="",
name_prefix='',
)
}
@ -41,13 +42,13 @@ class BoxStructureNode(base.MaxwellSimNode):
# - Output Socket Computation
####################
@base.computes_output_socket(
"Structure",
input_sockets={"Medium", "Center", "Size"},
'Structure',
input_sockets={'Medium', 'Center', 'Size'},
)
def compute_simulation(self, input_sockets: dict) -> td.Box:
medium = input_sockets["Medium"]
_center = input_sockets["Center"]
_size = input_sockets["Size"]
medium = input_sockets['Medium']
_center = input_sockets['Center']
_size = input_sockets['Size']
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
size = tuple(spu.convert_to(_size, spu.um) / spu.um)
@ -64,64 +65,61 @@ class BoxStructureNode(base.MaxwellSimNode):
# - Preview - Changes to Input Sockets
####################
@base.on_value_changed(
socket_name={"Center", "Size"},
input_sockets={"Center", "Size"},
managed_objs={"structure_box"},
socket_name={'Center', 'Size'},
input_sockets={'Center', 'Size'},
managed_objs={'structure_box'},
)
def on_value_changed__center_size(
self,
input_sockets: dict,
managed_objs: dict[str, ct.schemas.ManagedObj],
):
_center = input_sockets["Center"]
center = tuple([
float(el)
for el in spu.convert_to(_center, spu.um) / spu.um
])
_center = input_sockets['Center']
center = tuple(
[float(el) for el in spu.convert_to(_center, spu.um) / spu.um]
)
_size = input_sockets["Size"]
size = tuple([
float(el)
for el in spu.convert_to(_size, spu.um) / spu.um
])
_size = input_sockets['Size']
size = tuple(
[float(el) for el in spu.convert_to(_size, spu.um) / spu.um]
)
## TODO: Preview unit system?? Presume um for now
# Retrieve Hard-Coded GeoNodes and Analyze Input
geo_nodes = bpy.data.node_groups[GEONODES_STRUCTURE_BOX]
geonodes_interface = analyze_geonodes.interface(
geo_nodes, direc="INPUT"
geo_nodes, direc='INPUT'
)
# Sync Modifier Inputs
managed_objs["structure_box"].sync_geonodes_modifier(
managed_objs['structure_box'].sync_geonodes_modifier(
geonodes_node_group=geo_nodes,
geonodes_identifier_to_value={
geonodes_interface["Size"].identifier: size,
geonodes_interface['Size'].identifier: size,
## TODO: Use 'bl_socket_map.value_to_bl`!
## - This accounts for auto-conversion, unit systems, etc. .
## - We could keep it in the node base class...
## - ...But it needs aligning with Blender, too. Hmm.
}
},
)
# Sync Object Position
managed_objs["structure_box"].bl_object("MESH").location = center
managed_objs['structure_box'].bl_object('MESH').location = center
####################
# - Preview - Show Preview
####################
@base.on_show_preview(
managed_objs={"structure_box"},
managed_objs={'structure_box'},
)
def on_show_preview(
self,
managed_objs: dict[str, ct.schemas.ManagedObj],
):
managed_objs["structure_box"].show_preview("MESH")
managed_objs['structure_box'].show_preview('MESH')
self.on_value_changed__center_size()
####################
# - Blender Registration
####################

View File

@ -6,43 +6,44 @@ from .... import contracts
from .... import sockets
from ... import base
class CylinderStructureNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.CylinderStructure
bl_label = "Cylinder Structure"
bl_label = 'Cylinder Structure'
# bl_icon = ...
####################
# - Sockets
####################
input_sockets = {
"medium": sockets.MaxwellMediumSocketDef(
label="Medium",
'medium': sockets.MaxwellMediumSocketDef(
label='Medium',
),
"center": sockets.PhysicalPoint3DSocketDef(
label="Center",
'center': sockets.PhysicalPoint3DSocketDef(
label='Center',
),
"radius": sockets.PhysicalLengthSocketDef(
label="Radius",
'radius': sockets.PhysicalLengthSocketDef(
label='Radius',
),
"height": sockets.PhysicalLengthSocketDef(
label="Height",
'height': sockets.PhysicalLengthSocketDef(
label='Height',
),
}
output_sockets = {
"structure": sockets.MaxwellStructureSocketDef(
label="Structure",
'structure': sockets.MaxwellStructureSocketDef(
label='Structure',
),
}
####################
# - Output Socket Computation
####################
@base.computes_output_socket("structure")
@base.computes_output_socket('structure')
def compute_simulation(self: contracts.NodeTypeProtocol) -> td.Box:
medium = self.compute_input("medium")
_center = self.compute_input("center")
_radius = self.compute_input("radius")
_height = self.compute_input("height")
medium = self.compute_input('medium')
_center = self.compute_input('center')
_radius = self.compute_input('radius')
_height = self.compute_input('height')
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
radius = spu.convert_to(_radius, spu.um) / spu.um
@ -58,7 +59,6 @@ class CylinderStructureNode(base.MaxwellSimTreeNode):
)
####################
# - Blender Registration
####################

View File

@ -10,30 +10,31 @@ from .... import sockets
from .... import managed_objs
from ... import base
GEONODES_STRUCTURE_SPHERE = "structure_sphere"
GEONODES_STRUCTURE_SPHERE = 'structure_sphere'
class SphereStructureNode(base.MaxwellSimNode):
node_type = ct.NodeType.SphereStructure
bl_label = "Sphere Structure"
bl_label = 'Sphere Structure'
####################
# - Sockets
####################
input_sockets = {
"Center": sockets.PhysicalPoint3DSocketDef(),
"Radius": sockets.PhysicalLengthSocketDef(
'Center': sockets.PhysicalPoint3DSocketDef(),
'Radius': sockets.PhysicalLengthSocketDef(
default_value=150 * spu.nm,
),
"Medium": sockets.MaxwellMediumSocketDef(),
'Medium': sockets.MaxwellMediumSocketDef(),
}
output_sockets = {
"Structure": sockets.MaxwellStructureSocketDef(),
'Structure': sockets.MaxwellStructureSocketDef(),
}
managed_obj_defs = {
"structure_sphere": ct.schemas.ManagedObjDef(
'structure_sphere': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLObject(name),
name_prefix="",
name_prefix='',
)
}
@ -41,13 +42,13 @@ class SphereStructureNode(base.MaxwellSimNode):
# - Output Socket Computation
####################
@base.computes_output_socket(
"Structure",
input_sockets={"Center", "Radius", "Medium"},
'Structure',
input_sockets={'Center', 'Radius', 'Medium'},
)
def compute_structure(self, input_sockets: dict) -> td.Box:
medium = input_sockets["Medium"]
_center = input_sockets["Center"]
_radius = input_sockets["Radius"]
medium = input_sockets['Medium']
_center = input_sockets['Center']
_radius = input_sockets['Radius']
center = tuple(spu.convert_to(_center, spu.um) / spu.um)
radius = spu.convert_to(_radius, spu.um) / spu.um
@ -64,61 +65,59 @@ class SphereStructureNode(base.MaxwellSimNode):
# - Preview - Changes to Input Sockets
####################
@base.on_value_changed(
socket_name={"Center", "Radius"},
input_sockets={"Center", "Radius"},
managed_objs={"structure_sphere"},
socket_name={'Center', 'Radius'},
input_sockets={'Center', 'Radius'},
managed_objs={'structure_sphere'},
)
def on_value_changed__center_radius(
self,
input_sockets: dict,
managed_objs: dict[str, ct.schemas.ManagedObj],
):
_center = input_sockets["Center"]
center = tuple([
float(el)
for el in spu.convert_to(_center, spu.um) / spu.um
])
_center = input_sockets['Center']
center = tuple(
[float(el) for el in spu.convert_to(_center, spu.um) / spu.um]
)
_radius = input_sockets["Radius"]
_radius = input_sockets['Radius']
radius = float(spu.convert_to(_radius, spu.um) / spu.um)
## TODO: Preview unit system?? Presume um for now
# Retrieve Hard-Coded GeoNodes and Analyze Input
geo_nodes = bpy.data.node_groups[GEONODES_STRUCTURE_SPHERE]
geonodes_interface = analyze_geonodes.interface(
geo_nodes, direc="INPUT"
geo_nodes, direc='INPUT'
)
# Sync Modifier Inputs
managed_objs["structure_sphere"].sync_geonodes_modifier(
managed_objs['structure_sphere'].sync_geonodes_modifier(
geonodes_node_group=geo_nodes,
geonodes_identifier_to_value={
geonodes_interface["Radius"].identifier: radius,
geonodes_interface['Radius'].identifier: radius,
## TODO: Use 'bl_socket_map.value_to_bl`!
## - This accounts for auto-conversion, unit systems, etc. .
## - We could keep it in the node base class...
## - ...But it needs aligning with Blender, too. Hmm.
}
},
)
# Sync Object Position
managed_objs["structure_sphere"].bl_object("MESH").location = center
managed_objs['structure_sphere'].bl_object('MESH').location = center
####################
# - Preview - Show Preview
####################
@base.on_show_preview(
managed_objs={"structure_sphere"},
managed_objs={'structure_sphere'},
)
def on_show_preview(
self,
managed_objs: dict[str, ct.schemas.ManagedObj],
):
managed_objs["structure_sphere"].show_preview("MESH")
managed_objs['structure_sphere'].show_preview('MESH')
self.on_value_changed__center_radius()
####################
# - Blender Registration
####################

View File

@ -3,4 +3,3 @@
####################
BL_REGISTER = []
BL_NODES = {}

View File

@ -4,13 +4,11 @@ from . import combine
BL_REGISTER = [
# *math.BL_REGISTER,
*combine.BL_REGISTER,
# *separate.BL_REGISTER,
]
BL_NODES = {
# **math.BL_NODES,
**combine.BL_NODES,
# **separate.BL_NODES,
}

View File

@ -10,21 +10,21 @@ from .. import base
MAX_AMOUNT = 20
class CombineNode(base.MaxwellSimNode):
node_type = ct.NodeType.Combine
bl_label = "Combine"
bl_label = 'Combine'
# bl_icon = ...
####################
# - Sockets
####################
input_socket_sets = {
"Maxwell Sources": {},
"Maxwell Structures": {},
"Maxwell Monitors": {},
"Real 3D Vector": {
f"x_{i}": sockets.RealNumberSocketDef()
for i in range(3)
'Maxwell Sources': {},
'Maxwell Structures': {},
'Maxwell Monitors': {},
'Real 3D Vector': {
f'x_{i}': sockets.RealNumberSocketDef() for i in range(3)
},
# "Point 3D": {
# axis: sockets.PhysicalLengthSocketDef()
@ -43,23 +43,23 @@ class CombineNode(base.MaxwellSimNode):
# },
}
output_socket_sets = {
"Maxwell Sources": {
"Sources": sockets.MaxwellSourceSocketDef(
'Maxwell Sources': {
'Sources': sockets.MaxwellSourceSocketDef(
is_list=True,
),
},
"Maxwell Structures": {
"Structures": sockets.MaxwellStructureSocketDef(
'Maxwell Structures': {
'Structures': sockets.MaxwellStructureSocketDef(
is_list=True,
),
},
"Maxwell Monitors": {
"Monitors": sockets.MaxwellMonitorSocketDef(
'Maxwell Monitors': {
'Monitors': sockets.MaxwellMonitorSocketDef(
is_list=True,
),
},
"Real 3D Vector": {
"Real 3D Vector": sockets.Real3DVectorSocketDef(),
'Real 3D Vector': {
'Real 3D Vector': sockets.Real3DVectorSocketDef(),
},
# "Point 3D": {
# "3D Point": sockets.PhysicalPoint3DSocketDef(),
@ -70,92 +70,83 @@ class CombineNode(base.MaxwellSimNode):
}
amount: bpy.props.IntProperty(
name="# Objects to Combine",
description="Amount of Objects to Combine",
name='# Objects to Combine',
description='Amount of Objects to Combine',
default=1,
min=1,
max=MAX_AMOUNT,
update=lambda self, context: self.sync_prop("amount", context)
update=lambda self, context: self.sync_prop('amount', context),
)
####################
# - Draw
####################
def draw_props(self, context, layout):
layout.prop(self, "amount", text="#")
layout.prop(self, 'amount', text='#')
####################
# - Output Socket Computation
####################
@base.computes_output_socket(
"Real 3D Vector",
input_sockets={"x_0", "x_1", "x_2"}
'Real 3D Vector', input_sockets={'x_0', 'x_1', 'x_2'}
)
def compute_real_3d_vector(self, input_sockets) -> sp.Expr:
return sp.Matrix([input_sockets[f"x_{i}"] for i in range(3)])
return sp.Matrix([input_sockets[f'x_{i}'] for i in range(3)])
@base.computes_output_socket(
"Sources",
input_sockets={f"Source #{i}" for i in range(MAX_AMOUNT)},
props={"amount"},
'Sources',
input_sockets={f'Source #{i}' for i in range(MAX_AMOUNT)},
props={'amount'},
)
def compute_sources(self, input_sockets, props) -> sp.Expr:
return [
input_sockets[f"Source #{i}"]
for i in range(props["amount"])
]
return [input_sockets[f'Source #{i}'] for i in range(props['amount'])]
@base.computes_output_socket(
"Structures",
input_sockets={f"Structure #{i}" for i in range(MAX_AMOUNT)},
props={"amount"},
'Structures',
input_sockets={f'Structure #{i}' for i in range(MAX_AMOUNT)},
props={'amount'},
)
def compute_structures(self, input_sockets, props) -> sp.Expr:
return [
input_sockets[f"Structure #{i}"]
for i in range(props["amount"])
input_sockets[f'Structure #{i}'] for i in range(props['amount'])
]
@base.computes_output_socket(
"Monitors",
input_sockets={f"Monitor #{i}" for i in range(MAX_AMOUNT)},
props={"amount"},
'Monitors',
input_sockets={f'Monitor #{i}' for i in range(MAX_AMOUNT)},
props={'amount'},
)
def compute_monitors(self, input_sockets, props) -> sp.Expr:
return [
input_sockets[f"Monitor #{i}"]
for i in range(props["amount"])
]
return [input_sockets[f'Monitor #{i}'] for i in range(props['amount'])]
####################
# - Input Socket Compilation
####################
@base.on_value_changed(
prop_name="active_socket_set",
props={"active_socket_set", "amount"},
prop_name='active_socket_set',
props={'active_socket_set', 'amount'},
)
def on_value_changed__active_socket_set(self, props):
if props["active_socket_set"] == "Maxwell Sources":
if props['active_socket_set'] == 'Maxwell Sources':
self.loose_input_sockets = {
f"Source #{i}": sockets.MaxwellSourceSocketDef()
for i in range(props["amount"])
f'Source #{i}': sockets.MaxwellSourceSocketDef()
for i in range(props['amount'])
}
elif props["active_socket_set"] == "Maxwell Structures":
elif props['active_socket_set'] == 'Maxwell Structures':
self.loose_input_sockets = {
f"Structure #{i}": sockets.MaxwellStructureSocketDef()
for i in range(props["amount"])
f'Structure #{i}': sockets.MaxwellStructureSocketDef()
for i in range(props['amount'])
}
elif props["active_socket_set"] == "Maxwell Monitors":
elif props['active_socket_set'] == 'Maxwell Monitors':
self.loose_input_sockets = {
f"Monitor #{i}": sockets.MaxwellMonitorSocketDef()
for i in range(props["amount"])
f'Monitor #{i}': sockets.MaxwellMonitorSocketDef()
for i in range(props['amount'])
}
else:
self.loose_input_sockets = {}
@base.on_value_changed(
prop_name="amount",
prop_name='amount',
)
def on_value_changed__amount(self):
self.on_value_changed__active_socket_set()
@ -171,8 +162,4 @@ class CombineNode(base.MaxwellSimNode):
BL_REGISTER = [
CombineNode,
]
BL_NODES = {
ct.NodeType.Combine: (
ct.NodeCategory.MAXWELLSIM_UTILITIES
)
}
BL_NODES = {ct.NodeType.Combine: (ct.NodeCategory.MAXWELLSIM_UTILITIES)}

View File

@ -7,9 +7,10 @@ from .... import contracts
from .... import sockets
from ... import base
class WaveConverterNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.WaveConverter
bl_label = "Wave Converter"
bl_label = 'Wave Converter'
# bl_icon = ...
####################
@ -17,27 +18,27 @@ class WaveConverterNode(base.MaxwellSimTreeNode):
####################
input_sockets = {}
input_socket_sets = {
"freq_to_vacwl": {
"freq": sockets.PhysicalFreqSocketDef(
label="Freq",
'freq_to_vacwl': {
'freq': sockets.PhysicalFreqSocketDef(
label='Freq',
),
},
"vacwl_to_freq": {
"vacwl": sockets.PhysicalVacWLSocketDef(
label="Vac WL",
'vacwl_to_freq': {
'vacwl': sockets.PhysicalVacWLSocketDef(
label='Vac WL',
),
},
}
output_sockets = {}
output_socket_sets = {
"freq_to_vacwl": {
"vacwl": sockets.PhysicalVacWLSocketDef(
label="Vac WL",
'freq_to_vacwl': {
'vacwl': sockets.PhysicalVacWLSocketDef(
label='Vac WL',
),
},
"vacwl_to_freq": {
"freq": sockets.PhysicalFreqSocketDef(
label="Freq",
'vacwl_to_freq': {
'freq': sockets.PhysicalFreqSocketDef(
label='Freq',
),
},
}
@ -45,22 +46,26 @@ class WaveConverterNode(base.MaxwellSimTreeNode):
####################
# - Output Socket Computation
####################
@base.computes_output_socket("freq")
@base.computes_output_socket('freq')
def compute_freq(self: contracts.NodeTypeProtocol) -> sp.Expr:
vac_speed_of_light = sc.constants.speed_of_light * spu.meter/spu.second
vac_speed_of_light = (
sc.constants.speed_of_light * spu.meter / spu.second
)
vacwl = self.compute_input("vacwl")
vacwl = self.compute_input('vacwl')
return spu.convert_to(
vac_speed_of_light / vacwl,
spu.hertz,
)
@base.computes_output_socket("vacwl")
@base.computes_output_socket('vacwl')
def compute_vacwl(self: contracts.NodeTypeProtocol) -> sp.Expr:
vac_speed_of_light = sc.constants.speed_of_light * spu.meter/spu.second
vac_speed_of_light = (
sc.constants.speed_of_light * spu.meter / spu.second
)
freq = self.compute_input("freq")
freq = self.compute_input('freq')
return spu.convert_to(
vac_speed_of_light / freq,
@ -68,7 +73,6 @@ class WaveConverterNode(base.MaxwellSimTreeNode):
)
####################
# - Blender Registration
####################

View File

@ -16,28 +16,27 @@ from ... import managed_objs
CACHE = {}
class FDTDSimDataVizNode(base.MaxwellSimNode):
node_type = ct.NodeType.FDTDSimDataViz
bl_label = "FDTD Sim Data Viz"
bl_label = 'FDTD Sim Data Viz'
####################
# - Sockets
####################
input_sockets = {
"FDTD Sim Data": sockets.MaxwellFDTDSimDataSocketDef(),
}
output_sockets= {
"Preview": sockets.AnySocketDef()
'FDTD Sim Data': sockets.MaxwellFDTDSimDataSocketDef(),
}
output_sockets = {'Preview': sockets.AnySocketDef()}
managed_obj_defs = {
"viz_plot": ct.schemas.ManagedObjDef(
'viz_plot': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLImage(name),
name_prefix="",
name_prefix='',
),
"viz_object": ct.schemas.ManagedObjDef(
'viz_object': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLObject(name),
name_prefix="",
name_prefix='',
),
}
@ -45,147 +44,168 @@ class FDTDSimDataVizNode(base.MaxwellSimNode):
# - Properties
####################
viz_monitor_name: bpy.props.EnumProperty(
name="Viz Monitor Name",
description="Monitor to visualize within the attached SimData",
name='Viz Monitor Name',
description='Monitor to visualize within the attached SimData',
items=lambda self, context: self.retrieve_monitors(context),
update=(lambda self, context: self.sync_viz_monitor_name(context)),
)
cache_viz_monitor_type: bpy.props.StringProperty(
name="Viz Monitor Type",
description="Type of the viz monitor",
default=""
name='Viz Monitor Type',
description='Type of the viz monitor',
default='',
)
# Field Monitor Type
field_viz_component: bpy.props.EnumProperty(
name="Field Component",
description="Field component to visualize",
name='Field Component',
description='Field component to visualize',
items=[
("E", "E", "Electric"),
('E', 'E', 'Electric'),
# ("H", "H", "Magnetic"),
# ("S", "S", "Poynting"),
("Ex", "Ex", "Ex"),
("Ey", "Ey", "Ey"),
("Ez", "Ez", "Ez"),
('Ex', 'Ex', 'Ex'),
('Ey', 'Ey', 'Ey'),
('Ez', 'Ez', 'Ez'),
# ("Hx", "Hx", "Hx"),
# ("Hy", "Hy", "Hy"),
# ("Hz", "Hz", "Hz"),
],
default="E",
update=lambda self, context: self.sync_prop("field_viz_component", context),
default='E',
update=lambda self, context: self.sync_prop(
'field_viz_component', context
),
)
field_viz_part: bpy.props.EnumProperty(
name="Field Part",
description="Field part to visualize",
name='Field Part',
description='Field part to visualize',
items=[
("real", "Real", "Electric"),
("imag", "Imaginary", "Imaginary"),
("abs", "Abs", "Abs"),
("abs^2", "Squared Abs", "Square Abs"),
("phase", "Phase", "Phase"),
('real', 'Real', 'Electric'),
('imag', 'Imaginary', 'Imaginary'),
('abs', 'Abs', 'Abs'),
('abs^2', 'Squared Abs', 'Square Abs'),
('phase', 'Phase', 'Phase'),
],
default="real",
update=lambda self, context: self.sync_prop("field_viz_part", context),
default='real',
update=lambda self, context: self.sync_prop('field_viz_part', context),
)
field_viz_scale: bpy.props.EnumProperty(
name="Field Scale",
description="Field scale to visualize in, Linear or Log",
name='Field Scale',
description='Field scale to visualize in, Linear or Log',
items=[
("lin", "Linear", "Linear Scale"),
("dB", "Log (dB)", "Logarithmic (dB) Scale"),
('lin', 'Linear', 'Linear Scale'),
('dB', 'Log (dB)', 'Logarithmic (dB) Scale'),
],
default="lin",
update=lambda self, context: self.sync_prop("field_viz_scale", context),
default='lin',
update=lambda self, context: self.sync_prop(
'field_viz_scale', context
),
)
field_viz_structure_visibility: bpy.props.FloatProperty(
name="Field Viz Plot: Structure Visibility",
description="Visibility of structes",
name='Field Viz Plot: Structure Visibility',
description='Visibility of structes',
default=0.2,
min=0.0,
max=1.0,
update=lambda self, context: self.sync_prop("field_viz_plot_fixed_f", context),
update=lambda self, context: self.sync_prop(
'field_viz_plot_fixed_f', context
),
)
field_viz_plot_fix_x: bpy.props.BoolProperty(
name="Field Viz Plot: Fix X",
description="Fix the x-coordinate on the plot",
name='Field Viz Plot: Fix X',
description='Fix the x-coordinate on the plot',
default=False,
update=lambda self, context: self.sync_prop("field_viz_plot_fix_x", context),
update=lambda self, context: self.sync_prop(
'field_viz_plot_fix_x', context
),
)
field_viz_plot_fix_y: bpy.props.BoolProperty(
name="Field Viz Plot: Fix Y",
description="Fix the y coordinate on the plot",
name='Field Viz Plot: Fix Y',
description='Fix the y coordinate on the plot',
default=False,
update=lambda self, context: self.sync_prop("field_viz_plot_fix_y", context),
update=lambda self, context: self.sync_prop(
'field_viz_plot_fix_y', context
),
)
field_viz_plot_fix_z: bpy.props.BoolProperty(
name="Field Viz Plot: Fix Z",
description="Fix the z coordinate on the plot",
name='Field Viz Plot: Fix Z',
description='Fix the z coordinate on the plot',
default=False,
update=lambda self, context: self.sync_prop("field_viz_plot_fix_z", context),
update=lambda self, context: self.sync_prop(
'field_viz_plot_fix_z', context
),
)
field_viz_plot_fix_f: bpy.props.BoolProperty(
name="Field Viz Plot: Fix Freq",
description="Fix the frequency coordinate on the plot",
name='Field Viz Plot: Fix Freq',
description='Fix the frequency coordinate on the plot',
default=False,
update=lambda self, context: self.sync_prop("field_viz_plot_fix_f", context),
update=lambda self, context: self.sync_prop(
'field_viz_plot_fix_f', context
),
)
field_viz_plot_fixed_x: bpy.props.FloatProperty(
name="Field Viz Plot: Fix X",
description="Fix the x-coordinate on the plot",
name='Field Viz Plot: Fix X',
description='Fix the x-coordinate on the plot',
default=0.0,
update=lambda self, context: self.sync_prop("field_viz_plot_fixed_x", context),
update=lambda self, context: self.sync_prop(
'field_viz_plot_fixed_x', context
),
)
field_viz_plot_fixed_y: bpy.props.FloatProperty(
name="Field Viz Plot: Fixed Y",
description="Fix the y coordinate on the plot",
name='Field Viz Plot: Fixed Y',
description='Fix the y coordinate on the plot',
default=0.0,
update=lambda self, context: self.sync_prop("field_viz_plot_fixed_y", context),
update=lambda self, context: self.sync_prop(
'field_viz_plot_fixed_y', context
),
)
field_viz_plot_fixed_z: bpy.props.FloatProperty(
name="Field Viz Plot: Fixed Z",
description="Fix the z coordinate on the plot",
name='Field Viz Plot: Fixed Z',
description='Fix the z coordinate on the plot',
default=0.0,
update=lambda self, context: self.sync_prop("field_viz_plot_fixed_z", context),
update=lambda self, context: self.sync_prop(
'field_viz_plot_fixed_z', context
),
)
field_viz_plot_fixed_f: bpy.props.FloatProperty(
name="Field Viz Plot: Fixed Freq (Thz)",
description="Fix the frequency coordinate on the plot",
name='Field Viz Plot: Fixed Freq (Thz)',
description='Fix the frequency coordinate on the plot',
default=0.0,
update=lambda self, context: self.sync_prop("field_viz_plot_fixed_f", context),
update=lambda self, context: self.sync_prop(
'field_viz_plot_fixed_f', context
),
)
####################
# - Derived Properties
####################
def sync_viz_monitor_name(self, context):
if (sim_data := self._compute_input("FDTD Sim Data")) is None:
if (sim_data := self._compute_input('FDTD Sim Data')) is None:
return
self.cache_viz_monitor_type = sim_data.monitor_data[
self.viz_monitor_name
].type
self.sync_prop("viz_monitor_name", context)
self.sync_prop('viz_monitor_name', context)
def retrieve_monitors(self, context) -> list[tuple]:
global CACHE
if not CACHE.get(self.instance_id):
sim_data = self._compute_input("FDTD Sim Data")
sim_data = self._compute_input('FDTD Sim Data')
if sim_data is not None:
CACHE[self.instance_id] = {"monitors": list(
sim_data.monitor_data.keys()
)}
CACHE[self.instance_id] = {
'monitors': list(sim_data.monitor_data.keys())
}
else:
return [("NONE", "None", "No monitors")]
return [('NONE', 'None', 'No monitors')]
monitor_names = CACHE[self.instance_id]["monitors"]
monitor_names = CACHE[self.instance_id]['monitors']
# Check for No Monitors
if not monitor_names:
return [("NONE", "None", "No monitors")]
return [('NONE', 'None', 'No monitors')]
return [
(
@ -201,42 +221,41 @@ class FDTDSimDataVizNode(base.MaxwellSimNode):
####################
def draw_props(self, context, layout):
row = layout.row()
row.prop(self, "viz_monitor_name", text="")
if self.cache_viz_monitor_type == "FieldData":
row.prop(self, 'viz_monitor_name', text='')
if self.cache_viz_monitor_type == 'FieldData':
# Array Selection
split = layout.split(factor=0.45)
col = split.column(align=False)
col.label(text="Component")
col.label(text="Part")
col.label(text="Scale")
col.label(text='Component')
col.label(text='Part')
col.label(text='Scale')
col = split.column(align=False)
col.prop(self, "field_viz_component", text="")
col.prop(self, "field_viz_part", text="")
col.prop(self, "field_viz_scale", text="")
col.prop(self, 'field_viz_component', text='')
col.prop(self, 'field_viz_part', text='')
col.prop(self, 'field_viz_scale', text='')
# Coordinate Fixing
split = layout.split(factor=0.45)
col = split.column(align=False)
col.prop(self, "field_viz_plot_fix_x", text="Fix x (um)")
col.prop(self, "field_viz_plot_fix_y", text="Fix y (um)")
col.prop(self, "field_viz_plot_fix_z", text="Fix z (um)")
col.prop(self, "field_viz_plot_fix_f", text="Fix f (THz)")
col.prop(self, 'field_viz_plot_fix_x', text='Fix x (um)')
col.prop(self, 'field_viz_plot_fix_y', text='Fix y (um)')
col.prop(self, 'field_viz_plot_fix_z', text='Fix z (um)')
col.prop(self, 'field_viz_plot_fix_f', text='Fix f (THz)')
col = split.column(align=False)
col.prop(self, "field_viz_plot_fixed_x", text="")
col.prop(self, "field_viz_plot_fixed_y", text="")
col.prop(self, "field_viz_plot_fixed_z", text="")
col.prop(self, "field_viz_plot_fixed_f", text="")
col.prop(self, 'field_viz_plot_fixed_x', text='')
col.prop(self, 'field_viz_plot_fixed_y', text='')
col.prop(self, 'field_viz_plot_fixed_z', text='')
col.prop(self, 'field_viz_plot_fixed_f', text='')
####################
# - On Value Changed Methods
####################
@base.on_value_changed(
socket_name="FDTD Sim Data",
managed_objs={"viz_object"},
input_sockets={"FDTD Sim Data"},
socket_name='FDTD Sim Data',
managed_objs={'viz_object'},
input_sockets={'FDTD Sim Data'},
)
def on_value_changed__fdtd_sim_data(
self,
@ -245,29 +264,35 @@ class FDTDSimDataVizNode(base.MaxwellSimNode):
) -> None:
global CACHE
if (sim_data := input_sockets["FDTD Sim Data"]) is None:
if (sim_data := input_sockets['FDTD Sim Data']) is None:
CACHE.pop(self.instance_id, None)
return
CACHE[self.instance_id] = {"monitors": list(
sim_data.monitor_data.keys()
)}
CACHE[self.instance_id] = {
'monitors': list(sim_data.monitor_data.keys())
}
####################
# - Plotting
####################
@base.on_show_plot(
managed_objs={"viz_plot"},
managed_objs={'viz_plot'},
props={
"viz_monitor_name", "field_viz_component",
"field_viz_part", "field_viz_scale",
"field_viz_structure_visibility",
"field_viz_plot_fix_x", "field_viz_plot_fix_y",
"field_viz_plot_fix_z", "field_viz_plot_fix_f",
"field_viz_plot_fixed_x", "field_viz_plot_fixed_y",
"field_viz_plot_fixed_z", "field_viz_plot_fixed_f",
'viz_monitor_name',
'field_viz_component',
'field_viz_part',
'field_viz_scale',
'field_viz_structure_visibility',
'field_viz_plot_fix_x',
'field_viz_plot_fix_y',
'field_viz_plot_fix_z',
'field_viz_plot_fix_f',
'field_viz_plot_fixed_x',
'field_viz_plot_fixed_y',
'field_viz_plot_fixed_z',
'field_viz_plot_fixed_f',
},
input_sockets={"FDTD Sim Data"},
input_sockets={'FDTD Sim Data'},
stop_propagation=True,
)
def on_show_plot(
@ -276,35 +301,35 @@ class FDTDSimDataVizNode(base.MaxwellSimNode):
input_sockets: dict[str, typ.Any],
props: dict[str, typ.Any],
):
if (
(sim_data := input_sockets["FDTD Sim Data"]) is None
or (monitor_name := props["viz_monitor_name"]) == "NONE"
):
if (sim_data := input_sockets['FDTD Sim Data']) is None or (
monitor_name := props['viz_monitor_name']
) == 'NONE':
return
coord_fix = {}
for coord in ["x", "y", "z", "f"]:
if props[f"field_viz_plot_fix_{coord}"]:
for coord in ['x', 'y', 'z', 'f']:
if props[f'field_viz_plot_fix_{coord}']:
coord_fix |= {
coord: props[f"field_viz_plot_fixed_{coord}"],
coord: props[f'field_viz_plot_fixed_{coord}'],
}
if "f" in coord_fix:
coord_fix["f"] *= 1e12
if 'f' in coord_fix:
coord_fix['f'] *= 1e12
managed_objs["viz_plot"].mpl_plot_to_image(
managed_objs['viz_plot'].mpl_plot_to_image(
lambda ax: sim_data.plot_field(
monitor_name,
props["field_viz_component"],
val=props["field_viz_part"],
scale=props["field_viz_scale"],
eps_alpha=props["field_viz_structure_visibility"],
props['field_viz_component'],
val=props['field_viz_part'],
scale=props['field_viz_scale'],
eps_alpha=props['field_viz_structure_visibility'],
phase=0,
**coord_fix,
ax=ax,
),
bl_select=True,
)
# @base.on_show_preview(
# managed_objs={"viz_object"},
# )
@ -325,8 +350,4 @@ class FDTDSimDataVizNode(base.MaxwellSimNode):
BL_REGISTER = [
FDTDSimDataVizNode,
]
BL_NODES = {
ct.NodeType.FDTDSimDataViz: (
ct.NodeCategory.MAXWELLSIM_VIZ
)
}
BL_NODES = {ct.NodeType.FDTDSimDataViz: (ct.NodeCategory.MAXWELLSIM_VIZ)}

View File

@ -1,18 +1,21 @@
from . import base
from . import basic
AnySocketDef = basic.AnySocketDef
BoolSocketDef = basic.BoolSocketDef
StringSocketDef = basic.StringSocketDef
FilePathSocketDef = basic.FilePathSocketDef
from . import number
IntegerNumberSocketDef = number.IntegerNumberSocketDef
RationalNumberSocketDef = number.RationalNumberSocketDef
RealNumberSocketDef = number.RealNumberSocketDef
ComplexNumberSocketDef = number.ComplexNumberSocketDef
from . import vector
Real2DVectorSocketDef = vector.Real2DVectorSocketDef
Complex2DVectorSocketDef = vector.Complex2DVectorSocketDef
Integer3DVectorSocketDef = vector.Integer3DVectorSocketDef
@ -20,6 +23,7 @@ Real3DVectorSocketDef = vector.Real3DVectorSocketDef
Complex3DVectorSocketDef = vector.Complex3DVectorSocketDef
from . import physical
PhysicalUnitSystemSocketDef = physical.PhysicalUnitSystemSocketDef
PhysicalTimeSocketDef = physical.PhysicalTimeSocketDef
PhysicalAngleSocketDef = physical.PhysicalAngleSocketDef
@ -36,6 +40,7 @@ PhysicalPolSocketDef = physical.PhysicalPolSocketDef
PhysicalFreqSocketDef = physical.PhysicalFreqSocketDef
from . import blender
BlenderObjectSocketDef = blender.BlenderObjectSocketDef
BlenderCollectionSocketDef = blender.BlenderCollectionSocketDef
BlenderImageSocketDef = blender.BlenderImageSocketDef
@ -43,6 +48,7 @@ BlenderGeoNodesSocketDef = blender.BlenderGeoNodesSocketDef
BlenderTextSocketDef = blender.BlenderTextSocketDef
from . import maxwell
MaxwellBoundCondSocketDef = maxwell.MaxwellBoundCondSocketDef
MaxwellBoundCondsSocketDef = maxwell.MaxwellBoundCondsSocketDef
MaxwellMediumSocketDef = maxwell.MaxwellMediumSocketDef
@ -58,6 +64,7 @@ MaxwellSimGridAxisSocketDef = maxwell.MaxwellSimGridAxisSocketDef
MaxwellSimDomainSocketDef = maxwell.MaxwellSimDomainSocketDef
from . import tidy3d
Tidy3DCloudTaskSocketDef = tidy3d.Tidy3DCloudTaskSocketDef
BL_REGISTER = [

View File

@ -9,6 +9,7 @@ import sympy as sp
import sympy.physics.units as spu
from .. import contracts as ct
class MaxwellSimSocket(bpy.types.NodeSocket):
# Fundamentals
socket_type: ct.SocketType
@ -16,8 +17,12 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
# Style
display_shape: typx.Literal[
"CIRCLE", "SQUARE", "DIAMOND", "CIRCLE_DOT", "SQUARE_DOT",
"DIAMOND_DOT",
'CIRCLE',
'SQUARE',
'DIAMOND',
'CIRCLE_DOT',
'SQUARE_DOT',
'DIAMOND_DOT',
]
## We use the following conventions for shapes:
## - CIRCLE: Single Value.
@ -41,14 +46,14 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
super().__init_subclass__(**kwargs) ## Yucky superclass setup.
# Setup Blender ID for Node
if not hasattr(cls, "socket_type"):
if not hasattr(cls, 'socket_type'):
msg = f"Socket class {cls} does not define 'socket_type'"
raise ValueError(msg)
cls.bl_idname = str(cls.socket_type.value)
# Setup Locked Property for Node
cls.__annotations__["locked"] = bpy.props.BoolProperty(
name="Locked State",
cls.__annotations__['locked'] = bpy.props.BoolProperty(
name='Locked State',
description="The lock-state of a particular socket, which determines the socket's user editability",
default=False,
)
@ -58,37 +63,37 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
cls.socket_shape = ct.SOCKET_SHAPES[cls.socket_type]
# Setup List
cls.__annotations__["is_list"] = bpy.props.BoolProperty(
name="Is List",
description="Whether or not a particular socket is a list type socket",
cls.__annotations__['is_list'] = bpy.props.BoolProperty(
name='Is List',
description='Whether or not a particular socket is a list type socket',
default=False,
update=lambda self, context: self.sync_is_list(context)
update=lambda self, context: self.sync_is_list(context),
)
# Configure Use of Units
if cls.use_units:
# Set Shape :)
cls.socket_shape += "_DOT"
cls.socket_shape += '_DOT'
if not (socket_units := ct.SOCKET_UNITS.get(cls.socket_type)):
msg = "Tried to `use_units` on {cls.bl_idname} socket, but `SocketType` has no units defined in `contracts.SOCKET_UNITS`"
msg = 'Tried to `use_units` on {cls.bl_idname} socket, but `SocketType` has no units defined in `contracts.SOCKET_UNITS`'
raise RuntimeError(msg)
# Current Unit
cls.__annotations__["active_unit"] = bpy.props.EnumProperty(
name="Unit",
description="Choose a unit",
cls.__annotations__['active_unit'] = bpy.props.EnumProperty(
name='Unit',
description='Choose a unit',
items=[
(unit_name, str(unit_value), str(unit_value))
for unit_name, unit_value in socket_units["values"].items()
for unit_name, unit_value in socket_units['values'].items()
],
default=socket_units["default"],
default=socket_units['default'],
update=lambda self, context: self.sync_unit_change(),
)
# Previous Unit (for conversion)
cls.__annotations__["prev_active_unit"] = bpy.props.StringProperty(
default=socket_units["default"],
cls.__annotations__['prev_active_unit'] = bpy.props.StringProperty(
default=socket_units['default'],
)
####################
@ -96,7 +101,13 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
####################
def trigger_action(
self,
action: typx.Literal["enable_lock", "disable_lock", "value_changed", "show_preview", "show_plot"],
action: typx.Literal[
'enable_lock',
'disable_lock',
'value_changed',
'show_preview',
'show_plot',
],
) -> None:
"""Called whenever the socket's output value has changed.
@ -109,7 +120,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
`trigger_action` method will be called.
"""
# Forwards Chains
if action in {"value_changed"}:
if action in {'value_changed'}:
## Input Socket
if not self.is_output:
self.node.trigger_action(action, socket_name=self.name)
@ -120,11 +131,16 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
link.to_socket.trigger_action(action)
# Backwards Chains
elif action in {"enable_lock", "disable_lock", "show_preview", "show_plot"}:
if action == "enable_lock":
elif action in {
'enable_lock',
'disable_lock',
'show_preview',
'show_plot',
}:
if action == 'enable_lock':
self.locked = True
if action == "disable_lock":
if action == 'disable_lock':
self.locked = False
## Output Socket
@ -140,36 +156,35 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
# - Action Chain: Event Handlers
####################
def sync_is_list(self, context: bpy.types.Context):
"""Called when the "is_list_ property has been updated.
"""
"""Called when the "is_list_ property has been updated."""
if self.is_list:
if self.use_units:
self.display_shape = "SQUARE_DOT"
self.display_shape = 'SQUARE_DOT'
else:
self.display_shape = "SQUARE"
self.display_shape = 'SQUARE'
self.trigger_action("value_changed")
self.trigger_action('value_changed')
def sync_prop(self, prop_name: str, context: bpy.types.Context):
"""Called when a property has been updated.
"""
"""Called when a property has been updated."""
if not hasattr(self, prop_name):
msg = f"Property {prop_name} not defined on socket {self}"
msg = f'Property {prop_name} not defined on socket {self}'
raise RuntimeError(msg)
self.trigger_action("value_changed")
self.trigger_action('value_changed')
def sync_link_added(self, link) -> bool:
"""Called when a link has been added to this (input) socket.
Returns a bool, whether or not the socket consents to the link change.
"""
if self.locked: return False
if self.locked:
return False
if self.is_output:
msg = f"Tried to sync 'link add' on output socket"
raise RuntimeError(msg)
self.trigger_action("value_changed")
self.trigger_action('value_changed')
return True
@ -178,12 +193,13 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
Returns a bool, whether or not the socket consents to the link change.
"""
if self.locked: return False
if self.locked:
return False
if self.is_output:
msg = f"Tried to sync 'link add' on output socket"
raise RuntimeError(msg)
self.trigger_action("value_changed")
self.trigger_action('value_changed')
return True
@ -193,6 +209,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
@property
def value(self) -> typ.Any:
raise NotImplementedError
@value.setter
def value(self, value: typ.Any) -> None:
raise NotImplementedError
@ -200,26 +217,29 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
@property
def value_list(self) -> typ.Any:
return [self.value]
@value_list.setter
def value_list(self, value: typ.Any) -> None:
raise NotImplementedError
def value_as_unit_system(
self,
unit_system: dict,
dimensionless: bool = True
self, unit_system: dict, dimensionless: bool = True
) -> typ.Any:
## TODO: Caching could speed this boi up quite a bit
unit_system_unit = unit_system[self.socket_type]
return spu.convert_to(
return (
spu.convert_to(
self.value,
unit_system_unit,
) / unit_system_unit
)
/ unit_system_unit
)
@property
def lazy_value(self) -> None:
raise NotImplementedError
@lazy_value.setter
def lazy_value(self, lazy_value: typ.Any) -> None:
raise NotImplementedError
@ -227,6 +247,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
@property
def lazy_value_list(self) -> typ.Any:
return [self.lazy_value]
@lazy_value_list.setter
def lazy_value_list(self, value: typ.Any) -> None:
raise NotImplementedError
@ -244,11 +265,15 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
**NOTE**: Low-level method. Use `compute_data` instead.
"""
if kind == ct.DataFlowKind.Value:
if self.is_list: return self.value_list
else: return self.value
if self.is_list:
return self.value_list
else:
return self.value
elif kind == ct.DataFlowKind.LazyValue:
if self.is_list: return self.lazy_value_list
else: return self.lazy_value
if self.is_list:
return self.lazy_value_list
else:
return self.lazy_value
return self.lazy_value
elif kind == ct.DataFlowKind.Capabilities:
return self.capabilities
@ -268,21 +293,23 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
## List-like sockets guarantee that a list of a thing is passed.
if self.is_output:
res = self.node.compute_output(self.name, kind=kind)
if self.is_list and not isinstance(res, list): return [res]
if self.is_list and not isinstance(res, list):
return [res]
return res
# Compute Input Socket
## Unlinked: Retrieve Socket Value
if not self.is_linked: return self._compute_data(kind)
if not self.is_linked:
return self._compute_data(kind)
## Linked: Compute Output of Linked Sockets
linked_values = [
link.from_socket.compute_data(kind)
for link in self.links
link.from_socket.compute_data(kind) for link in self.links
]
## Return Single Value / List of Values
if len(linked_values) == 1: return linked_values[0]
if len(linked_values) == 1:
return linked_values[0]
return linked_values
####################
@ -294,9 +321,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
msg = "Tried to get possible units for socket {self}, but socket doesn't `use_units`"
raise ValueError(msg)
return ct.SOCKET_UNITS[
self.socket_type
]["values"]
return ct.SOCKET_UNITS[self.socket_type]['values']
@property
def unit(self) -> sp.Expr:
@ -344,8 +369,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
## - Using only self.value avoids implementation-specific details.
self.value = spu.convert_to(
prev_value,
self.unit
prev_value, self.unit
) ## Now, the unit conversion can be done correctly.
self.prev_active_unit = self.active_unit
@ -358,14 +382,12 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
context: bpy.types.Context,
node: bpy.types.Node,
) -> ct.BLColorRGBA:
"""Color of the socket icon, when embedded in a node.
"""
"""Color of the socket icon, when embedded in a node."""
return self.socket_color
@classmethod
def draw_color_simple(cls) -> ct.BLColorRGBA:
"""Fallback color of the socket icon (ex.when not embedded in a node).
"""
"""Fallback color of the socket icon (ex.when not embedded in a node)."""
return cls.socket_color
####################
@ -378,8 +400,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
node: bpy.types.Node,
text: str,
) -> None:
"""Called by Blender to draw the socket UI.
"""
"""Called by Blender to draw the socket UI."""
if self.is_output:
self.draw_output(context, layout, node, text)
@ -402,13 +423,13 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
node: bpy.types.Node,
text: str,
) -> None:
"""Draws the socket UI, when the socket is an input socket.
"""
"""Draws the socket UI, when the socket is an input socket."""
col = layout.column(align=False)
# Label Row
row = col.row(align=False)
if self.locked: row.enabled = False
if self.locked:
row.enabled = False
## Linked Label
if self.is_linked:
@ -423,7 +444,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
self.draw_label_row(_row, text)
_col = split.column(align=True)
_col.prop(self, "active_unit", text="")
_col.prop(self, 'active_unit', text='')
else:
self.draw_label_row(row, text)
@ -438,7 +459,8 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
row = col.row(align=False)
row.enabled = False
else:
if self.locked: row.enabled = False
if self.locked:
row.enabled = False
# Value Column(s)
col = row.column(align=True)
@ -454,8 +476,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
node: bpy.types.Node,
text: str,
) -> None:
"""Draws the socket UI, when the socket is an output socket.
"""
"""Draws the socket UI, when the socket is an output socket."""
layout.label(text=text)
####################
@ -485,4 +506,3 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
Can be overridden.
"""
pass

Some files were not shown because too many files have changed in this diff Show More