# 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 from ..exporter import utils class PlasmaMeshOperator: @classmethod def poll(cls, context): return context.scene.render.engine == "PLASMA_GAME" and context.mode == "OBJECT" FLARE_MATERIAL_BASE_NAME = "FLAREGEN" class PlasmaAddFlareOperator(PlasmaMeshOperator, bpy.types.Operator): bl_idname = "mesh.plasma_flare_add" bl_label = "Add Lamp Flare" bl_category = "Plasma" bl_description = "Adds a new Plasma Lamp Flare" bl_options = {"REGISTER", "UNDO"} # Allows user to specify their own name stem flare_name = bpy.props.StringProperty(name="Name", description="Flare name stem", default="Flare", options=set()) flare_distance = bpy.props.FloatProperty(name="Distance", description="Flare's distance from the illuminating object", min=0.1, max=2.0, step=10, precision=1, default=1.0, options=set()) flare_material_name = bpy.props.StringProperty(name="Material", description="A specially-crafted material to use for this flare", default=FLARE_MATERIAL_BASE_NAME, options=set()) @classmethod def poll(cls, context): return super().poll(context) and context.mode == "OBJECT" def draw(self, context): layout = self.layout box = layout.box() box.label("Flare Name:") row = box.row() row.alert = not self.flare_name row.prop(self, "flare_name", text="") box = layout.box() box.label("Geometry:") row = box.row() row.prop(self, "flare_distance") box = layout.box() box.label("Material:") row = box.row() row.prop_search(self, "flare_material_name", bpy.data, "materials", text="") def execute(self, context): if context.space_data.local_view: bpy.ops.view3d.localview() self.create_flare_objects() return {"FINISHED"} def create_flare_objects(self): bpyscene = bpy.context.scene cursor_shift = mathutils.Matrix.Translation(bpy.context.scene.cursor_location) for obj in bpy.data.objects: obj.select = False # Create Empty, rotated to horizontal flare_root = bpy.data.objects.new("{}".format(self.name_stem), None) flare_root.empty_draw_type = "SINGLE_ARROW" flare_root.matrix_world = cursor_shift flare_root.rotation_euler[0] = math.radians(90) flare_root.plasma_object.enabled = True flare_root.select = True bpy.context.scene.objects.link(flare_root) bpyscene.objects.active = flare_root # Enable VFM on Empty flare_root.plasma_modifiers.viewfacemod.enabled = True flare_root.plasma_modifiers.viewfacemod.preset_options = "Sprite" # Create a textured 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.object 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") # Give the plane a basic UV unwrap, so that it's texture-ready bpy.ops.object.editmode_toggle() bpy.ops.uv.smart_project() bpy.ops.object.editmode_toggle() # Create AUTOGEN_FLARE material and texture, setting No-Z-Write auto_mat = self.find_create_material() flare_plane.data.materials.append(auto_mat) # Parent Plane to Empty flare_plane.parent = flare_root def find_create_material(self): # If the selected flare material exists, use it auto_mat = bpy.data.materials.get(self.flare_material_name, None) if auto_mat is None: # Generate a new flare material and texture auto_mat = bpy.data.materials.new(name=FLARE_MATERIAL_BASE_NAME) auto_mat.emit = 1.0 auto_mat.use_shadows = False auto_mat.use_cast_shadows = False self.flare_material_name = auto_mat.name auto_tex = bpy.data.textures.new(name=FLARE_MATERIAL_BASE_NAME, type="IMAGE") auto_tex.use_alpha = True auto_tex.plasma_layer.skip_depth_write = True auto_tex.plasma_layer.z_bias = True new_slot = auto_mat.texture_slots.add() new_slot.texture = auto_tex new_slot.blend_type = "ADD" new_slot.use_map_emit = True return auto_mat @property def name_stem(self): return self.flare_name if self.flare_name else "Flare" class PlasmaAddLadderMeshOperator(PlasmaMeshOperator, bpy.types.Operator): bl_idname = "mesh.plasma_ladder_add" bl_label = "Add Ladder" bl_category = "Plasma" bl_description = "Adds a new Plasma Ladder" bl_options = {"REGISTER", "UNDO"} # Allows user to specify their own name stem ladder_name = bpy.props.StringProperty(name="Name", description="Ladder name stem", default="Ladder", options=set()) # 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", options=set()) ladder_width = bpy.props.FloatProperty(name="Width", description="Width of ladder in inches", min=30, max=42, step=100, precision=0, default=30, options=set()) rung_height = bpy.props.FloatProperty(name="Rung height", description="Height of rungs in inches", min=1, max=6, step=100, precision=0, default=6, options=set()) # Template generation gen_back_guide = bpy.props.BoolProperty(name="Ladder", description="Generates helper object where ladder back should be placed", default=True, options=set()) gen_ground_guides = bpy.props.BoolProperty(name="Ground", description="Generates helper objects where ground should be placed", default=True, options=set()) gen_rung_guides = bpy.props.BoolProperty(name="Rungs", description="Generates helper objects where rungs should be placed", default=True, options=set()) 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", options=set()) # Game options has_upper_entry = bpy.props.BoolProperty(name="Has Upper Entry Point", description="Specifies whether the ladder has an upper entry", default=True, options=set()) 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, options=set()) has_lower_entry = bpy.props.BoolProperty(name="Has Lower Entry Point", description="Specifies whether the ladder has a lower entry", default=True, options=set()) 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, options=set()) def draw(self, context): layout = self.layout space = bpy.context.space_data if not space.local_view: box = layout.box() box.label("Ladder Name:") row = box.row() row.alert = not self.ladder_name row.prop(self, "ladder_name", text="") 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 context.space_data.local_view: bpy.ops.view3d.localview() self.create_ladder_objects() return {"FINISHED"} 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("{}_Rung_{}_{}".format(self.name_stem, side, rung_num)) rungs = bpy.data.objects.new("{}_Rung_{}_{}".format(self.name_stem, 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. name = "{}_Back".format(self.name_stem) mesh = bpy.data.meshes.new(name) back = bpy.data.objects.new(name, 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. name = "{}_Ground_{}".format(self.name_stem, pos) mesh = bpy.data.meshes.new(name) ground = bpy.data.objects.new(name, 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. name = "{}_Entry_Upper".format(self.name_stem) mesh = bpy.data.meshes.new(name) upper_rgn = bpy.data.objects.new(name, 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) laddermod = upper_rgn.plasma_modifiers.laddermod laddermod.enabled = True 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. name = "{}_Entry_Lower".format(self.name_stem) mesh = bpy.data.meshes.new(name) lower_rgn = bpy.data.objects.new(name, 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) laddermod = lower_rgn.plasma_modifiers.laddermod laddermod.enabled = True 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() @property def name_stem(self): return self.ladder_name if self.ladder_name else "Ladder" 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 class PlasmaAddLinkingBookMeshOperator(PlasmaMeshOperator, bpy.types.Operator): bl_idname = "mesh.plasma_linkingbook_add" bl_label = "Add Linking Book" bl_category = "Plasma" bl_description = "Adds a new Plasma Linking Book" bl_options = {"REGISTER", "UNDO"} anim_offsets = { "LinkOut": (0.07, 2.0, -3.6), "FishBookLinkOut": (0.8, 2.7, -1.84), } # Allows user to specify their own name stem panel_name = bpy.props.StringProperty(name="Name", description="Linking Book name stem", default="LinkingBook", options=set()) link_anim_type = bpy.props.EnumProperty(name="Link Animation", description="Type of Linking Animation to use", items=[("LinkOut", "Standing", "The avatar steps up to the book and places their hand on the panel"), ("FishBookLinkOut", "Kneeling", "The avatar kneels in front of the book and places their hand on the panel"),], default="LinkOut", options=set()) def draw(self, context): layout = self.layout space = bpy.context.space_data if not space.local_view: box = layout.box() box.label("Linking Book Name:") row = box.row() row.alert = not self.panel_name row.prop(self, "panel_name", text="") box.label("Options:") row = box.row() row.prop(self, "link_anim_type", text="Type") else: row = layout.row() row.label("Warning: Operator does not work in local view mode", icon="ERROR") def execute(self, context): if context.space_data.local_view: bpy.ops.view3d.localview() self.create_linkingbook_objects() return {"FINISHED"} def create_linkingbook_objects(self): bpyscene = bpy.context.scene cursor_shift = mathutils.Matrix.Translation(bpy.context.scene.cursor_location) for obj in bpy.data.objects: obj.select = False # Create Linking Panel empty panel_root = bpy.data.objects.new("{}".format(self.name_stem), None) bpy.context.scene.objects.link(panel_root) panel_root.empty_draw_type = "IMAGE" panel_root.empty_draw_size = 0.5 panel_root.empty_image_offset = (-0.5, -0.5) panel_root.matrix_world = cursor_shift panel_root.plasma_object.enabled = True # Create SeekPoint seek_point = bpy.data.objects.new("{}_SeekPoint".format(self.name_stem), None) bpy.context.scene.objects.link(seek_point) seek_point.show_name = True seek_point.empty_draw_type = "ARROWS" link_anim_offset = mathutils.Matrix.Translation(self.anim_offsets[self.link_anim_type]) seek_point.matrix_local = link_anim_offset seek_point.plasma_object.enabled = True # Create Clickable Region clk_rgn_name = "{}_ClkRegion".format(self.name_stem) clk_rgn_size = 6.0 clk_rgn = utils.BMeshObject(clk_rgn_name, False) with clk_rgn as bm: bmesh.ops.create_cube(bm, size=(1.0), matrix=(mathutils.Matrix.Scale(clk_rgn_size, 4))) clk_rgn.hide_render = True clk_rgn.plasma_object.enabled = True # Set the region back two feet, and align the bottom with the seek point z_off = clk_rgn_size / 2 + self.anim_offsets[self.link_anim_type][2] clk_rgn.matrix_local = mathutils.Matrix.Translation((0.0, 2.0, z_off)) # Parent Region and SeekPoint to Panel seek_point.parent = panel_root clk_rgn.parent = panel_root # Add Linking Book modifier bpyscene.objects.active = panel_root panel_root.select = True lbmod = panel_root.plasma_modifiers.linkingbookmod lbmod.enabled = True lbmod.clickable_region = clk_rgn.object lbmod.seek_point = seek_point lbmod.anim_type = self.link_anim_type @property def name_stem(self): return self.panel_name if self.panel_name else "LinkingBook" def register(): bpy.utils.register_module(__name__) def unregister(): bpy.utils.unregister_module(__name__)