More configuration parameters for P&R Conductor, for experimenting.

* Change: In Hurricane::Viewer::ExceptionWidget & CRL/python/helpers/io.py,
    downscale icons for non-HiDPI screen.
* Change: In CRL/etc/common/display.py, change the display threshold of
    METAL1 layer so it do not appear at high scaling.
* New: In Etesian, activate the "setFixedAbHeight()" feature and export it
    to Python. Allows to increase the space margin at constant height.
      To be used by the P&R conductor.
* Change: In Unicorn/cgt.py, when running a script, insert the current
    working directory at head of the sys.path, not at the end. So installed
    modules do not shadow local one.
* New: In Anabatic::Edge, new accessor "getRawcapacity()" to know the full
    capacity of the edge, that is, the real maximum number of tracks that
    can go through the associated side.
* Change: In Katana/BloatProfile/Slice::tagsOverloaded(), bloating policy
    change, now bloat all the instances under the GCell, not only the first
    one.
* Change: In KatanaEngine::runGlobalRouter(), restore the search halo growth
    policy to expanding of 3 GCells every 3 iterations.
      New metrics displayed, the edge wire length length overload.
* Change: In cumulus/plugins/ConductorPlugin.py, now accepts the following
    configuration parameters:
      - "anabatic.globalIterationsEstimate", maximum number of global
        iterations during the bloating computation passes.
      - "conductor.useFixedAbHeight", tells wether the additionnal blank
        space must be added so the aspect ratio is kept or the height is
	kept (and the block become wider).
    Disable the display of "rubber" to unclutter a little the view.
This commit is contained in:
Jean-Paul Chaput 2019-12-15 19:28:54 +01:00
parent 9812f2fc3a
commit e711ce8dd2
11 changed files with 186 additions and 88 deletions

View File

