Example #1
0
def mesh_to_nodepath(mesh, boundsInfo):
    scene_members = pcore.getSceneMembers(mesh)

    rootNode = p3d.NodePath("rootnode")
    rotatePath = rootNode.attachNewNode("rotater")
    matrix = numpy.identity(4)
    if mesh.assetInfo.upaxis == collada.asset.UP_AXIS.X_UP:
        r = collada.scene.RotateTransform(0, 1, 0, 90)
        matrix = r.matrix
    elif mesh.assetInfo.upaxis == collada.asset.UP_AXIS.Y_UP:
        r = collada.scene.RotateTransform(1, 0, 0, 90)
        matrix = r.matrix
    rotatePath.setMat(p3d.Mat4(*matrix.T.flatten().tolist()))

    modelNode = p3d.ModelNode("colladanode")
    modelNode.setPreserveTransform(p3d.ModelNode.PTNet)
    modelPath = rotatePath.attachNewNode(modelNode)
    for geom, renderstate, mat4 in scene_members:
        node = p3d.GeomNode("primitive")
        node.addGeom(geom)
        if renderstate is not None:
            # FIXME: disabled transparency attrib because of progressive transparency bug
            renderstate = renderstate.removeAttrib(p3d.ColorScaleAttrib)
            node.setGeomState(0, renderstate)
        geomPath = modelPath.attachNewNode(node)
        geomPath.setMat(mat4)

    newroot = centerAndScale(rotatePath, boundsInfo)
    newroot.flattenStrong()
    return modelPath
Example #2
0
def getBam(mesh, filename):
    scene_members = pandacore.getSceneMembers(mesh)
    
    rotateNode = GeomNode("rotater")
    rotatePath = NodePath(rotateNode)
    matrix = numpy.identity(4)
    if mesh.assetInfo.upaxis == collada.asset.UP_AXIS.X_UP:
        r = collada.scene.RotateTransform(0,1,0,90)
        matrix = r.matrix
    elif mesh.assetInfo.upaxis == collada.asset.UP_AXIS.Y_UP:
        r = collada.scene.RotateTransform(1,0,0,90)
        matrix = r.matrix
    rotatePath.setMat(Mat4(*matrix.T.flatten().tolist()))
    
    for geom, renderstate, mat4 in scene_members:
        node = GeomNode("primitive")
        node.addGeom(geom)
        if renderstate is not None:
            node.setGeomState(0, renderstate)
        geomPath = rotatePath.attachNewNode(node)
        geomPath.setMat(mat4)

    rotatePath.flattenStrong()
    wrappedNode = pandacore.centerAndScale(rotatePath)
    
    model_name = filename.replace('/', '_')
    wrappedNode.setName(model_name)
    
    bam_temp = tempfile.mktemp(suffix = model_name + '.bam')
    wrappedNode.writeBamFile(bam_temp)
    
    bam_f = open(bam_temp, 'rb')
    bam_data = bam_f.read()
    bam_f.close()
    
    os.remove(bam_temp)
    
    return bam_data
Example #3
0
def getBam(mesh, filename):
    scene_members = pandacore.getSceneMembers(mesh)
    
    rotateNode = GeomNode("rotater")
    rotatePath = NodePath(rotateNode)
    matrix = numpy.identity(4)
    if mesh.assetInfo.upaxis == collada.asset.UP_AXIS.X_UP:
        r = collada.scene.RotateTransform(0,1,0,90)
        matrix = r.matrix
    elif mesh.assetInfo.upaxis == collada.asset.UP_AXIS.Y_UP:
        r = collada.scene.RotateTransform(1,0,0,90)
        matrix = r.matrix
    rotatePath.setMat(Mat4(*matrix.T.flatten().tolist()))
    
    for geom, renderstate, mat4 in scene_members:
        node = GeomNode("primitive")
        node.addGeom(geom)
        if renderstate is not None:
            node.setGeomState(0, renderstate)
        geomPath = rotatePath.attachNewNode(node)
        geomPath.setMat(mat4)

    rotatePath.flattenStrong()
    wrappedNode = pandacore.centerAndScale(rotatePath)
    
    model_name = filename.replace('/', '_')
    wrappedNode.setName(model_name)
    
    bam_temp = tempfile.mktemp(suffix = model_name + '.bam')
    wrappedNode.writeBamFile(bam_temp)
    
    bam_f = open(bam_temp, 'rb')
    bam_data = bam_f.read()
    bam_f.close()
    
    os.remove(bam_temp)
    
    return bam_data
