Browse Source

Merge pull request #23 from Hoikas/softvolumes

Soft Volumes and Vis Regions
pull/24/head
Adam Johnson 9 years ago
parent
commit
9f9926488c
  1. 2
      korman/exporter/mesh.py
  2. 18
      korman/exporter/rtlight.py
  3. 2
      korman/nodes/__init__.py
  4. 26
      korman/nodes/node_core.py
  5. 195
      korman/nodes/node_softvolume.py
  6. 91
      korman/properties/modifiers/region.py
  7. 81
      korman/properties/modifiers/render.py
  8. 18
      korman/ui/modifiers/region.py
  9. 35
      korman/ui/modifiers/render.py

2
korman/exporter/mesh.py

@ -264,7 +264,7 @@ class MeshConverter:
# Create the DrawInterface # Create the DrawInterface
if drawables: if drawables:
diface = self._mgr.add_object(pl=plDrawInterface, bl=bo) diface = self._mgr.find_create_object(plDrawInterface, bl=bo)
for dspan_key, idx in drawables: for dspan_key, idx in drawables:
diface.addDrawable(dspan_key, idx) diface.addDrawable(dspan_key, idx)

18
korman/exporter/rtlight.py

@ -89,18 +89,11 @@ class LightConverter:
def _convert_sun_lamp(self, bl, pl): def _convert_sun_lamp(self, bl, pl):
print(" [DirectionalLightInfo '{}']".format(bl.name)) print(" [DirectionalLightInfo '{}']".format(bl.name))
def _create_light_key(self, bo, bl_light, so):
try:
xlate = _BL2PL[bl_light.type]
return self.mgr.find_create_key(xlate, bl=bo, so=so)
except LookupError:
raise BlenderOptionNotSupported("Object ('{}') lamp type '{}'".format(bo.name, bl_light.type))
def export_rtlight(self, so, bo): def export_rtlight(self, so, bo):
bl_light = bo.data bl_light = bo.data
# The specifics be here... # The specifics be here...
pl_light = self._create_light_key(bo, bl_light, so).object pl_light = self.get_light_key(bo, bl_light, so).object
self._converter_funcs[bl_light.type](bl_light, pl_light) self._converter_funcs[bl_light.type](bl_light, pl_light)
# Light color nonsense # Light color nonsense
@ -188,7 +181,7 @@ class LightConverter:
continue continue
# This is probably where PermaLight vs PermaProj should be sorted out... # This is probably where PermaLight vs PermaProj should be sorted out...
pl_light = self._create_light_key(obj, lamp, None) pl_light = self.get_light_key(obj, lamp, None)
if self._is_projection_lamp(lamp): if self._is_projection_lamp(lamp):
print(" [{}] PermaProj '{}'".format(lamp.type, obj.name)) print(" [{}] PermaProj '{}'".format(lamp.type, obj.name))
permaProj.append(pl_light) permaProj.append(pl_light)
@ -200,6 +193,13 @@ class LightConverter:
return (permaLights, permaProjs) return (permaLights, permaProjs)
def get_light_key(self, bo, bl_light, so):
try:
xlate = _BL2PL[bl_light.type]
return self.mgr.find_create_key(xlate, bl=bo, so=so)
except LookupError:
raise BlenderOptionNotSupported("Object ('{}') lamp type '{}'".format(bo.name, bl_light.type))
def _is_projection_lamp(self, bl_light): def _is_projection_lamp(self, bl_light):
for tex in bl_light.texture_slots: for tex in bl_light.texture_slots:
if tex is None or tex.texture is None: if tex is None or tex.texture is None:

2
korman/nodes/__init__.py

@ -26,6 +26,7 @@ from .node_logic import *
from .node_messages import * from .node_messages import *
from .node_python import * from .node_python import *
from .node_responder import * from .node_responder import *
from .node_softvolume import *
class PlasmaNodeCategory(NodeCategory): class PlasmaNodeCategory(NodeCategory):
"""Plasma Node Category""" """Plasma Node Category"""
@ -45,6 +46,7 @@ _kategory_names = {
"LOGIC": "Logic", "LOGIC": "Logic",
"MSG": "Message", "MSG": "Message",
"PYTHON": "Python", "PYTHON": "Python",
"SV": "Soft Volume",
} }
# Now, generate the categories as best we can... # Now, generate the categories as best we can...

26
korman/nodes/node_core.py

