refactor: Big categories, structure change.
We're back down to a single working node, but this was a very practical refactor. In general, very good progress towards making #2 easy to fulfill in its entirety. Bugs remain: - Category discovery has big code smells and needs smoothing. Blender complains especially about wanting `_MT_` prefix/suffix on node category submenu types. There's also a piece of registration logic in category.py (big no no). - I'd love to pass a `ruff`/`mypy` run before doubling down on node creation, especially to help manage the complex pieces of MP logic. - Still needs socket-bound unit-awareness feat. sympy units, before doubling down on a data flow convention. - Dependency management should also be smoothed out wrt. the user experience, with cached directories exposed in addon preferences.blender-plugin-mvp
parent
a7fc66376b
commit
b592ea4b10
|
@ -0,0 +1,192 @@
|
||||||
|
# Node Design
|
||||||
|
Now that we can do all the cool things ex. presets and such, it's time to think more design.
|
||||||
|
|
||||||
|
## Categories
|
||||||
|
**NOTE**: Throughout, when an object can be selected (ex. for GeoNodes structure to affect), a button should be available to generate a new object for the occasion.
|
||||||
|
|
||||||
|
**NOTE**: Throughout, all nodes that output floats/vectors should have a sympy dimension. Any node that takes floats/vectors should either have a pre-defined unit (exposed as a string in the node UI), or a selectable unit (ex. for value inputs).
|
||||||
|
|
||||||
|
- Inputs
|
||||||
|
- Scene
|
||||||
|
- Time
|
||||||
|
- Object Info
|
||||||
|
- Parameter
|
||||||
|
- Float Parameter
|
||||||
|
- Complex Parameter
|
||||||
|
- Vec3 Parameter
|
||||||
|
- Constant
|
||||||
|
- Scientific Constant
|
||||||
|
- Float Constant
|
||||||
|
- Complex Constant
|
||||||
|
- 3-Vector Constant
|
||||||
|
- Array
|
||||||
|
- Element: Create a 1-element array, with a typed value.
|
||||||
|
- Float Array Element
|
||||||
|
- Complex Array Element
|
||||||
|
- 3-Vector Array Element
|
||||||
|
- Union: Concatenate two arrays.
|
||||||
|
- Float Array Union
|
||||||
|
- Complex Array Union
|
||||||
|
- 3-Vector Array Union
|
||||||
|
- Dictionary
|
||||||
|
- Element: Create a 1-element (typed) dictionary, with a string and a typed value.
|
||||||
|
- Float Dict Element
|
||||||
|
- Complex Dict Element
|
||||||
|
- 3-Vector Dict Element
|
||||||
|
- Union: Concatenate two dictionaries.
|
||||||
|
- Float Dict Element
|
||||||
|
- Complex Dict Element
|
||||||
|
- 3-Vector Dict Element
|
||||||
|
- Field
|
||||||
|
- Float Field
|
||||||
|
- Complex Field
|
||||||
|
- Vec3 Field
|
||||||
|
|
||||||
|
- Outputs
|
||||||
|
- Viewer
|
||||||
|
- Value Viewer: Live-monitor non-special types.
|
||||||
|
- Console Viewer: Print to console with button.
|
||||||
|
- Exporter
|
||||||
|
- JSON File Export
|
||||||
|
|
||||||
|
- Viz
|
||||||
|
- Temporal Shape Viz: Some kind of plot (animated) of the shape.
|
||||||
|
- Source Viz: 3D
|
||||||
|
- Structure Viz
|
||||||
|
- Bound Viz
|
||||||
|
- FDTD Viz
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- Sources
|
||||||
|
- Temporal Shapes
|
||||||
|
- Gaussian Pulse Temporal Shape
|
||||||
|
- Continuous Wave Temporal Shape
|
||||||
|
- Data Driven Temporal Shape
|
||||||
|
|
||||||
|
- Modelled
|
||||||
|
- Point Dipole Source
|
||||||
|
- Uniform Current Source
|
||||||
|
- Plane Wave Source
|
||||||
|
- Mode Source
|
||||||
|
- Gaussian Beam Source
|
||||||
|
- Astigmatic Gaussian Beam Source
|
||||||
|
- TFSF Source
|
||||||
|
|
||||||
|
- Data-Driven
|
||||||
|
- E/H Equivalence Source
|
||||||
|
- E/H Source
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- Mediums
|
||||||
|
- **NOTE**: Mediums should optionally accept a spatially varying field. If not, the medium should be considered spatially uniform.
|
||||||
|
- **NOTE**: Mediums should optionally accept non-linear effects, either individually or summed using Non-Linear / Operations / Add.
|
||||||
|
- **NOTE**: Mediums should optionally accept space-time modulation effects, either individually or summed using Non-Linear / Operations / Add.
|
||||||
|
|
||||||
|
- Library Medium
|
||||||
|
- **NOTE**: Should provide an EnumProperty of materials with its own categorizations. It should provide another EnumProperty to choose the experiment. It should also be filterable by wavelength range, maybe also model info. Finally, a reference should be generated on use as text.
|
||||||
|
|
||||||
|
- Linear Mediums
|
||||||
|
- PEC Medium
|
||||||
|
- Isotropic Medium
|
||||||
|
- Anisotropic Medium
|
||||||
|
|
||||||
|
- 3-Sellmeier Medium
|
||||||
|
- Sellmeier Medium
|
||||||
|
- Pole-Residue Medium
|
||||||
|
- Drude Medium
|
||||||
|
- Drude-Lorentz Medium
|
||||||
|
- Debye Medium
|
||||||
|
|
||||||
|
- Non-Linearities
|
||||||
|
- Add Non-Linearity
|
||||||
|
- \chi_3 Susceptibility Non-Linearity
|
||||||
|
- Two-Photon Absorption Non-Linearity
|
||||||
|
- Kerr Non-Linearity
|
||||||
|
|
||||||
|
- Space/Time \epsilon/\mu Modulation
|
||||||
|
|
||||||
|
- Structures
|
||||||
|
- TriMesh Structure
|
||||||
|
|
||||||
|
- Primitives
|
||||||
|
- Box Structure
|
||||||
|
- Sphere Structure
|
||||||
|
- Cylinder Structure
|
||||||
|
- Generated
|
||||||
|
- GeoNodes Structure: Takes dict, compute geonode tree on new object.
|
||||||
|
- Scripted Structure: Python script generating geometry.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- Bounds
|
||||||
|
- Bound Box
|
||||||
|
|
||||||
|
- Bound Faces
|
||||||
|
- PML Bound Face: Should have an option to switch to "stable".
|
||||||
|
- PEC Bound Face
|
||||||
|
- PMC Bound Face
|
||||||
|
|
||||||
|
- Bloch Bound Face
|
||||||
|
- Periodic Bound Face
|
||||||
|
- Absorbing Bound Face
|
||||||
|
|
||||||
|
|
||||||
|
- Monitors
|
||||||
|
- **NOTE**: Some/all should have dropdown to choose between a single (aka. steady-state) monitoring at the end of the simulation, or a continuous / animated (aka. movie) monitoring over the course of the whole simulation.
|
||||||
|
|
||||||
|
- **TODO**: "Modal" solver monitoring (seems to be some kind of spatial+frequency feature, which an EM field can be decomposed into using a specially configured solver, which can be used to look for very particular kinds of effects by constraining investigations of a solver result to filter out everything that isn't these particular modes aka. features. Kind of a fourier-based redimensionalization, almost).
|
||||||
|
|
||||||
|
- E/H Field Monitor
|
||||||
|
- Field Power Flux Monitor
|
||||||
|
- \epsilon Tensor Monitor
|
||||||
|
- Diffraction Monitor
|
||||||
|
|
||||||
|
- Near-Field Projections
|
||||||
|
- **TODO**: Figure out exactly how to deal with these visually.
|
||||||
|
|
||||||
|
- Cartesian Near-Field Projection Monitor
|
||||||
|
- Observation Angle Near-Field Projection Monitor
|
||||||
|
- K-Space Near-Field Projection Monitor
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- Simulations
|
||||||
|
- FDTD Simulation
|
||||||
|
|
||||||
|
- Discretizations
|
||||||
|
- Simulation Grid Discretization
|
||||||
|
- 1D Grid Discretizations
|
||||||
|
- Automatic 1D Grid Discretization
|
||||||
|
- Manual 1D Grid Discretization
|
||||||
|
- Uniform 1D Grid Discretization
|
||||||
|
- Data-Driven 1D Grid Discretization
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- Utilities
|
||||||
|
- Math: Contains a dropdown for operation.
|
||||||
|
- Float Math
|
||||||
|
- Complex Math
|
||||||
|
- Vector Math
|
||||||
|
- Field Math: Contains a dropdown for operation.
|
||||||
|
- Float Field Math
|
||||||
|
- Complex Field Math
|
||||||
|
- 3-Vector Field Math
|
||||||
|
- Spectral Math: Contains a dropdown for operation.
|
||||||
|
|
||||||
|
|
||||||
|
### Structures
|
||||||
|
|
||||||
|
All should support
|
||||||
|
- Medium
|
||||||
|
|
||||||
|
TriMesh should support:
|
||||||
|
- EnumProperty defining whether to select an object or a collection.
|
||||||
|
- Medium
|
||||||
|
|
||||||
|
**NOTE**: When several geometries assigned to the same medium are assigned to the same `tidy3d.GeometryGroup`, there can apparently be "significant performance enhancement"s (<https://docs.flexcompute.com/projects/tidy3d/en/latest/_autosummary/tidy3d.GeometryGroup.html#tidy3d.GeometryGroup>).
|
||||||
|
- We can and should, in the Simulation builder (or just the structure concatenator), batch together structures with the same Medium.
|
||||||
|
|
||||||
|
###
|
|
@ -53,20 +53,10 @@ BL_NODE_CATEGORIES = [
|
||||||
def register():
|
def register():
|
||||||
for cls in BL_REGISTER:
|
for cls in BL_REGISTER:
|
||||||
bpy.utils.register_class(cls)
|
bpy.utils.register_class(cls)
|
||||||
|
|
||||||
for bl_node_category in BL_NODE_CATEGORIES:
|
|
||||||
nodeitems_utils.register_node_categories(*bl_node_category)
|
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
for bl_node_category in reversed(BL_NODE_CATEGORIES):
|
|
||||||
try:
|
|
||||||
nodeitems_utils.unregister_node_categories(bl_node_category[0])
|
|
||||||
except: pass
|
|
||||||
|
|
||||||
for cls in reversed(BL_REGISTER):
|
for cls in reversed(BL_REGISTER):
|
||||||
try:
|
bpy.utils.unregister_class(cls)
|
||||||
bpy.utils.unregister_class(cls)
|
|
||||||
except: pass
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
register()
|
register()
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
from . import tree, socket_types, nodes
|
from . import nodes
|
||||||
|
from . import categories
|
||||||
|
from . import socket_types
|
||||||
|
from . import tree
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
*tree.BL_REGISTER,
|
*tree.BL_REGISTER,
|
||||||
*socket_types.BL_REGISTER,
|
*socket_types.BL_REGISTER,
|
||||||
*nodes.BL_REGISTER,
|
*nodes.BL_REGISTER,
|
||||||
|
*categories.BL_REGISTER,
|
||||||
]
|
]
|
||||||
|
|
||||||
BL_NODE_CATEGORIES = [
|
BL_NODE_CATEGORIES = [
|
||||||
*nodes.BL_NODE_CATEGORIES,
|
*categories.BL_NODE_CATEGORIES,
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
import bpy
|
||||||
|
import nodeitems_utils
|
||||||
|
from . import types
|
||||||
|
from .nodes import BL_NODES
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Assembly of Node Categories
|
||||||
|
####################
|
||||||
|
class MaxwellSimNodeCategory(nodeitems_utils.NodeCategory):
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
"""Constrain node category availability to within a MaxwellSimTree."""
|
||||||
|
|
||||||
|
return context.space_data.tree_type == types.TreeType.MaxwellSim.value
|
||||||
|
|
||||||
|
DYNAMIC_SUBMENU_REGISTRATIONS = []
|
||||||
|
def mk_node_categories(
|
||||||
|
tree,
|
||||||
|
syllable_prefix = [],
|
||||||
|
#root = True,
|
||||||
|
):
|
||||||
|
global DYNAMIC_SUBMENU_REGISTRATIONS
|
||||||
|
items = []
|
||||||
|
|
||||||
|
# Add Node Items
|
||||||
|
base_category = types.NodeCategory["_".join(syllable_prefix)]
|
||||||
|
for node_type, node_category in BL_NODES.items():
|
||||||
|
if node_category == base_category:
|
||||||
|
items.append(nodeitems_utils.NodeItem(node_type.value))
|
||||||
|
|
||||||
|
# Add Node Sub-Menus
|
||||||
|
for syllable, sub_tree in tree.items():
|
||||||
|
current_syllable_path = syllable_prefix + [syllable]
|
||||||
|
current_category = types.NodeCategory[
|
||||||
|
"_".join(current_syllable_path)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Build Items for Sub-Categories
|
||||||
|
subitems = mk_node_categories(
|
||||||
|
sub_tree,
|
||||||
|
current_syllable_path,
|
||||||
|
)
|
||||||
|
if len(subitems) == 0: continue
|
||||||
|
|
||||||
|
# Define Dynamic Node Submenu
|
||||||
|
def draw_factory(items):
|
||||||
|
def draw(self, context):
|
||||||
|
for nodeitem_or_submenu in items:
|
||||||
|
if isinstance(
|
||||||
|
nodeitem_or_submenu,
|
||||||
|
nodeitems_utils.NodeItem,
|
||||||
|
):
|
||||||
|
nodeitem = nodeitem_or_submenu
|
||||||
|
self.layout.operator(
|
||||||
|
"node.add_node",
|
||||||
|
text=nodeitem.label,
|
||||||
|
).type = nodeitem.nodetype
|
||||||
|
elif isinstance(nodeitem_or_submenu, str):
|
||||||
|
submenu_id = nodeitem_or_submenu
|
||||||
|
self.layout.menu(submenu_id)
|
||||||
|
return draw
|
||||||
|
|
||||||
|
menu_class = type(current_category.value, (bpy.types.Menu,), {
|
||||||
|
'bl_idname': current_category.value,
|
||||||
|
'bl_label': types.NodeCategory_to_category_label[current_category],
|
||||||
|
'draw': draw_factory(tuple(subitems)),
|
||||||
|
})
|
||||||
|
|
||||||
|
# Report to Items and Registration List
|
||||||
|
items.append(current_category.value)
|
||||||
|
DYNAMIC_SUBMENU_REGISTRATIONS.append(menu_class)
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Registration
|
||||||
|
####################
|
||||||
|
BL_NODE_CATEGORIES = mk_node_categories(
|
||||||
|
types.NodeCategory.get_tree()["MAXWELL"]["SIM"],
|
||||||
|
syllable_prefix = ["MAXWELL", "SIM"],
|
||||||
|
)
|
||||||
|
## TODO: refractor, this has a big code smell
|
||||||
|
BL_REGISTER = [
|
||||||
|
*DYNAMIC_SUBMENU_REGISTRATIONS
|
||||||
|
] ## Must be run after, right now.
|
||||||
|
|
||||||
|
## TEST - TODO this is a big code smell
|
||||||
|
def menu_draw(self, context):
|
||||||
|
if context.space_data.tree_type == types.TreeType.MaxwellSim.value:
|
||||||
|
for nodeitem_or_submenu in BL_NODE_CATEGORIES:
|
||||||
|
if isinstance(nodeitem_or_submenu, str):
|
||||||
|
submenu_id = nodeitem_or_submenu
|
||||||
|
self.layout.menu(submenu_id)
|
||||||
|
|
||||||
|
bpy.types.NODE_MT_add.append(menu_draw)
|
|
@ -1,9 +1,8 @@
|
||||||
from . import definitions, categories
|
from . import mediums
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
*definitions.BL_REGISTER,
|
*mediums.BL_REGISTER,
|
||||||
]
|
|
||||||
|
|
||||||
BL_NODE_CATEGORIES = [
|
|
||||||
*categories.BL_NODE_CATEGORIES,
|
|
||||||
]
|
]
|
||||||
|
BL_NODES = {
|
||||||
|
**mediums.BL_NODES,
|
||||||
|
}
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
import nodeitems_utils
|
|
||||||
from . import types
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Node Category Types
|
|
||||||
####################
|
|
||||||
CATEGORIES_MAXWELL_SIM = 'MAXWELL_SIM'
|
|
||||||
|
|
||||||
CATEGORY_MAXWELL_SIM_DEBUG = 'MAXWELL_SIM_DEBUG'
|
|
||||||
CATEGORY_MAXWELL_SIM_SOURCES = 'MAXWELL_SIM_SOURCES'
|
|
||||||
CATEGORY_MAXWELL_SIM_MEDIUMS = 'MAXWELL_SIM_MEDIUMS'
|
|
||||||
CATEGORY_MAXWELL_SIM_STRUCTURES = 'MAXWELL_SIM_STRUCTURES'
|
|
||||||
CATEGORY_MAXWELL_SIM_BOUNDS = 'MAXWELL_SIM_BOUNDS'
|
|
||||||
CATEGORY_MAXWELL_SIM_SIMULATIONS = 'MAXWELL_SIM_SIMULATIONS'
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Node Category Class
|
|
||||||
####################
|
|
||||||
class MaxwellSimNodeCategory(nodeitems_utils.NodeCategory):
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
"""Constrain node category availability to within a MaxwellSimTree."""
|
|
||||||
|
|
||||||
return context.space_data.tree_type == types.tree_types.MaxwellSimTreeType
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Node Category Definition
|
|
||||||
####################
|
|
||||||
CATEGORIES_MaxwellSimTree = [
|
|
||||||
MaxwellSimNodeCategory(CATEGORY_MAXWELL_SIM_DEBUG, "Debug", items=[
|
|
||||||
nodeitems_utils.NodeItem(types.DebugPrinterNodeType),
|
|
||||||
]),
|
|
||||||
MaxwellSimNodeCategory(CATEGORY_MAXWELL_SIM_SOURCES, "Sources", items=[
|
|
||||||
nodeitems_utils.NodeItem(types.PointDipoleMaxwellSourceNodeType),
|
|
||||||
]),
|
|
||||||
MaxwellSimNodeCategory(CATEGORY_MAXWELL_SIM_MEDIUMS, "Mediums", items=[
|
|
||||||
nodeitems_utils.NodeItem(types.SellmeierMaxwellMediumNodeType),
|
|
||||||
]),
|
|
||||||
MaxwellSimNodeCategory(CATEGORY_MAXWELL_SIM_STRUCTURES, "Structures", items=[
|
|
||||||
nodeitems_utils.NodeItem(types.TriMeshMaxwellStructureNodeType),
|
|
||||||
]),
|
|
||||||
MaxwellSimNodeCategory(CATEGORY_MAXWELL_SIM_BOUNDS, "Bounds", items=[
|
|
||||||
nodeitems_utils.NodeItem(types.PMLMaxwellBoundNodeType),
|
|
||||||
]),
|
|
||||||
MaxwellSimNodeCategory(CATEGORY_MAXWELL_SIM_SIMULATIONS, "Simulation", items=[
|
|
||||||
nodeitems_utils.NodeItem(types.FDTDMaxwellSimulationNodeType),
|
|
||||||
]),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Blender Registration
|
|
||||||
####################
|
|
||||||
BL_NODE_CATEGORIES = [
|
|
||||||
(CATEGORIES_MAXWELL_SIM, CATEGORIES_MaxwellSimTree)
|
|
||||||
]
|
|
|
@ -1 +0,0 @@
|
||||||
from .. import constants as tree_constants
|
|
|
@ -1,16 +0,0 @@
|
||||||
#from . import bounds, mediums, simulations, sources, structures
|
|
||||||
from . import debug
|
|
||||||
from . import sources
|
|
||||||
#from . import mediums
|
|
||||||
#from . import structures
|
|
||||||
#from . import bounds
|
|
||||||
from . import simulations
|
|
||||||
|
|
||||||
BL_REGISTER = [
|
|
||||||
*debug.BL_REGISTER,
|
|
||||||
#*bounds.BL_REGISTER,
|
|
||||||
#*mediums.BL_REGISTER,
|
|
||||||
*sources.BL_REGISTER,
|
|
||||||
#*structures.BL_REGISTER,
|
|
||||||
*simulations.BL_REGISTER,
|
|
||||||
]
|
|
|
@ -1 +0,0 @@
|
||||||
BL_REGISTER = []
|
|
|
@ -1,53 +0,0 @@
|
||||||
import bpy
|
|
||||||
from .. import types, constants
|
|
||||||
|
|
||||||
class DebugPrinterNodeOperator(bpy.types.Operator):
|
|
||||||
"""Print, to the console, the object retrieved by the calling
|
|
||||||
DebugPrinterNode.
|
|
||||||
"""
|
|
||||||
|
|
||||||
bl_idname = "blender_maxwell.debug_printer_node_operator"
|
|
||||||
bl_label = "Print the object linked into a DebugPrinterNode."
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
node = context.node
|
|
||||||
node.print_linked_data()
|
|
||||||
return {'FINISHED'}
|
|
||||||
|
|
||||||
class DebugPrinterNode(types.MaxwellSimTreeNode):
|
|
||||||
bl_idname = types.DebugPrinterNodeType
|
|
||||||
bl_label = "Debug Printer"
|
|
||||||
bl_icon = constants.tree_constants.ICON_SIM
|
|
||||||
|
|
||||||
input_sockets = {
|
|
||||||
"data": ("NodeSocketVirtual", "Data", lambda v: v),
|
|
||||||
}
|
|
||||||
output_sockets = {}
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Setup and Computation
|
|
||||||
####################
|
|
||||||
def print_linked_data(self):
|
|
||||||
if self.inputs[self.input_sockets["data"][1]].is_linked:
|
|
||||||
print(self.compute_input("data"))
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Node UI and Layout
|
|
||||||
####################
|
|
||||||
def draw_buttons(self, context, layout):
|
|
||||||
layout.operator(DebugPrinterNodeOperator.bl_idname, text="Print Debug")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Blender Registration
|
|
||||||
####################
|
|
||||||
BL_REGISTER = [
|
|
||||||
DebugPrinterNodeOperator,
|
|
||||||
DebugPrinterNode,
|
|
||||||
]
|
|
|
@ -1,88 +0,0 @@
|
||||||
from dataclasses import dataclass
|
|
||||||
import bpy
|
|
||||||
from .. import types, constants
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Utilities (to move out)
|
|
||||||
####################
|
|
||||||
def mk_set_preset(preset_dataclass):
|
|
||||||
def set_preset(self, context):
|
|
||||||
if self.preset in preset_dataclass.values:
|
|
||||||
for parameter, value in preset_dataclass.values.items():
|
|
||||||
self.inputs[parameter].default_value = value
|
|
||||||
|
|
||||||
return set_preset
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Medium: Sellmeier
|
|
||||||
####################
|
|
||||||
@dataclass
|
|
||||||
class _Presets_SellmeierMaxwellMediumNode:
|
|
||||||
descriptions = [
|
|
||||||
('BK7', "BK7 Glass", "Borosilicate crown glass (known as BK7)"),
|
|
||||||
('FUSED_SILICA', "Fused Silica", "Fused silica aka. SiO2"),
|
|
||||||
]
|
|
||||||
values = {
|
|
||||||
"BK7": {
|
|
||||||
"B1": 1.03961212,
|
|
||||||
"B2": 0.231792344,
|
|
||||||
"B3": 1.01046945,
|
|
||||||
"C1 (um^2)": 6.00069867e-3,
|
|
||||||
"C2 (um^2)": 2.00179144e-2,
|
|
||||||
"C3 (um^2)": 103.560653,
|
|
||||||
},
|
|
||||||
"FUSED_SILICA": {
|
|
||||||
"B1": 0.696166300,
|
|
||||||
"B2": 0.407942600,
|
|
||||||
"B3": 0.897479400,
|
|
||||||
"C1 (um^2)": 4.67914826e-3,
|
|
||||||
"C2 (um^2)": 1.35120631e-2,
|
|
||||||
"C3 (um^2)": 97.9340025,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
def mk_set_preset(self):
|
|
||||||
return mk_set_preset(self)
|
|
||||||
|
|
||||||
Presets_SellmeierMaxwellMediumNode = _Presets_SellmeierMaxwellMediumNode()
|
|
||||||
|
|
||||||
class SellmeierMaxwellMediumNode(types.MaxwellSimTreeNode, bpy.types.Node):
|
|
||||||
bl_idname = types.SellmeierMaxwellMediumNodeType
|
|
||||||
bl_label = "Sellmeier"
|
|
||||||
bl_icon = constants.tree_constants.ICON_SIM_MEDIUM
|
|
||||||
|
|
||||||
preset: bpy.props.EnumProperty(
|
|
||||||
name="Presets",
|
|
||||||
description="Select a preset",
|
|
||||||
items=Presets_SellmeierMaxwellMediumNode.descriptions,
|
|
||||||
default='BK7',
|
|
||||||
update=Presets_SellmeierMaxwellMediumNode.mk_set_preset(),
|
|
||||||
)
|
|
||||||
|
|
||||||
def init(self, context):
|
|
||||||
# Declare Node Inputs
|
|
||||||
self.inputs.new('NodeSocketFloat', "B1")
|
|
||||||
self.inputs.new('NodeSocketFloat', "B2")
|
|
||||||
self.inputs.new('NodeSocketFloat', "B3")
|
|
||||||
self.inputs.new('NodeSocketFloat', "C1 (um^2)")
|
|
||||||
self.inputs.new('NodeSocketFloat', "C2 (um^2)")
|
|
||||||
self.inputs.new('NodeSocketFloat', "C3 (um^2)")
|
|
||||||
|
|
||||||
# Declare Node Outputs
|
|
||||||
self.outputs.new(types.tree_types.MaxwellMediumSocketType, "Medium")
|
|
||||||
|
|
||||||
# Set Preset Values
|
|
||||||
Presets_SellmeierMaxwellMediumNode.mk_set_preset()(self, context)
|
|
||||||
|
|
||||||
def draw_buttons(self, context, layout):
|
|
||||||
layout.prop(self, 'sellmeier_presets', text="")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Blender Registration
|
|
||||||
####################
|
|
||||||
BL_REGISTER = [
|
|
||||||
SellmeierMaxwellMediumNode,
|
|
||||||
]
|
|
|
@ -1,49 +0,0 @@
|
||||||
import bpy
|
|
||||||
import tidy3d as td
|
|
||||||
|
|
||||||
from .. import types, constants
|
|
||||||
|
|
||||||
class FDTDMaxwellSimulationNode(types.MaxwellSimTreeNode, bpy.types.Node):
|
|
||||||
bl_idname = types.FDTDMaxwellSimulationNodeType
|
|
||||||
bl_label = "FDTD"
|
|
||||||
bl_icon = constants.tree_constants.ICON_SIM_SIMULATION
|
|
||||||
|
|
||||||
input_sockets = {
|
|
||||||
"run_time": ("NodeSocketFloatTime", "Run Time"),
|
|
||||||
"ambient_medium": (types.tree_types.MaxwellMediumSocketType, "Ambient Medium"),
|
|
||||||
"source": (types.tree_types.MaxwellSourceSocketType, "Source"),
|
|
||||||
"structure": (types.tree_types.MaxwellStructureSocketType, "Structure"),
|
|
||||||
"bound": (types.tree_types.MaxwellBoundSocketType, "Bound"),
|
|
||||||
}
|
|
||||||
output_sockets = {
|
|
||||||
"fdtd_sim": (types.tree_types.MaxwellFDTDSimSocketType, "FDTD Sim")
|
|
||||||
}
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Socket Properties
|
|
||||||
####################
|
|
||||||
@types.output_socket_cb("fdtd_sim")
|
|
||||||
def output_source(self):
|
|
||||||
return td.Simulation(
|
|
||||||
#center= ## Default: 0,0,0
|
|
||||||
size=(1, 1, 1), ## PLACEHOLDER
|
|
||||||
run_time=self.compute_input("run_time"),
|
|
||||||
#structures=[],
|
|
||||||
#symmetry=,
|
|
||||||
sources=[self.compute_input("source")],
|
|
||||||
#boundary_spec=, ## Default: PML
|
|
||||||
monitors=[],
|
|
||||||
#grid_spec=, ## Default: Autogrid
|
|
||||||
#shutoff=, ## Default: 1e-05
|
|
||||||
#subpixel=, ## Default: True
|
|
||||||
#normalize_index=, ## Default: 0
|
|
||||||
#courant=, ## Default: 0.99
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Blender Registration
|
|
||||||
####################
|
|
||||||
BL_REGISTER = [
|
|
||||||
FDTDMaxwellSimulationNode,
|
|
||||||
]
|
|
|
@ -1,63 +0,0 @@
|
||||||
import bpy
|
|
||||||
from .. import types, constants
|
|
||||||
|
|
||||||
import tidy3d as td
|
|
||||||
|
|
||||||
class PointDipoleMaxwellSourceNode(types.MaxwellSimTreeNode, bpy.types.Node):
|
|
||||||
bl_idname = types.PointDipoleMaxwellSourceNodeType
|
|
||||||
bl_label = "Point Dipole"
|
|
||||||
bl_icon = constants.tree_constants.ICON_SIM_SOURCE
|
|
||||||
|
|
||||||
input_sockets = {
|
|
||||||
"center": ("NodeSocketVector", "Center"),
|
|
||||||
"interpolate": ("NodeSocketBool", "Interpolate"),
|
|
||||||
}
|
|
||||||
output_sockets = {
|
|
||||||
"source": (types.tree_types.MaxwellSourceSocketType, "Source")
|
|
||||||
}
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Properties
|
|
||||||
####################
|
|
||||||
polarization: bpy.props.EnumProperty(
|
|
||||||
name="Polarization",
|
|
||||||
description="Polarization of the generated point dipole field",
|
|
||||||
items=[
|
|
||||||
("Ex", "Ex", "x-component of E-field"),
|
|
||||||
("Ey", "Ey", "y-component of E-field"),
|
|
||||||
("Ez", "Ez", "z-component of E-field"),
|
|
||||||
("Hx", "Hx", "x-component of H-field"),
|
|
||||||
("Hy", "Hy", "y-component of H-field"),
|
|
||||||
("Hz", "Hz", "z-component of H-field"),
|
|
||||||
],
|
|
||||||
default="Ex",
|
|
||||||
)
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Node UI and Layout
|
|
||||||
####################
|
|
||||||
def draw_buttons(self, context, layout):
|
|
||||||
layout.prop(self, 'polarization', text="")
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Socket Properties
|
|
||||||
####################
|
|
||||||
@types.output_socket_cb("source")
|
|
||||||
def output_source(self):
|
|
||||||
return td.PointDipole(
|
|
||||||
center=tuple(self.compute_input("center")),
|
|
||||||
size=(0, 0, 0),
|
|
||||||
source_time=td.GaussianPulse(freq0=200e12, fwidth=200e12),
|
|
||||||
## ^ Placeholder
|
|
||||||
interpolate=self.compute_input("interpolate"),
|
|
||||||
polarization=str(self.polarization),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Blender Registration
|
|
||||||
####################
|
|
||||||
BL_REGISTER = [
|
|
||||||
PointDipoleMaxwellSourceNode,
|
|
||||||
]
|
|
|
@ -1,25 +0,0 @@
|
||||||
import bpy
|
|
||||||
from .. import types, constants
|
|
||||||
|
|
||||||
class TriMeshMaxwellStructureNode(types.MaxwellSimTreeNode, bpy.types.Node):
|
|
||||||
bl_idname = types.TriMeshMaxwellStructureNodeType
|
|
||||||
bl_label = "TriMesh"
|
|
||||||
bl_icon = constants.tree_constants.ICON_SIM_STRUCTURE
|
|
||||||
|
|
||||||
# Initialization - Called on Node Creation
|
|
||||||
def init(self, context):
|
|
||||||
# Declare Node Inputs
|
|
||||||
self.inputs.new('NodeSocketObject', "Object")
|
|
||||||
self.inputs.new(types.tree_types.MaxwellMediumSocketType, "Medium")
|
|
||||||
|
|
||||||
# Declare Node Outputs
|
|
||||||
self.outputs.new(types.tree_types.MaxwellStructureSocketType, "Structure")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Blender Registration
|
|
||||||
####################
|
|
||||||
BL_REGISTER = [
|
|
||||||
TriMeshMaxwellStructureNode,
|
|
||||||
]
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
from . import linear_mediums
|
||||||
|
|
||||||
|
BL_REGISTER = [
|
||||||
|
*linear_mediums.BL_REGISTER,
|
||||||
|
]
|
||||||
|
BL_NODES = {
|
||||||
|
**linear_mediums.BL_NODES,
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
from . import triple_sellmeier_medium
|
||||||
|
|
||||||
|
BL_REGISTER = [
|
||||||
|
*triple_sellmeier_medium.BL_REGISTER,
|
||||||
|
]
|
||||||
|
BL_NODES = {
|
||||||
|
**triple_sellmeier_medium.BL_NODES,
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
import bpy
|
||||||
|
from .... import types, constants
|
||||||
|
from ... import node_base
|
||||||
|
|
||||||
|
class TripleSellmeierMediumNode(node_base.MaxwellSimTreeNode):
|
||||||
|
bl_idname = types.NodeType.TripleSellmeierMedium.value
|
||||||
|
bl_label = "Triple Sellmeier Medium"
|
||||||
|
bl_icon = constants.ICON_SIM_MEDIUM
|
||||||
|
|
||||||
|
input_sockets = {
|
||||||
|
"B1": ("NodeSocketFloat", "B1"),
|
||||||
|
"B2": ("NodeSocketFloat", "B2"),
|
||||||
|
"B3": ("NodeSocketFloat", "B3"),
|
||||||
|
"C1": ("NodeSocketFloat", "C1 (um^2)"),
|
||||||
|
"C2": ("NodeSocketFloat", "C2 (um^2)"),
|
||||||
|
"C3": ("NodeSocketFloat", "C3 (um^2)"),
|
||||||
|
}
|
||||||
|
output_sockets = {
|
||||||
|
"medium": (types.SocketType.MaxwellMedium, "Medium")
|
||||||
|
}
|
||||||
|
socket_presets = {
|
||||||
|
"_description": [
|
||||||
|
('BK7', "BK7 Glass", "Borosilicate crown glass (known as BK7)"),
|
||||||
|
('FUSED_SILICA', "Fused Silica", "Fused silica aka. SiO2"),
|
||||||
|
],
|
||||||
|
"_default": "BK7",
|
||||||
|
"_values": {
|
||||||
|
"BK7": {
|
||||||
|
"B1": 1.03961212,
|
||||||
|
"B2": 0.231792344,
|
||||||
|
"B3": 1.01046945,
|
||||||
|
"C1": 6.00069867e-3,
|
||||||
|
"C2": 2.00179144e-2,
|
||||||
|
"C3": 103.560653,
|
||||||
|
},
|
||||||
|
"FUSED_SILICA": {
|
||||||
|
"B1": 0.696166300,
|
||||||
|
"B2": 0.407942600,
|
||||||
|
"B3": 0.897479400,
|
||||||
|
"C1": 4.67914826e-3,
|
||||||
|
"C2": 1.35120631e-2,
|
||||||
|
"C3": 97.9340025,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Properties
|
||||||
|
####################
|
||||||
|
def draw_buttons(self, context, layout):
|
||||||
|
layout.prop(self, 'preset', text="")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Registration
|
||||||
|
####################
|
||||||
|
BL_REGISTER = [
|
||||||
|
TripleSellmeierMediumNode,
|
||||||
|
]
|
||||||
|
BL_NODES = {
|
||||||
|
types.NodeType.TripleSellmeierMedium: (
|
||||||
|
types.NodeCategory.MAXWELL_SIM_MEDIUMS_LINEARMEDIUMS
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,31 +1,21 @@
|
||||||
import bpy
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from .. import types as tree_types
|
import bpy
|
||||||
|
import nodeitems_utils
|
||||||
|
|
||||||
|
from .. import types
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Blender Types
|
# - Decorator: Output Socket
|
||||||
####################
|
|
||||||
DebugPrinterNodeType = 'DebugPrinterNodeType'
|
|
||||||
|
|
||||||
PointDipoleMaxwellSourceNodeType = 'PointDipoleMaxwellSourceNodeType'
|
|
||||||
|
|
||||||
SellmeierMaxwellMediumNodeType = 'SellmeierMaxwellMediumNodeType'
|
|
||||||
|
|
||||||
TriMeshMaxwellStructureNodeType = 'TriMeshMaxwellStructureNodeType'
|
|
||||||
|
|
||||||
PMLMaxwellBoundNodeType = 'PMLMaxwellBoundNodeType'
|
|
||||||
|
|
||||||
FDTDMaxwellSimulationNodeType = 'FDTDMaxwellSimulationNodeType'
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Node Superclass
|
|
||||||
####################
|
####################
|
||||||
def output_socket_cb(name):
|
def output_socket_cb(name):
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
func._output_socket_name = name # Set a marker attribute
|
func._output_socket_name = name # Set a marker attribute
|
||||||
return func
|
return func
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Socket Type Casters
|
||||||
|
####################
|
||||||
SOCKET_CAST_MAP = {
|
SOCKET_CAST_MAP = {
|
||||||
"NodeSocketBool": bool,
|
"NodeSocketBool": bool,
|
||||||
"NodeSocketFloat": float,
|
"NodeSocketFloat": float,
|
||||||
|
@ -48,6 +38,20 @@ SOCKET_CAST_MAP = {
|
||||||
"NodeSocketVectorVelocity": np.array,
|
"NodeSocketVectorVelocity": np.array,
|
||||||
"NodeSocketVectorXYZ": np.array,
|
"NodeSocketVectorXYZ": np.array,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Node Superclass
|
||||||
|
####################
|
||||||
|
def set_preset(self, context):
|
||||||
|
for preset_name, preset_dict in self.socket_presets["_values"].items():
|
||||||
|
if self.preset == preset_name:
|
||||||
|
for input_socket_name, value in preset_dict.items():
|
||||||
|
self.inputs[
|
||||||
|
self.input_sockets[input_socket_name][1]
|
||||||
|
].default_value = value
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
class MaxwellSimTreeNode(bpy.types.Node):
|
class MaxwellSimTreeNode(bpy.types.Node):
|
||||||
def __init_subclass__(cls, **kwargs):
|
def __init_subclass__(cls, **kwargs):
|
||||||
super().__init_subclass__(**kwargs)
|
super().__init_subclass__(**kwargs)
|
||||||
|
@ -63,6 +67,15 @@ class MaxwellSimTreeNode(bpy.types.Node):
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
f"class {cls.__name__} is missing required '{attr}' attribute"
|
f"class {cls.__name__} is missing required '{attr}' attribute"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if hasattr(cls, 'socket_presets'):
|
||||||
|
cls.__annotations__["preset"] = bpy.props.EnumProperty(
|
||||||
|
name="Presets",
|
||||||
|
description="Select a preset",
|
||||||
|
items=cls.socket_presets["_description"],
|
||||||
|
default=cls.socket_presets["_default"],
|
||||||
|
update=set_preset,
|
||||||
|
)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Node Initialization
|
# - Node Initialization
|
||||||
|
@ -73,6 +86,8 @@ class MaxwellSimTreeNode(bpy.types.Node):
|
||||||
|
|
||||||
for output_socket_name in self.output_sockets:
|
for output_socket_name in self.output_sockets:
|
||||||
self.outputs.new(*self.output_sockets[output_socket_name][:2])
|
self.outputs.new(*self.output_sockets[output_socket_name][:2])
|
||||||
|
|
||||||
|
set_preset(self, context)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Node Computation
|
# - Node Computation
|
||||||
|
@ -122,4 +137,4 @@ class MaxwellSimTreeNode(bpy.types.Node):
|
||||||
def poll(cls, ntree):
|
def poll(cls, ntree):
|
||||||
"""Constrain node instantiation to within a MaxwellSimTree."""
|
"""Constrain node instantiation to within a MaxwellSimTree."""
|
||||||
|
|
||||||
return ntree.bl_idname == tree_types.MaxwellSimTreeType
|
return ntree.bl_idname == types.TreeType.MaxwellSim
|
|
@ -3,7 +3,7 @@ import bpy
|
||||||
from . import constants, types
|
from . import constants, types
|
||||||
|
|
||||||
class MaxwellSourceSocket(bpy.types.NodeSocket):
|
class MaxwellSourceSocket(bpy.types.NodeSocket):
|
||||||
bl_idname = types.MaxwellSourceSocketType
|
bl_idname = types.SocketType.MaxwellSource
|
||||||
bl_label = "Maxwell Source"
|
bl_label = "Maxwell Source"
|
||||||
|
|
||||||
def draw(self, context, layout, node, text):
|
def draw(self, context, layout, node, text):
|
||||||
|
@ -15,7 +15,7 @@ class MaxwellSourceSocket(bpy.types.NodeSocket):
|
||||||
|
|
||||||
|
|
||||||
class MaxwellMediumSocket(bpy.types.NodeSocket):
|
class MaxwellMediumSocket(bpy.types.NodeSocket):
|
||||||
bl_idname = types.MaxwellMediumSocketType
|
bl_idname = types.SocketType.MaxwellMedium
|
||||||
bl_label = "Maxwell Medium"
|
bl_label = "Maxwell Medium"
|
||||||
|
|
||||||
def draw(self, context, layout, node, text):
|
def draw(self, context, layout, node, text):
|
||||||
|
@ -26,7 +26,7 @@ class MaxwellMediumSocket(bpy.types.NodeSocket):
|
||||||
return constants.COLOR_SOCKET_MEDIUM
|
return constants.COLOR_SOCKET_MEDIUM
|
||||||
|
|
||||||
class MaxwellStructureSocket(bpy.types.NodeSocket):
|
class MaxwellStructureSocket(bpy.types.NodeSocket):
|
||||||
bl_idname = types.MaxwellStructureSocketType
|
bl_idname = types.SocketType.MaxwellStructure
|
||||||
bl_label = "Maxwell Structure"
|
bl_label = "Maxwell Structure"
|
||||||
|
|
||||||
def draw(self, context, layout, node, text):
|
def draw(self, context, layout, node, text):
|
||||||
|
@ -37,7 +37,7 @@ class MaxwellStructureSocket(bpy.types.NodeSocket):
|
||||||
return constants.COLOR_SOCKET_STRUCTURE
|
return constants.COLOR_SOCKET_STRUCTURE
|
||||||
|
|
||||||
class MaxwellBoundSocket(bpy.types.NodeSocket):
|
class MaxwellBoundSocket(bpy.types.NodeSocket):
|
||||||
bl_idname = types.MaxwellBoundSocketType
|
bl_idname = types.SocketType.MaxwellBound
|
||||||
bl_label = "Maxwell Bound"
|
bl_label = "Maxwell Bound"
|
||||||
|
|
||||||
def draw(self, context, layout, node, text):
|
def draw(self, context, layout, node, text):
|
||||||
|
@ -48,8 +48,8 @@ class MaxwellBoundSocket(bpy.types.NodeSocket):
|
||||||
return constants.COLOR_SOCKET_BOUND
|
return constants.COLOR_SOCKET_BOUND
|
||||||
|
|
||||||
class MaxwellFDTDSimSocket(bpy.types.NodeSocket):
|
class MaxwellFDTDSimSocket(bpy.types.NodeSocket):
|
||||||
bl_idname = types.MaxwellFDTDSimSocketType
|
bl_idname = types.SocketType.MaxwellFDTDSim
|
||||||
bl_label = "Maxwell Bound"
|
bl_label = "Maxwell FDTD Simulation"
|
||||||
|
|
||||||
def draw(self, context, layout, node, text):
|
def draw(self, context, layout, node, text):
|
||||||
layout.label(text=text)
|
layout.label(text=text)
|
||||||
|
@ -68,4 +68,5 @@ BL_REGISTER = [
|
||||||
MaxwellMediumSocket,
|
MaxwellMediumSocket,
|
||||||
MaxwellStructureSocket,
|
MaxwellStructureSocket,
|
||||||
MaxwellBoundSocket,
|
MaxwellBoundSocket,
|
||||||
|
MaxwellFDTDSimSocket,
|
||||||
]
|
]
|
||||||
|
|
|
@ -3,7 +3,7 @@ import bpy
|
||||||
from . import types, constants
|
from . import types, constants
|
||||||
|
|
||||||
class MaxwellSimTree(bpy.types.NodeTree):
|
class MaxwellSimTree(bpy.types.NodeTree):
|
||||||
bl_idname = types.MaxwellSimTreeType
|
bl_idname = types.TreeType.MaxwellSim
|
||||||
bl_label = "Maxwell Sim Editor"
|
bl_label = "Maxwell Sim Editor"
|
||||||
bl_icon = constants.ICON_SIM ## Icon ID
|
bl_icon = constants.ICON_SIM ## Icon ID
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue