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 1 year 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
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)
def _(temporary, parent):

105
korman/exporter/utils.py

@ -13,12 +13,14 @@
# You should have received a copy of the GNU General Public License
# along with Korman. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import bmesh
import bpy
import mathutils
from typing import Callable, Iterator, Tuple
from contextlib import contextmanager
from typing import *
from PyHSPlasma import *
@ -54,46 +56,73 @@ def quaternion(blquat):
return hsQuat(blquat.x, blquat.y, blquat.z, blquat.w)
@contextmanager
def bmesh_temporary_object(name : str, factory : Callable, page_name : str=None):
"""Creates a temporary object and mesh that exists only for the duration of
the context"""
mesh = bpy.data.meshes.new(name)
obj = bpy.data.objects.new(name, mesh)
obj.draw_type = "WIRE"
if page_name is not None:
obj.plasma_object.page = page_name
bpy.context.scene.objects.link(obj)
bm = bmesh.new()
try:
factory(bm)
bm.to_mesh(mesh)
yield obj
finally:
bm.free()
bpy.context.scene.objects.unlink(obj)
class BMeshObject:
def __init__(self, name: str, managed: bool = True):
self._managed = managed
self._bmesh = None
self._mesh = bpy.data.meshes.new(name)
self._obj = bpy.data.objects.new(name, self._mesh)
self._obj.draw_type = "WIRE"
bpy.context.scene.objects.link(self._obj)
def __del__(self):
if self._managed:
bpy.context.scene.objects.unlink(self._obj)
bpy.data.meshes.remove(self._mesh)
def __enter__(self) -> bmesh.types.BMesh:
if self._mesh is not None:
self._bmesh = bmesh.new()
self._bmesh.from_mesh(self._mesh)
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
def bmesh_object(name: str) -> Iterator[Tuple[bpy.types.Object, bmesh.types.BMesh]]:
"""Creates an object and mesh that will be removed if the context is exited
due to an error"""
mesh = bpy.data.meshes.new(name)
obj = bpy.data.objects.new(name, mesh)
obj.draw_type = "WIRE"
bpy.context.scene.objects.link(obj)
bm = bmesh.new()
try:
yield obj, bm
except:
bpy.context.scene.objects.unlink(obj)
bpy.data.meshes.remove(mesh)
raise
def pre_export_optional_cube_region(source, attr: str, name: str, size: float, owner_object: bpy.types.Object) -> Optional[bpy.types.Object]:
if getattr(source, attr) is None:
region_object = create_cube_region(name, size, owner_object)
setattr(source, attr, region_object)
try:
yield region_object
finally:
source.property_unset(attr)
else:
bm.to_mesh(mesh)
finally:
bm.free()
# contextlib.contextmanager requires for us to yield. Sad.
yield
@contextmanager
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"
# Create a textured Plane
with utils.bmesh_object("{}_Visible".format(self.name_stem)) as (flare_plane, bm):
flare_plane.hide_render = True
flare_plane.plasma_object.enabled = True
bpyscene.objects.active = flare_plane
flare_plane = utils.BMeshObject(f"{self.name_stem}_Visible", managed=False)
flare_plane.hide_render = True
flare_plane.plasma_object.enabled = True
bpyscene.objects.active = flare_plane
with flare_plane as bm:
# 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.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
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:
# We aren't needed here
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
if self.clickable_region is None:
with utils.bmesh_object("{}_Journal_ClkRgn".format(self.key_name)) as (rgn_obj, bm):
bmesh.ops.create_cube(bm, size=(6.0))
bmesh.ops.transform(bm, matrix=mathutils.Matrix.Translation(bo.matrix_world.translation - rgn_obj.matrix_world.translation),
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 clickable region if it was not provided
yield utils.pre_export_optional_cube_region(
self, "clickable_region",
f"{bo.name}_Journal_ClkRgn", 6.0, bo
)
# 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
journal_pfm = journal_pfms[version]
journalnode = self._create_python_file_node(tree, journal_pfm["filename"], journal_pfm["attribs"])
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:
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.region_object = rgn_obj
clickable_region.region_object = self.clickable_region
facing_object = nodes.new("PlasmaFacingTargetNode")
facing_object.directional = False
@ -281,9 +275,9 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
height.link_output(journalnode, "pfm", "BookHeight")
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.region_object = rgn_obj
clickable_region.region_object = self.clickable_region
facing_object = nodes.new("PlasmaFacingTargetNode")
facing_object.directional = False
@ -471,33 +465,21 @@ class PlasmaLinkingBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
return
# Auto-generate a six-foot cube region around the clickable if none was provided.
if self.clickable_region is None:
with utils.bmesh_object("{}_LinkingBook_ClkRgn".format(self.key_name)) as (rgn_obj, bm):
bmesh.ops.create_cube(bm, size=(6.0))
rgn_offset = mathutils.Matrix.Translation(self.clickable.matrix_world.translation - rgn_obj.matrix_world.translation)
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
yield utils.pre_export_optional_cube_region(
self, "clickable_region",
f"{self.key_name}_LinkingBook_ClkRgn", 6.0,
self.clickable
)
# Auto-generate a ten-foot cube region around the clickable if none was provided.
if self.shareable:
if self.share_region is None:
with utils.bmesh_object("{}_LinkingBook_ShareRgn".format(self.key_name)) as (share_region, bm):
# Generate a cube for the share region.
bmesh.ops.create_cube(bm, size=(10.0))
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 utils.pre_export_optional_cube_region(
self, "share_region",
f"{self.key_name}_LinkingBook_ShareRgn", 10.0,
self.clickable
)
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):
if self._check_version(pvPrime, pvPots):
@ -514,19 +496,19 @@ class PlasmaLinkingBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
if self.seek_point is not None:
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
linking_pfm = linking_pfms[version]
linkingnode = self._create_python_file_node(tree, linking_pfm["filename"], linking_pfm["attribs"])
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:
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_region = nodes.new("PlasmaClickableRegionNode")
clickable_region.region_object = clk_region
clickable_region.region_object = self.clickable_region
clickable = nodes.new("PlasmaClickableNode")
clickable.clickable_object = self.clickable
@ -601,10 +583,10 @@ class PlasmaLinkingBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
responder.link_output(responder_state, "state_refs", "resp")
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_region = nodes.new("PlasmaClickableRegionNode")
clickable_region.region_object = clk_region
clickable_region.region_object = self.clickable_region
clickable = nodes.new("PlasmaClickableNode")
clickable.clickable_object = self.clickable
@ -657,10 +639,10 @@ class PlasmaLinkingBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
linking_panel_name.link_output(linkingnode, "pfm", "TargetAge")
# Share MSB
if share_region is not None:
if self.shareable:
# Region
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:
i.allow = True
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!")
def pre_export(self, exporter, bo):
if self.clickable_region is None:
with utils.bmesh_object(f"{self.key_name}_Telescope_ClkRgn") as (rgn_obj, bm):
bmesh.ops.create_cube(bm, size=(6.0))
bmesh.ops.transform(bm, matrix=mathutils.Matrix.Translation(bo.matrix_world.translation - rgn_obj.matrix_world.translation),
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 a six-foot cube region if none was provided.
yield utils.pre_export_optional_cube_region(
self, "clickable_region",
f"{self.key_name}_Telescope_ClkRgn", 6.0,
bo
)
# 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
# Create Python Node
@ -188,7 +183,7 @@ class PlasmaTelescope(PlasmaModifierProperties, PlasmaModifierLogicWiz):
# Region
telescoperegion = nodes.new("PlasmaClickableRegionNode")
telescoperegion.region_object = rgn_obj
telescoperegion.region_object = self.clickable_region
telescoperegion.link_output(telescopeclick, "satisfies", "region")
# Telescope Camera

Loading…
Cancel
Save