You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
288 lines
11 KiB
288 lines
11 KiB
#!/usr/bin/env python
|
|
"""
|
|
collisionbox Lua generator
|
|
1. Select a mob mesh or an Empty
|
|
2. Press the "Run Script" button below
|
|
- The script copies the collisionbox the the clipboard.
|
|
- An 'Empty' with the object's name will appear,
|
|
which visually represents the collisionbox
|
|
(which MUST be symmetrical on horizontal axes and
|
|
centered at 0,0,0 for Minetest, since
|
|
collision boxes to not turn).
|
|
3. To adjust, scale the Empty.collisionbox.* object
|
|
in Blender then repeat steps 1-2. To keep the Empty
|
|
symmetrical for Minetest, scale ONLY with one
|
|
of the following hotkey sequences (with
|
|
the mouse pointer in the 3D View):
|
|
- 's'
|
|
- 's', 'z'
|
|
- 's', "shift z"
|
|
|
|
How to use: Paste this script into a Blender"
|
|
Text Editor panel, select an object,"
|
|
press the 'Run Script' button")
|
|
"""
|
|
# from mathutils import Matrix
|
|
from mathutils import Vector
|
|
# from mathutils import Euler
|
|
print("""
|
|
|
|
STARTING generate_lua_collisionbox...
|
|
""")
|
|
y_up = True
|
|
enable_minetest = True
|
|
enable_lowest_h = False
|
|
enable_center_h = False
|
|
if enable_minetest:
|
|
enable_lowest_h = True
|
|
enable_center_h = True
|
|
|
|
hs = (0, 1) # horizontal axis indices
|
|
v = 2 # vertical axis index
|
|
# Do NOT swap until end.
|
|
# if y_up:
|
|
# hs = (0, 2)
|
|
# v = 1
|
|
try:
|
|
import bpy
|
|
except ImportError:
|
|
print(__doc__)
|
|
exit(1)
|
|
|
|
|
|
def calculate_one():
|
|
ob1 = None
|
|
ob1 = bpy.context.active_object # works with 2.7 or 2.8
|
|
calculate_collisionbox(ob1)
|
|
|
|
|
|
class MessageBox(bpy.types.Operator):
|
|
bl_idname = "message.messagebox"
|
|
bl_label = ""
|
|
message = bpy.props.StringProperty(
|
|
name="message",
|
|
description="message",
|
|
default=''
|
|
)
|
|
|
|
def execute(self, context):
|
|
self.report({'INFO'}, self.message)
|
|
# print(self.message)
|
|
return {'FINISHED'}
|
|
|
|
def invoke(self, context, event):
|
|
return context.window_manager.invoke_props_dialog(self,
|
|
width=400)
|
|
|
|
def draw(self, context):
|
|
self.layout.label(text=self.message)
|
|
self.layout.label(text="")
|
|
# col = self.layout.column(align = True)
|
|
# col.prop(context.scene, "my_string_prop")
|
|
|
|
|
|
bpy.utils.register_class(MessageBox)
|
|
|
|
msgSuffix = ""
|
|
|
|
|
|
def calculate_collisionbox(ob1):
|
|
global msgSuffix
|
|
mesh = None
|
|
if ob1 is None:
|
|
if len(bpy.context.selected_objects) > 0:
|
|
ob1 = bpy.context.selected_objects[0]
|
|
if ob1 is not None:
|
|
mesh = ob1.data
|
|
if ob1 is None:
|
|
msg = "Nothing is selected."
|
|
bpy.ops.message.messagebox('INVOKE_DEFAULT', message=msg)
|
|
elif (mesh is not None) and (not hasattr(mesh, 'vertices')):
|
|
msg = ("A collision box cannot be calculated for a non-mesh"
|
|
" object.")
|
|
bpy.ops.message.messagebox('INVOKE_DEFAULT', message=msg)
|
|
else:
|
|
# extents1 = ob1.dimensions.copy()
|
|
obj1Loc = ob1.location
|
|
|
|
# See <https://blender.stackexchange.com/questions/8459/get-
|
|
# blender-x-y-z-and-bounding-box-with-script>
|
|
# bbox_corners = [(ob1.matrix_world
|
|
# * Vector(corner)) for corner in ob1.bound_box]
|
|
|
|
# See <https://blender.stackexchange.com/questions/6139/
|
|
# how-to-iterate-through-all-vertices-of-an-object-that-
|
|
# contains-multiple-meshes>
|
|
# print("mesh:" + str(mesh))
|
|
# print("hasattr(mesh, 'vertices'):"
|
|
# + str(hasattr(mesh, 'vertices')))]
|
|
mins = [None, None, None] # minimums; in outer scope for check
|
|
maxes = [None, None, None] # minimums; in outer scope for check
|
|
if mesh is not None:
|
|
wm = ob1.matrix_world
|
|
for vert in mesh.vertices:
|
|
# This matrix multiplication is NOT transitive.
|
|
try:
|
|
loc = wm @ vert.co
|
|
except TypeError:
|
|
loc = wm * vert.co # Blender <2.8
|
|
# NOTE: swap y and z for Minetest (y-up) LATER
|
|
coords = (loc.x, loc.y, loc.z)
|
|
for i in range(3):
|
|
if (mins[i] is None) or (coords[i] < mins[i]):
|
|
mins[i] = coords[i]
|
|
if (maxes[i] is None) or (coords[i] > maxes[i]):
|
|
maxes[i] = coords[i]
|
|
# print(str(extents1))
|
|
# print("--by vertices (raw):")
|
|
# print(" collisionbox = {{{:.2f}, {:.2f}, {:.2f},"
|
|
# " {:.2f}, {:.2f}, {:.2f}}}".format(mins[0], mins[2],
|
|
# mins[1], maxes[0],
|
|
# maxes[2], maxes[1]))
|
|
|
|
# Use ob1.matrix_world (above) instead of incrementing
|
|
# ob1.location.x, y, and z
|
|
|
|
# newNamePrefix = "Empty.EDGE." + ob1.name
|
|
# i = 0
|
|
# wm = ob1.matrix_world
|
|
# for vert in mesh.vertices:
|
|
# newName = newNamePrefix + "." + str(i)
|
|
# # This matrix multiplication is NOT transitive.
|
|
# try:
|
|
# loc = wm @ vert.co
|
|
# except TypeError:
|
|
# loc = wm * vert.co # Blender <2.8
|
|
# isFar = False
|
|
# if (loc.x == maxes[0] or loc.y == maxes[1] or
|
|
# loc.z == maxes[2]):
|
|
# isFar = True
|
|
# elif (loc.x == mins[0] or loc.y == mins[1] or
|
|
# loc.z == mins[2]):
|
|
# isFar = True
|
|
# if isFar:
|
|
# pass
|
|
# # result = bpy.ops.object.add(type='EMPTY', radius=.25,
|
|
# # location=loc)
|
|
# # NOTE: result is merely {'FINISHED'}
|
|
# # print("{:.2f}, {:.2f}, {:.2f}".format(loc.x, loc.y,
|
|
# # loc.z))
|
|
|
|
# bpy.ops.object.add_named(name=newName, type='EMPTY',
|
|
# radius=.25, location=loc)
|
|
# i += 1
|
|
else:
|
|
extents1 = ob1.scale.copy()
|
|
print(dir(ob1))
|
|
# Object is an empty, so scale up for Minetest
|
|
try:
|
|
extents1.x = extents1.x * (ob1.empty_display_size * 2.0)
|
|
extents1.y = extents1.y * (ob1.empty_display_size * 2.0)
|
|
extents1.z = extents1.z * (ob1.empty_display_size * 2.0)
|
|
except AttributeError:
|
|
# Blender 2.7
|
|
extents1.x = extents1.x * (ob1.empty_draw_size * 2.0)
|
|
extents1.y = extents1.y * (ob1.empty_draw_size * 2.0)
|
|
extents1.z = extents1.z * (ob1.empty_draw_size * 2.0)
|
|
mins[0] = obj1Loc.x - extents1.x / 2.0
|
|
maxes[0] = obj1Loc.x + extents1.x / 2.0
|
|
mins[1] = obj1Loc.y - extents1.y / 2.0
|
|
maxes[1] = obj1Loc.y + extents1.y / 2.0
|
|
mins[2] = obj1Loc.z - extents1.z / 2.0
|
|
maxes[2] = obj1Loc.z + extents1.z / 2.0
|
|
msgSuffix = " (using Empty object's scale)"
|
|
# print("--using empty object:")
|
|
|
|
# use ground as bottom (don't do this--it is not the Minetest way)
|
|
# if mins[2] < 0.0:
|
|
# maxes[1] -= mins[1]
|
|
# mins[1] = 0.0
|
|
|
|
# print(" collisionbox = {{{:.2f}, {:.2f}, {:.2f}, {:.2f},"
|
|
# " {:.2f}, {:.2f}}}".format(mins[0], mins[1], mins[2],
|
|
# maxes[0], maxes[1],
|
|
# maxes[2]))
|
|
sizes = [None, None, None]
|
|
centers = [None, None, None]
|
|
for i in range(3):
|
|
sizes[i] = maxes[i] - mins[i]
|
|
centers[i] = mins[i] + sizes[i] / 2.0
|
|
|
|
if enable_lowest_h:
|
|
# OK to use z as up, since will y&z will be swapped if y_up
|
|
hSize = None
|
|
for i in range(len(hs)):
|
|
axis_i = hs[i]
|
|
if (hSize is None) or (sizes[axis_i] < hSize):
|
|
hSize = sizes[axis_i]
|
|
for i in range(len(hs)):
|
|
axis_i = hs[i]
|
|
sizes[axis_i] = hSize
|
|
mins[axis_i] = centers[axis_i] - hSize / 2
|
|
maxes[axis_i] = mins[axis_i] + hSize
|
|
|
|
if enable_center_h:
|
|
for i in range(len(hs)):
|
|
axis_i = hs[i]
|
|
centers[i] = 0
|
|
mins[axis_i] = centers[axis_i] - sizes[axis_i] / 2
|
|
maxes[axis_i] = mins[axis_i] + sizes[axis_i]
|
|
|
|
loc = (centers[0], centers[1], centers[2])
|
|
bpy.ops.object.add(type='EMPTY', radius=.5, location=loc)
|
|
collisionboxName = "Empty.collisionbox." + ob1.name
|
|
newEmpty = bpy.context.object # works with 2.7 or 2.8
|
|
# newEmpty = bpy.context.selected_objects[0]
|
|
try:
|
|
pass
|
|
# bpy.context.scene.collection.objects.link(newEmpty)
|
|
except AttributeError:
|
|
# Blender 2.7
|
|
pass
|
|
newEmpty.name = collisionboxName
|
|
newEmpty.location = (centers[0], centers[1], centers[2])
|
|
try:
|
|
newEmpty.empty_display_type = 'CUBE'
|
|
# newEmpty.empty_display_size = (sizes[0], sizes[1], sizes[2])
|
|
except AttributeError:
|
|
# Blender 2.7:
|
|
newEmpty.empty_draw_type = 'CUBE'
|
|
# newEmpty.empty_draw_size = (sizes[0], sizes[1], sizes[2])
|
|
# newEmpty.dimensions = (sizes[0], sizes[1], sizes[2])
|
|
# newEmpty.scale = (sizes[0]/2.0, sizes[1]/2.0, sizes[2]/2.0)
|
|
newEmpty.scale = (sizes[0], sizes[1], sizes[2])
|
|
|
|
if enable_minetest:
|
|
for i in range(3):
|
|
mins[i] /= 10.0
|
|
maxes[i] /= 10.0
|
|
|
|
msg = ('Size is not available. Make sure you have a mesh object'
|
|
' selected.')
|
|
|
|
if mins[0] is not None:
|
|
# swap y and z for Minetest (y-up):
|
|
if y_up:
|
|
tmp = mins[1]
|
|
mins[1] = mins[2]
|
|
mins[2] = tmp
|
|
tmp = maxes[1]
|
|
maxes[1] = maxes[2]
|
|
maxes[2] = tmp
|
|
msg = (" collisionbox = {{{:.2f}, {:.2f}, {:.2f}, {:.2f},"
|
|
" {:.2f}, {:.2f}}}".format(mins[0], mins[1], mins[2],
|
|
maxes[0], maxes[1], maxes[2]))
|
|
if len(msgSuffix) > 0:
|
|
msgSuffix = " -- " + msgSuffix
|
|
bpy.context.window_manager.clipboard = msg + msgSuffix
|
|
msg += " --copied to clipboard"
|
|
# if enable_minetest:
|
|
# msg += " -- *10"
|
|
print(msg)
|
|
|
|
bpy.ops.message.messagebox('INVOKE_DEFAULT', message=msg)
|
|
|
|
|
|
# Unregistering before user clicks the MessageBox will crash Blender!
|
|
# bpy.utils.unregister_class(MessageBox)
|
|
calculate_one()
|
|
|