Browse Source

Merge pull request #172 from Hoikas/decals

Decals
pull/175/head
Adam Johnson 5 years ago committed by GitHub
parent
commit
0bbdb33f29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      korman/exporter/convert.py
  2. 185
      korman/exporter/decal.py
  3. 116
      korman/exporter/material.py
  4. 29
      korman/exporter/mesh.py
  5. 19
      korman/properties/modifiers/base.py
  6. 99
      korman/properties/modifiers/render.py
  7. 4
      korman/properties/modifiers/water.py
  8. 90
      korman/properties/prop_scene.py
  9. 1
      korman/ui/__init__.py
  10. 49
      korman/ui/modifiers/render.py
  11. 100
      korman/ui/ui_scene.py

2
korman/exporter/convert.py

@ -21,6 +21,7 @@ import time
from . import animation from . import animation
from . import camera from . import camera
from . import decal
from . import explosions from . import explosions
from . import etlight from . import etlight
from . import image from . import image
@ -54,6 +55,7 @@ class Exporter:
self.camera = camera.CameraConverter(self) self.camera = camera.CameraConverter(self)
self.image = image.ImageCache(self) self.image = image.ImageCache(self)
self.locman = locman.LocalizationConverter(self) self.locman = locman.LocalizationConverter(self)
self.decal = decal.DecalConverter(self)
# Step 0.8: Init the progress mgr # Step 0.8: Init the progress mgr
self.mesh.add_progress_presteps(self.report) self.mesh.add_progress_presteps(self.report)

185
korman/exporter/decal.py

