Browse Source

Simplify cube region generation code. (#367)

This consolidates a very common bit of LogicWiz modifier boilerplate
into one location for ease of use.
pull/369/head
Adam Johnson 2 years ago committed by GitHub
parent
commit
8c4a08c3e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      korman/exporter/convert.py
  2. 105
      korman/exporter/utils.py
  3. 12
      korman/operators/op_mesh.py
  4. 88
      korman/properties/modifiers/gui.py
  5. 23
      korman/properties/modifiers/logic.py

8
korman/exporter/convert.py

@ -415,7 +415,13 @@ class Exporter:
@functools.singledispatch @functools.singledispatch
def handle_temporary(temporary, parent): def handle_temporary(temporary, parent):
raise RuntimeError("Temporary object of type '{}' generated by '{}' was unhandled".format(temporary.__class__, parent.name)) # Maybe this was an embedded context manager?
if hasattr(temporary, "__enter__"):
ctx_temporary = self.exit_stack.enter_context(temporary)
if ctx_temporary is not None:
handle_temporary(ctx_temporary, parent)
else:
raise RuntimeError("Temporary object of type '{}' generated by '{}' was unhandled".format(temporary.__class__, parent.name))
@handle_temporary.register(bpy.types.Object) @handle_temporary.register(bpy.types.Object)
def _(temporary, parent): def _(temporary, parent):

105
korman/exporter/utils.py

@ -13,12 +13,14 @@
# 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/>.
from __future__ import annotations
import bmesh import bmesh
import bpy import bpy
import mathutils import mathutils
from typing import Callable, Iterator, Tuple
from contextlib import contextmanager from contextlib import contextmanager
from typing import *
from PyHSPlasma import * from PyHSPlasma import *
@ -54,46 +56,73 @@ def quaternion(blquat):
return hsQuat(blquat.x, blquat.y, blquat.z, blquat.w) return hsQuat(blquat.x, blquat.y, blquat.z, blquat.w)
@contextmanager class BMeshObject:
def bmesh_temporary_object(name : str, factory : Callable, page_name : str=None): def __init__(self, name: str, managed: bool = True):
"""Creates a temporary object and mesh that exists only for the duration of self._managed = managed
the context""" self._bmesh = None
mesh = bpy.data.meshes.new(name) self._mesh = bpy.data.meshes.new(name)
obj = bpy.data.objects.new(name, mesh) self._obj = bpy.data.objects.new(name, self._mesh)
obj.draw_type = "WIRE" self._obj.draw_type = "WIRE"
if page_name is not None: bpy.context.scene.objects.link(self._obj)
obj.plasma_object.page = page_name
bpy.context.scene.objects.link(obj) def __del__(self):
if self._managed:
bm = bmesh.new() bpy.context.scene.objects.unlink(self._obj)
try: bpy.data.meshes.remove(self._mesh)
factory(bm)
bm.to_mesh(mesh) def __enter__(self) -> bmesh.types.BMesh:
yield obj if self._mesh is not None:
finally: self._bmesh = bmesh.new()
bm.free() self._bmesh.from_mesh(self._mesh)
bpy.context.scene.objects.unlink(obj) return self._bmesh
def __exit__(self, type, value, traceback):
if self._bmesh is not None:
self._bmesh.to_mesh(self._mesh)
self._bmesh.free()
self._bmesh = None
def __getattr__(self, name: str) -> Any:
return getattr(self._obj, name)
@property
def object(self) -> bpy.types.Object:
return self._obj
def release(self) -> bpy.types.Object:
self._managed = False
return self._obj
def create_cube_region(name: str, size: float, owner_object: bpy.types.Object) -> bpy.types.Object:
"""Create a cube shaped region object"""
region_object = BMeshObject(name)
region_object.plasma_object.enabled = True
region_object.plasma_object.page = owner_object.plasma_object.page
region_object.hide_render = True
with region_object as bm:
bmesh.ops.create_cube(bm, size=(size))
bmesh.ops.transform(
bm,
matrix=mathutils.Matrix.Translation(
owner_object.matrix_world.translation - region_object.matrix_world.translation
),
space=region_object.matrix_world, verts=bm.verts
)
return region_object.release()
@contextmanager @contextmanager
def bmesh_object(name: str) -> Iterator[Tuple[bpy.types.Object, bmesh.types.BMesh]]: def pre_export_optional_cube_region(source, attr: str, name: str, size: float, owner_object: bpy.types.Object) -> Optional[bpy.types.Object]:
"""Creates an object and mesh that will be removed if the context is exited if getattr(source, attr) is None:
due to an error""" region_object = create_cube_region(name, size, owner_object)
mesh = bpy.data.meshes.new(name) setattr(source, attr, region_object)
obj = bpy.data.objects.new(name, mesh) try:
obj.draw_type = "WIRE" yield region_object
bpy.context.scene.objects.link(obj) finally:
source.property_unset(attr)
bm = bmesh.new()
try:
yield obj, bm
except:
bpy.context.scene.objects.unlink(obj)
bpy.data.meshes.remove(mesh)
raise
else: else:
bm.to_mesh(mesh) # contextlib.contextmanager requires for us to yield. Sad.
finally: yield
bm.free()
@contextmanager @contextmanager
def temporary_mesh_object(source : bpy.types.Object) -> bpy.types.Object: def temporary_mesh_object(source : bpy.types.Object) -> bpy.types.Object:

12
korman/operators/op_mesh.py

@ -100,15 +100,15 @@ class PlasmaAddFlareOperator(PlasmaMeshOperator, bpy.types.Operator):
flare_root.plasma_modifiers.viewfacemod.preset_options = "Sprite" flare_root.plasma_modifiers.viewfacemod.preset_options = "Sprite"
# Create a textured Plane # Create a textured Plane
with utils.bmesh_object("{}_Visible".format(self.name_stem)) as (flare_plane, bm): flare_plane = utils.BMeshObject(f"{self.name_stem}_Visible", managed=False)
flare_plane.hide_render = True flare_plane.hide_render = True
flare_plane.plasma_object.enabled = True flare_plane.plasma_object.enabled = True
bpyscene.objects.active = flare_plane bpyscene.objects.active = flare_plane
with flare_plane as bm:
# Make the actual plane mesh, facing away from the empty # Make the actual plane mesh, facing away from the empty
bmesh.ops.create_grid(bm, size=(0.5 + self.flare_distance * 0.5), matrix=mathutils.Matrix.Rotation(math.radians(180.0), 4, 'X')) bmesh.ops.create_grid(bm, size=(0.5 + self.flare_distance * 0.5), matrix=mathutils.Matrix.Rotation(math.radians(180.0), 4, 'X'))
bmesh.ops.transform(bm, matrix=mathutils.Matrix.Translation((0.0, 0.0, -self.flare_distance)), space=flare_plane.matrix_world, verts=bm.verts) bmesh.ops.transform(bm, matrix=mathutils.Matrix.Translation((0.0, 0.0, -self.flare_distance)), space=flare_plane.matrix_world, verts=bm.verts)
bpy.ops.object.origin_set(type="ORIGIN_GEOMETRY") bpy.ops.object.origin_set(type="ORIGIN_GEOMETRY")
# Give the plane a basic UV unwrap, so that it's texture-ready # Give the plane a basic UV unwrap, so that it's texture-ready
bpy.ops.object.editmode_toggle() bpy.ops.object.editmode_toggle()

88
korman/properties/modifiers/gui.py

@ -224,36 +224,30 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
if version not in our_versions: if version not in our_versions:
# We aren't needed here # We aren't needed here
exporter.report.port("Object '{}' has a JournalMod not enabled for export to the selected engine. Skipping.", exporter.report.port("Object '{}' has a JournalMod not enabled for export to the selected engine. Skipping.",
bo.name, version, indent=2) bo.name, version)
return return
if self.clickable_region is None: # Generate the clickable region if it was not provided
with utils.bmesh_object("{}_Journal_ClkRgn".format(self.key_name)) as (rgn_obj, bm): yield utils.pre_export_optional_cube_region(
bmesh.ops.create_cube(bm, size=(6.0)) self, "clickable_region",
bmesh.ops.transform(bm, matrix=mathutils.Matrix.Translation(bo.matrix_world.translation - rgn_obj.matrix_world.translation), f"{bo.name}_Journal_ClkRgn", 6.0, bo
space=rgn_obj.matrix_world, verts=bm.verts) )
rgn_obj.plasma_object.enabled = True
rgn_obj.hide_render = True
yield rgn_obj
else:
# Use the region provided
rgn_obj = self.clickable_region
# Generate the logic nodes # Generate the logic nodes
yield self.convert_logic(bo, age_name=exporter.age_name, rgn_obj=rgn_obj, version=version) yield self.convert_logic(bo, age_name=exporter.age_name, version=version)
def logicwiz(self, bo, tree, age_name, rgn_obj, version): def logicwiz(self, bo, tree, age_name, version):
# Assign journal script based on target version # Assign journal script based on target version
journal_pfm = journal_pfms[version] journal_pfm = journal_pfms[version]
journalnode = self._create_python_file_node(tree, journal_pfm["filename"], journal_pfm["attribs"]) journalnode = self._create_python_file_node(tree, journal_pfm["filename"], journal_pfm["attribs"])
if version <= pvPots: if version <= pvPots:
self._create_pots_nodes(bo, tree.nodes, journalnode, age_name, rgn_obj) self._create_pots_nodes(bo, tree.nodes, journalnode, age_name)
else: else:
self._create_moul_nodes(bo, tree.nodes, journalnode, age_name, rgn_obj) self._create_moul_nodes(bo, tree.nodes, journalnode, age_name)
def _create_pots_nodes(self, clickable_object, nodes, journalnode, age_name, rgn_obj): def _create_pots_nodes(self, clickable_object, nodes, journalnode, age_name):
clickable_region = nodes.new("PlasmaClickableRegionNode") clickable_region = nodes.new("PlasmaClickableRegionNode")
clickable_region.region_object = rgn_obj clickable_region.region_object = self.clickable_region
facing_object = nodes.new("PlasmaFacingTargetNode") facing_object = nodes.new("PlasmaFacingTargetNode")
facing_object.directional = False facing_object.directional = False
@ -281,9 +275,9 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
height.link_output(journalnode, "pfm", "BookHeight") height.link_output(journalnode, "pfm", "BookHeight")
height.value_float = self.book_scale_h / 100.0 height.value_float = self.book_scale_h / 100.0
def _create_moul_nodes(self, clickable_object, nodes, journalnode, age_name, rgn_obj): def _create_moul_nodes(self, clickable_object, nodes, journalnode, age_name):
clickable_region = nodes.new("PlasmaClickableRegionNode") clickable_region = nodes.new("PlasmaClickableRegionNode")
clickable_region.region_object = rgn_obj clickable_region.region_object = self.clickable_region
facing_object = nodes.new("PlasmaFacingTargetNode") facing_object = nodes.new("PlasmaFacingTargetNode")
facing_object.directional = False facing_object.directional = False
@ -471,33 +465,21 @@ class PlasmaLinkingBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
return return
# Auto-generate a six-foot cube region around the clickable if none was provided. # Auto-generate a six-foot cube region around the clickable if none was provided.
if self.clickable_region is None: yield utils.pre_export_optional_cube_region(
with utils.bmesh_object("{}_LinkingBook_ClkRgn".format(self.key_name)) as (rgn_obj, bm): self, "clickable_region",
bmesh.ops.create_cube(bm, size=(6.0)) f"{self.key_name}_LinkingBook_ClkRgn", 6.0,
rgn_offset = mathutils.Matrix.Translation(self.clickable.matrix_world.translation - rgn_obj.matrix_world.translation) self.clickable
bmesh.ops.transform(bm, matrix=rgn_offset, space=rgn_obj.matrix_world, verts=bm.verts) )
rgn_obj.plasma_object.enabled = True
rgn_obj.hide_render = True
yield rgn_obj
else:
rgn_obj = self.clickable_region
# Auto-generate a ten-foot cube region around the clickable if none was provided.
if self.shareable: if self.shareable:
if self.share_region is None: yield utils.pre_export_optional_cube_region(
with utils.bmesh_object("{}_LinkingBook_ShareRgn".format(self.key_name)) as (share_region, bm): self, "share_region",
# Generate a cube for the share region. f"{self.key_name}_LinkingBook_ShareRgn", 10.0,
bmesh.ops.create_cube(bm, size=(10.0)) self.clickable
share_region_offset = mathutils.Matrix.Translation(self.clickable.matrix_world.translation - share_region.matrix_world.translation) )
bmesh.ops.transform(bm, matrix=share_region_offset, space=share_region.matrix_world, verts=bm.verts)
share_region.plasma_object.enabled = True
share_region.hide_render = True
yield share_region
else:
share_region = self.share_region
else:
share_region = None
yield self.convert_logic(bo, age_name=exporter.age_name, version=exporter.mgr.getVer(), click_region=rgn_obj, share_region=share_region) yield self.convert_logic(bo, age_name=exporter.age_name, version=exporter.mgr.getVer())
def export(self, exporter, bo, so): def export(self, exporter, bo, so):
if self._check_version(pvPrime, pvPots): if self._check_version(pvPrime, pvPots):
@ -514,19 +496,19 @@ class PlasmaLinkingBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
if self.seek_point is not None: if self.seek_point is not None:
yield self.seek_point.name yield self.seek_point.name
def logicwiz(self, bo, tree, age_name, version, click_region, share_region): def logicwiz(self, bo, tree, age_name, version):
# Assign linking book script based on target version # Assign linking book script based on target version
linking_pfm = linking_pfms[version] linking_pfm = linking_pfms[version]
linkingnode = self._create_python_file_node(tree, linking_pfm["filename"], linking_pfm["attribs"]) linkingnode = self._create_python_file_node(tree, linking_pfm["filename"], linking_pfm["attribs"])
if version <= pvPots: if version <= pvPots:
self._create_pots_nodes(bo, tree.nodes, linkingnode, age_name, click_region) self._create_pots_nodes(bo, tree.nodes, linkingnode, age_name)
else: else:
self._create_moul_nodes(bo, tree.nodes, linkingnode, age_name, click_region, share_region) self._create_moul_nodes(bo, tree.nodes, linkingnode, age_name)
def _create_pots_nodes(self, clickable_object, nodes, linkingnode, age_name, clk_region): def _create_pots_nodes(self, clickable_object, nodes, linkingnode, age_name):
# Clickable # Clickable
clickable_region = nodes.new("PlasmaClickableRegionNode") clickable_region = nodes.new("PlasmaClickableRegionNode")
clickable_region.region_object = clk_region clickable_region.region_object = self.clickable_region
clickable = nodes.new("PlasmaClickableNode") clickable = nodes.new("PlasmaClickableNode")
clickable.clickable_object = self.clickable clickable.clickable_object = self.clickable
@ -601,10 +583,10 @@ class PlasmaLinkingBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
responder.link_output(responder_state, "state_refs", "resp") responder.link_output(responder_state, "state_refs", "resp")
responder.link_output(linkingnode, "keyref", "respOneShot") responder.link_output(linkingnode, "keyref", "respOneShot")
def _create_moul_nodes(self, clickable_object, nodes, linkingnode, age_name, clk_region, share_region): def _create_moul_nodes(self, clickable_object, nodes, linkingnode, age_name):
# Clickable # Clickable
clickable_region = nodes.new("PlasmaClickableRegionNode") clickable_region = nodes.new("PlasmaClickableRegionNode")
clickable_region.region_object = clk_region clickable_region.region_object = self.clickable_region
clickable = nodes.new("PlasmaClickableNode") clickable = nodes.new("PlasmaClickableNode")
clickable.clickable_object = self.clickable clickable.clickable_object = self.clickable
@ -657,10 +639,10 @@ class PlasmaLinkingBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
linking_panel_name.link_output(linkingnode, "pfm", "TargetAge") linking_panel_name.link_output(linkingnode, "pfm", "TargetAge")
# Share MSB # Share MSB
if share_region is not None: if self.shareable:
# Region # Region
share_msb_region = nodes.new("PlasmaVolumeSensorNode") share_msb_region = nodes.new("PlasmaVolumeSensorNode")
share_msb_region.region_object = share_region share_msb_region.region_object = self.share_region
for i in share_msb_region.inputs: for i in share_msb_region.inputs:
i.allow = True i.allow = True
share_msb_region.link_output(linkingnode, "satisfies", "shareRegion") share_msb_region.link_output(linkingnode, "satisfies", "shareRegion")

23
korman/properties/modifiers/logic.py

@ -158,22 +158,17 @@ class PlasmaTelescope(PlasmaModifierProperties, PlasmaModifierLogicWiz):
raise ExportError(f"'{self.id_data.name}': Telescopes must specify a camera!") raise ExportError(f"'{self.id_data.name}': Telescopes must specify a camera!")
def pre_export(self, exporter, bo): def pre_export(self, exporter, bo):
if self.clickable_region is None: # Generate a six-foot cube region if none was provided.
with utils.bmesh_object(f"{self.key_name}_Telescope_ClkRgn") as (rgn_obj, bm): yield utils.pre_export_optional_cube_region(
bmesh.ops.create_cube(bm, size=(6.0)) self, "clickable_region",
bmesh.ops.transform(bm, matrix=mathutils.Matrix.Translation(bo.matrix_world.translation - rgn_obj.matrix_world.translation), f"{self.key_name}_Telescope_ClkRgn", 6.0,
space=rgn_obj.matrix_world, verts=bm.verts) bo
rgn_obj.plasma_object.enabled = True )
rgn_obj.hide_render = True
yield rgn_obj
else:
# Use the region provided
rgn_obj = self.clickable_region
# Generate the logic nodes # Generate the logic nodes
yield self.convert_logic(bo, rgn_obj=rgn_obj) yield self.convert_logic(bo)
def logicwiz(self, bo, tree, rgn_obj): def logicwiz(self, bo, tree):
nodes = tree.nodes nodes = tree.nodes
# Create Python Node # Create Python Node
@ -188,7 +183,7 @@ class PlasmaTelescope(PlasmaModifierProperties, PlasmaModifierLogicWiz):
# Region # Region
telescoperegion = nodes.new("PlasmaClickableRegionNode") telescoperegion = nodes.new("PlasmaClickableRegionNode")
telescoperegion.region_object = rgn_obj telescoperegion.region_object = self.clickable_region
telescoperegion.link_output(telescopeclick, "satisfies", "region") telescoperegion.link_output(telescopeclick, "satisfies", "region")
# Telescope Camera # Telescope Camera

Loading…
Cancel
Save