@ -60,6 +60,7 @@ namespace Anabatic {
inline bool isHorizontal () const; inline bool isHorizontal () const;
inline bool hasNet ( const Net* ) const; inline bool hasNet ( const Net* ) const;
inline unsigned int getCapacity () const; inline unsigned int getCapacity () const;
inline unsigned int getRawCapacity () const;
inline unsigned int getReservedCapacity () const; inline unsigned int getReservedCapacity () const;
inline unsigned int getCapacity ( size_t depth ) const; inline unsigned int getCapacity ( size_t depth ) const;
inline unsigned int getRealOccupancy () const; inline unsigned int getRealOccupancy () const;
@ -137,6 +138,7 @@ namespace Anabatic {
inline bool Edge::isHorizontal () const { return _flags.isset(Flags::Horizontal); } inline bool Edge::isHorizontal () const { return _flags.isset(Flags::Horizontal); }
inline bool Edge::hasNet ( const Net* owner ) const { return getSegment(owner); } inline bool Edge::hasNet ( const Net* owner ) const { return getSegment(owner); }
inline unsigned int Edge::getCapacity ( size_t depth ) const { return (_capacities) ? _capacities->getCapacity(depth) : 0; } inline unsigned int Edge::getCapacity ( size_t depth ) const { return (_capacities) ? _capacities->getCapacity(depth) : 0; }
inline unsigned int Edge::getRawCapacity () const { return (_capacities) ? _capacities->getCapacity() : 0; }
inline unsigned int Edge::getReservedCapacity () const { return _reservedCapacity; } inline unsigned int Edge::getReservedCapacity () const { return _reservedCapacity; }
inline unsigned int Edge::getRealOccupancy () const { return _realOccupancy; } inline unsigned int Edge::getRealOccupancy () const { return _realOccupancy; }
inline float Edge::getEstimateOccupancy () const { return _estimateOccupancy; } inline float Edge::getEstimateOccupancy () const { return _estimateOccupancy; }

View File

@ -229,7 +229,7 @@ def createStyles ( scale=1.0 ):
style.addDrawingStyle( group='Active Layers', name='poly2' , color=toRGB('Orange' ), pattern=toHexa('poids2.8' ), border=1, threshold=0.00*scale ) style.addDrawingStyle( group='Active Layers', name='poly2' , color=toRGB('Orange' ), pattern=toHexa('poids2.8' ), border=1, threshold=0.00*scale )
# Routing Layers. # Routing Layers.
style.addDrawingStyle( group='Routing Layers', name='metal1' , color=toRGB('Blue' ), pattern=toHexa('slash.8' ), border=1, threshold=0.00*scale ) style.addDrawingStyle( group='Routing Layers', name='metal1' , color=toRGB('Blue' ), pattern=toHexa('slash.8' ), border=1, threshold=0.80*scale )
style.addDrawingStyle( group='Routing Layers', name='metal2' , color=toRGB('Aqua' ), pattern=toHexa('poids4.8' ), border=1, threshold=0.00*scale ) style.addDrawingStyle( group='Routing Layers', name='metal2' , color=toRGB('Aqua' ), pattern=toHexa('poids4.8' ), border=1, threshold=0.00*scale )
style.addDrawingStyle( group='Routing Layers', name='metcap' , color=toRGB('DarkTurquoise'), pattern=toHexa('poids2.8' ), border=2, threshold=0.00*scale ) style.addDrawingStyle( group='Routing Layers', name='metcap' , color=toRGB('DarkTurquoise'), pattern=toHexa('poids2.8' ), border=2, threshold=0.00*scale )
style.addDrawingStyle( group='Routing Layers', name='metal3' , color=toRGB('LightPink' ), pattern=toHexa('poids4.8' ), border=1, threshold=0.00*scale ) style.addDrawingStyle( group='Routing Layers', name='metal3' , color=toRGB('LightPink' ), pattern=toHexa('poids4.8' ), border=1, threshold=0.00*scale )

View File

@ -121,8 +121,10 @@ class ErrorWidget ( QDialog ):
vLayout.addStretch( 1 ) vLayout.addStretch( 1 )
vLayout.addLayout ( buttonLayout ) vLayout.addLayout ( buttonLayout )
pixmapWidth = 150
if not Viewer.Graphics.isHighDpi(): pixmapWidth = 70
pixmap = QPixmap( ':/images/angry-birds-red.png' ) pixmap = QPixmap( ':/images/angry-birds-red.png' )
pixmap = pixmap.scaledToWidth( 150 ) pixmap = pixmap.scaledToWidth( pixmapWidth )
icon = QLabel() icon = QLabel()
icon.setPixmap( pixmap ) icon.setPixmap( pixmap )

View File

@ -20,6 +20,7 @@ try:
import math import math
import Cfg import Cfg
import Hurricane import Hurricane
from Hurricane import DbU
from Hurricane import Breakpoint from Hurricane import Breakpoint
from Hurricane import UpdateSession from Hurricane import UpdateSession
import Viewer import Viewer
@ -66,10 +67,25 @@ def ScriptMain ( **kw ):
stopLevel = Cfg.getParamInt('conductor.stopLevel').asInt() stopLevel = Cfg.getParamInt('conductor.stopLevel').asInt()
Breakpoint.setStopLevel( stopLevel ) Breakpoint.setStopLevel( stopLevel )
Cfg.Configuration.pushDefaultPriority( Cfg.Parameter.Priority.Interactive )
grIterations = 10
if Cfg.hasParameter('anabatic.globalIterations'):
grIterations = Cfg.getParamInt('anabatic.globalIterations').asInt()
grIterationsEstimate = 7
if Cfg.hasParameter('anabatic.globalIterationsEstimate'):
grIterationsEstimate = Cfg.getParamInt('anabatic.globalIterationsEstimate').asInt()
Cfg.getParamInt('anabatic.globalIterations').setInt( grIterationsEstimate )
maxPlaceIterations = 2 maxPlaceIterations = 2
if Cfg.hasParameter('conductor.maxPlaceIterations'): if Cfg.hasParameter('conductor.maxPlaceIterations'):
maxPlaceIterations = Cfg.getParamInt('conductor.maxPlaceIterations').asInt() maxPlaceIterations = Cfg.getParamInt('conductor.maxPlaceIterations').asInt()
useFixedAbHeight = False
if Cfg.hasParameter('conductor.useFixedAbHeight'):
useFixedAbHeight = Cfg.getParamBool('conductor.useFixedAbHeight').asBool()
cell = None cell = None
if kw.has_key('cell') and kw['cell']: if kw.has_key('cell') and kw['cell']:
cell = kw['cell'] cell = kw['cell']
@ -78,6 +94,7 @@ def ScriptMain ( **kw ):
if kw.has_key('editor') and kw['editor']: if kw.has_key('editor') and kw['editor']:
editor = kw['editor'] editor = kw['editor']
print ' o Editor found, running in graphic mode.' print ' o Editor found, running in graphic mode.'
editor.setLayerVisible( 'rubber', False )
if cell == None: cell = editor.getCell() if cell == None: cell = editor.getCell()
if cell == None: if cell == None:
@ -102,7 +119,11 @@ def ScriptMain ( **kw ):
etesian = Etesian.EtesianEngine.create( cell ) etesian = Etesian.EtesianEngine.create( cell )
etesian.setPassNumber( iteration ) etesian.setPassNumber( iteration )
if editor: etesian.setViewer( editor ) if editor: etesian.setViewer( editor )
if iteration: etesian.resetPlacement() if iteration:
if useFixedAbHeight and iteration == 1:
etesian.setFixedAbHeight( cell.getAbutmentBox().getHeight() )
print 'etesian.setFixedAbHeight():', DbU.getValueString(cell.getAbutmentBox().getHeight())
etesian.resetPlacement()
etesian.place() etesian.place()
etesian.destroy() etesian.destroy()
etesian = None etesian = None
@ -110,6 +131,9 @@ def ScriptMain ( **kw ):
editor.refresh() editor.refresh()
editor.fit() editor.fit()
if iteration+1 == maxPlaceIterations:
Cfg.getParamInt('anabatic.globalIterations').setInt( grIterations )
katana = Katana.KatanaEngine.create( cell ) katana = Katana.KatanaEngine.create( cell )
katana.setPassNumber( iteration ) katana.setPassNumber( iteration )
if editor: katana.setViewer( editor ) if editor: katana.setViewer( editor )
@ -120,6 +144,8 @@ def ScriptMain ( **kw ):
) )
#| Katana.Flags.ShowFailedNets #| Katana.Flags.ShowFailedNets
Breakpoint.stop( 1, 'After routing iteration %d' % iteration ) Breakpoint.stop( 1, 'After routing iteration %d' % iteration )
if editor:
editor.setShowSelection( False )
if katana.isGlobalRoutingSuccess(): break if katana.isGlobalRoutingSuccess(): break
iteration += 1 iteration += 1
@ -138,6 +164,8 @@ def ScriptMain ( **kw ):
#plugins.RSavePlugin.ScriptMain( **kw ) #plugins.RSavePlugin.ScriptMain( **kw )
Cfg.Configuration.popDefaultPriority()
except Exception, e: except Exception, e:
catch( e ) catch( e )

