source: cpp/gdk/modelparts.cpp @ 100

Last change on this file since 100 was 81, checked in by Maciej Komosinski, 12 years ago

improved parsing of properties (e.g. in f0 genotypes)

  • Property svn:eol-style set to native
File size: 17.3 KB
Line 
1// This file is a part of the Framsticks GDK library.
2// Copyright (C) 2002-2011  Szymon Ulatowski.  See LICENSE.txt for details.
3// Refer to http://www.framsticks.com/ for further information.
4
5#include <stdlib.h>
6#include <math.h>
7#include <stdio.h>
8#include <string.h>
9#include <ctype.h>
10#include <time.h>
11#include <errno.h>
12
13///////////////////////////////     MODELPARTS.CPP     ///////////////
14
15#include "modelparts.h"
16#include "model.h"
17
18#include "nonstd.h"
19#include "param.h"
20#include "neurolibrary.h"
21#include "multirange.h"
22#include "extvalue.h"
23
24#ifndef GDK_WITHOUT_FRAMS
25#include "neuroclsobject.h"
26#endif
27
28template<>
29char ModelUserTags::reg[5]={0};
30
31/////////////////////////
32
33PartBase::~PartBase()
34{if (mapped) delete mapped;}
35
36void PartBase::notifyMappingChange()
37{
38if (owner) owner->partmappingchanged=1;
39}
40
41void PartBase::setMapping(const IRange &r)
42{
43if (mapped) (*mapped)=r;
44else mapped=new MultiRange(r);
45notifyMappingChange();
46}
47
48void PartBase::clearMapping()
49{
50if (mapped) {delete mapped; mapped=0;}
51}
52
53void PartBase::addMapping(const IRange &r)
54{
55if (mapped) mapped->add(r);
56else mapped=new MultiRange(r);
57notifyMappingChange();
58}
59
60void PartBase::setMapping(const MultiRange &mr)
61{
62if (mapped) (*mapped)=mr;
63else mapped=new MultiRange(mr);
64notifyMappingChange();
65}
66
67void PartBase::addMapping(const MultiRange &mr)
68{
69if (mapped) mapped->add(mr);
70else mapped=new MultiRange(mr);
71notifyMappingChange();
72}
73
74void PartBase::setInfo(const SString& name,const SString& value)
75{
76strSetField(info,name,value);
77}
78
79void PartBase::setInfo(const SString& name,int value)
80{
81setInfo(name,SString::valueOf(value));
82}
83
84void PartBase::setInfo(const SString& name,double value)
85{
86setInfo(name,SString::valueOf(value));
87}
88
89SString PartBase::getInfo(const SString& name)
90{
91return strGetField(info,name);
92}
93
94/////////////////////////
95
96NeuroClass::NeuroClass(ParamEntry *_props,SString _description,
97                       int _prefinputs,int _prefoutput,int _preflocation,
98                       int *_vectordata,bool own_vd,int vhints)
99        :longname(_props->id),
100         name(_props->name),
101         description(_description),
102         props(_props),
103         prefinputs(_prefinputs),
104         prefoutput(_prefoutput),
105         preflocation(_preflocation),
106         vectordata(_vectordata),ownedvectordata(own_vd),
107         visualhints(vhints),impl(0),active(1),genactive(0)
108{}
109
110NeuroClass::~NeuroClass()
111{
112setSymbolGlyph(0,0);
113}
114
115NeuroClass::NeuroClass()
116        :name("Invalid"),props(empty_paramtab),
117         prefinputs(0), prefoutput(0),
118         preflocation(0), vectordata(0), ownedvectordata(0),
119         impl(0), active(1), genactive(0)
120{}
121
122SString NeuroClass::getSummary()
123{
124SString t;
125t=getDescription();
126if (t.len()) t+="\n\n";
127t+="Characteristics:\n";
128if(getPreferredInputs())
129        {
130        if (getPreferredInputs()<0) t+="   supports any number of inputs\n";
131        else if (getPreferredInputs()==1) t+="   uses single input\n";
132        else {sprintf(t.directAppend(30),"   uses %d inputs\n",getPreferredInputs()); t.endAppend();}
133        }
134else t+="   does not use inputs\n";
135if(getPreferredOutput())
136        t+="   provides output value\n";
137else
138        t+="   does not provide output value\n";
139switch(getPreferredLocation())
140        {
141        case 0: t+="   does not require location in body\n"; break;
142        case 1: t+="   should be located on a Part\n"; break;
143        case 2: t+="   should be located on a Joint\n"; break;
144        }
145Param p=getProperties();
146if (p.getPropCount())
147        {
148        if (t.len()) t+="\n\n";
149        t+="Properties:\n";
150        const char *h;
151        int i;
152        for(i=0;i<p.getPropCount();i++)
153                {
154                if (i) t+="\n";
155                t+="   "; t+=p.name(i); t+=" ("; t+=p.id(i); t+=")";
156                switch(*p.type(i))
157                        {
158                        case 'd': t+=" integer";
159                        {long a,b,c; if (p.getMinMax(i,a,b,c)>=2) {sprintf(t.directAppend(30)," %d..%d",a,b); t.endAppend();}}
160                                break;
161                        case 'f': t+=" float";
162                        {double a,b,c; if (p.getMinMax(i,a,b,c)>=2) {sprintf(t.directAppend(30)," %g..%g",a,b); t.endAppend();}}
163                                break;
164                        case 's': t+=" string"; break;
165                        case 'x': t+=" anything"; break;
166                        }
167                if (h=p.help(i)) if (*h) {t+=" - "; t+=h;}
168                }
169        }
170return t;
171}
172
173/////////////////////////
174
175/////////////////////////////////////
176
177Neuro::Neuro(double _state,double _inertia,double _force,double _sigmo)
178        :state(_state),PartBase(getDefaultStyle())
179#ifdef MODEL_V1_COMPATIBLE
180,inertia(_inertia),force(_force),sigmo(_sigmo)
181#endif
182{
183#ifdef MODEL_V1_COMPATIBLE
184olditems=0;
185#endif
186flags=0;
187myclass=0;
188knownclass=1;
189}
190
191Neuro::Neuro(void):PartBase(getDefaultStyle())
192{
193#include "defassign-f0_neuro.h"
194state=0.0;
195myclass=NULL;
196myclassname="N";//default d="N" but f0.def is unable to set this (d is GETSET, not a regular FIELD)
197knownclass=0;
198refno=0;
199pos=Pt3D_0; rot=Pt3D_0;
200parent=0; part=0; joint=0;
201parentcount=0;
202#ifdef MODEL_V1_COMPATIBLE
203olditems=0;
204#endif
205flags=0;
206}
207
208
209Neuro::~Neuro()
210{
211#ifdef MODEL_V1_COMPATIBLE
212if (olditems) delete olditems;
213#endif
214int i;
215for(i=0;i<inputs.size();i++)
216        {
217        NInput &ni=inputs(i);
218        if (ni.info) delete ni.info;
219        }
220}
221
222SString** Neuro::inputInfo(int i)
223{
224if (i>=getInputCount()) return 0;
225return &inputs(i).info;
226}
227
228void Neuro::setInputInfo(int i,const SString& name,const SString &value)
229{
230SString **s=inputInfo(i);
231if (!s) return;
232if (!*s) *s=new SString();
233strSetField(**s,name,value);
234}
235
236void Neuro::setInputInfo(int i,const SString& name,int value)
237{
238setInputInfo(i,name,SString::valueOf(value));
239}
240
241void Neuro::setInputInfo(int i,const SString& name,double value)
242{
243setInputInfo(i,name,SString::valueOf(value));
244}
245
246SString Neuro::getInputInfo(int i)
247{
248SString **s=inputInfo(i);
249if (!s) return SString();
250if (!*s) return SString();
251return **s;
252}
253
254SString Neuro::getInputInfo(int i,const SString& name)
255{
256SString **s=inputInfo(i);
257if (!s) return SString();
258if (!*s) return SString();
259return strGetField(**s,name);
260}
261
262void Neuro::operator=(const Neuro& src)
263{
264refno=src.refno;
265#ifdef MODEL_V1_COMPATIBLE
266neuro_refno=-1;
267conn_refno=-1;
268force=src.force;
269sigmo=src.sigmo;
270inertia=src.inertia;
271weight=src.weight;
272olditems=0;
273#endif
274state=src.state;
275part_refno=-1;
276joint_refno=-1;
277pos=src.pos; rot=src.rot;
278parent=0; part=0; joint=0;
279parentcount=0;
280flags=0;
281myclass=src.myclass;
282knownclass=src.knownclass;
283myclassname=src.myclassname;
284myclassparams=src.myclassparams;
285}
286
287void Neuro::attachToPart(int i)
288{attachToPart((i>=0)?owner->getPart(i):0);}
289
290void Neuro::attachToJoint(int i)
291{attachToJoint((i>=0)?owner->getJoint(i):0);}
292
293int Neuro::getClassCount()
294{return NeuroLibrary::staticlibrary.getClassCount();}
295
296NeuroClass* Neuro::getClass(int classindex)
297{return NeuroLibrary::staticlibrary.getClass(classindex);}
298
299NeuroClass* Neuro::getClass(const SString& classname)
300{return NeuroLibrary::staticlibrary.findClass(classname);}
301
302int Neuro::getClassIndex(const NeuroClass*nc)
303{return NeuroLibrary::staticlibrary.classes.find((void*)nc);}
304
305NeuroClass* Neuro::getClass()
306{
307checkClass();
308return myclass;
309}
310
311void Neuro::setClass(NeuroClass* cl)
312{
313myclass=cl;
314myclassname=cl->getName();
315knownclass=1;
316}
317
318SString Neuro::getClassName(int classindex)
319{
320NeuroClass *cl=NeuroLibrary::staticlibrary.getClass(classindex);
321return cl? cl->getName() : SString();
322}
323
324void Neuro::setDetails(const SString& details)
325{
326int colon=details.indexOf(':');
327if (colon>=0) {myclassname=details.substr(0,colon); myclassparams=details.substr(colon+1);}
328else {myclassname=details; myclassparams=0;}
329knownclass=0;
330}
331
332SString Neuro::getDetails()
333{
334SString ret=getClassName();
335if (myclassparams.len()) {if (!ret.len()) ret="N"; ret+=":"; ret+=myclassparams;}
336return ret;
337}
338
339void Neuro::checkClass()
340{
341if (knownclass) return;
342myclass=getClass(myclassname);
343knownclass=1;
344}
345
346SyntParam Neuro::classProperties(bool handle_defaults_when_saving)
347{
348NeuroClass *cl=getClass();
349ParamEntry *pe = cl ? cl->getParamTab() : emptyParamTab;
350return SyntParam(pe,&myclassparams,handle_defaults_when_saving);
351}
352
353SString Neuro::getClassName()
354{
355return myclassname;
356}
357
358void Neuro::setClassName(const SString& clazz)
359{
360myclassname=clazz;
361knownclass=0;
362}
363
364int Neuro::addInput(Neuro* child,float weight,const SString *info)
365{
366inputs+=NInput(child,weight,(info&&(info->len()))?new SString(*info):0);
367child->parentcount++;
368if (child->parentcount==1) {child->parent=this;}
369return inputs.size()-1;
370}
371
372int Neuro::findInput(Neuro* child) const
373{
374for(int i=0;i<inputs.size();i++)
375        if (inputs(i).n==child) return i;
376return -1;
377}
378
379Neuro* Neuro::getInput(int i,float &weight) const
380{
381if (i>=getInputCount()) return 0;
382NInput &inp=inputs(i);
383weight=inp.weight;
384return inp.n;
385}
386
387float Neuro::getInputWeight(int i) const
388{
389return inputs(i).weight;
390}
391
392void Neuro::setInputWeight(int i,float w)
393{
394inputs(i).weight=w;
395}
396
397void Neuro::setInput(int i,Neuro* n)
398{
399NInput &inp=inputs(i);
400inp.n=n;
401}
402
403void Neuro::setInput(int i,Neuro* n,float w)
404{
405NInput &inp=inputs(i);
406inp.n=n;
407inp.weight=w;
408}
409
410void Neuro::removeInput(int refno)
411{
412Neuro *child=getInput(refno);
413child->parentcount--;
414if (child->parent==this) child->parent=0;
415SString *s=inputs(refno).info;
416if (s) delete s;
417inputs.remove(refno);
418}
419
420int Neuro::removeInput(Neuro* child)
421{
422int i=findInput(child);
423if (i>=0) removeInput(i);
424return i;
425}
426
427int Neuro::getOutputsCount() const
428{
429   int c=0;
430   for(int i=0;i<owner->getNeuroCount();i++)
431     for(int j=0;j<owner->getNeuro(i)->getInputCount();j++) c+=owner->getNeuro(i)->getInput(j)==this;
432   return c;
433}
434
435int Neuro::isOldEffector()
436{
437static SString bend("|"),rot("@");
438return ((getClassName()==bend)||(getClassName()==rot));
439}
440
441int Neuro::isOldReceptor()
442{
443static SString g("G"),t("T"),s("S");
444return ((getClassName()==g)||(getClassName()==t)||(getClassName()==s));
445}
446
447int Neuro::isOldNeuron()
448{
449static SString n("N");
450return (getClassName()==n);
451}
452
453int Neuro::isNNConnection()
454{
455static SString conn("-");
456return (getClassName()==conn);
457}
458
459int Neuro::findInputs(SList& result,const char* classname,const Part* part,const Joint* joint) const
460{
461Neuro *nu;
462SString cn(classname);
463int n0=result.size();
464for(int i=0;nu=getInput(i);i++)
465        {
466        if (part)
467                if (nu->part != part) continue;
468        if (joint)
469                if (nu->joint != joint) continue;
470        if (classname)
471                if (nu->getClassName() != cn) continue;
472        result+=(void*)nu;
473        }
474return result.size()-n0;
475}
476
477int Neuro::findOutputs(SList& result,const char* classname,const Part* part,const Joint* joint) const
478{ // not very efficient...
479Neuro *nu,*inp;
480SString cn(classname);
481SList found;
482int n0=result.size();
483for(int i=0;nu=getModel().getNeuro(i);i++)
484        {
485        if (part)
486                if (nu->part != part) continue;
487        if (joint)
488                if (nu->joint != joint) continue;
489        if (classname)
490                if (inp->getClassName() != cn) continue;
491        for (int j=0;inp=nu->getInput(j);j++)
492                if (inp==this)
493                        {
494                        result+=(void*)nu;
495                        break;
496                        }
497        }
498return result.size()-n0;
499}
500
501void Neuro::get_inputCount(PARAMGETARGS)
502{ret->setInt(inputs.size());}
503
504void Neuro::p_getInputNeuroDef(ExtValue *args,ExtValue *ret)
505{
506int i=args->getInt();
507if ((i<0)||(i>=inputs.size()))
508        ret->setEmpty();
509else
510        ret->setObject(ExtObject(&Neuro::getStaticParam(),inputs(i).n));
511}
512
513void Neuro::p_getInputWeight(ExtValue *args,ExtValue *ret)
514{
515int i=args->getInt();
516if ((i<0)||(i>=inputs.size()))
517        ret->setEmpty();
518else
519        ret->setDouble(inputs(i).weight);
520}
521
522void Neuro::p_getInputNeuroIndex(ExtValue *args,ExtValue *ret)
523{
524int i=args->getInt();
525if ((i<0)||(i>=inputs.size()))
526        ret->setInt(-1);
527else
528        ret->setInt(inputs(i).n->refno);
529}
530
531void Neuro::get_classObject(PARAMGETARGS)
532{
533#ifndef GDK_WITHOUT_FRAMS
534NeuroClassExt::makeStaticObject(ret,getClass());
535#endif
536}
537
538/////// old items
539#ifdef MODEL_V1_COMPATIBLE
540void OldItems::buildlist()
541{ // guaranteed to work only for old NN layouts
542 // (neurons,neuro connections, old receptors and effectors)
543if (listok) return;
544 // inputs can contain both neuroitem connections (details="") and direct neuron references (details="N")
545 // in OldItems we create neuroitems freom direct references
546for(int i=0;i<neuro.getInputCount();i++)
547        {
548        float w;
549        Neuro *n=neuro.getInput(i,w);
550        if (n->isOldNeuron())
551                {
552                Neuro *ni=new Neuro();
553                ni->setClassName("-");
554                ni->weight=w;
555                ni->neuro_refno=neuro.refno;
556                ni->conn_refno=n->refno;
557                items+=ni;
558                syntitems+=ni;
559                }
560        else
561                {
562                items+=n;
563                n->weight=w;
564                }
565        }
566SList outputs;
567neuro.findOutputs(outputs);
568FOREACH(Neuro*,n,outputs)
569        {
570        if (n->isNNConnection() || n->isOldNeuron())
571                outputs-=n;
572        }
573items+=outputs;
574listok=1;
575}
576
577void OldItems::freelist()
578{
579FOREACH(Neuro*,n,syntitems)
580        delete n;
581syntitems.clear();
582items.clear();
583listok=0;
584}
585
586int OldItems::getItemCount()
587{
588buildlist();
589return items.size();
590}
591
592NeuroItem *OldItems::getNeuroItem(int i)
593{
594buildlist();
595return (NeuroItem*)items(i);
596}
597
598NeuroItem *OldItems::addNewNeuroItem()
599{
600Neuro *nu=neuro.getModel().addNewNeuro();
601nu->setClassName("-");
602if (listok) items+=nu;
603neuro.addInput(nu);
604return (NeuroItem*)nu;
605}
606
607int OldItems::findNeuroItem(NeuroItem *ni)
608{
609buildlist();
610return items.find((void*)ni);
611}
612#endif
613
614///////////////////////////////////////
615
616const SString& Part::getDefaultStyle()
617{static SString s("part"); return s;}
618const SString& Joint::getDefaultStyle()
619{static SString s("joint"); return s;}
620/*
621const SString& Neuro::getDefaultStyle()
622{static SString s("neuro"); return s;}
623const SString& NeuroItem::getDefaultStyle()
624{static SString s("neuroitem"); return s;}
625*/
626const SString& Neuro::getDefaultStyle()
627{static SString s("neuro"); return s;}
628
629Part::Part():PartBase(getDefaultStyle())
630{
631o=Orient_1;
632p=Pt3D_0;
633rot=Pt3D_0;
634flags=0;
635#include "defassign-f0_part.h"
636}
637
638void Part::operator=(const Part& src)
639{
640p=src.p; o=src.o;
641flags=src.flags;
642mass=src.mass; density=src.density;
643friction=src.friction;
644ingest=src.ingest;
645assim=src.assim;
646size=src.size;
647rot=src.rot;
648refno=src.refno;
649}
650
651Param& Part::getStaticParam()
652{
653static Param p(f0_part_paramtab,0,"Part");
654return p;
655}
656
657
658///////////////////////////
659
660Joint::Joint():PartBase(getDefaultStyle())
661{
662rot=Pt3D_0;
663#include "defassign-f0_joint.h"
664d.x=JOINT_DELTA_MARKER;
665part1=0; part2=0;
666flags=0;
667usedelta=0;
668}
669
670void Joint::operator=(const Joint& src)
671{
672rot=src.rot;
673d=src.d;
674stamina=src.stamina;
675stif=src.stif; rotstif=src.rotstif;
676part1=0; part2=0;
677flags=src.flags;
678usedelta=src.usedelta;
679refno=src.refno;
680}
681
682void Joint::attachToParts(Part* p1,Part* p2)
683{
684part1=p1;
685part2=p2;
686if (p1 && p2)
687        {
688        o=rot;
689        if (usedelta)
690                {
691                p1->o.transform(p2->o,o);
692//              p2->o.x=p1->o/o.x; p2->o.y=p1->o/o.y; p2->o.z=p1->o/o.z;
693                p2->p=p2->o.transform(d)+p1->p;
694                }
695        }
696}
697
698void Joint::attachToParts(int p1,int p2)
699{
700attachToParts((p1>=0)?owner->getPart(p1):0,(p2>=0)?owner->getPart(p2):0);
701}
702
703void Joint::resetDelta()
704{
705d=Pt3D(JOINT_DELTA_MARKER,0,0);
706}
707
708void Joint::useDelta(int false_or_true)
709{
710usedelta=false_or_true;
711}
712
713int Joint::isDelta()
714{
715return usedelta;
716}
717
718Param& Joint::getStaticParam()
719{
720static Param p(f0_joint_paramtab,0,"Joint");
721return p;
722}
723
724
725/////////////////////////////////////////////////////////////////
726/////////////////////////////////////////////////////////////////
727
728#include "f0classes.h"
729
730#ifdef MODEL_V1_COMPATIBLE
731
732#define FIELDSTRUCT Neuro
733ParamEntry f0_old_neuro_tab[]=
734{
735{"Connections",2,6,"n",},
736{"Other properties",},
737{"p",0,0,"part ref#","d",FIELD(part_refno),},
738{"j",0,0,"joint ref#","d",FIELD(joint_refno),},
739{"s",1,0,"state","f",FIELD(state),},
740{"in",1,0,"Inertia","f",FIELD(inertia),},
741{"fo",1,0,"Force","f",FIELD(force),},
742{"si",1,0,"Sigmoid","f",FIELD(sigmo),},
743{0,0,0,},
744};
745#undef FIELDSTRUCT
746
747#define FIELDSTRUCT NeuroItem
748ParamEntry f0_neuroitem_paramtab[]=
749{
750{"Connections",3,12,"ni",},
751{"Geometry",},
752{"Other",},
753{"n",0,0,"neuron ref#","d",FIELD(neuro_refno),},
754{"c",2,0,"connection ref#","d",FIELD(conn_refno),},
755{"w",2,0,"connection weight","f",FIELD(weight),},
756{"p",0,0,"part ref#","d",FIELD(part_refno),},
757{"j",0,0,"joint ref#","d",FIELD(joint_refno),},
758{"px",1,0,"position.x","f",FIELD(pos.x),},
759{"py",1,0,"position.y","f",FIELD(pos.y),},
760{"pz",1,0,"position.z","f",FIELD(pos.z),},
761{"rx",1,0,"rotation.x","f",FIELD(rot.x),},
762{"ry",1,0,"rotation.y","f",FIELD(rot.y),},
763{"rz",1,0,"rotation.z","f",FIELD(rot.z),},
764{"d",2,0,"item details","s",GETSET(details),},
765{0,0,0,},
766};
767#undef FIELDSTRUCT
768#endif
769
770////////////////////////////////////////
771
772ParamEntry Neuro::emptyParamTab[]=
773{
774{"Undefined Neuro",1,0,"?",},
775{0,0,0,},
776};
777
778static Param staticparam;
779
780ParamInterface &Part::extraProperties()
781{
782staticparam.setParamTab(f0_part_xtra_paramtab);
783staticparam.select(this);
784return staticparam;
785}
786
787ParamInterface &Joint::extraProperties()
788{
789staticparam.setParamTab(f0_joint_xtra_paramtab);
790staticparam.select(this);
791return staticparam;
792}
793
794ParamInterface &Neuro::extraProperties()
795{
796staticparam.setParamTab(f0_neuro_xtra_paramtab);
797staticparam.select(this);
798return staticparam;
799}
800
801ParamInterface &Part::properties()
802{
803staticparam.setParamTab(f0_part_paramtab);
804staticparam.select(this);
805return staticparam;
806}
807
808ParamInterface &Joint::properties()
809{
810staticparam.setParamTab(usedelta?f0_joint_paramtab:f0_nodeltajoint_paramtab);
811staticparam.select(this);
812return staticparam;
813}
814
815ParamInterface &Neuro::properties()
816{
817staticparam.setParamTab(f0_neuro_paramtab);
818staticparam.select(this);
819return staticparam;
820}
821
822class NeuroExtParamTab: public ParamTab
823{
824  public:
825NeuroExtParamTab():ParamTab(f0_neuro_paramtab)
826        {
827#define FIELDSTRUCT NeuroExt
828        ParamEntry entry={"class",2,0,"neuro class","s",GETSET(neuroclass)};
829#undef FIELDSTRUCT
830        add(&entry);
831
832#define FIELDSTRUCT Neuro
833        ParamEntry entry2={"state",2,0,"state","f",FIELD(state)};
834#undef FIELDSTRUCT
835        add(&entry2);
836        }
837};
838
839Param& Neuro::getStaticParam()
840{
841static Param p(f0_neuro_paramtab,0,"NeuroDef");
842return p;
843}
844
845////////////////////////
846
847NeuroConn::NeuroConn()
848{
849#include "defassign-f0_neuroconn.h"
850}
851
852//////////////////////////////////////
853
854ParamEntry *NeuroExt::getParamTab()
855{
856static NeuroExtParamTab tab;
857return tab.getParamTab();
858}
859
860void NeuroExt::get_neuroclass(PARAMGETARGS)
861{ret->setString(getClassName());}
862
863int NeuroExt::set_neuroclass(PARAMSETARGS)
864{setClassName(arg->getString());return PSET_CHANGED;}
865
866
Note: See TracBrowser for help on using the repository browser.