diff --git a/src/TutorialCode/Tutorial0/tutorial0.pro b/src/TutorialCode/Tutorial0/tutorial0.pro --- a/src/TutorialCode/Tutorial0/tutorial0.pro +++ b/src/TutorialCode/Tutorial0/tutorial0.pro @@ -32,7 +32,7 @@ LIBDIR = $${VLEAFHOME}/lib INCDIR = $${VLEAFHOME}/src DEFINES = QTGRAPHICS # VLEAFPLUGIN DESTDIR = $${BINDIR}/models -HEADERS = $${TARGET}.h +HEADERS = $${TARGET}.h $${INCDIR}/simplugin.h INCLUDEPATH += $${INCDIR} QMAKE_CXXFLAGS += -fexceptions #-I$${INCDIR} diff --git a/src/VirtualLeaf.cpp b/src/VirtualLeaf.cpp --- a/src/VirtualLeaf.cpp +++ b/src/VirtualLeaf.cpp @@ -226,6 +226,13 @@ INIT { } else { mesh.StandardInit(); } + + Cell::SetMagnification(1); + Cell::setOffset(0,0); + + FitLeafToCanvas(); + Plot(); + } TIMESTEP { @@ -412,22 +419,25 @@ int main(int argc,char **argv) { QObject::connect( qApp, SIGNAL(lastWindowClosed()), qApp, SLOT(quit()) ); } + // main_window->Init(leaffile); + // Install model or read catalogue of models ModelCatalogue model_catalogue(&mesh, useGUI?(Main *)main_window:0,modelfile); + + if (useGUI) model_catalogue.PopulateModelMenu(); model_catalogue.InstallFirstModel(); + + - if (leaffile) - main_window->Init(leaffile); - - Cell::SetMagnification(1); + /* Cell::SetMagnification(1); Cell::setOffset(0,0); main_window->FitLeafToCanvas(); main_window->Plot(); - + */ if (batch) { double t=0.; do { diff --git a/src/VirtualLeaf.pro b/src/VirtualLeaf.pro --- a/src/VirtualLeaf.pro +++ b/src/VirtualLeaf.pro @@ -19,8 +19,8 @@ # Copyright 2010 Roeland Merks. # -CONFIG += release -CONFIG -= debug +CONFIG -= release +CONFIG += debug CONFIG += qt QMAKE_CXXFLAGS += -fexceptions @@ -82,6 +82,7 @@ HEADERS += \ cell.h \ cellitem.h \ forwardeuler.h \ + hull.h \ infobar.h \ mainbase.h \ mainbase.h \ @@ -123,6 +124,7 @@ SOURCES += \ cell.cpp \ cellitem.cpp \ forwardeuler.cpp \ + hull.cpp \ mainbase.cpp \ matrix.cpp \ mesh.cpp \ diff --git a/src/build_models/auxingrowthplugin.cpp b/src/build_models/auxingrowthplugin.cpp --- a/src/build_models/auxingrowthplugin.cpp +++ b/src/build_models/auxingrowthplugin.cpp @@ -257,6 +257,9 @@ void AuxinGrowthPlugin::CellDynamics(Cel } } + + + Q_EXPORT_PLUGIN2(auxingrowthplugin, AuxinGrowthPlugin) /* finis */ diff --git a/src/build_models/auxingrowthplugin.h b/src/build_models/auxingrowthplugin.h --- a/src/build_models/auxingrowthplugin.h +++ b/src/build_models/auxingrowthplugin.h @@ -55,7 +55,9 @@ class AuxinGrowthPlugin : public QObject virtual void SetCellColor(CellBase *c, QColor *color); // return number of chemicals virtual int NChem(void) { return 2; } - + + virtual QString DefaultLeafML(void) { return QString("auxin_growth.xml"); } + private: double complex_PijAj(CellBase *here, CellBase *nb, Wall *w); }; diff --git a/src/canvas.cpp b/src/canvas.cpp --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -477,6 +477,7 @@ Main::Main(QGraphicsScene& c, Mesh &m, Q file->insertItem("Read previous leaf", this, SLOT(readPrevStateXML()), Qt::Key_PageUp); file->insertItem("Read last leaf", this, SLOT(readLastStateXML()), Qt::Key_End); file->insertItem("Read first leaf", this, SLOT(readFirstStateXML()), Qt::Key_Home); + file->insertItem("Export cell areas", this, SLOT(exportCellData())); file->insertSeparator(); file->insertItem("&Print...", this, SLOT(print()), Qt::CTRL+Qt::Key_P); @@ -1353,4 +1354,18 @@ xmlNode *Main::XMLSettingsTree(void) return settings; } +void Main::exportCellData(void) { + + // perhaps make this more general: a popup window were user selects data he wants to export + // can go to "settings" section of XML file as well so this can also be measured off-line + // mesh.CSVExport would take an QMap or so to record all options + // first line gives legenda + QFile file("areas.csv"); + if ( file.open( IO_WriteOnly ) ) { + QTextStream stream( &file ); + mesh.CSVExportCellData(stream); + mesh.CSVExportMeshData(stream); + file.close(); + } +} /* finis */ diff --git a/src/canvas.h b/src/canvas.h --- a/src/canvas.h +++ b/src/canvas.h @@ -206,6 +206,7 @@ class Main : public Q3MainWindow, public void readPrevStateXML(); void readFirstStateXML(); void readLastStateXML(); + void exportCellData(); void saveStateXML(); void snapshot(); void savePars(); diff --git a/src/hull.cpp b/src/hull.cpp new file mode 100644 --- /dev/null +++ b/src/hull.cpp @@ -0,0 +1,110 @@ +// Copyright 2001, softSurfer (www.softsurfer.com) +// This code may be freely used and modified for any purpose +// providing that this copyright notice is included with it. +// SoftSurfer makes no warranty for this code, and cannot be held +// liable for any real or imagined damage resulting from its use. +// Users of this code must verify correctness for their application. + +// Assume that a class is already given for the object: +// Point with coordinates {float x, y;} +//=================================================================== + +// isLeft(): tests if a point is Left|On|Right of an infinite line. +// Input: three points P0, P1, and P2 +// Return: >0 for P2 left of the line through P0 and P1 +// =0 for P2 on the line +// <0 for P2 right of the line +// See: the January 2001 Algorithm on Area of Triangles + +#include "hull.h" + + +inline float +isLeft( Point P0, Point P1, Point P2 ) +{ + return (P1.x - P0.x)*(P2.y - P0.y) - (P2.x - P0.x)*(P1.y - P0.y); +} +//=================================================================== + + +// chainHull_2D(): Andrew's monotone chain 2D convex hull algorithm +// Input: P[] = an array of 2D points +// presorted by increasing x- and y-coordinates +// n = the number of points in P[] +// Output: H[] = an array of the convex hull vertices (max is n) +// Return: the number of points in H[] +int +chainHull_2D( Point* P, int n, Point* H ) +{ + // the output array H[] will be used as the stack + int bot=0, top=(-1); // indices for bottom and top of the stack + int i; // array scan index + + // Get the indices of points with min x-coord and min|max y-coord + int minmin = 0, minmax; + float xmin = P[0].x; + for (i=1; i=0; i--) + if (P[i].x != xmax) break; + maxmin = i+1; + + // Compute the lower hull on the stack H + H[++top] = P[minmin]; // push minmin point onto stack + i = minmax; + while (++i <= maxmin) + { + // the lower line joins P[minmin] with P[maxmin] + if (isLeft( P[minmin], P[maxmin], P[i]) >= 0 && i < maxmin) + continue; // ignore P[i] above or on the lower line + + while (top > 0) // there are at least 2 points on the stack + { + // test if P[i] is left of the line at the stack top + if (isLeft( H[top-1], H[top], P[i]) > 0) + break; // P[i] is a new hull vertex + else + top--; // pop top point off stack + } + H[++top] = P[i]; // push P[i] onto stack + } + + // Next, compute the upper hull on the stack H above the bottom hull + if (maxmax != maxmin) // if distinct xmax points + H[++top] = P[maxmax]; // push maxmax point onto stack + bot = top; // the bottom point of the upper hull stack + i = maxmin; + while (--i >= minmax) + { + // the upper line joins P[maxmax] with P[minmax] + if (isLeft( P[maxmax], P[minmax], P[i]) >= 0 && i > minmax) + continue; // ignore P[i] below or on the upper line + + while (top > bot) // at least 2 points on the upper stack + { + // test if P[i] is left of the line at the stack top + if (isLeft( H[top-1], H[top], P[i]) > 0) + break; // P[i] is a new hull vertex + else + top--; // pop top point off stack + } + H[++top] = P[i]; // push P[i] onto stack + } + if (minmax != minmin) + H[++top] = P[minmin]; // push joining endpoint onto stack + + return top+1; +} + diff --git a/src/hull.h b/src/hull.h new file mode 100644 --- /dev/null +++ b/src/hull.h @@ -0,0 +1,17 @@ +// Class point needed by 2D convex hull code +class Point { + +public: + Point(float xx, float yy) { + x=xx; + y=yy; + } + Point(void) { + x=0; y=0; + } + float x,y; + +}; + +int chainHull_2D( Point* P, int n, Point* H ); + diff --git a/src/mainbase.h b/src/mainbase.h --- a/src/mainbase.h +++ b/src/mainbase.h @@ -63,7 +63,7 @@ class MainBase { virtual ~MainBase() {}; virtual double TimeStep(); - virtual void Init(char *leaffile=0); + virtual void Init(const char *leaffile=0); virtual bool ShowCentersP(void) {return showcentersp;} virtual bool ShowMeshP(void) {return showmeshp; } @@ -133,7 +133,7 @@ class MainBase { //#include #define TIMESTEP double MainBase::TimeStep(void) -#define INIT void MainBase::Init(char *leaffile) +#define INIT void MainBase::Init(const char *leaffile) #endif diff --git a/src/mesh.cpp b/src/mesh.cpp --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -46,6 +46,7 @@ #include #include #include +#include static const std::string _module_id("$Id$"); @@ -467,7 +468,10 @@ void Mesh::Clear(void) { cells.clear(); Cell::NCells() = 0; - delete boundary_polygon; + if (boundary_polygon) { + delete boundary_polygon; + boundary_polygon=0; + } // Clear walls for (list::iterator i=walls.begin(); i!=walls.end(); i++) { @@ -1995,8 +1999,10 @@ void Mesh::Clean(void) { cells.clear(); Cell::NCells()=0; - delete boundary_polygon; // (already deleted during cleaning of cells?) - + if (boundary_polygon) { + delete boundary_polygon; // (already deleted during cleaning of cells?) + boundary_polygon=0; + } #ifdef QDEBUG qDebug() << "Freeing walls" << endl; #endif @@ -2027,4 +2033,97 @@ void Mesh::StandardInit(void) { } } +#include "hull.h" + +double Mesh::Compactness(double *res_compactness, double *res_area, double *res_cell_area) { + + // Calculate compactness using the convex hull of the cells + // We use Andrew's Monotone Chain Algorithm (see hull.cpp) + + // Step 1. Prepare data for 2D hull code - get boundary polygon + int pc=0; + Point *p=new Point[boundary_polygon->nodes.size()]; + for (list::const_iterator i = boundary_polygon->nodes.begin(); + i!=boundary_polygon->nodes.end(); i++) { + p[pc++]=Point((*i)->x,(*i)->y); + } + + // Step 2: call 2D Hull code + int np=boundary_polygon->nodes.size(); + Point *hull=new Point[np]; + int nph=chainHull_2D(p,np,hull); + + + // Step 3: calculate area of convex hull + double hull_area=0.; + for (int i=0;iCalcArea(); + + + /* ofstream datastr("hull.dat"); + for (int i=0;i::const_iterator i=cells.begin(); + i!=cells.end(); + i++) { + Vector centroid = (*i)->Centroid(); + csv_stream << (*i)->Index() << ", " + << centroid.x << ", " + << centroid.y << ", " + << (*i)->Area() << ", " + <<(*i)->Length(); + for (int c=0;cChemical(c); + } + csv_stream << endl; + } +} + + +void Mesh::CSVExportMeshData(QTextStream &csv_stream) { + + csv_stream << "\"Mesh area\",\"Number of cells\",\"Number of nodes\",\"Compactness\",\"Hull area\",\"Cell area\"" << endl; + + double res_compactness, res_area, res_cell_area; + Compactness(&res_compactness, &res_area, &res_cell_area); + csv_stream << Area() << ", " << NCells() << ", " << NNodes() << ", " << res_compactness << ", " << res_area << ", " << res_cell_area << endl; + +} /* finis */ diff --git a/src/mesh.h b/src/mesh.h --- a/src/mesh.h +++ b/src/mesh.h @@ -41,6 +41,7 @@ #include #include #include +#include using namespace std; // new queue which rejects duplicate elements @@ -81,9 +82,13 @@ class Mesh { time = 0.; plugin = 0; + boundary_polygon=0; }; ~Mesh(void) { - delete boundary_polygon; + if (boundary_polygon) { + delete boundary_polygon; + boundary_polygon=0; + } }; void Clean(void); @@ -380,7 +385,10 @@ class Mesh { } QString ModelID(void) { return plugin?plugin->ModelID():QString("undefined"); } void StandardInit(void); - + double Compactness(double *res_compactness=0, double *res_area=0, double *res_cell_area=0); + void CSVExportCellData(QTextStream &csv_stream) const; + void CSVExportMeshData(QTextStream &csv_stream); + Node* findNextBoundaryNode(Node*); private: diff --git a/src/modelcatalogue.cpp b/src/modelcatalogue.cpp --- a/src/modelcatalogue.cpp +++ b/src/modelcatalogue.cpp @@ -147,14 +147,58 @@ void ModelCatalogue::InstallModel(QActio void ModelCatalogue::InstallModel(SimPluginInterface *plugin) { // make sure both main and plugin use the same static datamembers (ncells, nchems...) + mesh->Clean(); plugin->SetCellsStaticDatamembers(CellBase::GetStaticDataMemberPointer()); mesh->SetSimPlugin(plugin); + + Cell::SetNChem(plugin->NChem()); plugin->SetParameters(&par); - + if (mainwin) { mainwin->RefreshInfoBar(); - mainwin->Init(0); + if (plugin->DefaultLeafML().isEmpty()) { + mainwin->Init(0); + } else { + // locate LeafML file + + QDir pluginDir(QApplication::applicationDirPath()); + QStringList plugin_filters; // filter for plugins, i.e "*.dll", "*.dylib" + + +#if defined(Q_OS_WIN) + if (pluginDir.dirName().toLower() =="debug" + ||pluginDir.dirName().toLower() =="release") + pluginDir.cdUp(); + //plugin_filters << "*.dll"; +#elif defined(Q_OS_MAC) + if (pluginDir.dirName() =="MacOS"){ + pluginDir.cdUp(); + pluginDir.cdUp(); + pluginDir.cdUp(); + } + +#endif + // for all OS-es. Move from "bin" directory to root application folder. + pluginDir.cdUp(); + cerr << "pluginDir: " << pluginDir.dirName().toStdString().c_str() << endl; + if (!pluginDir.cd("data/leaves")) { + MyWarning::warning("Directory 'data/leaves' not found! Cannot load LeafML file '%s'. Reverting to standard initial condition now...",plugin->DefaultLeafML().toStdString().c_str()); + mainwin->Init(0); + } else { + + if (!pluginDir.exists(plugin->DefaultLeafML())) { + MyWarning::error("LeafML file '%s' not found - hint: is file in data/leaves folder? Reverting to standard initial condition now...",plugin->DefaultLeafML().toStdString().c_str()); + mainwin->Init(0); + } else { + // Initialize simulation using default LeafML file referenced in plugin. + //mainwin->Init(0); + cerr << "Default LeafML: " << plugin->DefaultLeafML().toStdString().c_str() << endl; + mainwin->Init(pluginDir.absFilePath(plugin->DefaultLeafML()).toStdString().c_str()); + } + } + } } + } diff --git a/src/simplugin.cpp b/src/simplugin.cpp --- a/src/simplugin.cpp +++ b/src/simplugin.cpp @@ -20,6 +20,7 @@ */ #include +#include #include "simplugin.h" static const std::string _module_id("$Id$"); @@ -34,4 +35,6 @@ void SimPluginInterface::SetCellsStaticD CellBase::static_data_members = cells_static_data_members_of_main; } +QString SimPluginInterface::DefaultLeafML(void) { return QString(); } + /* finis */ diff --git a/src/simplugin.h b/src/simplugin.h --- a/src/simplugin.h +++ b/src/simplugin.h @@ -65,6 +65,9 @@ class SimPluginInterface { // Number of chemicals virtual int NChem(void) = 0; + // Default LeafML-file to be read after model startup + virtual QString DefaultLeafML(void); + // For internal use; not to be redefined by end users virtual void SetParameters(Parameter *pass_pars);// { par = pass_pars; } virtual void SetCellsStaticDatamembers (CellsStaticDatamembers *cells_static_data_members_of_main); @@ -73,7 +76,7 @@ class SimPluginInterface { class Parameter *par; }; -Q_DECLARE_INTERFACE(SimPluginInterface, "nl.cwi.VirtualLeaf.SimPluginInterface/1.2") +Q_DECLARE_INTERFACE(SimPluginInterface, "nl.cwi.VirtualLeaf.SimPluginInterface/1.3") Q_DECLARE_METATYPE(SimPluginInterface *) #endif diff --git a/src/xmlwrite.cpp b/src/xmlwrite.cpp --- a/src/xmlwrite.cpp +++ b/src/xmlwrite.cpp @@ -1446,7 +1446,10 @@ void Mesh::XMLReadCells(xmlNode *root) cells.clear(); Cell::NCells() = 0; - delete boundary_polygon; + if (boundary_polygon) { + delete boundary_polygon; + boundary_polygon=0; + } xmlNode *cur = root->xmlChildrenNode;