@ -52,6 +52,14 @@ class PlasmaNodeBase:
return None return None
raise KeyError(key) raise KeyError(key)
def find_inputs(self, key, idname=None):
for i in self.inputs:
if i.alias == key:
if i.links:
node = i.links[0].from_node
if idname is None or idname == node.bl_idname:
yield node
def find_input_socket(self, key): def find_input_socket(self, key):
for i in self.inputs: for i in self.inputs:
if i.alias == key: if i.alias == key:
@ -223,6 +231,18 @@ class PlasmaNodeBase:
socket.link_limit = link_limit socket.link_limit = link_limit
class PlasmaTreeOutputNodeBase(PlasmaNodeBase):
"""Represents the final output of a node tree"""
def init(self, context):
nodes = self.id_data.nodes
# There can only be one of these nodes per tree, so let's make sure I'm the only one.
for i in nodes:
if isinstance(i, self.__class__) and i != self:
nodes.remove(self)
return
class PlasmaNodeSocketBase: class PlasmaNodeSocketBase:
@property @property
def alias(self): def alias(self):
@ -265,6 +285,12 @@ class PlasmaNodeTree(bpy.types.NodeTree):
for node in self.nodes: for node in self.nodes:
node.export(exporter, bo, so) node.export(exporter, bo, so)
def find_output(self, idname):
for node in self.nodes:
if node.bl_idname == idname:
return node
return None
def harvest_actors(self): def harvest_actors(self):
actors = set() actors = set()
for node in self.nodes: for node in self.nodes:

195
korman/nodes/node_softvolume.py

@ -0,0 +1,195 @@
# This file is part of Korman.
#
# Korman is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Korman is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Korman. If not, see <http://www.gnu.org/licenses/>.
import bpy
from bpy.props import *
from collections import OrderedDict
from PyHSPlasma import *
from .node_core import PlasmaNodeBase, PlasmaNodeSocketBase, PlasmaTreeOutputNodeBase
class PlasmaSoftVolumeOutputNode(PlasmaTreeOutputNodeBase, bpy.types.Node):
bl_category = "SV"
bl_idname = "PlasmaSoftVolumeOutputNode"
bl_label = "Soft Volume Output"
input_sockets = OrderedDict([
("input", {
"text": "Final Volume",
"type": "PlasmaSoftVolumeNodeSocket",
}),
])
def get_key(self, exporter, so):
svNode = self.find_input("input")
if svNode is not None:
return svNode.get_key(exporter, so)
return None
class PlasmaSoftVolumeNodeSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
bl_color = (0.133, 0.094, 0.345, 1.0)
class PlasmaSoftVolumePropertiesNodeSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
bl_color = (0.067, 0.40, 0.067, 1.0)
class PlasmaSoftVolumePropertiesNode(PlasmaNodeBase, bpy.types.Node):
bl_category = "SV"
bl_idname = "PlasmaSoftVolumePropertiesNode"
bl_label = "Soft Volume Properties"
output_sockets = OrderedDict([
("target", {
"text": "Volume",
"type": "PlasmaSoftVolumePropertiesNodeSocket"
}),
])
inside_strength = IntProperty(name="Inside", description="Strength inside the region",
subtype="PERCENTAGE", default=100, min=0, max=100)
outside_strength = IntProperty(name="Outside", description="Strength outside the region",
subtype="PERCENTAGE", default=0, min=0, max=100)
def draw_buttons(self, context, layout):
layout.prop(self, "inside_strength")
layout.prop(self, "outside_strength")
def propagate(self, softvolume):
softvolume.insideStrength = self.inside_strength / 100
softvolume.outsideStrength = self.outside_strength / 100
class PlasmaSoftVolumeReferenceNode(PlasmaNodeBase, bpy.types.Node):
bl_category = "SV"
bl_idname = "PlasmaSoftVolumeReferenceNode"
bl_label = "Soft Region"
bl_width_default = 150
output_sockets = OrderedDict([
("output", {
"text": "Volume",
"type": "PlasmaSoftVolumeNodeSocket"
}),
])
soft_object = StringProperty(name="Soft Volume",
description="Object whose Soft Volume modifier we should use")
def draw_buttons(self, context, layout):
layout.prop_search(self, "soft_object", bpy.data, "objects", icon="OBJECT_DATA", text="")
def get_key(self, exporter, so):
softvol = bpy.data.objects.get(self.soft_object, None)
if softvol is None:
self.raise_error("Volume Object '{}' not found".format(self.soft_object))
# Don't use SO here because that's the tree owner's SO. This soft region will find or create
# its own SceneObject. Yay!
return softvol.plasma_modifiers.softvolume.get_key(exporter)
class PlasmaSoftVolumeInvertNode(PlasmaNodeBase, bpy.types.Node):
bl_category = "SV"
bl_idname = "PlasmaSoftVolumeInvertNode"
bl_label = "Soft Volume Invert"
# The only difference between this and PlasmaSoftVolumeLinkNode is this can only have ONE input
input_sockets = OrderedDict([
("properties", {
"text": "Properties",
"type": "PlasmaSoftVolumePropertiesNodeSocket",
}),
("input", {
"text": "Input Volume",
"type": "PlasmaSoftVolumeNodeSocket",
}),
])
output_sockets = OrderedDict([
("output", {
"text": "Output Volume",
"type": "PlasmaSoftVolumeNodeSocket"
}),
])
def get_key(self, exporter, so):
return exporter.mgr.find_create_key(plSoftVolumeInvert, name=self.key_name, so=so)
def export(self, exporter, bo, so):
parent = self.find_input("input")
if parent is None:
self.raise_error("SoftVolume Invert requires an input volume!")
sv = self.get_key(exporter, so).object
sv.addSubVolume(parent.get_key(exporter, so))
props = self.find_input("properties")
if props is not None:
props.propagate(sv)
else:
sv.insideStrength = 1.0
sv.outsideStrength = 0.0
class PlasmaSoftVolumeLinkNode(PlasmaNodeBase):
input_sockets = OrderedDict([
("properties", {
"text": "Properties",
"type": "PlasmaSoftVolumePropertiesNodeSocket",
}),
("input", {
"text": "Input Volume",
"type": "PlasmaSoftVolumeNodeSocket",
"spawn_empty": True,
}),
])
output_sockets = OrderedDict([
("output", {
"text": "Output Volume",
"type": "PlasmaSoftVolumeNodeSocket"
}),
])
def export(self, exporter, bo, so):
sv = self.get_key(exporter, so).object
for node in self.find_inputs("input"):
sv.addSubVolume(node.get_key(exporter, so))
props = self.find_input("properties")
if props is not None:
props.propagate(sv)
else:
sv.insideStrength = 1.0
sv.outsideStrength = 0.0
class PlasmaSoftVolumeIntersectNode(PlasmaSoftVolumeLinkNode, bpy.types.Node):
bl_category = "SV"
bl_idname = "PlasmaSoftVolumeIntersectNode"
bl_label = "Soft Volume Intersect"
def get_key(self, exporter, so):
## FIXME: SoftVolumeIntersect should not be listed as an interface
return exporter.mgr.find_create_key(plSoftVolumeIntersect, name=self.key_name, so=so)
class PlasmaSoftVolumeUnionNode(PlasmaSoftVolumeLinkNode, bpy.types.Node):
bl_category = "SV"
bl_idname = "PlasmaSoftVolumeUnionNode"
bl_label = "Soft Volume Union"
def get_key(self, exporter, so):
## FIXME: SoftVolumeUnion should not be listed as an interface
return exporter.mgr.find_create_key(plSoftVolumeUnion, name=self.key_name, so=so)

