Skip to content

Commit 3da61eb

Browse files
committed
Nodes: Refactor parent classes to support other nodetree types
1 parent f2152e7 commit 3da61eb

File tree

7 files changed

+227
-166
lines changed

7 files changed

+227
-166
lines changed

mitsuba-blender/nodes/__init__.py

Lines changed: 5 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,13 @@
1-
from bpy.utils import register_class, unregister_class
2-
from nodeitems_utils import NodeCategory, NodeItem, register_node_categories, unregister_node_categories
3-
4-
from . import (
5-
sockets
6-
)
7-
8-
class MitsubaNodeCategoryMaterial(NodeCategory):
9-
@classmethod
10-
def poll(cls, context):
11-
return context.space_data.tree_type == 'mitsuba_material_nodes'
12-
13-
mitsuba_material_node_categories = [
14-
MitsubaNodeCategoryMaterial('MITSUBA_MATERIAL_BSDF', 'BSDFs', items=[
15-
NodeItem('MitsubaNodeTwosidedBSDF', label='Twosided'),
16-
NodeItem('MitsubaNodeDiffuseBSDF', label='Diffuse'),
17-
NodeItem('MitsubaNodeDielectricBSDF', label='Dielectric'),
18-
NodeItem('MitsubaNodeThinDielectricBSDF', label='Thin Dielectric'),
19-
NodeItem('MitsubaNodeRoughDielectricBSDF', label='Rough Dielectric'),
20-
NodeItem('MitsubaNodeConductorBSDF', label='Conductor'),
21-
NodeItem('MitsubaNodeRoughConductorBSDF', label='Rough Conductor'),
22-
NodeItem('MitsubaNodePlasticBSDF', label='Plastic'),
23-
NodeItem('MitsubaNodeRoughPlasticBSDF', label='Rough Plastic'),
24-
NodeItem('MitsubaNodeBumpMapBSDF', label='Bump Map'),
25-
NodeItem('MitsubaNodeNormalMapBSDF', label='Normal Map'),
26-
NodeItem('MitsubaNodeBlendBSDF', label='Blend'),
27-
NodeItem('MitsubaNodeMaskBSDF', label='Opacity Mask'),
28-
NodeItem('MitsubaNodeNullBSDF', label='Null'),
29-
NodeItem('MitsubaNodePrincipledBSDF', label='Principled'),
30-
]),
31-
32-
MitsubaNodeCategoryMaterial('MITSUBA_MATERIAL_TEXTURE', 'Textures', items=[
33-
NodeItem('MitsubaNodeBitmapTexture', label='Bitmap'),
34-
NodeItem('MitsubaNodeCheckerboardTexture', label='Checkerboard'),
35-
]),
36-
37-
MitsubaNodeCategoryMaterial('MITSUBA_MATERIAL_OUTPUT', 'Output', items=[
38-
NodeItem('MitsubaNodeOutputMaterial', label='Output'),
39-
]),
40-
41-
MitsubaNodeCategoryMaterial('MITSUBA_MATERIAL_TRANSFORM', 'Transforms', items=[
42-
NodeItem('MitsubaNode2DTransform', label='Transform 2D'),
43-
]),
44-
]
45-
46-
classes = (
47-
sockets.MitsubaSocketBSDF,
48-
sockets.MitsubaSocketColorTexture,
49-
sockets.MitsubaSocketNormalMap,
50-
sockets.MitsubaSocketFloatTextureNoDefault,
51-
sockets.MitsubaSocketFloatTextureUnbounded,
52-
sockets.MitsubaSocketFloatTextureBounded0to1,
53-
sockets.MitsubaSocket2DTransform,
54-
)
55-
561
def register():
57-
register_node_categories('MITSUBA_MATERIAL_TREE', mitsuba_material_node_categories)
58-
59-
from . import (materials, textures, transforms)
2+
from . import (materials, textures, transforms, sockets)
3+
sockets.register()
604
transforms.register()
61-
materials.register()
625
textures.register()
63-
for cls in classes:
64-
register_class(cls)
6+
materials.register()
657

668
def unregister():
67-
unregister_node_categories('MITSUBA_MATERIAL_TREE')
68-
69-
from . import (materials, textures, transforms)
9+
from . import (materials, textures, transforms, sockets)
7010
materials.unregister()
7111
textures.unregister()
7212
transforms.unregister()
73-
for cls in classes:
74-
unregister_class(cls)
13+
sockets.unregister()

mitsuba-blender/nodes/base.py

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,80 @@
1-
import bpy
1+
from bpy.props import BoolProperty
2+
3+
from ..utils.nodetree import get_output_nodes
4+
5+
class MitsubaSocket:
6+
'''
7+
Base class for custom Mitsuba node sockets
8+
'''
9+
bl_label = ''
10+
11+
color = (1, 1, 1, 1)
12+
slider = False
13+
14+
@classmethod
15+
def is_input_connection_valid(cls, connected_socket):
16+
for valid_cls in cls.valid_inputs:
17+
if isinstance(connected_socket, valid_cls):
18+
return True
19+
return False
20+
21+
def get_linked_node(self):
22+
if self.is_linked and len(self.links) > 0:
23+
return self.links[0].from_node
24+
return None
25+
26+
def has_valid_state(self):
27+
# If this socket is linked, check that the connected
28+
# socket is of a valid type.
29+
if self.is_linked and len(self.links) > 0:
30+
link = self.links[0]
31+
if link and hasattr(self, 'valid_inputs'):
32+
if not self.is_input_connection_valid(link.from_socket):
33+
return False
34+
return True
35+
36+
def draw_prop(self, context, layout, node, text):
37+
layout.prop(self, 'default_value', text=text, slider=self.slider)
38+
39+
def draw(self, context, layout, node, text):
40+
if not self.has_valid_state():
41+
layout.label(text='Wrong Input', icon='CANCEL')
42+
return
43+
44+
has_default = hasattr(self, "default_value") and self.default_value is not None
45+
46+
if self.is_output or self.is_linked or not has_default:
47+
layout.label(text=text)
48+
else:
49+
self.draw_prop(context, layout, node, text)
50+
51+
def draw_color(self, context, node):
52+
return self.color
53+
54+
def to_default_dict(self, export_context):
55+
'''
56+
Export the default value of a socket in a form the Mitsuba can understand
57+
(either a value or a dictionary for complex types).
58+
'''
59+
# Implement in subclasses
60+
raise RuntimeError(f'{self.bl_idname} default conversion not implemented')
61+
62+
def to_dict(self, export_context):
63+
'''
64+
Convert this socket to a Mitsuba dictionary.
65+
'''
66+
if not self.has_valid_state():
67+
export_context.log('Cannot export material: Invalid socket state', 'ERROR')
68+
return None
69+
linked_node = self.get_linked_node()
70+
if linked_node is not None:
71+
# If a socket is connected, convert the connected node to a Mitsuba dictionary
72+
return linked_node.to_dict(export_context)
73+
if hasattr(self, 'default_value'):
74+
# If the socket is not connected and has a default value, convert it to a Mitsuba dictionary
75+
return self.to_default_dict(export_context)
76+
# Otherwise, this socket cannot be converted
77+
return None
278