@ -0,0 +1,185 @@
# 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 collections import defaultdict
import itertools
from PyHSPlasma import *
import weakref
from ..exporter.explosions import ExportError
def _get_puddle_class(exporter, name, vs):
if vs:
# sigh... thou shalt not...
exporter.report.warn("'{}': Cannot use 'Water Ripple (Shallow) on a waveset--forcing to 'Water Ripple (Deep)", name)
return plDynaRippleVSMgr
return plDynaPuddleMgr
def _get_footprint_class(exporter, name, vs):
if vs:
raise ExportError("'{}': Footprints cannot be attached to wavesets", name)
return plDynaFootMgr
class DecalConverter:
_decal_lookup = {
"footprint_dry": _get_footprint_class,
"footprint_wet": _get_footprint_class,
"puddle": _get_puddle_class,
"ripple": lambda e, name, vs: plDynaRippleVSMgr if vs else plDynaRippleMgr,
}
def __init__(self, exporter):
self._decal_managers = defaultdict(list)
self._exporter = weakref.ref(exporter)
self._notifies = defaultdict(set)
def add_dynamic_decal_receiver(self, so, decal_name):
# One decal manager in Blender can map to many Plasma decal managers.
# The case we care about: a single water decal exporting to multiple DynaDecalMgrs
# eg two wavesets (two mgrs) and two water planes (one mgr)
# We don't care about: DynaDecalMgrs in another page.
decal_mgrs, so_key = self._decal_managers.get(decal_name), so.key
if decal_mgrs is None:
raise ExportError("'{}': Invalid decal manager '{}'", so_key.name, decal_name)
# If we are waveset water, then we can only have one target...
waveset_id = plFactory.ClassIndex("plWaveSet7")
waveset = next((i for i in so.modifiers if i.type == waveset_id), None)
so_loc = so_key.location
for key, decal_mgr in ((i, i.object) for i in decal_mgrs):
if key.location == so_loc and getattr(decal_mgr, "waveSet", None) == waveset:
decal_mgr.addTarget(so_key)
# HACKAGE: Add the wet/dirty notifes now that we know about all the decal managers.
notify_names = self._notifies[decal_name]
notify_keys = itertools.chain.from_iterable((self._decal_managers[i] for i in notify_names))
for notify_key in notify_keys:
for i in (i.object for i in decal_mgrs):
i.addNotify(notify_key)
# Don't need to do that again.
del self._notifies[decal_name]
def export_active_print_shape(self, print_shape, decal_name):
decal_mgrs = self._decal_managers.get(decal_name)
if decal_mgrs is None:
raise ExportError("'{}': Invalid decal manager '{}'", print_shape.key.name, decal_name)
for i in decal_mgrs:
print_shape.addDecalMgr(i)
def export_static_decal(self, bo):
mat_mgr = self._exporter().mesh.material
mat_keys = mat_mgr.get_materials(bo)
if not mat_keys:
raise ExportError("'{}': Cannot print decal onto object with no materials", bo.name)
zFlags = hsGMatState.kZIncLayer | hsGMatState.kZNoZWrite
for material in (i.object for i in mat_keys):
# Only useful in a debugging context
material.compFlags |= hsGMaterial.kCompDecal
# zFlags should only be applied to the material's base layer
# note: changing blend flags is unsafe here -- so don't even think about it!
layer = mat_mgr.get_base_layer(material)
layer.state.ZFlags |= zFlags
def generate_dynamic_decal(self, bo, decal_name):
decal = next((i for i in bpy.context.scene.plasma_scene.decal_managers if i.name == decal_name), None)
if decal is None:
raise ExportError("'{}': Invalid decal manager '{}'", bo.name, decal_name)
exporter = self._exporter()
decal_type = decal.decal_type
is_waveset = bo.plasma_modifiers.water_basic.enabled
pClass = self._decal_lookup[decal_type](exporter, decal_name, is_waveset)
# DynaDecal Managers generate geometry at runtime, so we need to share them as much as
# possible. However, it is best to keep things page local. Furthermore, wavesets cannot
# share decal managers due to vertex shaders being used.
name = "{}_{}".format(decal_name, bo.name) if is_waveset else decal_name
decal_mgr = exporter.mgr.find_object(pClass, bl=bo, name=name)
if decal_mgr is None:
self._report.msg("Exporing decal manager '{}' to '{}'", decal_name, name, indent=2)
decal_mgr = exporter.mgr.add_object(pClass, bl=bo, name=name)
self._decal_managers[decal_name].append(decal_mgr.key)
# Certain decals are required to be squares
if decal_type in {"footprint_dry", "footprint_wet", "wake"}:
length, width = decal.length / 100.0, decal.width / 100.0
else:
length = max(decal.length, decal.width) / 100.0
width = max(decal.length, decal.width) / 100.0
image = decal.image
if image is None:
raise ExportError("'{}': decal manager '{}' has no image set", bo.name, decal_name)
blend = getattr(hsGMatState, decal.blend)
mats = exporter.mesh.material.export_print_materials(bo, image, name, blend)
decal_mgr.matPreShade, decal_mgr.matRTShade = mats
# Hardwired values from PlasmaMAX
decal_mgr.maxNumVerts = 1000
decal_mgr.maxNumIdx = 1000
decal_mgr.intensity = decal.intensity / 100.0
decal_mgr.gridSizeU = 2.5
decal_mgr.gridSizeV = 2.5
decal_mgr.scale = hsVector3(length, width, 1.0)
# Hardwired calculations from PlasmaMAX
if decal_type in {"footprint_dry", "footprint_wet", "bullet"}:
decal_mgr.rampEnd = 0.1
decal_mgr.decayStart = decal.life_span - (decal.life_span * 0.25)
decal_mgr.lifeSpan = decal.life_span
elif decal_type in {"puddle", "ripple", "torpedo", "wake"}:
decal_mgr.rampEnd = 0.25
life_span = decal.life_span if decal_type == "torpedo" else length / 2.0
decal_mgr.decayStart = life_span * 0.8
decal_mgr.lifeSpan = life_span
else:
raise RuntimeError()
# While any decal manager can be wet/dry, it really makes the most sense to only
# expose wet footprints. In the future, we could expose the plDynaDecalEnableMsg
# to nodes for advanced hacking.
decal_mgr.waitOnEnable = decal_type == "footprint_wet"
if decal_type in {"puddle", "ripple"}:
decal_mgr.wetLength = decal.wet_time
self._notifies[decal_name].update((i.name for i in decal.wet_managers
if i.enabled and i.name != decal_name))
# UV Animations are hardcoded in PlasmaMAX. Any reason why we should expose this?
# I can't think of any presently... Note testing the final instance instead of the
# artist setting in case that gets overridden (puddle -> ripple)
if isinstance(decal_mgr, plDynaPuddleMgr):
decal_mgr.initUVW = hsVector3(5.0, 5.0, 5.0)
decal_mgr.finalUVW = hsVector3(1.0, 1.0, 1.0)
elif isinstance(decal_mgr, plDynaRippleMgr):
# wakes, torpedos, and ripples...
decal_mgr.initUVW = hsVector3(3.0, 3.0, 3.0)
decal_mgr.finalUVW = hsVector3(1.0, 1.0, 1.0)
if isinstance(decal_mgr, (plDynaRippleVSMgr, plDynaTorpedoVSMgr)):
decal_mgr.waveSet = exporter.mgr.find_create_key(plWaveSet7, bl=bo)
@property
def _mgr(self):
return self._exporter().mgr
@property
def _report(self):
return self._exporter().report

116
korman/exporter/material.py

