738 lines
30 KiB
Python
738 lines
30 KiB
Python
from panda3d.core import loadPrcFileData
|
|
#loadPrcFileData("", "window-type none" ) # Make sure we don't need a graphics engine (Will also prevent X errors / Display errors when starting on linux without X server)
|
|
loadPrcFileData("", "window-type offscreen" )
|
|
loadPrcFileData("", "audio-library-name null" ) # Prevent ALSA errors
|
|
loadPrcFileData('', 'show-frame-rate-meter true')
|
|
loadPrcFileData('', 'sync-video 0')
|
|
loadPrcFileData('', 'load-file-type p3assimp')
|
|
from direct.showbase.ShowBase import ShowBase
|
|
from panda3d.core import FrameBufferProperties, WindowProperties
|
|
from panda3d.core import GraphicsPipe, GraphicsOutput
|
|
from panda3d.core import Texture
|
|
from panda3d.core import AmbientLight, DirectionalLight, LightAttrib
|
|
from panda3d.core import NodePath
|
|
from panda3d.core import LVector3
|
|
from panda3d.core import Filename
|
|
from direct.interval.IntervalGlobal import * # Needed to use Intervals
|
|
from direct.gui.DirectGui import *
|
|
from math import pi, sin, tan, sqrt
|
|
import sys
|
|
import os
|
|
import json
|
|
import torch
|
|
import numpy as np
|
|
from PIL import Image, ImageFilter
|
|
import folder_paths
|
|
import time
|
|
from datetime import datetime
|
|
import cv2
|
|
|
|
comfy_path = os.path.dirname(folder_paths.__file__)
|
|
panda3d_models_path=f'{comfy_path}/custom_nodes/ComfyUI-Panda3d/models'
|
|
|
|
def show_rgbd_image(image, depth_image, window_name='Image window', delay=1, depth_offset=0.0, depth_scale=1.0):
|
|
if depth_image.dtype != np.uint8:
|
|
if depth_scale is None:
|
|
depth_scale = depth_image.max() - depth_image.min()
|
|
if depth_offset is None:
|
|
depth_offset = depth_image.min()
|
|
depth_image = np.clip((depth_image - depth_offset) / depth_scale, 0.0, 1.0)
|
|
depth_image = (255.0 * depth_image).astype(np.uint8)
|
|
depth_image = np.tile(depth_image, (1, 1, 3))
|
|
if image.shape[2] == 4: # add alpha channel
|
|
alpha = np.full(depth_image.shape[:2] + (1,), 255, dtype=np.uint8)
|
|
depth_image = np.concatenate([depth_image, alpha], axis=-1)
|
|
images = np.concatenate([image, depth_image], axis=1)
|
|
# images = cv2.cvtColor(images, cv2.COLOR_RGB2BGR) # not needed since image is already in BGR format
|
|
cv2.imshow(window_name, images)
|
|
key = cv2.waitKey(delay)
|
|
key &= 255
|
|
if key == 27 or key == ord('q'):
|
|
print("Pressed ESC or q, exiting")
|
|
exit_request = True
|
|
else:
|
|
exit_request = False
|
|
return exit_request
|
|
|
|
def create_mtl(mtlPath, matName, texturePath):
|
|
if max(mtlPath.find('\\'), mtlPath.find('/')) > -1:
|
|
os.makedirs(os.path.dirname(mtlPath), exist_ok=True)
|
|
with open(mtlPath, "w") as f:
|
|
f.write("newmtl " + matName + "\n" )
|
|
f.write("Ns 10.0000\n" )
|
|
f.write("d 1.0000\n" )
|
|
f.write("Tr 0.0000\n" )
|
|
f.write("illum 2\n" )
|
|
f.write("Ka 1.000 1.000 1.000\n" )
|
|
f.write("Kd 1.000 1.000 1.000\n" )
|
|
f.write("Ks 0.000 0.000 0.000\n" )
|
|
f.write("map_Ka " + os.path.basename(texturePath) + "\n" )
|
|
f.write("map_Kd " + os.path.basename(texturePath) + "\n" )
|
|
|
|
def vete(v, vt):
|
|
return str(v)+"/"+str(vt)
|
|
|
|
def create_obj(depthPath, depthInvert, objPath, mtlPath, matName, useMaterial = True):
|
|
test_img=cv2.imread(depthPath)
|
|
coefficients = np.uint16(256 * np.array((.114, .587, .299)))
|
|
img=test_img.dot(coefficients)
|
|
#img = cv2.imread(depthPath, -1).astype(np.float32) / 1000.0
|
|
img=img/1000.0
|
|
|
|
if len(img.shape) > 2 and img.shape[2] > 1:
|
|
print('Expecting a 1D map, but depth map at path %s has shape %r'% (depthPath, img.shape))
|
|
return
|
|
|
|
if depthInvert == True:
|
|
img = 1.0 - img
|
|
|
|
w = img.shape[1]
|
|
h = img.shape[0]
|
|
|
|
FOV = pi/4
|
|
D = (img.shape[0]/2)/tan(FOV/2)
|
|
|
|
if max(objPath.find('\\'), objPath.find('/')) > -1:
|
|
os.makedirs(os.path.dirname(mtlPath), exist_ok=True)
|
|
|
|
with open(objPath,"w") as f:
|
|
if useMaterial:
|
|
f.write("mtllib " + os.path.basename(mtlPath) + "\n")
|
|
f.write("usemtl " + matName + "\n")
|
|
|
|
ids = np.zeros((img.shape[1], img.shape[0]), int)
|
|
vid = 1
|
|
|
|
for u in range(0, w):
|
|
for v in range(h-1, -1, -1):
|
|
|
|
d = img[v, u]
|
|
|
|
ids[u,v] = vid
|
|
if d == 0.0:
|
|
ids[u,v] = 0
|
|
vid += 1
|
|
|
|
x = u - w/2
|
|
y = v - h/2
|
|
z = -D
|
|
|
|
norm = 1 / sqrt(x*x + y*y + z*z)
|
|
|
|
t = d/(z*norm)
|
|
|
|
x = -t*x*norm
|
|
y = t*y*norm
|
|
z = -t*z*norm
|
|
|
|
f.write("v " + str(x) + " " + str(y) + " " + str(z) + "\n")
|
|
|
|
for u in range(0, img.shape[1]):
|
|
for v in range(0, img.shape[0]):
|
|
f.write("vt " + str(u/img.shape[1]) + " " + str(v/img.shape[0]) + "\n")
|
|
|
|
for u in range(0, img.shape[1]-1):
|
|
for v in range(0, img.shape[0]-1):
|
|
|
|
v1 = ids[u,v]; v2 = ids[u+1,v]; v3 = ids[u,v+1]; v4 = ids[u+1,v+1];
|
|
|
|
if v1 == 0 or v2 == 0 or v3 == 0 or v4 == 0:
|
|
continue
|
|
|
|
f.write("f " + vete(v1,v1) + " " + vete(v2,v2) + " " + vete(v3,v3) + "\n")
|
|
f.write("f " + vete(v3,v3) + " " + vete(v2,v2) + " " + vete(v4,v4) + "\n")
|
|
|
|
class Panda3dLoadDepthModel:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"base":("Panda3dBase",),
|
|
"loader":("Panda3dLoader",),
|
|
"parent":("Panda3dModel",),
|
|
"depthimg":("IMAGE",),
|
|
"textureimg":("IMAGE",),
|
|
"x": ("FLOAT", {"default": 0}),
|
|
"y": ("FLOAT", {"default": 0}),
|
|
"z": ("FLOAT", {"default": 0}),
|
|
"h": ("FLOAT", {"default": 0}),
|
|
"p": ("FLOAT", {"default": 90}),
|
|
"r": ("FLOAT", {"default": 0}),
|
|
"sx": ("FLOAT", {"default": 1}),
|
|
"sy": ("FLOAT", {"default": 1}),
|
|
"sz": ("FLOAT", {"default": 1}),
|
|
}
|
|
}
|
|
RETURN_TYPES = ("Panda3dBase","Panda3dModel",)
|
|
RETURN_NAMES = ("base","model",)
|
|
FUNCTION = "run"
|
|
CATEGORY = "Panda3d"
|
|
|
|
def run(self,base,loader,parent,depthimg,textureimg,x,y,z,h,p,r,sx,sy,sz):
|
|
textureimg = 255.0 * textureimg[0].cpu().numpy()
|
|
textureimg = Image.fromarray(np.clip(textureimg, 0, 255).astype(np.uint8))
|
|
now=int(datetime.now().timestamp())
|
|
texturePath=f'{panda3d_models_path}/output/texture{now}.png'
|
|
mtlPath=f'{panda3d_models_path}/output/mtl{now}.mtl'
|
|
os.makedirs(os.path.dirname(mtlPath), exist_ok=True)
|
|
textureimg.save(texturePath)
|
|
matName=f'mtl{now}'
|
|
|
|
create_mtl(mtlPath, matName, texturePath)
|
|
|
|
depthimg = 255.0 * depthimg[0].cpu().numpy()
|
|
depthimg = Image.fromarray(np.clip(depthimg, 0, 255).astype(np.uint8))
|
|
depthPath=f'{panda3d_models_path}/output/depth{now}.png'
|
|
depthimg.save(depthPath)
|
|
|
|
objPath=f'{panda3d_models_path}/output/depth{now}.obj'
|
|
|
|
create_obj(depthPath, True, objPath, mtlPath, matName, True)
|
|
|
|
time.sleep(1)
|
|
model = loader.loadModel(Filename.fromOsSpecific(objPath))
|
|
model.reparentTo(parent)
|
|
model.setPos(x,y,z)
|
|
model.setHpr(h,p,r)
|
|
model.setScale(sx,sy,sz)
|
|
return (base,model,)
|
|
|
|
class Panda3dBase(ShowBase):
|
|
@classmethod
|
|
def IS_CHANGED(s):
|
|
return int(datetime.now())
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("Panda3dBase","Panda3dLoader","Panda3dModel",)
|
|
RETURN_NAMES = ("base","loader","render",)
|
|
FUNCTION = "run"
|
|
CATEGORY = "Panda3d"
|
|
|
|
def __init__(self):
|
|
try:
|
|
super(Panda3dBase, self).__init__()
|
|
except:
|
|
self.destroy()
|
|
super(Panda3dBase, self).__init__()
|
|
|
|
base.disableMouse() # Allow manual positioning of the camera
|
|
camera.setPosHpr(0, -8, 2.5, 0, -9, 0)
|
|
|
|
#self.loadModels() # Load and position our models
|
|
#self.setupLights() # Add some basic lighting
|
|
#self.startCarousel() # Create the needed intervals and put the
|
|
# carousel into motion
|
|
|
|
# Needed for camera image
|
|
self.dr = self.camNode.getDisplayRegion(0)
|
|
|
|
# Needed for camera depth image
|
|
winprops = WindowProperties.size(self.win.getXSize(), self.win.getYSize())
|
|
fbprops = FrameBufferProperties()
|
|
fbprops.setDepthBits(1)
|
|
self.depthBuffer = self.graphicsEngine.makeOutput(
|
|
self.pipe, "depth buffer", -2,
|
|
fbprops, winprops,
|
|
GraphicsPipe.BFRefuseWindow,
|
|
self.win.getGsg(), self.win)
|
|
self.depthTex = Texture()
|
|
self.depthTex.setFormat(Texture.FDepthComponent)
|
|
self.depthBuffer.addRenderTexture(self.depthTex,
|
|
GraphicsOutput.RTMCopyRam, GraphicsOutput.RTPDepth)
|
|
lens = self.cam.node().getLens()
|
|
# the near and far clipping distances can be changed if desired
|
|
# lens.setNear(5.0)
|
|
# lens.setFar(500.0)
|
|
self.depthCam = self.makeCamera(self.depthBuffer,
|
|
lens=lens,
|
|
scene=render)
|
|
self.depthCam.reparentTo(self.cam)
|
|
|
|
# TODO: Scene is rendered twice: once for rgb and once for depth image.
|
|
# How can both images be obtained in one rendering pass?
|
|
|
|
def loadModels(self):
|
|
# Load the carousel base
|
|
self.carousel = loader.loadModel(Filename.fromOsSpecific(f"{panda3d_models_path}/carousel_base"))
|
|
self.carousel.reparentTo(render) # Attach it to render
|
|
|
|
# Load the modeled lights that are on the outer rim of the carousel
|
|
# (not Panda lights)
|
|
# There are 2 groups of lights. At any given time, one group will have
|
|
# the "on" texture and the other will have the "off" texture.
|
|
self.lights1 = loader.loadModel(Filename.fromOsSpecific(f"{panda3d_models_path}/carousel_lights"))
|
|
self.lights1.reparentTo(self.carousel)
|
|
|
|
# Load the 2nd set of lights
|
|
self.lights2 = loader.loadModel(Filename.fromOsSpecific(f"{panda3d_models_path}/carousel_lights"))
|
|
# We need to rotate the 2nd so it doesn't overlap with the 1st set.
|
|
self.lights2.setH(36)
|
|
self.lights2.reparentTo(self.carousel)
|
|
|
|
# Load the textures for the lights. One texture is for the "on" state,
|
|
# the other is for the "off" state.
|
|
self.lightOffTex = loader.loadTexture(Filename.fromOsSpecific(f"{panda3d_models_path}/carousel_lights_off.jpg"))
|
|
self.lightOnTex = loader.loadTexture(Filename.fromOsSpecific(f"{panda3d_models_path}/carousel_lights_on.jpg"))
|
|
|
|
# Create an list (self.pandas) with filled with 4 dummy nodes attached
|
|
# to the carousel.
|
|
# This uses a python concept called "Array Comprehensions." Check the
|
|
# Python manual for more information on how they work
|
|
self.pandas = [self.carousel.attachNewNode("panda" + str(i))
|
|
for i in range(4)]
|
|
self.models = [loader.loadModel(Filename.fromOsSpecific(f"{panda3d_models_path}/carousel_panda"))
|
|
for i in range(4)]
|
|
self.moves = [0] * 4
|
|
|
|
for i in range(4):
|
|
# set the position and orientation of the ith panda node we just created
|
|
# The Z value of the position will be the base height of the pandas.
|
|
# The headings are multiplied by i to put each panda in its own position
|
|
# around the carousel
|
|
self.pandas[i].setPosHpr(0, 0, 1.3, i * 90, 0, 0)
|
|
|
|
# Load the actual panda model, and parent it to its dummy node
|
|
self.models[i].reparentTo(self.pandas[i])
|
|
# Set the distance from the center. This distance is based on the way the
|
|
# carousel was modeled in Maya
|
|
self.models[i].setY(.85)
|
|
|
|
# Load the environment (Sky sphere and ground plane)
|
|
self.env = loader.loadModel(Filename.fromOsSpecific(f"{panda3d_models_path}/env"))
|
|
self.env.reparentTo(render)
|
|
self.env.setScale(7)
|
|
|
|
# Panda Lighting
|
|
def setupLights(self):
|
|
# Create some lights and add them to the scene. By setting the lights on
|
|
# render they affect the entire scene
|
|
# Check out the lighting tutorial for more information on lights
|
|
ambientLight = AmbientLight("ambientLight")
|
|
ambientLight.setColor((.4, .4, .35, 1))
|
|
directionalLight = DirectionalLight("directionalLight")
|
|
directionalLight.setDirection(LVector3(0, 8, -2.5))
|
|
directionalLight.setColor((0.9, 0.8, 0.9, 1))
|
|
render.setLight(render.attachNewNode(directionalLight))
|
|
render.setLight(render.attachNewNode(ambientLight))
|
|
|
|
# Explicitly set the environment to not be lit
|
|
self.env.setLightOff()
|
|
|
|
def startCarousel(self):
|
|
# Here's where we actually create the intervals to move the carousel
|
|
# The first type of interval we use is one created directly from a NodePath
|
|
# This interval tells the NodePath to vary its orientation (hpr) from its
|
|
# current value (0,0,0) to (360,0,0) over 20 seconds. Intervals created from
|
|
# NodePaths also exist for position, scale, color, and shear
|
|
|
|
self.carouselSpin = self.carousel.hprInterval(20, LVector3(360, 0, 0))
|
|
# Once an interval is created, we need to tell it to actually move.
|
|
# start() will cause an interval to play once. loop() will tell an interval
|
|
# to repeat once it finished. To keep the carousel turning, we use
|
|
# loop()
|
|
self.carouselSpin.loop()
|
|
|
|
# The next type of interval we use is called a LerpFunc interval. It is
|
|
# called that becuase it linearly interpolates (aka Lerp) values passed to
|
|
# a function over a given amount of time.
|
|
|
|
# In this specific case, horses on a carousel don't move contantly up,
|
|
# suddenly stop, and then contantly move down again. Instead, they start
|
|
# slowly, get fast in the middle, and slow down at the top. This motion is
|
|
# close to a sine wave. This LerpFunc calls the function oscillatePanda
|
|
# (which we will create below), which changes the height of the panda based
|
|
# on the sin of the value passed in. In this way we achieve non-linear
|
|
# motion by linearly changing the input to a function
|
|
for i in range(4):
|
|
self.moves[i] = LerpFunc(
|
|
self.oscillatePanda, # function to call
|
|
duration=3, # 3 second duration
|
|
fromData=0, # starting value (in radians)
|
|
toData=2 * pi, # ending value (2pi radians = 360 degrees)
|
|
# Additional information to pass to
|
|
# self.oscialtePanda
|
|
extraArgs=[self.models[i], pi * (i % 2)]
|
|
)
|
|
# again, we want these to play continuously so we start them with
|
|
# loop()
|
|
self.moves[i].loop()
|
|
|
|
# Finally, we combine Sequence, Parallel, Func, and Wait intervals,
|
|
# to schedule texture swapping on the lights to simulate the lights turning
|
|
# on and off.
|
|
# Sequence intervals play other intervals in a sequence. In other words,
|
|
# it waits for the current interval to finish before playing the next
|
|
# one.
|
|
# Parallel intervals play a group of intervals at the same time
|
|
# Wait intervals simply do nothing for a given amount of time
|
|
# Func intervals simply make a single function call. This is helpful because
|
|
# it allows us to schedule functions to be called in a larger sequence. They
|
|
# take virtually no time so they don't cause a Sequence to wait.
|
|
|
|
self.lightBlink = Sequence(
|
|
# For the first step in our sequence we will set the on texture on one
|
|
# light and set the off texture on the other light at the same time
|
|
Parallel(
|
|
Func(self.lights1.setTexture, self.lightOnTex, 1),
|
|
Func(self.lights2.setTexture, self.lightOffTex, 1)),
|
|
Wait(1), # Then we will wait 1 second
|
|
# Then we will switch the textures at the same time
|
|
Parallel(
|
|
Func(self.lights1.setTexture, self.lightOffTex, 1),
|
|
Func(self.lights2.setTexture, self.lightOnTex, 1)),
|
|
Wait(1) # Then we will wait another second
|
|
)
|
|
|
|
self.lightBlink.loop() # Loop this sequence continuously
|
|
|
|
def oscillatePanda(self, rad, panda, offset):
|
|
# This is the oscillation function mentioned earlier. It takes in a
|
|
# degree value, a NodePath to set the height on, and an offset. The
|
|
# offset is there so that the different pandas can move opposite to
|
|
# each other. The .2 is the amplitude, so the height of the panda will
|
|
# vary from -.2 to .2
|
|
print(sin(rad + offset) * .2)
|
|
panda.setZ(sin(rad + offset) * .2)
|
|
|
|
def get_camera_image(self, requested_format=None):
|
|
"""
|
|
Returns the camera's image, which is of type uint8 and has values
|
|
between 0 and 255.
|
|
The 'requested_format' argument should specify in which order the
|
|
components of the image must be. For example, valid format strings are
|
|
"RGBA" and "BGRA". By default, Panda's internal format "BGRA" is used,
|
|
in which case no data is copied over.
|
|
"""
|
|
tex = self.dr.getScreenshot()
|
|
if requested_format is None:
|
|
data = tex.getRamImage()
|
|
else:
|
|
data = tex.getRamImageAs(requested_format)
|
|
image = np.frombuffer(data, np.uint8) # use data.get_data() instead of data in python 2
|
|
image.shape = (tex.getYSize(), tex.getXSize(), tex.getNumComponents())
|
|
image = np.flipud(image)
|
|
return image
|
|
|
|
def get_camera_depth_image(self):
|
|
"""
|
|
Returns the camera's depth image, which is of type float32 and has
|
|
values between 0.0 and 1.0.
|
|
"""
|
|
data = self.depthTex.getRamImage()
|
|
depth_image = np.frombuffer(data, np.float32)
|
|
depth_image.shape = (self.depthTex.getYSize(), self.depthTex.getXSize(), self.depthTex.getNumComponents())
|
|
depth_image = np.flipud(depth_image)
|
|
return depth_image
|
|
|
|
def run(self):
|
|
return (self,self.loader,self.render,)
|
|
|
|
class Panda3dLoadTexture:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"base":("Panda3dBase",),
|
|
"loader":("Panda3dLoader",),
|
|
"texture_path": ("STRING",{"default":f"{panda3d_models_path}/carousel_lights_off.jpg"}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("Panda3dBase","Panda3dTexture",)
|
|
RETURN_NAMES = ("base","texture",)
|
|
FUNCTION = "run"
|
|
CATEGORY = "Panda3d"
|
|
|
|
def run(self,base,loader,texture_path):
|
|
texture=loader.loadTexture(Filename.fromOsSpecific(texture_path))
|
|
return (base,texture,)
|
|
|
|
class Panda3dAttachNewNode:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"base":("Panda3dBase",),
|
|
"parent":("Panda3dModel",),
|
|
"nodepath": ("STRING",{"default":""}),
|
|
"x": ("FLOAT", {"default": 0, "min": -1000, "max": 1000, "step": 0.1}),
|
|
"y": ("FLOAT", {"default": 0, "min": -1000, "max": 1000, "step": 0.1}),
|
|
"z": ("FLOAT", {"default": 0, "min": -1000, "max": 1000, "step": 0.1}),
|
|
"h": ("FLOAT", {"default": 0, "min": -1000, "max": 1000, "step": 0.1}),
|
|
"p": ("FLOAT", {"default": 0, "min": -1000, "max": 1000, "step": 0.1}),
|
|
"r": ("FLOAT", {"default": 0, "min": -1000, "max": 1000, "step": 0.1}),
|
|
"sx": ("FLOAT", {"default": 1, "min": -1000, "max": 1000, "step": 0.1}),
|
|
"sy": ("FLOAT", {"default": 1, "min": -1000, "max": 1000, "step": 0.1}),
|
|
"sz": ("FLOAT", {"default": 1, "min": -1000, "max": 1000, "step": 0.1}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("Panda3dBase","Panda3dModel",)
|
|
RETURN_NAMES = ("base","model",)
|
|
FUNCTION = "run"
|
|
CATEGORY = "Panda3d"
|
|
|
|
def run(self,base,parent,nodepath,x,y,z,h,p,r,sx,sy,sz):
|
|
newnode=parent.attachNewNode(nodepath)
|
|
newnode.setPos(x,y,z)
|
|
newnode.setHpr(h,p,r)
|
|
newnode.setScale(sx,sy,sz)
|
|
return (base,newnode,)
|
|
|
|
class Panda3dAmbientLight:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"base":("Panda3dBase",),
|
|
"r": ("FLOAT", {"default": .4}),
|
|
"g": ("FLOAT", {"default": .4}),
|
|
"b": ("FLOAT", {"default": .35}),
|
|
"a": ("FLOAT", {"default": 1}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("Panda3dBase","Panda3dModel",)
|
|
RETURN_NAMES = ("base","light",)
|
|
FUNCTION = "run"
|
|
CATEGORY = "Panda3d"
|
|
|
|
def run(self,base,r,g,b,a):
|
|
ambientLight = AmbientLight("ambientLight")
|
|
ambientLight.setColor((r,g,b,a))
|
|
return (base,ambientLight,)
|
|
|
|
class Panda3dDirectionalLight:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"base":("Panda3dBase",),
|
|
"r": ("FLOAT", {"default": .9}),
|
|
"g": ("FLOAT", {"default": .8}),
|
|
"b": ("FLOAT", {"default": .9}),
|
|
"a": ("FLOAT", {"default": 1}),
|
|
"dx": ("FLOAT", {"default": 0}),
|
|
"dy": ("FLOAT", {"default": 8}),
|
|
"dz": ("FLOAT", {"default": -2.5}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("Panda3dBase","Panda3dModel",)
|
|
RETURN_NAMES = ("base","light",)
|
|
FUNCTION = "run"
|
|
CATEGORY = "Panda3d"
|
|
|
|
def run(self,base,r,g,b,a):
|
|
directionalLight = DirectionalLight("directionalLight")
|
|
directionalLight.setDirection(LVector3(dx,dy,dz))
|
|
directionalLight.setColor((r,g,b,a))
|
|
return (base,directionalLight,)
|
|
|
|
class Panda3dLoadModel:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"base":("Panda3dBase",),
|
|
"loader":("Panda3dLoader",),
|
|
"parent":("Panda3dModel",),
|
|
"model_path": ("STRING",{"default":f"{panda3d_models_path}/carousel_base"}),
|
|
"x": ("FLOAT", {"default": 0, "min": -1000, "max": 1000, "step": 0.1}),
|
|
"y": ("FLOAT", {"default": 0, "min": -1000, "max": 1000, "step": 0.1}),
|
|
"z": ("FLOAT", {"default": 0, "min": -1000, "max": 1000, "step": 0.1}),
|
|
"h": ("FLOAT", {"default": 0, "min": -1000, "max": 1000, "step": 0.1}),
|
|
"p": ("FLOAT", {"default": 0, "min": -1000, "max": 1000, "step": 0.1}),
|
|
"r": ("FLOAT", {"default": 0, "min": -1000, "max": 1000, "step": 0.1}),
|
|
"sx": ("FLOAT", {"default": 1, "min": -1000, "max": 1000, "step": 0.1}),
|
|
"sy": ("FLOAT", {"default": 1, "min": -1000, "max": 1000, "step": 0.1}),
|
|
"sz": ("FLOAT", {"default": 1, "min": -1000, "max": 1000, "step": 0.1}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("Panda3dBase","Panda3dModel",)
|
|
RETURN_NAMES = ("base","model",)
|
|
FUNCTION = "run"
|
|
CATEGORY = "Panda3d"
|
|
|
|
def run(self,base,loader,parent,model_path,x,y,z,h,p,r,sx,sy,sz):
|
|
model = loader.loadModel(Filename.fromOsSpecific(model_path))
|
|
model.reparentTo(parent)
|
|
model.setPos(x,y,z)
|
|
model.setHpr(h,p,r)
|
|
model.setScale(sx,sy,sz)
|
|
return (base,model,)
|
|
|
|
class Panda3dModelMerge:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"model1": ("Panda3dModel",),
|
|
"model2": ("Panda3dModel",),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("Panda3dModel",)
|
|
RETURN_NAMES = ("models",)
|
|
FUNCTION = "run"
|
|
CATEGORY = "Panda3d"
|
|
|
|
def run(self,model1,model2):
|
|
return (model1+model2,)
|
|
|
|
class Panda3dTextureMerge:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"texture1": ("Panda3dTexture",),
|
|
"texture2": ("Panda3dTexture",),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("Panda3dTexture",)
|
|
RETURN_NAMES = ("textures",)
|
|
FUNCTION = "run"
|
|
CATEGORY = "Panda3d"
|
|
|
|
def run(self,texture1,texture2):
|
|
if type(texture1) is not list:
|
|
texture1=[texture1]
|
|
if type(texture2) is not list:
|
|
texture2=[texture2]
|
|
return (texture1+texture2,)
|
|
|
|
class Panda3dTest:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"base":("Panda3dBase",),
|
|
"frame_length": ("INT",{"default":14}),
|
|
},
|
|
"optional": {
|
|
"model0":("Panda3dModel",{"default":None}),
|
|
"model0Animation":("STRING",{"default":"{}"}),
|
|
"model1":("Panda3dModel",{"default":None}),
|
|
"model1Animation":("STRING",{"default":"{}"}),
|
|
"model2":("Panda3dModel",{"default":None}),
|
|
"model2Animation":("STRING",{"default":"{}"}),
|
|
"model3":("Panda3dModel",{"default":None}),
|
|
"model3Animation":("STRING",{"default":"{}"}),
|
|
"model4":("Panda3dModel",{"default":None}),
|
|
"model4Animation":("STRING",{"default":"{}"}),
|
|
"model5":("Panda3dModel",{"default":None}),
|
|
"model5Animation":("STRING",{"default":"{}"}),
|
|
"model6":("Panda3dModel",{"default":None}),
|
|
"model6Animation":("STRING",{"default":"{}"}),
|
|
"model7":("Panda3dModel",{"default":None}),
|
|
"model7Animation":("STRING",{"default":"{}"}),
|
|
"model8":("Panda3dModel",{"default":None}),
|
|
"model8Animation":("STRING",{"default":"{}"}),
|
|
"model9":("Panda3dModel",{"default":None}),
|
|
"model9Animation":("STRING",{"default":"{}"}),
|
|
"model10":("Panda3dModel",{"default":None}),
|
|
"model10Animation":("STRING",{"default":"{}"}),
|
|
"textures":("Panda3dTexture",{"default":None}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
RETURN_NAMES = ("image",)
|
|
FUNCTION = "run"
|
|
CATEGORY = "Panda3d"
|
|
|
|
def run(self,base,frame_length,model0=None,model0Animation="{}",model1=None,model1Animation="{}",model2=None,model2Animation="{}",model3=None,model3Animation="{}",model4=None,model4Animation="{}",model5=None,model5Animation="{}",model6=None,model6Animation="{}",model7=None,model7Animation="{}",model8=None,model8Animation="{}",model9=None,model9Animation="{}",model10=None,model10Animation="{}",textures=None):
|
|
outframes=[]
|
|
#path = os.path.join(folder_paths.output_directory, file_name)
|
|
#base = Panda3dBase()#windowType='offscreen'
|
|
#base.run()
|
|
radius = 20
|
|
step = 1
|
|
models=[model0,model1,model2,model3,model4,model5,model6,model7,model8,model9,model10]
|
|
animations=[model0Animation,model1Animation,model2Animation,model3Animation,model4Animation,model5Animation,model6Animation,model7Animation,model8Animation,model9Animation,model10Animation]
|
|
for f in range(frame_length):
|
|
print(f)
|
|
base.graphicsEngine.renderFrame()
|
|
|
|
for i in range(len(models)):
|
|
model=models[i]
|
|
animation=animations[i]
|
|
if model is None or animation=="{}":
|
|
continue
|
|
animates=json.loads(animation)
|
|
if "x" in animates.keys():
|
|
if len(animates["x"])>f:
|
|
model.setX(animates["x"][f])
|
|
if "y" in animates.keys():
|
|
if len(animates["y"])>f:
|
|
model.setY(animates["y"][f])
|
|
if "z" in animates.keys():
|
|
if len(animates["z"])>f:
|
|
model.setZ(animates["z"][f])
|
|
if "h" in animates.keys():
|
|
if len(animates["h"])>f:
|
|
model.setH(animates["h"][f])
|
|
if "p" in animates.keys():
|
|
if len(animates["p"])>f:
|
|
model.setP(animates["p"][f])
|
|
if "r" in animates.keys():
|
|
if len(animates["r"])>f:
|
|
model.setR(animates["r"][f])
|
|
if "sx" in animates.keys():
|
|
if len(animates["sx"])>f:
|
|
model.setSx(animates["sx"][f])
|
|
if "sy" in animates.keys():
|
|
if len(animates["sy"])>f:
|
|
model.setSy(animates["sy"][f])
|
|
if "sz" in animates.keys():
|
|
if len(animates["sz"])>f:
|
|
model.setSz(animates["sz"][f])
|
|
if textures is not None and "texture" in animates.keys():
|
|
if len(animates["texture"])>f:
|
|
model.setTexture(textures[animates["texture"][f]])
|
|
#angleDegrees = i * step
|
|
#angleRadians = angleDegrees * (np.pi / 180.0)
|
|
|
|
#carousel.setHpr(angleDegrees, 0, 0)
|
|
|
|
image = base.get_camera_image()
|
|
#depth_image = base.get_camera_depth_image()
|
|
#show_rgbd_image(image, depth_image)
|
|
#base.screenshot(namePrefix='screenshot', defaultFilename=1, source=None, imageComment="")
|
|
|
|
image_pil = Image.fromarray(np.clip(image, 0, 255).astype(np.uint8))
|
|
image_tensor_out = torch.tensor(np.array(image_pil).astype(np.float32) / 255.0)
|
|
image_tensor_out = torch.unsqueeze(image_tensor_out, 0)
|
|
outframes.append(image_tensor_out)
|
|
Wait(1)
|
|
|
|
for i in range(len(models)):
|
|
model=models[i]
|
|
if model is None:
|
|
continue
|
|
model.get_children().detach()
|
|
|
|
return torch.cat(tuple(outframes), dim=0).unsqueeze(0)
|
|
|
|
NODE_CLASS_MAPPINGS = {
|
|
"Panda3dBase":Panda3dBase,
|
|
"Panda3dLoadModel":Panda3dLoadModel,
|
|
"Panda3dAttachNewNode":Panda3dAttachNewNode,
|
|
"Panda3dLoadTexture":Panda3dLoadTexture,
|
|
"Panda3dAmbientLight":Panda3dAmbientLight,
|
|
"Panda3dDirectionalLight":Panda3dDirectionalLight,
|
|
"Panda3dModelMerge":Panda3dModelMerge,
|
|
"Panda3dTextureMerge":Panda3dTextureMerge,
|
|
"Panda3dTest":Panda3dTest,
|
|
"Panda3dLoadDepthModel":Panda3dLoadDepthModel,
|
|
}
|
|
|