Browse Source

Improve stenciling support. NOTE: BREAKING CHANGE

OK, this commit does a lot, and it was hard to separate the changes, so
mega-commit.
- We now export stencils in such a way that Plasma layers can be affected
  by multiple stencils, theoretically matching the Blender behavior
- We don't try to animate stencils or even offer animation options. This
  is nonsense.
- In our zealousness to skip over illegal texture slots, there was a bug
  introduced in which we queried texture fcurve paths illegally,
  potentially causing animations to be exported incorrectly. This has been
  fixed.
- There was a potential issue with the animation command node's material
  option in which the message could be sent to the wrong page. This has
  been corrected by making the Object field mandatory for all animation
  types.
pull/43/head
Adam Johnson 9 years ago
parent
commit
6ed81d2794
  1. 95
      korman/exporter/material.py
  2. 2
      korman/exporter/rtlight.py
  3. 32
      korman/nodes/node_messages.py
  4. 5
      korman/ui/ui_texture.py

95
korman/exporter/material.py

@ -24,6 +24,8 @@ from .. import helpers
from ..korlib import * from ..korlib import *
from . import utils from . import utils
_MAX_STENCILS = 6
class _Texture: class _Texture:
_DETAIL_BLEND = { _DETAIL_BLEND = {
TEX_DETAIL_ALPHA: "AL", TEX_DETAIL_ALPHA: "AL",
@ -125,14 +127,40 @@ class MaterialConverter:
print(" Exporting Material '{}'".format(bm.name)) print(" Exporting Material '{}'".format(bm.name))
hsgmat = self._mgr.add_object(hsGMaterial, name=bm.name, bl=bo) hsgmat = self._mgr.add_object(hsGMaterial, name=bm.name, bl=bo)
slots = [slot for slot in bm.texture_slots if slot is not None and slot.use and slots = [(idx, slot) for idx, slot in enumerate(bm.texture_slots) if slot is not None and slot.use \
slot.texture is not None and slot.texture.type in self._tex_exporters] and slot.texture is not None and slot.texture.type in self._tex_exporters]
# Okay, I know this isn't Pythonic... But we're doing it this way because we might actually # There is a major difference in how Blender and Plasma handle stencils.
# export many slots in one go. Think stencils. # In Blender, the stencil is on top and applies to every layer below is. In Plasma, the stencil
i = 0 # is below the SINGLE layer it affects. The main texture is marked BindNext and RestartPassHere.
while i < len(slots): # The pipeline indicates that we can render 8 layers simultaneously, so we will collect all
i += self.export_texture_slot(bo, bm, hsgmat, slots, i) # stencils and apply this arrangement. We're going to limit to 6 stencils however. 1 layer for
# main texture and 1 piggyback.
num_stencils = sum((1 for i in slots if i[1].use_stencil))
if num_stencils > _MAX_STENCILS:
raise ExportError("Material '{}' uses too many stencils. The maximum is {}".format(bm.name, _MAX_STENCILS))
stencils = []
# Loop over layers
for idx, slot in slots:
if slot.use_stencil:
stencils.append((idx, slot))
else:
tex_layer = self.export_texture_slot(bo, bm, hsgmat, slot, idx)
hsgmat.addLayer(tex_layer.key)
if stencils:
tex_state = tex_layer.state
if not tex_state.blendFlags & hsGMatState.kBlendMask:
tex_state.blendFlags |= hsGMatState.kBlendAlpha
tex_state.miscFlags |= hsGMatState.kMiscRestartPassHere | hsGMatState.kMiscBindNext
curr_stencils = len(stencils)
for i in range(curr_stencils):
stencil_idx, stencil = stencils[i]
stencil_name = "STENCILGEN_{}@{}_{}".format(stencil.name, bm.name, slot.name)
stencil_layer = self.export_texture_slot(bo, bm, hsgmat, stencil, stencil_idx, name=stencil_name)
if i+1 < curr_stencils:
stencil_layer.state.miscFlags |= hsGMatState.kMiscBindNext
hsgmat.addLayer(stencil_layer.key)
# Plasma makes several assumptions that every hsGMaterial has at least one layer. If this # Plasma makes several assumptions that every hsGMaterial has at least one layer. If this
# material had no Textures, we will need to initialize a default layer # material had no Textures, we will need to initialize a default layer
@ -166,11 +194,9 @@ class MaterialConverter:
# Wasn't that easy? # Wasn't that easy?
return hsgmat.key return hsgmat.key
def export_texture_slot(self, bo, bm, hsgmat, slots, idx, blend_flags=True): def export_texture_slot(self, bo, bm, hsgmat, slot, idx, name=None, blend_flags=True):
slot = slots[idx] if name is None:
num_exported = 1 name = "{}_{}".format(bm.name if bm is not None else bo.name, slot.name)
name = "{}_{}".format(bm.name if bm is not None else bo.name, slot.name)
print(" Exporting Plasma Layer '{}'".format(name)) print(" Exporting Plasma Layer '{}'".format(name))
layer = self._mgr.add_object(plLayer, name=name, bl=bo) layer = self._mgr.add_object(plLayer, name=name, bl=bo)
if bm is not None: if bm is not None:
@ -204,22 +230,6 @@ class MaterialConverter:
state.clampFlags |= hsGMatState.kClampTexture state.clampFlags |= hsGMatState.kClampTexture
state.ZFlags |= hsGMatState.kZNoZWrite state.ZFlags |= hsGMatState.kZNoZWrite
layer.ambient = hsColorRGBA(1.0, 1.0, 1.0, 1.0) layer.ambient = hsColorRGBA(1.0, 1.0, 1.0, 1.0)
# Plasma actually wants the next layer first, so let's export him
nextIdx = idx + 1
if len(slots) == nextIdx:
raise ExportError("Texture Slot '{}' wants to be a stencil, but there are no more TextureSlots.".format(slot.name))
print(" --- BEGIN STENCIL ---")
self.export_texture_slot(bo, bm, hsgmat, slots, nextIdx)
print(" --- END STENCIL ---")
num_exported += 1
# Now that we've exported the bugger, flag him as binding with this texture
prev_layer = hsgmat.layers[-1].object
prev_state = prev_layer.state
prev_state.miscFlags |= hsGMatState.kMiscBindNext | hsGMatState.kMiscRestartPassHere
if not prev_state.blendFlags & hsGMatState.kBlendMask:
prev_state.blendFlags |= hsGMatState.kBlendAlpha
elif blend_flags: elif blend_flags:
# Standard layer flags ahoy # Standard layer flags ahoy
if slot.blend_type == "ADD": if slot.blend_type == "ADD":
@ -241,13 +251,10 @@ class MaterialConverter:
self._tex_exporters[texture.type](bo, layer, slot) self._tex_exporters[texture.type](bo, layer, slot)
# Export any layer animations # Export any layer animations
layer = self._export_layer_animations(bo, bm, slot, idx, layer) # NOTE: animated stencils are nonsense.
if slot.use_stencil:
if hsgmat is None: layer = self._export_layer_animations(bo, bm, slot, idx, layer)
return layer return layer
else:
hsgmat.addLayer(layer.key)
return num_exported
def _export_layer_animations(self, bo, bm, tex_slot, idx, base_layer): def _export_layer_animations(self, bo, bm, tex_slot, idx, base_layer):
"""Exports animations on this texture and chains the Plasma layers as needed""" """Exports animations on this texture and chains the Plasma layers as needed"""
@ -263,17 +270,13 @@ class MaterialConverter:
if data_path is None: if data_path is None:
collection.extend(action.fcurves) collection.extend(action.fcurves)
else: else:
collection.extend([i for i in action.fcurves if i.data_path.startswith(data_path)]) collection.extend((i for i in action.fcurves if i.data_path.startswith(data_path)))
return action return action
return None return None
# First, we must gather relevant FCurves from both the material and the texture itself
# Because, you know, that totally makes sense...
fcurves = [] fcurves = []
mat_action = harvest_fcurves(bm, fcurves, "texture_slots[{}]".format(idx)) mat_action = harvest_fcurves(bm, fcurves, "texture_slots[{}]".format(idx))
tex_action = harvest_fcurves(tex_slot.texture, fcurves) tex_action = harvest_fcurves(bm.texture_slots[idx].texture, fcurves)
# No fcurves, no animation
if not fcurves: if not fcurves:
return base_layer return base_layer
@ -589,6 +592,14 @@ class MaterialConverter:
def get_materials(self, bo): def get_materials(self, bo):
return self._obj2mat[bo] return self._obj2mat[bo]
def get_texture_animation_key(self, bo, bm, tex_name):
"""Finds or creates the appropriate key for sending messages to an animated Texture"""
tex_slot = bm.texture_slots.get(tex_name, None)
if tex_slot is None:
raise ExportError("Material '{}' does not contain Texture '{}'".format(bm.name, tex_name))
name = "{}_{}_LayerAnim".format(bm.name, tex_name)
return self._mgr.find_create_key(plLayerAnimation, bl=bo, name=name)
@property @property
def _mgr(self): def _mgr(self):
return self._exporter().mgr return self._exporter().mgr

2
korman/exporter/rtlight.py

@ -203,7 +203,7 @@ class LightConverter:
# one layer. We could exploit the fUnderLay and fOverLay system to export everything, but meh. # one layer. We could exploit the fUnderLay and fOverLay system to export everything, but meh.
if len(tex_slots) > 1: if len(tex_slots) > 1:
self._exporter().warn("Only one texture slot can be exported per Lamp. Picking the first one: '{}'".format(slot.name), indent=3) self._exporter().warn("Only one texture slot can be exported per Lamp. Picking the first one: '{}'".format(slot.name), indent=3)
layer = mat.export_texture_slot(bo, None, None, tex_slots, 0, blend_flags=False) layer = mat.export_texture_slot(bo, None, None, slot, 0, blend_flags=False)
state = layer.state state = layer.state
# Colors science'd from PRPs # Colors science'd from PRPs

32
korman/nodes/node_messages.py

@ -121,13 +121,19 @@ class PlasmaAnimCmdMsgNode(PlasmaMessageNode, bpy.types.Node):
def draw_buttons(self, context, layout): def draw_buttons(self, context, layout):
layout.prop(self, "anim_type") layout.prop(self, "anim_type")
if self.anim_type == "OBJECT": layout.prop_search(self, "object_name", bpy.data, "objects")
layout.prop_search(self, "object_name", bpy.data, "objects")
else: if self.anim_type != "OBJECT":
layout.prop_search(self, "material_name", bpy.data, "materials") bo = bpy.data.objects.get(self.object_name)
material = bpy.data.materials.get(self.material_name, None) if bo is None or not hasattr(bo.data, "materials"):
if material is not None: layout.label("Invalid Object", icon="ERROR")
layout.prop_search(self, "texture_name", material, "texture_slots") else:
layout.prop_search(self, "material_name", bo.data, "materials")
material = bpy.data.materials.get(self.material_name, None)
if material is None:
layout.label("Invalid Material", icon="ERROR")
else:
layout.prop_search(self, "texture_name", material, "texture_slots")
layout.prop(self, "go_to") layout.prop(self, "go_to")
layout.prop(self, "action") layout.prop(self, "action")
@ -164,10 +170,10 @@ class PlasmaAnimCmdMsgNode(PlasmaMessageNode, bpy.types.Node):
msg = plAnimCmdMsg() msg = plAnimCmdMsg()
# We're either sending this off to an AGMasterMod or a LayerAnim # We're either sending this off to an AGMasterMod or a LayerAnim
obj = bpy.data.objects.get(self.object_name, None)
if obj is None:
self.raise_error("invalid object: '{}'".format(self.object_name))
if self.anim_type == "OBJECT": if self.anim_type == "OBJECT":
obj = bpy.data.objects.get(self.object_name, None)
if obj is None:
self.raise_error("invalid object: '{}'".format(self.object_name))
if not exporter.animation.is_animated(obj): if not exporter.animation.is_animated(obj):
self.raise_error("invalid animation") self.raise_error("invalid animation")
group = obj.plasma_modifiers.animation_group group = obj.plasma_modifiers.animation_group
@ -182,11 +188,7 @@ class PlasmaAnimCmdMsgNode(PlasmaMessageNode, bpy.types.Node):
material = bpy.data.materials.get(self.material_name, None) material = bpy.data.materials.get(self.material_name, None)
if material is None: if material is None:
self.raise_error("invalid material: '{}'".format(self.material_name)) self.raise_error("invalid material: '{}'".format(self.material_name))
tex_slot = material.texture_slots.get(self.texture_name, None) target = exporter.mesh.material.get_texture_animation_key(obj, material, self.texture_name)
if tex_slot is None:
self.raise_error("invalid texture: '{}'".format(self.texture_name))
name = "{}_{}_LayerAnim".format(self.material_name, self.texture_name)
target = exporter.mgr.find_create_key(plLayerAnimation, name=name, so=so)
if target is None: if target is None:
raise RuntimeError() raise RuntimeError()
msg.addReceiver(target) msg.addReceiver(target)

5
korman/ui/ui_texture.py

@ -62,7 +62,7 @@ class PlasmaLayerPanel(TextureButtonsPanel, bpy.types.Panel):
bl_label = "Plasma Layer Options" bl_label = "Plasma Layer Options"
def draw(self, context): def draw(self, context):
texture = context.texture texture, slot = context.texture, context.texture_slot
layer_props = texture.plasma_layer layer_props = texture.plasma_layer
layout = self.layout layout = self.layout
@ -84,12 +84,13 @@ class PlasmaLayerPanel(TextureButtonsPanel, bpy.types.Panel):
split = layout.split() split = layout.split()
col = split.column() col = split.column()
col.label("Animation:") col.label("Animation:")
col.enabled = self._has_animation_data(context) col.enabled = self._has_animation_data(context) and not slot.use_stencil
col.prop(layer_props, "anim_auto_start") col.prop(layer_props, "anim_auto_start")
col.prop(layer_props, "anim_loop") col.prop(layer_props, "anim_loop")
col = split.column() col = split.column()
col.label("General:") col.label("General:")
col.active = not slot.use_stencil
col.prop(layer_props, "opacity", text="Opacity") col.prop(layer_props, "opacity", text="Opacity")
col.prop(layer_props, "alpha_halo") col.prop(layer_props, "alpha_halo")

Loading…
Cancel
Save