@ -90,6 +90,7 @@ class _Texture:
self.ephemeral = kwargs.get("ephemeral", False) self.ephemeral = kwargs.get("ephemeral", False)
self.image = image self.image = image
self.tag = kwargs.get("tag", None) self.tag = kwargs.get("tag", None)
self.name = kwargs.get("name", image.name)
def __eq__(self, other): def __eq__(self, other):
if not isinstance(other, _Texture): if not isinstance(other, _Texture):
@ -106,17 +107,17 @@ class _Texture:
def __str__(self): def __str__(self):
if self.extension is None: if self.extension is None:
name = self.image.name name = self.name
else: else:
name = str(Path(self.image.name).with_suffix(".{}".format(self.extension))) name = str(Path(self.name).with_suffix(".{}".format(self.extension)))
if self.calc_alpha: if self.calc_alpha:
name = "ALPHAGEN_{}".format(name) name = "ALPHAGEN_{}".format(self.name)
if self.is_detail_map: if self.is_detail_map:
name = "DETAILGEN_{}-{}-{}-{}-{}_{}".format(self._DETAIL_BLEND[self.detail_blend], name = "DETAILGEN_{}-{}-{}-{}-{}_{}".format(self._DETAIL_BLEND[self.detail_blend],
self.detail_fade_start, self.detail_fade_stop, self.detail_fade_start, self.detail_fade_stop,
self.detail_opacity_start, self.detail_opacity_stop, self.detail_opacity_start, self.detail_opacity_stop,
name) self.name)
return name return name
def _update(self, other): def _update(self, other):
@ -179,9 +180,9 @@ class MaterialConverter:
"""Exports a Blender Material as an hsGMaterial""" """Exports a Blender Material as an hsGMaterial"""
# Sometimes, a material might need to be single-use. Right now, the most apparent example # Sometimes, a material might need to be single-use. Right now, the most apparent example
# of that situation is when a lightmap image is baked. Wavesets are in the same boat, but # of that situation is when a lightmap image is baked. There are others, but as of right now,
# that's a special case as of the writing of this code. # it can all be determined by what mods are attached.
single_user = self._requires_single_user_material(bo, bm) single_user = any((i.copy_material for i in bo.plasma_modifiers.modifiers))
if single_user: if single_user:
mat_name = "{}_AutoSingle".format(bm.name) if bo.name == bm.name else "{}_{}".format(bo.name, bm.name) mat_name = "{}_AutoSingle".format(bm.name) if bo.name == bm.name else "{}_{}".format(bo.name, bm.name)
self._report.msg("Exporting Material '{}' as single user '{}'", bm.name, mat_name, indent=1) self._report.msg("Exporting Material '{}' as single user '{}'", bm.name, mat_name, indent=1)
@ -260,6 +261,78 @@ class MaterialConverter:
# Looks like we're done... # Looks like we're done...
return hsgmat.key return hsgmat.key
def export_print_materials(self, bo, image, name, blend):
"""Exports dynamic decal print material(s)"""
def make_print_material(name):
layer = self._mgr.add_object(plLayer, bl=bo, name=name)
layer.state.blendFlags = blend
layer.state.clampFlags = hsGMatState.kClampTexture
layer.state.ZFlags = hsGMatState.kZNoZWrite | hsGMatState.kZIncLayer
layer.ambient = hsColorRGBA(0.0, 0.0, 0.0, 1.0)
layer.preshade = hsColorRGBA(0.0, 0.0, 0.0, 1.0)
layer.runtime = hsColorRGBA(1.0, 1.0, 1.0, 1.0)
self.export_prepared_image(name=image_name, image=image, alpha_type=image_alpha,
owner=layer, allowed_formats={"DDS"}, indent=4)
material = self._mgr.add_object(hsGMaterial, bl=bo, name=name)
material.addLayer(layer.key)
return material, layer
want_preshade = blend == hsGMatState.kBlendAlpha
image_alpha = self._test_image_alpha(image)
if image_alpha == TextureAlpha.opaque and want_preshade:
self._report.warn("Using an opaque texture with alpha blending -- this may look bad")
# Non-alpha blendmodes absolutely cannot have an alpha channel. Period. Nada.
# You can't even filter it out with blend flags. We'll try to mitigate the damage by
# exporting a DXT1 version. As of right now, opaque vs on_off does nothing, so we still
# get some turd-alpha data.
if image_alpha == TextureAlpha.full and not want_preshade:
self._report.warn("Using an alpha texture with a non-alpha blend mode -- this may look bad", indent=3)
image_alpha = TextureAlpha.opaque
image_name = "DECALPRINT_{}".format(image.name)
else:
image_name = image.name
# Check to see if we have already processed this print material...
rtname = "DECALPRINT_{}".format(name)
rt_key = self._mgr.find_key(hsGMaterial, bl=bo, name=rtname)
if want_preshade:
prename = "DECALPRINT_{}_AH".format(name)
pre_key = self._mgr.find_key(hsGMaterial, bl=bo, name=prename)
else:
pre_key = None
if rt_key or pre_key:
return pre_key, rt_key
self._report.msg("Exporting Print Material '{}'", rtname, indent=3)
rt_material, rt_layer = make_print_material(rtname)
if blend == hsGMatState.kBlendMult:
rt_layer.state.blendFlags |= hsGMatState.kBlendInvertFinalColor
rt_key = rt_material.key
if want_preshade:
self._report.msg("Exporting Print Material '{}'", prename, indent=3)
pre_material, pre_layer = make_print_material(prename)
pre_material.compFlags |= hsGMaterial.kCompNeedsBlendChannel
pre_layer.state.miscFlags |= hsGMatState.kMiscBindNext | hsGMatState.kMiscRestartPassHere
pre_layer.preshade = hsColorRGBA(1.0, 1.0, 1.0, 1.0)
blend_layer = self._mgr.add_object(plLayer, bl=bo, name="{}_AlphaBlend".format(rtname))
blend_layer.state.blendFlags = hsGMatState.kBlendAlpha | hsGMatState.kBlendNoTexColor | \
hsGMatState.kBlendAlphaMult
blend_layer.state.clampFlags = hsGMatState.kClampTexture
blend_layer.state.ZFlags = hsGMatState.kZNoZWrite
blend_layer.ambient = hsColorRGBA(1.0, 1.0, 1.0, 1.0)
pre_material.addLayer(blend_layer.key)
self.export_alpha_blend("LINEAR", "HORIZONTAL", owner=blend_layer, indent=4)
pre_key = pre_material.key
else:
pre_key = None
return pre_key, rt_key
def export_waveset_material(self, bo, bm): def export_waveset_material(self, bo, bm):
self._report.msg("Exporting WaveSet Material '{}'", bm.name, indent=1) self._report.msg("Exporting WaveSet Material '{}'", bm.name, indent=1)
@ -356,8 +429,6 @@ class MaterialConverter:
if canStencil: if canStencil:
hsgmat.compFlags |= hsGMaterial.kCompNeedsBlendChannel hsgmat.compFlags |= hsGMaterial.kCompNeedsBlendChannel
state.blendFlags |= hsGMatState.kBlendAlpha | hsGMatState.kBlendAlphaMult | hsGMatState.kBlendNoTexColor state.blendFlags |= hsGMatState.kBlendAlpha | hsGMatState.kBlendAlphaMult | hsGMatState.kBlendNoTexColor
if slot.texture.type == "BLEND":
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)
elif blend_flags: elif blend_flags:
@ -380,6 +451,8 @@ class MaterialConverter:
layer.specularPower = 1.0 layer.specularPower = 1.0
texture = slot.texture texture = slot.texture
if texture.type == "BLEND":
hsgmat.compFlags |= hsGMaterial.kCompNeedsBlendChannel
# Apply custom layer properties # Apply custom layer properties
wantBumpmap = bm is not None and slot.use_map_normal wantBumpmap = bm is not None and slot.use_map_normal
@ -715,6 +788,11 @@ class MaterialConverter:
pass pass
def _export_texture_type_blend(self, bo, layer, slot): def _export_texture_type_blend(self, bo, layer, slot):
state = layer.state
state.blendFlags |= hsGMatState.kBlendAlpha | hsGMatState.kBlendAlphaMult | hsGMatState.kBlendNoTexColor
state.clampFlags |= hsGMatState.kClampTexture
state.ZFlags |= hsGMatState.kZNoZWrite
# This has been separated out because other things may need alpha blend textures. # This has been separated out because other things may need alpha blend textures.
texture = slot.texture texture = slot.texture
self.export_alpha_blend(texture.progression, texture.use_flip_axis, layer) self.export_alpha_blend(texture.progression, texture.use_flip_axis, layer)
@ -818,7 +896,7 @@ class MaterialConverter:
image.pixels = pixels image.pixels = pixels
self.export_prepared_image(image=image, owner=owner, allowed_formats={"BMP"}, self.export_prepared_image(image=image, owner=owner, allowed_formats={"BMP"},
alpha_type=TextureAlpha.full, indent=2, ephemeral=True) alpha_type=TextureAlpha.full, indent=indent, ephemeral=True)
def export_prepared_image(self, **kwargs): def export_prepared_image(self, **kwargs):
"""This exports an externally prepared image and an optional owning layer. """This exports an externally prepared image and an optional owning layer.
@ -1073,6 +1151,16 @@ class MaterialConverter:
def get_materials(self, bo): def get_materials(self, bo):
return self._obj2mat.get(bo, []) return self._obj2mat.get(bo, [])
def get_base_layer(self, hsgmat):
try:
layer = hsgmat.layers[0].object
except IndexError:
return None
else:
while layer.underLay is not None:
layer = layer.underLay.object
return layer
def get_bump_layer(self, bo): def get_bump_layer(self, bo):
return self._bump_mats.get(bo, None) return self._bump_mats.get(bo, None)
@ -1132,14 +1220,6 @@ class MaterialConverter:
def _report(self): def _report(self):
return self._exporter().report return self._exporter().report
def _requires_single_user_material(self, bo, bm):
modifiers = bo.plasma_modifiers
if modifiers.lightmap.bake_lightmap:
return True
if modifiers.water_basic.enabled:
return True
return False
def _test_image_alpha(self, image): def _test_image_alpha(self, image):
"""Tests to see if this image has any alpha data""" """Tests to see if this image has any alpha data"""

