import posixpath import tkinter as tk import tkinter.ttk as ttk from source.packages import PackageManager from direct.showbase.ShowBase import ShowBase from panda3d.core import VirtualFileSystem, Material, NodePath from direct.stdpy.file import walk import simplepbr import gltf def get_icons(): return type('', (object,), { 'app': tk.PhotoImage(file="icons/application.png"), 'brick': tk.PhotoImage(file="icons/brick.png"), 'bricks': tk.PhotoImage(file="icons/bricks.png"), 'computer': tk.PhotoImage(file="icons/computer.png"), 'folder': tk.PhotoImage(file="icons/folder.png"), 'shape_group': tk.PhotoImage(file="icons/shape_group.png"), 'pictures': tk.PhotoImage(file="icons/pictures.png"), 'picture': tk.PhotoImage(file='icons/picture.png'), "page_red": tk.PhotoImage(file='icons/page_red.png'), 'shape_square': tk.PhotoImage(file='icons/shape_square.png'), 'color_wheel': tk.PhotoImage(file='icons/color_wheel.png'), 'color_swatch': tk.PhotoImage(file='icons/color_swatch.png'), 'link': tk.PhotoImage(file='icons/link.png'), 'sound': tk.PhotoImage(file='icons/sound.png'), 'chart_organisation': tk.PhotoImage(file='icons/chart_organisation.png'), 'anchor': tk.PhotoImage(file='icons/anchor.png'), 'shape_flip_horizontal': tk.PhotoImage(file='icons/shape_flip_horizontal.png'), 'film': tk.PhotoImage(file='icons/film.png'), 'user': tk.PhotoImage(file='icons/user.png') })() def add_package_contents_to_tree(tree, package, type, name, extensions, icon_folder, icon_group, icon_item): data_path = posixpath.join(package.mount_dir, type) tree.insert(package.mount_dir, 'end', data_path, text=f' {name}', image=icon_group) file_count = 0 for root, paths, files in walk(data_path): for path in paths: tree.insert(root, 'end', posixpath.join(root, path), text=f' {path}', image=icon_folder) for file in files: if file.lower().endswith(extensions): file_count += 1 tree.insert(root, 'end', posixpath.join(root, file), text=f' {file}', image=icon_item) tree.item(data_path, text=f' {name} ({file_count})') def add_package_models_to_tree(tree, package, icons): add_package_contents_to_tree(tree, package, 'models/', '3D Models', ('.glb', '.gltf', '.obj', '.egg'), icons.folder, icons.shape_group, icons.shape_square) def add_package_textures_to_tree(tree, package, icons): add_package_contents_to_tree(tree, package, 'textures/', 'Image Textures', ('.png', '.jpg', '.bmp'), icons.folder, icons.pictures, icons.picture) def add_package_materials_to_tree(tree, package, icons): add_package_contents_to_tree(tree, package, 'materials/', 'Materials', ('.json'), icons.folder, icons.color_wheel, icons.color_swatch) def add_package_sounds_to_tree(tree, package, icons): add_package_contents_to_tree(tree, package, 'sounds/', 'Sounds', ('.ogg', '.wav'), icons.folder, icons.sound, icons.sound) def add_package_to_tree(tree, package, icons): add_package_models_to_tree(tree, package, icons) add_package_textures_to_tree(tree, package, icons) add_package_materials_to_tree(tree, package, icons) add_package_sounds_to_tree(tree, package, icons) def init_packages_tree(tree, packages, icons): tree.insert('', 'end', '/', text=' /', open=True, image=icons.app) tree.insert('/', 'end', '/main/', text=f' Main Package "{packages.main_package.meta.name}" - {packages.main_package.meta.version}', image=icons.computer) tree.insert('/', 'end', '/include/', text=f' Included Packages ({len(packages.package_list)})', image=icons.bricks) add_package_to_tree(tree, packages.main_package, icons) for package in packages.package_list: tree.insert('/include/', 'end', package.mount_dir, text=f' "{package.meta.name}" - {package.meta.version}', image=icons.brick) add_package_to_tree(tree, package, icons) return tree def init_gui(app, args): app.gui.geometry("700x500") app.gui.grid_rowconfigure(0, weight=10) app.gui.grid_rowconfigure(1, weight=5) app.gui.grid_columnconfigure(0, weight=1) package_frame = tk.Frame(app.gui, padx=4, pady=4) package_frame.grid(column=0, row=0, sticky="news") package_frame.grid_columnconfigure(0, weight=1) package_frame.grid_rowconfigure(0, weight=1) package_frame.grid_rowconfigure(1, weight=1000) packages_label = tk.Label(package_frame, text="Packages") packages_label.grid(column=0, row=0, sticky="w") packagetree_frame = tk.Frame(package_frame) packagetree_frame.grid(column=0, row=1, sticky="news") packagetree_frame.grid_columnconfigure(0, weight=1000) packagetree_frame.grid_columnconfigure(1, weight=1) packagetree_frame.grid_rowconfigure(0, weight=1) app.tree_packages = ttk.Treeview(packagetree_frame, show="tree") app.tree_packages.grid(column=0, row=0, sticky="news") packagetree_scrollbar = ttk.Scrollbar(packagetree_frame, orient="vertical", command=app.tree_packages.yview) packagetree_scrollbar.grid(column=1, row=0) app.tree_packages.bind('', app.on_packagetree_doubleclick) scene_frame = tk.Frame(app.gui, padx=4, pady=4) scene_frame.grid(column=0, row=1, sticky="news") scene_frame.grid_columnconfigure(0, weight=1) scene_frame.grid_rowconfigure(0, weight=1) scene_frame.grid_rowconfigure(1, weight=1000) scene_label = tk.Label(scene_frame, text="Scene Graph") scene_label.grid(column=0, row=0, sticky="w") scenetree_frame = tk.Frame(scene_frame) scenetree_frame.grid(column=0, row=1, sticky="news") scenetree_frame.grid_columnconfigure(0, weight=10000) scenetree_frame.grid_columnconfigure(1, weight=1) scenetree_frame.grid_rowconfigure(0, weight=1) app.tree_scene = ttk.Treeview(scenetree_frame, show="tree") app.tree_scene.grid(column=0, row=0, sticky="news") scenetree_scrollbar = ttk.Scrollbar(scenetree_frame, orient="vertical", command=app.tree_scene.yview) scenetree_scrollbar.grid(column=1, row=0) class PackageExplorer: def __init__(self, args): self.gui = tk.Tk() self.panda = ShowBase() self.package_manager = PackageManager(VirtualFileSystem.getGlobalPtr()) self.package_manager.set_main_package(args.main) self.icons = get_icons() self.prev = None if args.package: for include in args.package: self.package_manager.include_package(include) self.tree_packages = None self.tree_scene = None self.running = True simplepbr.init() gltf.patch_loader(self.panda.loader) init_gui(self, args) init_packages_tree(self.tree_packages, self.package_manager, self.icons) env_root = NodePath('env_root') env_root.reparentTo(self.panda.render) axis = self.panda.loader.loadModel(self.package_manager.get_model_path('axis.glb')) axis.reparentTo(env_root) plane = self.panda.loader.loadModel(self.package_manager.get_model_path('debugplane.glb')) plane.reparentTo(env_root) self.scene_root = NodePath('scene_root') self.scene_root.reparentTo(self.panda.render) self.gui.protocol("WM_DELETE_WINDOW", self.on_tk_close) def on_tk_close(self): self.running = False def on_packagetree_doubleclick(self, event): item = self.tree_packages.selection()[0] if item.lower().endswith(('.glb', '.gltf', '.obj', '.egg')): for child in self.scene_root.getChildren(): child.removeNode() mat = Material() mat.setAmbient((1, 0, 0, 1)) node = self.panda.loader.loadModel(item) node.reparentTo(self.scene_root) node.setMaterial(mat) self.display_scenegraph(node, item) self.prev = node def display_scenegraph(self, node, item): self.tree_scene.delete(*self.tree_scene.get_children()) self.tree_scene.insert('', 'end', '/', text=f' {item}', open=True, image=self.icons.shape_square) self.add_scene_nodes(node, '/') def add_scene_nodes(self, node, parent): pn = node.node() tree_id = f'{pn.getType().getName()} {node.getName()}' tree_name = tree_id image = '' match pn.getType().getName(): case 'ModelRoot': image = self.icons.chart_organisation case 'PandaNode': image = self.icons.anchor case 'GeomNode': image = self.icons.shape_flip_horizontal case 'AnimBundleNode': bundle = pn.getBundle().getName() tree_id = f'{tree_id}_"{bundle}"' tree_name = f'"{bundle}"' image = self.icons.film case 'Character': image = self.icons.user self.tree_scene.insert(parent, 'end', tree_id, text=f' {tree_name}', open=True, image=image) for child in node.getChildren(): self.add_scene_nodes(child, tree_id) def loop(self): try: while self.running: self.gui.update_idletasks() self.gui.update() self.panda.taskMgr.step() except KeyboardInterrupt: return def start_explorer(args): app = PackageExplorer(args) app.loop()