Example #4
0
    def __init__(self, mesh_path, progressive_texture_path):

        resolutions = []
        f = tarfile.open(progressive_texture_path)
        for resolution_name in f.getnames():
            toset = {
                'size': resolution_name[:-4],
                'contents': f.extractfile(resolution_name).read()
            }
            texpnm = PNMImage()
            texpnm.read(StringStream(toset['contents']), 'something.jpg')
            newtex = Texture()
            newtex.load(texpnm)
            toset['texture'] = newtex
            resolutions.append(toset)

        self.resolutions = resolutions

        def aux_loader(fname):
            return resolutions[0]['contents']

        mesh = collada.Collada(mesh_path, aux_file_loader=aux_loader)

        scene_members = getSceneMembers(mesh)

        base = ShowBase()

        rotateNode = GeomNode("rotater")
        rotatePath = render.attachNewNode(rotateNode)
        matrix = numpy.identity(4)
        if mesh.assetInfo.upaxis == collada.asset.UP_AXIS.X_UP:
            r = collada.scene.RotateTransform(0, 1, 0, 90)
            matrix = r.matrix
        elif mesh.assetInfo.upaxis == collada.asset.UP_AXIS.Y_UP:
            r = collada.scene.RotateTransform(1, 0, 0, 90)
            matrix = r.matrix
        rotatePath.setMat(Mat4(*matrix.T.flatten().tolist()))

        geom, renderstate, mat4 = scene_members[0]
        node = GeomNode("primitive")
        node.addGeom(geom)
        if renderstate is not None:
            node.setGeomState(0, renderstate)
        self.geomPath = rotatePath.attachNewNode(node)
        self.geomPath.setMat(mat4)

        wrappedNode = ensureCameraAt(self.geomPath, base.camera)
        base.disableMouse()
        attachLights(render)
        render.setShaderAuto()
        render.setTransparency(TransparencyAttrib.MDual, 1)

        base.render.analyze()
        KeyboardMovement()
        MouseDrag(wrappedNode)
        MouseScaleZoom(wrappedNode)
        MouseCamera()

        num_resolutions = len(resolutions) - 1
        self.slider = DirectSlider(range=(0, num_resolutions),
                                   value=0,
                                   pageSize=1,
                                   command=self.sliderMoved,
                                   pos=(0, 0, -.9),
                                   scale=1)
        for key, val in uiArgs.iteritems():
            self.slider.thumb[key] = val

        self.triText = OnscreenText(text="",
                                    pos=(-1, 0.85),
                                    scale=0.15,
                                    fg=(1, 0.5, 0.5, 1),
                                    align=TextNode.ALeft,
                                    mayChange=1)

        base.run()