29
korman/exporter/mesh.py

@ -39,11 +39,13 @@ class _RenderLevel:
_MAJOR_SHIFT = 28 _MAJOR_SHIFT = 28
_MINOR_MASK = ((1 << _MAJOR_SHIFT) - 1) _MINOR_MASK = ((1 << _MAJOR_SHIFT) - 1)
def __init__(self, bo, hsgmat, pass_index, blendSpan=False): def __init__(self, bo, hsgmat, pass_index, blend_span=False):
self.level = 0 self.level = 0
if pass_index > 0:
if blendSpan: self.major = self.MAJOR_FRAMEBUF
self.major = self.MAJOR_DEFAULT self.minor = pass_index * 4
else:
self.major = self.MAJOR_BLEND if blend_span else self.MAJOR_OPAQUE
# We use the blender material's pass index (which we stashed in the hsGMaterial) to increment # We use the blender material's pass index (which we stashed in the hsGMaterial) to increment
# the render pass, just like it says... # the render pass, just like it says...
@ -74,11 +76,10 @@ class _DrawableCriteria:
self.criteria = 0 self.criteria = 0
if self.blend_span: if self.blend_span:
for mod in bo.plasma_modifiers.modifiers: if self._face_sort_allowed(bo):
if mod.requires_face_sort: self.criteria |= plDrawable.kCritSortFaces
self.criteria |= plDrawable.kCritSortFaces if self._span_sort_allowed(bo):
if mod.requires_span_sort: self.criteria |= plDrawable.kCritSortSpans
self.criteria |= plDrawable.kCritSortSpans
self.render_level = _RenderLevel(bo, hsgmat, pass_index, self.blend_span) self.render_level = _RenderLevel(bo, hsgmat, pass_index, self.blend_span)
def __eq__(self, other): def __eq__(self, other):
@ -92,6 +93,16 @@ class _DrawableCriteria:
def __hash__(self): def __hash__(self):
return hash(self.render_level) ^ hash(self.blend_span) ^ hash(self.criteria) return hash(self.render_level) ^ hash(self.blend_span) ^ hash(self.criteria)
def _face_sort_allowed(self, bo):
# For now, only test the modifiers
# This will need to be tweaked further for GUIs...
return not any((i.no_face_sort for i in bo.plasma_modifiers.modifiers))
def _span_sort_allowed(self, bo):
# For now, only test the modifiers
# This will need to be tweaked further for GUIs...
return not any((i.no_face_sort for i in bo.plasma_modifiers.modifiers))
@property @property
def span_type(self): def span_type(self):
if self.blend_span: if self.blend_span:

19
korman/properties/modifiers/base.py

@ -19,6 +19,11 @@ from bpy.props import *
from contextlib import contextmanager from contextlib import contextmanager
class PlasmaModifierProperties(bpy.types.PropertyGroup): class PlasmaModifierProperties(bpy.types.PropertyGroup):
@property
def copy_material(self):
"""Materials MUST be single-user"""
return False
def created(self): def created(self):
pass pass
@ -37,19 +42,19 @@ class PlasmaModifierProperties(bpy.types.PropertyGroup):
return self.id_data.name return self.id_data.name
@property @property
def requires_actor(self): def no_face_sort(self):
"""Indicates if this modifier requires the object to be a movable actor""" """Indicates that the geometry's faces should never be sorted by the engine"""
return False return False
@property @property
def requires_face_sort(self): def no_span_sort(self):
"""Indicates that the geometry's faces must be sorted by the engine""" """Indicates that the geometry's Spans should never be sorted with those from other
Drawables that will render in the same pass"""
return False return False
@property @property
def requires_span_sort(self): def requires_actor(self):
"""Indicates that the geometry's Spans must be sorted with those from other Drawables that """Indicates if this modifier requires the object to be a movable actor"""
will render in the same pass"""
return False return False
# Guess what? # Guess what?

99
korman/properties/modifiers/render.py

