diff --git a/korman/exporter/decal.py b/korman/exporter/decal.py index ab05e23..33461a1 100644 --- a/korman/exporter/decal.py +++ b/korman/exporter/decal.py @@ -61,6 +61,29 @@ class DecalConverter: if key.location == so_loc and getattr(decal_mgr, "waveSet", None) == waveset: decal_mgr.addTarget(so_key) + 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: diff --git a/korman/exporter/material.py b/korman/exporter/material.py index eac3fd4..b56ea8b 100644 --- a/korman/exporter/material.py +++ b/korman/exporter/material.py @@ -180,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) @@ -1151,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) @@ -1210,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""" diff --git a/korman/properties/modifiers/base.py b/korman/properties/modifiers/base.py index dd4a77a..0d2049d 100644 --- a/korman/properties/modifiers/base.py +++ b/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 diff --git a/korman/properties/modifiers/render.py b/korman/properties/modifiers/render.py index 1827923..856840c 100644 --- a/korman/properties/modifiers/render.py +++ b/korman/properties/modifiers/render.py @@ -34,7 +34,70 @@ class PlasmaDecalManagerRef(bpy.types.PropertyGroup): options=set()) -class PlasmaDecalReceiveMod(PlasmaModifierProperties): +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" @@ -44,11 +107,6 @@ class PlasmaDecalReceiveMod(PlasmaModifierProperties): managers = CollectionProperty(type=PlasmaDecalManagerRef) active_manager_index = IntProperty(options={"HIDDEN"}) - def _iter_decals(self, func): - for decal_ref in self.managers: - if decal_ref.enabled: - func(decal_ref.name) - def export(self, exporter, bo, so): f = functools.partial(exporter.decal.generate_dynamic_decal, bo) self._iter_decals(f) @@ -231,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: diff --git a/korman/properties/modifiers/water.py b/korman/properties/modifiers/water.py index 9920e77..e97d554 100644 --- a/korman/properties/modifiers/water.py +++ b/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: diff --git a/korman/properties/prop_scene.py b/korman/properties/prop_scene.py index 6d9dc76..61b66a2 100644 --- a/korman/properties/prop_scene.py +++ b/korman/properties/prop_scene.py @@ -15,6 +15,7 @@ import bpy from bpy.props import * +import itertools from ..exporter.etlight import _NUM_RENDER_LAYERS @@ -48,7 +49,8 @@ class PlasmaDecalManager(bpy.types.PropertyGroup): prev_value = self.name for i in bpy.data.objects: decal_receive = i.plasma_modifiers.decal_receive - for j in decal_receive.managers: + 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 self.name = value diff --git a/korman/ui/modifiers/render.py b/korman/ui/modifiers/render.py index 19afc84..fa4e5f6 100644 --- a/korman/ui/modifiers/render.py +++ b/korman/ui/modifiers/render.py @@ -27,6 +27,32 @@ class DecalMgrListUI(bpy.types.UIList): 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="NONE") + 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)