91
korman/properties/modifiers/region.py

@ -17,6 +17,9 @@ import bpy
from bpy.props import * from bpy.props import *
from PyHSPlasma import * from PyHSPlasma import *
from ...exporter import ExportError
from ...helpers import TemporaryObject
from .base import PlasmaModifierProperties, PlasmaModifierLogicWiz from .base import PlasmaModifierProperties, PlasmaModifierLogicWiz
from .physics import bounds_types from .physics import bounds_types
@ -140,3 +143,91 @@ class PlasmaPanicLinkRegion(PlasmaModifierProperties):
@property @property
def requires_actor(self): def requires_actor(self):
return True return True
class PlasmaSoftVolume(PlasmaModifierProperties):
pl_id = "softvolume"
bl_category = "Region"
bl_label = "Soft Volume"
bl_description = "Soft-Boundary Region"
# Advanced
use_nodes = BoolProperty(name="Use Nodes",
description="Make this a node-based Soft Volume",
default=False)
node_tree_name = StringProperty(name="Node Tree",
description="Node Tree detailing soft volume logic")
# Basic
invert = BoolProperty(name="Invert",
description="Invert the soft region")
inside_strength = IntProperty(name="Inside", description="Strength inside the region",
subtype="PERCENTAGE", default=100, min=0, max=100)
outside_strength = IntProperty(name="Outside", description="Strength outside the region",
subtype="PERCENTAGE", default=0, min=0, max=100)
soft_distance = FloatProperty(name="Distance", description="Soft Distance",
default=0.0, min=0.0, max=500.0)
def _apply_settings(self, sv):
sv.insideStrength = self.inside_strength / 100.0
sv.outsideStrength = self.outside_strength / 100.0
def get_key(self, exporter, so=None):
"""Fetches the key appropriate for this Soft Volume"""
if so is None:
so = exporter.mgr.find_create_object(plSceneObject, bl=self.id_data)
if self.use_nodes:
output = self.node_tree.find_output("PlasmaSoftVolumeOutputNode")
if output is None:
raise ExportError("SoftVolume '{}' Node Tree '{}' has no output node!".format(self.key_name, self.node_tree))
return output.get_key(exporter, so)
else:
pClass = plSoftVolumeInvert if self.invert else plSoftVolumeSimple
return exporter.mgr.find_create_key(pClass, bl=self.id_data, so=so)
def export(self, exporter, bo, so):
if self.use_nodes:
self._export_sv_nodes(exporter, bo, so)
else:
self._export_convex_region(exporter, bo, so)
def _export_convex_region(self, exporter, bo, so):
if bo.type != "MESH":
raise ExportError("SoftVolume '{}': Simple SoftVolumes can only be meshes!".format(bo.name))
# Grab the SoftVolume KO
sv = self.get_key(exporter, so).object
self._apply_settings(sv)
# If "invert" was checked, we got a SoftVolumeInvert, but we need to make a Simple for the
# region data to be exported into..
if isinstance(sv, plSoftVolumeInvert):
svSimple = exporter.mgr.find_create_object(plSoftVolumeSimple, bl=bo, so=so)
self._apply_settings(svSimple)
sv.addSubVolume(svSimple.key)
sv = svSimple
sv.softDist = self.soft_distance
# Initialize the plVolumeIsect. Currently, we only support convex isects. If you want parallel
# isects from empties, be my guest...
with TemporaryObject(bo.to_mesh(bpy.context.scene, True, "RENDER", calc_tessface=False), bpy.data.meshes.remove) as mesh:
mesh.transform(bo.matrix_world)
isect = plConvexIsect()
for i in mesh.vertices:
isect.addPlane(hsVector3(*i.normal), hsVector3(*i.co))
sv.volume = isect
def _export_sv_nodes(self, exporter, bo, so):
if self.node_tree_name not in exporter.node_trees_exported:
exporter.node_trees_exported.add(self.node_tree_name)
self.node_tree.export(exporter, bo, so)
@property
def node_tree(self):
tree = bpy.data.node_groups.get(self.node_tree_name, None)
if tree is None:
raise ExportError("SoftVolume '{}': Node Tree '{}' does not exist!".format(self.key_name, self.node_tree_name))
return tree