@ -16,6 +16,7 @@
import bpy import bpy
from bpy.props import * from bpy.props import *
import functools
from PyHSPlasma import * from PyHSPlasma import *
from .base import PlasmaModifierProperties, PlasmaModifierUpgradable from .base import PlasmaModifierProperties, PlasmaModifierUpgradable
@ -24,6 +25,96 @@ from ...exporter import utils
from ...exporter.explosions import ExportError from ...exporter.explosions import ExportError
from ... import idprops from ... import idprops
class PlasmaDecalManagerRef(bpy.types.PropertyGroup):
enabled = BoolProperty(name="Enabled",
default=True,
options=set())
name = StringProperty(name="Decal Name",
options=set())
class PlasmaDecalMod:
def _iter_decals(self, func):
for decal_ref in self.managers:
if decal_ref.enabled:
func(decal_ref.name)
@classmethod
def register(cls):
cls.managers = CollectionProperty(type=PlasmaDecalManagerRef)
cls.active_manager_index = IntProperty(options={"HIDDEN"})
class PlasmaDecalPrintMod(PlasmaDecalMod, PlasmaModifierProperties):
pl_id = "decal_print"
bl_category = "Render"
bl_label = "Print Decal"
bl_description = "Prints a decal onto an object"
decal_type = EnumProperty(name="Decal Type",
description="Type of decal to print onto another object",
items=[("DYNAMIC", "Dynamic", "This object prints a decal onto dynamic decal surfaces"),
("STATIC", "Static", "This object is a decal itself")],
options=set())
# Dynamic Decals
length = FloatProperty(name="Length",
min=0.1, soft_max=30.0, precision=2,
default=0.45,
options=set())
width = FloatProperty(name="Width",
min=0.1, soft_max=30.0, precision=2,
default=0.9,
options=set())
height = FloatProperty(name="Height",
min=0.1, soft_max=30.0, precision=2,
default=1.0,
options=set())
@property
def copy_material(self):
return self.decal_type == "STATIC"
def get_key(self, exporter, so):
if self.decal_type == "DYNAMIC":
pClass = plActivePrintShape if any((i.enabled for i in self.managers)) else plPrintShape
return exporter.mgr.find_create_key(pClass, so=so)
def export(self, exporter, bo, so):
if self.decal_type == "STATIC":
exporter.decal.export_static_decal(bo)
elif self.decal_type == "DYNAMIC":
print_shape = self.get_key(exporter, so).object
print_shape.length = self.length
print_shape.width = self.width
print_shape.height = self.height
def post_export(self, exporter, bo, so):
if self.decal_type == "DYNAMIC":
print_shape = self.get_key(exporter, so).object
f = functools.partial(exporter.decal.export_active_print_shape, print_shape)
self._iter_decals(f)
class PlasmaDecalReceiveMod(PlasmaDecalMod, PlasmaModifierProperties):
pl_id = "decal_receive"
bl_category = "Render"
bl_label = "Receive Decal"
bl_description = "Allows this object to receive dynamic decals"
managers = CollectionProperty(type=PlasmaDecalManagerRef)
active_manager_index = IntProperty(options={"HIDDEN"})
def export(self, exporter, bo, so):
f = functools.partial(exporter.decal.generate_dynamic_decal, bo)
self._iter_decals(f)
def post_export(self, exporter, bo, so):
f = functools.partial(exporter.decal.add_dynamic_decal_receiver, so)
self._iter_decals(f)
class PlasmaFadeMod(PlasmaModifierProperties): class PlasmaFadeMod(PlasmaModifierProperties):
pl_id = "fademod" pl_id = "fademod"
@ -81,10 +172,6 @@ class PlasmaFadeMod(PlasmaModifierProperties):
mod.farOpaq = self.far_opaq mod.farOpaq = self.far_opaq
mod.farTrans = self.far_trans mod.farTrans = self.far_trans
@property
def requires_span_sort(self):
return True
class PlasmaFollowMod(idprops.IDPropObjectMixin, PlasmaModifierProperties): class PlasmaFollowMod(idprops.IDPropObjectMixin, PlasmaModifierProperties):
pl_id = "followmod" pl_id = "followmod"
@ -202,6 +289,10 @@ class PlasmaLightMapGen(idprops.IDPropMixin, PlasmaModifierProperties, PlasmaMod
else: else:
return self.bake_type == "lightmap" return self.bake_type == "lightmap"
@property
def copy_material(self):
return self.bake_lightmap
def export(self, exporter, bo, so): def export(self, exporter, bo, so):
# If we're exporting vertex colors, who gives a rat's behind? # If we're exporting vertex colors, who gives a rat's behind?
if not self.bake_lightmap: if not self.bake_lightmap:

4
korman/properties/modifiers/water.py

@ -238,6 +238,10 @@ class PlasmaWaterModifier(idprops.IDPropMixin, PlasmaModifierProperties, bpy.typ
min=-10.0, max=10.0, min=-10.0, max=10.0,
default=0.0) default=0.0)
@property
def copy_material(self):
return True
def export(self, exporter, bo, so): def export(self, exporter, bo, so):
waveset = exporter.mgr.find_create_object(plWaveSet7, name=bo.name, so=so) waveset = exporter.mgr.find_create_object(plWaveSet7, name=bo.name, so=so)
if self.wind_object: if self.wind_object:

90
korman/properties/prop_scene.py

@ -15,6 +15,7 @@
import bpy import bpy
from bpy.props import * from bpy.props import *
import itertools
from ..exporter.etlight import _NUM_RENDER_LAYERS from ..exporter.etlight import _NUM_RENDER_LAYERS
@ -41,10 +42,99 @@ class PlasmaBakePass(bpy.types.PropertyGroup):
default=((True,) * _NUM_RENDER_LAYERS)) default=((True,) * _NUM_RENDER_LAYERS))
class PlasmaWetDecalRef(bpy.types.PropertyGroup):
enabled = BoolProperty(name="Enabled",
default=True,
options=set())
name = StringProperty(name="Decal Name",
description="Wet decal manager",
options=set())
class PlasmaDecalManager(bpy.types.PropertyGroup):
def _get_display_name(self):
return self.name
def _set_display_name(self, value):
prev_value = self.name
for i in bpy.data.objects:
decal_receive = i.plasma_modifiers.decal_receive
decal_print = i.plasma_modifiers.decal_print
for j in itertools.chain(decal_receive.managers, decal_print.managers):
if j.name == prev_value:
j.name = value
for i in bpy.context.scene.plasma_scene.decal_managers:
for j in i.wet_managers:
if j.name == prev_value:
j.name = value
self.name = value
name = StringProperty(name="Decal Name",
options=set())
display_name = StringProperty(name="Display Name",
get=_get_display_name,
set=_set_display_name,
options=set())
decal_type = EnumProperty(name="Decal Type",
description="",
items=[("footprint_dry", "Footprint (Dry)", ""),
("footprint_wet", "Footprint (Wet)", ""),
("puddle", "Water Ripple (Shallow)", ""),
("ripple", "Water Ripple (Deep)", "")],
default="footprint_dry",
options=set())
image = PointerProperty(name="Image",
description="",
type=bpy.types.Image,
options=set())
blend = EnumProperty(name="Blend Mode",
description="",
items=[("kBlendAdd", "Add", ""),
("kBlendAlpha", "Alpha", ""),
("kBlendMADD", "Brighten", ""),
("kBlendMult", "Multiply", "")],
default="kBlendAlpha",
options=set())
length = IntProperty(name="Length",
description="",
subtype="PERCENTAGE",
min=0, soft_min=25, soft_max=400, default=100,
options=set())
width = IntProperty(name="Width",
description="",
subtype="PERCENTAGE",
min=0, soft_min=25, soft_max=400, default=100,
options=set())
intensity = IntProperty(name="Intensity",
description="",
subtype="PERCENTAGE",
min=0, soft_max=100, default=100,
options=set())
life_span = FloatProperty(name="Life Span",
description="",
subtype="TIME", unit="TIME",
min=0.0, soft_max=300.0, default=30.0,
options=set())
wet_time = FloatProperty(name="Wet Time",
description="How long the decal print shapes stay wet after losing contact with this surface",
subtype="TIME", unit="TIME",
min=0.0, soft_max=300.0, default=10.0,
options=set())
# Footprints to wet-ize
wet_managers = CollectionProperty(type=PlasmaWetDecalRef)
active_wet_index = IntProperty(options={"HIDDEN"})
class PlasmaScene(bpy.types.PropertyGroup): class PlasmaScene(bpy.types.PropertyGroup):
bake_passes = CollectionProperty(type=PlasmaBakePass) bake_passes = CollectionProperty(type=PlasmaBakePass)
active_pass_index = IntProperty(options={"HIDDEN"}) active_pass_index = IntProperty(options={"HIDDEN"})
decal_managers = CollectionProperty(type=PlasmaDecalManager)
active_decal_index = IntProperty(options={"HIDDEN"})
modifier_copy_object = PointerProperty(name="INTERNAL: Object to copy modifiers from", modifier_copy_object = PointerProperty(name="INTERNAL: Object to copy modifiers from",
options={"HIDDEN", "SKIP_SAVE"}, options={"HIDDEN", "SKIP_SAVE"},
type=bpy.types.Object) type=bpy.types.Object)

1
korman/ui/__init__.py

@ -21,6 +21,7 @@ from .ui_menus import *
from .ui_modifiers import * from .ui_modifiers import *
from .ui_object import * from .ui_object import *
from .ui_render_layer import * from .ui_render_layer import *
from .ui_scene import *
from .ui_text import * from .ui_text import *
from .ui_texture import * from .ui_texture import *
from .ui_toolbox import * from .ui_toolbox import *

49
korman/ui/modifiers/render.py

