Compare commits

..

6 Commits

Author SHA1 Message Date
Adam Johnson 29b03a5f08
Merge pull request #395 from Hoikas/entry_cam 10 months ago
Adam Johnson c873cf968b
Don't move the original object! 10 months ago
Adam Johnson e3d3c953df
Convert to xEntryCam.py 10 months ago
Adam Johnson 5085cf5814
Add box region helper for non-cube boxes. 10 months ago
Adam Johnson e8d3cb0e5e
Fix `_create_python_attribute` for non-"simple" types. 10 months ago
Adam Johnson 9363757d36
Add entry cameras to the Spawn Point Modifier. 10 months ago
  1. 52
      korman/exporter/utils.py
  2. 32
      korman/properties/modifiers/base.py
  3. 90
      korman/properties/modifiers/logic.py
  4. 19
      korman/ui/modifiers/logic.py

52
korman/exporter/utils.py

@ -20,6 +20,7 @@ import bpy
import mathutils
from contextlib import contextmanager
import enum
from typing import *
from PyHSPlasma import *
@ -122,21 +123,54 @@ def create_camera_object(name: str) -> bpy.types.Object:
bpy.context.scene.objects.link(cam_obj)
return cam_obj
def create_cube_region(name: str, size: float, owner_object: bpy.types.Object) -> bpy.types.Object:
class RegionOrigin(enum.Enum):
center = enum.auto()
bottom = enum.auto()
def create_cube_region(name: str, size: float, owner_object: bpy.types.Object, origin: RegionOrigin = RegionOrigin.center) -> bpy.types.Object:
"""Create a cube shaped region object"""
# Proxy over to the generic rectangular prism implementation to
# ensure that it is well tested.
return create_box_region(name, (size, size, size), owner_object, origin)
def create_box_region(
name: str, size: Union[Tuple[float, float, float], mathutils.Vector],
owner_object: bpy.types.Object,
origin: RegionOrigin = RegionOrigin.center
) -> bpy.types.Object:
"""Create a rectangular prism region object"""
if not isinstance(size, mathutils.Vector):
size = mathutils.Vector(size)
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
)
center = owner_object.matrix_world.translation
if origin == RegionOrigin.bottom:
center = center.copy()
center.z += size.z * 0.5
vert_src = [
(center.x + size.x * 0.5, center.y + size.y * 0.5, center.z + size.z * 0.5),
(center.x + size.x * 0.5, center.y + size.y * 0.5, center.z - size.z * 0.5),
(center.x + size.x * 0.5, center.y - size.y * 0.5, center.z + size.z * 0.5),
(center.x + size.x * 0.5, center.y - size.y * 0.5, center.z - size.z * 0.5),
(center.x - size.x * 0.5, center.y + size.y * 0.5, center.z + size.z * 0.5),
(center.x - size.x * 0.5, center.y + size.y * 0.5, center.z - size.z * 0.5),
(center.x - size.x * 0.5, center.y - size.y * 0.5, center.z + size.z * 0.5),
(center.x - size.x * 0.5, center.y - size.y * 0.5, center.z - size.z * 0.5),
]
verts = [bm.verts.new(i) for i in vert_src]
new_face = bm.faces.new
new_face((verts[0], verts[1], verts[3], verts[2])) # X+
new_face((verts[4], verts[5], verts[7], verts[6])) # X-
new_face((verts[0], verts[1], verts[5], verts[4])) # Y+
new_face((verts[2], verts[3], verts[7], verts[6])) # Y-
new_face((verts[0], verts[2], verts[6], verts[4])) # Z+
new_face((verts[1], verts[3], verts[7], verts[5])) # Z-
return region_object.release()
@contextmanager

32
korman/properties/modifiers/base.py

