UNPKG

webgme-gridlabd

Version:

Metamodel, visualization, and model generators for gridlab-d in WebGME. Allows graphical model-driven development and simulation of power grids and power generation / transmission / distribution / storage systems.

928 lines (874 loc) 26 kB
/*globals define*/ /*jshint node:true, browser:true*/ /** * Generated by PluginGenerator 0.14.0 from webgme on Mon Apr 04 2016 15:12:25 GMT-0700 (PDT). */ define([ 'plugin/PluginConfig', 'text!./metadata.json', 'plugin/PluginBase', 'gridlabd/meta', 'q', './cola.min' ], function ( PluginConfig, pluginMetadata, PluginBase, MetaTypes, Q) { 'use strict'; pluginMetadata = JSON.parse(pluginMetadata); /** * Initializes a new instance of ImportGLM. * @class * @augments {PluginBase} * @classdesc This class represents the plugin ImportGLM. * @constructor */ var ImportGLM = function () { // Call base class' constructor. PluginBase.call(this); this.pluginMetadata = pluginMetadata; this.metaTypes = MetaTypes; }; /** * Metadata associated with the plugin. Contains id, name, version, description, icon, configStructue etc. * This is also available at the instance at this.pluginMetadata. * @type {object} */ ImportGLM.metadata = pluginMetadata; // Prototypal inheritance from PluginBase. ImportGLM.prototype = Object.create(PluginBase.prototype); ImportGLM.prototype.constructor = ImportGLM; ImportGLM.prototype.notify = function(level, msg) { var self = this; self.logData += msg + '\n'; if (!self.logDebug && level == 'debug') return; var prefix = self.projectId + '::' + self.projectName + '::' + level + '::'; if (level=='error') self.logger.error(msg); else if (level=='debug') self.logger.debug(msg); else if (level=='info') self.logger.info(msg); else if (level=='warning') self.logger.warn(msg); self.createMessage(self.activeNode, msg, level); self.sendNotification(prefix+msg); }; /** * Main function for the plugin to execute. This will perform the execution. * Notes: * - Always log with the provided logger.[error,warning,info,debug]. * - Do NOT put any user interaction logic UI, etc. inside this method. * - callback always has to be called even if error happened. * * @param {function(string, plugin.PluginResult)} callback - the result callback */ ImportGLM.prototype.main = function (callback) { // Use self to access core, project, result, logger etc from PluginBase. // These are all instantiated at this point. var self = this, nodeObject; self.runningOnClient = false; if (typeof WebGMEGlobal !== 'undefined') { self.runningOnClient = true; } self.updateMETA(self.metaTypes); // Default fails self.result.success = false; self.logData = ''; // fill this out before creating the WebGME nodes self.newModel = { base: 'Model', children: [], attributes: [] }; var currentConfig = self.getCurrentConfig(), glmFileHash = currentConfig.glmFile; self.logDebug = currentConfig.logDebug; self.iterations = currentConfig.iterations; self.linkDistance = currentConfig.linkDistance; self.nodeSize = currentConfig.nodeSize; // Using the coreAPI to make changes. nodeObject = self.activeNode; self.blobClient.getMetadata(glmFileHash) .then(function(glmMetaData) { var splitName = glmMetaData.name.split("."); var newName = ""; for (var i=0;i<splitName.length-1;i++) { newName += splitName[i]; } self.modelName = newName; self.newModel.name = newName; //self.logger.error('loaded model: ' + self.modelName); }) .then(function() { return self.blobClient.getObjectAsString(glmFileHash) }) .then(function(glmFile) { return self.parseObjectsFromGLM(glmFile); }) .then(function() { return self.layoutObjects(); }) .then(function() { return self.createModelArtifacts(); }) .then(function() { // This will save the changes. If you don't want to save; // exclude self.save and call callback directly from this scope. return self.save('ImportGLM updated model.'); }) .then(function() { if (self.logDebug) { return self.blobClient.putFile(self.modelName+'.log', self.logData) .then(function(hash) { self.result.addArtifact(hash); }); } }) .then(function() { self.result.setSuccess(true); callback(null, self.result); }) .catch(function(err) { if (self.logDebug) { return self.blobClient.putFile(self.modelName+'.log', self.logData) .then(function(hash) { self.result.addArtifact(hash); self.notify('error', err); self.result.setSuccess(false); callback(err, self.result); }); } else { self.notify('error', err); self.result.setSuccess(false); callback(err, self.result); } }); }; var delimeter = '/'; ImportGLM.prototype.objectNamePrefix = function(obj) { return obj.base + delimeter + obj.type + delimeter; }; ImportGLM.prototype.objectToKey = function(obj) { return this.objectNamePrefix(obj) + obj.name; }; ImportGLM.prototype.nameToKey = function(name, dict) { var self = this; self.notify('debug', 'Looking up key for '+name); var keys = Object.keys(dict); var suffix = delimeter + name; var objKeys = keys.filter(function(val) {return val.substr(-suffix.length)===suffix;}); var objKey = ''; if (objKeys.length) objKey = objKeys[0]; self.notify('debug', 'Got key: '+objKey + ' for ' + name); return objKey; }; ImportGLM.prototype.nameToObject = function(name, dict) { var self = this; var objKey = self.nameToKey(name, dict); var p = dict[objKey]; if (!p) { // didn't find the name // map from objName (e.g. node:412) to actual name (e.g. node_412) name = name.replace(/:/g,'/'); objKey = self.nameToKey(name, dict); p = dict[objKey]; } return p; }; ImportGLM.prototype.parseObjectsFromGLM = function(glmFile) { // fill out self.newModel var self = this, objDict = {}, displayNameDict = {}, objID = {}, objByDepth = [], results; // remove the comments glmFile = self.removeComments(glmFile); // fix any possible syntax issues glmFile = self.fixSyntax(glmFile); // split the file into lines var lines = glmFile.split('\n'); var line_num = 0; lines.map((line) => { self.notify('debug', 'parsing line number: '+line_num+':'+line); line_num++; var macro_regex = /^#/gm, module_def_regex = /module\s+(\w+);/, container_regex = /(\w+)(?:\s+(\w+))?:?([\.\d]+)?\s*{/, container_end_regex = /(^|[^\S]+)};?/; if (macro_regex.test(line)) { // parse macros var obj = self.parseMacro(line, self.newModel); obj._line_def = line_num; self.newModel.children.push(obj); } else if (results=module_def_regex.exec(line)) { // simple module def var obj = self.getObjStub(line); obj._line_def = line_num; obj.base = 'module'; obj.name = results[1]; var key = self.objectToKey(obj); objDict[key] = obj; self.newModel.children.push(obj); } else if (container_regex.test(line)) { // start object / module / class / clock / schedule var obj = self.getObjStub(line); obj._line_def = line_num; var key = self.objectToKey(obj); if (objDict[key]) { if (!objID[obj.type]) { objID[obj.type] = 0; } obj.name += '_' + objID[obj.type]; obj.displayName = obj.name; objID[obj.type]++; } self.notify('debug','pushing '+key); objByDepth.push(obj); } else if (container_end_regex.test(line)) { // end object / module / class / clock / schedule var obj = objByDepth.pop(); // work out parent var key = self.objectToKey(obj); self.notify('debug', 'popped ' + key); if (obj.base == 'object' && objByDepth.length > 0) { obj.parent = objByDepth[objByDepth.length-1]; } // add to model self.newModel.children.push(obj); objDict[key] = obj; if (obj.displayName) { var key = self.objectNamePrefix(obj) + obj.displayName; displayNameDict[key] = obj; } } else if (line.length > 0 && objByDepth.length){ var obj = objByDepth[objByDepth.length - 1]; // parse based on the specific type if (obj.base == 'object') { obj = self.parseObjectLine(line, obj); } else if (obj.base == 'class') { obj = self.parseClassLine(line, obj); } else if (obj.base == 'module') { obj = self.parseModuleLine(line, obj); } else if (obj.base == 'clock') { obj = self.parseClockLine(line, obj); } else if (obj.base == 'schedule') { obj = self.parseScheduleLine(line, obj); } } }); // handle 'parent' attributes here var keys = Object.keys(objDict); keys.map((key) => { var obj = objDict[key]; var pAttr = obj.attributes.find((a) => { return a.name == 'parent'; }); if (pAttr) { self.notify('debug', 'Updating parent for ' + self.objectToKey(obj)); var parentName = pAttr.value; var p = self.nameToObject(parentName, objDict); if (!p) { p = self.nameToObject(parentName, displayNameDict); if (!p) { self.notify('error', JSON.stringify(Object.keys(displayNameDict))); throw new String("Couldn't get parent " + parentName); } } // set the parent obj.parent = p; // remove the parent attribute var index = obj.attributes.indexOf(pAttr); if (index >= 0) { obj.attributes.splice( index, 1 ); } } }); // convert attributes that should be pointers according to the META keys.map((key) => { var obj = objDict[key]; var metaNode = self.objToMeta(obj); if (!metaNode) return; var ptrNames = self.filterPointerNames(self.core.getPointerNames(metaNode)); if (!ptrNames.length) return; self.notify('debug', 'Checking pointer attributes for ' + self.objectToKey(obj)); for (var i = obj.attributes.length - 1; i >= 0; i--) { var attr = obj.attributes[i]; self.notify('debug', 'checking attribute ' + attr.name + ' to see if it is a pointer.'); if (ptrNames.indexOf(attr.name) > -1) { self.notify('debug', 'updating attribute ' + attr.name + ' to pointer'); var ptrObjName = attr.value; var p = self.nameToObject(ptrObjName, objDict) if (!p) { p = self.nameToObject(ptrObjName, displayNameDict); if (!p) { self.notify('error', JSON.stringify(Object.keys(displayNameDict))); throw new String("Couldn't get pointer " + ptrObjName); } } obj.pointers.push({ name: self.convertPointerName(attr.name), value: p }); self.notify('debug', 'pointer goes to: ' + p.name); obj.attributes.splice(i, 1); } } }); }; ImportGLM.prototype.convertPointerName = function(ptrName) { if (ptrName == 'from') ptrName = 'src'; else if (ptrName == 'to') ptrName = 'dst'; return ptrName; }; ImportGLM.prototype.filterPointerNames = function(ptrNames) { ptrNames.splice(ptrNames.indexOf('base'), 1); ptrNames[ptrNames.indexOf('src')] = 'from'; ptrNames[ptrNames.indexOf('dst')] = 'to'; return ptrNames; }; ImportGLM.prototype.objToMeta = function(obj) { var metaNode = null; if (obj.base == 'object') { if (obj.type) { metaNode = this.META[obj.type]; } } else if (obj.base) { metaNode = this.META[obj.base]; } return metaNode; }; var objNames = {}; ImportGLM.prototype.parseObjectName = function(name, obj) { name = name.replace(/[^:]*:/g,''); if (obj.base == 'object') { var index = name.indexOf('..'); if (index == 0) { // ..<count> if (objNames[obj.type] == undefined) { objNames[obj.type] = 0; } name = obj.type + objNames[obj.type]; objNames[obj.type]++; } else if (index > 0) { // <start id>..<end id> var splits = name.split('..'); var startId = +splits[0]; var endId = +splits[1]; if (objNames[obj.type] == undefined) { objNames[obj.type] = startId; } name = obj.type + objNames[obj.type]; objNames[obj.type]++; } } return name; } ImportGLM.prototype.getObjStub = function(line) { var container_regex = /(\w+)(?:\s+(\w+))?:?([\.\d]+)?\s*{/gm, obj = { name: null, displayName: null, type: null, base: null, parent: null, attributes: [], children: [], pointers: [] }, results = container_regex.exec(line); if (results) { obj.base = results[1]; if (obj.base == 'clock') { } else if (obj.base == 'schedule' || obj.base == 'module' || obj.base == 'class') { obj.name = results[2]; } else if (obj.base == 'object') { obj.type = results[2]; obj.attributes.push({ name: 'meta_type', value: obj.type }); if (results[3]) { obj.name = this.parseObjectName(results[3], obj); obj.displayName = obj.name; } } obj.name = obj.name || obj.type || obj.base; } return obj; }; ImportGLM.prototype.removeComments = function(str) { var regex = /(?:[\s]+|^|;)\/\/.*$/gm; return str.replace(regex, '').replace(/\r/gm,''); }; ImportGLM.prototype.fixSyntax = function(str) { var regex = /};\s*object/gm; str = str.replace(regex, '};\nobject'); regex = /}\s*object/gm; str = str.replace(regex, '}\nobject'); return str; }; ImportGLM.prototype.parseMacro = function(line) { // parses anything in GLM that starts with '#' var self = this, obj; if (line.indexOf('#setenv') > -1) obj = self.parseVariable(line); else if (line.indexOf('#set') > -1 || line.indexOf('#define') > -1) obj = self.parseGlobal(line); else if (line.indexOf('#include') > -1) obj = self.parseInclude(line); return obj; }; ImportGLM.prototype.parseInclude = function(line) { // includes set by: #include "<filename>" var self = this, name = null, value = null, type = null, obj, regex = /#include\s+"(\S+)"/gi, results = regex.exec(line); if (results) { name = results[1]; } obj = { name: name, base: 'Include', attributes: [] }; return obj; }; ImportGLM.prototype.parseGlobal = function(line) { // globals set by: #set <global>="<value>" // or: #define <global>="<value>" var self = this, name = null, value = null, type = null, obj, regex = /#(define|set)\s+(\S+)\s*=\s*([^;]+);?/gi, results = regex.exec(line); if (results) { type = results[1]; name = results[2]; value = results[3]; } obj = { name: name, base: 'Global', attributes: [ { name: 'Value', value: value }, { name: 'Type', value: type } ] }; return obj; }; ImportGLM.prototype.parseVariable = function(line) { // environment variables set by: #setenv <variable>=<expression> var self = this, name = null, value = null, obj, regex = /#setenv\s+(\S+)\s*=\s*([\w '"]+);?/gim, results = regex.exec(line); if (results) { name = results[1]; value = results[2]; } obj = { name: name, base: 'Variable', attributes: [ { name: 'Expression', value: value } ] }; self.logger.error(obj); return obj; }; ImportGLM.prototype.parseClockLine = function(line, obj) { var self = this, ts_regex = /timestamp\s+'([^\/\n\r\v;]*)';?/gi, st_regex = /stoptime\s+'([^\/\n\r\v;]*)';?/gi, tz_regex = /timezone\s+([^\/\n\r\v;]*);?/gi, results; if (results = ts_regex.exec(line)) { obj.attributes.push({ name: 'timestamp', value: results[1] }); } else if (results = st_regex.exec(line)) { obj.attributes.push({ name: 'stoptime', value: results[1] }); } else if (results = tz_regex.exec(line)) { obj.attributes.push({ name: 'timezone', value: results[1] }); } return obj; }; ImportGLM.prototype.parseScheduleLine = function(line, obj) { var self = this, id = obj.children.length, pattern = /([\s]+[\d\*\.]+[\-,\.\d]*)+/gi, matches = pattern.exec(line); if (matches) { var splits = matches[0].split(new RegExp(" |\t|\s|;",'g')).filter(function(obj) {return obj.length > 0;}); if ( splits.length >= 5 ) { var entry = { base: 'Entry', name: 'Entry_' + id, attributes: [ { name: "Minutes", value: splits[0] }, { name: "Hours", value: splits[1] }, { name: "Days", value: splits[2] }, { name: "Months", value: splits[3] }, { name: "Weekdays", value: splits[4] } ] }; if (splits.length > 5) entry.attributes.push({ name: "Value", value: splits[5] }); obj.children.push(entry) } else { throw new String('Schedule ' + obj.name + ' has improperly formmated entry: ' + line); } } return obj; }; ImportGLM.prototype.parseModuleLine = function(line, obj) { // <variable> <expression>; var self = this, attr_regex = /(\w+)\s+([^;]*);/g, results; if (results = attr_regex.exec(line)) { var variable = { base: "Variable", name: results[1], attributes: [ { name: "Expression", value: results[2] } ] }; obj.children.push(variable); } return obj; }; ImportGLM.prototype.parseClassLine = function(line, obj) { // <type> <property>[<unit>]; var self = this, attr_regex = /(\w+)\s+(\w+)(?:\[(\w+)\])?;/g, results; if (results = attr_regex.exec(line)) { var propDef = { base: "PropertyDef", name: results[2], attributes: [ { name: "Type", value: results[1] } ] }; if (results[3]) { propDef.attributes.push({ name: "Unit", value: results[3] }); } obj.children.push(propDef); } return obj; }; ImportGLM.prototype.parseObjectLine = function(line, obj) { var self = this, attr_regex = /(\w+)\s+([^;]*);?/g, results; if (results = attr_regex.exec(line)) { /* if (results[1] == 'name' && obj.name) return obj; */ var attr = { name: results[1], value: results[2].replace(/'/g,'').replace(/"/g,'') }; obj.attributes.push(attr); if (attr.name == 'name') { self.notify('debug', 'Updating name from '+obj.name+' to '+attr.value); obj.name = attr.value; } } return obj; }; ImportGLM.prototype.isLink = function(obj) { var src = null, dst = null; for (var p in obj.pointers) { var ptr = obj.pointers[p]; if (ptr.name === 'src') src = ptr; else if (ptr.name === 'dst') dst = ptr; else if (src && dst) break; } return (src && dst); }; ImportGLM.prototype.isNode = function(obj) { var self = this; return !self.isLink(obj); }; ImportGLM.prototype.layoutObjects = function() { var self = this; // go through all of newModel's children and lay them out // (only the ones which do not have both a 'src' and a 'dst' // pointer //var objects = self.newModel.children.filter(function(c) { return self.isNode(c); }); //var links = self.newModel.children.filter(function(c) { return self.isLink(c); }); var nodes = []; var links = []; self.notify('info','Laying out objects, note this may take a while.'); var getIndexOfObjWithAttr = function(array, attr, value) { for(var i = 0; i < array.length; i++) { if(array[i][attr] === value) { return i; } } return -1; } var minx = 0, miny=0; if (self.newModel.children) { for (var i=0; i<self.newModel.children.length; i++) { var child = self.newModel.children[i]; if (self.isNode(child)) { nodes.push({ "name":self.objectToKey(child), "displayName": self.objectNamePrefix(child) + child.name, "original": i }); } } self.newModel.children.map(function(child) { if (self.isLink(child)) { var srcPtr = child.pointers.find(function(p) { return p.name == 'src'; }); var dstPtr = child.pointers.find(function(p) { return p.name == 'dst'; }); //self.notify('warning', child.name + ' has ' + srcPtr + ' and ' + dstPtr); var src = srcPtr.value; var dst = dstPtr.value; // can only handle layout of nodes and links, not links to links if ( self.isNode(src) && self.isNode(dst) ) { var srcKey = self.objectToKey(src); var dstKey = self.objectToKey(dst); var srcIndex = getIndexOfObjWithAttr(nodes, 'name', srcKey); var dstIndex = getIndexOfObjWithAttr(nodes, 'name', dstKey); if (dstIndex == -1 || srcIndex == -1) { if (srcIndex == -1) { srcKey = self.objectNamePrefix(src) + src.displayName; srcIndex = getIndexOfObjWithAttr(nodes, 'displayName', srcKey); } if (dstIndex == -1) { dstKey = self.objectNamePrefix(dst) + dst.displayName; dstIndex = getIndexOfObjWithAttr(nodes, 'displayName', dstKey); } if (dstIndex == -1 || srcIndex == -1) { self.notify('error', 'Couldnt get src/dst for '+child.name); self.notify('error', 'Looking up '+srcKey + ' and '+dstKey + ' '+srcIndex + ' ' + dstIndex); } } else { links.push({ "source": srcIndex, "target": dstIndex, }); } } else { self.notify('warning', 'Can only layout links between nodes, not links between links. ' +src.name+ ' may not be laid out well.'); } } else if (child.parent) { // now add visualized pointers! self.notify('debug', 'Converting parent to link for ' + child.name); var src = child; var dst = child.parent; // can only handle layout of nodes and links, not links to links if ( self.isNode(src) && self.isNode(dst) ) { var srcKey = self.objectToKey(src); var dstKey = self.objectToKey(dst); var srcIndex = getIndexOfObjWithAttr(nodes, 'name', srcKey); var dstIndex = getIndexOfObjWithAttr(nodes, 'name', dstKey); if (dstIndex == -1 || srcIndex == -1) { if (srcIndex == -1) { srcKey = self.objectNamePrefix(src) + src.displayName; srcIndex = getIndexOfObjWithAttr(nodes, 'displayName', srcKey); } if (dstIndex == -1) { dstKey = self.objectNamePrefix(dst) + dst.displayName; dstIndex = getIndexOfObjWithAttr(nodes, 'displayName', dstKey); } if (dstIndex == -1 || srcIndex == -1) { self.notify('error', 'Couldnt layout parent for '+child.name); self.notify('error', 'Looking up '+srcKey + ' and '+dstKey + ', indices: '+srcIndex + ' ' + dstIndex); } } else { links.push({ "source": srcIndex, "target": dstIndex, }); } } else { self.notify('warning', 'Can only layout links between nodes, not links between links. ' +src.name+ ' may not be laid out well.'); } } }); } var width = nodes.length * 5, height = nodes.length * 5; var d3cola = cola.d3adaptor() .linkDistance(self.linkDistance) .avoidOverlaps(true) .defaultNodeSize(self.nodeSize) .size([width, height]); d3cola .nodes(nodes) .links(links) .symmetricDiffLinkLengths(10) .start(self.iterations, self.iterations, self.iterations); nodes.map(function(node) { if (node.x < minx) minx = node.x; if (node.y < miny) miny = node.y; }); self.notify('debug', 'minx, miny: ' + minx + ', '+miny); nodes.map(function(node) { var realNode = self.newModel.children[node.original]; var newx = (node.x - minx) * 2 + 50, // minx,miny will always be negative newy = (node.y - miny) * 2 + 50; var newPos = {x: newx, y: newy}; self.notify('debug', 'Updating position of ' + node.name + ', ' + realNode.name + ' to ' + JSON.stringify(newPos)); realNode._position = newPos; }); self.notify('debug', 'layed out nodes: '+nodes.length); self.notify('debug', 'layed out links: '+links.length); }; // When saving the objects, need to check against META to figure out what the relevant pointers and attributes are: // self.core.getValidAttributeNames(self.META[<type>]) // self.core.getValidPointerNames(self.META[<type>]) ImportGLM.prototype.saveObject = function(obj, parentNode) { var self = this; var base = obj.type; if (!self.META[base]) base = obj.base; if (!self.META[base]) base = "Object"; parentNode = parentNode || self.newModel.node; var parentName = self.core.getAttribute(parentNode, 'name'); if ( base == null ) { self.notify('warning', 'Encountered null base object! Child of ' + parentName + ', name: ' + obj.name + ', defined on line: ' + obj._line_def); return; } self.notify('debug', "Creating object " + obj.name + " of type " + base + " from " + self.META[base]); var newNode = self.core.createNode({parent: parentNode, base: self.META[base]}); if (obj._position) { self.notify('debug', 'Setting position of new object to '+JSON.stringify(obj._position)); self.core.setRegistry(newNode, 'position', obj._position); } obj.node = newNode; if (obj.attributes) { obj.attributes.map((attr) => { // set any attributes here self.core.setAttribute(newNode, attr.name, attr.value); }); } var name = obj.name || obj.type || obj.base; self.core.setAttribute(newNode, 'name', name); if (obj.children) { obj.children.map((child) => { // create any children here if (!child.node) self.saveObject(child, newNode); }); } if (obj.pointers) { obj.pointers.map((ptr) => { // create any pointers here if (!ptr.value.node) self.saveObject(ptr.value); self.core.setPointer(newNode, ptr.name, ptr.value.node); }); } if (obj.parent) { // create parent object with src/dst here if (!obj.parent.node) { self.saveObject(obj.parent); } var parent = self.core.createNode({parent: self.newModel.node, base: self.META.Parent}); self.core.setPointer(parent, 'src', obj.node); self.core.setPointer(parent, 'dst', obj.parent.node); } }; ImportGLM.prototype.createModelArtifacts = function() { // use self.newModel var self = this; self.saveObject(self.newModel, self.activeNode); }; return ImportGLM; });