Skip to content
Snippets Groups Projects
import_c3d.py 9.97 KiB
Newer Older
  • Learn to ignore specific revisions
  • # ##### BEGIN GPL LICENSE BLOCK #####
    #
    #  This program is free software; you can redistribute it and/or
    #  modify it under the terms of the GNU General Public License
    #  as published by the Free Software Foundation; either version 3
    #  of the License, or (at your option) any later version.
    #
    #  This program is distributed in the hope that it will be useful,
    #  but WITHOUT ANY WARRANTY; without even the implied warranty of
    #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    #  GNU General Public License for more details.
    #
    #  You should have received a copy of the GNU General Public License
    #  along with this program; if not, write to the Free Software Foundation,
    #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
    #
    # ##### END GPL LICENSE BLOCK #####
    
    # <pep8-80 compliant>
    
    # By Daniel Monteiro Basso, April-November 2011.
    
    # This script was developed with financial support from the Foundation for
    # Science and Technology of Portugal, under the grant SFRH/BD/66452/2009.
    
    # Complete rewrite, but based on the original importer for Blender
    # 2.39, developed by Jean-Baptiste PERIN (jb_perin(at)yahoo.fr), which was
    # based on the MATLAB C3D loader from Alan Morris, Toronto, October 1998
    # and Jaap Harlaar, Amsterdam, april 2002
    
    
    import struct
    try:
        from numpy import array as vec  # would be nice to have NumPy in Blender
    except:
        from mathutils import Vector as vec
    
    
    class Marker:
        position = (0., 0., 0.)
        confidence = -1.
    
    
    class Parameter:
        def __init__(self, infile):
            (nameLength, self.paramIdx) = struct.unpack('bb', infile.read(2))
            if not nameLength:
                self.name = ''
                return
    
            nameLength = abs(nameLength)  # negative flags something
            if nameLength > 64:
    
                raise ValueError
            self.name = infile.read(nameLength).decode('ascii')
            (offset, b) = struct.unpack('hb', infile.read(3))
            if self.paramIdx > 0:
                self.isGroup = False
                self.data = infile.read(offset - 3)
            else:
                self.isGroup = True
                self.paramIdx *= -1
                self.description = infile.read(b)
                self.params = {}
    
        def collect(self, infile):
            while True:
                p = Parameter(infile)
                if not p.name or p.isGroup:
                    return p
                self.params[p.name] = p
    
        def decode(self):
            # for now only decode labels
            l, c = struct.unpack('BB', self.data[1:3])
            return [self.data[3 + i:3 + i + l].strip().decode('ascii')
                    for i in range(0, l * c, l)]
    
    
    class MarkerSet:
        def __init__(self, fileName, scale=1., stripPrefix=True, onlyHeader=False):
            self.fileName = fileName
            if fileName.endswith('.csv'):
                with open(fileName, 'rt') as infile:
                    self.readCSV(infile)
                return
            if onlyHeader:
                self.infile = open(fileName, 'rb')
                self.readHeader(self.infile, scale)
                self.identifyMarkerPrefix(stripPrefix)
                self.infile.seek(512 * (self.dataBlock - 1))
                self.frames = []
                return
            with open(fileName, 'rb') as infile:
                self.readHeader(infile, scale)
                self.identifyMarkerPrefix(stripPrefix)
                self.readFrameData(infile)
    
        def readCSV(self, infile):
            import csv
            csvr = csv.reader(infile)
            header = next(csvr)
            if 0 != len(header) % 3:
                raise Exception('Incorrect data format in CSV file')
            self.markerLabels = [label[:-2] for label in header[::3]]
            self.frames = []
            for framerow in csvr:
                newFrame = []
                for c in range(0, len(framerow), 3):
                    m = Marker()
                    try:
                        m.position = vec([float(v) for v in framerow[c:c + 3]])
                        m.confidence = 1.
                    except:
                        pass
                    newFrame.append(m)
                self.frames.append(newFrame)
            self.startFrame = 0
            self.endFrame = len(self.frames) - 1
            self.scale = 1.
    
        def writeCSV(self, fileName, applyScale=True, mfilter=[]):
            import csv
            with open(fileName, 'w') as fo:
                o = csv.writer(fo)
                appxyz = lambda m: [m + a for a in ('_X', '_Y', '_Z')]
                explabels = (appxyz(m) for m in self.markerLabels
                             if not mfilter or m in mfilter)
                o.writerow(sum(explabels, []))
                fmt = lambda m: tuple('{0:.4f}'.format(
                    a * (self.scale if applyScale else 1.))
                    for a in m.position)
                nan = ('NaN', 'NaN', 'NaN')
                if mfilter:
                    mfilter = [self.markerLabels.index(m)
                                for m in self.markerLabels if m in mfilter]
                for f in self.frames:
                    F = f
                    if mfilter:
                        F = [m for i, m in enumerate(f) if i in mfilter]
                    expmarkers = (m.confidence < 0 and nan or fmt(m) for m in F)
                    o.writerow(sum(expmarkers, ()))
    
        def identifyMarkerPrefix(self, stripPrefix):
            prefix = self.markerLabels[0]
            for ml in self.markerLabels[1:]:
                if len(ml) < len(prefix):
                    prefix = prefix[:len(ml)]
                if not prefix:
                    break
                for i in range(len(prefix)):
                    if prefix[i] != ml[i]:
                        prefix = prefix[:i]
                        break
            self.prefix = prefix
            if stripPrefix:
                p = len(self.prefix)
                self.markerLabels = [ml[p:] for ml in self.markerLabels]
    
        def readHeader(self, infile, scale):
            (self.firstParameterBlock, key, self.markerCount, bogus,
             self.startFrame, self.endFrame,
             bogus) = struct.unpack('BBhhhhh', infile.read(12))
            if key != 80:
                raise Exception('Not a C3D file.')
            self.readParameters(infile)
            infile.seek(12)
            td = infile.read(12)
            if self.procType == 2:
                td = td[2:4] + td[:2] + td[4:8] + td[10:] + td[8:10]
            (self.scale, self.dataBlock, bogus,
             self.frameRate) = struct.unpack('fhhf', td)
            self.scale *= scale
            if self.scale < 0:
    
                if self.procType == 2:
                    self.readMarker = self.readFloatMarkerInvOrd
                else:
                    self.readMarker = self.readFloatMarker
    
                self.scale *= -1
            else:
                self.readMarker = self.readShortMarker
    
        def readParameters(self, infile):
            infile.seek(512 * (self.firstParameterBlock - 1))
            (ig, ig, pointIdx,
             self.procType) = struct.unpack('BBBB', infile.read(4))
            self.procType -= 83
    
            if self.procType not in {1, 2}:
    
                # 1(INTEL-PC); 2(DEC-VAX); 3(MIPS-SUN/SGI)
                print('Warning: importer was not tested for files from '
                      'architectures other than Intel-PC and DEC-VAX')
                print('Type: {0}'.format(self.procType))
            self.paramGroups = {}
            g = Parameter(infile)
            self.paramGroups[g.name] = g
            while(True):
                g = g.collect(infile)
                if not g.name:
                    break
                self.paramGroups[g.name] = g
    
            for pg in self.paramGroups:
                #print("group: " + pg)
                #for p in self.paramGroups[pg].params:
                #    print("   * " + p)
                if 'LABELS' in self.paramGroups[pg].params:
                    break
            # pg should be 'POINT', but let's be liberal and accept any group
            self.markerLabels = self.paramGroups[pg].params['LABELS'].decode()
    
    
        def readMarker(self, infile):
            pass  # ...
    
        def readFloatMarker(self, infile):
            m = Marker()
            x, y, z, m.confidence = struct.unpack('ffff', infile.read(16))
            m.position = (x * self.scale, y * self.scale, z * self.scale)
            return m
    
    
        def readFloatMarkerInvOrd(self, infile):
            m = Marker()
            inv = lambda f: f[2:] + f[:2]
            i = lambda: inv(infile.read(4))
            x, y, z, m.confidence = struct.unpack('ffff', i() + i() + i() + i())
            m.position = (x * self.scale, y * self.scale, z * self.scale)
            return m
    
    
        def readShortMarker(self, infile):
            m = Marker()
            x, y, z, m.confidence = struct.unpack('hhhh', infile.read(8))
            m.position = (x * self.scale, y * self.scale, z * self.scale)
            return m
    
        def readFrameData(self, infile):
            infile.seek(512 * (self.dataBlock - 1))
            self.frames = []
            for f in range(self.startFrame, self.endFrame + 1):
                frame = [self.readMarker(infile) for m in range(self.markerCount)]
                self.frames.append(frame)
    
        def readNextFrameData(self):
            if len(self.frames) < (self.endFrame - self.startFrame + 1):
                frame = [self.readMarker(self.infile)
                    for m in range(self.markerCount)]
                self.frames.append(frame)
            return self.frames[-1]
    
        def getFramesByMarker(self, marker):
            if type(marker) == int:
                idx = marker
            else:
                idx = self.markerLabels.index(marker)
            fcnt = self.endFrame - self.startFrame + 1
            return [self.frames[f][idx] for f in range(fcnt)]
    
        def getMarker(self, marker, frame):
            idx = self.markerLabels.index(marker)
            return self.frames[frame - self.startFrame][idx]
    
    
    def read(filename, *a, **kw):
        return MarkerSet(filename, *a, **kw)
    
    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
    
    
    if __name__ == '__main__':
        import os
        import sys
    
        sys.argv.pop(0)
        if not sys.argv:
            print("Convert C3D to CSV.\n"
                  "Please specify at least one C3D input file.")
            raise SystemExit
        while sys.argv:
            fname = sys.argv.pop(0)
            markerset = read(fname)
            print("frameRate={0.frameRate}\t"
                  "scale={0.scale:.2f}\t"
                  "markers={0.markerCount}\t"
                  "startFrame={0.startFrame}\t"
                  "endFrame={0.endFrame}".format(markerset))
            markerset.writeCSV(fname.lower().replace(".c3d", ".csv"))