@ -18,6 +18,55 @@ import bpy
from .. import ui_list from .. import ui_list
from ...exporter.mesh import _VERTEX_COLOR_LAYERS from ...exporter.mesh import _VERTEX_COLOR_LAYERS
class DecalMgrListUI(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
if item.name:
layout.label(item.name, icon="BRUSH_DATA")
layout.prop(item, "enabled", text="")
else:
layout.label("[Empty]")
def decal_print(modifier, layout, context):
layout.prop(modifier, "decal_type")
layout = layout.column()
layout.enabled = modifier.decal_type == "DYNAMIC"
layout.label("Dimensions:")
row = layout.row(align=True)
row.prop(modifier, "length")
row.prop(modifier, "width")
row.prop(modifier, "height")
layout.separator()
ui_list.draw_modifier_list(layout, "DecalMgrListUI", modifier, "managers",
"active_manager_index", rows=2, maxrows=3)
try:
mgr_ref = modifier.managers[modifier.active_manager_index]
except:
pass
else:
scene = context.scene.plasma_scene
decal_mgr = next((i for i in scene.decal_managers if i.display_name == mgr_ref), None)
layout.alert = decal_mgr is None
layout.prop_search(mgr_ref, "name", scene, "decal_managers", icon="BRUSH_DATA")
layout.alert = False
def decal_receive(modifier, layout, context):
ui_list.draw_modifier_list(layout, "DecalMgrListUI", modifier, "managers",
"active_manager_index", rows=2, maxrows=3)
try:
mgr_ref = modifier.managers[modifier.active_manager_index]
except:
pass
else:
scene = context.scene.plasma_scene
decal_mgr = next((i for i in scene.decal_managers if i.display_name == mgr_ref), None)
layout.alert = decal_mgr is None
layout.prop_search(mgr_ref, "name", scene, "decal_managers", icon="BRUSH_DATA")
def fademod(modifier, layout, context): def fademod(modifier, layout, context):
layout.prop(modifier, "fader_type") layout.prop(modifier, "fader_type")

100
korman/ui/ui_scene.py

@ -0,0 +1,100 @@
# 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
import functools
from . import ui_list
class SceneButtonsPanel:
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@classmethod
def poll(cls, context):
return context.scene.render.engine == "PLASMA_GAME"
class DecalManagerListUI(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
layout.prop(item, "display_name", emboss=False, text="", icon="BRUSH_DATA")
class WetManagerListUI(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
if item.name:
layout.label(item.name, icon="BRUSH_DATA")
layout.prop(item, "enabled", text="")
else:
layout.label("[Empty]")
class PlasmaDecalManagersPanel(SceneButtonsPanel, bpy.types.Panel):
bl_label = "Plasma Decal Managers"
def draw(self, context):
layout, scene = self.layout, context.scene.plasma_scene
ui_list.draw_list(layout, "DecalManagerListUI", "scene", scene, "decal_managers",
"active_decal_index", name_prefix="Decal", name_prop="display_name",
rows=3)
try:
decal_mgr = scene.decal_managers[scene.active_decal_index]
except:
pass
else:
box = layout.box().column()
box.prop(decal_mgr, "decal_type")
box.alert = decal_mgr.image is None
box.prop(decal_mgr, "image")
box.alert = False
box.prop(decal_mgr, "blend")
box.separator()
split = box.split()
col = split.column(align=True)
col.label("Scale:")
col.alert = decal_mgr.decal_type in {"ripple", "puddle", "bullet", "torpedo"} \
and decal_mgr.length != decal_mgr.width
col.prop(decal_mgr, "length")
col.prop(decal_mgr, "width")
col = split.column()
col.label("Draw Settings:")
col.prop(decal_mgr, "intensity")
sub = col.row()
sub.active = decal_mgr.decal_type in {"footprint_dry", "footprint_wet", "bullet", "torpedo"}
sub.prop(decal_mgr, "life_span")
sub = col.row()
sub.active = decal_mgr.decal_type in {"puddle", "ripple"}
sub.prop(decal_mgr, "wet_time")
if decal_mgr.decal_type in {"puddle", "ripple"}:
box.separator()
box.label("Wet Footprints:")
ui_list.draw_list(box, "WetManagerListUI", "scene", decal_mgr, "wet_managers",
"active_wet_index", rows=2, maxrows=3)
try:
wet_ref = decal_mgr.wet_managers[decal_mgr.active_wet_index]
except:
pass
else:
wet_mgr = next((i for i in scene.decal_managers if i.name == wet_ref.name), None)
box.alert = getattr(wet_mgr, "decal_type", None) == "footprint_wet"
box.prop_search(wet_ref, "name", scene, "decal_managers", icon="NONE")
if wet_ref.name == decal_mgr.name:
box.label(text="Circular reference", icon="ERROR")
Loading…
Cancel
Save