Example #5
0
def load_into_bamfile(meshdata, subfiles, model):
    """Uses pycollada and panda3d to load meshdata and subfiles and
    write out to a bam file on disk"""

    if os.path.isfile(model.bam_file):
        print 'returning cached bam file'
        return model.bam_file

    mesh = load_mesh(meshdata, subfiles)
    model_name = model.model_json['full_path'].replace('/', '_')

    if model.model_type == 'progressive' and model.model_subtype == 'full':
        progressive_stream = model.model_json['metadata']['types'][
            'progressive'].get('progressive_stream')
        if progressive_stream is not None:
            print 'LOADING PROGRESSIVE STREAM'
            data = model.prog_data
            try:
                mesh = add_back_pm.add_back_pm(mesh, StringIO(data), 100)
                print '-----'
                print 'SUCCESSFULLY ADDED BACK PM'
                print '-----'
            except:
                f = open(model.bam_file, 'w')
                f.close()
                raise

    print 'loading into bamfile', model_name, mesh
    scene_members = pandacore.getSceneMembers(mesh)
    print 'got scene members', model_name, mesh

    rotateNode = GeomNode("rotater")
    rotatePath = NodePath(rotateNode)
    matrix = numpy.identity(4)
    if mesh.assetInfo.upaxis == collada.asset.UP_AXIS.X_UP:
        r = collada.scene.RotateTransform(0, 1, 0, 90)
        matrix = r.matrix
    elif mesh.assetInfo.upaxis == collada.asset.UP_AXIS.Y_UP:
        r = collada.scene.RotateTransform(1, 0, 0, 90)
        matrix = r.matrix
    rotatePath.setMat(Mat4(*matrix.T.flatten().tolist()))

    for geom, renderstate, mat4 in scene_members:
        node = GeomNode("primitive")
        node.addGeom(geom)
        if renderstate is not None:
            node.setGeomState(0, renderstate)
        geomPath = rotatePath.attachNewNode(node)
        geomPath.setMat(mat4)

    print 'created np', model_name, mesh

    if model.model_type != 'optimized_unflattened' and model.model_type != 'progressive':
        print 'ABOUT TO FLATTEN'
        rotatePath.flattenStrong()
        print 'DONE FLATTENING'

    print 'flattened', model_name, mesh

    wrappedNode = pandacore.centerAndScale(rotatePath)
    wrappedNode.setName(model_name)

    wrappedNode.writeBamFile(model.bam_file)
    print 'saved', model_name, mesh
    wrappedNode = None

    return model.bam_file
Example #6
0
    def __init__(self, models):
        ShowBase.ShowBase.__init__(self)

        self.models = models

        unique_meshes = set(m.mesh for m in models)
        mesh2nodepath = {}
        for mesh in unique_meshes:
            scene_members = pcore.getSceneMembers(mesh)

            rotateNode = p3d.GeomNode("rotater")
            rotatePath = p3d.NodePath(rotateNode)
            matrix = numpy.identity(4)
            if mesh.assetInfo.upaxis == collada.asset.UP_AXIS.X_UP:
                r = collada.scene.RotateTransform(0, 1, 0, 90)
                matrix = r.matrix
            elif mesh.assetInfo.upaxis == collada.asset.UP_AXIS.Y_UP:
                r = collada.scene.RotateTransform(1, 0, 0, 90)
                matrix = r.matrix
            rotatePath.setMat(p3d.Mat4(*matrix.T.flatten().tolist()))

            rbc = p3d.RigidBodyCombiner('combiner')
            rbcPath = rotatePath.attachNewNode(rbc)

            for geom, renderstate, mat4 in scene_members:
                node = p3d.GeomNode("primitive")
                node.addGeom(geom)
                if renderstate is not None:
                    node.setGeomState(0, renderstate)
                geomPath = rbcPath.attachNewNode(node)
                geomPath.setMat(mat4)

            rbc.collect()

            mesh2nodepath[mesh] = centerAndScale(
                rotatePath, print_bounds.getBoundsInfo(mesh))

        scenepath = render.attachNewNode("scene")
        for model in self.models:
            np = mesh2nodepath[model.mesh]
            instance = scenepath.attachNewNode("model")
            np.instanceTo(instance)
            instance.setPos(model.x, model.y, model.z)
            instance.setScale(model.scale, model.scale, model.scale)
            q = p3d.Quat()
            q.setI(model.orient_x)
            q.setJ(model.orient_y)
            q.setK(model.orient_z)
            q.setR(model.orient_w)
            instance.setQuat(q)

        base.camLens.setFar(sys.maxint)
        base.camLens.setNear(8.0)

        pcore.attachLights(render)

        render.setShaderAuto()
        render.setTransparency(p3d.TransparencyAttrib.MDual, 1)
        render.setAntialias(p3d.AntialiasAttrib.MAuto)

        controls.KeyboardMovement()
        controls.ButtonUtils(scenepath)
        controls.MouseDrag(scenepath)
        controls.MouseCamera()
        controls.MouseScaleZoom(scenepath)