View File

@ -394,7 +394,7 @@ namespace Etesian {
double gcellLength = cellLength*(1.0+spaceMargin) / DbU::toLambda( getSliceHeight() ); double gcellLength = cellLength*(1.0+spaceMargin) / DbU::toLambda( getSliceHeight() );
double rows = 0.0; double rows = 0.0;
setFixedAbHeight( 0 ); //setFixedAbHeight( 0 );
if (getFixedAbHeight()) rows = getFixedAbHeight() / getSliceHeight(); if (getFixedAbHeight()) rows = getFixedAbHeight() / getSliceHeight();
else rows = std::ceil( sqrt( gcellLength/aspectRatio ) ); else rows = std::ceil( sqrt( gcellLength/aspectRatio ) );

View File

@ -69,6 +69,7 @@ extern "C" {
DirectVoidMethod(EtesianEngine,etesian,setDefaultAb) DirectVoidMethod(EtesianEngine,etesian,setDefaultAb)
DirectVoidMethod(EtesianEngine,etesian,resetPlacement) DirectVoidMethod(EtesianEngine,etesian,resetPlacement)
DirectSetLongAttribute(PyEtesianEngine_setFixedAbHeight,setFixedAbHeight,PyEtesianEngine,EtesianEngine)
static PyObject* PyEtesianEngine_get ( PyObject*, PyObject* args ) static PyObject* PyEtesianEngine_get ( PyObject*, PyObject* args )
@ -208,6 +209,8 @@ extern "C" {
, "Set the sub-block (Instance) to place." } , "Set the sub-block (Instance) to place." }
, { "setDefaultAb" , (PyCFunction)PyEtesianEngine_setDefaultAb , METH_NOARGS , { "setDefaultAb" , (PyCFunction)PyEtesianEngine_setDefaultAb , METH_NOARGS
, "Compute and set the abutment box using the aspect ratio and the space margin." } , "Compute and set the abutment box using the aspect ratio and the space margin." }
, { "setFixedAbHeight" , (PyCFunction)PyEtesianEngine_setFixedAbHeight , METH_VARARGS
, "Use this height when computing the size of the default abutment box (disable aspect ratio)." }
, { "resetPlacement" , (PyCFunction)PyEtesianEngine_resetPlacement , METH_NOARGS , { "resetPlacement" , (PyCFunction)PyEtesianEngine_resetPlacement , METH_NOARGS
, "Compute and set the abutment box using the aspect ratio and the space margin." } , "Compute and set the abutment box using the aspect ratio and the space margin." }
, { "place" , (PyCFunction)PyEtesianEngine_place , METH_NOARGS , { "place" , (PyCFunction)PyEtesianEngine_place , METH_NOARGS

View File

@ -137,7 +137,7 @@ namespace Hurricane {
QLabel* leftMargin = new QLabel (); QLabel* leftMargin = new QLabel ();
leftMargin->setSizePolicy ( QSizePolicy::Preferred, QSizePolicy::MinimumExpanding ); leftMargin->setSizePolicy ( QSizePolicy::Preferred, QSizePolicy::MinimumExpanding );
leftMargin->setPixmap ( QPixmap(":/images/angry-birds-bomb.png").scaledToWidth(200) ); leftMargin->setPixmap ( QPixmap(":/images/angry-birds-bomb.png").scaledToWidth( Graphics::isHighDpi() ? 200 : 80 ) );
leftMargin->setStyleSheet ( "QLabel { background-color: #FF9999;" leftMargin->setStyleSheet ( "QLabel { background-color: #FF9999;"
" padding: 5px }" ); " padding: 5px }" );

View File

@ -52,6 +52,8 @@ namespace {
using Anabatic::Edge; using Anabatic::Edge;
using namespace Katana; using namespace Katana;
class Slices;
// ------------------------------------------------------------------- // -------------------------------------------------------------------
// Class : "FlatInstance". // Class : "FlatInstance".
@ -116,19 +118,21 @@ namespace {
class Slice { class Slice {
public: public:
inline Slice ( GCell* ); inline Slice ( Slices*, GCell* );
inline DbU::Unit getY () const; inline DbU::Unit getY () const;
inline void add ( Occurrence ); inline void add ( Occurrence );
inline void sort (); inline void sort ();
void tagOverloadeds ( size_t& count, size_t& newCount ); void tagOverloadeds ( size_t& count, size_t& newCount );
private: private:
Slices* _owner;
GCell* _left; GCell* _left;
vector<FlatInstance> _instances; vector<FlatInstance> _instances;
}; };
inline Slice::Slice ( GCell* left ) inline Slice::Slice ( Slices* owner, GCell* left )
: _left (left) : _owner (owner)
, _left (left)
, _instances() , _instances()
{ } { }
@ -138,68 +142,6 @@ namespace {
inline void Slice::sort () { std::sort( _instances.begin(), _instances.end() ); } inline void Slice::sort () { std::sort( _instances.begin(), _instances.end() ); }
void Slice::tagOverloadeds ( size_t& count, size_t& newCount )
{
GCell* gcell = _left;
Edge* eastEdge = _left->getEastEdge();
Edge* northEdge = _left->getNorthEdge();
bool bloated = false;
//cerr << "+ Slice @" << DbU::getValueString(getY()) << endl;
for ( FlatInstance& fi : _instances ) {
//cerr << "| @" << DbU::getValueString(fi.getX()) << " " << fi.getOccurrence() << endl;
if (fi.getX() >= gcell->getXMax()) {
for ( gcell = gcell->getEast() ; gcell and (fi.getX() > gcell->getXMax())
; gcell = gcell->getEast() ) {
//cerr << "| Skip " << gcell << endl;
}
//cerr << "| Advance to " << gcell << endl;
if (not gcell) break;
bloated = false;
eastEdge = gcell->getEastEdge();
northEdge = gcell->getNorthEdge();
}
unsigned int overload = 0;
if (eastEdge) {
if (eastEdge->getRealOccupancy() > eastEdge->getCapacity()) {
overload = eastEdge->getRealOccupancy() - eastEdge->getCapacity();
}
// else if (eastEdge->getHistoricCost() > 3.0) {
// overload = 4;
// }
}
if (northEdge) {
if (northEdge->getRealOccupancy() > northEdge->getCapacity()) {
overload = std::max( overload, northEdge->getRealOccupancy() - northEdge->getCapacity() );
}
// else if (northEdge->getHistoricCost() > 3.0) {
// overload = 4;
// }
}
if (overload and not bloated) {
bloated = true;
BloatState* state = BloatExtension::get( fi.getOccurrence() );
if (not state) {
state = BloatExtension::create( fi.getOccurrence(), overload );
//cerr << "> Bloat: " << fi.getOccurrence() << endl;
//cerr << "> Under:" << gcell << endl;
++newCount;
} else {
state->setTracksCount( state->getTracksCount() + overload );
}
++count;
}
}
}
// ------------------------------------------------------------------- // -------------------------------------------------------------------
// Class : "Slices". // Class : "Slices".
@ -207,6 +149,7 @@ namespace {
public: public:
Slices ( KatanaEngine* ); Slices ( KatanaEngine* );
~Slices (); ~Slices ();
inline KatanaEngine* getKatana () const;
inline void add ( Occurrence ); inline void add ( Occurrence );
inline void sort (); inline void sort ();
inline void tagOverloadeds (); inline void tagOverloadeds ();
@ -250,7 +193,7 @@ namespace {
, getString(left).c_str() , getString(left).c_str()
); );
_slices.push_back( new Slice( left ) ); _slices.push_back( new Slice( this, left ) );
left = left->getNorth(); left = left->getNorth();
} }
} }
@ -260,6 +203,10 @@ namespace {
{ for ( Slice* slice : _slices ) delete slice; } { for ( Slice* slice : _slices ) delete slice; }
inline KatanaEngine* Slices::getKatana () const
{ return _katana; }
inline void Slices::sort () inline void Slices::sort ()
{ for ( Slice* slice : _slices ) slice->sort(); } { for ( Slice* slice : _slices ) slice->sort(); }
@ -293,6 +240,118 @@ namespace {
} }
void Slice::tagOverloadeds ( size_t& count, size_t& newCount )
{
GCell* gcell = _left;
size_t iLeft = 0;
size_t iRight = 0;
while ( gcell ) {
//cerr << "> Under:" << gcell << endl;
uint32_t overload = 0;
for ( Edge* edge : gcell->getEdges( Flags::NorthSide|Flags::EastSide ) ) {
if (edge->getRealOccupancy() > edge->getCapacity()) {
overload = std::max( overload, edge->getRealOccupancy() - edge->getCapacity() );
// if (edge->getRealOccupancy() > edge->getRawCapacity()) {
// overload += 4;
// }
}
}
iLeft = iRight;
while ( iRight < _instances.size() ) {
if (_instances[iRight].getX() >= gcell->getXMax()) break;
++iRight;
}
if (iLeft >= _instances.size()) break;
if (overload) {
overload += 4;
for ( size_t i=iLeft ; i<iRight ; ++i ) {
BloatState* state = BloatExtension::get( _instances[i].getOccurrence() );
if (not state) {
if (_owner->getKatana()->getPassNumber() > 0) continue;
state = BloatExtension::create( _instances[i].getOccurrence(), overload );
++newCount;
//cerr << "| Bloat: " << overload << " " << _instances[i].getOccurrence() << endl;
} else {
state->setTracksCount( state->getTracksCount() + overload );
}
++count;
}
}
gcell = gcell->getEast();
}
// GCell* gcell = _left;
// Edge* eastEdge = _left->getEastEdge();
// Edge* northEdge = _left->getNorthEdge();
// bool bloated = false;
// //cerr << "+ Slice @" << DbU::getValueString(getY()) << endl;
// for ( FlatInstance& fi : _instances ) {
// //cerr << "| @" << DbU::getValueString(fi.getX()) << " " << fi.getOccurrence() << endl;
// if (fi.getX() >= gcell->getXMax()) {
// for ( gcell = gcell->getEast() ; gcell and (fi.getX() > gcell->getXMax())
// ; gcell = gcell->getEast() ) {
// //cerr << "| Skip " << gcell << endl;
// }
// //cerr << "| Advance to " << gcell << endl;
// if (not gcell) break;
// bloated = false;
// eastEdge = gcell->getEastEdge();
// northEdge = gcell->getNorthEdge();
// }
// unsigned int overload = 0;
// if (eastEdge) {
// if (eastEdge->getRealOccupancy() > eastEdge->getCapacity()) {
// overload = eastEdge->getRealOccupancy() - eastEdge->getCapacity();
// }
// // else if (eastEdge->getHistoricCost() > 3.0) {
// // overload = 4;
// // }
// }
// if (northEdge) {
// if (northEdge->getRealOccupancy() > northEdge->getCapacity()) {
// overload = std::max( overload, northEdge->getRealOccupancy() - northEdge->getCapacity() );
// }
// // else if (northEdge->getHistoricCost() > 3.0) {
// // overload = 4;
// // }
// }
// if (overload and not bloated) {
// bloated = true;
// overload += 8;
// BloatState* state = BloatExtension::get( fi.getOccurrence() );
// if (not state) {
// state = BloatExtension::create( fi.getOccurrence(), overload );
// //cerr << "> Bloat: " << fi.getOccurrence() << endl;
// //cerr << "> Under:" << gcell << endl;
// ++newCount;
// } else {
// state->setTracksCount( state->getTracksCount() + overload );
// }
// ++count;
// }
// }
}
} // Anonymous namespace. } // Anonymous namespace.

View File

@ -493,6 +493,9 @@ namespace Katana {
if (getState() >= EngineState::EngineGlobalLoaded) if (getState() >= EngineState::EngineGlobalLoaded)
throw Error ("KatanaEngine::runGlobalRouter(): Global routing already done or loaded."); throw Error ("KatanaEngine::runGlobalRouter(): Global routing already done or loaded.");
if (flags & Flags::ShowBloatedInstances) selectBloatedInstances( this );
Breakpoint::stop( 1, "Bloated cells from previous placement iteration." );
startMeasures(); startMeasures();
cmess1 << " o Running global routing." << endl; cmess1 << " o Running global routing." << endl;
@ -580,11 +583,13 @@ namespace Katana {
edgeOverflowWL = 0; edgeOverflowWL = 0;
netCount = 0; netCount = 0;
if (iteration < globalIterations - 1) { if (iteration < globalIterations - 1) {
for ( Edge* edge : ovEdges ) {
edgeOverflowWL += edge->getRealOccupancy() - edge->getCapacity();
}
size_t iEdge = 0; size_t iEdge = 0;
while ( iEdge < ovEdges.size() ) { while ( iEdge < ovEdges.size() ) {
Edge* edge = ovEdges[iEdge]; Edge* edge = ovEdges[iEdge];
edgeOverflowWL += edge->getRealOccupancy() - edge->getCapacity();
netCount += edge->ripup(); netCount += edge->ripup();
if (iEdge >= ovEdges.size()) break; if (iEdge >= ovEdges.size()) break;
@ -597,16 +602,15 @@ namespace Katana {
} }
} }
//dijkstra->setSearchAreaHalo( (getSearchHalo() + 3*(iteration/3)) * Session::getSliceHeight() ); dijkstra->setSearchAreaHalo( (getSearchHalo() + 3*(iteration/3)) * Session::getSliceHeight() );
dijkstra->setSearchAreaHalo( 3 * Session::getSliceHeight() );
} }
cmess2 << " ovE:" << setw(4) << overflow << " " << setw(5) << edgeOverflowWL; cmess2 << " ovE:" << setw(4) << overflow << " ovWL:" << setw(5) << edgeOverflowWL;
cmess2 << " ripup:" << setw(4) << netCount << right; cmess2 << " ripup:" << setw(4) << netCount << right;
suspendMeasures(); suspendMeasures();
cmess2 << " " << setw(6) << Timer::getStringMemory(getTimer().getIncrease()) cmess2 << " " << setw(7) << Timer::getStringMemory(getTimer().getIncrease())
<< " " << setw(5) << Timer::getStringTime (getTimer().getCombTime()) << endl; << " " << setw(6) << Timer::getStringTime (getTimer().getCombTime()) << endl;
resumeMeasures(); resumeMeasures();
++iteration; ++iteration;

View File

@ -68,7 +68,7 @@ def runScript ( scriptPath, editor ):
try: try:
kw = { } kw = { }
if editor: kw[ 'editor' ] = editor if editor: kw[ 'editor' ] = editor
sys.path.append(os.path.dirname(scriptPath)) sys.path.insert( 0, os.path.dirname(scriptPath) )
module = __import__( os.path.basename(scriptPath), globals(), locals() ) module = __import__( os.path.basename(scriptPath), globals(), locals() )
if not module.__dict__.has_key('ScriptMain'): if not module.__dict__.has_key('ScriptMain'):