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 @@
import tomllib
from pathlib import Path
import bpy
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 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",
'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
####################
# - sys.path Library Inclusion
# - Load and Register Addon
####################
import sys
sys.path.insert(0, "/home/sofus/src/college/bsc_ge/thesis/code/.cached-dependencies")
## ^^ Placeholder
BL_REGISTER__BEFORE_DEPS = [
*operators_nodeps.BL_REGISTER,
*preferences.BL_REGISTER,
]
####################
# - Module Import
####################
if "bpy" not in locals():
import bpy
import nodeitems_utils
try:
from . import node_trees
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
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)
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,33 +114,44 @@ 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.
) ## Don't include the "2D" token, if defined.
if (
socket_type := ct.BL_SOCKET_DESCR_TYPE_MAP.get(
(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 := [
unit
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"]
]
socket_unit = (
_socket_unit
if (
_socket_unit := [
unit
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']
]
)
## 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([
make_scalar_bl_compat(bl_socket_value[0]),
make_scalar_bl_compat(bl_socket_value[1]),
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
]),
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],
## 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]
),
}[size_from_bl_interface_socket(bl_interface_socket)]()
## The 'lambda' delays construction until size is determined

View File

@ -6,16 +6,18 @@ from . import contracts as ct
from .nodes import BL_NODES
DYNAMIC_SUBMENU_REGISTRATIONS = []
def mk_node_categories(
tree,
syllable_prefix = [],
#root = True,
syllable_prefix=[],
# root = True,
):
global DYNAMIC_SUBMENU_REGISTRATIONS
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,), {
'bl_idname': current_category.value,
'bl_label': ct.NODE_CAT_LABELS[current_category],
'draw': draw_factory(tuple(subitems)),
})
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(
pattern=r'^[A-Z_]+$',
)]
SocketName = pytypes_ext.Annotated[str, pyd.StringConstraints(
pattern=r'^[a-zA-Z0-9_]+$',
)]
PresetName = pytypes_ext.Annotated[str, pyd.StringConstraints(
pattern=r'^[a-zA-Z0-9_]+$',
)]
BLEnumID = pytypes_ext.Annotated[
str,
pyd.StringConstraints(
pattern=r'^[A-Z_]+$',
),
]
SocketName = pytypes_ext.Annotated[
str,
pyd.StringConstraints(
pattern=r'^[a-zA-Z0-9_]+$',
),
]
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(
pattern=r'^[a-z_]+$',
)]
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,36 +1,33 @@
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,
#("NodeSocketFloatAngle", 1): ST.PhysicalAngle,
#("NodeSocketFloatDistance", 1): ST.PhysicalLength,
("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,
#("NodeSocketVectorAcceleration", 2): ST.PhysicalAccel2D,
#("NodeSocketVectorAcceleration", 3): ST.PhysicalAccel3D,
#("NodeSocketVectorDirection", 2): ST.Real2DVectorDir,
#("NodeSocketVectorDirection", 3): ST.Real3DVectorDir,
("NodeSocketVectorEuler", 2): ST.PhysicalRot2D,
("NodeSocketVectorEuler", 3): ST.PhysicalRot3D,
#("NodeSocketVectorTranslation", 3): ST.PhysicalDisp3D,
#("NodeSocketVectorVelocity", 3): ST.PhysicalVel3D,
#("NodeSocketVectorXYZ", 3): ST.PhysicalPoint3D,
('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,
# ("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,
# ("NodeSocketVectorAcceleration", 2): ST.PhysicalAccel2D,
# ("NodeSocketVectorAcceleration", 3): ST.PhysicalAccel3D,
# ("NodeSocketVectorDirection", 2): ST.Real2DVectorDir,
# ("NodeSocketVectorDirection", 3): ST.Real3DVectorDir,
('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 (
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")
)
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')
):
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[
collection_name
]).exclude != view_layer_exclude:
if (
layer_collection := bpy.context.view_layer.layer_collection.children[
collection_name
]
).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(
PREVIEW_COLLECTION_NAME, view_layer_exclude=False
)).objects:
if (
bl_object.name
not in (
preview_collection := bl_collection(
PREVIEW_COLLECTION_NAME, view_layer_exclude=False
)
).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(
PREVIEW_COLLECTION_NAME, view_layer_exclude=False
)).objects:
if (
bl_object.name
not in (
preview_collection := bl_collection(
PREVIEW_COLLECTION_NAME, view_layer_exclude=False
)
).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,18 +396,17 @@ 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.
# @property
# def volume(self) -> bpy.types.Volume:
# """Returns the object's volume data.
#
# Raises an error if the object has no volume data.
# """
# if (
# (bl_object := bpy.data.objects.get(self.bl_object_name))
# and bl_object.type == "VOLUME"
# ):
# return bl_object.data
# Raises an error if the object has no volume data.
# """
# if (
# (bl_object := bpy.data.objects.get(self.bl_object_name))
# and bl_object.type == "VOLUME"
# ):
# return bl_object.data
#
# msg = f"Requested VOLUME data from `bl_object` of type {bl_object.type}"
# raise ValueError(msg)
# msg = f"Requested VOLUME data from `bl_object` of type {bl_object.type}"
# raise ValueError(msg)

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,8 +82,10 @@ 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
self._node_link_cache = NodeLinkCache(self)
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

@ -1,37 +1,38 @@
#from . import kitchen_sink
# from . import kitchen_sink
from . import inputs
from . import outputs
from . import sources
from . import mediums
from . import structures
#from . import bounds
# from . import bounds
from . import monitors
from . import simulations
from . import utilities
from . import viz
BL_REGISTER = [
#*kitchen_sink.BL_REGISTER,
# *kitchen_sink.BL_REGISTER,
*inputs.BL_REGISTER,
*outputs.BL_REGISTER,
*sources.BL_REGISTER,
*mediums.BL_REGISTER,
*structures.BL_REGISTER,
# *bounds.BL_REGISTER,
# *bounds.BL_REGISTER,
*monitors.BL_REGISTER,
*simulations.BL_REGISTER,
*utilities.BL_REGISTER,
*viz.BL_REGISTER,
]
BL_NODES = {
#**kitchen_sink.BL_NODES,
# **kitchen_sink.BL_NODES,
**inputs.BL_NODES,
**outputs.BL_NODES,
**sources.BL_NODES,
**mediums.BL_NODES,
**structures.BL_NODES,
# **bounds.BL_NODES,
# **bounds.BL_NODES,
**monitors.BL_NODES,
**simulations.BL_NODES,
**utilities.BL_NODES,

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
@ -26,14 +28,14 @@ class MaxwellSimNode(bpy.types.Node):
bl_idname: str
use_sim_node_name: bool = False
bl_label: str
#draw_label(self) -> str: pass
# draw_label(self) -> str: pass
# Style
bl_description: str = ""
bl_description: str = ''
#bl_width_default: float = 0.0
#bl_width_min: float = 0.0
#bl_width_max: float = 0.0
# bl_width_default: float = 0.0
# bl_width_min: float = 0.0
# bl_width_max: float = 0.0
# Sockets
_output_socket_methods: dict
@ -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()))
+ [
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
]
)
_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()
],
"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]:
return json.dumps(
{
'socket_names': list(deser.keys()),
'socket_def_names': [
model.__class__.__name__ for model in deser.values()
],
'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]:
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
@ -508,8 +524,8 @@ class MaxwellSimNode(bpy.types.Node):
"""
if not (bl_socket := self.inputs.get(input_socket_name)):
return None
#msg = f"Input socket name {input_socket_name} is not an active input sockets."
#raise ValueError(msg)
# msg = f"Input socket name {input_socket_name} is not an active input sockets."
# raise ValueError(msg)
return bl_socket.compute_data(kind=kind)
@ -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 (
prop_name
and prop_name in method._extra_data.get("changed_props")
) or (
socket_name
and method._extra_data["changed_loose_input"]
and socket_name in self.loose_input_sockets
(
socket_name
and socket_name
in method._extra_data.get('changed_sockets')
)
or (
prop_name
and prop_name
in method._extra_data.get('changed_props')
)
or (
socket_name
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([
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"
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'
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_icon = ...
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

@ -1,26 +1,22 @@
from . import wave_constant
#from . import unit_system
# from . import unit_system
from . import constants
from . import web_importers
#from . import file_importers
# from . import file_importers
BL_REGISTER = [
*wave_constant.BL_REGISTER,
# *unit_system.BL_REGISTER,
# *unit_system.BL_REGISTER,
*constants.BL_REGISTER,
*web_importers.BL_REGISTER,
# *file_importers.BL_REGISTER,
# *file_importers.BL_REGISTER,
]
BL_NODES = {
**wave_constant.BL_NODES,
# **unit_system.BL_NODES,
# **unit_system.BL_NODES,
**constants.BL_NODES,
**web_importers.BL_NODES,
# *file_importers.BL_REGISTER,
# *file_importers.BL_REGISTER,
}

View File

@ -1,17 +1,18 @@
#from . import scientific_constant
# from . import scientific_constant
from . import number_constant
#from . import physical_constant
# from . import physical_constant
from . import blender_constant
BL_REGISTER = [
# *scientific_constant.BL_REGISTER,
# *scientific_constant.BL_REGISTER,
*number_constant.BL_REGISTER,
# *physical_constant.BL_REGISTER,
# *physical_constant.BL_REGISTER,
*blender_constant.BL_REGISTER,
]
BL_NODES = {
# **scientific_constant.BL_NODES,
# **scientific_constant.BL_NODES,
**number_constant.BL_NODES,
# **physical_constant.BL_NODES,
# **physical_constant.BL_NODES,
**blender_constant.BL_NODES,
}

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_icon = constants.ICON_SIM_INPUT
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(
default_value=500*spu.nm,
'Vacuum WL': {
'WL': sockets.PhysicalLengthSocketDef(
default_value=500 * spu.nm,
default_unit=spu.nm,
),
},
"Frequency": {
"Freq": sockets.PhysicalFreqSocketDef(
default_value=500*spux.THz,
'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,50 +17,51 @@ 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"
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")
return ## I guess?
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_icon = ...
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_point_2d": sockets.PhysicalPoint2DSocketDef(),
"Angle": sockets.PhysicalAngleSocketDef(),
"Length": sockets.PhysicalLengthSocketDef(),
"Area": sockets.PhysicalAreaSocketDef(),
"Volume": sockets.PhysicalVolumeSocketDef(),
"Point 3D": sockets.PhysicalPoint3DSocketDef(),
'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(),
##"physical_size_2d": sockets.PhysicalSize2DSocketDef(),
"Size 3D": sockets.PhysicalSize3DSocketDef(),
"Mass": sockets.PhysicalMassSocketDef(),
"Speed": sockets.PhysicalSpeedSocketDef(),
"Accel Scalar": sockets.PhysicalAccelScalarSocketDef(),
"Force Scalar": sockets.PhysicalForceScalarSocketDef(),
#"physical_accel_3dvector": sockets.PhysicalAccel3DVectorSocketDef(),
'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

@ -1,47 +1,45 @@
from . import library_medium
#from . import pec_medium
#from . import isotropic_medium
#from . import anisotropic_medium
# from . import pec_medium
# from . import isotropic_medium
# from . import anisotropic_medium
#
#from . import triple_sellmeier_medium
#from . import sellmeier_medium
#from . import pole_residue_medium
#from . import drude_medium
#from . import drude_lorentz_medium
#from . import debye_medium
# from . import triple_sellmeier_medium
# from . import sellmeier_medium
# from . import pole_residue_medium
# from . import drude_medium
# from . import drude_lorentz_medium
# from . import debye_medium
#
#from . import non_linearities
# from . import non_linearities
BL_REGISTER = [
*library_medium.BL_REGISTER,
# *pec_medium.BL_REGISTER,
# *isotropic_medium.BL_REGISTER,
# *anisotropic_medium.BL_REGISTER,
#
# *triple_sellmeier_medium.BL_REGISTER,
# *sellmeier_medium.BL_REGISTER,
# *pole_residue_medium.BL_REGISTER,
# *drude_medium.BL_REGISTER,
# *drude_lorentz_medium.BL_REGISTER,
# *debye_medium.BL_REGISTER,
#
# *non_linearities.BL_REGISTER,
# *pec_medium.BL_REGISTER,
# *isotropic_medium.BL_REGISTER,
# *anisotropic_medium.BL_REGISTER,
#
# *triple_sellmeier_medium.BL_REGISTER,
# *sellmeier_medium.BL_REGISTER,
# *pole_residue_medium.BL_REGISTER,
# *drude_medium.BL_REGISTER,
# *drude_lorentz_medium.BL_REGISTER,
# *debye_medium.BL_REGISTER,
#
# *non_linearities.BL_REGISTER,
]
BL_NODES = {
**library_medium.BL_NODES,
# **pec_medium.BL_NODES,
# **isotropic_medium.BL_NODES,
# **anisotropic_medium.BL_NODES,
#
# **triple_sellmeier_medium.BL_NODES,
# **sellmeier_medium.BL_NODES,
# **pole_residue_medium.BL_NODES,
# **drude_medium.BL_NODES,
# **drude_lorentz_medium.BL_NODES,
# **debye_medium.BL_NODES,
#
# **non_linearities.BL_NODES,
# **pec_medium.BL_NODES,
# **isotropic_medium.BL_NODES,
# **anisotropic_medium.BL_NODES,
#
# **triple_sellmeier_medium.BL_NODES,
# **sellmeier_medium.BL_NODES,
# **pole_residue_medium.BL_NODES,
# **drude_medium.BL_NODES,
# **drude_lorentz_medium.BL_NODES,
# **debye_medium.BL_NODES,
#
# **non_linearities.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_icon = ...
bl_label = 'Drude-Lorentz Medium'
# bl_icon = ...
####################
# - Sockets
####################
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}",
)
for i in [1, 2, 3]
}
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}',
)
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"),
coeffs = [
(
self.compute_input(f"del_eps{i}"),
spu.convert_to(
self.compute_input(f"f{i}"),
spu.hertz,
) / spu.hertz,
spu.convert_to(
self.compute_input(f"delta{i}"),
spu.hertz,
) / spu.hertz,
)
for i in [1, 2, 3]
]
eps_inf=self.compute_input(f'eps_inf'),
coeffs=[
(
self.compute_input(f'del_eps{i}'),
spu.convert_to(
self.compute_input(f'f{i}'),
spu.hertz,
)
/ spu.hertz,
spu.convert_to(
self.compute_input(f'delta{i}'),
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="",
#icon="NODE_MATERIAL",
name='',
description='',
# icon="NODE_MATERIAL",
items=[
(
mat_key,
td.material_library[mat_key].name,
", ".join([
ref.journal
for ref in td.material_library[mat_key].variants[
td.material_library[mat_key].default
].reference
])
', '.join(
[
ref.journal
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,86 +6,86 @@ 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_icon = ...
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")
#C1_with_units = self.compute_input("C1")
# B1 = self.compute_input("B1")
# C1_with_units = self.compute_input("C1")
#
## Processing
#C1 = spu.convert_to(C1_with_units, spu.um**2) / spu.um**2
return td.Sellmeier(coeffs = [
(
self.compute_input(f"B{i}"),
spu.convert_to(
self.compute_input(f"C{i}"),
spu.um**2,
) / spu.um**2
)
for i in [1, 2, 3]
])
# C1 = spu.convert_to(C1_with_units, spu.um**2) / spu.um**2
return td.Sellmeier(
coeffs=[
(
self.compute_input(f'B{i}'),
spu.convert_to(
self.compute_input(f'C{i}'),
spu.um**2,
)
/ spu.um**2,
)
for i in [1, 2, 3]
]
)
####################

View File

@ -1,17 +1,17 @@
from . import eh_field_monitor
from . import field_power_flux_monitor
#from . import epsilon_tensor_monitor
#from . import diffraction_monitor
# from . import epsilon_tensor_monitor
# from . import diffraction_monitor
BL_REGISTER = [
*eh_field_monitor.BL_REGISTER,
*field_power_flux_monitor.BL_REGISTER,
# *epsilon_tensor_monitor.BL_REGISTER,
# *diffraction_monitor.BL_REGISTER,
# *epsilon_tensor_monitor.BL_REGISTER,
# *diffraction_monitor.BL_REGISTER,
]
BL_NODES = {
**eh_field_monitor.BL_NODES,
**field_power_flux_monitor.BL_NODES,
# **epsilon_tensor_monitor.BL_NODES,
# **diffraction_monitor.BL_NODES,
# **epsilon_tensor_monitor.BL_NODES,
# **diffraction_monitor.BL_NODES,
}

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(
default_value=200*spux.fs
'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(
default_value=200*spux.fs
'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_icon = constants.ICON_SIM_INPUT
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(
context.node.tracked_task_id
)) is not None
and task_info.status == "draft"
and (
task_info := tdcloud.TidyCloudTasks.task_info(
context.node.tracked_task_id
)
)
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 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)
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,
tdcloud.CloudTask,
)
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,15 +433,15 @@ 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
sim.validate_pre_upload(source_required = True)
sim.validate_pre_upload(source_required=True)
self.cache_total_monitor_data = sum(sim.monitors_data_size.values())

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

@ -1,19 +1,19 @@
from . import sim_domain
#from . import sim_grid
#from . import sim_grid_axes
# from . import sim_grid
# from . import sim_grid_axes
from . import fdtd_sim
BL_REGISTER = [
*sim_domain.BL_REGISTER,
# *sim_grid.BL_REGISTER,
# *sim_grid_axes.BL_REGISTER,
# *sim_grid.BL_REGISTER,
# *sim_grid_axes.BL_REGISTER,
*fdtd_sim.BL_REGISTER,
]
BL_NODES = {
**sim_domain.BL_NODES,
# **sim_grid.BL_NODES,
# **sim_grid_axes.BL_NODES,
# **sim_grid.BL_NODES,
# **sim_grid_axes.BL_NODES,
**fdtd_sim.BL_NODES,
}

View File

@ -6,53 +6,52 @@ 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]
#if not isinstance(structures, list):
# structures = [structures]
#if not isinstance(monitors, list):
# monitors = [monitors]
# if not isinstance(sources, list):
# sources = [sources]
# if not isinstance(structures, list):
# structures = [structures]
# if not isinstance(monitors, list):
# monitors = [monitors]
return td.Simulation(
**sim_domain, ## run_time=, size=, grid=, medium=
@ -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(
default_value = 5 * spu.ps,
default_unit = spu.ps,
'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,27 +1,28 @@
from . import temporal_shapes
from . import point_dipole_source
#from . import uniform_current_source
# from . import uniform_current_source
from . import plane_wave_source
#from . import gaussian_beam_source
#from . import astigmatic_gaussian_beam_source
#from . import tfsf_source
# from . import gaussian_beam_source
# from . import astigmatic_gaussian_beam_source
# from . import tfsf_source
BL_REGISTER = [
*temporal_shapes.BL_REGISTER,
*point_dipole_source.BL_REGISTER,
# *uniform_current_source.BL_REGISTER,
# *uniform_current_source.BL_REGISTER,
*plane_wave_source.BL_REGISTER,
# *gaussian_beam_source.BL_REGISTER,
# *astigmatic_gaussian_beam_source.BL_REGISTER,
# *tfsf_source.BL_REGISTER,
# *gaussian_beam_source.BL_REGISTER,
# *astigmatic_gaussian_beam_source.BL_REGISTER,
# *tfsf_source.BL_REGISTER,
]
BL_NODES = {
**temporal_shapes.BL_NODES,
**point_dipole_source.BL_NODES,
# **uniform_current_source.BL_NODES,
# **uniform_current_source.BL_NODES,
**plane_wave_source.BL_NODES,
# **gaussian_beam_source.BL_NODES,
# **astigmatic_gaussian_beam_source.BL_NODES,
# **tfsf_source.BL_NODES,
# **gaussian_beam_source.BL_NODES,
# **astigmatic_gaussian_beam_source.BL_NODES,
# **tfsf_source.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

@ -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")
bl_object.location = center #tuple([float(el) for el in center])
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

@ -1,14 +1,14 @@
from . import gaussian_pulse_temporal_shape
#from . import continuous_wave_temporal_shape
#from . import array_temporal_shape
# from . import continuous_wave_temporal_shape
# from . import array_temporal_shape
BL_REGISTER = [
*gaussian_pulse_temporal_shape.BL_REGISTER,
# *continuous_wave_temporal_shape.BL_REGISTER,
# *array_temporal_shape.BL_REGISTER,
# *continuous_wave_temporal_shape.BL_REGISTER,
# *array_temporal_shape.BL_REGISTER,
]
BL_NODES = {
**gaussian_pulse_temporal_shape.BL_NODES,
# **continuous_wave_temporal_shape.BL_NODES,
# **array_temporal_shape.BL_NODES,
# **continuous_wave_temporal_shape.BL_NODES,
# **array_temporal_shape.BL_NODES,
}

View File

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

View File

@ -6,48 +6,49 @@ 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_icon = ...
bl_label = 'Continuous Wave Temporal Shape'
# bl_icon = ...
####################
# - Sockets
####################
input_sockets = {
#"amplitude": sockets.RealNumberSocketDef(
# label="Temporal Shape",
#), ## Should have a unit of some kind...
"phase": sockets.PhysicalAngleSocketDef(
label="Phase",
# "amplitude": sockets.RealNumberSocketDef(
# label="Temporal Shape",
# ), ## Should have a unit of some kind...
'phase': sockets.PhysicalAngleSocketDef(
label='Phase',
),
"freq_center": sockets.PhysicalFreqSocketDef(
label="Freq Center",
'freq_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,40 +13,41 @@ 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_icon = ...
bl_label = 'Gaussian Pulse Temporal Shape'
# bl_icon = ...
####################
# - Sockets
####################
input_sockets = {
#"amplitude": sockets.RealNumberSocketDef(
# label="Temporal Shape",
#), ## Should have a unit of some kind...
"Freq Center": sockets.PhysicalFreqSocketDef(
# "amplitude": sockets.RealNumberSocketDef(
# label="Temporal Shape",
# ), ## Should have a unit of some kind...
'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

@ -1,17 +1,15 @@
#from . import object_structure
# from . import object_structure
from . import geonodes_structure
from . import primitives
BL_REGISTER = [
# *object_structure.BL_REGISTER,
# *object_structure.BL_REGISTER,
*geonodes_structure.BL_REGISTER,
*primitives.BL_REGISTER,
]
BL_NODES = {
# **object_structure.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_icon = ...
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,14 +1,15 @@
from . import box_structure
#from . import cylinder_structure
# from . import cylinder_structure
from . import sphere_structure
BL_REGISTER = [
*box_structure.BL_REGISTER,
# *cylinder_structure.BL_REGISTER,
# *cylinder_structure.BL_REGISTER,
*sphere_structure.BL_REGISTER,
]
BL_NODES = {
**box_structure.BL_NODES,
# **cylinder_structure.BL_NODES,
# **cylinder_structure.BL_NODES,
**sphere_structure.BL_NODES,
}

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_icon = ...
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(
default_value=150*spu.nm,
'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

@ -1,16 +1,14 @@
#from . import math
# from . import math
from . import combine
#from . import separate
# from . import separate
BL_REGISTER = [
# *math.BL_REGISTER,
# *math.BL_REGISTER,
*combine.BL_REGISTER,
#*separate.BL_REGISTER,
# *separate.BL_REGISTER,
]
BL_NODES = {
# **math.BL_NODES,
# **math.BL_NODES,
**combine.BL_NODES,
#**separate.BL_NODES,
# **separate.BL_NODES,
}

View File

@ -10,152 +10,143 @@ from .. import base
MAX_AMOUNT = 20
class CombineNode(base.MaxwellSimNode):
node_type = ct.NodeType.Combine
bl_label = "Combine"
#bl_icon = ...
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()
# for i, axis in zip(
# range(3),
# ["x", "y", "z"]
# )
#},
#"Size 3D": {
# axis_key: sockets.PhysicalLengthSocketDef()
# for i, axis_key, axis_label in zip(
# range(3),
# ["x_size", "y_size", "z_size"],
# ["X Size", "Y Size", "Z Size"],
# )
#},
# "Point 3D": {
# axis: sockets.PhysicalLengthSocketDef()
# for i, axis in zip(
# range(3),
# ["x", "y", "z"]
# )
# },
# "Size 3D": {
# axis_key: sockets.PhysicalLengthSocketDef()
# for i, axis_key, axis_label in zip(
# range(3),
# ["x_size", "y_size", "z_size"],
# ["X Size", "Y Size", "Z Size"],
# )
# },
}
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(),
#},
#"Size 3D": {
# "3D Size": sockets.PhysicalSize3DSocketDef(),
#},
# "Point 3D": {
# "3D Point": sockets.PhysicalPoint3DSocketDef(),
# },
# "Size 3D": {
# "3D Size": sockets.PhysicalSize3DSocketDef(),
# },
}
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,37 +7,38 @@ from .... import contracts
from .... import sockets
from ... import base
class WaveConverterNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.WaveConverter
bl_label = "Wave Converter"
#bl_icon = ...
bl_label = 'Wave Converter'
# bl_icon = ...
####################
# - Sockets
####################
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
####################

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