Compare commits

...

2 Commits

174 changed files with 5658 additions and 4643 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
dev dev
build
*.blend[0-9] *.blend[0-9]
.cached-dependencies .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" } { name = "Sofus Albert Høgsbro Rose", email = "blender-maxwell@sofusrose.com" }
] ]
dependencies = [ dependencies = [
"tidy3d>=2.6.1", "tidy3d~=2.6.1",
"pydantic>=2.6.4", "pydantic~=2.6.4",
"sympy>=1.12", "sympy~=1.12",
"scipy>=1.12.0", "scipy~=1.12.0",
"trimesh>=4.2.0", "trimesh~=4.2.0",
"networkx>=3.2.1", "networkx~=3.2.1",
"rtree>=1.2.0", "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" readme = "README.md"
requires-python = "~= 3.10" requires-python = "~= 3.11"
license = { text = "AGPL-3.0-or-later" } license = { text = "AGPL-3.0-or-later" }
#################### ####################
@ -26,13 +35,18 @@ managed = true
virtual = true virtual = true
dev-dependencies = [ dev-dependencies = [
"ruff>=0.3.2", "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 # - Tooling: Ruff
#################### ####################
[tool.ruff] [tool.ruff]
target-version = "py312" target-version = "py311"
line-length = 79 line-length = 79
[tool.ruff.lint] [tool.ruff.lint]
@ -77,14 +91,15 @@ select = [
"PT", # flake8-pytest-style ## pytest-Specific Checks "PT", # flake8-pytest-style ## pytest-Specific Checks
] ]
ignore = [ 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 "COM812", # Conflicts w/Formatter
"ISC001", # Conflicts w/Formatter "ISC001", # Conflicts w/Formatter
"Q000", # Conflicts w/Formatter "Q000", # Conflicts w/Formatter
"Q001", # Conflicts w/Formatter "Q001", # Conflicts w/Formatter
"Q002", # Conflicts w/Formatter "Q002", # Conflicts w/Formatter
"Q003", # 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 botocore==1.26.10
# via boto3 # via boto3
# via s3transfer # via s3transfer
certifi==2024.2.2 certifi==2021.10.8
# via requests # via requests
charset-normalizer==3.3.2 charset-normalizer==2.0.10
# via requests # via requests
click==8.0.3 click==8.0.3
# via dask # via dask
@ -31,6 +31,7 @@ cycler==0.12.1
# via matplotlib # via matplotlib
dask==2023.10.1 dask==2023.10.1
# via tidy3d # via tidy3d
fake-bpy-module-4-0==20231118
fonttools==4.49.0 fonttools==4.49.0
# via matplotlib # via matplotlib
fsspec==2024.2.0 fsspec==2024.2.0
@ -40,7 +41,7 @@ h5netcdf==1.0.2
h5py==3.10.0 h5py==3.10.0
# via h5netcdf # via h5netcdf
# via tidy3d # via tidy3d
idna==3.6 idna==3.3
# via requests # via requests
importlib-metadata==6.11.0 importlib-metadata==6.11.0
# via dask # via dask
@ -57,11 +58,10 @@ matplotlib==3.8.3
mpmath==1.3.0 mpmath==1.3.0
# via sympy # via sympy
networkx==3.2.1 networkx==3.2.1
numpy==1.26.4 numpy==1.24.3
# via contourpy # via contourpy
# via h5py # via h5py
# via matplotlib # via matplotlib
# via pandas
# via scipy # via scipy
# via shapely # via shapely
# via trimesh # via trimesh
@ -99,10 +99,10 @@ pyyaml==6.0.1
# via dask # via dask
# via responses # via responses
# via tidy3d # via tidy3d
requests==2.31.0 requests==2.27.1
# via responses # via responses
# via tidy3d # via tidy3d
responses==0.25.0 responses==0.23.1
# via tidy3d # via tidy3d
rich==12.5.1 rich==12.5.1
# via tidy3d # via tidy3d
@ -124,12 +124,14 @@ toolz==0.12.1
# via dask # via dask
# via partd # via partd
trimesh==4.2.0 trimesh==4.2.0
types-pyyaml==6.0.12.20240311
# via responses
typing-extensions==4.10.0 typing-extensions==4.10.0
# via pydantic # via pydantic
# via pydantic-core # via pydantic-core
tzdata==2024.1 tzdata==2024.1
# via pandas # via pandas
urllib3==1.26.18 urllib3==1.26.8
# via botocore # via botocore
# via requests # via requests
# via responses # via responses

View File

@ -14,9 +14,9 @@ boto3==1.23.1
botocore==1.26.10 botocore==1.26.10
# via boto3 # via boto3
# via s3transfer # via s3transfer
certifi==2024.2.2 certifi==2021.10.8
# via requests # via requests
charset-normalizer==3.3.2 charset-normalizer==2.0.10
# via requests # via requests
click==8.0.3 click==8.0.3
# via dask # via dask
@ -40,7 +40,7 @@ h5netcdf==1.0.2
h5py==3.10.0 h5py==3.10.0
# via h5netcdf # via h5netcdf
# via tidy3d # via tidy3d
idna==3.6 idna==3.3
# via requests # via requests
importlib-metadata==6.11.0 importlib-metadata==6.11.0
# via dask # via dask
@ -57,11 +57,10 @@ matplotlib==3.8.3
mpmath==1.3.0 mpmath==1.3.0
# via sympy # via sympy
networkx==3.2.1 networkx==3.2.1
numpy==1.26.4 numpy==1.24.3
# via contourpy # via contourpy
# via h5py # via h5py
# via matplotlib # via matplotlib
# via pandas
# via scipy # via scipy
# via shapely # via shapely
# via trimesh # via trimesh
@ -99,10 +98,10 @@ pyyaml==6.0.1
# via dask # via dask
# via responses # via responses
# via tidy3d # via tidy3d
requests==2.31.0 requests==2.27.1
# via responses # via responses
# via tidy3d # via tidy3d
responses==0.25.0 responses==0.23.1
# via tidy3d # via tidy3d
rich==12.5.1 rich==12.5.1
# via tidy3d # via tidy3d
@ -123,12 +122,14 @@ toolz==0.12.1
# via dask # via dask
# via partd # via partd
trimesh==4.2.0 trimesh==4.2.0
types-pyyaml==6.0.12.20240311
# via responses
typing-extensions==4.10.0 typing-extensions==4.10.0
# via pydantic # via pydantic
# via pydantic-core # via pydantic-core
tzdata==2024.1 tzdata==2024.1
# via pandas # via pandas
urllib3==1.26.18 urllib3==1.26.8
# via botocore # via botocore
# via requests # via requests
# via responses # 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 = { bl_info = {
"name": "Maxwell Simulation and Visualization", 'name': 'Maxwell PDE Sim and Viz',
"blender": (4, 0, 2), 'blender': (4, 1, 0),
"category": "Node", 'category': 'Node',
"description": "Custom node trees for defining and visualizing Maxwell simulation.", 'description': 'Placeholder',
"author": "Sofus Albert Høgsbro Rose", 'author': 'Sofus Albert Høgsbro Rose',
"version": (0, 1), 'version': (0, 0, 0),
"wiki_url": "https://git.sofus.io/dtu-courses/bsc_thesis", 'wiki_url': 'https://git.sofus.io/dtu-courses/bsc_thesis',
"tracker_url": "https://git.sofus.io/dtu-courses/bsc_thesis/issues", '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 BL_REGISTER__BEFORE_DEPS = [
sys.path.insert(0, "/home/sofus/src/college/bsc_ge/thesis/code/.cached-dependencies") *operators_nodeps.BL_REGISTER,
## ^^ Placeholder *preferences.BL_REGISTER,
]
####################
# - Module Import def BL_REGISTER__AFTER_DEPS(path_deps: Path):
#################### with pydeps.importable_addon_deps(path_deps):
if "bpy" not in locals(): from . import node_trees, operators
import bpy return [
import nodeitems_utils *operators.BL_REGISTER,
try: *node_trees.BL_REGISTER,
from . import node_trees ]
def BL_KEYMAP_ITEM_DEFS(path_deps: Path):
with pydeps.importable_addon_deps(path_deps):
from . import operators from . import operators
from . import preferences return [
except ImportError: *operators.BL_KMI_REGISTER,
import sys ]
sys.path.insert(0, "/home/sofus/src/college/bsc_ge/thesis/code/blender-maxwell")
import node_trees
import operators
import preferences
else:
import importlib
importlib.reload(node_trees)
#################### ####################
# - Registration # - 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(): 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)
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"],
)
REGISTERED_KEYMAPS.append(kmi)
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__": # Retrieve PyDeps Path from Addon Preferences
register() addon_prefs = ADDON_PREFS()
path_pydeps = addon_prefs.path_addon_pydeps
# 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,
)
# TODO: A popup before the addon fully loads or something like that?
## TODO: Communicate that deps must be installed and all that?
def unregister():
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 import sympy as sp
sp.printing.str.StrPrinter._default_settings['abbrev'] = True sp.printing.str.StrPrinter._default_settings['abbrev'] = True
## In this tree, all Sympy unit printing must be abbreviated. ## In this tree, all Sympy unit printing must be abbreviated.
## By configuring this in __init__.py, we guarantee it for all subimports. ## By configuring this in __init__.py, we guarantee it for all subimports.

View File

@ -26,47 +26,47 @@ Unit = typ.Any ## Type of a valid unit
SOCKET_DEFS = { SOCKET_DEFS = {
socket_type: getattr( socket_type: getattr(
sck, sck,
socket_type.value.removesuffix("SocketType") + "SocketDef", socket_type.value.removesuffix('SocketType') + 'SocketDef',
) )
for socket_type in ST for socket_type in ST
if hasattr( if hasattr(sck, socket_type.value.removesuffix('SocketType') + 'SocketDef')
sck,
socket_type.value.removesuffix("SocketType") + "SocketDef"
)
} }
## TODO: Bit of a hack. Is it robust enough? ## TODO: Bit of a hack. Is it robust enough?
for socket_type in ST: for socket_type in ST:
if not hasattr( if not hasattr(
sck, 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 Size Parser
#################### ####################
BL_SOCKET_3D_TYPE_PREFIXES = { BL_SOCKET_3D_TYPE_PREFIXES = {
"NodeSocketVector", 'NodeSocketVector',
"NodeSocketRotation", 'NodeSocketRotation',
} }
BL_SOCKET_4D_TYPE_PREFIXES = { BL_SOCKET_4D_TYPE_PREFIXES = {
"NodeSocketColor", 'NodeSocketColor',
} }
def size_from_bl_interface_socket( def size_from_bl_interface_socket(
bl_interface_socket: bpy.types.NodeTreeInterfaceSocket bl_interface_socket: bpy.types.NodeTreeInterfaceSocket,
) -> typx.Literal[1, 2, 3, 4]: ) -> typx.Literal[1, 2, 3, 4]:
"""Parses the `size`, aka. number of elements, contained within the `default_value` of a Blender interface socket. """Parses the `size`, aka. number of elements, contained within the `default_value` of a Blender interface socket.
Since there are no 2D sockets in Blender, the user can specify "2D" in the Blender socket's description to "promise" that only the first two values will be used. Since there are no 2D sockets in Blender, the user can specify "2D" in the Blender socket's description to "promise" that only the first two values will be used.
When this is done, the third value is left entirely untouched by this entire system. When this is done, the third value is left entirely untouched by this entire system.
A hard-coded set of NodeSocket<Type> prefixes are used to determine which interface sockets are, in fact, 3D. A hard-coded set of NodeSocket<Type> prefixes are used to determine which interface sockets are, in fact, 3D.
- For 3D sockets, a hard-coded list of Blender node socket types is used. - For 3D sockets, a hard-coded list of Blender node socket types is used.
- Else, it is a 1D socket type. - 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( if any(
bl_interface_socket.socket_type.startswith(bl_socket_3d_type_prefix) bl_interface_socket.socket_type.startswith(bl_socket_3d_type_prefix)
for bl_socket_3d_type_prefix in BL_SOCKET_3D_TYPE_PREFIXES for bl_socket_3d_type_prefix in BL_SOCKET_3D_TYPE_PREFIXES
@ -77,7 +77,7 @@ def size_from_bl_interface_socket(
for bl_socket_4d_type_prefix in BL_SOCKET_4D_TYPE_PREFIXES for bl_socket_4d_type_prefix in BL_SOCKET_4D_TYPE_PREFIXES
): ):
return 4 return 4
return 1 return 1
@ -88,15 +88,15 @@ def parse_bl_interface_socket(
bl_interface_socket: bpy.types.NodeTreeInterfaceSocket, bl_interface_socket: bpy.types.NodeTreeInterfaceSocket,
) -> tuple[ST, sp.Expr | None]: ) -> tuple[ST, sp.Expr | None]:
"""Parse a Blender interface socket by parsing its description, falling back to any direct type links. """Parse a Blender interface socket by parsing its description, falling back to any direct type links.
Arguments: Arguments:
bl_interface_socket: An interface socket associated with the global input to a node tree. bl_interface_socket: An interface socket associated with the global input to a node tree.
Returns: Returns:
The type of a corresponding MaxwellSimSocket, as well as a unit (if a particular unit was requested by the Blender interface socket). The type of a corresponding MaxwellSimSocket, as well as a unit (if a particular unit was requested by the Blender interface socket).
""" """
size = size_from_bl_interface_socket(bl_interface_socket) size = size_from_bl_interface_socket(bl_interface_socket)
# Determine Direct Socket Type # Determine Direct Socket Type
if ( if (
direct_socket_type := ct.BL_SOCKET_DIRECT_TYPE_MAP.get( direct_socket_type := ct.BL_SOCKET_DIRECT_TYPE_MAP.get(
@ -105,44 +105,55 @@ def parse_bl_interface_socket(
) is None: ) is None:
msg = "Blender interface socket has no mapping among 'MaxwellSimSocket's." msg = "Blender interface socket has no mapping among 'MaxwellSimSocket's."
raise ValueError(msg) raise ValueError(msg)
# (Maybe) Return Direct Socket Type # (Maybe) Return Direct Socket Type
## When there's no description, that's it; return. ## When there's no description, that's it; return.
if not ct.BL_SOCKET_DESCR_ANNOT_STRING in bl_interface_socket.description: if not ct.BL_SOCKET_DESCR_ANNOT_STRING in bl_interface_socket.description:
return (direct_socket_type, None) return (direct_socket_type, None)
# Parse Description for Socket Type # Parse Description for Socket Type
tokens = ( tokens = (
_tokens _tokens
if (_tokens := bl_interface_socket.description.split(" "))[0] != "2D" if (_tokens := bl_interface_socket.description.split(' '))[0] != '2D'
else _tokens[1:] else _tokens[1:]
) ## Don't include the "2D" token, if defined. ) ## Don't include the "2D" token, if defined.
if ( if (
socket_type := ct.BL_SOCKET_DESCR_TYPE_MAP.get( socket_type := ct.BL_SOCKET_DESCR_TYPE_MAP.get(
(tokens[0], bl_interface_socket.socket_type, size) (tokens[0], bl_interface_socket.socket_type, size)
) )
) is None: ) 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") # Determine Socket Unit (to use instead of "unit system")
## This is entirely OPTIONAL ## This is entirely OPTIONAL
socket_unit = None socket_unit = None
if socket_type in ct.SOCKET_UNITS: if socket_type in ct.SOCKET_UNITS:
## Case: Unit is User-Defined ## 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 # 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 # Compare Unit Token to Valid Sympy-Printed Units
socket_unit = _socket_unit if (_socket_unit := [ socket_unit = (
unit _socket_unit
for unit in ct.SOCKET_UNITS[socket_type]["values"].values() if (
if str(unit) == unit_token _socket_unit := [
]) else ct.SOCKET_UNITS[socket_type]["values"][ unit
ct.SOCKET_UNITS[socket_type]["default"] 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 ## TODO: Enforce abbreviated sympy printing here, not globally
return (socket_type, socket_unit) return (socket_type, socket_unit)
@ -152,11 +163,8 @@ def parse_bl_interface_socket(
def socket_def_from_bl_interface_socket( def socket_def_from_bl_interface_socket(
bl_interface_socket: bpy.types.NodeTreeInterfaceSocket, bl_interface_socket: bpy.types.NodeTreeInterfaceSocket,
): ):
"""Computes an appropriate (no-arg) SocketDef from the given `bl_interface_socket`, by parsing it. """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]]
return SOCKET_DEFS[
parse_bl_interface_socket(bl_interface_socket)[0]
]
#################### ####################
@ -169,7 +177,7 @@ def value_from_bl(
"""Reads the value of any Blender socket, and writes its `default_value` to the `value` of any `MaxwellSimSocket`. """Reads the value of any Blender socket, and writes its `default_value` to the `value` of any `MaxwellSimSocket`.
- If the size of the Blender socket is >1, then `value` is written to as a `sympy.Matrix`. - If the size of the Blender socket is >1, then `value` is written to as a `sympy.Matrix`.
- If a unit system is given, then the Blender socket is matched to a `MaxwellSimSocket`, which is used to lookup an appropriate unit in the given `unit_system`. - If a unit system is given, then the Blender socket is matched to a `MaxwellSimSocket`, which is used to lookup an appropriate unit in the given `unit_system`.
""" """
## TODO: Consider sympy.S()'ing the default_value ## TODO: Consider sympy.S()'ing the default_value
parsed_bl_socket_value = { parsed_bl_socket_value = {
@ -179,24 +187,25 @@ def value_from_bl(
4: lambda: sp.Matrix(tuple(bl_interface_socket.default_value)), 4: lambda: sp.Matrix(tuple(bl_interface_socket.default_value)),
}[size_from_bl_interface_socket(bl_interface_socket)]() }[size_from_bl_interface_socket(bl_interface_socket)]()
## The 'lambda' delays construction until size is determined ## The 'lambda' delays construction until size is determined
socket_type, unit = parse_bl_interface_socket(bl_interface_socket) socket_type, unit = parse_bl_interface_socket(bl_interface_socket)
# Add Unit to Parsed (if relevant) # Add Unit to Parsed (if relevant)
if unit is not None: if unit is not None:
parsed_bl_socket_value *= unit parsed_bl_socket_value *= unit
elif unit_system is not None: elif unit_system is not None:
parsed_bl_socket_value *= unit_system[socket_type] parsed_bl_socket_value *= unit_system[socket_type]
return parsed_bl_socket_value return parsed_bl_socket_value
#################### ####################
# - Convert to Blender-Compatible Value # - Convert to Blender-Compatible Value
#################### ####################
def make_scalar_bl_compat(scalar: typ.Any) -> typ.Any: def make_scalar_bl_compat(scalar: typ.Any) -> typ.Any:
"""Blender doesn't accept ex. Sympy numbers as values. """Blender doesn't accept ex. Sympy numbers as values.
Therefore, we need to do some conforming. Therefore, we need to do some conforming.
Currently hard-coded; this is probably best. Currently hard-coded; this is probably best.
""" """
if isinstance(scalar, sp.Integer): if isinstance(scalar, sp.Integer):
@ -208,44 +217,43 @@ def make_scalar_bl_compat(scalar: typ.Any) -> typ.Any:
elif isinstance(scalar, sp.Expr): elif isinstance(scalar, sp.Expr):
return float(scalar.n()) return float(scalar.n())
## TODO: More? ## TODO: More?
return scalar return scalar
def value_to_bl( def value_to_bl(
bl_interface_socket: bpy.types.NodeSocket, bl_interface_socket: bpy.types.NodeSocket,
value: typ.Any, value: typ.Any,
unit_system: dict | None = None, unit_system: dict | None = None,
) -> typ.Any: ) -> typ.Any:
socket_type, unit = parse_bl_interface_socket(bl_interface_socket) socket_type, unit = parse_bl_interface_socket(bl_interface_socket)
# Set Socket # Set Socket
if unit is not None: if unit is not None:
bl_socket_value = spu.convert_to(value, unit) / unit bl_socket_value = spu.convert_to(value, unit) / unit
elif ( elif unit_system is not None and socket_type in unit_system:
unit_system is not None bl_socket_value = (
and socket_type in unit_system spu.convert_to(value, unit_system[socket_type])
): / unit_system[socket_type]
bl_socket_value = spu.convert_to( )
value, unit_system[socket_type]
) / unit_system[socket_type]
else: else:
bl_socket_value = value bl_socket_value = value
return { return {
1: lambda: make_scalar_bl_compat(bl_socket_value), 1: lambda: make_scalar_bl_compat(bl_socket_value),
2: lambda: tuple([ 2: lambda: tuple(
make_scalar_bl_compat(bl_socket_value[0]), [
make_scalar_bl_compat(bl_socket_value[1]), make_scalar_bl_compat(bl_socket_value[0]),
bl_interface_socket.default_value[2] make_scalar_bl_compat(bl_socket_value[1]),
## Don't touch (unused) 3rd bl_socket coordinate 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 3: lambda: tuple(
]), [make_scalar_bl_compat(el) for el in bl_socket_value]
4: lambda: tuple([ ),
make_scalar_bl_compat(el) 4: lambda: tuple(
for el in bl_socket_value [make_scalar_bl_compat(el) for el in bl_socket_value]
]), ),
}[size_from_bl_interface_socket(bl_interface_socket)]() }[size_from_bl_interface_socket(bl_interface_socket)]()
## The 'lambda' delays construction until size is determined ## The 'lambda' delays construction until size is determined

View File

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

View File

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

View File

@ -2,11 +2,12 @@ import enum
from ....utils.blender_type_enum import BlenderTypeEnum from ....utils.blender_type_enum import BlenderTypeEnum
class DataFlowKind(BlenderTypeEnum): class DataFlowKind(BlenderTypeEnum):
"""Defines a shape/kind of data that may flow through a node tree. """Defines a shape/kind of data that may flow through a node tree.
Since a node socket may define one of each, we can support several related kinds of data flow through the same node-graph infrastructure. Since a node socket may define one of each, we can support several related kinds of data flow through the same node-graph infrastructure.
Attributes: Attributes:
Value: A value usable without new data. Value: A value usable without new data.
- Basic types aka. float, int, list, string, etc. . - Basic types aka. float, int, list, string, etc. .
@ -14,43 +15,43 @@ class DataFlowKind(BlenderTypeEnum):
- A usable constructed object, ex. a `tidy3d.Box`. - A usable constructed object, ex. a `tidy3d.Box`.
- Expressions (`sp.Expr`) that don't have unknown variables. - Expressions (`sp.Expr`) that don't have unknown variables.
- Lazy sequences aka. generators, with all data bound. - Lazy sequences aka. generators, with all data bound.
LazyValue: An object which, when given new data, can make many values. LazyValue: An object which, when given new data, can make many values.
- An `sp.Expr`, which might need `simplify`ing, `jax` JIT'ing, unit cancellations, variable substitutions, etc. before use. - An `sp.Expr`, which might need `simplify`ing, `jax` JIT'ing, unit cancellations, variable substitutions, etc. before use.
- Lazy objects, for which all parameters aren't yet known. - Lazy objects, for which all parameters aren't yet known.
- A computational graph aka. `aesara`, which may even need to be handled before - A computational graph aka. `aesara`, which may even need to be handled before
Capabilities: A `ValueCapability` object providing compatibility. Capabilities: A `ValueCapability` object providing compatibility.
# Value Data Flow # Value Data Flow
Simply passing values is the simplest and easiest use case. Simply passing values is the simplest and easiest use case.
This doesn't mean it's "dumb" - ex. a `sp.Expr` might, before use, have `simplify`, rewriting, unit cancellation, etc. run. This doesn't mean it's "dumb" - ex. a `sp.Expr` might, before use, have `simplify`, rewriting, unit cancellation, etc. run.
All of this is okay, as long as there is no *introduction of new data* ex. variable substitutions. All of this is okay, as long as there is no *introduction of new data* ex. variable substitutions.
# Lazy Value Data Flow # Lazy Value Data Flow
By passing (essentially) functions, one supports: By passing (essentially) functions, one supports:
- **Lightness**: While lazy values can be made expensive to construct, they will generally not be nearly as heavy to handle when trying to work with ex. operations on voxel arrays. - **Lightness**: While lazy values can be made expensive to construct, they will generally not be nearly as heavy to handle when trying to work with ex. operations on voxel arrays.
- **Performance**: Parameterizing ex. `sp.Expr` with variables allows one to build very optimized functions, which can make ex. node graph updates very fast if the only operation run is the `jax` JIT'ed function (aka. GPU accelerated) generated from the final full expression. - **Performance**: Parameterizing ex. `sp.Expr` with variables allows one to build very optimized functions, which can make ex. node graph updates very fast if the only operation run is the `jax` JIT'ed function (aka. GPU accelerated) generated from the final full expression.
- **Numerical Stability**: Libraries like `aesara` build a computational graph, which can be automatically rewritten to avoid many obvious conditioning / cancellation errors. - **Numerical Stability**: Libraries like `aesara` build a computational graph, which can be automatically rewritten to avoid many obvious conditioning / cancellation errors.
- **Lazy Output**: The goal of a node-graph may not be the definition of a single value, but rather, a parameterized expression for generating *many values* with known properties. This is especially interesting for use cases where one wishes to build an optimization step using nodes. - **Lazy Output**: The goal of a node-graph may not be the definition of a single value, but rather, a parameterized expression for generating *many values* with known properties. This is especially interesting for use cases where one wishes to build an optimization step using nodes.
# Capability Passing # Capability Passing
By being able to pass "capabilities" next to other kinds of values, nodes can quickly determine whether a given link is valid without having to actually compute it. By being able to pass "capabilities" next to other kinds of values, nodes can quickly determine whether a given link is valid without having to actually compute it.
# Lazy Parameter Value # Lazy Parameter Value
When using parameterized LazyValues, one may wish to independently pass parameter values through the graph, so they can be inserted into the final (cached) high-performance expression without. When using parameterized LazyValues, one may wish to independently pass parameter values through the graph, so they can be inserted into the final (cached) high-performance expression without.
The advantage of using a different data flow would be changing this kind of value would ONLY invalidate lazy parameter value caches, which would allow an incredibly fast path of getting the value into the lazy expression for high-performance computation. The advantage of using a different data flow would be changing this kind of value would ONLY invalidate lazy parameter value caches, which would allow an incredibly fast path of getting the value into the lazy expression for high-performance computation.
Implementation TBD - though, ostensibly, one would have a "parameter" node which both would only provide a LazyValue (aka. a symbolic variable), but would also be able to provide a LazyParamValue, which would be a particular value of some kind (probably via the `value` of some other node socket). Implementation TBD - though, ostensibly, one would have a "parameter" node which both would only provide a LazyValue (aka. a symbolic variable), but would also be able to provide a LazyParamValue, which would be a particular value of some kind (probably via the `value` of some other node socket).
""" """
Value = enum.auto() Value = enum.auto()
LazyValue = enum.auto() LazyValue = enum.auto()
Capabilities = enum.auto() Capabilities = enum.auto()
LazyParamValue = enum.auto() LazyParamValue = enum.auto()

View File

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

View File

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

View File

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

View File

@ -1,13 +1,12 @@
import enum import enum
from ....utils.blender_type_enum import ( from ....utils.blender_type_enum import BlenderTypeEnum, wrap_values_in_MT
BlenderTypeEnum, wrap_values_in_MT
)
@wrap_values_in_MT @wrap_values_in_MT
class NodeCategory(BlenderTypeEnum): class NodeCategory(BlenderTypeEnum):
MAXWELLSIM = enum.auto() MAXWELLSIM = enum.auto()
# Inputs/ # Inputs/
MAXWELLSIM_INPUTS = enum.auto() MAXWELLSIM_INPUTS = enum.auto()
MAXWELLSIM_INPUTS_IMPORTERS = enum.auto() MAXWELLSIM_INPUTS_IMPORTERS = enum.auto()
@ -15,63 +14,63 @@ class NodeCategory(BlenderTypeEnum):
MAXWELLSIM_INPUTS_PARAMETERS = enum.auto() MAXWELLSIM_INPUTS_PARAMETERS = enum.auto()
MAXWELLSIM_INPUTS_CONSTANTS = enum.auto() MAXWELLSIM_INPUTS_CONSTANTS = enum.auto()
MAXWELLSIM_INPUTS_LISTS = enum.auto() MAXWELLSIM_INPUTS_LISTS = enum.auto()
# Outputs/ # Outputs/
MAXWELLSIM_OUTPUTS = enum.auto() MAXWELLSIM_OUTPUTS = enum.auto()
MAXWELLSIM_OUTPUTS_VIEWERS = enum.auto() MAXWELLSIM_OUTPUTS_VIEWERS = enum.auto()
MAXWELLSIM_OUTPUTS_EXPORTERS = enum.auto() MAXWELLSIM_OUTPUTS_EXPORTERS = enum.auto()
MAXWELLSIM_OUTPUTS_PLOTTERS = enum.auto() MAXWELLSIM_OUTPUTS_PLOTTERS = enum.auto()
# Sources/ # Sources/
MAXWELLSIM_SOURCES = enum.auto() MAXWELLSIM_SOURCES = enum.auto()
MAXWELLSIM_SOURCES_TEMPORALSHAPES = enum.auto() MAXWELLSIM_SOURCES_TEMPORALSHAPES = enum.auto()
# Mediums/ # Mediums/
MAXWELLSIM_MEDIUMS = enum.auto() MAXWELLSIM_MEDIUMS = enum.auto()
MAXWELLSIM_MEDIUMS_NONLINEARITIES = enum.auto() MAXWELLSIM_MEDIUMS_NONLINEARITIES = enum.auto()
# Structures/ # Structures/
MAXWELLSIM_STRUCTURES = enum.auto() MAXWELLSIM_STRUCTURES = enum.auto()
MAXWELLSIM_STRUCTURES_PRIMITIVES = enum.auto() MAXWELLSIM_STRUCTURES_PRIMITIVES = enum.auto()
# Bounds/ # Bounds/
MAXWELLSIM_BOUNDS = enum.auto() MAXWELLSIM_BOUNDS = enum.auto()
MAXWELLSIM_BOUNDS_BOUNDCONDS = enum.auto() MAXWELLSIM_BOUNDS_BOUNDCONDS = enum.auto()
# Monitors/ # Monitors/
MAXWELLSIM_MONITORS = enum.auto() MAXWELLSIM_MONITORS = enum.auto()
MAXWELLSIM_MONITORS_NEARFIELDPROJECTIONS = enum.auto() MAXWELLSIM_MONITORS_NEARFIELDPROJECTIONS = enum.auto()
# Simulations/ # Simulations/
MAXWELLSIM_SIMS = enum.auto() MAXWELLSIM_SIMS = enum.auto()
MAXWELLSIM_SIMGRIDAXES = enum.auto() MAXWELLSIM_SIMGRIDAXES = enum.auto()
# Utilities/ # Utilities/
MAXWELLSIM_UTILITIES = enum.auto() MAXWELLSIM_UTILITIES = enum.auto()
MAXWELLSIM_UTILITIES_CONVERTERS = enum.auto() MAXWELLSIM_UTILITIES_CONVERTERS = enum.auto()
MAXWELLSIM_UTILITIES_OPERATIONS = enum.auto() MAXWELLSIM_UTILITIES_OPERATIONS = enum.auto()
# Viz/ # Viz/
MAXWELLSIM_VIZ = enum.auto() MAXWELLSIM_VIZ = enum.auto()
@classmethod @classmethod
def get_tree(cls): def get_tree(cls):
## TODO: Refactor ## TODO: Refactor
syllable_categories = [ syllable_categories = [
str(node_category.value).split("_") str(node_category.value).split('_')
for node_category in cls for node_category in cls
if node_category.value != "MAXWELLSIM" if node_category.value != 'MAXWELLSIM'
] ]
category_tree = {} category_tree = {}
for syllable_category in syllable_categories: for syllable_category in syllable_categories:
# Set Current Subtree to Root # Set Current Subtree to Root
current_category_subtree = category_tree current_category_subtree = category_tree
for i, syllable in enumerate(syllable_category): for i, syllable in enumerate(syllable_category):
# Create New Category Subtree and/or Step to Subtree # Create New Category Subtree and/or Step to Subtree
if syllable not in current_category_subtree: if syllable not in current_category_subtree:
current_category_subtree[syllable] = {} current_category_subtree[syllable] = {}
current_category_subtree = current_category_subtree[syllable] current_category_subtree = current_category_subtree[syllable]
return category_tree return category_tree

View File

@ -1,58 +1,58 @@
import enum import enum
from ....utils.blender_type_enum import ( from ....utils.blender_type_enum import (
BlenderTypeEnum, append_cls_name_to_values BlenderTypeEnum,
append_cls_name_to_values,
) )
@append_cls_name_to_values @append_cls_name_to_values
class NodeType(BlenderTypeEnum): class NodeType(BlenderTypeEnum):
KitchenSink = enum.auto() KitchenSink = enum.auto()
# Inputs # Inputs
UnitSystem = enum.auto() UnitSystem = enum.auto()
## Inputs / Scene ## Inputs / Scene
Time = enum.auto() Time = enum.auto()
## Inputs / Importers ## Inputs / Importers
Tidy3DWebImporter = enum.auto() Tidy3DWebImporter = enum.auto()
## Inputs / Parameters ## Inputs / Parameters
NumberParameter = enum.auto() NumberParameter = enum.auto()
PhysicalParameter = enum.auto() PhysicalParameter = enum.auto()
## Inputs / Constants ## Inputs / Constants
WaveConstant = enum.auto() WaveConstant = enum.auto()
ScientificConstant = enum.auto() ScientificConstant = enum.auto()
NumberConstant = enum.auto() NumberConstant = enum.auto()
PhysicalConstant = enum.auto() PhysicalConstant = enum.auto()
BlenderConstant = enum.auto() BlenderConstant = enum.auto()
## Inputs / Lists ## Inputs / Lists
RealList = enum.auto() RealList = enum.auto()
ComplexList = enum.auto() ComplexList = enum.auto()
## Inputs / ## Inputs /
InputFile = enum.auto() InputFile = enum.auto()
# Outputs # Outputs
## Outputs / Viewers ## Outputs / Viewers
Viewer = enum.auto() Viewer = enum.auto()
ValueViewer = enum.auto() ValueViewer = enum.auto()
ConsoleViewer = enum.auto() ConsoleViewer = enum.auto()
## Outputs / Exporters ## Outputs / Exporters
JSONFileExporter = enum.auto() JSONFileExporter = enum.auto()
Tidy3DWebExporter = enum.auto() Tidy3DWebExporter = enum.auto()
# Sources # Sources
## Sources / Temporal Shapes ## Sources / Temporal Shapes
GaussianPulseTemporalShape = enum.auto() GaussianPulseTemporalShape = enum.auto()
ContinuousWaveTemporalShape = enum.auto() ContinuousWaveTemporalShape = enum.auto()
ListTemporalShape = enum.auto() ListTemporalShape = enum.auto()
## Sources / ## Sources /
PointDipoleSource = enum.auto() PointDipoleSource = enum.auto()
UniformCurrentSource = enum.auto() UniformCurrentSource = enum.auto()
@ -61,92 +61,86 @@ class NodeType(BlenderTypeEnum):
GaussianBeamSource = enum.auto() GaussianBeamSource = enum.auto()
AstigmaticGaussianBeamSource = enum.auto() AstigmaticGaussianBeamSource = enum.auto()
TFSFSource = enum.auto() TFSFSource = enum.auto()
EHEquivalenceSource = enum.auto() EHEquivalenceSource = enum.auto()
EHSource = enum.auto() EHSource = enum.auto()
# Mediums # Mediums
LibraryMedium = enum.auto() LibraryMedium = enum.auto()
PECMedium = enum.auto() PECMedium = enum.auto()
IsotropicMedium = enum.auto() IsotropicMedium = enum.auto()
AnisotropicMedium = enum.auto() AnisotropicMedium = enum.auto()
TripleSellmeierMedium = enum.auto() TripleSellmeierMedium = enum.auto()
SellmeierMedium = enum.auto() SellmeierMedium = enum.auto()
PoleResidueMedium = enum.auto() PoleResidueMedium = enum.auto()
DrudeMedium = enum.auto() DrudeMedium = enum.auto()
DrudeLorentzMedium = enum.auto() DrudeLorentzMedium = enum.auto()
DebyeMedium = enum.auto() DebyeMedium = enum.auto()
## Mediums / Non-Linearities ## Mediums / Non-Linearities
AddNonLinearity = enum.auto() AddNonLinearity = enum.auto()
ChiThreeSusceptibilityNonLinearity = enum.auto() ChiThreeSusceptibilityNonLinearity = enum.auto()
TwoPhotonAbsorptionNonLinearity = enum.auto() TwoPhotonAbsorptionNonLinearity = enum.auto()
KerrNonLinearity = enum.auto() KerrNonLinearity = enum.auto()
# Structures # Structures
ObjectStructure = enum.auto() ObjectStructure = enum.auto()
GeoNodesStructure = enum.auto() GeoNodesStructure = enum.auto()
ScriptedStructure = enum.auto() ScriptedStructure = enum.auto()
## Structures / Primitives ## Structures / Primitives
BoxStructure = enum.auto() BoxStructure = enum.auto()
SphereStructure = enum.auto() SphereStructure = enum.auto()
CylinderStructure = enum.auto() CylinderStructure = enum.auto()
# Bounds # Bounds
BoundConds = enum.auto() BoundConds = enum.auto()
## Bounds / Bound Faces ## Bounds / Bound Faces
PMLBoundCond = enum.auto() PMLBoundCond = enum.auto()
PECBoundCond = enum.auto() PECBoundCond = enum.auto()
PMCBoundCond = enum.auto() PMCBoundCond = enum.auto()
BlochBoundCond = enum.auto() BlochBoundCond = enum.auto()
PeriodicBoundCond = enum.auto() PeriodicBoundCond = enum.auto()
AbsorbingBoundCond = enum.auto() AbsorbingBoundCond = enum.auto()
# Monitors # Monitors
EHFieldMonitor = enum.auto() EHFieldMonitor = enum.auto()
FieldPowerFluxMonitor = enum.auto() FieldPowerFluxMonitor = enum.auto()
EpsilonTensorMonitor = enum.auto() EpsilonTensorMonitor = enum.auto()
DiffractionMonitor = enum.auto() DiffractionMonitor = enum.auto()
## Monitors / Near-Field Projections ## Monitors / Near-Field Projections
CartesianNearFieldProjectionMonitor = enum.auto() CartesianNearFieldProjectionMonitor = enum.auto()
ObservationAngleNearFieldProjectionMonitor = enum.auto() ObservationAngleNearFieldProjectionMonitor = enum.auto()
KSpaceNearFieldProjectionMonitor = enum.auto() KSpaceNearFieldProjectionMonitor = enum.auto()
# Sims # Sims
SimDomain = enum.auto() SimDomain = enum.auto()
SimGrid = enum.auto() SimGrid = enum.auto()
## Sims / Sim Grid Axis ## Sims / Sim Grid Axis
AutomaticSimGridAxis = enum.auto() AutomaticSimGridAxis = enum.auto()
ManualSimGridAxis = enum.auto() ManualSimGridAxis = enum.auto()
UniformSimGridAxis = enum.auto() UniformSimGridAxis = enum.auto()
ArraySimGridAxis = enum.auto() ArraySimGridAxis = enum.auto()
## Sim / ## Sim /
FDTDSim = enum.auto() FDTDSim = enum.auto()
# Utilities # Utilities
Combine = enum.auto() Combine = enum.auto()
Separate = enum.auto() Separate = enum.auto()
Math = enum.auto() Math = enum.auto()
## Utilities / Converters ## Utilities / Converters
WaveConverter = enum.auto() WaveConverter = enum.auto()
## Utilities / Operations ## Utilities / Operations
ArrayOperation = enum.auto() ArrayOperation = enum.auto()
# Viz # Viz
FDTDSimDataViz = enum.auto() FDTDSimDataViz = enum.auto()

View File

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

View File

@ -6,6 +6,7 @@ import pydantic as pyd
from ..bl import PresetName, SocketName, BLEnumID from ..bl import PresetName, SocketName, BLEnumID
from .managed_obj import ManagedObj from .managed_obj import ManagedObj
class ManagedObjDef(pyd.BaseModel): class ManagedObjDef(pyd.BaseModel):
mk: typ.Callable[[str], ManagedObj] 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 from ..bl import PresetName, SocketName, BLEnumID
class PresetDef(pyd.BaseModel): class PresetDef(pyd.BaseModel):
label: PresetName label: PresetName
description: str description: str

View File

@ -4,9 +4,9 @@ import bpy
from ..socket_types import SocketType from ..socket_types import SocketType
@typ.runtime_checkable @typ.runtime_checkable
class SocketDef(typ.Protocol): class SocketDef(typ.Protocol):
socket_type: SocketType 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.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.String: (0.7, 0.7, 0.7, 1.0), # Medium Light Grey
ST.FilePath: (0.6, 0.6, 0.6, 1.0), # Medium Grey ST.FilePath: (0.6, 0.6, 0.6, 1.0), # Medium Grey
# Number # Number
ST.IntegerNumber: (0.5, 0.5, 1.0, 1.0), # Light Blue 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.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.RealNumber: (0.3, 0.3, 0.8, 1.0), # Medium Blue
ST.ComplexNumber: (0.2, 0.2, 0.7, 1.0), # Dark Blue ST.ComplexNumber: (0.2, 0.2, 0.7, 1.0), # Dark Blue
# Vector # Vector
ST.Integer2DVector: (0.5, 1.0, 0.5, 1.0), # Light Green ST.Integer2DVector: (0.5, 1.0, 0.5, 1.0), # Light Green
ST.Real2DVector: (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.Integer3DVector: (0.3, 0.8, 0.3, 1.0), # Medium Green
ST.Real3DVector: (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 ST.Complex3DVector: (0.2, 0.7, 0.2, 1.0), # Dark Green
# Physical # Physical
ST.PhysicalUnitSystem: (1.0, 0.5, 0.5, 1.0), # Light Red ST.PhysicalUnitSystem: (1.0, 0.5, 0.5, 1.0), # Light Red
ST.PhysicalTime: (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.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.PhysicalPol: (0.5, 0.4, 0.2, 1.0), # Dark Orange
ST.PhysicalFreq: (1.0, 0.7, 0.5, 1.0), # Light Peach ST.PhysicalFreq: (1.0, 0.7, 0.5, 1.0), # Light Peach
# Blender # Blender
ST.BlenderObject: (0.7, 0.5, 1.0, 1.0), # Light Purple 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.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.BlenderImage: (0.5, 0.4, 0.8, 1.0), # Medium Purple
ST.BlenderGeoNodes: (0.3, 0.3, 0.6, 1.0), # Dark 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 ST.BlenderText: (0.5, 0.5, 0.75, 1.0), # Light Lavender
# Maxwell # Maxwell
ST.MaxwellSource: (1.0, 1.0, 0.5, 1.0), # Light Yellow 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 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.MaxwellSimGrid: (0.5, 0.4, 0.3, 1.0), # Dark Gold
ST.MaxwellSimGridAxis: (0.4, 0.3, 0.25, 1.0), # Darkest 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 ST.MaxwellSimDomain: (0.4, 0.3, 0.25, 1.0), # Darkest Gold
# Tidy3D # Tidy3D
ST.Tidy3DCloudTask: (0.4, 0.3, 0.25, 1.0), # Darkest Gold 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 from .socket_types import SocketType as ST
BL_SOCKET_DESCR_ANNOT_STRING = ":: " BL_SOCKET_DESCR_ANNOT_STRING = ':: '
BL_SOCKET_DESCR_TYPE_MAP = { BL_SOCKET_DESCR_TYPE_MAP = {
("Time", "NodeSocketFloat", 1): ST.PhysicalTime, ('Time', 'NodeSocketFloat', 1): ST.PhysicalTime,
('Angle', 'NodeSocketFloat', 1): ST.PhysicalAngle,
("Angle", "NodeSocketFloat", 1): ST.PhysicalAngle, ('SolidAngle', 'NodeSocketFloat', 1): ST.PhysicalSolidAngle,
("SolidAngle", "NodeSocketFloat", 1): ST.PhysicalSolidAngle, ('Rotation', 'NodeSocketVector', 2): ST.PhysicalRot2D,
('Rotation', 'NodeSocketVector', 3): ST.PhysicalRot3D,
("Rotation", "NodeSocketVector", 2): ST.PhysicalRot2D, ('Freq', 'NodeSocketFloat', 1): ST.PhysicalFreq,
("Rotation", "NodeSocketVector", 3): ST.PhysicalRot3D, ('AngFreq', 'NodeSocketFloat', 1): ST.PhysicalAngFreq,
("Freq", "NodeSocketFloat", 1): ST.PhysicalFreq,
("AngFreq", "NodeSocketFloat", 1): ST.PhysicalAngFreq,
## Cartesian ## Cartesian
("Length", "NodeSocketFloat", 1): ST.PhysicalLength, ('Length', 'NodeSocketFloat', 1): ST.PhysicalLength,
("Area", "NodeSocketFloat", 1): ST.PhysicalArea, ('Area', 'NodeSocketFloat', 1): ST.PhysicalArea,
("Volume", "NodeSocketFloat", 1): ST.PhysicalVolume, ('Volume', 'NodeSocketFloat', 1): ST.PhysicalVolume,
('Disp', 'NodeSocketVector', 2): ST.PhysicalDisp2D,
("Disp", "NodeSocketVector", 2): ST.PhysicalDisp2D, ('Disp', 'NodeSocketVector', 3): ST.PhysicalDisp3D,
("Disp", "NodeSocketVector", 3): ST.PhysicalDisp3D, ('Point', 'NodeSocketFloat', 1): ST.PhysicalPoint1D,
('Point', 'NodeSocketVector', 2): ST.PhysicalPoint2D,
("Point", "NodeSocketFloat", 1): ST.PhysicalPoint1D, ('Point', 'NodeSocketVector', 3): ST.PhysicalPoint3D,
("Point", "NodeSocketVector", 2): ST.PhysicalPoint2D, ('Size', 'NodeSocketVector', 2): ST.PhysicalSize2D,
("Point", "NodeSocketVector", 3): ST.PhysicalPoint3D, ('Size', 'NodeSocketVector', 3): ST.PhysicalSize3D,
("Size", "NodeSocketVector", 2): ST.PhysicalSize2D,
("Size", "NodeSocketVector", 3): ST.PhysicalSize3D,
## Mechanical ## Mechanical
("Mass", "NodeSocketFloat", 1): ST.PhysicalMass, ('Mass', 'NodeSocketFloat', 1): ST.PhysicalMass,
('Speed', 'NodeSocketFloat', 1): ST.PhysicalSpeed,
("Speed", "NodeSocketFloat", 1): ST.PhysicalSpeed, ('Vel', 'NodeSocketVector', 2): ST.PhysicalVel2D,
("Vel", "NodeSocketVector", 2): ST.PhysicalVel2D, ('Vel', 'NodeSocketVector', 3): ST.PhysicalVel3D,
("Vel", "NodeSocketVector", 3): ST.PhysicalVel3D, ('Accel', 'NodeSocketFloat', 1): ST.PhysicalAccelScalar,
("Accel", "NodeSocketFloat", 1): ST.PhysicalAccelScalar, ('Accel', 'NodeSocketVector', 2): ST.PhysicalAccel2D,
("Accel", "NodeSocketVector", 2): ST.PhysicalAccel2D, ('Accel', 'NodeSocketVector', 3): ST.PhysicalAccel3D,
("Accel", "NodeSocketVector", 3): ST.PhysicalAccel3D, ('Force', 'NodeSocketFloat', 1): ST.PhysicalForceScalar,
("Force", "NodeSocketFloat", 1): ST.PhysicalForceScalar, ('Force', 'NodeSocketVector', 2): ST.PhysicalForce2D,
("Force", "NodeSocketVector", 2): ST.PhysicalForce2D, ('Force', 'NodeSocketVector', 3): ST.PhysicalForce3D,
("Force", "NodeSocketVector", 3): ST.PhysicalForce3D, ('Pressure', 'NodeSocketFloat', 1): ST.PhysicalPressure,
("Pressure", "NodeSocketFloat", 1): ST.PhysicalPressure,
## Energetic ## Energetic
("Energy", "NodeSocketFloat", 1): ST.PhysicalEnergy, ('Energy', 'NodeSocketFloat', 1): ST.PhysicalEnergy,
("Power", "NodeSocketFloat", 1): ST.PhysicalPower, ('Power', 'NodeSocketFloat', 1): ST.PhysicalPower,
("Temp", "NodeSocketFloat", 1): ST.PhysicalTemp, ('Temp', 'NodeSocketFloat', 1): ST.PhysicalTemp,
## ELectrodynamical ## ELectrodynamical
("Curr", "NodeSocketFloat", 1): ST.PhysicalCurr, ('Curr', 'NodeSocketFloat', 1): ST.PhysicalCurr,
("CurrDens", "NodeSocketVector", 2): ST.PhysicalCurrDens2D, ('CurrDens', 'NodeSocketVector', 2): ST.PhysicalCurrDens2D,
("CurrDens", "NodeSocketVector", 3): ST.PhysicalCurrDens3D, ('CurrDens', 'NodeSocketVector', 3): ST.PhysicalCurrDens3D,
('Charge', 'NodeSocketFloat', 1): ST.PhysicalCharge,
("Charge", "NodeSocketFloat", 1): ST.PhysicalCharge, ('Voltage', 'NodeSocketFloat', 1): ST.PhysicalVoltage,
("Voltage", "NodeSocketFloat", 1): ST.PhysicalVoltage, ('Capacitance', 'NodeSocketFloat', 1): ST.PhysicalCapacitance,
("Capacitance", "NodeSocketFloat", 1): ST.PhysicalCapacitance, ('Resistance', 'NodeSocketFloat', 1): ST.PhysicalResistance,
("Resistance", "NodeSocketFloat", 1): ST.PhysicalResistance, ('Conductance', 'NodeSocketFloat', 1): ST.PhysicalConductance,
("Conductance", "NodeSocketFloat", 1): ST.PhysicalConductance, ('MagFlux', 'NodeSocketFloat', 1): ST.PhysicalMagFlux,
('MagFluxDens', 'NodeSocketFloat', 1): ST.PhysicalMagFluxDens,
("MagFlux", "NodeSocketFloat", 1): ST.PhysicalMagFlux, ('Inductance', 'NodeSocketFloat', 1): ST.PhysicalInductance,
("MagFluxDens", "NodeSocketFloat", 1): ST.PhysicalMagFluxDens, ('EField', 'NodeSocketFloat', 2): ST.PhysicalEField3D,
("Inductance", "NodeSocketFloat", 1): ST.PhysicalInductance, ('EField', 'NodeSocketFloat', 3): ST.PhysicalEField2D,
('HField', 'NodeSocketFloat', 2): ST.PhysicalHField3D,
("EField", "NodeSocketFloat", 2): ST.PhysicalEField3D, ('HField', 'NodeSocketFloat', 3): ST.PhysicalHField2D,
("EField", "NodeSocketFloat", 3): ST.PhysicalEField2D,
("HField", "NodeSocketFloat", 2): ST.PhysicalHField3D,
("HField", "NodeSocketFloat", 3): ST.PhysicalHField2D,
## Luminal ## Luminal
("LumIntensity", "NodeSocketFloat", 1): ST.PhysicalLumIntensity, ('LumIntensity', 'NodeSocketFloat', 1): ST.PhysicalLumIntensity,
("LumFlux", "NodeSocketFloat", 1): ST.PhysicalLumFlux, ('LumFlux', 'NodeSocketFloat', 1): ST.PhysicalLumFlux,
("Illuminance", "NodeSocketFloat", 1): ST.PhysicalIlluminance, ('Illuminance', 'NodeSocketFloat', 1): ST.PhysicalIlluminance,
## Optical ## Optical
("PolJones", "NodeSocketFloat", 2): ST.PhysicalPolJones, ('PolJones', 'NodeSocketFloat', 2): ST.PhysicalPolJones,
("Pol", "NodeSocketFloat", 4): ST.PhysicalPol, ('Pol', 'NodeSocketFloat', 4): ST.PhysicalPol,
} }

View File

@ -1,36 +1,33 @@
from .socket_types import SocketType as ST from .socket_types import SocketType as ST
BL_SOCKET_DIRECT_TYPE_MAP = { BL_SOCKET_DIRECT_TYPE_MAP = {
("NodeSocketString", 1): ST.String, ('NodeSocketString', 1): ST.String,
("NodeSocketBool", 1): ST.Bool, ('NodeSocketBool', 1): ST.Bool,
("NodeSocketCollection", 1): ST.BlenderCollection, ('NodeSocketCollection', 1): ST.BlenderCollection,
("NodeSocketImage", 1): ST.BlenderImage, ('NodeSocketImage', 1): ST.BlenderImage,
("NodeSocketObject", 1): ST.BlenderObject, ('NodeSocketObject', 1): ST.BlenderObject,
('NodeSocketFloat', 1): ST.RealNumber,
("NodeSocketFloat", 1): ST.RealNumber, # ("NodeSocketFloatAngle", 1): ST.PhysicalAngle,
#("NodeSocketFloatAngle", 1): ST.PhysicalAngle, # ("NodeSocketFloatDistance", 1): ST.PhysicalLength,
#("NodeSocketFloatDistance", 1): ST.PhysicalLength, ('NodeSocketFloatFactor', 1): ST.RealNumber,
("NodeSocketFloatFactor", 1): ST.RealNumber, ('NodeSocketFloatPercentage', 1): ST.RealNumber,
("NodeSocketFloatPercentage", 1): ST.RealNumber, # ("NodeSocketFloatTime", 1): ST.PhysicalTime,
#("NodeSocketFloatTime", 1): ST.PhysicalTime, # ("NodeSocketFloatTimeAbsolute", 1): ST.PhysicalTime,
#("NodeSocketFloatTimeAbsolute", 1): ST.PhysicalTime, ('NodeSocketInt', 1): ST.IntegerNumber,
('NodeSocketIntFactor', 1): ST.IntegerNumber,
("NodeSocketInt", 1): ST.IntegerNumber, ('NodeSocketIntPercentage', 1): ST.IntegerNumber,
("NodeSocketIntFactor", 1): ST.IntegerNumber, ('NodeSocketIntUnsigned', 1): ST.IntegerNumber,
("NodeSocketIntPercentage", 1): ST.IntegerNumber, ('NodeSocketRotation', 2): ST.PhysicalRot2D,
("NodeSocketIntUnsigned", 1): ST.IntegerNumber, ('NodeSocketColor', 3): ST.Color,
('NodeSocketVector', 2): ST.Real2DVector,
("NodeSocketRotation", 2): ST.PhysicalRot2D, ('NodeSocketVector', 3): ST.Real3DVector,
("NodeSocketColor", 3): ST.Color, # ("NodeSocketVectorAcceleration", 2): ST.PhysicalAccel2D,
("NodeSocketVector", 2): ST.Real2DVector, # ("NodeSocketVectorAcceleration", 3): ST.PhysicalAccel3D,
("NodeSocketVector", 3): ST.Real3DVector, # ("NodeSocketVectorDirection", 2): ST.Real2DVectorDir,
#("NodeSocketVectorAcceleration", 2): ST.PhysicalAccel2D, # ("NodeSocketVectorDirection", 3): ST.Real3DVectorDir,
#("NodeSocketVectorAcceleration", 3): ST.PhysicalAccel3D, ('NodeSocketVectorEuler', 2): ST.PhysicalRot2D,
#("NodeSocketVectorDirection", 2): ST.Real2DVectorDir, ('NodeSocketVectorEuler', 3): ST.PhysicalRot3D,
#("NodeSocketVectorDirection", 3): ST.Real3DVectorDir, # ("NodeSocketVectorTranslation", 3): ST.PhysicalDisp3D,
("NodeSocketVectorEuler", 2): ST.PhysicalRot2D, # ("NodeSocketVectorVelocity", 3): ST.PhysicalVel3D,
("NodeSocketVectorEuler", 3): ST.PhysicalRot3D, # ("NodeSocketVectorXYZ", 3): ST.PhysicalPoint3D,
#("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 = { SOCKET_SHAPES = {
# Basic # Basic
ST.Any: "CIRCLE", ST.Any: 'CIRCLE',
ST.Bool: "CIRCLE", ST.Bool: 'CIRCLE',
ST.String: "CIRCLE", ST.String: 'CIRCLE',
ST.FilePath: "CIRCLE", ST.FilePath: 'CIRCLE',
# Number # Number
ST.IntegerNumber: "CIRCLE", ST.IntegerNumber: 'CIRCLE',
ST.RationalNumber: "CIRCLE", ST.RationalNumber: 'CIRCLE',
ST.RealNumber: "CIRCLE", ST.RealNumber: 'CIRCLE',
ST.ComplexNumber: "CIRCLE", ST.ComplexNumber: 'CIRCLE',
# Vector # Vector
ST.Integer2DVector: "CIRCLE", ST.Integer2DVector: 'CIRCLE',
ST.Real2DVector: "CIRCLE", ST.Real2DVector: 'CIRCLE',
ST.Complex2DVector: "CIRCLE", ST.Complex2DVector: 'CIRCLE',
ST.Integer3DVector: "CIRCLE", ST.Integer3DVector: 'CIRCLE',
ST.Real3DVector: "CIRCLE", ST.Real3DVector: 'CIRCLE',
ST.Complex3DVector: "CIRCLE", ST.Complex3DVector: 'CIRCLE',
# Physical # Physical
ST.PhysicalUnitSystem: "CIRCLE", ST.PhysicalUnitSystem: 'CIRCLE',
ST.PhysicalTime: "CIRCLE", ST.PhysicalTime: 'CIRCLE',
ST.PhysicalAngle: "CIRCLE", ST.PhysicalAngle: 'CIRCLE',
ST.PhysicalLength: "CIRCLE", ST.PhysicalLength: 'CIRCLE',
ST.PhysicalArea: "CIRCLE", ST.PhysicalArea: 'CIRCLE',
ST.PhysicalVolume: "CIRCLE", ST.PhysicalVolume: 'CIRCLE',
ST.PhysicalPoint2D: "CIRCLE", ST.PhysicalPoint2D: 'CIRCLE',
ST.PhysicalPoint3D: "CIRCLE", ST.PhysicalPoint3D: 'CIRCLE',
ST.PhysicalSize2D: "CIRCLE", ST.PhysicalSize2D: 'CIRCLE',
ST.PhysicalSize3D: "CIRCLE", ST.PhysicalSize3D: 'CIRCLE',
ST.PhysicalMass: "CIRCLE", ST.PhysicalMass: 'CIRCLE',
ST.PhysicalSpeed: "CIRCLE", ST.PhysicalSpeed: 'CIRCLE',
ST.PhysicalAccelScalar: "CIRCLE", ST.PhysicalAccelScalar: 'CIRCLE',
ST.PhysicalForceScalar: "CIRCLE", ST.PhysicalForceScalar: 'CIRCLE',
ST.PhysicalAccel3D: "CIRCLE", ST.PhysicalAccel3D: 'CIRCLE',
ST.PhysicalForce3D: "CIRCLE", ST.PhysicalForce3D: 'CIRCLE',
ST.PhysicalPol: "CIRCLE", ST.PhysicalPol: 'CIRCLE',
ST.PhysicalFreq: "CIRCLE", ST.PhysicalFreq: 'CIRCLE',
# Blender # Blender
ST.BlenderObject: "DIAMOND", ST.BlenderObject: 'DIAMOND',
ST.BlenderCollection: "DIAMOND", ST.BlenderCollection: 'DIAMOND',
ST.BlenderImage: "DIAMOND", ST.BlenderImage: 'DIAMOND',
ST.BlenderGeoNodes: "DIAMOND", ST.BlenderGeoNodes: 'DIAMOND',
ST.BlenderText: "DIAMOND", ST.BlenderText: 'DIAMOND',
# Maxwell # Maxwell
ST.MaxwellSource: "CIRCLE", ST.MaxwellSource: 'CIRCLE',
ST.MaxwellTemporalShape: "CIRCLE", ST.MaxwellTemporalShape: 'CIRCLE',
ST.MaxwellMedium: "CIRCLE", ST.MaxwellMedium: 'CIRCLE',
ST.MaxwellMediumNonLinearity: "CIRCLE", ST.MaxwellMediumNonLinearity: 'CIRCLE',
ST.MaxwellStructure: "CIRCLE", ST.MaxwellStructure: 'CIRCLE',
ST.MaxwellBoundConds: "CIRCLE", ST.MaxwellBoundConds: 'CIRCLE',
ST.MaxwellBoundCond: "CIRCLE", ST.MaxwellBoundCond: 'CIRCLE',
ST.MaxwellMonitor: "CIRCLE", ST.MaxwellMonitor: 'CIRCLE',
ST.MaxwellFDTDSim: "CIRCLE", ST.MaxwellFDTDSim: 'CIRCLE',
ST.MaxwellFDTDSimData: "CIRCLE", ST.MaxwellFDTDSimData: 'CIRCLE',
ST.MaxwellSimGrid: "CIRCLE", ST.MaxwellSimGrid: 'CIRCLE',
ST.MaxwellSimGridAxis: "CIRCLE", ST.MaxwellSimGridAxis: 'CIRCLE',
ST.MaxwellSimDomain: "CIRCLE", ST.MaxwellSimDomain: 'CIRCLE',
# Tidy3D # Tidy3D
ST.Tidy3DCloudTask: "DIAMOND", ST.Tidy3DCloudTask: 'DIAMOND',
} }

View File

@ -1,9 +1,12 @@
import enum import enum
from ....utils.blender_type_enum import ( 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 @append_cls_name_to_values
class SocketType(BlenderTypeEnum): class SocketType(BlenderTypeEnum):
# Base # Base
@ -12,43 +15,43 @@ class SocketType(BlenderTypeEnum):
String = enum.auto() String = enum.auto()
FilePath = enum.auto() FilePath = enum.auto()
Color = enum.auto() Color = enum.auto()
# Number # Number
IntegerNumber = enum.auto() IntegerNumber = enum.auto()
RationalNumber = enum.auto() RationalNumber = enum.auto()
RealNumber = enum.auto() RealNumber = enum.auto()
ComplexNumber = enum.auto() ComplexNumber = enum.auto()
# Vector # Vector
Integer2DVector = enum.auto() Integer2DVector = enum.auto()
Real2DVector = enum.auto() Real2DVector = enum.auto()
Real2DVectorDir = enum.auto() Real2DVectorDir = enum.auto()
Complex2DVector = enum.auto() Complex2DVector = enum.auto()
Integer3DVector = enum.auto() Integer3DVector = enum.auto()
Real3DVector = enum.auto() Real3DVector = enum.auto()
Real3DVectorDir = enum.auto() Real3DVectorDir = enum.auto()
Complex3DVector = enum.auto() Complex3DVector = enum.auto()
# Blender # Blender
BlenderObject = enum.auto() BlenderObject = enum.auto()
BlenderCollection = enum.auto() BlenderCollection = enum.auto()
BlenderImage = enum.auto() BlenderImage = enum.auto()
BlenderGeoNodes = enum.auto() BlenderGeoNodes = enum.auto()
BlenderText = enum.auto() BlenderText = enum.auto()
# Maxwell # Maxwell
MaxwellBoundConds = enum.auto() MaxwellBoundConds = enum.auto()
MaxwellBoundCond = enum.auto() MaxwellBoundCond = enum.auto()
MaxwellMedium = enum.auto() MaxwellMedium = enum.auto()
MaxwellMediumNonLinearity = enum.auto() MaxwellMediumNonLinearity = enum.auto()
MaxwellSource = enum.auto() MaxwellSource = enum.auto()
MaxwellTemporalShape = enum.auto() MaxwellTemporalShape = enum.auto()
MaxwellStructure = enum.auto() MaxwellStructure = enum.auto()
MaxwellMonitor = enum.auto() MaxwellMonitor = enum.auto()
@ -57,42 +60,42 @@ class SocketType(BlenderTypeEnum):
MaxwellSimDomain = enum.auto() MaxwellSimDomain = enum.auto()
MaxwellSimGrid = enum.auto() MaxwellSimGrid = enum.auto()
MaxwellSimGridAxis = enum.auto() MaxwellSimGridAxis = enum.auto()
# Tidy3D # Tidy3D
Tidy3DCloudTask = enum.auto() Tidy3DCloudTask = enum.auto()
# Physical # Physical
PhysicalUnitSystem = enum.auto() PhysicalUnitSystem = enum.auto()
PhysicalTime = enum.auto() PhysicalTime = enum.auto()
PhysicalAngle = enum.auto() PhysicalAngle = enum.auto()
PhysicalSolidAngle = enum.auto() PhysicalSolidAngle = enum.auto()
PhysicalRot2D = enum.auto() PhysicalRot2D = enum.auto()
PhysicalRot3D = enum.auto() PhysicalRot3D = enum.auto()
PhysicalFreq = enum.auto() PhysicalFreq = enum.auto()
PhysicalAngFreq = enum.auto() PhysicalAngFreq = enum.auto()
## Cartesian ## Cartesian
PhysicalLength = enum.auto() PhysicalLength = enum.auto()
PhysicalArea = enum.auto() PhysicalArea = enum.auto()
PhysicalVolume = enum.auto() PhysicalVolume = enum.auto()
PhysicalDisp2D = enum.auto() PhysicalDisp2D = enum.auto()
PhysicalDisp3D = enum.auto() PhysicalDisp3D = enum.auto()
PhysicalPoint1D = enum.auto() PhysicalPoint1D = enum.auto()
PhysicalPoint2D = enum.auto() PhysicalPoint2D = enum.auto()
PhysicalPoint3D = enum.auto() PhysicalPoint3D = enum.auto()
PhysicalSize2D = enum.auto() PhysicalSize2D = enum.auto()
PhysicalSize3D = enum.auto() PhysicalSize3D = enum.auto()
## Mechanical ## Mechanical
PhysicalMass = enum.auto() PhysicalMass = enum.auto()
PhysicalSpeed = enum.auto() PhysicalSpeed = enum.auto()
PhysicalVel2D = enum.auto() PhysicalVel2D = enum.auto()
PhysicalVel3D = enum.auto() PhysicalVel3D = enum.auto()
@ -103,37 +106,37 @@ class SocketType(BlenderTypeEnum):
PhysicalForce2D = enum.auto() PhysicalForce2D = enum.auto()
PhysicalForce3D = enum.auto() PhysicalForce3D = enum.auto()
PhysicalPressure = enum.auto() PhysicalPressure = enum.auto()
## Energetic ## Energetic
PhysicalEnergy = enum.auto() PhysicalEnergy = enum.auto()
PhysicalPower = enum.auto() PhysicalPower = enum.auto()
PhysicalTemp = enum.auto() PhysicalTemp = enum.auto()
## Electrodynamical ## Electrodynamical
PhysicalCurr = enum.auto() PhysicalCurr = enum.auto()
PhysicalCurrDens2D = enum.auto() PhysicalCurrDens2D = enum.auto()
PhysicalCurrDens3D = enum.auto() PhysicalCurrDens3D = enum.auto()
PhysicalCharge = enum.auto() PhysicalCharge = enum.auto()
PhysicalVoltage = enum.auto() PhysicalVoltage = enum.auto()
PhysicalCapacitance = enum.auto() PhysicalCapacitance = enum.auto()
PhysicalResistance = enum.auto() PhysicalResistance = enum.auto()
PhysicalConductance = enum.auto() PhysicalConductance = enum.auto()
PhysicalMagFlux = enum.auto() PhysicalMagFlux = enum.auto()
PhysicalMagFluxDens = enum.auto() PhysicalMagFluxDens = enum.auto()
PhysicalInductance = enum.auto() PhysicalInductance = enum.auto()
PhysicalEField2D = enum.auto() PhysicalEField2D = enum.auto()
PhysicalEField3D = enum.auto() PhysicalEField3D = enum.auto()
PhysicalHField2D = enum.auto() PhysicalHField2D = enum.auto()
PhysicalHField3D = enum.auto() PhysicalHField3D = enum.auto()
## Luminal ## Luminal
PhysicalLumIntensity = enum.auto() PhysicalLumIntensity = enum.auto()
PhysicalLumFlux = enum.auto() PhysicalLumFlux = enum.auto()
PhysicalIlluminance = enum.auto() PhysicalIlluminance = enum.auto()
## Optical ## Optical
PhysicalPolJones = enum.auto() PhysicalPolJones = enum.auto()
PhysicalPol = enum.auto() PhysicalPol = enum.auto()

View File

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

View File

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

View File

@ -10,48 +10,49 @@ import bpy
from .. import contracts as ct from .. import contracts as ct
AREA_TYPE = "IMAGE_EDITOR" AREA_TYPE = 'IMAGE_EDITOR'
SPACE_TYPE = "IMAGE_EDITOR" SPACE_TYPE = 'IMAGE_EDITOR'
class ManagedBLImage(ct.schemas.ManagedObj): class ManagedBLImage(ct.schemas.ManagedObj):
managed_obj_type = ct.ManagedObjType.ManagedBLImage managed_obj_type = ct.ManagedObjType.ManagedBLImage
_bl_image_name: str _bl_image_name: str
def __init__(self, name: str): def __init__(self, name: str):
## TODO: Check that blender doesn't have any other images by the same name. ## TODO: Check that blender doesn't have any other images by the same name.
self._bl_image_name = name self._bl_image_name = name
@property @property
def name(self): def name(self):
return self._bl_image_name return self._bl_image_name
@name.setter @name.setter
def name(self, value: str): def name(self, value: str):
# Image Doesn't Exist # Image Doesn't Exist
if not (bl_image := bpy.data.images.get(self._bl_image_name)): if not (bl_image := bpy.data.images.get(self._bl_image_name)):
# ...AND Desired Image Name is Not Taken # ...AND Desired Image Name is Not Taken
if not bpy.data.objects.get(value): if not bpy.data.objects.get(value):
self._bl_image_name = value self._bl_image_name = value
return return
# ...AND Desired Image Name is Taken # ...AND Desired Image Name is Taken
else: else:
msg = f"Desired name {value} for BL image is taken" msg = f'Desired name {value} for BL image is taken'
raise ValueError(msg) raise ValueError(msg)
# Object DOES Exist # Object DOES Exist
bl_image.name = value bl_image.name = value
self._bl_image_name = bl_image.name self._bl_image_name = bl_image.name
## - When name exists, Blender adds .### to prevent overlap. ## - When name exists, Blender adds .### to prevent overlap.
## - `set_name` is allowed to change the name; nodes account for this. ## - `set_name` is allowed to change the name; nodes account for this.
def free(self): def free(self):
if not (bl_image := bpy.data.images.get(self.name)): if not (bl_image := bpy.data.images.get(self.name)):
msg = "Can't free BL image that doesn't exist" msg = "Can't free BL image that doesn't exist"
raise ValueError(msg) raise ValueError(msg)
bpy.data.images.remove(bl_image) bpy.data.images.remove(bl_image)
#################### ####################
# - Managed Object Management # - Managed Object Management
#################### ####################
@ -59,27 +60,24 @@ class ManagedBLImage(ct.schemas.ManagedObj):
self, self,
width_px: int, width_px: int,
height_px: int, height_px: int,
color_model: typx.Literal["RGB", "RGBA"], color_model: typx.Literal['RGB', 'RGBA'],
dtype: typx.Literal["uint8", "float32"], dtype: typx.Literal['uint8', 'float32'],
): ):
"""Returns the managed blender image. """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. 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) # Remove Image (if mismatch)
if ( if (bl_image := bpy.data.images.get(self.name)) and (
(bl_image := bpy.data.images.get(self.name)) bl_image.size[0] != width_px
and ( or bl_image.size[1] != height_px
bl_image.size[0] != width_px or bl_image.channels != channels
or bl_image.size[1] != height_px or bl_image.is_float ^ (dtype == 'float32')
or bl_image.channels != channels
or bl_image.is_float ^ (dtype == "float32")
)
): ):
self.free() self.free()
# Create Image w/Geometry (if none exists) # Create Image w/Geometry (if none exists)
if not (bl_image := bpy.data.images.get(self.name)): if not (bl_image := bpy.data.images.get(self.name)):
bl_image = bpy.data.images.new( bl_image = bpy.data.images.new(
@ -87,9 +85,9 @@ class ManagedBLImage(ct.schemas.ManagedObj):
width=width_px, width=width_px,
height=height_px, height=height_px,
) )
return bl_image return bl_image
#################### ####################
# - Editor UX Manipulation # - Editor UX Manipulation
#################### ####################
@ -99,25 +97,23 @@ class ManagedBLImage(ct.schemas.ManagedObj):
If none are valid, return None. If none are valid, return None.
""" """
valid_areas = [ valid_areas = [
area area for area in bpy.context.screen.areas if area.type == AREA_TYPE
for area in bpy.context.screen.areas
if area.type == AREA_TYPE
] ]
if valid_areas: if valid_areas:
return valid_areas[0] return valid_areas[0]
@property @property
def preview_space(self) -> bpy.types.SpaceProperties: def preview_space(self) -> bpy.types.SpaceProperties:
"""Returns the visible preview space in the visible preview area of """Returns the visible preview space in the visible preview area of
the Blender UI the Blender UI
""" """
if (preview_area := self.preview_area): if preview_area := self.preview_area:
return next( return next(
space space
for space in preview_area.spaces for space in preview_area.spaces
if space.type == SPACE_TYPE if space.type == SPACE_TYPE
) )
#################### ####################
# - Actions # - Actions
#################### ####################
@ -125,9 +121,9 @@ class ManagedBLImage(ct.schemas.ManagedObj):
"""Synchronizes the managed object to the preview, by manipulating """Synchronizes the managed object to the preview, by manipulating
relevant editors. 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 self.preview_space.image = bl_image
#################### ####################
# - Special Methods # - Special Methods
#################### ####################
@ -140,37 +136,37 @@ class ManagedBLImage(ct.schemas.ManagedObj):
bl_select: bool = False, bl_select: bool = False,
): ):
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
# Compute Image Geometry # Compute Image Geometry
if (preview_area := self.preview_area): if preview_area := self.preview_area:
# Retrieve DPI from Blender Preferences # Retrieve DPI from Blender Preferences
_dpi = bpy.context.preferences.system.dpi _dpi = bpy.context.preferences.system.dpi
# Retrieve Image Geometry from Area # Retrieve Image Geometry from Area
width_px = preview_area.width width_px = preview_area.width
height_px = preview_area.height height_px = preview_area.height
# Compute Inches # Compute Inches
_width_inches = width_px / _dpi _width_inches = width_px / _dpi
_height_inches = height_px / _dpi _height_inches = height_px / _dpi
elif width_inches and height_inches and dpi: elif width_inches and height_inches and dpi:
# Copy Parameters # Copy Parameters
_dpi = dpi _dpi = dpi
_width_inches = height_inches _width_inches = height_inches
_height_inches = height_inches _height_inches = height_inches
# Compute Pixel Geometry # Compute Pixel Geometry
width_px = int(_width_inches * _dpi) width_px = int(_width_inches * _dpi)
height_px = int(_height_inches * _dpi) height_px = int(_height_inches * _dpi)
else: 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) raise ValueError(msg)
# Compute Plot Dimensions # Compute Plot Dimensions
aspect_ratio = _width_inches / _height_inches aspect_ratio = _width_inches / _height_inches
# Create MPL Figure, Axes, and Compute Figure Geometry # Create MPL Figure, Axes, and Compute Figure Geometry
fig, ax = plt.subplots( fig, ax = plt.subplots(
figsize=[_width_inches, _height_inches], figsize=[_width_inches, _height_inches],
@ -179,10 +175,10 @@ class ManagedBLImage(ct.schemas.ManagedObj):
ax.set_aspect(aspect_ratio) ax.set_aspect(aspect_ratio)
cmp_width_px, cmp_height_px = fig.canvas.get_width_height() cmp_width_px, cmp_height_px = fig.canvas.get_width_height()
## Use computed pixel w/h to preempt off-by-one size errors. ## Use computed pixel w/h to preempt off-by-one size errors.
# Plot w/User Parameter # Plot w/User Parameter
func_plotter(ax) func_plotter(ax)
# Save Figure to BytesIO # Save Figure to BytesIO
with io.BytesIO() as buff: with io.BytesIO() as buff:
fig.savefig(buff, format='raw', dpi=dpi) fig.savefig(buff, format='raw', dpi=dpi)
@ -191,15 +187,14 @@ class ManagedBLImage(ct.schemas.ManagedObj):
buff.getvalue(), buff.getvalue(),
dtype=np.uint8, dtype=np.uint8,
).reshape([cmp_height_px, cmp_width_px, -1]) ).reshape([cmp_height_px, cmp_width_px, -1])
image_data = np.flipud(image_data).astype(np.float32) / 255 image_data = np.flipud(image_data).astype(np.float32) / 255
plt.close(fig) plt.close(fig)
# Optimized Write to Blender Image # 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.pixels.foreach_set(image_data.ravel())
bl_image.update() bl_image.update()
if bl_select: if bl_select:
self.bl_select() self.bl_select()

View File

@ -13,13 +13,14 @@ import bmesh
from .. import contracts as ct from .. import contracts as ct
ModifierType = typx.Literal["NODES", "ARRAY"] ModifierType = typx.Literal['NODES', 'ARRAY']
MODIFIER_NAMES = { MODIFIER_NAMES = {
"NODES": "BLMaxwell_GeoNodes", 'NODES': 'BLMaxwell_GeoNodes',
"ARRAY": "BLMaxwell_Array", 'ARRAY': 'BLMaxwell_Array',
} }
MANAGED_COLLECTION_NAME = "BLMaxwell" MANAGED_COLLECTION_NAME = 'BLMaxwell'
PREVIEW_COLLECTION_NAME = "BLMaxwell Visible" PREVIEW_COLLECTION_NAME = 'BLMaxwell Visible'
def bl_collection( def bl_collection(
collection_name: str, view_layer_exclude: bool collection_name: str, view_layer_exclude: bool
@ -27,113 +28,133 @@ def bl_collection(
# Init the "Managed Collection" # Init the "Managed Collection"
# Ensure Collection exists (and is in the Scene collection) # Ensure Collection exists (and is in the Scene collection)
if collection_name not in bpy.data.collections: if collection_name not in bpy.data.collections:
collection = bpy.data.collections.new(collection_name) collection = bpy.data.collections.new(collection_name)
bpy.context.scene.collection.children.link(collection) bpy.context.scene.collection.children.link(collection)
else: else:
collection = bpy.data.collections[collection_name] collection = bpy.data.collections[collection_name]
## Ensure synced View Layer exclusion ## Ensure synced View Layer exclusion
if (layer_collection := bpy.context.view_layer.layer_collection.children[ if (
collection_name layer_collection := bpy.context.view_layer.layer_collection.children[
]).exclude != view_layer_exclude: collection_name
]
).exclude != view_layer_exclude:
layer_collection.exclude = view_layer_exclude layer_collection.exclude = view_layer_exclude
return collection return collection
class ManagedBLObject(ct.schemas.ManagedObj): class ManagedBLObject(ct.schemas.ManagedObj):
managed_obj_type = ct.ManagedObjType.ManagedBLObject managed_obj_type = ct.ManagedObjType.ManagedBLObject
_bl_object_name: str _bl_object_name: str
def __init__(self, name: str): def __init__(self, name: str):
self._bl_object_name = name self._bl_object_name = name
# Object Name # Object Name
@property @property
def name(self): def name(self):
return self._bl_object_name return self._bl_object_name
@name.setter @name.setter
def set_name(self, value: str) -> None: def set_name(self, value: str) -> None:
# Object Doesn't Exist # Object Doesn't Exist
if not (bl_object := bpy.data.objects.get(self._bl_object_name)): if not (bl_object := bpy.data.objects.get(self._bl_object_name)):
# ...AND Desired Object Name is Not Taken # ...AND Desired Object Name is Not Taken
if not bpy.data.objects.get(value): if not bpy.data.objects.get(value):
self._bl_object_name = value self._bl_object_name = value
return return
# ...AND Desired Object Name is Taken # ...AND Desired Object Name is Taken
else: else:
msg = f"Desired name {value} for BL object is taken" msg = f'Desired name {value} for BL object is taken'
raise ValueError(msg) raise ValueError(msg)
# Object DOES Exist # Object DOES Exist
bl_object.name = value bl_object.name = value
self._bl_object_name = bl_object.name self._bl_object_name = bl_object.name
## - When name exists, Blender adds .### to prevent overlap. ## - When name exists, Blender adds .### to prevent overlap.
## - `set_name` is allowed to change the name; nodes account for this. ## - `set_name` is allowed to change the name; nodes account for this.
# Object Datablock Name # Object Datablock Name
@property @property
def bl_mesh_name(self): def bl_mesh_name(self):
return self.name return self.name
@property @property
def bl_volume_name(self): def bl_volume_name(self):
return self.name return self.name
# Deallocation # Deallocation
def free(self): def free(self):
if not (bl_object := bpy.data.objects.get(self.name)): if not (bl_object := bpy.data.objects.get(self.name)):
return ## Nothing to do return ## Nothing to do
# Delete the Underlying Datablock # Delete the Underlying Datablock
## This automatically deletes the object too ## This automatically deletes the object too
if bl_object.type == "MESH": if bl_object.type == 'MESH':
bpy.data.meshes.remove(bl_object.data) bpy.data.meshes.remove(bl_object.data)
elif bl_object.type == "EMPTY": elif bl_object.type == 'EMPTY':
bpy.data.meshes.remove(bl_object.data) bpy.data.meshes.remove(bl_object.data)
elif bl_object.type == "VOLUME": elif bl_object.type == 'VOLUME':
bpy.data.volumes.remove(bl_object.data) bpy.data.volumes.remove(bl_object.data)
else: 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) raise ValueError(msg)
#################### ####################
# - Actions # - Actions
#################### ####################
def show_preview( def show_preview(
self, self,
kind: typx.Literal["MESH", "EMPTY", "VOLUME"], kind: typx.Literal['MESH', 'EMPTY', 'VOLUME'],
empty_display_type: typx.Literal[ empty_display_type: typx.Literal[
"PLAIN_AXES", "ARROWS", "SINGLE_ARROW", "CIRCLE", "CUBE", 'PLAIN_AXES',
"SPHERE", "CONE", "IMAGE", 'ARROWS',
] | None = None, 'SINGLE_ARROW',
'CIRCLE',
'CUBE',
'SPHERE',
'CONE',
'IMAGE',
]
| None = None,
) -> None: ) -> None:
"""Moves the managed Blender object to the preview collection. """Moves the managed Blender object to the preview collection.
If it's already included, do nothing. If it's already included, do nothing.
""" """
bl_object = self.bl_object(kind) bl_object = self.bl_object(kind)
if bl_object.name not in (preview_collection := bl_collection( if (
PREVIEW_COLLECTION_NAME, view_layer_exclude=False bl_object.name
)).objects: not in (
preview_collection := bl_collection(
PREVIEW_COLLECTION_NAME, view_layer_exclude=False
)
).objects
):
preview_collection.objects.link(bl_object) 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 bl_object.empty_display_type = empty_display_type
def hide_preview( def hide_preview(
self, self,
kind: typx.Literal["MESH", "EMPTY", "VOLUME"], kind: typx.Literal['MESH', 'EMPTY', 'VOLUME'],
) -> None: ) -> None:
"""Removes the managed Blender object from the preview collection. """Removes the managed Blender object from the preview collection.
If it's already removed, do nothing. If it's already removed, do nothing.
""" """
bl_object = self.bl_object(kind) bl_object = self.bl_object(kind)
if bl_object.name not in (preview_collection := bl_collection( if (
PREVIEW_COLLECTION_NAME, view_layer_exclude=False bl_object.name
)).objects: not in (
preview_collection := bl_collection(
PREVIEW_COLLECTION_NAME, view_layer_exclude=False
)
).objects
):
preview_collection.objects.unlink(bl_object) preview_collection.objects.unlink(bl_object)
def bl_select(self) -> None: def bl_select(self) -> None:
@ -141,68 +162,68 @@ class ManagedBLObject(ct.schemas.ManagedObj):
outlined in the 3D viewport. outlined in the 3D viewport.
""" """
if not (bl_object := bpy.data.objects.get(self.name)): 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) raise ValueError(msg)
bpy.ops.object.select_all(action='DESELECT') bpy.ops.object.select_all(action='DESELECT')
bl_object.select_set(True) bl_object.select_set(True)
#################### ####################
# - Managed Object Management # - Managed Object Management
#################### ####################
def bl_object( def bl_object(
self, self,
kind: typx.Literal["MESH", "EMPTY", "VOLUME"], kind: typx.Literal['MESH', 'EMPTY', 'VOLUME'],
): ):
"""Returns the managed blender object. """Returns the managed blender object.
If the requested object data type is different, then delete the old If the requested object data type is different, then delete the old
object and recreate. object and recreate.
""" """
# Remove Object (if mismatch) # Remove Object (if mismatch)
if ( if (
(bl_object := bpy.data.objects.get(self.name)) bl_object := bpy.data.objects.get(self.name)
and bl_object.type != kind ) and bl_object.type != kind:
):
self.free() self.free()
# Create Object w/Appropriate Data Block # Create Object w/Appropriate Data Block
if not (bl_object := bpy.data.objects.get(self.name)): 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) bl_data = bpy.data.meshes.new(self.bl_mesh_name)
elif kind == "EMPTY": elif kind == 'EMPTY':
bl_data = None bl_data = None
elif kind == "VOLUME": elif kind == 'VOLUME':
raise NotImplementedError raise NotImplementedError
else: 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) raise ValueError(msg)
bl_object = bpy.data.objects.new(self.name, bl_data) bl_object = bpy.data.objects.new(self.name, bl_data)
bl_collection( bl_collection(
MANAGED_COLLECTION_NAME, view_layer_exclude=True MANAGED_COLLECTION_NAME, view_layer_exclude=True
).objects.link(bl_object) ).objects.link(bl_object)
return bl_object return bl_object
#################### ####################
# - Mesh Data Properties # - Mesh Data Properties
#################### ####################
@property @property
def raw_mesh(self) -> bpy.types.Mesh: def raw_mesh(self) -> bpy.types.Mesh:
"""Returns the object's raw mesh data. """Returns the object's raw mesh data.
Raises an error if the object has no mesh data. Raises an error if the object has no mesh data.
""" """
if ( if (
(bl_object := bpy.data.objects.get(self.name)) bl_object := bpy.data.objects.get(self.name)
and bl_object.type == "MESH" ) and bl_object.type == 'MESH':
):
return bl_object.data 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) raise ValueError(msg)
@contextlib.contextmanager @contextlib.contextmanager
def mesh_as_bmesh( def mesh_as_bmesh(
self, self,
@ -210,9 +231,8 @@ class ManagedBLObject(ct.schemas.ManagedObj):
triangulate: bool = False, triangulate: bool = False,
) -> bpy.types.Mesh: ) -> bpy.types.Mesh:
if ( if (
(bl_object := bpy.data.objects.get(self.name)) bl_object := bpy.data.objects.get(self.name)
and bl_object.type == "MESH" ) and bl_object.type == 'MESH':
):
bmesh_mesh = None bmesh_mesh = None
try: try:
bmesh_mesh = bmesh.new() bmesh_mesh = bmesh.new()
@ -223,52 +243,53 @@ class ManagedBLObject(ct.schemas.ManagedObj):
) )
else: else:
bmesh_mesh.from_object(bl_object) bmesh_mesh.from_object(bl_object)
if triangulate: if triangulate:
bmesh.ops.triangulate(bmesh_mesh, faces=bmesh_mesh.faces) bmesh.ops.triangulate(bmesh_mesh, faces=bmesh_mesh.faces)
yield bmesh_mesh yield bmesh_mesh
finally: finally:
if bmesh_mesh: bmesh_mesh.free() if bmesh_mesh:
bmesh_mesh.free()
else: 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) raise ValueError(msg)
@property @property
def mesh_as_arrays(self) -> dict: def mesh_as_arrays(self) -> dict:
## TODO: Cached ## TODO: Cached
# Ensure Updated Geometry # Ensure Updated Geometry
bpy.context.view_layer.update() bpy.context.view_layer.update()
## TODO: Must we? ## TODO: Must we?
# Compute Evaluted + Triangulated Mesh # 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: with self.mesh_as_bmesh(evaluate=True, triangulate=True) as bmesh_mesh:
bmesh_mesh.to_mesh(_mesh) bmesh_mesh.to_mesh(_mesh)
# Optimized Vertex Copy # Optimized Vertex Copy
## See <https://blog.michelanders.nl/2016/02/copying-vertices-to-numpy-arrays-in_4.html> ## See <https://blog.michelanders.nl/2016/02/copying-vertices-to-numpy-arrays-in_4.html>
verts = np.zeros(3 * len(_mesh.vertices), dtype=np.float64) verts = np.zeros(3 * len(_mesh.vertices), dtype=np.float64)
_mesh.vertices.foreach_get('co', verts) _mesh.vertices.foreach_get('co', verts)
verts.shape = (-1, 3) verts.shape = (-1, 3)
# Optimized Triangle Copy # Optimized Triangle Copy
## To understand, read it, **carefully**. ## To understand, read it, **carefully**.
faces = np.zeros(3 * len(_mesh.polygons), dtype=np.uint64) faces = np.zeros(3 * len(_mesh.polygons), dtype=np.uint64)
_mesh.polygons.foreach_get('vertices', faces) _mesh.polygons.foreach_get('vertices', faces)
faces.shape = (-1, 3) faces.shape = (-1, 3)
# Remove Temporary Mesh # Remove Temporary Mesh
bpy.data.meshes.remove(_mesh) bpy.data.meshes.remove(_mesh)
return { return {
"verts": verts, 'verts': verts,
"faces": faces, 'faces': faces,
} }
#################### ####################
# - Modifier Methods # - Modifier Methods
#################### ####################
@ -277,13 +298,13 @@ class ManagedBLObject(ct.schemas.ManagedObj):
modifier_type: ModifierType, modifier_type: ModifierType,
): ):
"""Creates a new modifier for the current `bl_object`. """Creates a new modifier for the current `bl_object`.
For all Blender modifier type names, see: <https://docs.blender.org/api/current/bpy_types_enum_items/object_modifier_type_items.html#rna-enum-object-modifier-type-items> For all Blender modifier type names, see: <https://docs.blender.org/api/current/bpy_types_enum_items/object_modifier_type_items.html#rna-enum-object-modifier-type-items>
""" """
if not (bl_object := bpy.data.objects.get(self.name)): if not (bl_object := bpy.data.objects.get(self.name)):
msg = "Can't add modifier to BL object that doesn't exist" msg = "Can't add modifier to BL object that doesn't exist"
raise ValueError(msg) raise ValueError(msg)
# (Create and) Return Modifier # (Create and) Return Modifier
bl_modifier_name = MODIFIER_NAMES[modifier_type] bl_modifier_name = MODIFIER_NAMES[modifier_type]
if bl_modifier_name not in bl_object.modifiers: if bl_modifier_name not in bl_object.modifiers:
@ -292,33 +313,33 @@ class ManagedBLObject(ct.schemas.ManagedObj):
type=modifier_type, type=modifier_type,
) )
return bl_object.modifiers[bl_modifier_name] return bl_object.modifiers[bl_modifier_name]
def modifier_attrs(self, modifier_type: ModifierType) -> dict: def modifier_attrs(self, modifier_type: ModifierType) -> dict:
"""Based on the modifier type, retrieve a representative dictionary of modifier attributes. """Based on the modifier type, retrieve a representative dictionary of modifier attributes.
The attributes can then easily be set using `setattr`. The attributes can then easily be set using `setattr`.
""" """
bl_modifier = self.bl_modifier(modifier_type) bl_modifier = self.bl_modifier(modifier_type)
if modifier_type == "NODES": if modifier_type == 'NODES':
return { return {
"node_group": bl_modifier.node_group, 'node_group': bl_modifier.node_group,
} }
elif modifier_type == "ARRAY": elif modifier_type == 'ARRAY':
raise NotImplementedError raise NotImplementedError
def s_modifier_attrs( def s_modifier_attrs(
self, self,
modifier_type: ModifierType, modifier_type: ModifierType,
modifier_attrs: dict, modifier_attrs: dict,
): ):
bl_modifier = self.bl_modifier(modifier_type) bl_modifier = self.bl_modifier(modifier_type)
if modifier_type == "NODES": if modifier_type == 'NODES':
if bl_modifier.node_group != modifier_attrs["node_group"]: if bl_modifier.node_group != modifier_attrs['node_group']:
bl_modifier.node_group = modifier_attrs["node_group"] bl_modifier.node_group = modifier_attrs['node_group']
elif modifier_type == "ARRAY": elif modifier_type == 'ARRAY':
raise NotImplementedError raise NotImplementedError
#################### ####################
# - GeoNodes Modifier # - GeoNodes Modifier
#################### ####################
@ -328,65 +349,64 @@ class ManagedBLObject(ct.schemas.ManagedObj):
geonodes_identifier_to_value: dict, geonodes_identifier_to_value: dict,
): ):
"""Push the given GeoNodes Interface values to a GeoNodes modifier attached to a managed MESH object. """Push the given GeoNodes Interface values to a GeoNodes modifier attached to a managed MESH object.
The values must be compatible with the `default_value`s of the interface sockets. The values must be compatible with the `default_value`s of the interface sockets.
If there is no object, it is created. If there is no object, it is created.
If the object isn't a MESH object, it is made so. If the object isn't a MESH object, it is made so.
If the GeoNodes modifier doesn't exist, it is created. If the GeoNodes modifier doesn't exist, it is created.
If the GeoNodes node group doesn't match, it is changed. If the GeoNodes node group doesn't match, it is changed.
Only differing interface values are actually 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 # 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') # 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 # Set GeoNodes Values
modifier_altered = False modifier_altered = False
for interface_identifier, value in ( for (
geonodes_identifier_to_value.items() interface_identifier,
): value,
) in geonodes_identifier_to_value.items():
if bl_modifier[interface_identifier] != value: if bl_modifier[interface_identifier] != value:
# Quickly Determine if IDPropertyArray is Equal # Quickly Determine if IDPropertyArray is Equal
if hasattr( if (
bl_modifier[interface_identifier], hasattr(bl_modifier[interface_identifier], 'to_list')
"to_list" and tuple(bl_modifier[interface_identifier].to_list())
) and tuple( == value
bl_modifier[interface_identifier].to_list() ):
) == value:
continue continue
# Quickly Determine int/float Mismatch # Quickly Determine int/float Mismatch
if isinstance( if isinstance(
bl_modifier[interface_identifier], bl_modifier[interface_identifier],
float, float,
) and isinstance(value, int): ) and isinstance(value, int):
value = float(value) value = float(value)
bl_modifier[interface_identifier] = value bl_modifier[interface_identifier] = value
modifier_altered = True modifier_altered = True
# Update DepGraph (if anything changed) # Update DepGraph (if anything changed)
if modifier_altered: if modifier_altered:
bl_object.data.update() bl_object.data.update()
# @property
#@property # def volume(self) -> bpy.types.Volume:
#def volume(self) -> bpy.types.Volume: # """Returns the object's volume data.
# """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
# #
# msg = f"Requested VOLUME data from `bl_object` of type {bl_object.type}" # Raises an error if the object has no volume data.
# raise ValueError(msg) # """
# 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)

