Created
October 30, 2022 01:40
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" 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