81
korman/properties/modifiers/render.py

@ -172,6 +172,87 @@ class PlasmaViewFaceMod(PlasmaModifierProperties):
def requires_actor(self): def requires_actor(self):
return True return True
class PlasmaVisControl(PlasmaModifierProperties):
pl_id = "visregion"
bl_category = "Render"
bl_label = "Visibility Control"
bl_description = "Controls object visibility using VisRegions"
mode = EnumProperty(name="Mode",
description="Purpose of the VisRegion",
items=[("normal", "Normal", "Objects are only visible when the camera is inside this region"),
("exclude", "Exclude", "Objects are only visible when the camera is outside this region"),
("fx", "Special FX", "This is a list of objects used for special effects only")])
softvolume = StringProperty(name="Region",
description="Object defining the SoftVolume for this VisRegion")
replace_normal = BoolProperty(name="Hide Drawables",
description="Hides drawables attached to this region",
default=True)
def export(self, exporter, bo, so):
rgn = exporter.mgr.find_create_object(plVisRegion, bl=bo, so=so)
rgn.setProperty(plVisRegion.kReplaceNormal, self.replace_normal)
if self.mode == "fx":
rgn.setProperty(plVisRegion.kDisable, True)
else:
this_sv = bo.plasma_modifiers.softvolume
if this_sv.enabled:
print(" [VisRegion] I'm a SoftVolume myself :)")
rgn.region = this_sv.get_key(exporter, so)
else:
print(" [VisRegion] SoftVolume '{}'".format(self.softvolume))
sv_bo = bpy.data.objects.get(self.softvolume, None)
if sv_bo is None:
raise ExportError("'{}': Invalid object '{}' for VisControl soft volume".format(bo.name, self.softvolume))
sv = sv_bo.plasma_modifiers.softvolume
if not sv.enabled:
raise ExportError("'{}': '{}' is not a SoftVolume".format(bo.name, self.softvolume))
rgn.region = sv.get_key(exporter)
rgn.setProperty(plVisRegion.kIsNot, self.mode == "exclude")
class VisRegion(bpy.types.PropertyGroup):
enabled = BoolProperty(default=True)
region_name = StringProperty(name="Control",
description="Object defining a Plasma Visibility Control")
class PlasmaVisibilitySet(PlasmaModifierProperties):
pl_id = "visibility"
bl_category = "Render"
bl_label = "Visibility Set"
bl_description = "Defines areas where this object is visible"
regions = CollectionProperty(name="Visibility Regions",
type=VisRegion)
active_region_index = IntProperty(options={"HIDDEN"})
def export(self, exporter, bo, so):
if not self.regions:
# TODO: Log message about how this modifier is totally worthless
return
# Currently, this modifier is valid for meshes and lamps
if bo.type == "MESH":
diface = exporter.mgr.find_create_object(plDrawInterface, bl=bo, so=so)
addRegion = diface.addRegion
elif bo.type == "LAMP":
light = exporter.light.get_light_key(bo, bo.data, so)
addRegion = light.object.addVisRegion
for region in self.regions:
if not region.enabled:
continue
rgn_bo = bpy.data.objects.get(region.region_name, None)
if rgn_bo is None:
raise ExportError("{}: Invalid VisControl '{}' in VisSet modifier".format(bo.name, region.region_name))
addRegion(exporter.mgr.find_create_key(plVisRegion, bl=rgn_bo))
class PlasmaFollowMod(PlasmaModifierProperties): class PlasmaFollowMod(PlasmaModifierProperties):
pl_id = "followmod" pl_id = "followmod"

18
korman/ui/modifiers/region.py

@ -13,6 +13,8 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Korman. If not, see <http://www.gnu.org/licenses/>. # along with Korman. If not, see <http://www.gnu.org/licenses/>.
import bpy
def footstep(modifier, layout, context): def footstep(modifier, layout, context):
layout.prop(modifier, "bounds") layout.prop(modifier, "bounds")
layout.prop(modifier, "surface") layout.prop(modifier, "surface")
@ -21,3 +23,19 @@ def paniclink(modifier, layout, context):
phys_mod = context.object.plasma_modifiers.collision phys_mod = context.object.plasma_modifiers.collision
layout.prop(phys_mod, "bounds") layout.prop(phys_mod, "bounds")
layout.prop(modifier, "play_anim") layout.prop(modifier, "play_anim")
def softvolume(modifier, layout, context):
row = layout.row()
row.prop(modifier, "use_nodes", text="", icon="NODETREE")
if modifier.use_nodes:
row.prop_search(modifier, "node_tree_name", bpy.data, "node_groups")
else:
row.label("Simple Soft Volume")
split = layout.split()
col = split.column()
col.prop(modifier, "inside_strength")
col.prop(modifier, "outside_strength")
col = split.column()
col.prop(modifier, "invert")
col.prop(modifier, "soft_distance")

35
korman/ui/modifiers/render.py

@ -56,6 +56,41 @@ def viewfacemod(modifier, layout, context):
col.enabled = modifier.offset col.enabled = modifier.offset
col.prop(modifier, "offset_coord") col.prop(modifier, "offset_coord")
class VisRegionListUI(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
myIcon = "ERROR" if bpy.data.objects.get(item.region_name, None) is None else "OBJECT_DATA"
label = item.region_name if item.region_name else "[No Object Specified]"
layout.label(label, icon=myIcon)
layout.prop(item, "enabled", text="")
def visibility(modifier, layout, context):
row = layout.row()
row.template_list("VisRegionListUI", "regions", modifier, "regions", modifier, "active_region_index",
rows=2, maxrows=3)
col = row.column(align=True)
op = col.operator("object.plasma_modifier_collection_add", icon="ZOOMIN", text="")
op.modifier = modifier.pl_id
op.collection = "regions"
op = col.operator("object.plasma_modifier_collection_remove", icon="ZOOMOUT", text="")
op.modifier = modifier.pl_id
op.collection = "regions"
op.index = modifier.active_region_index
if modifier.regions:
layout.prop_search(modifier.regions[modifier.active_region_index], "region_name", bpy.data, "objects")
def visregion(modifier, layout, context):
layout.prop(modifier, "mode")
# Only allow SoftVolume spec if this is not an FX and this object is not an SV itself
sv = modifier.id_data.plasma_modifiers.softvolume
if modifier.mode != "fx" and not sv.enabled:
layout.prop_search(modifier, "softvolume", bpy.data, "objects")
# Other settings
layout.prop(modifier, "replace_normal")
def followmod(modifier, layout, context): def followmod(modifier, layout, context):
layout.row().prop(modifier, "follow_mode", expand=True) layout.row().prop(modifier, "follow_mode", expand=True)
layout.prop(modifier, "leader_type") layout.prop(modifier, "leader_type")

Loading…
Cancel
Save