Skip to content

Instantly share code, notes, and snippets.

@unwave
Created October 30, 2022 01:40
Show Gist options
  • Save unwave/a0e5084971cb63b5879124abfe10e182 to your computer and use it in GitHub Desktop.
Save unwave/a0e5084971cb63b5879124abfe10e182 to your computer and use it in GitHub Desktop.
Generate panda3d.bullet.BulletConvexHullShape via the Blender's Geometry Nodes Convex Hull node.
""" Generate panda3d.bullet.BulletConvexHullShape via the Blender's Geometry Nodes Convex Hull node. """
from __future__ import annotations
import os
import sys
import tempfile
import typing
import random
# pip install git+https://github.com/unwave/blend_converter
from blend_converter import Bam
from direct.gui.OnscreenText import OnscreenText
from direct.showbase.ShowBase import ShowBase
from panda3d import bullet, core
base: World
render: core.NodePath
loader: typing.Any
taskMgr: typing.Any
globalClock: typing.Any
core.load_prc_file_data('', 'sync-video false')
# core.load_prc_file_data('', 'fullscreen true')
core.load_prc_file_data('', 'show-frame-rate-meter true')
def add_instructions(pos, msg, base: ShowBase):
return OnscreenText(text=msg, style=1, fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1),
parent=base.a2dTopLeft, align = core.TextNode.ALeft,
pos=(0.08, -pos - 0.04), scale=.05)
class World(ShowBase):
def __init__(self, test_objects: Bam):
super().__init__()
self.test_objects = test_objects
sky_color = core.Vec4F(135/255, 206/255 ,235/255, 1)
self.set_physics_bullet()
self.accept("escape", sys.exit)
sun = core.DirectionalLight("directionalLight")
self.light = self.render.attachNewNode(sun)
sun.setColor((1, 1, 1, 1))
sun.setScene(self.render)
sun.set_shadow_caster(True, 4096, 4096)
self.render.setLight(self.light)
sun.get_lens().set_near_far(-250, 250)
sun.get_lens().set_film_size(250, 250)
self.light.set_hpr(45, -45, 0)
sky_light = core.AmbientLight("Ambient")
sky_light.setColor(sky_color * 0.1)
self.alight = self.render.attachNewNode(sky_light)
self.render.setLight(self.alight)
self.objects: typing.List[core.NodePath] = []
self.accept("s", self.spawn)
self.spawn()
add_instructions(0.1, "Press 'S' to spawn objects.", self)
self.accept("1", self.bullet_debug.show)
add_instructions(0.15, "Press '1' to show debug.", self)
self.accept("2", self.bullet_debug.hide)
add_instructions(0.2, "Press '2' to hide debug.", self)
self.trackball.node().set_pos(core.LPoint3f(0, 34.5, 0))
self.trackball.node().set_hpr(core.LVecBase3f(49.9247, 14.8541, -7.28684))
bullet_node = bullet.BulletRigidBodyNode()
bullet_node.add_shape(bullet.BulletPlaneShape(core.Vec3(0, 0, 1), 1))
self.bullet_world.attach(bullet_node)
ground_node = self.render.attach_new_node(bullet_node)
def spawn(self):
node: core.NodePath = self.loader.load_model(self.test_objects.path)
node.reparent_to(self.render)
self.attach_to_bullet_world(node)
for child in node.children:
child.set_color(core.Vec4F(random.random(), random.random(), random.random(), 1))
def attach_to_bullet_world(self, node: core.NodePath):
for bullet_node in node.find_all_matches("**/+BulletRigidBodyNode"):
self.bullet_world.attach(bullet_node.node())
def update_bullet(self, do_physics, clock):
do_physics(clock.get_dt())
return 1
def set_physics_bullet(self):
self.bullet_world = bullet.BulletWorld()
self.bullet_world.setGravity(core.Vec3(0, 0, -10))
self.taskMgr.add(self.update_bullet, 'update_bullet', extraArgs = [self.bullet_world.do_physics, self.clock])
node = bullet.BulletDebugNode('Debug')
self.bullet_world.setDebugNode(node)
node.showWireframe(True)
node.showConstraints(True)
node.showBoundingBoxes(False)
node.showNormals(False)
self.bullet_debug = self.render.attachNewNode(node)
self.bullet_debug.show()
def create_test_objects():
import bpy
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False, confirm=False)
funcs = (
bpy.ops.mesh.primitive_monkey_add,
bpy.ops.mesh.primitive_torus_add,
bpy.ops.mesh.primitive_cylinder_add,
bpy.ops.mesh.primitive_uv_sphere_add,
bpy.ops.mesh.primitive_cone_add,
)
for index, function in enumerate(funcs):
function()
bpy.context.object.location[2] = index * 3 + 5
def generate_hull_collisions():
import bpy
def get_geo_node_group():
node_group = bpy.data.node_groups.new('To Convex Hull', 'GeometryNodeTree')
input = node_group.nodes.new('NodeGroupInput')
input.outputs.new('NodeSocketGeometry', 'Geometry', identifier = 'Input_0')
output = node_group.nodes.new('NodeGroupOutput')
output.inputs.new('NodeSocketGeometry', 'Geometry', identifier = 'Output_1')
hull_node = node_group.nodes.new('GeometryNodeConvexHull')
node_group.links.new(input.outputs['Geometry'], output.inputs['Geometry'])
node_group.links.new(input.outputs[0], hull_node.inputs[0])
node_group.links.new(hull_node.outputs[0], output.inputs[0])
return node_group
node_group = get_geo_node_group()
collection = bpy.context.collection
for object in bpy.data.objects.values():
object_copy = object.copy() # type: bpy.types.Object
object_copy.data = object.data.copy()
collection.objects.link(object_copy)
object_copy.name = object.name + '__COLLISION_SHAPE'
object_copy.data.name = object.data.name + '__COLLISION_SHAPE_MESH'
object_copy['COLLISION_SHAPE'] = True
object_copy.parent = object
object_copy.location = (0, 0, 0)
object_copy.display_type = 'WIRE'
modifier = object_copy.modifiers.new("To Convex Hull", 'NODES')
modifier.node_group = node_group
modifier = object_copy.modifiers.new("DECIMATE", 'DECIMATE')
modifier.ratio = 0.2
class Bam_Edit:
def __init__(self, bam_path: str):
self.bam_path = bam_path
def __enter__(self):
from panda3d import core
loader: core.Loader = core.Loader.get_global_ptr()
flags = core.LoaderOptions(core.LoaderOptions.LF_no_cache)
bam_path = core.Filename.from_os_specific(self.bam_path)
panda_node = loader.load_sync(bam_path, flags)
self.root_node = core.NodePath(panda_node)
return self.root_node
def __exit__(self , type, value, traceback):
from panda3d import core
is_success = self.root_node.write_bam_file(core.Filename.from_os_specific(self.bam_path))
if not is_success:
raise BaseException(f'Error writing file: {self.bam_path}')
def set_collisions(bam_path):
""" executes in the current interpreter without pickling """
with Bam_Edit(bam_path) as root_node:
for node in root_node.find_all_matches('**/=COLLISION_SHAPE'):
parent_node = node.get_parent()
shape = bullet.BulletConvexHullShape()
for geom_node in node.find_all_matches('**/+GeomNode'):
for geom in geom_node.node().get_geoms():
shape.add_geom(geom)
node.remove_node()
bullet_node = bullet.BulletRigidBodyNode(geom_node.name)
bullet_node.add_shape(shape)
bullet_node.mass = 5
bullet_node_path = parent_node.attach_new_node(bullet_node)
for geom_node in parent_node.find_all_matches('**/+GeomNode'):
geom_node.reparent_to(bullet_node_path)
def save_blend():
import bpy
bpy.ops.wm.save_as_mainfile()
def create_blend_file(temp_dir: str):
path = os.path.join(temp_dir, 'my_blend.blend')
from blend_converter.common import Script
from blend_converter.utils import BLENDER_BINARY, run_blender
def save(filepath):
import bpy
bpy.ops.wm.save_as_mainfile(filepath = filepath)
run_blender(['--python-expr', Script._get_expr(save, path)], blender_binary = BLENDER_BINARY)
return path
def main(debug_blend = 0):
with tempfile.TemporaryDirectory() as temp_dir:
blend_path = create_blend_file(temp_dir)
test_objects = Bam(blend_path, temp_dir)
test_objects.settings_gltf.physics_engine = 'bullet'
test_objects.attach_pre_gltf_script(create_test_objects)
test_objects.attach_pre_gltf_script(generate_hull_collisions)
# debug_blend = 1
if debug_blend:
test_objects.attach_pre_gltf_script(save_blend)
test_objects.path
os.startfile(blend_path)
input('press Enter to continue...')
test_objects.attach_post_bam_script(set_collisions, test_objects.os_path_target)
game = World(test_objects)
game.run()
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment