diff --git a/korman/__init__.py b/korman/__init__.py
index 093df40..f7b0df8 100644
--- a/korman/__init__.py
+++ b/korman/__init__.py
@@ -41,6 +41,7 @@ def register():
nodes.register()
operators.register()
properties.register()
+ ui.register()
def unregister():
@@ -48,6 +49,7 @@ def unregister():
bpy.utils.unregister_module(__name__)
nodes.unregister()
operators.unregister()
+ ui.unregister()
if __name__ == "__main__":
diff --git a/korman/operators/__init__.py b/korman/operators/__init__.py
index ef4e5c4..94b352f 100644
--- a/korman/operators/__init__.py
+++ b/korman/operators/__init__.py
@@ -15,6 +15,7 @@
from . import op_export as exporter
from . import op_lightmap as lightmap
+from . import op_mesh as mesh
from . import op_modifier as modifier
from . import op_nodes as nodes
from . import op_sound as sound
diff --git a/korman/operators/op_mesh.py b/korman/operators/op_mesh.py
new file mode 100644
index 0000000..408f3ba
--- /dev/null
+++ b/korman/operators/op_mesh.py
@@ -0,0 +1,366 @@
+# 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 .
+
+import bpy
+import bmesh
+import math
+import mathutils
+
+class PlasmaMeshOperator:
+ @classmethod
+ def poll(cls, context):
+ return context.scene.render.engine == "PLASMA_GAME"
+
+
+class PlasmaAddLadderMeshOperator(PlasmaMeshOperator, bpy.types.Operator):
+ bl_idname = "mesh.plasma_ladder_add"
+ bl_label = "Ladder"
+ bl_category = "Plasma"
+ bl_description = "Adds a new Plasma Ladder"
+ bl_options = {"REGISTER", "UNDO"}
+
+ # Basic stats
+ ladder_height = bpy.props.FloatProperty(name="Height",
+ description="Height of ladder in feet",
+ min=6, max=1000, step=200, precision=0, default=6,
+ unit="LENGTH", subtype="DISTANCE")
+ ladder_width = bpy.props.FloatProperty(name="Width",
+ description="Width of ladder in inches",
+ min=30, max=42, step=100, precision=0, default=30)
+ rung_height = bpy.props.FloatProperty(name="Rung height",
+ description="Height of rungs in inches",
+ min=1, max=6, step=100, precision=0, default=6)
+ # Template generation
+ gen_back_guide = bpy.props.BoolProperty(name="Ladder",
+ description="Generates helper object where ladder back should be placed",
+ default=True)
+ gen_ground_guides = bpy.props.BoolProperty(name="Ground",
+ description="Generates helper objects where ground should be placed",
+ default=True)
+ gen_rung_guides = bpy.props.BoolProperty(name="Rungs",
+ description="Generates helper objects where rungs should be placed",
+ default=True)
+ rung_width_type = bpy.props.EnumProperty(name="Rung Width",
+ description="Type of rungs to generate",
+ items=[("FULL", "Full Width Rungs", "The rungs cross the entire width of the ladder"),
+ ("HALF", "Half Width Rungs", "The rungs only cross half the ladder's width, on the side where the avatar will contact them"),],
+ default="FULL")
+ # Game options
+ has_upper_entry = bpy.props.BoolProperty(name="Has Upper Entry Point",
+ description="Specifies whether the ladder has an upper entry",
+ default=True)
+ upper_entry_enabled = bpy.props.BoolProperty(name="Upper Entry Enabled",
+ description="Specifies whether the ladder's upper entry is enabled by default at Age start",
+ default=True)
+ has_lower_entry = bpy.props.BoolProperty(name="Has Lower Entry Point",
+ description="Specifies whether the ladder has a lower entry",
+ default=True)
+ lower_entry_enabled = bpy.props.BoolProperty(name="Lower Entry Enabled",
+ description="Specifies whether the ladder's lower entry is enabled by default at Age start",
+ default=True)
+
+ def draw(self, context):
+ layout = self.layout
+ space = bpy.context.space_data
+ if (not space.local_view):
+ box = layout.box()
+ box.label("Geometry:")
+
+ row = box.row()
+ row.alert = self.ladder_height % 2 != 0
+ row.prop(self, "ladder_height")
+ row = box.row()
+ row.prop(self, "ladder_width")
+ row = box.row()
+ row.prop(self, "rung_height")
+
+ box = layout.box()
+ box.label("Template Guides:")
+ col = box.column()
+ col.prop(self, "gen_back_guide")
+ col.prop(self, "gen_ground_guides")
+ col.prop(self, "gen_rung_guides")
+ if self.gen_rung_guides:
+ col.separator()
+ col.prop(self, "rung_width_type", text="")
+
+ box = layout.box()
+ row = box.row()
+ col = row.column()
+ col.label("Upper Entry:")
+ col.row().prop(self, "has_upper_entry", text="Create")
+ row = col.row()
+ row.enabled = self.has_upper_entry
+ row.prop(self, "upper_entry_enabled", text="Enabled")
+ col.separator()
+ col.label("Lower Entry:")
+ col.row().prop(self, "has_lower_entry", text="Create")
+ row = col.row()
+ row.enabled = self.has_lower_entry
+ row.prop(self, "lower_entry_enabled", text="Enabled")
+
+ else:
+ row = layout.row()
+ row.label("Warning: Operator does not work in local view mode", icon="ERROR")
+
+ def execute(self, context):
+ if bpy.context.mode == "OBJECT":
+ self.create_ladder_objects()
+ return {"FINISHED"}
+ else:
+ self.report({"WARNING"}, "Ladder creation only valid in Object mode")
+ return {"CANCELLED"}
+
+
+ def create_guide_rungs(self):
+ bpyscene = bpy.context.scene
+ cursor_shift = mathutils.Matrix.Translation(bpy.context.scene.cursor_location)
+
+ rung_height_ft = self.rung_height / 12
+ rung_width_ft = self.ladder_width / 12
+
+ if self.rung_width_type == "FULL":
+ rung_width = rung_width_ft
+ rung_yoffset = 0.0
+ else:
+ rung_width = rung_width_ft / 2
+ rung_yoffset = rung_width_ft / 4
+
+ rungs_scale = mathutils.Matrix(
+ ((0.5, 0.0, 0.0),
+ (0.0, rung_width, 0.0),
+ (0.0, 0.0, rung_height_ft)))
+
+ for rung_num in range(0, int(self.ladder_height)):
+ side = "L" if (rung_num % 2) == 0 else "R"
+
+ mesh = bpy.data.meshes.new("LadderRung_{}_{}".format(side, rung_num))
+ rungs = bpy.data.objects.new("LadderRung_{}_{}".format(side, rung_num), mesh)
+ rungs.hide_render = True
+ rungs.draw_type = "BOUNDS"
+
+ bpyscene.objects.link(rungs)
+ bpyscene.objects.active = rungs
+ rungs.select = True
+
+ bm = bmesh.new()
+ bmesh.ops.create_cube(bm, size=(1.0), matrix=rungs_scale)
+
+ # Move each rung up, based on:
+ # its place in the array, aligned to the top of the rung position, shifted up to start at the ladder's base
+ if (rung_num % 2) == 0:
+ rung_pos = mathutils.Matrix.Translation((0.5, -rung_yoffset, rung_num + (1.0 - rung_height_ft) + (rung_height_ft / 2)))
+ else:
+ rung_pos = mathutils.Matrix.Translation((0.5, rung_yoffset, rung_num + (1.0 - rung_height_ft) + (rung_height_ft / 2)))
+ bmesh.ops.transform(bm, matrix=cursor_shift, space=rungs.matrix_world, verts=bm.verts)
+ bmesh.ops.transform(bm, matrix=rung_pos, space=rungs.matrix_world, verts=bm.verts)
+ bm.to_mesh(mesh)
+ bm.free()
+
+ def create_guide_back(self):
+ bpyscene = bpy.context.scene
+ cursor_shift = mathutils.Matrix.Translation(bpy.context.scene.cursor_location)
+
+ # Create an empty mesh and the object.
+ mesh = bpy.data.meshes.new("LadderBack")
+ back = bpy.data.objects.new("LadderBack", mesh)
+ back.hide_render = True
+ back.draw_type = "BOUNDS"
+
+ # Add the object into the scene.
+ bpyscene.objects.link(back)
+ bpyscene.objects.active = back
+ back.select = True
+
+ # Construct the bmesh and assign it to the blender mesh.
+ bm = bmesh.new()
+ ladder_scale = mathutils.Matrix(
+ ((0.5, 0.0, 0.0),
+ (0.0, self.ladder_width / 12, 0.0),
+ (0.0, 0.0, self.ladder_height)))
+ bmesh.ops.create_cube(bm, size=(1.0), matrix=ladder_scale)
+
+ # Shift the ladder up so that its base is at the 3D cursor
+ back_pos = mathutils.Matrix.Translation((0.0, 0.0, self.ladder_height / 2))
+ bmesh.ops.transform(bm, matrix=cursor_shift, space=back.matrix_world, verts=bm.verts)
+ bmesh.ops.transform(bm, matrix=back_pos, space=back.matrix_world, verts=bm.verts)
+ bm.to_mesh(mesh)
+ bm.free()
+
+ def create_guide_ground(self):
+ bpyscene = bpy.context.scene
+ cursor_shift = mathutils.Matrix.Translation(bpy.context.scene.cursor_location)
+
+ for pos in ("Upper", "Lower"):
+ # Create an empty mesh and the object.
+ mesh = bpy.data.meshes.new("LadderGround_{}".format(pos))
+ ground = bpy.data.objects.new("LadderGround_{}".format(pos), mesh)
+ ground.hide_render = True
+ ground.draw_type = "BOUNDS"
+
+ # Add the object into the scene.
+ bpyscene.objects.link(ground)
+ bpyscene.objects.active = ground
+ ground.select = True
+
+ # Construct the bmesh and assign it to the blender mesh.
+ bm = bmesh.new()
+ ground_depth = 3.0
+ ground_scale = mathutils.Matrix(
+ ((ground_depth, 0.0, 0.0),
+ (0.0, self.ladder_width / 12, 0.0),
+ (0.0, 0.0, 0.5)))
+ bmesh.ops.create_cube(bm, size=(1.0), matrix=ground_scale)
+
+ if pos == "Upper":
+ ground_pos = mathutils.Matrix.Translation((-(ground_depth / 2) + 0.25, 0.0, self.ladder_height + 0.25))
+ else:
+ ground_pos = mathutils.Matrix.Translation(((ground_depth / 2) + 0.25, 0.0, 0.25))
+ bmesh.ops.transform(bm, matrix=cursor_shift, space=ground.matrix_world, verts=bm.verts)
+ bmesh.ops.transform(bm, matrix=ground_pos, space=ground.matrix_world, verts=bm.verts)
+ bm.to_mesh(mesh)
+ bm.free()
+
+ def create_upper_entry(self):
+ bpyscene = bpy.context.scene
+ cursor_shift = mathutils.Matrix.Translation(bpy.context.scene.cursor_location)
+
+ # Create an empty mesh and the object.
+ mesh = bpy.data.meshes.new("LadderEntry_Upper")
+ upper_rgn = bpy.data.objects.new("LadderEntry_Upper", mesh)
+ upper_rgn.hide_render = True
+ upper_rgn.draw_type = "WIRE"
+
+ # Add the object into the scene.
+ bpyscene.objects.link(upper_rgn)
+ bpyscene.objects.active = upper_rgn
+ upper_rgn.select = True
+ upper_rgn.plasma_object.enabled = True
+
+ # Construct the bmesh and assign it to the blender mesh.
+ bm = bmesh.new()
+ rgn_scale = mathutils.Matrix(
+ ((self.ladder_width / 12, 0.0, 0.0),
+ (0.0, 2.5, 0.0),
+ (0.0, 0.0, 2.0)))
+ bmesh.ops.create_cube(bm, size=(1.0), matrix=rgn_scale)
+
+ rgn_pos = mathutils.Matrix.Translation((-1.80, 0.0, 1.5 + self.ladder_height))
+ bmesh.ops.transform(bm, matrix=cursor_shift, space=upper_rgn.matrix_world, verts=bm.verts)
+ bmesh.ops.transform(bm, matrix=rgn_pos, space=upper_rgn.matrix_world, verts=bm.verts)
+
+ bm.to_mesh(mesh)
+ bm.free()
+
+ origin_to_bottom(upper_rgn)
+ upper_rgn.rotation_euler[2] = math.radians(90.0)
+
+ bpy.ops.object.plasma_modifier_add(types="laddermod")
+ laddermod = upper_rgn.plasma_modifiers.laddermod
+ laddermod.is_enabled = self.lower_entry_enabled
+ laddermod.num_loops = (self.ladder_height - 6) / 2
+ laddermod.direction = "DOWN"
+
+ def create_lower_entry(self):
+ bpyscene = bpy.context.scene
+ cursor_shift = mathutils.Matrix.Translation(bpy.context.scene.cursor_location)
+
+ # Create an empty mesh and the object.
+ mesh = bpy.data.meshes.new("LadderEntry_Lower")
+ lower_rgn = bpy.data.objects.new("LadderEntry_Lower", mesh)
+ lower_rgn.hide_render = True
+ lower_rgn.draw_type = "WIRE"
+
+ # Add the object into the scene.
+ bpyscene.objects.link(lower_rgn)
+ bpyscene.objects.active = lower_rgn
+ lower_rgn.select = True
+ lower_rgn.plasma_object.enabled = True
+
+ # Construct the bmesh and assign it to the blender mesh.
+ bm = bmesh.new()
+ rgn_scale = mathutils.Matrix(
+ ((self.ladder_width / 12, 0.0, 0.0),
+ (0.0, 2.5, 0.0),
+ (0.0, 0.0, 2.0)))
+ bmesh.ops.create_cube(bm, size=(1.0), matrix=rgn_scale)
+
+ rgn_pos = mathutils.Matrix.Translation((2.70, 0.0, 1.5))
+ bmesh.ops.transform(bm, matrix=cursor_shift, space=lower_rgn.matrix_world, verts=bm.verts)
+ bmesh.ops.transform(bm, matrix=rgn_pos, space=lower_rgn.matrix_world, verts=bm.verts)
+
+ bm.to_mesh(mesh)
+ bm.free()
+
+ origin_to_bottom(lower_rgn)
+ lower_rgn.rotation_euler[2] = math.radians(-90.0)
+
+ bpy.ops.object.plasma_modifier_add(types="laddermod")
+ laddermod = lower_rgn.plasma_modifiers.laddermod
+ laddermod.is_enabled = self.lower_entry_enabled
+ laddermod.num_loops = (self.ladder_height - 6) / 2
+ laddermod.direction = "UP"
+
+ def create_ladder_objects(self):
+ for obj in bpy.data.objects:
+ obj.select = False
+
+ if self.gen_rung_guides:
+ self.create_guide_rungs()
+ if self.gen_back_guide:
+ self.create_guide_back()
+ if self.gen_ground_guides:
+ self.create_guide_ground()
+
+ bpy.ops.object.origin_set(type="ORIGIN_CENTER_OF_MASS")
+
+ if self.has_upper_entry:
+ self.create_upper_entry()
+ if self.has_lower_entry:
+ self.create_lower_entry()
+
+ bpy.ops.group.create(name="LadderGroup")
+ bpy.ops.group.objects_add_active()
+
+
+def origin_to_bottom(obj):
+ # Modified from https://blender.stackexchange.com/a/42110/3055
+ mw = obj.matrix_world
+ local_verts = [mathutils.Vector(v[:]) for v in obj.bound_box]
+ x, y, z = 0, 0, 0
+
+ l = len(local_verts)
+ y = sum((v.y for v in local_verts)) / l
+ x = sum((v.x for v in local_verts)) / l
+ z = min((v.z for v in local_verts))
+
+ local_origin = mathutils.Vector((x, y, z))
+ global_origin = mw * local_origin
+
+ bm = bmesh.new()
+ bm.from_mesh(obj.data)
+
+ for v in bm.verts:
+ v.co = v.co - local_origin
+
+ bm.to_mesh(obj.data)
+ mw.translation = global_origin
+
+
+def register():
+ bpy.utils.register_module(__name__)
+
+def unregister():
+ bpy.utils.unregister_module(__name__)
diff --git a/korman/ui/__init__.py b/korman/ui/__init__.py
index 3a865c4..c334f8a 100644
--- a/korman/ui/__init__.py
+++ b/korman/ui/__init__.py
@@ -14,8 +14,16 @@
# along with Korman. If not, see .
from .ui_lamp import *
+from .ui_menus import *
from .ui_modifiers import *
from .ui_object import *
from .ui_texture import *
from .ui_toolbox import *
from .ui_world import *
+
+
+def register():
+ ui_menus.register()
+
+def unregister():
+ ui_menus.unregister()
diff --git a/korman/ui/ui_menus.py b/korman/ui/ui_menus.py
new file mode 100644
index 0000000..1f51912
--- /dev/null
+++ b/korman/ui/ui_menus.py
@@ -0,0 +1,45 @@
+# 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 .
+
+from ..operators.op_mesh import *
+
+class PlasmaMenu:
+ @classmethod
+ def poll(cls, context):
+ return context.scene.render.engine == "PLASMA_GAME"
+
+
+class PlasmaAddMenu(PlasmaMenu, bpy.types.Menu):
+ bl_idname = "menu.plasma_add"
+ bl_label = "Plasma"
+ bl_description = "Add a Plasma premade"
+
+ def draw(self, context):
+ layout = self.layout
+
+ layout.operator("mesh.plasma_ladder_add", text="Ladder", icon="COLLAPSEMENU")
+
+
+def build_plasma_menu(self, context):
+ if context.scene.render.engine != "PLASMA_GAME":
+ return
+ self.layout.separator()
+ self.layout.menu("menu.plasma_add", icon="URL")
+
+def register():
+ bpy.types.INFO_MT_add.append(build_plasma_menu)
+
+def unregister():
+ bpy.types.INFO_MT_add.remove(build_plasma_menu)