379
class MitsubaNode:
480
'''
@@ -32,3 +108,51 @@ def update(self):
32108
def to_dict(self, export_context):
33109
# To implement in the subclasses
34110
raise RuntimeError(f'{self.bl_idname} conversion not implemented.')
111+
112+
class MitsubaNodeOutput(MitsubaNode):
113+
'''
114+
Shader node representing a Mitsuba material output
115+
'''
116+
def _update_active(output_node, context):
117+
if not output_node.is_active:
118+
output_node.is_active = True
119+
output_node.disable_other_outputs()
120+
121+
is_active: BoolProperty(name='Active', default=True, update=_update_active)
122+
123+
def init(self, context):
124+
super().init(context)
125+
self.disable_other_outputs()
126+
127+
def draw_buttons(self, context, layout):
128+
layout.prop(self, 'is_active')
129+
130+
def free(self):
131+
if not self.is_active:
132+
return
133+
134+
node_tree = self.id_data
135+
if node_tree is None:
136+
return
137+
for node in get_output_nodes(node_tree):
138+
if node != self:
139+
node['is_active'] = True
140+
break
141+
142+
def disable_other_outputs(self):
143+
node_tree = self.id_data
144+
if node_tree is None:
145+
return
146+
for node in get_output_nodes(node_tree):
147+
if node != self:
148+
node['is_active'] = False
149+
150+
class MitsubaNodeTree:
151+
'''
152+
Base class for custom Mitsuba shader node trees
153+
'''
154+
bl_label = ''
155+
156+
@classmethod
157+
def poll(cls, context):
158+
return context.scene.render.engine == 'MITSUBA'

mitsuba-blender/nodes/materials/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
import bpy
21
from bpy.utils import register_class, unregister_class
2+
from nodeitems_utils import register_node_categories, unregister_node_categories
3+
34
from . import (
45
blend, bumpmap, nodetree, output, twosided, diffuse, dielectric, conductor, plastic, normalmap, mask, null, principled
56
)
67

8+
from .nodetree import mitsuba_node_categories_material
9+
710
classes = (
811
nodetree.MitsubaNodeTreeMaterial,
912
output.MitsubaNodeOutputMaterial,
@@ -25,9 +28,13 @@
2528
)
2629

2730
def register():
31+
register_node_categories('MITSUBA_MATERIAL_TREE', mitsuba_node_categories_material)
32+
2833
for cls in classes:
2934
register_class(cls)
3035

3136
def unregister():
37+
unregister_node_categories('MITSUBA_MATERIAL_TREE')
38+
3239
for cls in classes:
3340
unregister_class(cls)

mitsuba-blender/nodes/materials/nodetree.py

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
import bpy
2+
from nodeitems_utils import NodeCategory, NodeItem
23

3-
class MitsubaNodeTreeMaterial(bpy.types.NodeTree):
4+
from ..base import MitsubaNodeTree
5+
6+
class MitsubaNodeTreeMaterial(bpy.types.NodeTree, MitsubaNodeTree):
47
'''
58
Custom Blender Node Tree for Mitsuba BSDFs
69
'''
710
bl_idname = 'mitsuba_material_nodes'
811
bl_label = 'Mitsuba Material Editor'
912
bl_icon = 'NODE_MATERIAL'
1013

11-
@classmethod
12-
def poll(cls, context):
13-
return context.scene.render.engine == 'MITSUBA'
14-
1514
@classmethod
1615
def get_from_context(cls, context):
1716
'''
@@ -25,3 +24,41 @@ def get_from_context(cls, context):
2524
if node_tree:
2625
return node_tree, mat, mat
2726
return None, None, None
27+
28+
class MitsubaNodeCategoryMaterial(NodeCategory):
29+
@classmethod
30+
def poll(cls, context):
31+
return context.space_data.tree_type == MitsubaNodeTreeMaterial.bl_idname
32+
33+
mitsuba_node_categories_material = [
34+
MitsubaNodeCategoryMaterial('MITSUBA_MATERIAL_BSDF', 'BSDFs', items=[
35+
NodeItem('MitsubaNodeTwosidedBSDF', label='Twosided'),
36+
NodeItem('MitsubaNodeDiffuseBSDF', label='Diffuse'),
37+
NodeItem('MitsubaNodeDielectricBSDF', label='Dielectric'),
38+
NodeItem('MitsubaNodeThinDielectricBSDF', label='Thin Dielectric'),
39+
NodeItem('MitsubaNodeRoughDielectricBSDF', label='Rough Dielectric'),
40+
NodeItem('MitsubaNodeConductorBSDF', label='Conductor'),
41+
NodeItem('MitsubaNodeRoughConductorBSDF', label='Rough Conductor'),
42+
NodeItem('MitsubaNodePlasticBSDF', label='Plastic'),
43+
NodeItem('MitsubaNodeRoughPlasticBSDF', label='Rough Plastic'),
44+
NodeItem('MitsubaNodeBumpMapBSDF', label='Bump Map'),
45+
NodeItem('MitsubaNodeNormalMapBSDF', label='Normal Map'),
46+
NodeItem('MitsubaNodeBlendBSDF', label='Blend'),
47+
NodeItem('MitsubaNodeMaskBSDF', label='Opacity Mask'),
48+
NodeItem('MitsubaNodeNullBSDF', label='Null'),
49+
NodeItem('MitsubaNodePrincipledBSDF', label='Principled'),
50+
]),
51+
52+
MitsubaNodeCategoryMaterial('MITSUBA_MATERIAL_TEXTURE', 'Textures', items=[
53+
NodeItem('MitsubaNodeBitmapTexture', label='Bitmap'),
54+
NodeItem('MitsubaNodeCheckerboardTexture', label='Checkerboard'),
55+
]),
56+
57+
MitsubaNodeCategoryMaterial('MITSUBA_MATERIAL_OUTPUT', 'Output', items=[
58+
NodeItem('MitsubaNodeOutputMaterial', label='Output'),
59+
]),
60+
61+
MitsubaNodeCategoryMaterial('MITSUBA_MATERIAL_TRANSFORM', 'Transforms', items=[
62+
NodeItem('MitsubaNode2DTransform', label='Transform 2D'),
63+
]),
64+
]

mitsuba-blender/nodes/materials/output.py

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,19 @@
11
import bpy
2-
from bpy.props import BoolProperty
3-
from ..base import MitsubaNode
2+
from ..base import MitsubaNodeOutput
43

5-
class MitsubaNodeOutputMaterial(bpy.types.Node, MitsubaNode):
4+
class MitsubaNodeOutputMaterial(bpy.types.Node, MitsubaNodeOutput):
65
'''
76
Shader node representing a Mitsuba material output
87
'''
98
bl_idname = 'MitsubaNodeOutputMaterial'
109
bl_label = 'Material Output'
1110

12-
def _update_active(output_node, context):
13-
if not output_node.is_active:
14-
output_node.is_active = True
15-
output_node.disable_other_outputs()
16-
17-
is_active: BoolProperty(name='Active', default=True, update=_update_active)
18-
1911
def init(self, context):
20-
super().init(context)
2112
self.add_input('MitsubaSocketBSDF', 'BSDF')
22-
self.disable_other_outputs()
13+
super().init(context)
2314

2415
def draw_buttons(self, context, layout):
25-
layout.prop(self, 'is_active')
26-
27-
def disable_other_outputs(self):
28-
node_tree = self.id_data
29-
if node_tree is None:
30-
return
31-
for node in node_tree.nodes:
32-
if getattr(node, 'bl_idname', None) == 'MitsubaNodeOutputMaterial' and node != self:
33-
node['is_active'] = False
16+
super().draw_buttons(context, layout)
3417

3518
def to_dict(self, export_context):
3619
if not self.is_active:

0 commit comments

Comments
 (0)