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. 25
      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 camera
from . import decal
from . import explosions
from . import etlight
from . import image
@ -54,6 +55,7 @@ class Exporter:
self.camera = camera.CameraConverter(self)
self.image = image.ImageCache(self)
self.locman = locman.LocalizationConverter(self)
self.decal = decal.DecalConverter(self)
# Step 0.8: Init the progress mgr
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.image = image
self.tag = kwargs.get("tag", None)
self.name = kwargs.get("name", image.name)
def __eq__(self, other):
if not isinstance(other, _Texture):
@ -106,17 +107,17 @@ class _Texture:
def __str__(self):
if self.extension is None:
name = self.image.name
name = self.name
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:
name = "ALPHAGEN_{}".format(name)
name = "ALPHAGEN_{}".format(self.name)
if self.is_detail_map:
name = "DETAILGEN_{}-{}-{}-{}-{}_{}".format(self._DETAIL_BLEND[self.detail_blend],
self.detail_fade_start, self.detail_fade_stop,
self.detail_opacity_start, self.detail_opacity_stop,
name)
self.name)
return name
def _update(self, other):
@ -179,9 +180,9 @@ class MaterialConverter:
"""Exports a Blender Material as an hsGMaterial"""
# 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
# that's a special case as of the writing of this code.
single_user = self._requires_single_user_material(bo, bm)
# of that situation is when a lightmap image is baked. There are others, but as of right now,
# it can all be determined by what mods are attached.
single_user = any((i.copy_material for i in bo.plasma_modifiers.modifiers))
if single_user:
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)
@ -260,6 +261,78 @@ class MaterialConverter:
# Looks like we're done...
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):
self._report.msg("Exporting WaveSet Material '{}'", bm.name, indent=1)
@ -356,8 +429,6 @@ class MaterialConverter:
if canStencil:
hsgmat.compFlags |= hsGMaterial.kCompNeedsBlendChannel
state.blendFlags |= hsGMatState.kBlendAlpha | hsGMatState.kBlendAlphaMult | hsGMatState.kBlendNoTexColor
if slot.texture.type == "BLEND":
state.clampFlags |= hsGMatState.kClampTexture
state.ZFlags |= hsGMatState.kZNoZWrite
layer.ambient = hsColorRGBA(1.0, 1.0, 1.0, 1.0)
elif blend_flags:
@ -380,6 +451,8 @@ class MaterialConverter:
layer.specularPower = 1.0
texture = slot.texture
if texture.type == "BLEND":
hsgmat.compFlags |= hsGMaterial.kCompNeedsBlendChannel
# Apply custom layer properties
wantBumpmap = bm is not None and slot.use_map_normal
@ -715,6 +788,11 @@ class MaterialConverter:
pass
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.
texture = slot.texture
self.export_alpha_blend(texture.progression, texture.use_flip_axis, layer)
@ -818,7 +896,7 @@ class MaterialConverter:
image.pixels = pixels
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):
"""This exports an externally prepared image and an optional owning layer.
@ -1073,6 +1151,16 @@ class MaterialConverter:
def get_materials(self, 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):
return self._bump_mats.get(bo, None)
@ -1132,14 +1220,6 @@ class MaterialConverter:
def _report(self):
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):
"""Tests to see if this image has any alpha data"""

25
korman/exporter/mesh.py

@ -39,11 +39,13 @@ class _RenderLevel:
_MAJOR_SHIFT = 28
_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
if blendSpan:
self.major = self.MAJOR_DEFAULT
if pass_index > 0:
self.major = self.MAJOR_FRAMEBUF
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
# the render pass, just like it says...
@ -74,10 +76,9 @@ class _DrawableCriteria:
self.criteria = 0
if self.blend_span:
for mod in bo.plasma_modifiers.modifiers:
if mod.requires_face_sort:
if self._face_sort_allowed(bo):
self.criteria |= plDrawable.kCritSortFaces
if mod.requires_span_sort:
if self._span_sort_allowed(bo):
self.criteria |= plDrawable.kCritSortSpans
self.render_level = _RenderLevel(bo, hsgmat, pass_index, self.blend_span)
@ -92,6 +93,16 @@ class _DrawableCriteria:
def __hash__(self):
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
def span_type(self):
if self.blend_span:

19
korman/properties/modifiers/base.py

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

99
korman/properties/modifiers/render.py

@ -16,6 +16,7 @@
import bpy
from bpy.props import *
import functools
from PyHSPlasma import *
from .base import PlasmaModifierProperties, PlasmaModifierUpgradable
@ -24,6 +25,96 @@ from ...exporter import utils
from ...exporter.explosions import ExportError
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):
pl_id = "fademod"
@ -81,10 +172,6 @@ class PlasmaFadeMod(PlasmaModifierProperties):
mod.farOpaq = self.far_opaq
mod.farTrans = self.far_trans
@property
def requires_span_sort(self):
return True
class PlasmaFollowMod(idprops.IDPropObjectMixin, PlasmaModifierProperties):
pl_id = "followmod"
@ -202,6 +289,10 @@ class PlasmaLightMapGen(idprops.IDPropMixin, PlasmaModifierProperties, PlasmaMod
else:
return self.bake_type == "lightmap"
@property
def copy_material(self):
return self.bake_lightmap
def export(self, exporter, bo, so):
# If we're exporting vertex colors, who gives a rat's behind?
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,
default=0.0)
@property
def copy_material(self):
return True
def export(self, exporter, bo, so):
waveset = exporter.mgr.find_create_object(plWaveSet7, name=bo.name, so=so)
if self.wind_object:

90
korman/properties/prop_scene.py

@ -15,6 +15,7 @@
import bpy
from bpy.props import *
import itertools
from ..exporter.etlight import _NUM_RENDER_LAYERS
@ -41,10 +42,99 @@ class PlasmaBakePass(bpy.types.PropertyGroup):
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):
bake_passes = CollectionProperty(type=PlasmaBakePass)
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",
options={"HIDDEN", "SKIP_SAVE"},
type=bpy.types.Object)

1
korman/ui/__init__.py

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

49
korman/ui/modifiers/render.py

@ -18,6 +18,55 @@ import bpy
from .. import ui_list
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):
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