[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Gzz-commits] libvob bench/vob/paper/dice.py doc/pegboard/vob...
From: |
Tuomas J. Lukka |
Subject: |
[Gzz-commits] libvob bench/vob/paper/dice.py doc/pegboard/vob... |
Date: |
Wed, 07 May 2003 08:00:43 -0400 |
CVSROOT: /cvsroot/libvob
Module name: libvob
Changes by: Tuomas J. Lukka <address@hidden> 03/05/07 08:00:42
Modified files:
bench/vob/paper: dice.py
doc/pegboard/vobscene_recursion--tjl: peg.rst
include/vob/poly: Dicer.hxx
include/vob/vobs: Paper.hxx
vob/buoy : buoymanager.py
Log message:
Improved tesselation
CVSWeb URLs:
http://savannah.gnu.org/cgi-bin/viewcvs/libvob/libvob/bench/vob/paper/dice.py.diff?tr1=1.1&tr2=1.2&r1=text&r2=text
http://savannah.gnu.org/cgi-bin/viewcvs/libvob/libvob/doc/pegboard/vobscene_recursion--tjl/peg.rst.diff?tr1=1.4&tr2=1.5&r1=text&r2=text
http://savannah.gnu.org/cgi-bin/viewcvs/libvob/libvob/include/vob/poly/Dicer.hxx.diff?tr1=1.2&tr2=1.3&r1=text&r2=text
http://savannah.gnu.org/cgi-bin/viewcvs/libvob/libvob/include/vob/vobs/Paper.hxx.diff?tr1=1.6&tr2=1.7&r1=text&r2=text
http://savannah.gnu.org/cgi-bin/viewcvs/libvob/libvob/vob/buoy/buoymanager.py.diff?tr1=1.11&tr2=1.12&r1=text&r2=text
Patches:
Index: libvob/bench/vob/paper/dice.py
diff -u libvob/bench/vob/paper/dice.py:1.1 libvob/bench/vob/paper/dice.py:1.2
--- libvob/bench/vob/paper/dice.py:1.1 Tue Apr 1 14:37:25 2003
+++ libvob/bench/vob/paper/dice.py Wed May 7 08:00:42 2003
@@ -1,23 +1,26 @@
# (c) Tuomas J. Lukka and Matti Katila
import java
+import math
from org.nongnu import libvob as vob
def benchScene(vs,
nreps = 10,
dicelen = 10,
+ dicelen2 = 100,
size = 400,
):
vs.put(vob.vobs.SolidBackdropVob(java.awt.Color(.3,.7,.6)))
- dice = vob.gl.GLRen.createDiceTester(dicelen, 1, 40)
+ dice = vob.gl.GLRen.createDiceTester(dicelen, dicelen2, 1, 40)
for i in range(0,nreps):
cs = vs.orthoCS(0 ,str(i), -i, 2, 2, size, size)
- vs.map.put(dice, cs)
+ cs2 = vs.coords.distort(cs, 0, 0, .2, .2, math.log(5), math.log(.5))
+ vs.map.put(dice, cs2)
args = {
- "nreps" : (1,),
- "dicelen" : (3,6,10,30,60,100),
- "size" : (400),
+ "nreps" : (100,),
+ "dicelen" : (1,2,4,8),
+ "size" : (400, 800),
}
Index: libvob/doc/pegboard/vobscene_recursion--tjl/peg.rst
diff -u libvob/doc/pegboard/vobscene_recursion--tjl/peg.rst:1.4
libvob/doc/pegboard/vobscene_recursion--tjl/peg.rst:1.5
--- libvob/doc/pegboard/vobscene_recursion--tjl/peg.rst:1.4 Tue Apr 8
16:25:02 2003
+++ libvob/doc/pegboard/vobscene_recursion--tjl/peg.rst Wed May 7 08:00:42 2003
@@ -3,8 +3,8 @@
=============================================================
:Author: Tuomas J. Lukka
-:Last-Modified: $Date: 2003/04/08 20:25:02 $
-:Revision: $Revision: 1.4 $
+:Last-Modified: $Date: 2003/05/07 12:00:42 $
+:Revision: $Revision: 1.5 $
:Status: Current
Performance problems in PP show that we should soon make a big
@@ -144,6 +144,15 @@
public int subSceneCoordsys(int vobId, int nth);
+VobScene semantics
+------------------
+So far, the only special CS has been coordinate system 0.
+From now on, coordinate system **1** shall be the "root" coordinate system,
+and **0** remains the "identity" coordinate system (representing
+an identity transformation).
+
+The unit box of CS 1 is set to screen size, while the unit box
+of CS 0 is (1,1).
Index: libvob/include/vob/poly/Dicer.hxx
diff -u libvob/include/vob/poly/Dicer.hxx:1.2
libvob/include/vob/poly/Dicer.hxx:1.3
--- libvob/include/vob/poly/Dicer.hxx:1.2 Tue Apr 1 06:09:25 2003
+++ libvob/include/vob/poly/Dicer.hxx Wed May 7 08:00:42 2003
@@ -3,10 +3,12 @@
#ifndef VOB_POLY_DICER_HXX
#define VOB_POLY_DICER_HXX
+#include <GL/gl.h>
#include <algorithm>
#include <set>
#include <map>
#include <vector>
+#include <list>
#include <ext/slist>
namespace Vob {
@@ -14,6 +16,131 @@
PREDBGVAR(dbg);
+ template<class Verts> struct Triangles {
+ Verts &verts;
+ struct Tri {
+ int v[3];
+ typename std::list<Tri>::iterator n[3];
+ };
+ std::list<Tri> tris;
+
+ typedef typename std::list<Tri>::iterator Titer;
+ vector<Titer> vertFirst;
+
+ void remove(Titer x) {
+ Tri &t = *x;
+ DBG(dbg) << "Triangler: remove "<<t.v[0]<<" "<<t.v[1]<<"
"<<t.v[2]<<"\n";
+ // Remove from the edge list
+ for(int i=0; i<3; i++) {
+ Titer v = vertFirst[t.v[i]];
+ DBG(dbg) << "Titer: "<<(*v).v[0]<<" "<<(*v).v[1]<<"
"<<(*v).v[2]<<"\n";
+ if(v == x) {
+ DBG(dbg) << "Was first\n";
+ vertFirst[t.v[i]] = t.n[i];
+ } else {
+ Titer prev = v;
+ int vind = 0;
+ if((*v).v[0] == t.v[i]) vind = 0;
+ if((*v).v[1] == t.v[i]) vind = 1;
+ if((*v).v[2] == t.v[i]) vind = 2;
+ v = (*v).n[vind];
+ while(v != tris.end()) {
+ DBG(dbg) << "Titer: "<<(*v).v[0]<<" "<<(*v).v[1]<<"
"<<(*v).v[2]<<"\n";
+ if(v == x) {
+ (*prev).n[vind] = t.n[i];
+ break;
+ }
+ if((*v).v[0] == t.v[i]) vind = 0;
+ if((*v).v[1] == t.v[i]) vind = 1;
+ if((*v).v[2] == t.v[i]) vind = 2;
+ prev = v;
+ v = (*v).n[vind];
+ }
+ }
+ }
+ DBG(dbg) << "Remove: realerase\n";
+ tris.erase(x);
+ }
+
+ void splitEdge(int v1, int v2) {
+ DBG(dbg) << "Triangler: split "<<v1<<" "<<v2<<"\n";
+ vector<Titer> toremove;
+ for(Titer x = vertFirst[v1]; x != tris.end(); ) {
+ int vind = 0;
+ if((*x).v[0] == v1) vind = 0;
+ if((*x).v[1] == v1) vind = 1;
+ if((*x).v[2] == v1) vind = 2;
+ if((*x).v[0] == v2 ||
+ (*x).v[1] == v2 ||
+ (*x).v[2] == v2) toremove.push_back(x);
+ x = (*x).n[vind];
+ }
+ if(toremove.size() == 0) return;
+ int nvert = verts(v1, v2, .5);
+ DBG(dbg) << "Have new vert "<<nvert<<"\n";
+ for(unsigned i=0; i<toremove.size(); i++) {
+ // Save to local
+ Tri t = *toremove[i];
+ remove(toremove[i]);
+ int i = (t.v[0] == v1 ? nvert : t.v[0]);
+ int j = (t.v[1] == v1 ? nvert : t.v[1]);
+ int k = (t.v[2] == v1 ? nvert : t.v[2]);
+ add(i, j, k);
+ i = (t.v[0] == v2 ? nvert : t.v[0]);
+ j = (t.v[1] == v2 ? nvert : t.v[1]);
+ k = (t.v[2] == v2 ? nvert : t.v[2]);
+ add(i, j, k);
+ }
+ DBG(dbg) << "Split finished\n";
+ }
+
+ public:
+ void add(int i, int j, int k) {
+ DBG(dbg) << "Triangler: add "<<i<<" "<<j<<" "<<k<<"\n";
+ Tri t;
+ t.v[0] = i;
+ t.v[1] = j;
+ t.v[2] = k;
+ if(vertFirst.size() != verts.size())
+ vertFirst.resize(verts.size(), tris.end());
+ t.n[0] = vertFirst[i];
+ t.n[1] = vertFirst[j];
+ t.n[2] = vertFirst[k];
+ vertFirst[i] = vertFirst[j] = vertFirst[k] =
+ tris.insert(tris.begin(), t);
+ }
+
+ template <class F> void dice(F criterion) {
+ DBG(dbg) << "Triangler: dice\n";
+ typedef std::pair<int, int> Edge;
+ vector<Edge> tosplit;
+ while(1) {
+ DBG(dbg) << "Triangler: dice round\n";
+ for(Titer t = tris.begin(); t != tris.end(); t++) {
+ Tri &tri = *t;
+ int spl = criterion(tri.v[0], tri.v[1], tri.v[2]);
+ if(spl >= 0) {
+ tosplit.push_back( Edge( tri.v[spl], tri.v[(spl+1) %
3]));
+ }
+ }
+ if(!tosplit.size()) return;
+ for(unsigned i=0; i<tosplit.size(); i++)
+ splitEdge(tosplit[i].first, tosplit[i].second);
+ tosplit.clear();
+ }
+ }
+ void draw() {
+ glBegin(GL_TRIANGLES);
+ for(Titer x = tris.begin(); x != tris.end(); x++) {
+ glArrayElement((*x).v[0]);
+ glArrayElement((*x).v[1]);
+ glArrayElement((*x).v[2]);
+ }
+ glEnd();
+ }
+ Triangles(Verts &v) : verts(v) {
+ }
+ };
/** Dice a line to pieces satisfying Criterion.
* This is a generic method to dice a given edge recursively.
@@ -34,7 +161,7 @@
* @return Number of new vertices added to the edge.
*/
template<class Verts, class Edge, class Criterion>
- int dice_line(Verts &verts, Edge &edge,
+ int dice_line2(Verts &verts, Edge &edge,
typename Edge::iterator begin, typename Edge::iterator
last,
Criterion split, int maxdepth, int depth=0) {
DBG(dbg) << "dice_line "<<depth<<"\n";
@@ -62,6 +189,61 @@
return ret;
}
+ template<class Verts, class Edge, class Criterion>
+ int dice_line3_h(Verts &verts, Edge &edge,
+ typename Edge::iterator begin,
+ typename Edge::iterator half,
+ typename Edge::iterator last,
+ Criterion split, int maxdepth, int depth) {
+ if(depth >= maxdepth) {
+ DBG(dbg) << "dice_line3_h STOPDEPTH\n";
+ return 0;
+ }
+ int splitflag = split(*begin, *half, *last);
+ int ret = 0;
+ if(splitflag == 1) {
+ int spv = verts(*begin, *half, .5);
+ ret ++;
+ typename Edge::iterator sp = edge.insert_after(begin, spv);
+ ret += dice_line3_h(verts, edge, begin, sp, half, split, maxdepth,
depth+1);
+ }
+ int esplitflag = split(*last, *half, *begin);
+ if(esplitflag == 1) {
+ int espv = verts(*half, *last, .5);
+ ret ++;
+ typename Edge::iterator esp = edge.insert_after(half, espv);
+ ret += dice_line3_h(verts, edge, half, esp, last, split, maxdepth,
depth+1);
+ }
+ return ret;
+
+ }
+
+ template<class Verts, class Edge, class Criterion>
+ int dice_line3(Verts &verts, Edge &edge,
+ typename Edge::iterator begin, typename Edge::iterator
last,
+ Criterion split, int maxdepth, int depth=0) {
+ DBG(dbg) << "dice_line "<<depth<<"\n";
+ if(depth >= maxdepth) {
+ DBG(dbg) << "dice_line STOPDEPTH\n";
+ return 0;
+ }
+ int halfv = verts(*begin, *last, .5);
+
+
+ int splitflag = split(*begin, halfv, *last);
+ if(splitflag == -1) {
+ DBG(dbg) << "dice_line STOP FLAG\n";
+ return 0;
+ }
+ typename Edge::iterator half = edge.insert_after(begin, halfv);
+ int ret = 1;
+ ret += dice_line3_h(verts, edge, begin, half, last, split, maxdepth,
depth + 1);
+
+ DBG(dbg) << "RET dice_line "<<depth<<" "<<ret << "\n";
+ return ret;
+ }
+
+
/** A triangle whose edges have been diced.
*/
template<class Edge> struct DicedTriangle {
@@ -118,7 +300,7 @@
DicedTriangle start;
start.ebegins[0] = newEdge.begin();
start.elasts[0] = newEdge.begin(); start.elasts[0] ++;
- start.n[0] = dice_line(verts, newEdge, start.ebegins[0],
start.elasts[0], split, maxdepth, depth);
+ start.n[0] = dice_line3(verts, newEdge, start.ebegins[0],
start.elasts[0], split, maxdepth, depth);
start.ebegins[1] = ebegins[thirdEdge];
start.elasts[1] = elasts[thirdEdge];
@@ -175,7 +357,7 @@
for(int i=0; i<3; i++) {
ebegins[i] = e[i].begin();
elasts[i] = ebegins[i]; elasts[i] ++;
- n[i] = dice_line(verts, e[i], ebegins[i], elasts[i], split,
maxdepth);
+ n[i] = dice_line3(verts, e[i], ebegins[i], elasts[i], split,
maxdepth);
}
}
};
Index: libvob/include/vob/vobs/Paper.hxx
diff -u libvob/include/vob/vobs/Paper.hxx:1.6
libvob/include/vob/vobs/Paper.hxx:1.7
--- libvob/include/vob/vobs/Paper.hxx:1.6 Thu Apr 24 13:49:53 2003
+++ libvob/include/vob/vobs/Paper.hxx Wed May 7 08:00:42 2003
@@ -32,7 +32,6 @@
struct Verts {
const Transform &t;
- Verts(const Transform &t) : t(t) { }
struct T2V3Vert {
Pt orig;
@@ -43,6 +42,14 @@
};
vector<T2V3Vert> points;
+ Verts(const Transform &t) : t(t) {
+ points.reserve(1000);
+ }
+
+ unsigned int size() {
+ return points.size();
+ }
+
int append(Pt p) {
DBG(dbg_paperquad) << "DiceTester append "<<p<<"\n";
int ind = points.size();
@@ -109,6 +116,66 @@
return ret;
}
+/** Whether the i-j stretch should be split again
+ * @return 1 = yes, 0 = no, -1 = j should be dropped
+ */
+inline int split3(Verts &v, float dicelen1, float dicelen2, int i, int j, int
k) {
+ Pt o1 = (Pt)v.points[i].final - (Pt)v.points[j].final;
+ Pt o2 = (Pt)v.points[j].final - (Pt)v.points[k].final;
+ float x1, x2;
+ if(fabs(o1.x) > fabs(o1.y)) {
+ x1 = o1.x; x2 = o2.x;
+ } else {
+ x1 = o1.y; x2 = o2.y;
+ }
+ Pt v1 = (Pt)v.points[i].final - (Pt)v.points[j].final;
+ Pt v2 = (Pt)v.points[j].final - (Pt)v.points[k].final;
+
+ float v1l = v1.length();
+ float v2l = v2.length();
+
+ if(v1l > dicelen1) return 1;
+
+ Pt v2_mapped = v2 * (x1 / x2);
+
+ float area = (v1 - v2_mapped).length();
+
+ float carea = area * v1l / (v1l + v2l);
+ DBG(dbg_paperquad) << "Split3 "<<i<<" "<<j<<" "<<
+ v.points[i].final<<" "<<v.points[j].final<<" "
+ << v.points[k].final<< " " <<area<<" "<<carea<<"\n";
+ if(area < dicelen2 / 2) return -1;
+ // The portion that is v1's worry
+ if(carea < dicelen2) return 0;
+ return 1;
+}
+
+inline int splitTri(Verts &v, float dicelen1, float dicelen2, int i, int j,
int k) {
+ DBG(dbg_paperquad) << "SplitTri "<<i<<" "<<j<<" "<<
+ v.points[i].final<<" "<<v.points[j].final<<" "
+ << v.points[k].final<< "\n";
+ Pt ctr = 1/3. * (
+ v.points[i].orig +
+ v.points[j].orig +
+ v.points[k].orig );
+ ZPt ctrt = 1/3. * (
+ v.points[i].final +
+ v.points[j].final +
+ v.points[k].final );
+ ZPt tc = v.t.transform(ctr);
+ if( (tc-ctrt).xylength() < dicelen1) {
+ DBG(dbg_paperquad) << "NO SPLIT\n";
+ return -1;
+ }
+ float l0 = (v.points[i].orig - v.points[j].orig).length();
+ float l1 = (v.points[j].orig - v.points[k].orig).length();
+ float l2 = (v.points[k].orig - v.points[i].orig).length();
+ DBG(dbg_paperquad) << "SPLIT "<<l0<<" "<<l1<<" "<<l2<<"\n";
+ if(l0 > l1 && l0 > l2) return 0;
+ if(l1 > l2) return 1;
+ return 2;
+}
+
}
@@ -116,30 +183,29 @@
public:
enum { NTrans = 1 };
- float dicelen;
+ float dicelen1;
+ float dicelen2;
int flags;
int maxdepth;
template<class F> void params(F &f) {
- f(dicelen, flags, maxdepth);
+ f(dicelen1, dicelen2, flags, maxdepth);
}
template<class T> void render(const T &coords) const {
using namespace PaperPriv;
- ::Vob::Dicer::InitialDicedTriangle<__gnu_cxx::slist<int> > tri;
- Triangler triangler;
Verts verts(coords);
+ ::Vob::Dicer::Triangles<Verts> triangler(verts);
verts.append(Pt(0,0));
verts.append(Pt(1,0));
+ verts.append(Pt(0,1));
verts.append(Pt(1,1));
using namespace boost;
using namespace boost::lambda;
DBG(dbg_paperquad) << "Set_and_initial\n";
- tri.set_and_initial_dice(verts,
- 0, 1, 2, bind(split, ref(verts), dicelen, _1, _2),
maxdepth);
- DBG(dbg_paperquad) << "dice\n";
- tri.dice(verts, triangler,
- bind(split, ref(verts), dicelen, _1, _2), maxdepth);
+ triangler.add(0,1,3);
+ triangler.add(0,3,2);
+ triangler.dice(bind(splitTri, ref(verts), dicelen1, dicelen2, _1, _2,
_3));
verts.startT2V3Operation();
triangler.draw();
@@ -164,11 +230,11 @@
float x0,y0,x1,y1;
int flags;
- float diceLength;
+ float diceLength, diceLength2;
int diceDepth;
template<class F> void params(F &f) {
- f(paper, x0, y0, x1, y1, flags, diceLength, diceDepth);
+ f(paper, x0, y0, x1, y1, flags, diceLength, diceLength2, diceDepth);
}
@@ -176,8 +242,8 @@
GLERR
using namespace PaperPriv;
- Triangler triangler;
Verts verts(coords);
+ ::Vob::Dicer::Triangles<Verts> triangler(verts);
verts.append(Pt(x0,y0));
verts.append(Pt(x1,y0));
verts.append(Pt(x0,y1));
@@ -187,25 +253,13 @@
DBG(dbg_paperquad) << "Set_and_initial\n";
{
- ::Vob::Dicer::InitialDicedTriangle<__gnu_cxx::slist<int> > tri;
- tri.set_and_initial_dice(verts,
- 0, 1, 3, bind(split, ref(verts), diceLength, _1, _2),
diceDepth);
+ triangler.add(0, 1, 3);
+ triangler.add(0, 3, 2);
DBG(dbg_paperquad) << "dice\n";
- tri.dice(verts, triangler,
- bind(split, ref(verts), diceLength, _1, _2),
diceDepth);
+ triangler.dice(bind(splitTri, ref(verts), diceLength, diceLength2,
_1, _2, _3));
+ DBG(dbg_paperquad) << "diced\n";
}
-
- {
- ::Vob::Dicer::InitialDicedTriangle<__gnu_cxx::slist<int> > tri;
- tri.set_and_initial_dice(verts,
- 0, 3, 2, bind(split, ref(verts), diceLength, _1, _2),
diceDepth);
- DBG(dbg_paperquad) << "dice\n";
- tri.dice(verts, triangler,
- bind(split, ref(verts), diceLength, _1, _2),
diceDepth);
- }
-
-
Paper::LightParam lightParam;
lightParam.orig = ZPt(0,0,0);
lightParam.e0 = ZPt(0,0,0);
@@ -234,9 +288,10 @@
glBegin(GL_TRIANGLES);
- for(unsigned i=0; i<triangler.tris.size(); i++) {
+ for(::Vob::Dicer::Triangles<Verts>::Titer
i=triangler.tris.begin();
+ i != triangler.tris.end(); i++) {
for(int v = 0; v < 3; v++) {
- int ind = triangler.tris[i].v[v];
+ int ind = (*i).v[v];
float tmp[4] = {
verts.points[ind].orig.x,
verts.points[ind].orig.y,
Index: libvob/vob/buoy/buoymanager.py
diff -u libvob/vob/buoy/buoymanager.py:1.11 libvob/vob/buoy/buoymanager.py:1.12
--- libvob/vob/buoy/buoymanager.py:1.11 Fri May 2 20:59:07 2003
+++ libvob/vob/buoy/buoymanager.py Wed May 7 08:00:42 2003
@@ -1,20 +1,20 @@
#
# Copyright (c) 2003, Tuomas J. Lukka
#
-# This file is part of Gzz.
+# This file is part of Fenfire.
#
-# Gzz is free software; you can redistribute it and/or modify it under
+# Fenfire is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
-# Gzz is distributed in the hope that it will be useful, but WITHOUT
+# Fenfire 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 Lesser General
# Public License for more details.
#
# You should have received a copy of the GNU Lesser General
-# Public License along with Gzz; if not, write to the Free
+# Public License along with Fenfire; if not, write to the Free
# Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA 02111-1307 USA
#
@@ -58,7 +58,7 @@
self.replaceScene = None
self.mainNode = mainNode
self.connectors = connectors
- def nadir(self, cs):
+ def _nadir(self, cs):
n = self.vs.coords.nadirUnitSq(cs, self.nadirCS)
self.vs.matcher.add(cs, n, "NADIR")
return n
@@ -96,7 +96,7 @@
self.links = []
for connector in self.connectors:
connector.addBuoys(vs, into, self.mainNode, self)
- for l in self.links: self.linkReally(*l)
+ for l in self.links: self._linkReally(*l)
self.cs[into] = None
# Interpolation : old buoy -> to new main vp
@@ -109,7 +109,7 @@
def link(self, *args):
self.links.append(args)
- def linkReally(self, direction, anchorCS, otherNode, linkId, otherAnchor):
+ def _linkReally(self, direction, anchorCS, otherNode, linkId, otherAnchor):
if dbg: pa('link really')
anchorUnit = self.vs.unitSqCS(anchorCS, "UN")
@@ -149,7 +149,7 @@
if 0:
dbg1 = self.vs.unitSqCS(into, "U")
self.vs.put(coloredQuad((0,1,0)), dbg1)
- into = self.nadir(into)
+ into = self._nadir(into)
# into = self.vs.orthoBoxCS(into, "Kludge CS", 0, 0,0, 1,1, w*sca,
h*sca)
self.vs.activate(into)
otherAnchorCS = otherNode.renderBuoy(self.vs, into, linkId,
otherAnchor, None)
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [Gzz-commits] libvob bench/vob/paper/dice.py doc/pegboard/vob...,
Tuomas J. Lukka <=