def load_into_bamfile(meshdata, subfiles, model):
    """Uses pycollada and panda3d to load meshdata and subfiles and
    write out to a bam file on disk"""

    if os.path.isfile(model.bam_file):
        print 'returning cached bam file'
        return model.bam_file

    mesh = load_mesh(meshdata, subfiles)
    model_name = model.model_json['full_path'].replace('/', '_')
    
    if model.model_type == 'progressive' and model.model_subtype == 'full':
        progressive_stream = model.model_json['metadata']['types']['progressive'].get('progressive_stream')
        if progressive_stream is not None:
            print 'LOADING PROGRESSIVE STREAM'
            data = model.prog_data
            try:
                mesh = add_back_pm.add_back_pm(mesh, StringIO(data), 100)
                print '-----'
                print 'SUCCESSFULLY ADDED BACK PM'
                print '-----'
            except:
                f = open(model.bam_file, 'w')
                f.close()
                raise

    print 'loading into bamfile', model_name, mesh
    scene_members = pandacore.getSceneMembers(mesh)
    print 'got scene members', model_name, mesh
    
    rotateNode = GeomNode("rotater")
    rotatePath = NodePath(rotateNode)
    matrix = numpy.identity(4)
    if mesh.assetInfo.upaxis == collada.asset.UP_AXIS.X_UP:
        r = collada.scene.RotateTransform(0,1,0,90)
        matrix = r.matrix
    elif mesh.assetInfo.upaxis == collada.asset.UP_AXIS.Y_UP:
        r = collada.scene.RotateTransform(1,0,0,90)
        matrix = r.matrix
    rotatePath.setMat(Mat4(*matrix.T.flatten().tolist()))

    for geom, renderstate, mat4 in scene_members:
        node = GeomNode("primitive")
        node.addGeom(geom)
        if renderstate is not None:
            node.setGeomState(0, renderstate)
        geomPath = rotatePath.attachNewNode(node)
        geomPath.setMat(mat4)
        
    print 'created np', model_name, mesh

    if model.model_type != 'optimized_unflattened' and model.model_type != 'progressive':
        print 'ABOUT TO FLATTEN'
        rotatePath.flattenStrong()
        print 'DONE FLATTENING'
        
    print 'flattened', model_name, mesh
    
    wrappedNode = pandacore.centerAndScale(rotatePath)
    wrappedNode.setName(model_name)

    wrappedNode.writeBamFile(model.bam_file)
    print 'saved', model_name, mesh
    wrappedNode = None
    
    return model.bam_file