@ -13,12 +13,17 @@
# 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 bpy
from bpy.props import *
import abc
import itertools
from typing import Any, Dict, FrozenSet, Optional
from typing import *
if TYPE_CHECKING:
from ...nodes.node_python import *
from ... import helpers
@ -202,24 +207,23 @@ class PlasmaModifierLogicWiz:
pfm_node.update()
return pfm_node
def _create_python_attribute(self, pfm_node, attribute_name: str, attribute_type: Optional[str] = None, **kwargs):
def _create_python_attribute(self, pfm_node: PlasmaPythonFileNode, attribute_name: str, **kwargs):
"""Creates and links a Python Attribute Node to the Python File Node given by `pfm_node`.
This will automatically handle simple attribute types such as numbers and strings, however,
for object linkage, you should specify the optional `attribute_type` to ensure the proper
attribute type is found. For attribute nodes that require multiple values, the `value` may
be set to None and handled in your code."""
from ...nodes.node_python import PlasmaAttribute, PlasmaAttribNodeBase
if attribute_type is None:
assert len(kwargs) == 1 and "value" in kwargs, \
"In order to deduce the attribute_type, exactly one attribute value must be passed as a kw named `value`"
attribute_type = PlasmaAttribute.type_LUT.get(kwargs["value"].__class__)
For attribute nodes that require multiple values, the `value` may be set to None and
handled in your code."""
attribute_defn = next((i for i in pfm_node.attributes if i.attribute_name == attribute_name), None)
if attribute_defn is None:
raise KeyError(attribute_name)
from ...nodes.node_python import PlasmaAttribNodeBase
attribute_type = attribute_defn.attribute_type
node_cls = next((i for i in PlasmaAttribNodeBase.__subclasses__() if attribute_type in i.pl_attrib), None)
assert node_cls is not None, "'{}': Unable to find attribute node type for '{}' ('{}')".format(
self.id_data.name, attribute_name, attribute_type
)
assert node_cls is not None, \
f"'{self.id_data.name}': Unable to find attribute node type for '{attribute_name}' ('{attribute_type}')"
node = pfm_node.id_data.nodes.new(node_cls.bl_idname)
node.link_output(pfm_node, "pfm", attribute_name)
assert kwargs
for i, j in kwargs.items():
setattr(node, i, j)
return node

90
korman/properties/modifiers/logic.py

@ -13,16 +13,35 @@
# 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
from bpy.props import *
import mathutils
from PyHSPlasma import *
from typing import *
if TYPE_CHECKING:
from ...exporter import Exporter
from ...nodes.node_conditions import *
from ...nodes.node_messages import *
from ...nodes.node_responder import *
from ...addon_prefs import game_versions
from .base import PlasmaModifierProperties, PlasmaModifierLogicWiz
from ...exporter import ExportError, utils
from ... import idprops
from .physics import bounds_type_index, bounds_type_str, bounds_types
entry_cam_pfm = {
"filename": "xEntryCam.py",
"attribs": (
{ 'id': 1, 'type': "ptAttribActivator", 'name': "actRegionSensor" },
{ 'id': 2, 'type': "ptAttribSceneobject", 'name': "camera" },
{ 'id': 3, 'type': "ptAttribBoolean", 'name': "undoFirstPerson" },
)
}
class PlasmaVersionedNodeTree(idprops.IDPropMixin, bpy.types.PropertyGroup):
version = EnumProperty(name="Version",
@ -76,7 +95,7 @@ class PlasmaAdvancedLogic(PlasmaModifierProperties):
return any((i.node_tree.requires_actor for i in self.logic_groups if i.node_tree))
class PlasmaSpawnPoint(PlasmaModifierProperties):
class PlasmaSpawnPoint(PlasmaModifierProperties, PlasmaModifierLogicWiz):
pl_id = "spawnpoint"
bl_category = "Logic"
@ -84,10 +103,73 @@ class PlasmaSpawnPoint(PlasmaModifierProperties):
bl_description = "Point at which avatars link into the Age"
bl_object_types = {"EMPTY"}
def _get_bounds(self) -> int:
if self.exit_region is not None:
return bounds_type_index(self.exit_region.plasma_modifiers.collision.bounds_type)
return bounds_type_index("hull")
def _set_bounds(self, value: int) -> None:
if self.exit_region is not None:
self.exit_region.plasma_modifiers.collision.bounds_type = bounds_type_str(value)
entry_camera = PointerProperty(
name="Entry Camera",
description="Camera to use when the player spawns at this location",
type=bpy.types.Object,
poll=idprops.poll_camera_objects
)
exit_region = PointerProperty(
name="Exit Region",
description="Pop the camera when the player exits this region",
type=bpy.types.Object,
poll=idprops.poll_mesh_objects
)
bounds_type = EnumProperty(
name="Bounds",
description="",
items=bounds_types,
get=_get_bounds,
set=_set_bounds,
default="hull"
)
def pre_export(self, exporter: Exporter, bo: bpy.types.Object) -> None:
if self.entry_camera is None:
return
if self.exit_region is None:
self.exit_region = yield utils.create_box_region(
f"{self.key_name}_ExitRgn", (2.0, 2.0, 6.0),
bo, utils.RegionOrigin.bottom
)
yield self.convert_logic(bo)
def logicwiz(self, bo, tree):
pfm_node = self._create_python_file_node(
tree,
entry_cam_pfm["filename"],
entry_cam_pfm["attribs"]
)
volume_sensor: PlasmaVolumeSensorNode = tree.nodes.new("PlasmaVolumeSensorNode")
volume_sensor.find_input_socket("enter").allow = True
volume_sensor.find_input_socket("exit").allow = True
volume_sensor.region_object = self.exit_region
volume_sensor.bounds = self.bounds_type
volume_sensor.link_output(pfm_node, "satisfies", "actRegionSensor")
self._create_python_attribute(
pfm_node,
"camera",
target_object=self.entry_camera
)
def export(self, exporter, bo, so):
# Not much to this modifier... It's basically a flag that tells the engine, "hey, this is a
# place the avatar can show up." Nice to have a simple one to get started with.
spawn = exporter.mgr.add_object(pl=plSpawnModifier, so=so, name=self.key_name)
exporter.mgr.add_object(pl=plSpawnModifier, so=so, name=self.key_name)
@property
def requires_actor(self):

19
korman/ui/modifiers/logic.py

@ -13,8 +13,15 @@
# 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 bpy
from typing import *
if TYPE_CHECKING:
from ...properties.modifiers.logic import *
from .. import ui_list
class LogicListUI(bpy.types.UIList):
@ -36,8 +43,18 @@ def advanced_logic(modifier, layout, context):
layout.row().prop_menu_enum(logic, "version")
layout.prop(logic, "node_tree", icon="NODETREE")
def spawnpoint(modifier, layout, context):
def spawnpoint(modifier: PlasmaSpawnPoint, layout, context):
layout.label(text="Avatar faces negative Y.")
layout.separator()
col = layout.column()
col.prop(modifier, "entry_camera", icon="CAMERA_DATA")
sub = col.row()
sub.active = modifier.entry_camera is not None
sub.prop(modifier, "exit_region", icon="MESH_DATA")
sub = col.row()
sub.active = modifier.entry_camera is not None and modifier.exit_region is not None
sub.prop(modifier, "bounds_type")
def maintainersmarker(modifier, layout, context):
layout.label(text="Positive Y is North, positive Z is up.")

Loading…
Cancel
Save