src/converter/MorpheusImport.js
import ModelDescription from "./ModelDescription.js"
class MorpheusImport extends ModelDescription {
constructor( config ){
super( )
this.xml = config.xml
this.from = "a Morpheus model"
this.build()
}
/* ========== GENERAL HELPER FUNCTIONS =========== */
readXMLTag( tag, xml, index = 0 ){
if( typeof xml === "undefined" ){
xml = this.xml
}
const tag2 = this.getXMLTag( tag, xml, index )
if( typeof tag2 !== "undefined" ){
return tag2.innerHTML
}
return undefined
}
getXMLTag( tag, xml, index = 0 ){
if( typeof xml === "undefined" ){
xml = this.xml
}
const tags = xml.getElementsByTagName( tag )
if( tags.length === 0 ){
return undefined
}
return tags[index]
}
readVectorAttribute( xml, attrName ){
const attr = xml.getAttribute( attrName )
return attr.split( "," ).map( function(x){
return( parseInt(x) )
})
}
readCoordinateAttribute( xml, attrName ){
const vec = this.readVectorAttribute( xml, attrName )
let outVec = []
for( let d = 0; d < this.grid.ndim; d++ ){
outVec.push( vec[d] )
}
return outVec
}
toCoordinate( string ){
const vec = string.split( "," ).map( function(x){
return( parseInt(x) )
})
let outVec = []
for( let d = 0; d < this.grid.ndim; d++ ){
outVec.push( vec[d] )
}
return outVec
}
/* ========== MORPHEUS READER FUNCTIONS =========== */
setModelInfo(){
// Get title from XML
const title = this.readXMLTag( "Title" )
if( typeof title === "undefined" ) {
this.conversionWarnings.modelInfo.push(
"Could not find model title."
)
} else {
this.modelInfo.title = title
}
// Get description from XML
const desc = this.readXMLTag( "Details" )
if( typeof desc === "undefined" ) {
this.conversionWarnings.modelInfo.push(
"Could not find a model description."
)
} else {
this.modelInfo.desc = desc.toString()
}
}
setTimeInfo(){
const time = this.getXMLTag( "Time" )
for( let time_ch of time.children ) {
switch (time_ch.nodeName) {
case "StartTime" :
this.timeInfo.start = Number( time_ch.getAttribute("value") )
break
case "StopTime" :
this.timeInfo.stop = Number( time_ch.getAttribute("value") )
break
case "RandomSeed" :
this.kinetics.seed = parseInt( time_ch.getAttribute("value") )
break
case "TimeSymbol" :
break
default :
this.conversionWarnings.time.push(
"I don't know what to do with tag <" + time_ch.nodeName + "> in <Time>. Ignoring it."
)
}
}
this.timeInfo.duration = this.timeInfo.stop - this.timeInfo.start
if( this.timeInfo.duration < 0 ){
throw( "Error: I cannot go back in time; timeInfo.stop must be larger than timeInfo.start!")
}
}
setGridInfo(){
const space = this.getXMLTag( "Space" )
for( let space_ch of space.children ){
switch( space_ch.nodeName ) {
case "Lattice" :
this.readMorpheusLattice( space_ch )
break
case "SpaceSymbol" :
break
default :
this.conversionWarnings.grid.push(
"Tags of type <" + space_ch.nodeName + "> are currently not supported. " +
"Ignoring it for now."
)
}
}
}
readMorpheusLattice( lattice ){
// this.grid.geometry and this.grid.ndim
this.setGridGeometry( lattice.className )
// declare some variables needed
const boundLookup = { x : 0, y : 1, z : 2 }
let order, bound, dimIndex, boundType
// Read info in the lattice
for( let lattice_ch of lattice.children ){
switch( lattice_ch.nodeName ) {
case "Size" :
this.grid.extents = this.readCoordinateAttribute( lattice_ch, "value" )
break
case "Neighborhood" :
for( let nn of lattice_ch.children ){
switch( nn.nodeName ){
case "Order" :
order = parseInt( this.readXMLTag( "Order", lattice_ch ) )
if( isNaN(order) ){
this.conversionWarnings.grid.push(
"Non-integer neighborhood order. Ignoring."
)
} else {
this.grid.neighborhood.order = order
}
break
case "Distance" :
this.grid.neighborhood.distance = this.readXMLTag( "Distance", lattice_ch )
break
default:
this.conversionWarnings.grid.push(
"I don't know what to do with tag <" + nn.nodeName +
"> in a <Neighborhood> of a <Space> <Lattice>. Ignoring."
)
}
}
break
case "BoundaryConditions" :
for( let nn of lattice_ch.children ) {
switch (nn.nodeName) {
case "Condition" :
bound = nn.getAttribute("boundary")
dimIndex = boundLookup[bound]
boundType = nn.getAttribute("type")
this.grid.boundaries[dimIndex] = boundType
break
default :
this.conversionWarnings.grid.push(
"I don't know what to do with tag <" + nn.nodeName +
"> in the <BoundaryConditions> of a <Space> <Lattice>. Ignoring."
)
}
}
break
case "Domain" :
this.conversionWarnings.grid.push(
"Your MorpheusModel has specified a <" + lattice_ch.nodeName + "> : \n\t\t\t" +
lattice_ch.outerHTML + "\n" +
"This is currently not supported, but you can mimic the behaviour " +
"by adding a physical barrier using a BarrierConstraint; " +
"e.g. see artistoo.net/examples/Microchannel.html."
)
break
case "NodeLength" :
this.conversionWarnings.grid.push(
"Your MorpheusModel has specified a <" + lattice_ch.nodeName + "> : \n\t\t\t" +
lattice_ch.outerHTML + "\n" +
"This is currently not supported, so spatial scales in your model " +
"are just measured in pixels. This shouldn't change behaviour as long as " +
"(spatial) parameters are defined in units of pixels. Please check this."
)
break
default :
this.conversionWarnings.grid.push(
"I don't know what to do with tag <" + lattice_ch.nodeName +
"> in a <Space> <Lattice>. Ignoring."
)
}
}
}
setCellKindNames(){
// this counter will increase every time a new CellType is handled.
let indexNonBackground = 1
for( let ck of this.getXMLTag( "CellTypes" ).children ){
if( ck.nodeName !== "CellType" ){
this.conversionWarnings.cells.push(
"Not expecting tag <" + ck.nodeName + "> inside CellTypes. Ignoring."
)
}
const kind_class = ck.className
const kind_name = ck.getAttribute( "name" )
// special case for kind of class 'medium', the background;
// this is always index 0 and there can be only one kind of this
// class.
if( kind_class === "medium" ){
if( this.cellKinds.name2index.hasOwnProperty( kind_class ) ){
throw( "There cannot be two CellTypes of class 'medium'! Aborting." )
} else {
// it gets index 0.
this.cellKinds.name2index[ kind_class ] = 0
this.cellKinds.index2name[ "0" ] = kind_class
}
} else if ( kind_class === "biological" ){
if( this.cellKinds.name2index.hasOwnProperty( kind_name ) ){
throw( "There cannot be two CellTypes with name '" + kind_name +
"' Aborting." )
} else {
this.cellKinds.name2index[ kind_name ] = indexNonBackground
this.cellKinds.index2name[ indexNonBackground.toString() ] = kind_name
}
indexNonBackground++
} else {
throw( "Don't know what to do with a CellType of class " + kind_class + ". Aborting." )
}
// Also extract any <Property> or <PropertyVector>
if( !this.cellKinds.properties.hasOwnProperty( kind_name ) ){
this.cellKinds.properties[ kind_name ] = {}
}
const props = ck.getElementsByTagName( "Property" )
for( let p of props ){
const propName = p.getAttribute( "symbol" )
const propVal = p.getAttribute( "value" )
this.cellKinds.properties[ kind_name ][ propName ] = propVal
}
const propVecs = ck.getElementsByTagName( "PropertyVector" )
for( let pv of propVecs ){
const propName = pv.getAttribute( "symbol" )
const propVal = this.readCoordinateAttribute( pv, "value" )
this.cellKinds.properties[ kind_name ][ propName ] = propVal
}
}
// counter: number of cell kinds including medium/background.
this.cellKinds.count = indexNonBackground
}
setCPMGeneral(){
// Random Seed
// Note that the random 'seed' in Morpheus is specified in <Time>,
// and as such is handled by this.setTimeInfo().
// Temperature
const cpm = this.getXMLTag( "CPM" )
for( let cpm_ch of cpm.children ) {
switch( cpm_ch.nodeName ){
case "Interaction" :
this.setAdhesionMorpheus( cpm_ch )
break
case "ShapeSurface" :
if( cpm_ch.getAttribute("scaling") !== "none" ){
this.conversionWarnings.grid.push(
"You are trying to use a ShapeSurface with scaling '" +
cpm_ch.getAttribute("scaling") +
"'. This is currently not supported in Artistoo; " +
"Reverting to the default scaling = 'none' instead. You may have " +
"to adapt parameters that involve cell-cell interfaces, such as " +
"those of the CPM's Adhesion and Perimeter constraints."
)
}
if( typeof this.readXMLTag( "Distance", cpm_ch ) !== "undefined" ){
this.conversionWarnings.grid.push(
"You are trying to use a ShapeSurface with neighborhood distance '" +
this.readXMLTag( "Distance", cpm_ch ) +
"'. This is currently not supported in Artistoo; " +
"Reverting to the default Moore neighborhood instead. You may have " +
"to adapt parameters that involve cell-cell interfaces, such as " +
"those of the CPM's Adhesion and Perimeter constraints."
)
}
if( this.readXMLTag( "Order", cpm_ch ) !== "2" ){
this.conversionWarnings.grid.push(
"You are trying to use a ShapeSurface with neighborhood order '" +
this.readXMLTag( "Order", cpm_ch ) +
"'. This is currently not supported in Artistoo; " +
"Reverting to the default Moore neighborhood instead. You may have " +
"to adapt parameters that involve cell-cell interfaces, such as " +
"those of the CPM's Adhesion and Perimeter constraints."
)
}
break
case "MonteCarloSampler" : {
const stepper = cpm_ch.getAttribute("stepper")
if (stepper !== "edgelist") {
this.conversionWarnings.kinetics.push(
"Stepper '" + stepper + "' not supported. Switching to 'edgelist'."
)
}
for (let nn of cpm_ch.children) {
switch (nn.nodeName) {
case "MetropolisKinetics": {
const kineticAttr = nn.getAttributeNames()
for (let k of kineticAttr) {
switch (k) {
case "temperature":
this.kinetics.T = parseFloat(nn.getAttribute("temperature"))
break
default :
this.conversionWarnings.kinetics.push(
"Unknown attribute of < MetropolisKinetics >: " +
k + ". Ignoring."
)
}
}
break
}
case "Neighborhood":
for (let nnn of nn.children) {
switch (nnn.nodeName) {
case "Order": {
const order = nnn.innerHTML
if ( order !== "2" ) {
this.conversionWarnings.grid.push(
"Ignoring < Neighborhood > <Order> in <MetropolisKinetics>: "
+ order + ". " +
"Using default order 2 (Moore-neighborhood) instead.")
}
break
}
default :
this.conversionWarnings.grid.push(
"I don't understand what you mean with a neighborhood " +
nnn.nodeName + ". Using the default (Moore) neighborhood instead."
)
}
}
break
case "MCSDuration" :
this.conversionWarnings.kinetics.push(
"Ignoring unsupported tag <MCSDuration>; this should not change " +
"behaviour of the model, but it means all time should be defined in units of MCS. " +
"Please check if this is the case."
)
break
default :
this.conversionWarnings.kinetics.push(
"Unknown child of < MonteCarloSampler >: <" +
nn.nodeName + ">.")
}
}
break
}
default :
this.conversionWarnings.kinetics.push(
"I don't know what to do with tag <" + cpm_ch.nodeName + ">. Ignoring it for now."
)
}
}
}
setConstraints(){
// Adhesion in Morpheus is stored under 'CPM' and is set by
// setCPMGeneral().
const ct = this.getXMLTag( "CellTypes" )
for (let cc of ct.children ){
const kindIndex = this.getKindIndex( cc.getAttribute("name") )
// add constraints:
for( let constraint of cc.children ){
switch( constraint.nodeName ){
case "VolumeConstraint" :
this.setVolumeConstraintForKind( constraint, kindIndex )
break
case "SurfaceConstraint" :
this.setPerimeterConstraintForKind( constraint, kindIndex )
break
case "Protrusion" :
this.setActivityConstraintForKind( constraint, kindIndex )
break
case "ConnectivityConstraint" :
this.setConnectivityConstraintForKind( constraint, kindIndex )
break
case "FreezeMotion":
this.setBarrierConstraintForKind( constraint, kindIndex )
break
case "PersistentMotion" :
this.setPersistenceConstraintForKind( constraint, kindIndex )
break
case "DirectedMotion" :
this.setPreferredDirectionConstraintForKind( constraint, kindIndex )
break
case "Chemotaxis" :
this.setChemotaxisConstraintForKind( constraint, kindIndex )
break
case "StarConvex":
this.unknownConstraintWarning( constraint.nodeName )
break
case "Haptotaxis" :
this.unknownConstraintWarning( constraint.nodeName )
break
case "LengthConstraint" :
this.unknownConstraintWarning( constraint.nodeName )
break
case "Property" :
// Properties are handled by this.setCellKindNames
break
case "PropertyVector" :
// PropertyVectors are handled by this.setCellKindNames
break
default:
this.conversionWarnings.cells.push(
"I don't know what to do with <CellType> property <" +
constraint.nodeName + "> for cell " +
cc.getAttribute("name") + ". Ignoring it. " )
}
}
}
}
unknownConstraintWarning( constraintName ){
this.conversionWarnings.constraints.push(
"Constraint '" + constraintName +
"' is currently not supported in Artistoo. +" +
"I am ignoring it for now, but model behaviour may change. " +
"Check out the online manual at https://artistoo.net/manual/custommodules.html " +
"to implement your own, or choose a similar constraint from available options " +
"https://artistoo.net/identifiers.html#hamiltonian"
)
}
setGridConfiguration(){
const pops = this.getXMLTag( "CellPopulations" )
for( let p of pops.children ){
// Check if this is a Population.
if( p.nodeName !== "Population" ){
this.conversionWarnings.init.push(
"Unexpected tag <" + p.nodeName + "> inside <CellPopulations>. Ignoring."
)
}
// Check which cellKind this population is of
const kindName = p.getAttribute( "type" )
// Handle the initialization depending on the type of child tags
for( let p_ch of p.children ){
switch( p_ch.nodeName ){
case "Cell":
this.setCellPixelList( p_ch, kindName )
break
case "InitCircle" :
this.setInitCircle( p_ch, kindName )
break
case "InitCellObjects":
this.setInitObjects( p_ch, kindName )
break
/*
case "InitDistribute":
this.setInitDistribute( p_ch, kindName )
break
case "InitHexLattice":
this.setHexLattice( p_ch, kindName )
break
case "InitRectangle" :
this.setInitRectangle (p_ch, kindName )
break*/
default: //InitProperty, InitVectorProperty, InitVoronoi, TIFFReader, InitPoissonDisc
this.conversionWarnings.init.push(
"Ignoring unsupported tag <" + p_ch.nodeName + "> inside " +
"a <Population>."
)
}
}
}
}
/* ========== MORPHEUS POPULATION READERS =========== */
setInitObjects( initXML, kindName ){
const mode = initXML.getAttribute( "mode" )
this.conversionWarnings.init.push( "In InitCellObjects: attribute 'mode' currently not supported. " +
"Ignoring mode = '" + mode + "' setting. " +
"If conflicts arise during cell seeding, the pixel gets the value of the last cell" +
" trying to occupy it. Adjust the script manually if this is not what you want.")
const arr = initXML.getElementsByTagName( "Arrangement" )[0]
const disp = this.readCoordinateAttribute( arr, "displacements" )
const reps = this.readCoordinateAttribute( arr, "repetitions" )
const obj = arr.children[0]
for( let xi = 0; xi < reps[0]; xi++ ){
const dx = xi*disp[0]
for( let yi = 0; yi < reps[1]; yi++ ){
const dy = yi*disp[1]
if( this.grid.ndim === 3 ){
for( let zi = 0; zi < reps[2]; zi++ ){
const dz = zi*disp[2]
this.addInitObject( [dx,dy,dz], obj, kindName )
}
} else {
this.addInitObject( [dx,dy], obj, kindName )
}
}
}
}
addInitObject( disp, objXML, kindName ) {
const objType = objXML.nodeName
const ndim = disp.length
let pos
switch (objType) {
case "Sphere" : {
// For a sphere, position is the center attribute; add the
// displacement to that.
pos = this.readCoordinateAttribute(objXML, "center")
for (let d = 0; d < ndim; d++) {
pos[d] += disp[d]
}
this.setup.init.push( {
setter : "circleObject",
kind : this.getKindIndex( kindName ),
kindName : kindName,
radius : parseFloat(objXML.getAttribute("radius")),
center : pos
} )
break
}
case "Box" : {
// For a box, position is the origin attribute; add the
// displacement to that.
pos = this.readCoordinateAttribute(objXML, "origin")
for (let d = 0; d < ndim; d++) {
pos[d] += disp[d]
}
// Read the box dimensions from the XML, this is the 'size' attribute.
let boxSize = this.readCoordinateAttribute(objXML, "size")
this.setup.init.push( {
setter : "boxObject",
kind : this.getKindIndex( kindName ),
kindName : kindName,
bottomLeft: pos,
boxSize: boxSize
} )
break
}
default :
this.conversionWarnings.init.push(
"No method to seed an object of type " + objType + ". Ignoring."
)
}
}
setInitCircle( initXML, kindName ){
let nCells
// Get information from attributes
for( let attr of initXML.getAttributeNames() ){
switch( attr ){
case "mode" :
if( initXML.getAttribute( "mode" ) !== "random" ){
this.conversionWarnings.init.push(
"In InitCircle: 'mode' " + initXML.getAttribute( "mode" ) +
" currently not supported. " +
"Seeding cells randomly in the circle; to change this, define your own" +
"initialization function as e.g. in " +
"https://artistoo.net/examples/DirectedMotionLinear.html." )
}
break
case "number-of-cells" :
// get number of cells to seed
nCells = initXML.getAttribute( "number-of-cells" )
// in morpheus, this can also be a function -- but this is not supported. Check and warn.
if( !isFinite( nCells ) ){
this.conversionWarnings.init.push(
"In InitCircle: it appears as if the number-of-cells attribute is " +
"not a number but a function. Ignoring it for now. " +
"Please change to a number or initialize the grid manually.")
}
break
default :
this.conversionWarnings.init.push(
"Attribute " + attr + " in <InitCircle> currently not " +
"supported; ignoring it for now." )
}
}
// Get the dimensions
for( let ch of initXML.children ){
switch( ch.nodeName ){
case "Dimensions" :
this.addInitCircle( ch, kindName, nCells )
break
default :
this.conversionWarnings.init.push(
"In <InitCircle> I don't know what to do with " +
"tag <" + ch.nodeName + ">. Ignoring it." )
}
}
}
addInitCircle( initXML, kindName, nCells ){
let center, radius
for( let attr of initXML.getAttributeNames() ){
switch( attr ){
case "center" : {
center = this.readCoordinateAttribute( initXML, "center" )
// check if it's an array of numbers and correct dimensions so that
// position has 2 coordinates for 2D grids.
for (let d = 0; d < this.grid.ndim; d++) {
if (!isFinite(center[d])) {
this.conversionWarnings.init.push(
"In <InitCircle> <Dimensions>, 'center' does not appear to be a numeric vector:" +
"center = " + center + ". Ignoring it for now."
)
return
}
}
break
}
case "radius" :
radius = parseInt( initXML.getAttribute( "radius" ) )
if( !isFinite(radius) ){
this.conversionWarnings.init.push(
"In <InitCircle> <Dimensions>, 'radius' does not appear to be a number:" +
"radius = " + radius + ". Ignoring it for now." )
return
}
break
default :
this.conversionWarnings.init.push(
"In <InitCircle> <Dimensions>, I don't know what to do with attribute " +
attr + ". Ignoring." )
}
}
if( typeof center === "undefined" || typeof radius === "undefined" ){
this.conversionWarnings.init.push(
"In <InitCircle> <Dimensions>, I cannot find a 'center' or a " +
"'radius'. Ignoring." )
return
}
// If we get here, we have both center and radius now.
this.setup.init.push( {
setter : "cellCircle",
kind : this.getKindIndex( kindName ),
kindName : kindName,
radius : radius,
center : center,
nCells : nCells
} )
}
setCellPixelList( cellXML, kindName ){
const kindIndex = this.getKindIndex( kindName )
const cid = cellXML.getAttribute( "id" )
let pixels = []
for( let n of cellXML.children ){
if( n.nodeName !== "Nodes" ){
this.conversionWarnings.init.push(
"Ignoring unexpected tag <" + n.nodeName + "> inside a " +
"<Population><Cell>."
)
} else {
pixels.push( this.toCoordinate( n.innerHTML ) )
}
}
this.setup.init.push( {
setter : "pixelSet",
kind : kindIndex,
kindName : kindName,
cid : cid,
pixels : pixels
} )
}
/* ========== MORPHEUS CONSTRAINT READERS =========== */
parameterFromXML( constraintXML, paramName, kindIndex, pType = "int" ){
let par = constraintXML.getAttribute( paramName )
const kindName = this.getKindName( kindIndex )
let parser
if( pType === "int" ){
parser = parseInt
} else if (pType === "float" ){
parser = parseFloat
} else {
throw ( "Unknown type of parameter : " + pType )
}
if( !isNaN( parser( par ) ) ){
par = parser( par )
} else {
// try if it is the name of one of the defined symbols.
if( this.cellKinds.properties[ kindName ].hasOwnProperty( par ) ){
par = parser( this.cellKinds.properties[ this.getKindName( kindIndex ) ][ par ] )
}
}
// Check if now okay, otherwise set to 0 and add a warning.
if( isNaN( parser( par ) ) ){
this.conversionWarnings.constraints.push(
"Failed to interpret parameter " + paramName + " in " +
constraintXML.nodeName + " for cellKind " + kindName +
". For now, this is set to 0; please correct manually."
)
return 0
}
return parser( par )
}
setAdhesionMorpheus( interactionXML ){
let defValue = NaN, negative = false
if( interactionXML.hasAttribute("default" ) ){
defValue = parseFloat( interactionXML.getAttribute( "default" ) )
}
if( interactionXML.hasAttribute("negative" ) ){
negative = ( interactionXML.getAttribute( "negative" ) === "true" )
}
let J = this.initCellKindMatrix( defValue )
for( let contact of interactionXML.children ){
// Check if child is indeed a contact energy
if( contact.nodeName !== "Contact" ){
this.conversionWarnings.constraints.push( "I don't know what to do with a tag <" +
contact.nodeName + "> inside the Adhesion <Interactions>. Ignoring. " )
break
}
// Get the types and convert to the corresponding number using the 'cellTypes' object used above
const type1 = this.getKindIndex( contact.getAttribute( "type1" ) )
const type2 = this.getKindIndex( contact.getAttribute( "type2" ) )
const energy = contact.getAttribute( "value" )
let energyNum = parseFloat( energy )
if( isNaN(energyNum) ){
this.conversionWarnings.constraints.push( "Contact energy value '" +
energy.toString() + "' inside Adhesion < Interactions > " +
"seems not to be a number and will be ignored.")
}
// set the value symmetrically
if( negative ){ energyNum = -energyNum }
J[type1][type2] = energyNum
J[type2][type1] = energyNum
}
// Add the adhesion constraint to the model description.
this.addConstraint( "Adhesion", { J : J } )
}
setVolumeConstraintForKind( constraintXML, kindIndex ){
if( !this.hasConstraint( "VolumeConstraint" ) ){
this.addConstraint( "VolumeConstraint", {
V : this.initCellKindVector( 0 ),
LAMBDA_V : this.initCellKindVector( 0 )
})
}
const vol = this.parameterFromXML( constraintXML,
"target", kindIndex, "int" )
const lambda = this.parameterFromXML( constraintXML,
"strength", kindIndex, "float" )
this.getConstraintParameter( "VolumeConstraint",
"V" )[kindIndex] = vol
this.getConstraintParameter( "VolumeConstraint",
"LAMBDA_V" )[kindIndex] = lambda
}
setPerimeterConstraintForKind( constraintXML, kindIndex ){
if( !this.hasConstraint( "PerimeterConstraint" ) ){
this.addConstraint( "PerimeterConstraint", {
P : this.initCellKindVector( 0 ),
LAMBDA_P : this.initCellKindVector( 0 ),
mode : "surface"
})
}
// Get info from XML object
let perimeter, lambda, mode
for( let att of constraintXML.getAttributeNames() ) {
switch (att) {
case "mode":
mode = constraintXML.getAttribute( "mode" )
break
case "target" :
perimeter = this.parameterFromXML( constraintXML,
"target", kindIndex, "float" )
break
case "strength":
lambda = this.parameterFromXML( constraintXML,
"strength", kindIndex, "float" )
break
default :
this.conversionWarnings.constraints.push(
"Ignoring unsupported attribute '" + att +
"' of the < SurfaceConstraint >")
}
}
// Set (converted) values.
this.getConstraintParameter( "PerimeterConstraint",
"P" )[kindIndex] = perimeter
this.getConstraintParameter( "PerimeterConstraint",
"LAMBDA_P" )[kindIndex] = lambda
this.getConstraint( "PerimeterConstraint" ).mode = mode
}
setActivityConstraintForKind( constraintXML, kindIndex ){
if( !this.hasConstraint( "ActivityConstraint" ) ){
this.addConstraint( "ActivityConstraint", {
MAX_ACT : this.initCellKindVector( 0 ),
LAMBDA_ACT : this.initCellKindVector( 0 ),
ACT_MEAN : "geometric"
})
}
const lambda = this.parameterFromXML( constraintXML,
"strength", kindIndex, "float" )
const max = this.parameterFromXML( constraintXML,
"maximum", kindIndex, "float" )
this.getConstraintParameter( "ActivityConstraint",
"MAX_ACT" )[kindIndex] = max
this.getConstraintParameter( "ActivityConstraint",
"LAMBDA_ACT" )[kindIndex] = lambda
}
setConnectivityConstraintForKind( constraintXML, kindIndex ){
if( !this.hasConstraint( "LocalConnectivityConstraint" ) ){
this.addConstraint( "LocalConnectivityConstraint", {
CONNECTED : this.initCellKindVector( false ),
})
}
this.getConstraintParameter( "LocalConnectivityConstraint",
"CONNECTED" )[kindIndex] = true
}
setBarrierConstraintForKind( constraintXML, kindIndex ){
if( !this.hasConstraint( "BarrierConstraint" ) ){
this.addConstraint( "BarrierConstraint", {
IS_BARRIER : this.initCellKindVector( false ),
})
}
this.getConstraintParameter( "BarrierConstraint",
"IS_BARRIER" )[kindIndex] = true
// The Morpheus <FreezeMotion> has an optional attribute/child 'Condition',
// which Artistoo doesn't have. It defaults to true, but for anything different
// the behaviour will be different; issue a warning.
for( let ch of constraintXML.children ){
if( ch.nodeName === "Condition" ){
if( this.readXMLTag( "Condition", constraintXML ) !== "1" ){
this.conversionWarnings.constraints.push(
"Converting a <FreezeMotion> constraint to an Artistoo 'BarrierConstraint', but " +
"don't know how to handle a <Condition> other than '1'. Ignoring Condition.")
}
} else {
this.conversionWarnings.constraints.push(
"I don't know what to do with <" + ch.nodeName +
"> in a <FreezeMotion> constraint. Ignoring.")
}
}
for( let ch of constraintXML.getAttributeNames() ){
if( ch === "condition" ){
if( constraintXML.getAttribute( "condition" ) !== "1" ){
this.conversionWarnings.constraints.push(
"Converting a <FreezeMotion> constraint to an Artistoo 'BarrierConstraint', but " +
"don't know how to handle a <Condition> other than '1'. Ignoring Condition.")
}
} else {
this.conversionWarnings.constraints.push(
"I don't know what to do with property '" + ch +
"' in a <FreezeMotion> constraint. Ignoring.")
}
}
}
setPersistenceConstraintForKind( constraintXML, kindIndex ){
if( !this.hasConstraint( "PersistenceConstraint" ) ){
this.addConstraint( "PersistenceConstraint", {
DELTA_T : this.initCellKindVector( 0 ),
LAMBDA_DIR : this.initCellKindVector( 0 ),
// Morpheus doesn't have this param and just uses the default 1.
PERSIST : this.initCellKindVector( 1 ),
PROTRUDE : this.initCellKindVector( true ),
RETRACT : this.initCellKindVector( false )
})
}
const dt = this.parameterFromXML( constraintXML,
"decay-time", kindIndex, "int" )
const lambda = this.parameterFromXML( constraintXML,
"strength", kindIndex, "float" )
// Two other params specified in morpheus
let protrude = constraintXML.getAttribute( "protrusion" )
if( typeof protrude === undefined ){
protrude = true
} else {
protrude = ( protrude === "true" )
}
let retract = constraintXML.getAttribute( "retraction" )
if( typeof retract === undefined ){
retract = false
} else {
retract = ( retract === "true" )
}
this.getConstraintParameter( "PersistenceConstraint",
"DELTA_T" )[kindIndex] = dt
this.getConstraintParameter( "PersistenceConstraint",
"LAMBDA_DIR" )[kindIndex] = lambda
this.getConstraintParameter( "PersistenceConstraint",
"PROTRUDE" )[kindIndex] = protrude
this.getConstraintParameter( "PersistenceConstraint",
"RETRACT" )[kindIndex] = retract
}
setPreferredDirectionConstraintForKind( constraintXML, kindIndex ){
if( !this.hasConstraint( "PreferredDirectionConstraint" ) ){
this.addConstraint( "PreferredDirectionConstraint", {
DIR : this.initCellKindVector( this.initDimensionVector(0) ),
LAMBDA_DIR : this.initCellKindVector( 0 ),
PROTRUDE : this.initCellKindVector( true ),
RETRACT : this.initCellKindVector( false )
})
}
const kindName = this.getKindName( kindIndex )
const dirSymbol = constraintXML.getAttribute( "direction" )
let dir = undefined
if( this.cellKinds.properties[kindName].hasOwnProperty( dirSymbol ) ){
// read value of this parameter
dir = this.cellKinds.properties[kindName][dirSymbol]
}
let lambdaDir = this.parameterFromXML( constraintXML,
"strength", kindIndex, "float" )
// Two other params specified in morpheus
let retract = constraintXML.getAttribute( "retraction" )
if( typeof retract === undefined ){
retract = false
} else {
retract = ( retract === "true" )
}
let protrude = constraintXML.getAttribute( "protrusion" )
if( typeof protrude === undefined ){
protrude = true
} else {
protrude = ( protrude === "true" )
}
this.getConstraintParameter( "PreferredDirectionConstraint",
"DIR" )[kindIndex] = dir
this.getConstraintParameter( "PreferredDirectionConstraint",
"LAMBDA_DIR" )[kindIndex] = lambdaDir
this.getConstraintParameter( "PreferredDirectionConstraint",
"PROTRUDE" )[kindIndex] = protrude
this.getConstraintParameter( "PreferredDirectionConstraint",
"RETRACT" )[kindIndex] = retract
}
}
export default MorpheusImport