Example #8
0
    def __init__(self, models):
        ShowBase.ShowBase.__init__(self)
        
        self.models = models
        
        unique_meshes = set(m.mesh for m in models)
        mesh2nodepath = {}
        for mesh in unique_meshes:
            scene_members = pcore.getSceneMembers(mesh)
            
            rotateNode = p3d.GeomNode("rotater")
            rotatePath = p3d.NodePath(rotateNode)
            matrix = numpy.identity(4)
            if mesh.assetInfo.upaxis == collada.asset.UP_AXIS.X_UP:
                r = collada.scene.RotateTransform(0,1,0,90)
                matrix = r.matrix
            elif mesh.assetInfo.upaxis == collada.asset.UP_AXIS.Y_UP:
                r = collada.scene.RotateTransform(1,0,0,90)
                matrix = r.matrix
            rotatePath.setMat(p3d.Mat4(*matrix.T.flatten().tolist()))
            
            rbc = p3d.RigidBodyCombiner('combiner')
            rbcPath = rotatePath.attachNewNode(rbc)
            
            for geom, renderstate, mat4 in scene_members:
                node = p3d.GeomNode("primitive")
                node.addGeom(geom)
                if renderstate is not None:
                    node.setGeomState(0, renderstate)
                geomPath = rbcPath.attachNewNode(node)
                geomPath.setMat(mat4)
                
            rbc.collect()

            mesh2nodepath[mesh] = centerAndScale(rotatePath, print_bounds.getBoundsInfo(mesh))

        scenepath = render.attachNewNode("scene")
        for model in self.models:
            np = mesh2nodepath[model.mesh]
            instance = scenepath.attachNewNode("model")
            np.instanceTo(instance)
            instance.setPos(model.x, model.y, model.z)
            instance.setScale(model.scale, model.scale, model.scale)
            q = p3d.Quat()
            q.setI(model.orient_x)
            q.setJ(model.orient_y)
            q.setK(model.orient_z)
            q.setR(model.orient_w)
            instance.setQuat(q)

        base.camLens.setFar(sys.maxint)
        base.camLens.setNear(8.0)

        pcore.attachLights(render)

        render.setShaderAuto()
        render.setTransparency(p3d.TransparencyAttrib.MDual, 1)
        render.setAntialias(p3d.AntialiasAttrib.MAuto)

        controls.KeyboardMovement()
        controls.ButtonUtils(scenepath)
        controls.MouseDrag(scenepath)
        controls.MouseCamera()
        controls.MouseScaleZoom(scenepath)
    def __init__(self, mesh_path, progressive_texture_path):

        resolutions = []
        f = tarfile.open(progressive_texture_path)
        for resolution_name in f.getnames():
            toset = {'size': resolution_name[:-4],
                     'contents': f.extractfile(resolution_name).read()}
            texpnm = PNMImage()
            texpnm.read(StringStream(toset['contents']), 'something.jpg')
            newtex = Texture()
            newtex.load(texpnm)
            toset['texture'] = newtex
            resolutions.append(toset)
        
        self.resolutions = resolutions
        def aux_loader(fname):
            return resolutions[0]['contents']
        mesh = collada.Collada(mesh_path, aux_file_loader=aux_loader)
        
        scene_members = getSceneMembers(mesh)
        
        base = ShowBase()
        
        rotateNode = GeomNode("rotater")
        rotatePath = render.attachNewNode(rotateNode)
        matrix = numpy.identity(4)
        if mesh.assetInfo.upaxis == collada.asset.UP_AXIS.X_UP:
            r = collada.scene.RotateTransform(0,1,0,90)
            matrix = r.matrix
        elif mesh.assetInfo.upaxis == collada.asset.UP_AXIS.Y_UP:
            r = collada.scene.RotateTransform(1,0,0,90)
            matrix = r.matrix
        rotatePath.setMat(Mat4(*matrix.T.flatten().tolist()))
        
        geom, renderstate, mat4 = scene_members[0]
        node = GeomNode("primitive")
        node.addGeom(geom)
        if renderstate is not None:
            node.setGeomState(0, renderstate)
        self.geomPath = rotatePath.attachNewNode(node)
        self.geomPath.setMat(mat4)
            
        wrappedNode = ensureCameraAt(self.geomPath, base.camera)
        base.disableMouse()
        attachLights(render)
        render.setShaderAuto()
        render.setTransparency(TransparencyAttrib.MDual, 1)
    
        base.render.analyze()
        KeyboardMovement()
        MouseDrag(wrappedNode)
        MouseScaleZoom(wrappedNode)
        MouseCamera()
        
        num_resolutions = len(resolutions) - 1
        self.slider = DirectSlider(range=(0, num_resolutions),
                                   value=0, pageSize=1,
                                   command=self.sliderMoved, pos=(0, 0, -.9), scale=1)
        for key, val in uiArgs.iteritems():
            self.slider.thumb[key] = val
        
        self.triText = OnscreenText(text="", pos=(-1,0.85), scale = 0.15,
                                    fg=(1, 0.5, 0.5, 1), align=TextNode.ALeft, mayChange=1)
        
        base.run()
