From 549d2c5675490f49fbe6adc37bb211c44ec75220 Mon Sep 17 00:00:00 2001 From: Aleksey Lim Date: Tue, 10 Feb 2009 17:45:00 +0000 Subject: Refactoring model, checkpoint #1 --- diff --git a/activity.py b/activity.py index a0943c6..ab16e94 100644 --- a/activity.py +++ b/activity.py @@ -55,6 +55,7 @@ class flipsticksActivity(activity.Activity): innerframe.add(self.app.main) self.set_canvas(outerframe) + """ def read_file(self, filepath): f = file(filepath) sdata = f.read() @@ -66,3 +67,4 @@ class flipsticksActivity(activity.Activity): f = open(filepath,'w') f.write(sdata) f.close() + """ diff --git a/flipsticks.py b/flipsticks.py index 80c403d..1498ed8 100644 --- a/flipsticks.py +++ b/flipsticks.py @@ -27,6 +27,8 @@ import math import textwrap import pickle +import model +import kinematic from theme import * def prepare_btn(btn, w=-1, h=-1): @@ -54,13 +56,6 @@ def inarea(x,y,awidth,aheight): return False return True -def interpolate(x,x0,y0,x1,y1): - if x1-x0 == 0: - return y0 - m = float(y1-y0)/float(x1-x0) - y = y0 + ((x-x0)*m) - return y - def getpoints(x,y,angle,len): nx = int(round(x + (len * math.cos(math.radians(angle))))) ny = int(round(y - (len * math.sin(math.radians(angle))))) @@ -186,7 +181,7 @@ class flipsticks: if self.kfpressed >= 0: if inarea(x,y,KEYFRAMEWIDTH,KEYFRAMEHEIGHT): xdiff = x-self.keyframes[self.kfpressed] - self.shiftjoints(xdiff,0,self.kfsjoints[self.kfpressed]) + self.shiftjoints(xdiff,0, model.keys[self.kfpressed].scaled_joints) self.keyframes[self.kfpressed] = x self.drawkeyframe() return True @@ -207,10 +202,10 @@ class flipsticks: def syncmaintokf(self): # set the main window to the keyframe - if self.kfsticks[self.kfselected]: - self.sticks = self.kfsticks[self.kfselected].copy() - self.parts = self.kfparts[self.kfselected].copy() - self.middle = self.kfmiddles[self.kfselected] + if model.keys[self.kfselected].sticks: + self.sticks = model.keys[self.kfselected].sticks.copy() + self.parts = model.keys[self.kfselected].parts.copy() + self.middle = model.keys[self.kfselected].middle self.setjoints() self.drawmainframe() @@ -347,125 +342,32 @@ class flipsticks: self.drawmainframe() def setframe(self, widget, data=None): - self.kfmiddles[self.kfselected] = self.middle - self.kfparts[self.kfselected] = self.parts.copy() - self.kfsticks[self.kfselected] = self.sticks.copy() - self.kfssticks[self.kfselected] = self.sticks.copy() - scalesticks(self.kfssticks[self.kfselected],.2) - self.kfjoints[self.kfselected] = self.joints.copy() - self.kfsjoints[self.kfselected] = self.initjoints() - #x, y, width, height = self.kfdraw.get_allocation() - #y = int(height/2.0) - #y = int(KEYFRAMEHEIGHT/2.0)-5 - y = int(KEYFRAMEHEIGHT/2.0) - x = self.keyframes[self.kfselected] - kfmiddle = (x,y) - self.setjoints(self.kfsjoints[self.kfselected],self.kfssticks[self.kfselected],kfmiddle) + v = model.KeyFrame() + v.middle = self.middle + v.parts = self.parts + v.sticks = self.sticks + v.joints = self.joints + + model.keys[self.kfselected].assign(v) self.drawkeyframe() def clearframe(self, widget, data=None): - self.kfsticks[self.kfselected] = None - self.kfssticks[self.kfselected] = None - self.kfjoints[self.kfselected] = None - self.kfsjoints[self.kfselected] = None - self.kfparts[self.kfselected] = None + model.keys[self.kfselected].clear() self.drawkeyframe() - def intjoints(self,sjoints,ejoints,count,numpoints): - # numpoints: number of points between start and end - # count: point were getting now - ijoints = {} - for jname in sjoints: - (x0,y0) = sjoints[jname] - (x1,y1) = ejoints[jname] - #print 'x0:%s,y0:%s' % (x0,y0) - #print 'x1:%s,y1:%s' % (x1,y1) - x = x0 + (count * ((x1-x0)/float(numpoints))) - y = interpolate(x,x0,y0,x1,y1) - ijoints[jname] = (int(x),int(y)) - return ijoints - - def intparts(self,sparts,eparts,count,numpoints): - iparts = {} - for pname in sparts: - x0 = sparts[pname] - x1 = eparts[pname] - if x0 == x1: - iparts[pname] = x0 - continue - x = x0 + (count * ((x1-x0)/float(numpoints))) - iparts[pname] = int(x) - return iparts - - def inthsize(self,shsize,ehsize,count,numpoints): - x0 = shsize - x1 = ehsize - if x0 == x1: - return x0 - x = x0 + (count * ((x1-x0)/float(numpoints))) - return int(x) - - def intmiddle(self,smiddle,emiddle,count,numpoints): - (x0,y0) = smiddle - (x1,y1) = emiddle - x = x0 + (count * ((x1-x0)/float(numpoints))) - y = interpolate(x,x0,y0,x1,y1) - return (int(x),int(y)) - def makeframes(self): - endsecs = KEYFRAMEWIDTH - fint = int(endsecs/float(TOTALFRAMES)) # frame interval self.frames = {} self.fparts = {} self.fmiddles = {} self.fhsize = {} - kf = {} # point to keyframes by x-middle (which represents a time, like seconds) - for i in range(len(self.keyframes)): - secs = self.keyframes[i] - #kf[secs] = i - # use self.kfjoints[kf[secs]] and self.kfparts[kf[secs]] - if self.kfjoints[i]: - self.frames[secs] = self.kfjoints[i].copy() - self.fparts[secs] = self.kfparts[i].copy() - self.fmiddles[secs] = self.kfmiddles[i] - #print '%s:KFMIDDLE:%s = (%s,%s)' % (i,secs,self.fmiddles[secs][0],self.fmiddles[secs][1]) - self.fhsize[secs] = self.kfsticks[i]['HEAD'][1] - fsecs = self.frames.keys() - fsecs.sort() - if not fsecs: - return - # ADD frame at 0 - self.frames[0] = self.frames[fsecs[0]].copy() - self.fparts[0] = self.fparts[fsecs[0]].copy() - self.fmiddles[0] = self.fmiddles[fsecs[0]] - self.fhsize[0] = self.fhsize[fsecs[0]] - # ADD frame at end - self.frames[endsecs] = self.frames[fsecs[-1]].copy() - self.fparts[endsecs] = self.fparts[fsecs[-1]].copy() - self.fmiddles[endsecs] = self.fmiddles[fsecs[-1]] - self.fhsize[endsecs] = self.fhsize[fsecs[-1]] - # now fill in frames between - fsecs = self.frames.keys() - fsecs.sort() - for i in range(len(fsecs)): - if i == len(fsecs)-1: - continue # nothing after end - startsecs = fsecs[i] - endsecs = fsecs[i+1] - numframes = int((endsecs-startsecs)/float(fint))-1 - #print 'NUMFRAMES(%s):%s' % (i,numframes) - for j in range(numframes-1): # MAYBE SHOULD BE numframes - secs = startsecs + ((j+1)*fint) - self.frames[secs] = self.intjoints(self.frames[startsecs],self.frames[endsecs], - j+1,numframes) - self.fparts[secs] = self.intparts(self.fparts[startsecs],self.fparts[endsecs], - j+1,numframes) - self.fmiddles[secs] = self.intmiddle(self.fmiddles[startsecs],self.fmiddles[endsecs], - j+1,numframes) - self.fhsize[secs] = self.inthsize(self.fhsize[startsecs],self.fhsize[endsecs], - j+1,numframes) - #print '%s,%s(%s secs):(%s,%s) START(%s,%s) - END(%s,%s) startsecs:%s endsecs:%s numframes:%s' % (i,j,secs,self.fmiddles[secs][0],self.fmiddles[secs][1],self.fmiddles[startsecs][0],self.fmiddles[startsecs][1],self.fmiddles[endsecs][0],self.fmiddles[endsecs][1],startsecs,endsecs,numframes) - #print self.frames.keys() + + frames = kinematic.makeframes() + + for key, value in frames.items(): + self.frames[key] = value.joints + self.fparts[key] = value.parts + self.fmiddles[key] = value.middle + self.fhsize[key] = value.hsize def shiftjoints(self,xdiff,ydiff,joints=None): if not joints: @@ -666,13 +568,13 @@ class flipsticks: # then the inner circle drawgc.set_foreground(white) self.kfpixmap.draw_arc(drawgc,True,x-35,y-35,70,70,0,360*64) - if self.kfssticks[i]: + if model.keys[i].scaled_sticks: # draw a man in the circle drawgc.set_foreground(black) - hsize = self.kfssticks[i]['HEAD'][1] - rhsize = int(self.kfparts[i]['RIGHT HAND']*0.2) - lhsize = int(self.kfparts[i]['LEFT HAND']*0.2) - self.drawstickman(drawgc,self.kfpixmap,(x,y),self.kfsjoints[i],hsize,rhsize,lhsize) + hsize = model.keys[i].scaled_sticks['HEAD'][1] + rhsize = int(model.keys[i].parts['RIGHT HAND']*0.2) + lhsize = int(model.keys[i].parts['LEFT HAND']*0.2) + self.drawstickman(drawgc,self.kfpixmap,(x,y), model.keys[i].scaled_joints,hsize,rhsize,lhsize) #self.kfpixmap.draw_arc(drawgc,True,x-5,y-5,10,10,0,360*64) self.kfdraw.queue_draw() @@ -819,10 +721,10 @@ class flipsticks: widget.set_image(playimg) self.playing = False # set the main window to the keyframe - if self.kfsticks[self.kfselected]: - self.sticks = self.kfsticks[self.kfselected].copy() - self.parts = self.kfparts[self.kfselected].copy() - self.middle = self.kfmiddles[self.kfselected] + if model.keys[self.kfselected]: + self.sticks = model.keys[self.kfselected].sticks.copy() + self.parts = model.keys[self.kfselected].parts.copy() + self.middle = model.keys[self.kfselected].middle self.setjoints() self.drawmainframe() self.updateentrybox() @@ -849,10 +751,10 @@ class flipsticks: widget.set_image(playimg) self.playing = False # set the main window to the keyframe - if self.kfsticks[self.kfselected]: - self.sticks = self.kfsticks[self.kfselected].copy() - self.parts = self.kfparts[self.kfselected].copy() - self.middle = self.kfmiddles[self.kfselected] + if model.keys[self.kfselected]: + self.sticks = model.keys[self.kfselected].sticks.copy() + self.parts = model.keys[self.kfselected].parts.copy() + self.middle = model.keys[self.kfselected].middle self.setjoints() self.drawmainframe() self.updateentrybox() @@ -875,33 +777,6 @@ class flipsticks: self.playingbackwards = False self.playing = gobject.timeout_add(self.waittime, self.playframe) - def getsdata(self): - self.makeframes() - sdd = {} # save data dictionary - sdd['kfmiddles'] = self.kfmiddles - sdd['kfparts'] = self.kfparts - sdd['kfsticks'] = self.kfsticks - sdd['kfssticks'] = self.kfssticks - sdd['kfjoints'] = self.kfjoints - sdd['kfsjoints'] = self.kfsjoints - sdd['keyframes'] = self.keyframes - sdd['kfselected'] = self.kfselected - return pickle.dumps(sdd) - - def restore(self, sdata): - sdd = pickle.loads(sdata) - self.kfmiddles = sdd['kfmiddles'] - self.kfparts = sdd['kfparts'] - self.kfsticks = sdd['kfsticks'] - self.kfssticks = sdd['kfssticks'] - self.kfjoints = sdd['kfjoints'] - self.kfsjoints = sdd['kfsjoints'] - self.keyframes = sdd['keyframes'] - self.kfselected = sdd['kfselected'] - self.drawkeyframe() - self.syncmaintokf() - self.updateentrybox() - def __init__(self, toplevel_window, mdirpath): self.playing = False self.playingbackwards = False @@ -919,12 +794,15 @@ class flipsticks: self.stickselected = 'RIGHT SHOULDER' self.laststickselected = None self.keyframes = KEYFRAMES - self.kfsticks = [None,None,None,None,None] - self.kfssticks = [None,None,None,None,None] - self.kfjoints = [None,None,None,None,None] - self.kfsjoints = [None,None,None,None,None] - self.kfmiddles = [None,None,None,None,None] - self.kfparts = [None,None,None,None,None] + + #self.kfsticks = [None,None,None,None,None] + #self.kfssticks = [None,None,None,None,None] + #self.kfjoints = [None,None,None,None,None] + #self.kfsjoints = [None,None,None,None,None] + #self.kfmiddles = [None,None,None,None,None] + #self.kfparts = [None,None,None,None,None] + + self.kfselected = 0 self.joints = self.initjoints() self.setjoints() diff --git a/kinematic.py b/kinematic.py new file mode 100644 index 0000000..12249d3 --- /dev/null +++ b/kinematic.py @@ -0,0 +1,241 @@ +# 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 2 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 St, Fifth Floor, Boston, MA 02110-1301 USA + +import math + +import model +import theme + +class Frame: + def __init__(self, keyframe=None): + if keyframe: + self.assign(keyframe) + else: + self.joints = None + self.parts = None + self.middle = None + self.hsize = None + + def assign(self, keyframe): + self.joints = keyframe.joints.copy() + self.parts = keyframe.parts.copy() + self.middle = keyframe.middle + self.hsize = keyframe.sticks['HEAD'][1] + +def makeframes(): + frames = {} + + for i in model.keys: + if not i.empty(): + frames[i.x] = Frame(i) + + if not frames: + return {} + + fsecs = frames.keys() + fsecs.sort() + + # set border frames + frames[0] = frames[fsecs[0]] + frames[theme.KEYFRAMEWIDTH] = frames[fsecs[-1]] + + # now fill in frames between + fsecs = frames.keys() + fsecs.sort() + + # frame interval + fint = int(theme.KEYFRAMEWIDTH/float(theme.TOTALFRAMES)) + + for i in range(len(fsecs)): + if i == len(fsecs)-1: + continue # nothing after end + + startsecs = fsecs[i] + endsecs = fsecs[i+1] + start_frame = frames[startsecs] + end_frame = frames[endsecs] + numframes = int((endsecs-startsecs)/float(fint))-1 + + for j in range(numframes-1): # MAYBE SHOULD BE numframes + frame = frames[startsecs + ((j+1)*fint)] = Frame() + + frame.joints = _intjoints(start_frame.joints, end_frame.joints, + j+1, numframes) + frame.parts = _intparts(start_frame.parts, end_frame.parts, + j+1, numframes) + frame.middle = _intmiddle(start_frame.middle, end_frame.middle, + j+1, numframes) + frame.hsize = _inthsize(start_frame.hsize, end_frame.hsize, + j+1, numframes) + + return frames + +def getparentjoint(jname, joints, middle): + if jname in ['rightshoulder','leftshoulder','groin','neck']: + return middle + + parentjoints = {'rightelbow':'rightshoulder', + 'righthand':'rightelbow', + 'leftelbow':'leftshoulder', + 'lefthand':'leftelbow', + 'righthip':'groin', + 'rightknee':'righthip', + 'rightheel':'rightknee', + 'righttoe':'rightheel', + 'lefthip':'groin', + 'leftknee':'lefthip', + 'leftheel':'leftknee', + 'lefttoe':'leftheel', + 'head':'neck'} + + return joints[parentjoints[jname]] + +def getparentsticks(stickname): + if stickname in ['RIGHT SHOULDER','LEFT SHOULDER','NECK','TORSO']: + return [] + if stickname in ['HEAD']: + return ['NECK'] + if stickname == 'UPPER RIGHT ARM': + return ['RIGHT SHOULDER'] + if stickname == 'LOWER RIGHT ARM': + return ['UPPER RIGHT ARM','RIGHT SHOULDER'] + if stickname == 'UPPER LEFT ARM': + return ['LEFT SHOULDER'] + if stickname == 'LOWER LEFT ARM': + return ['UPPER LEFT ARM','LEFT SHOULDER'] + if stickname == 'RIGHT HIP': + return ['TORSO'] + if stickname == 'UPPER RIGHT LEG': + return ['RIGHT HIP','TORSO'] + if stickname == 'LOWER RIGHT LEG': + return ['UPPER RIGHT LEG','RIGHT HIP','TORSO'] + if stickname == 'RIGHT FOOT': + return ['LOWER RIGHT LEG','UPPER RIGHT LEG','RIGHT HIP','TORSO'] + if stickname == 'LEFT HIP': + return ['TORSO'] + if stickname == 'UPPER LEFT LEG': + return ['LEFT HIP','TORSO'] + if stickname == 'LOWER LEFT LEG': + return ['UPPER LEFT LEG','LEFT HIP','TORSO'] + if stickname == 'LEFT FOOT': + return ['LOWER LEFT LEG','UPPER LEFT LEG','LEFT HIP','TORSO'] + +def getpoints(x,y,angle,len): + nx = int(round(x + (len * math.cos(math.radians(angle))))) + ny = int(round(y - (len * math.sin(math.radians(angle))))) + return (nx,ny) + +def _interpolate(x,x0,y0,x1,y1): + if x1-x0 == 0: + return y0 + m = float(y1-y0)/float(x1-x0) + y = y0 + ((x-x0)*m) + return y + +def _intjoints(sjoints, ejoints, count, numpoints): + # numpoints: number of points between start and end + # count: point were getting now + ijoints = {} + for jname in sjoints: + (x0,y0) = sjoints[jname] + (x1,y1) = ejoints[jname] + #print 'x0:%s,y0:%s' % (x0,y0) + #print 'x1:%s,y1:%s' % (x1,y1) + x = x0 + (count * ((x1-x0)/float(numpoints))) + y = _interpolate(x,x0,y0,x1,y1) + ijoints[jname] = (int(x),int(y)) + return ijoints + +def _intparts(sparts,eparts,count,numpoints): + iparts = {} + for pname in sparts: + x0 = sparts[pname] + x1 = eparts[pname] + if x0 == x1: + iparts[pname] = x0 + continue + x = x0 + (count * ((x1-x0)/float(numpoints))) + iparts[pname] = int(x) + return iparts + +def _intmiddle(smiddle,emiddle,count,numpoints): + (x0,y0) = smiddle + (x1,y1) = emiddle + x = x0 + (count * ((x1-x0)/float(numpoints))) + y = _interpolate(x,x0,y0,x1,y1) + return (int(x),int(y)) + +def _inthsize(shsize,ehsize,count,numpoints): + x0 = shsize + x1 = ehsize + if x0 == x1: + return x0 + x = x0 + (count * ((x1-x0)/float(numpoints))) + return int(x) + + +""" + def makeframes(self): + endsecs = KEYFRAMEWIDTH + fint = int(endsecs/float(TOTALFRAMES)) # frame interval + self.frames = {} + self.fparts = {} + self.fmiddles = {} + self.fhsize = {} + for i in range(len(self.keyframes)): + secs = self.keyframes[i] + if model.keys[i].joints: + self.frames[secs] = model.keys[i].joints.copy() + self.fparts[secs] = model.keys[i].parts.copy() + self.fmiddles[secs] = model.keys[i].middle + #print '%s:KFMIDDLE:%s = (%s,%s)' % (i,secs,self.fmiddles[secs][0],self.fmiddles[secs][1]) + self.fhsize[secs] = model.keys[i].sticks['HEAD'][1] + fsecs = self.frames.keys() + fsecs.sort() + if not fsecs: + return + # ADD frame at 0 + self.frames[0] = self.frames[fsecs[0]].copy() + self.fparts[0] = self.fparts[fsecs[0]].copy() + self.fmiddles[0] = self.fmiddles[fsecs[0]] + self.fhsize[0] = self.fhsize[fsecs[0]] + # ADD frame at end + self.frames[endsecs] = self.frames[fsecs[-1]].copy() + self.fparts[endsecs] = self.fparts[fsecs[-1]].copy() + self.fmiddles[endsecs] = self.fmiddles[fsecs[-1]] + self.fhsize[endsecs] = self.fhsize[fsecs[-1]] + # now fill in frames between + fsecs = self.frames.keys() + fsecs.sort() + for i in range(len(fsecs)): + if i == len(fsecs)-1: + continue # nothing after end + startsecs = fsecs[i] + endsecs = fsecs[i+1] + numframes = int((endsecs-startsecs)/float(fint))-1 + #print 'NUMFRAMES(%s):%s' % (i,numframes) + for j in range(numframes-1): # MAYBE SHOULD BE numframes + secs = startsecs + ((j+1)*fint) + self.frames[secs] = self.intjoints(self.frames[startsecs],self.frames[endsecs], + j+1,numframes) + self.fparts[secs] = self.intparts(self.fparts[startsecs],self.fparts[endsecs], + j+1,numframes) + self.fmiddles[secs] = self.intmiddle(self.fmiddles[startsecs],self.fmiddles[endsecs], + j+1,numframes) + self.fhsize[secs] = self.inthsize(self.fhsize[startsecs],self.fhsize[endsecs], + j+1,numframes) + #print '%s,%s(%s secs):(%s,%s) START(%s,%s) - END(%s,%s) startsecs:%s endsecs:%s numframes:%s' % (i,j,secs,self.fmiddles[secs][0],self.fmiddles[secs][1],self.fmiddles[startsecs][0],self.fmiddles[startsecs][1],self.fmiddles[endsecs][0],self.fmiddles[endsecs][1],startsecs,endsecs,numframes) + #print self.frames.keys() + +""" diff --git a/model.py b/model.py new file mode 100644 index 0000000..7d29069 --- /dev/null +++ b/model.py @@ -0,0 +1,78 @@ +# 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 2 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 St, Fifth Floor, Boston, MA 02110-1301 USA + +import theme +import kinematic + +class KeyFrame: + def __init__(self): + self.clear() + + def empty(self): + return self.joints == None + + def assign(self, x): + self.middle = x.middle + self.parts = x.parts.copy() + self.sticks = x.sticks.copy() + self.scaled_sticks = x.sticks.copy() + self.joints = x.joints.copy() + self.scaled_joints = initjoints() + + scalesticks(self.scaled_sticks, .2) + setjoints(self.scaled_joints, self.scaled_sticks, + (self.x, int(theme.KEYFRAMEHEIGHT/2.0))) + + def clear(self): + self.parts = None + self.sticks = None + self.scaled_sticks = None + self.joints = None + self.scaled_joints = None + +keys = [] + +for i in range(5): + key = KeyFrame() + keyframe_width = theme.KEYFRAMEWIDTH/5 + key.x = keyframe_width/2 + i*keyframe_width + keys.append(key) + +def initjoints(): + joints = {} + for stickname in theme.JOINTS: + jname = theme.JOINTS[stickname] + joints[jname] = (0,0) + return joints + +def scalesticks(stickdict, i): + for key in stickdict: + (angle,len) = stickdict[key] + newlen = int(len * i) + stickdict[key] = (angle,newlen) + +def setjoints(joints, sticks, middle): + # have to traverse in order because + # parent joints must be set right + for stickname in theme.STICKLIST: + (angle,len) = sticks[stickname] + jname = theme.JOINTS[stickname] + (x,y) = kinematic.getparentjoint(jname, joints, middle) + parents = kinematic.getparentsticks(stickname) + panglesum = 0 + for parentname in parents: + (pangle,plen) = sticks[parentname] + panglesum += pangle + (nx,ny) = kinematic.getpoints(x,y,angle+panglesum,len) + joints[jname] = (nx,ny) -- cgit v0.9.1