View File

@ -9,10 +9,12 @@ from . import contracts as ct
#################### ####################
MemAddr = int MemAddr = int
class DeltaNodeLinkCache(typ.TypedDict): class DeltaNodeLinkCache(typ.TypedDict):
added: set[MemAddr] added: set[MemAddr]
removed: set[MemAddr] removed: set[MemAddr]
class NodeLinkCache: class NodeLinkCache:
def __init__(self, node_tree: bpy.types.NodeTree): def __init__(self, node_tree: bpy.types.NodeTree):
# Initialize Parameters # Initialize Parameters
@ -21,46 +23,47 @@ class NodeLinkCache:
self.link_ptrs = set() self.link_ptrs = set()
self.link_ptrs_from_sockets = {} self.link_ptrs_from_sockets = {}
self.link_ptrs_to_sockets = {} self.link_ptrs_to_sockets = {}
# Fill Cache # Fill Cache
self.regenerate() self.regenerate()
def remove(self, link_ptrs: set[MemAddr]) -> None: def remove(self, link_ptrs: set[MemAddr]) -> None:
for link_ptr in link_ptrs: for link_ptr in link_ptrs:
self.link_ptrs.remove(link_ptr) self.link_ptrs.remove(link_ptr)
self.link_ptrs_to_links.pop(link_ptr, None) self.link_ptrs_to_links.pop(link_ptr, None)
def regenerate(self) -> DeltaNodeLinkCache: def regenerate(self) -> DeltaNodeLinkCache:
current_link_ptrs_to_links = { current_link_ptrs_to_links = {
link.as_pointer(): link for link in self._node_tree.links link.as_pointer(): link for link in self._node_tree.links
} }
current_link_ptrs = set(current_link_ptrs_to_links.keys()) current_link_ptrs = set(current_link_ptrs_to_links.keys())
# Compute Delta # Compute Delta
added_link_ptrs = current_link_ptrs - self.link_ptrs added_link_ptrs = current_link_ptrs - self.link_ptrs
removed_link_ptrs = self.link_ptrs - current_link_ptrs removed_link_ptrs = self.link_ptrs - current_link_ptrs
# Update Caches Incrementally # Update Caches Incrementally
self.remove(removed_link_ptrs) self.remove(removed_link_ptrs)
self.link_ptrs |= added_link_ptrs self.link_ptrs |= added_link_ptrs
for link_ptr in added_link_ptrs: for link_ptr in added_link_ptrs:
link = current_link_ptrs_to_links[link_ptr] link = current_link_ptrs_to_links[link_ptr]
self.link_ptrs_to_links[link_ptr] = link self.link_ptrs_to_links[link_ptr] = link
self.link_ptrs_from_sockets[link_ptr] = link.from_socket self.link_ptrs_from_sockets[link_ptr] = link.from_socket
self.link_ptrs_to_sockets[link_ptr] = link.to_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 # - Node Tree Definition
#################### ####################
class MaxwellSimTree(bpy.types.NodeTree): class MaxwellSimTree(bpy.types.NodeTree):
bl_idname = ct.TreeType.MaxwellSim.value bl_idname = ct.TreeType.MaxwellSim.value
bl_label = "Maxwell Sim Editor" bl_label = 'Maxwell Sim Editor'
bl_icon = ct.Icon.SimNodeEditor.value bl_icon = ct.Icon.SimNodeEditor.value
#################### ####################
# - Lock Methods # - Lock Methods
#################### ####################
@ -69,116 +72,117 @@ class MaxwellSimTree(bpy.types.NodeTree):
node.locked = False node.locked = False
for bl_socket in [*node.inputs, *node.outputs]: for bl_socket in [*node.inputs, *node.outputs]:
bl_socket.locked = False bl_socket.locked = False
#################### ####################
# - Init Methods # - Init Methods
#################### ####################
def on_load(self): def on_load(self):
"""Run by Blender when loading the NodeSimTree, ex. on file load, on creation, etc. . """Run by Blender when loading the NodeSimTree, ex. on file load, on creation, etc. .
It's a bit of a "fake" function - in practicality, it's triggered on the first update() function. 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 ## 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 # - Update Methods
#################### ####################
def sync_node_removed(self, node: bpy.types.Node): def sync_node_removed(self, node: bpy.types.Node):
"""Run by `Node.free()` when a node is being removed. """Run by `Node.free()` when a node is being removed.
Removes node input links from the internal cache (so we don't attempt to update non-existant sockets). Removes node input links from the internal cache (so we don't attempt to update non-existant sockets).
""" """
for bl_socket in node.inputs.values(): for bl_socket in node.inputs.values():
# Retrieve Socket Links (if any) # Retrieve Socket Links (if any)
self._node_link_cache.remove({ self._node_link_cache.remove(
link.as_pointer() {link.as_pointer() for link in bl_socket.links}
for link in bl_socket.links )
})
## ONLY Input Socket Links are Removed from the NodeLink Cache ## ONLY Input Socket Links are Removed from the NodeLink Cache
## - update() handles link-removal from still-existing node just fine. ## - update() handles link-removal from still-existing node just fine.
## - update() does NOT handle link-removal of non-existant nodes. ## - update() does NOT handle link-removal of non-existant nodes.
def update(self): def update(self):
"""Run by Blender when 'something changes' in the node tree. """Run by Blender when 'something changes' in the node tree.
Updates an internal node link cache, then updates sockets that just lost/gained an input link. 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() self.on_load()
## We presume update() is run before the first link is altered. ## We presume update() is run before the first link is altered.
## - Else, the first link of the session will not update caches. ## - Else, the first link of the session will not update caches.
## - We remain slightly unsure of the semantics. ## - 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 return
# Compute Changes to NodeLink Cache # Compute Changes to NodeLink Cache
delta_links = self._node_link_cache.regenerate() delta_links = self._node_link_cache.regenerate()
link_alterations = { link_alterations = {
"to_remove": [], 'to_remove': [],
"to_add": [], 'to_add': [],
} }
for link_ptr in delta_links["removed"]: for link_ptr in delta_links['removed']:
from_socket = self._node_link_cache.link_ptrs_from_sockets[link_ptr] from_socket = self._node_link_cache.link_ptrs_from_sockets[
link_ptr
]
to_socket = self._node_link_cache.link_ptrs_to_sockets[link_ptr] to_socket = self._node_link_cache.link_ptrs_to_sockets[link_ptr]
# Update Socket Caches # Update Socket Caches
self._node_link_cache.link_ptrs_from_sockets.pop(link_ptr, None) self._node_link_cache.link_ptrs_from_sockets.pop(link_ptr, None)
self._node_link_cache.link_ptrs_to_sockets.pop(link_ptr, None) self._node_link_cache.link_ptrs_to_sockets.pop(link_ptr, None)
# Trigger Report Chain on Socket that Just Lost a Link # Trigger Report Chain on Socket that Just Lost a Link
## Aka. Forward-Refresh Caches Relying on Linkage ## Aka. Forward-Refresh Caches Relying on Linkage
if not ( if not (
consent_removal := to_socket.sync_link_removed(from_socket) consent_removal := to_socket.sync_link_removed(from_socket)
): ):
# Did Not Consent to Removal: Queue Add Link # 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) 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 # Trigger Report Chain on Socket that Just Gained a Link
## Aka. Forward-Refresh Caches Relying on Linkage ## Aka. Forward-Refresh Caches Relying on Linkage
if not ( if not (consent_added := link.to_socket.sync_link_added(link)):
consent_added := link.to_socket.sync_link_added(link)
):
# Did Not Consent to Addition: Queue Remove Link # Did Not Consent to Addition: Queue Remove Link
link_alterations["to_remove"].append(link) link_alterations['to_remove'].append(link)
# Execute Queued Operations # Execute Queued Operations
## - Especially undoing undesirable link changes. ## - Especially undoing undesirable link changes.
## - This is important for locked graphs, whose links must not change. ## - 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) 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) self.links.new(from_socket, to_socket)
# If Queued Operations: Regenerate Cache # If Queued Operations: Regenerate Cache
## - This prevents the next update() from picking up on alterations. ## - 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() self._node_link_cache.regenerate()
#################### ####################
# - Post-Load Handler # - Post-Load Handler
#################### ####################
def initialize_sim_tree_node_link_cache(scene: bpy.types.Scene): def initialize_sim_tree_node_link_cache(_: bpy.types.Scene):
"""Whenever a file is loaded, create/regenerate the NodeLinkCache in all trees. """Whenever a file is loaded, create/regenerate the NodeLinkCache in all trees."""
"""
for node_tree in bpy.data.node_groups: for node_tree in bpy.data.node_groups:
if node_tree.bl_idname == "MaxwellSimTree": if node_tree.bl_idname == 'MaxwellSimTree':
if not hasattr(node_tree, "_node_link_cache"): node_tree.on_load()
node_tree._node_link_cache = NodeLinkCache(node_tree)
else:
node_tree._node_link_cache.regenerate()
#################### ####################
# - Blender Registration # - Blender Registration
#################### ####################
bpy.app.handlers.load_post.append(initialize_sim_tree_node_link_cache) bpy.app.handlers.load_post.append(initialize_sim_tree_node_link_cache)
## TODO: Move to top-level registration.
BL_REGISTER = [ BL_REGISTER = [
MaxwellSimTree, MaxwellSimTree,

View File

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

View File

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

View File

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

View File

@ -1,26 +1,22 @@
from . import wave_constant from . import wave_constant
#from . import unit_system # from . import unit_system
from . import constants from . import constants
from . import web_importers from . import web_importers
#from . import file_importers # from . import file_importers
BL_REGISTER = [ BL_REGISTER = [
*wave_constant.BL_REGISTER, *wave_constant.BL_REGISTER,
# *unit_system.BL_REGISTER, # *unit_system.BL_REGISTER,
*constants.BL_REGISTER, *constants.BL_REGISTER,
*web_importers.BL_REGISTER, *web_importers.BL_REGISTER,
# *file_importers.BL_REGISTER, # *file_importers.BL_REGISTER,
] ]
BL_NODES = { BL_NODES = {
**wave_constant.BL_NODES, **wave_constant.BL_NODES,
# **unit_system.BL_NODES, # **unit_system.BL_NODES,
**constants.BL_NODES, **constants.BL_NODES,
**web_importers.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 number_constant
#from . import physical_constant
# from . import physical_constant
from . import blender_constant from . import blender_constant
BL_REGISTER = [ BL_REGISTER = [
# *scientific_constant.BL_REGISTER, # *scientific_constant.BL_REGISTER,
*number_constant.BL_REGISTER, *number_constant.BL_REGISTER,
# *physical_constant.BL_REGISTER, # *physical_constant.BL_REGISTER,
*blender_constant.BL_REGISTER, *blender_constant.BL_REGISTER,
] ]
BL_NODES = { BL_NODES = {
# **scientific_constant.BL_NODES, # **scientific_constant.BL_NODES,
**number_constant.BL_NODES, **number_constant.BL_NODES,
# **physical_constant.BL_NODES, # **physical_constant.BL_NODES,
**blender_constant.BL_NODES, **blender_constant.BL_NODES,
} }

View File

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

View File

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

View File

@ -5,61 +5,61 @@ from .... import contracts
from .... import sockets from .... import sockets
from ... import base from ... import base
class PhysicalConstantNode(base.MaxwellSimTreeNode): class PhysicalConstantNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.PhysicalConstant node_type = contracts.NodeType.PhysicalConstant
bl_label = "Physical Constant" bl_label = 'Physical Constant'
#bl_icon = constants.ICON_SIM_INPUT # bl_icon = constants.ICON_SIM_INPUT
input_sockets = {} input_sockets = {}
input_socket_sets = { input_socket_sets = {
"time": { 'time': {
"value": sockets.PhysicalTimeSocketDef( 'value': sockets.PhysicalTimeSocketDef(
label="Time", label='Time',
), ),
}, },
"angle": { 'angle': {
"value": sockets.PhysicalAngleSocketDef( 'value': sockets.PhysicalAngleSocketDef(
label="Angle", label='Angle',
), ),
}, },
"length": { 'length': {
"value": sockets.PhysicalLengthSocketDef( 'value': sockets.PhysicalLengthSocketDef(
label="Length", label='Length',
), ),
}, },
"area": { 'area': {
"value": sockets.PhysicalAreaSocketDef( 'value': sockets.PhysicalAreaSocketDef(
label="Area", label='Area',
), ),
}, },
"volume": { 'volume': {
"value": sockets.PhysicalVolumeSocketDef( 'value': sockets.PhysicalVolumeSocketDef(
label="Volume", label='Volume',
), ),
}, },
"point_3d": { 'point_3d': {
"value": sockets.PhysicalPoint3DSocketDef( 'value': sockets.PhysicalPoint3DSocketDef(
label="3D Point", label='3D Point',
), ),
}, },
"size_3d": { 'size_3d': {
"value": sockets.PhysicalSize3DSocketDef( 'value': sockets.PhysicalSize3DSocketDef(
label="3D Size", label='3D Size',
), ),
}, },
## I got bored so maybe the rest later ## I got bored so maybe the rest later
} }
output_sockets = {} output_sockets = {}
output_socket_sets = input_socket_sets output_socket_sets = input_socket_sets
#################### ####################
# - Callbacks # - Callbacks
#################### ####################
@base.computes_output_socket("value") @base.computes_output_socket('value')
def compute_value(self: contracts.NodeTypeProtocol) -> sp.Expr: 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 sockets
from .. import base from .. import base
class PhysicalUnitSystemNode(base.MaxwellSimNode): class PhysicalUnitSystemNode(base.MaxwellSimNode):
node_type = ct.NodeType.UnitSystem node_type = ct.NodeType.UnitSystem
bl_label = "Unit System" bl_label = 'Unit System'
input_sockets = { input_sockets = {
"Unit System": sockets.PhysicalUnitSystemSocketDef( 'Unit System': sockets.PhysicalUnitSystemSocketDef(
show_by_default=True, show_by_default=True,
), ),
} }
output_sockets = { output_sockets = {
"Unit System": sockets.PhysicalUnitSystemSocketDef(), 'Unit System': sockets.PhysicalUnitSystemSocketDef(),
} }
#################### ####################
# - Callbacks # - Callbacks
#################### ####################
@base.computes_output_socket( @base.computes_output_socket(
"Unit System", 'Unit System',
input_sockets = {"Unit System"}, input_sockets={'Unit System'},
) )
def compute_value(self, input_sockets) -> dict: 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 = [ BL_REGISTER = [
PhysicalUnitSystemNode, PhysicalUnitSystemNode,
] ]
BL_NODES = { BL_NODES = {ct.NodeType.UnitSystem: (ct.NodeCategory.MAXWELLSIM_INPUTS)}
ct.NodeType.UnitSystem: (
ct.NodeCategory.MAXWELLSIM_INPUTS
)
}

View File

@ -8,89 +8,86 @@ from ... import contracts as ct
from ... import sockets from ... import sockets
from .. import base from .. import base
VAC_SPEED_OF_LIGHT = ( VAC_SPEED_OF_LIGHT = sc.constants.speed_of_light * spu.meter / spu.second
sc.constants.speed_of_light
* spu.meter/spu.second
)
class WaveConstantNode(base.MaxwellSimNode): class WaveConstantNode(base.MaxwellSimNode):
node_type = ct.NodeType.WaveConstant node_type = ct.NodeType.WaveConstant
bl_label = "Wave Constant" bl_label = 'Wave Constant'
input_socket_sets = { input_socket_sets = {
# Single # Single
"Vacuum WL": { 'Vacuum WL': {
"WL": sockets.PhysicalLengthSocketDef( 'WL': sockets.PhysicalLengthSocketDef(
default_value=500*spu.nm, default_value=500 * spu.nm,
default_unit=spu.nm, default_unit=spu.nm,
), ),
}, },
"Frequency": { 'Frequency': {
"Freq": sockets.PhysicalFreqSocketDef( 'Freq': sockets.PhysicalFreqSocketDef(
default_value=500*spux.THz, default_value=500 * spux.THz,
default_unit=spux.THz, default_unit=spux.THz,
), ),
}, },
# Listy # Listy
"Vacuum WLs": { 'Vacuum WLs': {
"WLs": sockets.PhysicalLengthSocketDef( 'WLs': sockets.PhysicalLengthSocketDef(
is_list=True, is_list=True,
), ),
}, },
"Frequencies": { 'Frequencies': {
"Freqs": sockets.PhysicalFreqSocketDef( 'Freqs': sockets.PhysicalFreqSocketDef(
is_list=True, is_list=True,
), ),
}, },
} }
#################### ####################
# - Callbacks # - Callbacks
#################### ####################
@base.computes_output_socket( @base.computes_output_socket(
"WL", 'WL',
input_sockets={"WL", "Freq"}, input_sockets={'WL', 'Freq'},
) )
def compute_vac_wl(self, input_sockets: dict) -> sp.Expr: 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 return vac_wl
elif (freq := input_sockets["Freq"]) is not None: elif (freq := input_sockets['Freq']) is not None:
return spu.convert_to( return spu.convert_to(
VAC_SPEED_OF_LIGHT / freq, VAC_SPEED_OF_LIGHT / freq,
spu.meter, spu.meter,
) )
raise RuntimeError("Vac WL and Freq are both None") raise RuntimeError('Vac WL and Freq are both None')
@base.computes_output_socket( @base.computes_output_socket(
"Freq", 'Freq',
input_sockets={"WL", "Freq"}, input_sockets={'WL', 'Freq'},
) )
def compute_freq(self, input_sockets: dict) -> sp.Expr: 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( return spu.convert_to(
VAC_SPEED_OF_LIGHT / vac_wl, VAC_SPEED_OF_LIGHT / vac_wl,
spu.hertz, spu.hertz,
) )
elif (freq := input_sockets["Freq"]) is not None: elif (freq := input_sockets['Freq']) is not None:
return freq return freq
raise RuntimeError("Vac WL and Freq are both None") raise RuntimeError('Vac WL and Freq are both None')
#################### ####################
# - Listy Callbacks # - Listy Callbacks
#################### ####################
@base.computes_output_socket( @base.computes_output_socket(
"WLs", 'WLs',
input_sockets={"WLs", "Freqs"}, input_sockets={'WLs', 'Freqs'},
) )
def compute_vac_wls(self, input_sockets: dict) -> sp.Expr: 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 return vac_wls
elif (freqs := input_sockets["Freqs"]) is not None: elif (freqs := input_sockets['Freqs']) is not None:
return [ return [
spu.convert_to( spu.convert_to(
VAC_SPEED_OF_LIGHT / freq, VAC_SPEED_OF_LIGHT / freq,
@ -98,15 +95,15 @@ class WaveConstantNode(base.MaxwellSimNode):
) )
for freq in freqs for freq in freqs
][::-1] ][::-1]
raise RuntimeError("Vac WLs and Freqs are both None") raise RuntimeError('Vac WLs and Freqs are both None')
@base.computes_output_socket( @base.computes_output_socket(
"Freqs", 'Freqs',
input_sockets={"WLs", "Freqs"}, input_sockets={'WLs', 'Freqs'},
) )
def compute_freqs(self, input_sockets: dict) -> sp.Expr: 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 [ return [
spu.convert_to( spu.convert_to(
VAC_SPEED_OF_LIGHT / vac_wl, VAC_SPEED_OF_LIGHT / vac_wl,
@ -114,43 +111,42 @@ class WaveConstantNode(base.MaxwellSimNode):
) )
for vac_wl in vac_wls for vac_wl in vac_wls
][::-1] ][::-1]
elif (freqs := input_sockets["Freqs"]) is not None: elif (freqs := input_sockets['Freqs']) is not None:
return freqs return freqs
raise RuntimeError("Vac WLs and Freqs are both None") raise RuntimeError('Vac WLs and Freqs are both None')
#################### ####################
# - Callbacks # - Callbacks
#################### ####################
@base.on_value_changed( @base.on_value_changed(
prop_name="active_socket_set", prop_name='active_socket_set', props={'active_socket_set'}
props={"active_socket_set"}
) )
def on_value_changed__active_socket_set(self, props: dict): def on_value_changed__active_socket_set(self, props: dict):
# Singular: Normal Output Sockets # 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 = {}
self.loose_output_sockets = { self.loose_output_sockets = {
"Freq": sockets.PhysicalFreqSocketDef(), 'Freq': sockets.PhysicalFreqSocketDef(),
"WL": sockets.PhysicalLengthSocketDef(), 'WL': sockets.PhysicalLengthSocketDef(),
} }
# Plural: Listy Output Sockets # 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 = {}
self.loose_output_sockets = { self.loose_output_sockets = {
"Freqs": sockets.PhysicalFreqSocketDef(is_list=True), 'Freqs': sockets.PhysicalFreqSocketDef(is_list=True),
"WLs": sockets.PhysicalLengthSocketDef(is_list=True), 'WLs': sockets.PhysicalLengthSocketDef(is_list=True),
} }
else: else:
msg = f"Active socket set invalid for wave constant: {props['active_socket_set']}" msg = f"Active socket set invalid for wave constant: {props['active_socket_set']}"
raise RuntimeError(msg) raise RuntimeError(msg)
@base.on_init() @base.on_init()
def on_init(self): def on_init(self):
self.on_value_changed__active_socket_set() self.on_value_changed__active_socket_set()
#################### ####################
# - Blender Registration # - Blender Registration
@ -158,8 +154,4 @@ class WaveConstantNode(base.MaxwellSimNode):
BL_REGISTER = [ BL_REGISTER = [
WaveConstantNode, WaveConstantNode,
] ]
BL_NODES = { BL_NODES = {ct.NodeType.WaveConstant: (ct.NodeCategory.MAXWELLSIM_INPUTS)}
ct.NodeType.WaveConstant: (
ct.NodeCategory.MAXWELLSIM_INPUTS
)
}

View File

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

View File

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

View File

@ -1,47 +1,45 @@
from . import library_medium from . import library_medium
#from . import pec_medium # from . import pec_medium
#from . import isotropic_medium # from . import isotropic_medium
#from . import anisotropic_medium # from . import anisotropic_medium
# #
#from . import triple_sellmeier_medium # from . import triple_sellmeier_medium
#from . import sellmeier_medium # from . import sellmeier_medium
#from . import pole_residue_medium # from . import pole_residue_medium
#from . import drude_medium # from . import drude_medium
#from . import drude_lorentz_medium # from . import drude_lorentz_medium
#from . import debye_medium # from . import debye_medium
# #
#from . import non_linearities # from . import non_linearities
BL_REGISTER = [ BL_REGISTER = [
*library_medium.BL_REGISTER, *library_medium.BL_REGISTER,
# *pec_medium.BL_REGISTER,
# *pec_medium.BL_REGISTER, # *isotropic_medium.BL_REGISTER,
# *isotropic_medium.BL_REGISTER, # *anisotropic_medium.BL_REGISTER,
# *anisotropic_medium.BL_REGISTER, #
# # *triple_sellmeier_medium.BL_REGISTER,
# *triple_sellmeier_medium.BL_REGISTER, # *sellmeier_medium.BL_REGISTER,
# *sellmeier_medium.BL_REGISTER, # *pole_residue_medium.BL_REGISTER,
# *pole_residue_medium.BL_REGISTER, # *drude_medium.BL_REGISTER,
# *drude_medium.BL_REGISTER, # *drude_lorentz_medium.BL_REGISTER,
# *drude_lorentz_medium.BL_REGISTER, # *debye_medium.BL_REGISTER,
# *debye_medium.BL_REGISTER, #
# # *non_linearities.BL_REGISTER,
# *non_linearities.BL_REGISTER,
] ]
BL_NODES = { BL_NODES = {
**library_medium.BL_NODES, **library_medium.BL_NODES,
# **pec_medium.BL_NODES,
# **pec_medium.BL_NODES, # **isotropic_medium.BL_NODES,
# **isotropic_medium.BL_NODES, # **anisotropic_medium.BL_NODES,
# **anisotropic_medium.BL_NODES, #
# # **triple_sellmeier_medium.BL_NODES,
# **triple_sellmeier_medium.BL_NODES, # **sellmeier_medium.BL_NODES,
# **sellmeier_medium.BL_NODES, # **pole_residue_medium.BL_NODES,
# **pole_residue_medium.BL_NODES, # **drude_medium.BL_NODES,
# **drude_medium.BL_NODES, # **drude_lorentz_medium.BL_NODES,
# **drude_lorentz_medium.BL_NODES, # **debye_medium.BL_NODES,
# **debye_medium.BL_NODES, #
# # **non_linearities.BL_NODES,
# **non_linearities.BL_NODES,
} }

View File

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

View File

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

View File

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

View File

@ -14,55 +14,55 @@ from ... import sockets
from ... import managed_objs from ... import managed_objs
from .. import base from .. import base
VAC_SPEED_OF_LIGHT = ( VAC_SPEED_OF_LIGHT = sc.constants.speed_of_light * spu.meter / spu.second
sc.constants.speed_of_light
* spu.meter/spu.second
)
class LibraryMediumNode(base.MaxwellSimNode): class LibraryMediumNode(base.MaxwellSimNode):
node_type = ct.NodeType.LibraryMedium node_type = ct.NodeType.LibraryMedium
bl_label = "Library Medium" bl_label = 'Library Medium'
#################### ####################
# - Sockets # - Sockets
#################### ####################
input_sockets = {} input_sockets = {}
output_sockets = { output_sockets = {
"Medium": sockets.MaxwellMediumSocketDef(), 'Medium': sockets.MaxwellMediumSocketDef(),
} }
managed_obj_defs = { managed_obj_defs = {
"nk_plot": ct.schemas.ManagedObjDef( 'nk_plot': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLImage(name), mk=lambda name: managed_objs.ManagedBLImage(name),
name_prefix="", name_prefix='',
) )
} }
#################### ####################
# - Properties # - Properties
#################### ####################
material: bpy.props.EnumProperty( material: bpy.props.EnumProperty(
name="", name='',
description="", description='',
#icon="NODE_MATERIAL", # icon="NODE_MATERIAL",
items=[ items=[
( (
mat_key, mat_key,
td.material_library[mat_key].name, td.material_library[mat_key].name,
", ".join([ ', '.join(
ref.journal [
for ref in td.material_library[mat_key].variants[ ref.journal
td.material_library[mat_key].default for ref in td.material_library[mat_key]
].reference .variants[td.material_library[mat_key].default]
]) .reference
]
),
) )
for mat_key in td.material_library 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", default='Au',
update=(lambda self, context: self.sync_prop("material", context)), update=(lambda self, context: self.sync_prop('material', context)),
) )
@property @property
def freq_range_str(self) -> tuple[sp.Expr, sp.Expr]: def freq_range_str(self) -> tuple[sp.Expr, sp.Expr]:
## TODO: Cache (node instances don't seem able to keep data outside of properties, not even cached_property) ## TODO: Cache (node instances don't seem able to keep data outside of properties, not even cached_property)
@ -71,14 +71,14 @@ class LibraryMediumNode(base.MaxwellSimNode):
spu.convert_to( spu.convert_to(
val * spu.hertz, val * spu.hertz,
spuex.terahertz, spuex.terahertz,
) / spuex.terahertz )
/ spuex.terahertz
for val in mat.medium.frequency_range for val in mat.medium.frequency_range
] ]
return sp.pretty( return sp.pretty(
[freq_range[0].n(4), freq_range[1].n(4)], [freq_range[0].n(4), freq_range[1].n(4)], use_unicode=True
use_unicode=True
) )
@property @property
def nm_range_str(self) -> str: def nm_range_str(self) -> str:
## TODO: Cache (node instances don't seem able to keep data outside of properties, not even cached_property) ## TODO: Cache (node instances don't seem able to keep data outside of properties, not even cached_property)
@ -87,47 +87,47 @@ class LibraryMediumNode(base.MaxwellSimNode):
spu.convert_to( spu.convert_to(
VAC_SPEED_OF_LIGHT / (val * spu.hertz), VAC_SPEED_OF_LIGHT / (val * spu.hertz),
spu.nanometer, spu.nanometer,
) / spu.nanometer )
/ spu.nanometer
for val in reversed(mat.medium.frequency_range) for val in reversed(mat.medium.frequency_range)
] ]
return sp.pretty( return sp.pretty(
[nm_range[0].n(4), nm_range[1].n(4)], [nm_range[0].n(4), nm_range[1].n(4)], use_unicode=True
use_unicode=True
) )
#################### ####################
# - UI # - UI
#################### ####################
def draw_props(self, context, layout): def draw_props(self, context, layout):
layout.prop(self, "material", text="") layout.prop(self, 'material', text='')
def draw_info(self, context, col): def draw_info(self, context, col):
# UI Drawing # UI Drawing
split = col.split(factor=0.23, align=True) split = col.split(factor=0.23, align=True)
_col = split.column(align=True) _col = split.column(align=True)
_col.alignment = "LEFT" _col.alignment = 'LEFT'
_col.label(text="nm") _col.label(text='nm')
_col.label(text="THz") _col.label(text='THz')
_col = split.column(align=True) _col = split.column(align=True)
_col.alignment = "RIGHT" _col.alignment = 'RIGHT'
_col.label(text=self.nm_range_str) _col.label(text=self.nm_range_str)
_col.label(text=self.freq_range_str) _col.label(text=self.freq_range_str)
#################### ####################
# - Output Sockets # - Output Sockets
#################### ####################
@base.computes_output_socket("Medium") @base.computes_output_socket('Medium')
def compute_vac_wl(self) -> sp.Expr: def compute_vac_wl(self) -> sp.Expr:
return td.material_library[self.material].medium return td.material_library[self.material].medium
#################### ####################
# - Event Callbacks # - Event Callbacks
#################### ####################
@base.on_show_plot( @base.on_show_plot(
managed_objs={"nk_plot"}, managed_objs={'nk_plot'},
props={"material"}, props={'material'},
stop_propagation=True, ## Plot only the first plottable node stop_propagation=True, ## Plot only the first plottable node
) )
def on_show_plot( def on_show_plot(
@ -135,28 +135,26 @@ class LibraryMediumNode(base.MaxwellSimNode):
managed_objs: dict[str, ct.schemas.ManagedObj], managed_objs: dict[str, ct.schemas.ManagedObj],
props: dict[str, typ.Any], props: dict[str, typ.Any],
): ):
medium = td.material_library[props["material"]].medium medium = td.material_library[props['material']].medium
freq_range = [ freq_range = [
spu.convert_to( spu.convert_to(
val * spu.hertz, val * spu.hertz,
spuex.terahertz, spuex.terahertz,
) / spu.hertz )
/ spu.hertz
for val in medium.frequency_range 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), lambda ax: medium.plot(medium.frequency_range, ax=ax),
bl_select=True, bl_select=True,
) )
#################### ####################
# - Blender Registration # - Blender Registration
#################### ####################
BL_REGISTER = [ BL_REGISTER = [
LibraryMediumNode, LibraryMediumNode,
] ]
BL_NODES = { BL_NODES = {ct.NodeType.LibraryMedium: (ct.NodeCategory.MAXWELLSIM_MEDIUMS)}
ct.NodeType.LibraryMedium: (
ct.NodeCategory.MAXWELLSIM_MEDIUMS
)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -16,25 +16,25 @@ from .... import contracts as ct
from .... import sockets from .... import sockets
from ... import base from ... import base
#################### ####################
# - Web Uploader / Loader / Runner / Releaser # - Web Uploader / Loader / Runner / Releaser
#################### ####################
class UploadSimulation(bpy.types.Operator): class UploadSimulation(bpy.types.Operator):
bl_idname = "blender_maxwell.nodes__upload_simulation" bl_idname = 'blender_maxwell.nodes__upload_simulation'
bl_label = "Upload Tidy3D 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_description = 'Upload the attached (locked) simulation, such that it is ready to run on the Tidy3D cloud'
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return ( return (
hasattr(context, "node") hasattr(context, 'node')
and hasattr(context.node, "node_type") and hasattr(context.node, 'node_type')
and context.node.node_type == ct.NodeType.Tidy3DWebExporter and context.node.node_type == ct.NodeType.Tidy3DWebExporter
and context.node.lock_tree and context.node.lock_tree
and tdcloud.IS_AUTHENTICATED and tdcloud.IS_AUTHENTICATED
and not context.node.tracked_task_id 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): def execute(self, context):
@ -42,24 +42,27 @@ class UploadSimulation(bpy.types.Operator):
node.upload_sim() node.upload_sim()
return {'FINISHED'} return {'FINISHED'}
class RunSimulation(bpy.types.Operator): class RunSimulation(bpy.types.Operator):
bl_idname = "blender_maxwell.nodes__run_simulation" bl_idname = 'blender_maxwell.nodes__run_simulation'
bl_label = "Run Tracked Tidy3D Sim" bl_label = 'Run Tracked Tidy3D Sim'
bl_description = "Run the currently tracked simulation task" bl_description = 'Run the currently tracked simulation task'
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return ( return (
hasattr(context, "node") hasattr(context, 'node')
and hasattr(context.node, "node_type") and hasattr(context.node, 'node_type')
and context.node.node_type == ct.NodeType.Tidy3DWebExporter and context.node.node_type == ct.NodeType.Tidy3DWebExporter
and tdcloud.IS_AUTHENTICATED and tdcloud.IS_AUTHENTICATED
and context.node.tracked_task_id and context.node.tracked_task_id
and (task_info := tdcloud.TidyCloudTasks.task_info( and (
context.node.tracked_task_id task_info := tdcloud.TidyCloudTasks.task_info(
)) is not None context.node.tracked_task_id
and task_info.status == "draft" )
)
is not None
and task_info.status == 'draft'
) )
def execute(self, context): def execute(self, context):
@ -67,18 +70,18 @@ class RunSimulation(bpy.types.Operator):
node.run_tracked_task() node.run_tracked_task()
return {'FINISHED'} return {'FINISHED'}
class ReloadTrackedTask(bpy.types.Operator): class ReloadTrackedTask(bpy.types.Operator):
bl_idname = "blender_maxwell.nodes__reload_tracked_task" bl_idname = 'blender_maxwell.nodes__reload_tracked_task'
bl_label = "Reload Tracked Tidy3D Cloud Task" bl_label = 'Reload Tracked Tidy3D Cloud Task'
bl_description = "Reload the currently tracked simulation task" bl_description = 'Reload the currently tracked simulation task'
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return ( return (
hasattr(context, "node") hasattr(context, 'node')
and hasattr(context.node, "node_type") and hasattr(context.node, 'node_type')
and context.node.node_type == ct.NodeType.Tidy3DWebExporter and context.node.node_type == ct.NodeType.Tidy3DWebExporter
and tdcloud.IS_AUTHENTICATED and tdcloud.IS_AUTHENTICATED
and context.node.tracked_task_id and context.node.tracked_task_id
) )
@ -90,22 +93,22 @@ class ReloadTrackedTask(bpy.types.Operator):
) is None: ) is None:
msg = "Tried to reload tracked task, but it doesn't exist" msg = "Tried to reload tracked task, but it doesn't exist"
raise RuntimeError(msg) raise RuntimeError(msg)
cloud_task = tdcloud.TidyCloudTasks.update_task(cloud_task) cloud_task = tdcloud.TidyCloudTasks.update_task(cloud_task)
return {'FINISHED'} return {'FINISHED'}
class EstCostTrackedTask(bpy.types.Operator): class EstCostTrackedTask(bpy.types.Operator):
bl_idname = "blender_maxwell.nodes__est_cost_tracked_task" bl_idname = 'blender_maxwell.nodes__est_cost_tracked_task'
bl_label = "Est Cost of Tracked Tidy3D Cloud Task" bl_label = 'Est Cost of Tracked Tidy3D Cloud Task'
bl_description = "Reload the currently tracked simulation task" bl_description = 'Reload the currently tracked simulation task'
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return ( return (
hasattr(context, "node") hasattr(context, 'node')
and hasattr(context.node, "node_type") and hasattr(context.node, 'node_type')
and context.node.node_type == ct.NodeType.Tidy3DWebExporter and context.node.node_type == ct.NodeType.Tidy3DWebExporter
and tdcloud.IS_AUTHENTICATED and tdcloud.IS_AUTHENTICATED
and context.node.tracked_task_id and context.node.tracked_task_id
) )
@ -113,142 +116,145 @@ class EstCostTrackedTask(bpy.types.Operator):
def execute(self, context): def execute(self, context):
node = context.node node = context.node
if ( 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: ) 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) raise RuntimeError(msg)
node.cache_est_cost = task_info.cost_est() node.cache_est_cost = task_info.cost_est()
return {'FINISHED'} return {'FINISHED'}
class ReleaseTrackedTask(bpy.types.Operator): class ReleaseTrackedTask(bpy.types.Operator):
bl_idname = "blender_maxwell.nodes__release_tracked_task" bl_idname = 'blender_maxwell.nodes__release_tracked_task'
bl_label = "Release Tracked Tidy3D Cloud Task" bl_label = 'Release Tracked Tidy3D Cloud Task'
bl_description = "Release the currently tracked simulation task" bl_description = 'Release the currently tracked simulation task'
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return ( return (
hasattr(context, "node") hasattr(context, 'node')
and hasattr(context.node, "node_type") and hasattr(context.node, 'node_type')
and context.node.node_type == ct.NodeType.Tidy3DWebExporter and context.node.node_type == ct.NodeType.Tidy3DWebExporter
# and tdcloud.IS_AUTHENTICATED
#and tdcloud.IS_AUTHENTICATED
and context.node.tracked_task_id and context.node.tracked_task_id
) )
def execute(self, context): def execute(self, context):
node = context.node node = context.node
node.tracked_task_id = "" node.tracked_task_id = ''
return {'FINISHED'} return {'FINISHED'}
#################### ####################
# - Node # - Node
#################### ####################
class Tidy3DWebExporterNode(base.MaxwellSimNode): class Tidy3DWebExporterNode(base.MaxwellSimNode):
node_type = ct.NodeType.Tidy3DWebExporter node_type = ct.NodeType.Tidy3DWebExporter
bl_label = "Tidy3D Web Exporter" bl_label = 'Tidy3D Web Exporter'
input_sockets = { input_sockets = {
"FDTD Sim": sockets.MaxwellFDTDSimSocketDef(), 'FDTD Sim': sockets.MaxwellFDTDSimSocketDef(),
"Cloud Task": sockets.Tidy3DCloudTaskSocketDef( 'Cloud Task': sockets.Tidy3DCloudTaskSocketDef(
should_exist=False, should_exist=False,
), ),
} }
#################### ####################
# - Properties # - Properties
#################### ####################
lock_tree: bpy.props.BoolProperty( lock_tree: bpy.props.BoolProperty(
name="Whether to lock the attached tree", name='Whether to lock the attached tree',
description="Whether or not to lock the attached tree", description='Whether or not to lock the attached tree',
default=False, default=False,
update=lambda self, context: self.sync_lock_tree(context), update=lambda self, context: self.sync_lock_tree(context),
) )
tracked_task_id: bpy.props.StringProperty( tracked_task_id: bpy.props.StringProperty(
name="Tracked Task ID", name='Tracked Task ID',
description="The currently tracked task ID", description='The currently tracked task ID',
default="", default='',
update=lambda self, context: self.sync_tracked_task_id(context), update=lambda self, context: self.sync_tracked_task_id(context),
) )
# Cache # Cache
cache_total_monitor_data: bpy.props.FloatProperty( cache_total_monitor_data: bpy.props.FloatProperty(
name="(Cached) Total Monitor Data", name='(Cached) Total Monitor Data',
description="Required storage space by all monitors", description='Required storage space by all monitors',
default=0.0, default=0.0,
) )
cache_est_cost: bpy.props.FloatProperty( cache_est_cost: bpy.props.FloatProperty(
name="(Cached) Estimated Total Cost", name='(Cached) Estimated Total Cost',
description="Est. Cost in FlexCompute units", description='Est. Cost in FlexCompute units',
default=-1.0, default=-1.0,
) )
#################### ####################
# - Sync Methods # - Sync Methods
#################### ####################
def sync_lock_tree(self, context): def sync_lock_tree(self, context):
if self.lock_tree: if self.lock_tree:
self.trigger_action("enable_lock") self.trigger_action('enable_lock')
self.locked = False self.locked = False
for bl_socket in self.inputs: for bl_socket in self.inputs:
if bl_socket.name == "FDTD Sim": continue if bl_socket.name == 'FDTD Sim':
continue
bl_socket.locked = False bl_socket.locked = False
else: 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): def sync_tracked_task_id(self, context):
# Select Tracked Task # Select Tracked Task
if self.tracked_task_id: if self.tracked_task_id:
cloud_task = tdcloud.TidyCloudTasks.task(self.tracked_task_id) cloud_task = tdcloud.TidyCloudTasks.task(self.tracked_task_id)
task_info = tdcloud.TidyCloudTasks.task_info(self.tracked_task_id) task_info = tdcloud.TidyCloudTasks.task_info(self.tracked_task_id)
self.loose_output_sockets = { self.loose_output_sockets = {
"Cloud Task": sockets.Tidy3DCloudTaskSocketDef( 'Cloud Task': sockets.Tidy3DCloudTaskSocketDef(
should_exist=True, should_exist=True,
), ),
} }
self.inputs["Cloud Task"].locked = True self.inputs['Cloud Task'].locked = True
# Release Tracked Task # Release Tracked Task
else: else:
self.cache_est_cost = -1.0 self.cache_est_cost = -1.0
self.loose_output_sockets = {} self.loose_output_sockets = {}
self.inputs["Cloud Task"].sync_prepare_new_task() self.inputs['Cloud Task'].sync_prepare_new_task()
self.inputs["Cloud Task"].locked = False self.inputs['Cloud Task'].locked = False
self.sync_prop("tracked_task_id", context) self.sync_prop('tracked_task_id', context)
#################### ####################
# - Output Socket Callbacks # - Output Socket Callbacks
#################### ####################
def validate_sim(self): def validate_sim(self):
if (sim := self._compute_input("FDTD Sim")) is None: if (sim := self._compute_input('FDTD Sim')) is None:
msg = "Tried to validate simulation, but none is attached" msg = 'Tried to validate simulation, but none is attached'
raise ValueError(msg) raise ValueError(msg)
sim.validate_pre_upload(source_required = True) sim.validate_pre_upload(source_required=True)
def upload_sim(self): def upload_sim(self):
if (sim := self._compute_input("FDTD Sim")) is None: if (sim := self._compute_input('FDTD Sim')) is None:
msg = "Tried to upload simulation, but none is attached" msg = 'Tried to upload simulation, but none is attached'
raise ValueError(msg) raise ValueError(msg)
if ( if (
(new_task := self._compute_input("Cloud Task")) is None new_task := self._compute_input('Cloud Task')
or isinstance( ) is None or isinstance(
new_task, new_task,
tdcloud.CloudTask, 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) raise ValueError(msg)
# Create Cloud Task # Create Cloud Task
cloud_task = tdcloud.TidyCloudTasks.mk_task( cloud_task = tdcloud.TidyCloudTasks.mk_task(
task_name=new_task[0], task_name=new_task[0],
@ -257,25 +263,27 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
upload_progress_cb=lambda uploaded_bytes: None, ## TODO: Use! upload_progress_cb=lambda uploaded_bytes: None, ## TODO: Use!
verbose=True, verbose=True,
) )
# Declare to Cloud Task that it Exists Now # Declare to Cloud Task that it Exists Now
## This will change the UI to not allow free-text input. ## This will change the UI to not allow free-text input.
## If the socket is linked, this errors. ## 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 # Track the Newly Uploaded Task ID
self.tracked_task_id = cloud_task.task_id self.tracked_task_id = cloud_task.task_id
def run_tracked_task(self): def run_tracked_task(self):
if ( if (
cloud_task := tdcloud.TidyCloudTasks.task(self.tracked_task_id) cloud_task := tdcloud.TidyCloudTasks.task(self.tracked_task_id)
) is None: ) is None:
msg = "Tried to run tracked task, but it doesn't exist" msg = "Tried to run tracked task, but it doesn't exist"
raise RuntimeError(msg) raise RuntimeError(msg)
cloud_task.submit() 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 # - UI
#################### ####################
@ -284,146 +292,156 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
row = layout.row(align=True) row = layout.row(align=True)
row.operator( row.operator(
UploadSimulation.bl_idname, UploadSimulation.bl_idname,
text="Upload", text='Upload',
) )
tree_lock_icon = "LOCKED" if self.lock_tree else "UNLOCKED" tree_lock_icon = 'LOCKED' if self.lock_tree else 'UNLOCKED'
row.prop(self, "lock_tree", toggle=True, icon=tree_lock_icon, text="") row.prop(self, 'lock_tree', toggle=True, icon=tree_lock_icon, text='')
# Row: Run Sim Buttons # Row: Run Sim Buttons
row = layout.row(align=True) row = layout.row(align=True)
row.operator( row.operator(
RunSimulation.bl_idname, RunSimulation.bl_idname,
text="Run", text='Run',
) )
if self.tracked_task_id: if self.tracked_task_id:
tree_lock_icon = "LOOP_BACK" tree_lock_icon = 'LOOP_BACK'
row.operator( row.operator(
ReleaseTrackedTask.bl_idname, ReleaseTrackedTask.bl_idname,
icon="LOOP_BACK", icon='LOOP_BACK',
text="", text='',
) )
def draw_info(self, context, layout): def draw_info(self, context, layout):
# Connection Info # Connection Info
auth_icon = "CHECKBOX_HLT" if tdcloud.IS_AUTHENTICATED else "CHECKBOX_DEHLT" auth_icon = (
conn_icon = "CHECKBOX_HLT" if tdcloud.IS_ONLINE else "CHECKBOX_DEHLT" 'CHECKBOX_HLT' if tdcloud.IS_AUTHENTICATED else 'CHECKBOX_DEHLT'
)
conn_icon = 'CHECKBOX_HLT' if tdcloud.IS_ONLINE else 'CHECKBOX_DEHLT'
row = layout.row() row = layout.row()
row.alignment = "CENTER" row.alignment = 'CENTER'
row.label(text="Cloud Status") row.label(text='Cloud Status')
box = layout.box() box = layout.box()
split = box.split(factor=0.85) split = box.split(factor=0.85)
## Split: Left Column ## Split: Left Column
col = split.column(align=False) col = split.column(align=False)
col.label(text="Authed") col.label(text='Authed')
col.label(text="Connected") col.label(text='Connected')
## Split: Right Column ## Split: Right Column
col = split.column(align=False) col = split.column(align=False)
col.label(icon=auth_icon) col.label(icon=auth_icon)
col.label(icon=conn_icon) col.label(icon=conn_icon)
# Simulation Info # Simulation Info
if self.inputs["FDTD Sim"].is_linked: if self.inputs['FDTD Sim'].is_linked:
row = layout.row() row = layout.row()
row.alignment = "CENTER" row.alignment = 'CENTER'
row.label(text="Sim Info") row.label(text='Sim Info')
box = layout.box() box = layout.box()
split = box.split(factor=0.4) split = box.split(factor=0.4)
## Split: Left Column ## Split: Left Column
col = split.column(align=False) col = split.column(align=False)
col.label(text="𝝨 Output") col.label(text='𝝨 Output')
## Split: Right Column ## Split: Right Column
col = split.column(align=False) col = split.column(align=False)
col.alignment = "RIGHT" col.alignment = 'RIGHT'
col.label(text=f"{self.cache_total_monitor_data / 1_000_000:.2f}MB") col.label(
text=f'{self.cache_total_monitor_data / 1_000_000:.2f}MB'
)
# Cloud Task Info # Cloud Task Info
if self.tracked_task_id and tdcloud.IS_AUTHENTICATED: if self.tracked_task_id and tdcloud.IS_AUTHENTICATED:
task_info = tdcloud.TidyCloudTasks.task_info( task_info = tdcloud.TidyCloudTasks.task_info(self.tracked_task_id)
self.tracked_task_id if task_info is None:
) return
if task_info is None: return
## Header ## Header
row = layout.row() row = layout.row()
row.alignment = "CENTER" row.alignment = 'CENTER'
row.label(text="Task Info") row.label(text='Task Info')
## Progress Bar ## Progress Bar
row = layout.row(align=True) row = layout.row(align=True)
row.progress( row.progress(
factor=0.0, factor=0.0,
type="BAR", type='BAR',
text=f"Status: {task_info.status.capitalize()}", text=f'Status: {task_info.status.capitalize()}',
) )
row.operator( row.operator(
ReloadTrackedTask.bl_idname, ReloadTrackedTask.bl_idname,
text="", text='',
icon="FILE_REFRESH", icon='FILE_REFRESH',
) )
row.operator( row.operator(
EstCostTrackedTask.bl_idname, EstCostTrackedTask.bl_idname,
text="", text='',
icon="SORTTIME", icon='SORTTIME',
) )
## Information ## Information
box = layout.box() box = layout.box()
split = box.split(factor=0.4) split = box.split(factor=0.4)
## Split: Left Column ## Split: Left Column
col = split.column(align=False) col = split.column(align=False)
col.label(text="Status") col.label(text='Status')
col.label(text="Est. Cost") col.label(text='Est. Cost')
col.label(text="Real Cost") col.label(text='Real Cost')
## Split: Right Column ## Split: Right Column
cost_est = f"{self.cache_est_cost:.2f}" if self.cache_est_cost >= 0 else "TBD" cost_est = (
cost_real = f"{task_info.cost_real:.2f}" if task_info.cost_real is not None else "TBD" 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 = split.column(align=False)
col.alignment = "RIGHT" col.alignment = 'RIGHT'
col.label(text=task_info.status.capitalize()) col.label(text=task_info.status.capitalize())
col.label(text=f"{cost_est} creds") col.label(text=f'{cost_est} creds')
col.label(text=f"{cost_real} creds") col.label(text=f'{cost_real} creds')
# Connection Information # Connection Information
#################### ####################
# - Output Methods # - Output Methods
#################### ####################
@base.computes_output_socket( @base.computes_output_socket(
"Cloud Task", 'Cloud Task',
input_sockets={"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( if isinstance(
cloud_task := input_sockets["Cloud Task"], cloud_task := input_sockets['Cloud Task'], tdcloud.CloudTask
tdcloud.CloudTask
): ):
return cloud_task return cloud_task
return None return None
#################### ####################
# - Output Methods # - Output Methods
#################### ####################
@base.on_value_changed( @base.on_value_changed(
socket_name="FDTD Sim", socket_name='FDTD Sim',
input_sockets={"FDTD Sim"}, input_sockets={'FDTD Sim'},
) )
def on_value_changed__fdtd_sim(self, input_sockets): 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 self.cache_total_monitor_data = 0
return 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()) 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): class ConsoleViewOperator(bpy.types.Operator):
bl_idname = "blender_maxwell.console_view_operator" bl_idname = 'blender_maxwell.console_view_operator'
bl_label = "View Plots" bl_label = 'View Plots'
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
@ -27,9 +27,10 @@ class ConsoleViewOperator(bpy.types.Operator):
node.print_data_to_console() node.print_data_to_console()
return {'FINISHED'} return {'FINISHED'}
class RefreshPlotViewOperator(bpy.types.Operator): class RefreshPlotViewOperator(bpy.types.Operator):
bl_idname = "blender_maxwell.refresh_plot_view_operator" bl_idname = 'blender_maxwell.refresh_plot_view_operator'
bl_label = "Refresh Plots" bl_label = 'Refresh Plots'
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
@ -37,93 +38,96 @@ class RefreshPlotViewOperator(bpy.types.Operator):
def execute(self, context): def execute(self, context):
node = context.node node = context.node
node.trigger_action("value_changed", "Data") node.trigger_action('value_changed', 'Data')
return {'FINISHED'} return {'FINISHED'}
#################### ####################
# - Node # - Node
#################### ####################
class ViewerNode(base.MaxwellSimNode): class ViewerNode(base.MaxwellSimNode):
node_type = ct.NodeType.Viewer node_type = ct.NodeType.Viewer
bl_label = "Viewer" bl_label = 'Viewer'
input_sockets = { input_sockets = {
"Data": sockets.AnySocketDef(), 'Data': sockets.AnySocketDef(),
} }
#################### ####################
# - Properties # - Properties
#################### ####################
auto_plot: bpy.props.BoolProperty( auto_plot: bpy.props.BoolProperty(
name="Auto-Plot", name='Auto-Plot',
description="Whether to auto-plot anything plugged into the viewer node", description='Whether to auto-plot anything plugged into the viewer node',
default=False, 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( 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", description="Whether to auto-preview anything 3D, that's plugged into the viewer node",
default=False, default=False,
update=lambda self, context: self.sync_prop("auto_3d_preview", context), update=lambda self, context: self.sync_prop(
'auto_3d_preview', context
),
) )
#################### ####################
# - UI # - UI
#################### ####################
def draw_operators(self, context, layout): def draw_operators(self, context, layout):
split = layout.split(factor=0.4) split = layout.split(factor=0.4)
# Split LHS # Split LHS
col = split.column(align=False) col = split.column(align=False)
col.label(text="Console") col.label(text='Console')
col.label(text="Plot") col.label(text='Plot')
col.label(text="3D") col.label(text='3D')
# Split RHS # Split RHS
col = split.column(align=False) col = split.column(align=False)
## Console Options ## Console Options
col.operator(ConsoleViewOperator.bl_idname, text="Print") col.operator(ConsoleViewOperator.bl_idname, text='Print')
## Plot Options ## Plot Options
row = col.row(align=True) 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( row.operator(
RefreshPlotViewOperator.bl_idname, RefreshPlotViewOperator.bl_idname,
text="", text='',
icon="FILE_REFRESH", icon='FILE_REFRESH',
) )
## 3D Preview Options ## 3D Preview Options
row = col.row(align=True) 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 # - Methods
#################### ####################
def print_data_to_console(self): def print_data_to_console(self):
if not (data := self._compute_input("Data")): if not (data := self._compute_input('Data')):
return return
if isinstance(data, sp.Basic): if isinstance(data, sp.Basic):
sp.pprint(data, use_unicode=True) sp.pprint(data, use_unicode=True)
print(str(data)) print(str(data))
#################### ####################
# - Updates # - Updates
#################### ####################
@base.on_value_changed( @base.on_value_changed(
socket_name="Data", socket_name='Data',
props={"auto_3d_preview"}, props={'auto_3d_preview'},
) )
def on_value_changed__data(self, props): def on_value_changed__data(self, props):
# Show Plot # Show Plot
## Don't have to un-show other plots. ## Don't have to un-show other plots.
if self.auto_plot: if self.auto_plot:
self.trigger_action("show_plot") self.trigger_action('show_plot')
# Remove Anything Previewed # Remove Anything Previewed
preview_collection = managed_bl_object.bl_collection( preview_collection = managed_bl_object.bl_collection(
managed_bl_object.PREVIEW_COLLECTION_NAME, managed_bl_object.PREVIEW_COLLECTION_NAME,
@ -131,14 +135,14 @@ class ViewerNode(base.MaxwellSimNode):
) )
for bl_object in preview_collection.objects.values(): for bl_object in preview_collection.objects.values():
preview_collection.objects.unlink(bl_object) preview_collection.objects.unlink(bl_object)
# Preview Anything that Should be Previewed (maybe) # Preview Anything that Should be Previewed (maybe)
if props["auto_3d_preview"]: if props['auto_3d_preview']:
self.trigger_action("show_preview") self.trigger_action('show_preview')
@base.on_value_changed( @base.on_value_changed(
prop_name="auto_3d_preview", prop_name='auto_3d_preview',
props={"auto_3d_preview"}, props={'auto_3d_preview'},
) )
def on_value_changed__auto_3d_preview(self, props): def on_value_changed__auto_3d_preview(self, props):
# Remove Anything Previewed # Remove Anything Previewed
@ -148,10 +152,10 @@ class ViewerNode(base.MaxwellSimNode):
) )
for bl_object in preview_collection.objects.values(): for bl_object in preview_collection.objects.values():
preview_collection.objects.unlink(bl_object) preview_collection.objects.unlink(bl_object)
# Preview Anything that Should be Previewed (maybe) # Preview Anything that Should be Previewed (maybe)
if props["auto_3d_preview"]: if props['auto_3d_preview']:
self.trigger_action("show_preview") self.trigger_action('show_preview')
#################### ####################
@ -162,8 +166,4 @@ BL_REGISTER = [
RefreshPlotViewOperator, RefreshPlotViewOperator,
ViewerNode, ViewerNode,
] ]
BL_NODES = { BL_NODES = {ct.NodeType.Viewer: (ct.NodeCategory.MAXWELLSIM_OUTPUTS)}
ct.NodeType.Viewer: (
ct.NodeCategory.MAXWELLSIM_OUTPUTS
)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,27 +1,28 @@
from . import temporal_shapes from . import temporal_shapes
from . import point_dipole_source from . import point_dipole_source
#from . import uniform_current_source
# from . import uniform_current_source
from . import plane_wave_source from . import plane_wave_source
#from . import gaussian_beam_source # from . import gaussian_beam_source
#from . import astigmatic_gaussian_beam_source # from . import astigmatic_gaussian_beam_source
#from . import tfsf_source # from . import tfsf_source
BL_REGISTER = [ BL_REGISTER = [
*temporal_shapes.BL_REGISTER, *temporal_shapes.BL_REGISTER,
*point_dipole_source.BL_REGISTER, *point_dipole_source.BL_REGISTER,
# *uniform_current_source.BL_REGISTER, # *uniform_current_source.BL_REGISTER,
*plane_wave_source.BL_REGISTER, *plane_wave_source.BL_REGISTER,
# *gaussian_beam_source.BL_REGISTER, # *gaussian_beam_source.BL_REGISTER,
# *astigmatic_gaussian_beam_source.BL_REGISTER, # *astigmatic_gaussian_beam_source.BL_REGISTER,
# *tfsf_source.BL_REGISTER, # *tfsf_source.BL_REGISTER,
] ]
BL_NODES = { BL_NODES = {
**temporal_shapes.BL_NODES, **temporal_shapes.BL_NODES,
**point_dipole_source.BL_NODES, **point_dipole_source.BL_NODES,
# **uniform_current_source.BL_NODES, # **uniform_current_source.BL_NODES,
**plane_wave_source.BL_NODES, **plane_wave_source.BL_NODES,
# **gaussian_beam_source.BL_NODES, # **gaussian_beam_source.BL_NODES,
# **astigmatic_gaussian_beam_source.BL_NODES, # **astigmatic_gaussian_beam_source.BL_NODES,
# **tfsf_source.BL_NODES, # **tfsf_source.BL_NODES,
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -13,88 +13,89 @@ from ... import contracts as ct
from ... import sockets from ... import sockets
from .. import base from .. import base
GEONODES_PLANE_WAVE = "source_plane_wave" GEONODES_PLANE_WAVE = 'source_plane_wave'
def convert_vector_to_spherical( def convert_vector_to_spherical(
v: sp.MatrixBase, v: sp.MatrixBase,
) -> tuple[str, str, sp.Expr, sp.Expr]: ) -> tuple[str, str, sp.Expr, sp.Expr]:
"""Converts a vector (maybe normalized) to spherical coordinates from an arbitrary choice of injection axis. """Converts a vector (maybe normalized) to spherical coordinates from an arbitrary choice of injection axis.
Injection axis is chosen to minimize `theta` Injection axis is chosen to minimize `theta`
""" """
x, y, z = v x, y, z = v
injection_axis = max( injection_axis = max(
('x', abs(x)), ('x', abs(x)), ('y', abs(y)), ('z', abs(z)), key=lambda item: item[1]
('y', abs(y)),
('z', abs(z)),
key=lambda item: item[1]
)[0] )[0]
## Select injection axis that minimizes 'theta' ## Select injection axis that minimizes 'theta'
if injection_axis == "x": if injection_axis == 'x':
direction = "+" if x >= 0 else "-" direction = '+' if x >= 0 else '-'
theta = sp.acos(x / sp.sqrt(x**2 + y**2 + z**2)) theta = sp.acos(x / sp.sqrt(x**2 + y**2 + z**2))
phi = sp.atan2(z, y) phi = sp.atan2(z, y)
elif injection_axis == "y": elif injection_axis == 'y':
direction = "+" if y >= 0 else "-" direction = '+' if y >= 0 else '-'
theta = sp.acos(y / sp.sqrt(x**2 + y**2 + z**2)) theta = sp.acos(y / sp.sqrt(x**2 + y**2 + z**2))
phi = sp.atan2(x, z) phi = sp.atan2(x, z)
else: else:
direction = "+" if z >= 0 else "-" direction = '+' if z >= 0 else '-'
theta = sp.acos(z / sp.sqrt(x**2 + y**2 + z**2)) theta = sp.acos(z / sp.sqrt(x**2 + y**2 + z**2))
phi = sp.atan2(y, x) phi = sp.atan2(y, x)
return injection_axis, direction, theta, phi return injection_axis, direction, theta, phi
class PlaneWaveSourceNode(base.MaxwellSimNode): class PlaneWaveSourceNode(base.MaxwellSimNode):
node_type = ct.NodeType.PlaneWaveSource node_type = ct.NodeType.PlaneWaveSource
bl_label = "Plane Wave Source" bl_label = 'Plane Wave Source'
#################### ####################
# - Sockets # - Sockets
#################### ####################
input_sockets = { input_sockets = {
"Temporal Shape": sockets.MaxwellTemporalShapeSocketDef(), 'Temporal Shape': sockets.MaxwellTemporalShapeSocketDef(),
"Center": sockets.PhysicalPoint3DSocketDef(), 'Center': sockets.PhysicalPoint3DSocketDef(),
"Direction": sockets.Real3DVectorSocketDef( 'Direction': sockets.Real3DVectorSocketDef(
default_value=sp.Matrix([0, 0, -1]) default_value=sp.Matrix([0, 0, -1])
), ),
"Pol Angle": sockets.PhysicalAngleSocketDef(), 'Pol Angle': sockets.PhysicalAngleSocketDef(),
} }
output_sockets = { output_sockets = {
"Source": sockets.MaxwellSourceSocketDef(), 'Source': sockets.MaxwellSourceSocketDef(),
} }
managed_obj_defs = { managed_obj_defs = {
"plane_wave_source": ct.schemas.ManagedObjDef( 'plane_wave_source': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLObject(name), mk=lambda name: managed_objs.ManagedBLObject(name),
name_prefix="", name_prefix='',
) )
} }
#################### ####################
# - Output Socket Computation # - Output Socket Computation
#################### ####################
@base.computes_output_socket( @base.computes_output_socket(
"Source", 'Source',
input_sockets={"Temporal Shape", "Center", "Direction", "Pol Angle"}, input_sockets={'Temporal Shape', 'Center', 'Direction', 'Pol Angle'},
) )
def compute_source(self, input_sockets: dict): def compute_source(self, input_sockets: dict):
temporal_shape = input_sockets["Temporal Shape"] temporal_shape = input_sockets['Temporal Shape']
_center = input_sockets["Center"] _center = input_sockets['Center']
direction = input_sockets["Direction"] direction = input_sockets['Direction']
pol_angle = input_sockets["Pol Angle"] 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 = { size = {
"x": (0, math.inf, math.inf), 'x': (0, math.inf, math.inf),
"y": (math.inf, 0, math.inf), 'y': (math.inf, 0, math.inf),
"z": (math.inf, math.inf, 0), 'z': (math.inf, math.inf, 0),
}[injection_axis] }[injection_axis]
center = tuple(spu.convert_to(_center, spu.um) / spu.um) center = tuple(spu.convert_to(_center, spu.um) / spu.um)
# Display the results # Display the results
return td.PlaneWave( return td.PlaneWave(
center=center, center=center,
@ -105,74 +106,65 @@ class PlaneWaveSourceNode(base.MaxwellSimNode):
angle_phi=phi, angle_phi=phi,
pol_angle=pol_angle, pol_angle=pol_angle,
) )
#################### ####################
# - Preview # - Preview
#################### ####################
@base.on_value_changed( @base.on_value_changed(
socket_name={"Center", "Direction"}, socket_name={'Center', 'Direction'},
input_sockets={"Center", "Direction"}, input_sockets={'Center', 'Direction'},
managed_objs={"plane_wave_source"}, managed_objs={'plane_wave_source'},
) )
def on_value_changed__center_direction( def on_value_changed__center_direction(
self, self,
input_sockets: dict, input_sockets: dict,
managed_objs: dict[str, ct.schemas.ManagedObj], managed_objs: dict[str, ct.schemas.ManagedObj],
): ):
_center = input_sockets["Center"] _center = input_sockets['Center']
center = tuple([ center = tuple(
float(el) [float(el) for el in spu.convert_to(_center, spu.um) / spu.um]
for el in spu.convert_to(_center, spu.um) / spu.um )
])
_direction = input_sockets['Direction']
_direction = input_sockets["Direction"] direction = tuple([float(el) for el in _direction])
direction = tuple([
float(el)
for el in _direction
])
## TODO: Preview unit system?? Presume um for now ## TODO: Preview unit system?? Presume um for now
# Retrieve Hard-Coded GeoNodes and Analyze Input # Retrieve Hard-Coded GeoNodes and Analyze Input
geo_nodes = bpy.data.node_groups[GEONODES_PLANE_WAVE] geo_nodes = bpy.data.node_groups[GEONODES_PLANE_WAVE]
geonodes_interface = analyze_geonodes.interface( geonodes_interface = analyze_geonodes.interface(
geo_nodes, direc="INPUT" geo_nodes, direc='INPUT'
) )
# Sync Modifier Inputs # 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_node_group=geo_nodes,
geonodes_identifier_to_value={ geonodes_identifier_to_value={
geonodes_interface["Direction"].identifier: direction, geonodes_interface['Direction'].identifier: direction,
## TODO: Use 'bl_socket_map.value_to_bl`! ## TODO: Use 'bl_socket_map.value_to_bl`!
## - This accounts for auto-conversion, unit systems, etc. . ## - This accounts for auto-conversion, unit systems, etc. .
## - We could keep it in the node base class... ## - We could keep it in the node base class...
## - ...But it needs aligning with Blender, too. Hmm. ## - ...But it needs aligning with Blender, too. Hmm.
} },
) )
# Sync Object Position # 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( @base.on_show_preview(
managed_objs={"plane_wave_source"}, managed_objs={'plane_wave_source'},
) )
def on_show_preview( def on_show_preview(
self, self,
managed_objs: dict[str, ct.schemas.ManagedObj], 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() self.on_value_changed__center_direction()
#################### ####################
# - Blender Registration # - Blender Registration
#################### ####################
BL_REGISTER = [ BL_REGISTER = [
PlaneWaveSourceNode, PlaneWaveSourceNode,
] ]
BL_NODES = { BL_NODES = {ct.NodeType.PlaneWaveSource: (ct.NodeCategory.MAXWELLSIM_SOURCES)}
ct.NodeType.PlaneWaveSource: (
ct.NodeCategory.MAXWELLSIM_SOURCES
)
}

View File

@ -10,79 +10,82 @@ from ... import sockets
from .. import base from .. import base
from ... import managed_objs from ... import managed_objs
class PointDipoleSourceNode(base.MaxwellSimNode): class PointDipoleSourceNode(base.MaxwellSimNode):
node_type = ct.NodeType.PointDipoleSource node_type = ct.NodeType.PointDipoleSource
bl_label = "Point Dipole Source" bl_label = 'Point Dipole Source'
#################### ####################
# - Sockets # - Sockets
#################### ####################
input_sockets = { input_sockets = {
"Temporal Shape": sockets.MaxwellTemporalShapeSocketDef(), 'Temporal Shape': sockets.MaxwellTemporalShapeSocketDef(),
"Center": sockets.PhysicalPoint3DSocketDef(), 'Center': sockets.PhysicalPoint3DSocketDef(),
"Interpolate": sockets.BoolSocketDef( 'Interpolate': sockets.BoolSocketDef(
default_value=True, default_value=True,
), ),
} }
output_sockets = { output_sockets = {
"Source": sockets.MaxwellSourceSocketDef(), 'Source': sockets.MaxwellSourceSocketDef(),
} }
managed_obj_defs = { managed_obj_defs = {
"sphere_empty": ct.schemas.ManagedObjDef( 'sphere_empty': ct.schemas.ManagedObjDef(
mk=lambda name: managed_objs.ManagedBLObject(name), mk=lambda name: managed_objs.ManagedBLObject(name),
name_prefix="", name_prefix='',
) )
} }
#################### ####################
# - Properties # - Properties
#################### ####################
pol_axis: bpy.props.EnumProperty( pol_axis: bpy.props.EnumProperty(
name="Polarization Axis", name='Polarization Axis',
description="Polarization Axis", description='Polarization Axis',
items=[ items=[
("EX", "Ex", "Electric field in x-dir"), ('EX', 'Ex', 'Electric field in x-dir'),
("EY", "Ey", "Electric field in y-dir"), ('EY', 'Ey', 'Electric field in y-dir'),
("EZ", "Ez", "Electric field in z-dir"), ('EZ', 'Ez', 'Electric field in z-dir'),
], ],
default="EX", default='EX',
update=(lambda self, context: self.sync_prop("pol_axis", context)), update=(lambda self, context: self.sync_prop('pol_axis', context)),
) )
#################### ####################
# - UI # - UI
#################### ####################
def draw_props(self, context, layout): def draw_props(self, context, layout):
split = layout.split(factor=0.6) split = layout.split(factor=0.6)
col = split.column() col = split.column()
col.label(text="Pol Axis") col.label(text='Pol Axis')
col = split.column() col = split.column()
col.prop(self, "pol_axis", text="") col.prop(self, 'pol_axis', text='')
#################### ####################
# - Output Socket Computation # - Output Socket Computation
#################### ####################
@base.computes_output_socket( @base.computes_output_socket(
"Source", 'Source',
input_sockets={"Temporal Shape", "Center", "Interpolate"}, input_sockets={'Temporal Shape', 'Center', 'Interpolate'},
props={"pol_axis"}, 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 = { pol_axis = {
"EX": "Ex", 'EX': 'Ex',
"EY": "Ey", 'EY': 'Ey',
"EZ": "Ez", 'EZ': 'Ez',
}[props["pol_axis"]] }[props['pol_axis']]
temporal_shape = input_sockets["Temporal Shape"] temporal_shape = input_sockets['Temporal Shape']
_center = input_sockets["Center"] _center = input_sockets['Center']
interpolate = input_sockets["Interpolate"] interpolate = input_sockets['Interpolate']
center = tuple(spu.convert_to(_center, spu.um) / spu.um) center = tuple(spu.convert_to(_center, spu.um) / spu.um)
_res = td.PointDipole( _res = td.PointDipole(
center=center, center=center,
source_time=temporal_shape, source_time=temporal_shape,
@ -90,41 +93,42 @@ class PointDipoleSourceNode(base.MaxwellSimNode):
polarization=pol_axis, polarization=pol_axis,
) )
return _res return _res
#################### ####################
# - Preview # - Preview
#################### ####################
@base.on_value_changed( @base.on_value_changed(
socket_name="Center", socket_name='Center',
input_sockets={"Center"}, input_sockets={'Center'},
managed_objs={"sphere_empty"}, managed_objs={'sphere_empty'},
) )
def on_value_changed__center( def on_value_changed__center(
self, self,
input_sockets: dict, input_sockets: dict,
managed_objs: dict[str, ct.schemas.ManagedObj], 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) center = tuple(spu.convert_to(_center, spu.um) / spu.um)
## TODO: Preview unit system?? Presume um for now ## TODO: Preview unit system?? Presume um for now
mobj = managed_objs["sphere_empty"] mobj = managed_objs['sphere_empty']
bl_object = mobj.bl_object("EMPTY") bl_object = mobj.bl_object('EMPTY')
bl_object.location = center #tuple([float(el) for el in center]) bl_object.location = center # tuple([float(el) for el in center])
@base.on_show_preview( @base.on_show_preview(
managed_objs={"sphere_empty"}, managed_objs={'sphere_empty'},
) )
def on_show_preview( def on_show_preview(
self, self,
managed_objs: dict[str, ct.schemas.ManagedObj], managed_objs: dict[str, ct.schemas.ManagedObj],
): ):
managed_objs["sphere_empty"].show_preview( managed_objs['sphere_empty'].show_preview(
"EMPTY", 'EMPTY',
empty_display_type="SPHERE", 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, PointDipoleSourceNode,
] ]
BL_NODES = { BL_NODES = {
ct.NodeType.PointDipoleSource: ( ct.NodeType.PointDipoleSource: (ct.NodeCategory.MAXWELLSIM_SOURCES)
ct.NodeCategory.MAXWELLSIM_SOURCES
)
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,17 +1,15 @@
#from . import object_structure # from . import object_structure
from . import geonodes_structure from . import geonodes_structure
from . import primitives from . import primitives
BL_REGISTER = [ BL_REGISTER = [
# *object_structure.BL_REGISTER, # *object_structure.BL_REGISTER,
*geonodes_structure.BL_REGISTER, *geonodes_structure.BL_REGISTER,
*primitives.BL_REGISTER, *primitives.BL_REGISTER,
] ]
BL_NODES = { BL_NODES = {
# **object_structure.BL_NODES, # **object_structure.BL_NODES,
**geonodes_structure.BL_NODES, **geonodes_structure.BL_NODES,
**primitives.BL_NODES, **primitives.BL_NODES,
} }

View File

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

View File

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

View File

@ -1,14 +1,15 @@
from . import box_structure from . import box_structure
#from . import cylinder_structure
# from . import cylinder_structure
from . import sphere_structure from . import sphere_structure
BL_REGISTER = [ BL_REGISTER = [
*box_structure.BL_REGISTER, *box_structure.BL_REGISTER,
# *cylinder_structure.BL_REGISTER, # *cylinder_structure.BL_REGISTER,
*sphere_structure.BL_REGISTER, *sphere_structure.BL_REGISTER,
] ]
BL_NODES = { BL_NODES = {
**box_structure.BL_NODES, **box_structure.BL_NODES,
# **cylinder_structure.BL_NODES, # **cylinder_structure.BL_NODES,
**sphere_structure.BL_NODES, **sphere_structure.BL_NODES,
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,68 +7,72 @@ from .... import contracts
from .... import sockets from .... import sockets
from ... import base from ... import base
class WaveConverterNode(base.MaxwellSimTreeNode): class WaveConverterNode(base.MaxwellSimTreeNode):
node_type = contracts.NodeType.WaveConverter node_type = contracts.NodeType.WaveConverter
bl_label = "Wave Converter" bl_label = 'Wave Converter'
#bl_icon = ... # bl_icon = ...
#################### ####################
# - Sockets # - Sockets
#################### ####################
input_sockets = {} input_sockets = {}
input_socket_sets = { input_socket_sets = {
"freq_to_vacwl": { 'freq_to_vacwl': {
"freq": sockets.PhysicalFreqSocketDef( 'freq': sockets.PhysicalFreqSocketDef(
label="Freq", label='Freq',
), ),
}, },
"vacwl_to_freq": { 'vacwl_to_freq': {
"vacwl": sockets.PhysicalVacWLSocketDef( 'vacwl': sockets.PhysicalVacWLSocketDef(
label="Vac WL", label='Vac WL',
), ),
}, },
} }
output_sockets = {} output_sockets = {}
output_socket_sets = { output_socket_sets = {
"freq_to_vacwl": { 'freq_to_vacwl': {
"vacwl": sockets.PhysicalVacWLSocketDef( 'vacwl': sockets.PhysicalVacWLSocketDef(
label="Vac WL", label='Vac WL',
), ),
}, },
"vacwl_to_freq": { 'vacwl_to_freq': {
"freq": sockets.PhysicalFreqSocketDef( 'freq': sockets.PhysicalFreqSocketDef(
label="Freq", label='Freq',
), ),
}, },
} }
#################### ####################
# - Output Socket Computation # - Output Socket Computation
#################### ####################
@base.computes_output_socket("freq") @base.computes_output_socket('freq')
def compute_freq(self: contracts.NodeTypeProtocol) -> sp.Expr: 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( return spu.convert_to(
vac_speed_of_light / vacwl, vac_speed_of_light / vacwl,
spu.hertz, spu.hertz,
) )
@base.computes_output_socket("vacwl") @base.computes_output_socket('vacwl')
def compute_vacwl(self: contracts.NodeTypeProtocol) -> sp.Expr: 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( return spu.convert_to(
vac_speed_of_light / freq, vac_speed_of_light / freq,
spu.meter, spu.meter,
) )
#################### ####################
# - Blender Registration # - Blender Registration
#################### ####################

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