Example #10
0
def getPmPerceptualError(mesh, pm_filebuf, mipmap_tarfilebuf):
    perceptualdiff = which('perceptualdiff')
    if perceptualdiff is None:
        raise Exception("perceptualdiff exectuable not found on path")

    pm_chunks = []

    if pm_filebuf is not None:
        data = pm_filebuf.read(PM_CHUNK_SIZE)
        refinements_read = 0
        num_refinements = None
        while len(data) > 0:
            (refinements_read, num_refinements, pm_refinements,
             data_left) = pdae_utils.readPDAEPartial(data, refinements_read,
                                                     num_refinements)
            pm_chunks.append(pm_refinements)
            data = data_left + pm_filebuf.read(PM_CHUNK_SIZE)

    tar = tarfile.TarFile(fileobj=mipmap_tarfilebuf)
    texsizes = []
    largest_tarinfo = (0, None)
    for tarinfo in tar:
        tarinfo.xsize = int(tarinfo.name.split('x')[0])
        if tarinfo.xsize > largest_tarinfo[0]:
            largest_tarinfo = (tarinfo.xsize, tarinfo)
        if tarinfo.xsize >= 128:
            texsizes.append(tarinfo)
    if len(texsizes) == 0:
        texsizes.append(largest_tarinfo[1])

    texsizes = sorted(texsizes, key=lambda t: t.xsize)
    texims = []
    first_image_data = None
    for tarinfo in texsizes:
        f = tar.extractfile(tarinfo)
        texdata = f.read()
        if first_image_data is None:
            first_image_data = texdata

        texpnm = PNMImage()
        texpnm.read(StringStream(texdata), 'something.jpg')
        newtex = Texture()
        newtex.load(texpnm)
        texims.append(newtex)

    mesh.images[0].setData(first_image_data)

    scene_members = getSceneMembers(mesh)

    # turn off panda3d printing to stdout
    nout = MultiplexStream()
    Notify.ptr().setOstreamPtr(nout, 0)
    nout.addFile(Filename(os.devnull))

    base = ShowBase()

    rotateNode = GeomNode("rotater")
    rotatePath = base.render.attachNewNode(rotateNode)
    matrix = numpy.identity(4)
    if mesh.assetInfo.upaxis == collada.asset.UP_AXIS.X_UP:
        r = collada.scene.RotateTransform(0, 1, 0, 90)
        matrix = r.matrix
    elif mesh.assetInfo.upaxis == collada.asset.UP_AXIS.Y_UP:
        r = collada.scene.RotateTransform(1, 0, 0, 90)
        matrix = r.matrix
    rotatePath.setMat(Mat4(*matrix.T.flatten().tolist()))
    geom, renderstate, mat4 = scene_members[0]
    node = GeomNode("primitive")
    node.addGeom(geom)
    if renderstate is not None:
        node.setGeomState(0, renderstate)
    geomPath = rotatePath.attachNewNode(node)
    geomPath.setMat(mat4)

    wrappedNode = ensureCameraAt(geomPath, base.camera)
    base.disableMouse()
    attachLights(base.render)
    base.render.setShaderAuto()
    base.render.setTransparency(TransparencyAttrib.MNone)
    base.render.setColorScaleOff(9999)

    controls.KeyboardMovement()
    controls.MouseDrag(wrappedNode)
    controls.MouseScaleZoom(wrappedNode)
    controls.ButtonUtils(wrappedNode)
    controls.MouseCamera()

    error_data = []

    try:
        tempdir = tempfile.mkdtemp(prefix='meshtool-print-pm-perceptual-error')

        triangleCounts = []

        hprs = [(0, 0, 0), (0, 90, 0), (0, 180, 0), (0, 270, 0), (90, 0, 0),
                (-90, 0, 0)]

        for texim in texims:
            np = base.render.find("**/rotater/collada")
            np.setTextureOff(1)
            np.setTexture(texim, 1)
            for angle, hpr in enumerate(hprs):
                wrappedNode.setHpr(*hpr)
                takeScreenshot(tempdir, base, geomPath, texim, angle)
        triangleCounts.append(getNumTriangles(geomPath))

        for pm_chunk in pm_chunks:
            pdae_panda.add_refinements(geomPath, pm_chunk)

            for texim in texims:
                np = base.render.find("**/rotater/collada")
                np.setTextureOff(1)
                np.setTexture(texim, 1)
                for angle, hpr in enumerate(hprs):
                    wrappedNode.setHpr(*hpr)
                    takeScreenshot(tempdir, base, geomPath, texim, angle)
            triangleCounts.append(getNumTriangles(geomPath))

        full_tris = triangleCounts[-1]
        full_tex = texims[-1]

        for numtris in triangleCounts:
            for texim in texims:
                pixel_diff = 0
                for angle, hpr in enumerate(hprs):
                    curFile = '%d_%d_%d_%d.png' % (numtris, texim.getXSize(),
                                                   texim.getYSize(), angle)
                    curFile = os.path.join(tempdir, curFile)

                    fullFile = '%d_%d_%d_%d.png' % (full_tris,
                                                    full_tex.getXSize(),
                                                    full_tex.getYSize(), angle)
                    fullFile = os.path.join(tempdir, fullFile)

                    try:
                        output = subprocess.check_output([
                            perceptualdiff, '-threshold', '1', fullFile,
                            curFile
                        ])
                    except subprocess.CalledProcessError as ex:
                        output = ex.output

                    output = output.strip()
                    if len(output) > 0:
                        pixel_diff = max(pixel_diff,
                                         int(output.split('\n')[1].split()[0]))

                error_data.append({
                    'triangles': numtris,
                    'width': texim.getXSize(),
                    'height': texim.getYSize(),
                    'pixel_error': pixel_diff
                })

    finally:
        shutil.rmtree(tempdir, ignore_errors=True)

    return error_data
def getPmPerceptualError(mesh, pm_filebuf, mipmap_tarfilebuf):
    perceptualdiff = which('perceptualdiff')
    if perceptualdiff is None:
        raise Exception("perceptualdiff exectuable not found on path")
    
    pm_chunks = []
    
    if pm_filebuf is not None:
        data = pm_filebuf.read(PM_CHUNK_SIZE)
        refinements_read = 0
        num_refinements = None
        while len(data) > 0:
            (refinements_read, num_refinements, pm_refinements, data_left) = pdae_utils.readPDAEPartial(data, refinements_read, num_refinements)
            pm_chunks.append(pm_refinements)
            data = data_left + pm_filebuf.read(PM_CHUNK_SIZE)
    
    tar = tarfile.TarFile(fileobj=mipmap_tarfilebuf)
    texsizes = []
    largest_tarinfo = (0, None)
    for tarinfo in tar:
        tarinfo.xsize = int(tarinfo.name.split('x')[0])
        if tarinfo.xsize > largest_tarinfo[0]:
            largest_tarinfo = (tarinfo.xsize, tarinfo)
        if tarinfo.xsize >= 128:
            texsizes.append(tarinfo)
    if len(texsizes) == 0:
        texsizes.append(largest_tarinfo[1])
    
    texsizes = sorted(texsizes, key=lambda t: t.xsize)
    texims = []
    first_image_data = None
    for tarinfo in texsizes:
        f = tar.extractfile(tarinfo)
        texdata = f.read()
        if first_image_data is None:
            first_image_data = texdata
        
        texpnm = PNMImage()
        texpnm.read(StringStream(texdata), 'something.jpg')
        newtex = Texture()
        newtex.load(texpnm)
        texims.append(newtex)
    
    mesh.images[0].setData(first_image_data)
    
    scene_members = getSceneMembers(mesh)
    
    # turn off panda3d printing to stdout
    nout = MultiplexStream()
    Notify.ptr().setOstreamPtr(nout, 0)
    nout.addFile(Filename(os.devnull))
    
    base = ShowBase()
    
    rotateNode = GeomNode("rotater")
    rotatePath = base.render.attachNewNode(rotateNode)
    matrix = numpy.identity(4)
    if mesh.assetInfo.upaxis == collada.asset.UP_AXIS.X_UP:
        r = collada.scene.RotateTransform(0,1,0,90)
        matrix = r.matrix
    elif mesh.assetInfo.upaxis == collada.asset.UP_AXIS.Y_UP:
        r = collada.scene.RotateTransform(1,0,0,90)
        matrix = r.matrix
    rotatePath.setMat(Mat4(*matrix.T.flatten().tolist()))
    geom, renderstate, mat4 = scene_members[0]
    node = GeomNode("primitive")
    node.addGeom(geom)
    if renderstate is not None:
        node.setGeomState(0, renderstate)
    geomPath = rotatePath.attachNewNode(node)
    geomPath.setMat(mat4)
        
    wrappedNode = ensureCameraAt(geomPath, base.camera)
    base.disableMouse()
    attachLights(base.render)
    base.render.setShaderAuto()
    base.render.setTransparency(TransparencyAttrib.MNone)
    base.render.setColorScaleOff(9999)
    
    controls.KeyboardMovement()
    controls.MouseDrag(wrappedNode)
    controls.MouseScaleZoom(wrappedNode)
    controls.ButtonUtils(wrappedNode)
    controls.MouseCamera()
    
    error_data = []
    
    try:
        tempdir = tempfile.mkdtemp(prefix='meshtool-print-pm-perceptual-error')
        
        triangleCounts = []
        
        hprs = [(0, 0, 0),
                (0, 90, 0),
                (0, 180, 0),
                (0, 270, 0),
                (90, 0, 0),
                (-90, 0, 0)]
        
        for texim in texims:
            np = base.render.find("**/rotater/collada")
            np.setTextureOff(1)
            np.setTexture(texim, 1)
            for angle, hpr in enumerate(hprs):
                wrappedNode.setHpr(*hpr)
                takeScreenshot(tempdir, base, geomPath, texim, angle)
        triangleCounts.append(getNumTriangles(geomPath))
        
        for pm_chunk in pm_chunks:
            pdae_panda.add_refinements(geomPath, pm_chunk)
            
            for texim in texims:
                np = base.render.find("**/rotater/collada")
                np.setTextureOff(1)
                np.setTexture(texim, 1)
                for angle, hpr in enumerate(hprs):
                    wrappedNode.setHpr(*hpr)
                    takeScreenshot(tempdir, base, geomPath, texim, angle)
            triangleCounts.append(getNumTriangles(geomPath))
        
        full_tris = triangleCounts[-1]
        full_tex = texims[-1]
        
        for numtris in triangleCounts:
            for texim in texims:
                pixel_diff = 0
                for angle, hpr in enumerate(hprs):
                    curFile = '%d_%d_%d_%d.png' % (numtris, texim.getXSize(), texim.getYSize(), angle)
                    curFile = os.path.join(tempdir, curFile)
                    
                    fullFile = '%d_%d_%d_%d.png' % (full_tris, full_tex.getXSize(), full_tex.getYSize(), angle)
                    fullFile = os.path.join(tempdir, fullFile)
                    
                    try:
                        output = subprocess.check_output([perceptualdiff, '-threshold', '1', fullFile, curFile])
                    except subprocess.CalledProcessError as ex:
                        output = ex.output
                    
                    output = output.strip()
                    if len(output) > 0:
                        pixel_diff = max(pixel_diff, int(output.split('\n')[1].split()[0]))
                    
                error_data.append({'triangles': numtris,
                                   'width': texim.getXSize(),
                                   'height': texim.getYSize(),
                                   'pixel_error': pixel_diff})
    
    finally:
        shutil.rmtree(tempdir, ignore_errors=True)
        
    return error_data