def load(filename): '''Load the scene contained in the file with the provided name. The root node is returned Currently the supported formats are psl, pslx, pslp. ''' filename = os.path.abspath(filename) dirname = os.path.dirname(filename) with SetPath(dirname): f = open(filename).read() ast=[] if filename.endswith(".psl"): ast = pslparserhjson.parse(f) elif filename.endswith(".pslx"): ast = pslparserxml.parse(f) elif filename.endswith(".pslp"): ast = pslparserpickled.parse(f) if len(ast) == 0: rootNode = Sofa.createNode("root") Sofa.msg_error(rootNode, "The file '"+filename+"' does not contains valid PSL content.") return rootNode directives = preProcess(ast[0][1]) if not directives["version"] in ["1.0"]: rootNode = Sofa.createNode("root") Sofa.msg_error(rootNode, "Unsupported PSLVersion"+str(directives["version"])) return rootNode g=globals() g["__file__"]=filename ret = pslengine.processTree(ast, directives, g) return ret return None
def addVisual(self, color=[1,1,1,1]): if self.dofs is None: Sofa.msg_error("Flexible.API.Deformable","addVisual : visual mesh not added because there is no dof, use LoadVisual instead to have a static visual mesh ") else: # create a new deformable d = Deformable(self.node,"Visual") d.visualFromDeformable(self,color) return d
def processPython(parent, key, token, stack, frame): """Process a python fragment of code with context provided by the content of the stack.""" if not isAStringToken(token, ("s", "m")): Sofa.msg_error(parent, "Python expect string only argument instead of '"+str(token)+"'") return None #if not isAStringToken(token, ("s", "m")): # raise Exception("Only string or multiline string allowed: "+str(token)) ## retrieve the value as a string. stringvalue = token[1] p=parent.createObject("Python", name='"'+stringvalue[0:20]+'..."') p.addNewData("psl_source","PSL", "This hold a python expression.", "s", str(stringvalue)) context = flattenStackFrame(stack) local = {} exec(stringvalue, context, local) ## Transfer the local entries to the previously defined context or in the existing variable ## somewhere in the stack frame. ## This allow import in one line and use the imported in the following ones. for k in local: lframe = findStackLevelFor(k, stack) if lframe != None: lframe[k] = local[k] else: stack[-1][k] = local[k]
def addMeshToImage(self, voxelSize): args = dict() i = 1 for name in self.meshSeq: mesh = self.meshes[name] if mesh.mesh is None: Sofa.msg_error("Image.API", "addMeshToImage : no mesh for " + name) return meshPath = mesh.mesh.getLinkPath() args["position" + str(i)] = meshPath + ".position" args["triangles" + str(i)] = meshPath + ".triangles" args["value" + str(i)] = mesh.value if mesh.insideValue is None: args["fillInside" + str(i)] = False else: args["insideValue" + str(i)] = mesh.insideValue if not mesh.roiIndices is None and len(mesh.roiValue) != 0: args["roiIndices" + str(i)] = mesh.roiIndices args["roiValue" + str(i)] = concat(mesh.roiValue) i += 1 self.image = self.node.createObject( "MeshToImageEngine", template=self.template(), name="image", voxelSize=voxelSize, padSize=1, subdiv=8, rotateImage=False, nbMeshes=len(self.meshes), **args )
def createScene(node): # some code before a = 0 # w/ emitter Sofa.msg_info( "MyPythonEmitter", "my message info a="+str(a) ) Sofa.msg_warning( "MyPythonEmitter a="+str(a), "my message warning" ) Sofa.msg_error( "MyPythonEmitter", "my message error" ) Sofa.msg_fatal( "MyPythonEmitter", "my message fatal" ) # some code in between a = 2 # w/o emitter Sofa.msg_info( "my message info a="+str(a) ) Sofa.msg_warning( "my message warning" ) Sofa.msg_error( "my message error" ) Sofa.msg_fatal( "my message fatal" ) # more complex code was causing trouble, so try it model = SofaPython.sml.Model("smlSimple.sml") # # invalid calls # Sofa.msg_info( 100 ) # Sofa.msg_info( "emitter", "message", "extra" ) sys.stdout.flush()
def processAlias(parent, key, token, stack, frame): if not isAStringToken(token, ('p')): Sofa.msg_error(parent, pslprefix+" Unable to set an alias because of invalid syntax.") global aliases value = token[1] oldName, newName = value.split(' as ') aliases[newName]=oldName
def setManually(self, filepath=None, offset=[[0,0,0,0,0,0,1]], voxelSize=0.01, density=1000, generatedDir=None): if len(offset) == 0: Sofa.msg_error("RigidScale.API","ShearlessAffineBody should have at least 1 ShearLessAffine") return self.framecom = Frame.Frame() self.bodyOffset = Frame.Frame([0,0,0,0,0,0,1]) path_affine_rigid = '@'+ Tools.node_path_rel(self.affineNode, self.rigidNode) path_affine_scale = '@'+ Tools.node_path_rel(self.affineNode, self.scaleNode) if len(offset) == 1: self.frame = [Frame.Frame(offset[0])] str_position = "" for p in offset: str_position = str_position + concat(p) + " " ### scene creation # rigid dof self.rigidDofs = self.rigidNode.createObject('MechanicalObject', template='Rigid3'+template_suffix, name='dofs', position=str_position, rest_position=str_position) # scale dofs self.scaleDofs = self.scaleNode.createObject('MechanicalObject', template='Vec3'+template_suffix, name='dofs', position=concat([1,1,1]*len(offset))) positiveNode = self.scaleNode.createChild('positive') positiveNode.createObject('MechanicalObject', template='Vec3'+template_suffix, name='positivescaleDOFs') positiveNode.createObject('DifferenceFromTargetMapping', template='Vec3'+template_suffix+',Vec3'+template_suffix, applyRestPosition=1, targets=concat(target_scale)) positiveNode.createObject('UniformCompliance', isCompliance=1, compliance=0) positiveNode.createObject('UnilateralConstraint') positiveNode.createObject('Stabilization', name='Stabilization') # affine dofs self.affineDofs = self.affineNode.createObject('MechanicalObject', template='Affine', name='parent', showObject=0) self.affineNode.createObject('RigidScaleToAffineMultiMapping', template='Rigid,Vec3,Affine', input1=path_affine_rigid, input2=path_affine_scale, output='@.', autoInit='1', printLog='0') if filepath: self.image = SofaImage.API.Image(self.affineNode, name="image_" + self.name, imageType="ImageUC") self.shapeFunction = Flexible.API.ShapeFunction(self.affineNode) if generatedDir is None: self.image.addMeshLoader(filepath, value=1, insideValue=1, scale=scale3d) # TODO support multiple meshes closingValue=1, self.image.addMeshToImage(voxelSize) self.shapeFunction.addVoronoi(self.image, position='@dofs.rest_position') # mass self.affineMassNode = self.affineNode.createChild('mass') self.affineMassNode.createObject('TransferFunction', name='density', template='ImageUC,ImageD', inputImage='@../image.image', param='0 0 1 '+str(density)) self.affineMassNode.createObject('MechanicalObject', template='Vec3'+template_suffix) self.affineMassNode.createObject('LinearMapping', template='Affine,Vec3'+template_suffix) self.affineMassNode.createObject('MassFromDensity', name='MassFromDensity', template='Affine,ImageD', image='@density.outputImage', transform='@../image.transform', lumping='0') self.mass = self.affineNode.createObject('AffineMass', massMatrix='@mass/MassFromDensity.massMatrix') else: self.image.addContainer(filename=self.node.name + "_rasterization.raw", directory=generatedDir) self.shapeFunction.shapeFunction = serialization.importImageShapeFunction(self.affineNode, generatedDir+self.node.name+"_SF_indices.raw", generatedDir+self.node.name+"_SF_weights.raw", 'dofs') self.mass = serialization.importAffineMass(self.affineNode, generatedDir+self.node.name+"_affinemass.json") # computation of the object mass center massInfo = SofaPython.mass.RigidMassInfo() massInfo.setFromMesh(filepath, density, [1,1,1]) # get the object mass center self.framecom.rotation = massInfo.inertia_rotation self.framecom.translation = massInfo.com else: print "You need a mesh to create an articulated system" self.frame = [] for o in offset: self.frame.append(Frame.Frame(o))
def addMechanicalObject(self, position=None, **kwargs): if position is None: if not self.mesh is None: # use implicit definition from mesh topology self.dof = self.node.createObject("MechanicalObject", template="Vec3", name="dofs", **kwargs) else: Sofa.msg_error("Flexible.API.FEMDof","addMechanicalObject : no input position") return self.dof = self.node.createObject("MechanicalObject", template="Vec3", name="dofs", position=position, **kwargs)
def parseImages(self, obj, objXml): images=objXml.findall("image") for i,m in enumerate(images): imageId = m.attrib["id"] if imageId in self.images: obj.addImage(self.images[imageId]) else: Sofa.msg_error("SofaPython.sml","Model: solid {0} references undefined image {1}".format(obj.name, imageId))
def parseMeshes(self, obj, objXml): meshes=objXml.findall("mesh") for i,m in enumerate(meshes): meshId = m.attrib["id"] attr = Model.MeshAttributes(m) if meshId in self.meshes: obj.addMesh(self.meshes[meshId], attr) else: Sofa.msg_error("SofaPython.sml","Model: solid {0} references undefined mesh {1}".format(obj.name, meshId))
def addMeshVisual(self, meshName=None, color=None): name = self.meshSeq[0] if meshName is None else meshName mesh = self.meshes[name] if mesh.mesh is None: Sofa.msg_error("Image.API", "addMeshVisual : no mesh for " + meshName) return mesh.visual = self.node.createObject("VisualModel", name="visual_" + name, src=mesh.mesh.getLinkPath()) if not color is None: mesh.visual.setColor(color[0], color[1], color[2], color[3])
def write(self, filenamePrefix=None, directory=""): if self.dof is None: Sofa.msg_error("Flexible.API.AffineDof","write : no dof") return filename = self.getFilename(filenamePrefix,directory) data = {'template':'Affine', 'rest_position': affineDatatostr(self.dof.rest_position), 'position': affineDatatostr(self.dof.position)} with open(filename, 'w') as f: json.dump(data, f) if printLog: Sofa.msg_info("Flexible.API.AffineDof",'Exported Affine Dof to '+filename)
def addViewer(self): if self.image is None: Sofa.msg_error("Image.API", "addViewer : no image") return self.viewer = self.node.createObject( "ImageViewer", name="viewer", template=self.imageType, image=self.getImagePath() + ".image", transform=self.getImagePath() + ".transform", )
def addMechanicalObject(self, template="Vec3"): if self.sampler is None: Sofa.msg_error("Image.API", "addMechanicalObject : no sampler") return None if self.mesh is None: self.dofs = self.node.createObject( "MechanicalObject", template=template, name="dofs", position="@sampler.position" ) else: self.dofs = self.node.createObject("MechanicalObject", template=template, name="dofs") return self.dofs
def processObject(parent, key, kv, stack, frame): global sofaComponents populateFrame(key, frame, stack) frame = {} kwargs = {} if not isinstance(kv, list): kv = [("name" , kv)] properties = None for k,v in kv: if k == "properties": if properties == None: properties = v else: c=parent.createChild("[XX"+key+"XX]") Sofa.msg_error(c, pslprefix+" Unable to create an object '"+key+"' because of duplicated properties keywords.") return None elif isAStringToken(v, ('s', 'p')): v = processString(v, stack, frame) kwargs[k] = v elif isinstance(v, int): kwargs[k] = v elif isinstance(v, float): kwargs[k] = v else: c=parent.createChild("[XX"+key+"XX]") Sofa.msg_error(c, pslprefix+" Unable to create an object '"+key+"' because of invalid parameter "+str(k)+"="+str(v)) return None stack.append(frame) frame["self"] = obj = createObject(parent, key, stack, frame, kwargs) if properties: processProperties(obj, obj.name, properties, stack, frame) ### Force all the data field into a non-persistant state. for datafield in obj.getListOfDataFields(): datafield.setPersistant(False) for link in obj.getListOfLinks(): link.setPersistant(False) ### Then revert only the ones that have been touched for dataname in kwargs: try: if dataname in datafieldQuirks: continue field = getField(obj, dataname) if field != None: field.setPersistant(True) except Exception,e: Sofa.msg_warning(obj, pslprefix+" this does not seems to be a valid field '"+str(dataname)+"'")
def addVoronoi(self, image, position='', cells='', nbRef=8): """ Add a Voronoi shape function using path to position and possibly cells """ if position =='': Sofa.msg_error("Flexible.API.ShapeFunction","addVoronoi : no position") return self.prefix = image.prefix # branching or regular image ? self.shapeFunction = self.node.createObject( "VoronoiShapeFunction", template="ShapeFunction,"+image.template(), name="shapeFunction", cell=cells, position=position, src=image.getImagePath(), method=0, nbRef=nbRef, bias=True)
def parseJointGenerics(self,modelXml): for j in modelXml.findall("jointGeneric"): if j.attrib["id"] in self.genericJoints: Sofa.msg_error("SofaPython.sml","Model: jointGeneric defined twice, id:", j.attrib["id"]) continue joint=Model.JointGeneric(j) solids=j.findall("jointSolidRef") for i,o in enumerate(solids): if o.attrib["id"] in self.solids: joint.solids[i] = self.solids[o.attrib["id"]] else: Sofa.msg_error("SofaPython.sml","Model: in joint {0}, unknown solid {1} referenced".format(joint.name, o.attrib["id"])) self.genericJoints[joint.id]=joint
def processImportPY(parent, importname, filename, key, stack, frame): Sofa.msg_info(parent, pslprefix+"Importing '"+importname+".py' file format.") pythonfile = open(filename).read() locals = {} frame = flattenStackFrame(stack) exec(pythonfile, frame, locals) if not "PSLExport" in locals: Sofa.msg_error(parent, pslprefix+"The file '"+filename+"' does not seem to contain PSL templates.") return for name in locals: templates[importname+"."+name] = locals[name]
def processParameter(parent, name, value, stack, frame): try: if isinstance(value, list): matches = difflib.get_close_matches(name, sofaComponents+templates.keys()+sofaAliases.keys(), n=4) c=parent.createChild("[XX"+name+"XX]") Sofa.msg_error(c, pslprefix+" unknow parameter or component [" + name + "] suggestions -> "+str(matches)) elif not name in datafieldQuirks: ## Python Hook to build an eval function. value = processString(value, stack, frame) try: field = getField(frame["self"], name) if field != None: field.setValueString(str(value)) field.setPersistant(True) else: Sofa.msg_error(parent, pslprefix+" unable to get the field '" +name+"'") except Exception,e: Sofa.msg_error(parent, pslprefix+" exception while parsing field '" +name+"' because "+str(e)) if name == "name": frame[value] = frame["self"] #frame["name"] = value except Exception, e: SofaPython.sendMessageFromException(e) Sofa.msg_error(parent, pslprefix+" unable to parse parameter '"+str(name)+ "=" + str(value)+"'")
def addExporter(self, filenamePrefix=None, directory=""): if self.shapeFunction is None: Sofa.msg_error("Flexible.API.ShapeFunction","addExporter : no shapeFunction") return sfPath = self.shapeFunction.getLinkPath() self.node.createObject( "ImageExporter", template=self.prefix+"ImageUI", name="exporterIndices", image=sfPath+".indices", transform=sfPath+".transform", filename=self.getFilenameIndices(filenamePrefix, directory), exportAtBegin=True, printLog=printLog) self.node.createObject( "ImageExporter", template=self.prefix+"ImageR", name="exporterWeights", image=sfPath+".weights", transform=sfPath+".transform", filename=self.getFilenameWeights(filenamePrefix, directory), exportAtBegin=True, printLog=printLog)
def createObject(parentNode, name, stack , frame, kv): if name in sofaComponents: obj = parentNode.createObject(name, **kv) for k in kv: if getField(obj, k) == None: Sofa.msg_info(obj, pslprefix+" attribute '"+str(k)+"' is a parsing hook. Let's add Data field to fix it. To remove this warning stop using parsing hook.") d = obj.addNewData(k, "PSL", "", "s", str(kv[k])) obj.findData(k).setPersistant(True) return obj kv["name"] = name failureObject = parentNode.createObject("Undefined", **kv) Sofa.msg_error(failureObject, pslprefix+" unable to create the object '"+str(name)+"'") return failureObject
def addClosingVisual(self, meshName=None, color=None): name = self.meshSeq[0] if meshName is None else meshName mesh = self.meshes[name] if mesh.mesh is None: Sofa.msg_error("Image.API", "addClosingVisual : no mesh for " + meshName) return if mesh.mesh.findData("closingPosition") is None: Sofa.msg_error("Image.API", "addClosingVisual : mesh is not a closing " + meshName) return mesh.closingVisual = self.node.createObject( "VisualModel", name="closingVisual_" + name, position=mesh.mesh.getLinkPath() + ".closingPosition", triangles=mesh.mesh.getLinkPath() + ".closingTriangles", ) if not color is None: mesh.closingVisual.setColor(color[0], color[1], color[2], color[3])
def loadAst(filename): ast=[] filename = os.path.abspath(filename) dirname = os.path.dirname(filename) with SetPath(dirname): os.chdir(dirname) if not os.path.exists(filename): Sofa.msg_error("PSL", "Unable to open file '"+filename+"'") return ast f = open(filename).read() if filename.endswith(".psl"): ast = pslparserhjson.parse(f) elif filename.endswith(".pslx"): ast = pslparserxml.parse(f) elif filename.endswith(".pslp"): ast = pslparserpickled.parse(f) return ast
def getValueByTag(valueByTag, tags): """ look into the valueByTag dictionary for a tag contained in tags \return the corresponding value, or the "default" value if none is found \todo print a warning if several matching tags are found in valueByTag """ if "default" in tags: Sofa.msg_error("SofaPython.sml.getValueByTag", "default tag has a special meaning, it should not be defined in {0}".format(tags)) tag = tags & set(valueByTag.keys()) if len(tag)>1: Sofa.msg_warning("SofaPython.sml.getValueByTag", "sevaral tags from {0} are defined in values {1}".format(tags, valueByTag)) if not len(tag)==0: return valueByTag[tag.pop()] else: if "default" in valueByTag: return valueByTag["default"] else: Sofa.msg_error("SofaPython.sml.getValueByTag", "No default value, and no tag from {0} found in {1}".format(tags, valueByTag)) return None
def sofaExceptHandler(type, value, tb): global oldexcepthook """This exception handler, convert python exceptions & traceback into more classical sofa error messages of the form: Message Description Python Stack (most recent are at the end) File file1.py line 4 ... File file1.py line 10 ... File file1.py line 40 ... File file1.py line 23 ... faulty line """ h = type.__name__ if str(value) != '': h += ': ' + str(value) s = ''.join(traceback.format_tb(tb)) Sofa.msg_error(h + '\n' + s, "line", 7)
def processRootNode(kv, stack, frame): global templates, aliases stack.append(frame) populateFrame("", frame, stack) if isinstance(kv, list): for key,value in kv: if isinstance(key, unicode): key = str(key) if key == "Node": n = processNode(None, key, value, stack, {}) return n else: Sofa.msg_error(tself, pslprefix+"Unable to find a root Node in this file") return None Sofa.msg_error(tself, pslprefix+"Unable to find a root Node in this file") return None
def loadOBJ(filename): ## quickly written .obj import # Sofa.msg_info("PythonMeshLoader","loadOBJ: "+filename) m = Mesh() if not os.path.exists(filename): Sofa.msg_error("PythonMeshLoader","loadOBJ: inexistent file "+filename) return Mesh() for line in open(filename, "r"): vals = line.split() if len(vals)==0: # empty line continue if vals[0] == "v": v = map(float, vals[1:4]) m.vertices.append(v) elif vals[0] == "vn": n = map(float, vals[1:4]) m.normals.append(n) elif vals[0] == "vt": t = map(float, vals[1:3]) m.uv.append(t) elif vals[0] == "f" or vals[0] == "l": faceV = [] faceUV = [] faceN = [] for f in vals[1:]: w = f.split("/") faceV.append(int(w[0])-1) if len(w) > 1 and w[1]: faceUV.append(int(w[1])-1) if len(w) > 2 and w[2]: faceN.append(int(w[2])-1) m.faceVertices.append(faceV) m.faceUv.append(faceUV) m.faceNormals.append(faceN) return m
def insertLinearMapping(node, dofRigidNode=None, dofAffineNode=None, cell='', assemble=True, geometricStiffness=2, isMechanical=True): """ insert the correct Linear(Multi)Mapping hopefully the template is deduced automatically by the component TODO: better names for input dofRigidNode and dofAffineNode, they can be any kind of nodes """ if dofRigidNode is None and dofAffineNode is None: Sofa.msg_error("Flexible.API","insertLinearMapping : no dof given") else: if dofRigidNode is None: return node.createObject( "LinearMapping", cell=cell, shapeFunction = 'shapeFunction', input="@"+dofAffineNode.getPathName(), output="@.", assemble=assemble, geometricStiffness=geometricStiffness, mapForces=isMechanical, mapConstraints=isMechanical, mapMasses=isMechanical) elif dofAffineNode is None: return node.createObject( "LinearMapping", cell=cell, shapeFunction = 'shapeFunction', input="@"+dofRigidNode.getPathName(), output="@.", assemble=assemble, geometricStiffness=geometricStiffness, mapForces=isMechanical, mapConstraints=isMechanical, mapMasses=isMechanical) else: return node.createObject( "LinearMultiMapping", cell=cell, shapeFunction = 'shapeFunction', input1="@"+dofRigidNode.getPathName(), input2="@"+dofAffineNode.getPathName(), output="@.", assemble=assemble, geometricStiffness=geometricStiffness, mapForces=isMechanical, mapConstraints=isMechanical, mapMasses=isMechanical)
def processImport(parent, key, kv, stack, frame): """ This function "import" the file provided as parameter """ ## Check that the kv value is in fact a string or an unicode if not isAStringToken(kv): Sofa.msg_error(parent, pslprefix+" to much parameter given in procesImport " + str(type(kv))) return importname=processString(kv, stack, frame) if isinstance(importname, unicode): importname=str(importname) ## If it is we get complete the filename and try to open it. filename = getFileToImportFromPartialName(parent, importname) if filename == None or not os.path.exists(filename): dircontent = os.listdir(os.getcwd()) matches = difflib.get_close_matches(filename, dircontent, n=4) Sofa.msg_error(parent, pslprefix+" the file '" + filename + "' does not exists. Do you mean: "+str(matches)) return if filename.endswith(".psl"): return processImportPSL(parent, importname, filename, key, stack, frame) elif filename.endswith(".pslx"): return processImportPSLX(parent, importname, filename, key, stack, frame) elif filename.endswith(".py"): return processImportPY(parent, importname, filename, key, stack, frame) Sofa.msg_error(parent, pslprefix+"invalid file format to import "+ os.getcwd() + "/"+filename+ " supproted format are [psl, pslx, py]")
def subsetFromDeformables(self, deformableIndicesList = list() ): args=dict() inputs=[] indexPairs=[] i=1 mapTopo = True mapDofs = True for s,ind in deformableIndicesList: s.node.addChild(self.node) if s.dofs is None: mapDofs = False else: if not ind is None: for p in ind: indexPairs+=[i-1,p] else: # no roi -> take all points. Warning: does not work if parent dofs are not initialized size = len(s.dofs.position) if size==0: Sofa.msg_error("Flexible.API.Deformable","subsetFromDeformables: no dof from "+ s.name) for p in xrange(size): indexPairs+=[i-1,p] if s.topology is None: mapTopo = False else: if not ind is None: subset = self.node.createObject("MeshSubsetEngine", template = "Vec3", name='MeshSubsetEngine_'+s.name, inputPosition=s.topology.getLinkPath()+'.position', inputTriangles=s.topology.getLinkPath()+'.triangles', inputQuads=s.topology.getLinkPath()+'.quads', indices=concat(ind)) path = '@'+subset.name else: # map all path = s.topology.getLinkPath() args["position"+str(i)]=path+".position" args["triangles"+str(i)]=path+".triangles" args["quads"+str(i)]=path+".quads" inputs.append('@'+s.node.getPathName()) i+=1 if mapTopo: self.meshLoader = self.node.createObject('MergeMeshes', name='MergeMeshes', nbMeshes=len(inputs), **args ) self.topology = self.node.createObject("MeshTopology", name="topology", src="@"+self.meshLoader.name ) if mapDofs: self.dofs = self.node.createObject("MechanicalObject", template = "Vec3", name="dofs") self.mapping = self.node.createObject("SubsetMultiMapping", name='mapping', indexPairs=concat(indexPairs), input=concat(inputs),output="@.")
def VolumeEffector(surfaceMeshFileName=None, attachedAsAChildOf=None, attachedTo=None, name="VolumeEffector", rotation=[0.0, 0.0, 0.0], translation=[0.0, 0.0, 0.0], uniformScale=1, initialValue=0, valueType="volumeGrowth"): """Creates and adds a volume effector constraint. This creates all the components necessary in Sofa to include a cavity model whose volume should be determined by optimizing an external force. The constraint apply to a parent mesh. Args: cavityMeshFile (string): path to the cavity mesh (the mesh should be a surfacic mesh, ie only triangles or quads). name (string): name of the created node. initialValue (real): value to apply, default is 0. valueType (string): type of the parameter value (volumeGrowth or pressure), default is volumeGrowth. Structure: .. sourcecode:: qml Node : { name : "VolumeEffector" MeshTopology, MechanicalObject, SurfacePressureConstraint, BarycentricMapping } """ if attachedAsAChildOf == None and attachedTo == None: Sofa.msg_error( "Your VolumeEffector isn't link/child of any node, please set the argument attachedTo or attachedAsAChildOf" ) return None if surfaceMeshFileName == None: Sofa.msg_error("No surfaceMeshFileName specified, please specify one") return None veffector = getOrCreateTheTemplateNode( attachedAsAChildOf=attachedAsAChildOf, attachedTo=attachedTo, name=name) # This create a MeshSTLLoader, a componant loading the topology of the cavity. if surfaceMeshFileName.endswith(".stl"): veffector.createObject('MeshSTLLoader', name='MeshLoader', filename=surfaceMeshFileName, rotation=rotation, translation=translation, scale=uniformScale) elif surfaceMeshFileName.endswith(".obj"): veffector.createObject('MeshObjLoader', name='MeshLoader', filename=surfaceMeshFileName, rotation=rotation, translation=translation, scale=uniformScale) else: Sofa.msg_error( "Your surfaceMeshFileName extension is not the right one, you have to give a surfacic mesh with .stl or .obj extension" ) return None # This create a MeshTopology, a componant holding the topology of the cavity. # veffector.createObject('MeshTopology', name="topology", filename=surfaceMeshFileName) veffector.createObject('MeshTopology', name='topology', src='@MeshLoader') # This create a MechanicalObject, a componant holding the degree of freedom of our # mechanical modelling. In the case of a cavity actuated with veffector, it is a set of positions specifying # the points where the pressure is applied. veffector.createObject('MechanicalObject', src="@topology") veffector.createObject('VolumeEffector', template='Vec3', triangles='@topology.triangles') #veffector.createObject('SurfacePressureConstraint', # value=initialValue, # valueType=valueType) # This create a BarycentricMapping. A BarycentricMapping is a key element as it will create a bi-directional link # between the cavity's DoFs and the parents's ones so that the pressure applied on the cavity wall will be mapped # to the volume structure and vice-versa; veffector.createObject('BarycentricMapping', name="Mapping", mapForces=False, mapMasses=False) return veffector
def setManually(self, filepath=None, offset=[[0, 0, 0, 0, 0, 0, 1]], voxelSize=0.01, density=1000, mass=1, inertia=[1, 1, 1], inertia_forces=0, generatedDir=None, uniformScale=False): if len(offset) == 0: Sofa.msg_error( "RigidScale.API", "ShearlessAffineBody should have at least 1 ShearLessAffine") return self.framecom = Frame.Frame() self.bodyOffset = Frame.Frame([0, 0, 0, 0, 0, 0, 1]) path_affine_rigid = '@' + Tools.node_path_rel(self.affineNode, self.rigidNode) path_affine_scale = '@' + Tools.node_path_rel(self.affineNode, self.scaleNode) if len(offset) == 1: self.frame = [Frame.Frame(offset[0])] str_position = "" for p in offset: str_position = str_position + concat(p) + " " if uniformScale: scaleTemplate = "Vec1" else: scaleTemplate = "Vec3" ### scene creation # rigid dof self.rigidDofs = self.rigidNode.createObject( 'MechanicalObject', template='Rigid3' + template_suffix, name='dofs', position=str_position, rest_position=str_position) # scale dofs self.scaleDofs = self.scaleNode.createObject( 'MechanicalObject', template=scaleTemplate + template_suffix, name='dofs', position=concat([1, 1, 1] * len(offset))) # The positiveNode is now commented for the moment since it seems not working """positiveNode = self.scaleNode.createChild('positive') positiveNode.createObject('MechanicalObject', template='Vec3'+template_suffix, name='positivescaleDOFs') positiveNode.createObject('DifferenceFromTargetMapping', template='Vec3'+template_suffix+',Vec3'+template_suffix, applyRestPosition=1, targets=concat(target_scale)) positiveNode.createObject('UniformCompliance', isCompliance=1, compliance=0) positiveNode.createObject('UnilateralConstraint') positiveNode.createObject('Stabilization', name='Stabilization')""" # affine dofs self.affineDofs = self.affineNode.createObject('MechanicalObject', template='Affine', name='parent', showObject=0) self.affineNode.createObject('RigidScaleToAffineMultiMapping', template='Rigid,' + scaleTemplate + ',Affine', input1=path_affine_rigid, input2=path_affine_scale, output='@.', autoInit='1', printLog='0') if filepath: self.image = SofaImage.API.Image(self.rigidNode, name="image_" + self.name, imageType="ImageUC") self.shapeFunction = Flexible.API.ShapeFunction(self.affineNode) if generatedDir is None: self.image.addMeshLoader( filepath, value=1, insideValue=1 ) # TODO support multiple meshes closingValue=1, self.image.addMeshToImage(voxelSize) self.shapeFunction.addVoronoi(self.image, position='@dofs.rest_position') # mass self.affineMassNode = self.affineNode.createChild('mass') self.affineMassNode.createObject( 'TransferFunction', name='density', template='ImageUC,ImageD', inputImage='@' + Tools.node_path_rel(self.affineMassNode, self.image.node) + '/image.image', param='0 0 1 ' + str(density)) self.affineMassNode.createObject('MechanicalObject', template='Vec3' + template_suffix) self.affineMassNode.createObject('LinearMapping', template='Affine,Vec3' + template_suffix) self.affineMassNode.createObject( 'MassFromDensity', name='MassFromDensity', template='Affine,ImageD', image='@density.outputImage', transform='@' + Tools.node_path_rel(self.affineMassNode, self.image.node) + '/image.transform', lumping='0') self.mass = self.affineNode.createObject( 'AffineMass', massMatrix='@mass/MassFromDensity.massMatrix') else: self.image.addContainer(filename=self.node.name + "_rasterization.raw", directory=generatedDir) self.shapeFunction.shapeFunction = serialization.importImageShapeFunction( self.affineNode, generatedDir + self.node.name + "_SF_indices.raw", generatedDir + self.node.name + "_SF_weights.raw", 'dofs') self.mass = serialization.importAffineMass( self.affineNode, generatedDir + self.node.name + "_affinemass.json") # computation of the object mass center massInfo = SofaPython.mass.RigidMassInfo() massInfo.setFromMesh(filepath, density, [1, 1, 1]) # get the object mass center self.framecom.rotation = massInfo.inertia_rotation self.framecom.translation = massInfo.com else: if (mass and inertia) and inertia != [0, 0, 0]: Sofa.msg_info( "RigidScale", "A RigidMass and a UniformMass are created for respectively the rigid and the scale since there is no mesh which can be used to compute the model mass." ) self.mass = self.rigidNode.createObject( 'RigidMass', name='mass', mass=mass, inertia=concat(inertia[:3]), inertia_forces=inertia_forces) self.scaleNode.createObject('UniformMass', name='mass', mass=mass) self.frame = [] for o in offset: self.frame.append(Frame.Frame(o))
def SubTopology(attachedTo=None, containerLink=None, boxRoiLink=None, linkType='tetrahedron', name="modelSubTopo", poissonRatio=0.3, youngModulus=18000): """ Args: attachedTo (Sofa.Node): Where the node is created. containerLink (str): path to container with the dataField to link, ie: '@../container.position' boxRoiLink (str): path to boxRoi with the dataField to link, linkType (str): indicate of which type is the subTopo, ei: 'triangle' -> TriangleSetTopologyContainer & TriangleFEMForceField name (str): name of the child node youngModulus (float): The young modulus. poissonRatio (float): The poisson parameter. Structure: .. sourcecode:: qml Node : { name : "modelSubTopo", TetrahedronSetTopologyContainer, TetrahedronFEMForceField } """ if attachedTo is None: Sofa.msg_error( "Your SubTopology isn't child of any node, please set the argument attachedTo" ) return None if containerLink is None or boxRoiLink is None: Sofa.msg_error( "You have to specify at least a container & boxROI link ") return None linkTypeUp = linkType[0].upper() + linkType[1:] # From the linkType which type of topology & forcefield to create : # ex : tetrahedra -> create TetrahedraSetTopologyContainer & TetrahedraFEMForceField # From the containerLink we have the position of our TetrahedraSetTopologyContainer # From the boxRoiLink we have the elements we want from our boxROI # here we extract from the boxRoiLink the name of the data field to link it to strTmp = boxRoiLink[::-1] start = strTmp.find('nI') + 2 end = strTmp.find('.') strTmp = strTmp[start:end] argument = strTmp[::-1] modelSubTopo = attachedTo.createChild(name) modelSubTopo.createObject(linkTypeUp + 'SetTopologyContainer', name='container', position=containerLink) modelSubTopo.getObject('container').findData(argument).value = boxRoiLink modelSubTopo.createObject(linkTypeUp + 'FEMForceField', name='FEM', method='large', poissonRatio=poissonRatio, youngModulus=youngModulus) return modelSubTopo
def PneumaticCavity(surfaceMeshFileName=None, attachedTo=None, name="PneumaticCavity", minPressure=None, maxPressure=None, minVolumeGrowth=None, maxVolumeGrowth=None, maxVolumeGrowthVariation=None, rotation=[0.0, 0.0, 0.0], translation=[0.0, 0.0, 0.0], uniformScale=1): """Creates and adds a pneumatic actuation. Should be used in the context of the resolution of an inverse problem: find the actuation that leads to a desired deformation. See documentation at: https://project.inria.fr/softrobot/documentation/constraint/surface-pressure-actuator/ The constraint is applied to a parent mesh. Args: cavityMeshFile (string): path to the cavity mesh (the mesh should be a surfacic mesh, ie only triangles or quads). name (string): name of the created node. minPressure: maxPressure: minVolumeGrowth: maxVolumeGrowth: maxVolumeGrowthVariation: Structure: .. sourcecode:: qml Node : { name : "PneumaticCavity" MeshTopology, MechanicalObject, SurfacePressureConstraint, BarycentricMapping } """ if attachedTo == None: Sofa.msg_error("Your PneumaticCavity isn't child of any node, please set the argument attachedTo") return None if surfaceMeshFileName == None: Sofa.msg_error("No surfaceMeshFileName specified, please specify one") return None # This create a componant loading the topology of the cavity. if surfaceMeshFileName.endswith(".stl"): pneumatic.createObject('MeshSTLLoader', name='MeshLoader', filename=surfaceMeshFileName, rotation=rotation, translation=translation, scale=uniformScale) elif surfaceMeshFileName.endswith(".obj"): pneumatic.createObject('MeshObjLoader', name='MeshLoader', filename=surfaceMeshFileName, rotation=rotation, translation=translation, scale=uniformScale) else : Sofa.msg_error("Your surfaceMeshFileName extension is not the right one, you have to give a surfacic mesh with .stl or .obj extension") return None # This create a MeshTopology, a componant holding the topology of the cavity. # pneumatic.createObject('MeshTopology', name="topology", filename=surfaceMeshFileName) pneumatic.createObject('MeshTopology', name='topology', src='@MeshLoader') # This create a MechanicalObject, a componant holding the degree of freedom of our # mechanical modelling. In the case of a cavity actuated with pneumatic, it is a set of positions specifying # the points where the pressure is applied. pneumatic.createObject('MechanicalObject', src="@topology") # Create a SurfacePressureConstraint object with a name. # the indices are referring to the MechanicalObject's positions. pneumatic.createObject('SurfacePressureActuator') if minPressure != None : pneumatic.minPressure = minPressure if maxPressure != None : pneumatic.maxPressure = maxPressure if minVolumeGrowth != None : pneumatic.minVolumeGrowth = minVolumeGrowth if maxVolumeGrowth != None : pneumatic.maxVolumeGrowth = maxVolumeGrowth if maxVolumeGrowthVariation != None : pneumatic.maxVolumeGrowthVariation = maxVolumeGrowthVariation # This create a BarycentricMapping. A BarycentricMapping is a key element as it will create a bi-directional link # between the cavity's DoFs and the parents's ones so that the pressure applied on the cavity wall will be mapped # to the volume structure and vice-versa; pneumatic.createObject('BarycentricMapping', name="Mapping", mapForces=False, mapMasses=False) return pneumatic
def Rigidify(targetObject, sourceObject, groupIndices, frames=None, name=None, frameOrientation=None): """ Transform a deformable object into a mixed one containing both rigid and deformable parts. :param targetObject: parent node where to attach the final object. :param sourceObject: node containing the deformable object. The object should be following the ElasticMaterialObject template. :param list groupIndices: array of array indices to rigidify. The length of the array should be equal to the number of rigid component. :param list frames: array of frames. The length of the array should be equal to the number of rigid component. The orientation are given in eulerAngles (in degree) by passing three values or using a quaternion by passing four values. [[rx,ry,rz], [qx,qy,qz,w]] User can also specify the position of the frame by passing six values (position and orientation in degree) or seven values (position and quaternion). [[x,y,z,rx,ry,rz], [x,y,z,qx,qy,qz,w]] If the position is not specified, the position of the rigids will be the barycenter of the region to rigidify. :param str name: specify the name of the Rigidified object, is none provided use the name of the SOurceObject. """ # Deprecation Warning if frameOrientation is not None: Sofa.msg_warning( "The parameter frameOrientations of the function Rigidify is now deprecated. Please use frames instead." ) frames = frameOrientation if frames is None: frames = [[0., 0., 0.]] * len(groupIndices) assert len(groupIndices) == len(frames), "size mismatch." if name is None: name = sourceObject.name sourceObject.init() ero = targetObject.createChild(name) allPositions = sourceObject.container.position allIndices = map(lambda x: x[0], sourceObject.container.points) rigids = [] indicesMap = [] def mfilter(si, ai, pts): tmp = [] for i in ai: if i in si: tmp.append(pts[i]) return tmp # get all the points from the source. sourcePoints = map(Vec3, sourceObject.dofs.position) selectedIndices = [] for i in range(len(groupIndices)): selectedPoints = mfilter(groupIndices[i], allIndices, sourcePoints) if len(frames[i]) == 3: orientation = Quat.createFromEuler(frames[i], inDegree=True) poscenter = getBarycenter(selectedPoints) elif len(frames[i]) == 4: orientation = frames[i] poscenter = getBarycenter(selectedPoints) elif len(frames[i]) == 6: orientation = Quat.createFromEuler( [frames[i][3], frames[i][4], frames[i][5]], inDegree=True) poscenter = [frames[i][0], frames[i][1], frames[i][2]] elif len(frames[i]) == 7: orientation = [ frames[i][3], frames[i][4], frames[i][5], frames[i][6] ] poscenter = [frames[i][0], frames[i][1], frames[i][2]] else: Sofa.msg_error("Do not understand the size of a frame.") rigids.append(poscenter + list(orientation)) selectedIndices += map(lambda x: x, groupIndices[i]) indicesMap += [i] * len(groupIndices[i]) otherIndices = filter(lambda x: x not in selectedIndices, allIndices) Kd = {v: None for k, v in enumerate(allIndices)} Kd.update({v: [0, k] for k, v in enumerate(otherIndices)}) Kd.update({v: [1, k] for k, v in enumerate(selectedIndices)}) indexPairs = [v for kv in Kd.values() for v in kv] freeParticules = ero.createChild("DeformableParts") freeParticules.createObject( "MechanicalObject", template="Vec3", name="dofs", position=[allPositions[i] for i in otherIndices]) rigidParts = ero.createChild("RigidParts") rigidParts.createObject("MechanicalObject", template="Rigid3", name="dofs", reserve=len(rigids), position=rigids) rigidifiedParticules = rigidParts.createChild("RigidifiedParticules") rigidifiedParticules.createObject( "MechanicalObject", template="Vec3", name="dofs", position=[allPositions[i] for i in selectedIndices]) rigidifiedParticules.createObject("RigidMapping", name="mapping", globalToLocalCoords='true', rigidIndexPerPoint=indicesMap) sourceObject.removeObject(sourceObject.solver) sourceObject.removeObject(sourceObject.integration) sourceObject.removeObject(sourceObject.LinearSolverConstraintCorrection) # The coupling is made with the sourceObject. If the source object is from an ElasticMaterialObject # We need to get the owning node form the current python object (this is a hack because of the not yet # Finalized design of stlib. coupling = sourceObject if hasattr(sourceObject, "node"): coupling = sourceObject.node coupling.createObject("SubsetMultiMapping", name="mapping", template="Vec3,Vec3", input=freeParticules.dofs.getLinkPath() + " " + rigidifiedParticules.dofs.getLinkPath(), output=sourceObject.dofs.getLinkPath(), indexPairs=indexPairs) rigidifiedParticules.addChild(coupling) freeParticules.addChild(coupling) return ero
def subsetFromDeformables(self, deformableIndicesList=list()): args = dict() inputs = [] indexPairs = [] i = 1 mapTopo = True mapDofs = True for s, ind in deformableIndicesList: s.node.addChild(self.node) if s.dofs is None: mapDofs = False else: if not ind is None: for p in ind: indexPairs += [i - 1, p] else: # no roi -> take all points. Warning: does not work if parent dofs are not initialized size = len(s.dofs.position) if size == 0: Sofa.msg_error( "Flexible.API.Deformable", "subsetFromDeformables: no dof from " + s.name) for p in xrange(size): indexPairs += [i - 1, p] if s.topology is None: mapTopo = False else: if not ind is None: subset = self.node.createObject( "MeshSubsetEngine", template="Vec3", name='MeshSubsetEngine_' + s.name, inputPosition=s.topology.getLinkPath() + '.position', inputTriangles=s.topology.getLinkPath() + '.triangles', inputQuads=s.topology.getLinkPath() + '.quads', indices=concat(ind)) path = '@' + subset.name else: # map all path = s.topology.getLinkPath() args["position" + str(i)] = path + ".position" args["triangles" + str(i)] = path + ".triangles" args["quads" + str(i)] = path + ".quads" inputs.append('@' + s.node.getPathName()) i += 1 if mapTopo: self.meshLoader = self.node.createObject('MergeMeshes', name='MergeMeshes', nbMeshes=len(inputs), **args) self.topology = self.node.createObject("MeshTopology", name="topology", src="@" + self.meshLoader.name) if mapDofs: self.dofs = self.node.createObject("MechanicalObject", template="Vec3", name="dofs") self.mapping = self.node.createObject( "SubsetMultiMapping", name='mapping', indexPairs=concat(indexPairs), input=concat(inputs), output="@.")
def addMass(self, totalMass): if self.dofs is None: Sofa.msg_error("Flexible.API.Deformable", "addMass : no dofs for " + self.name) return self.mass = self.node.createObject('UniformMass', totalMass=totalMass)
def open(self, filename): self.modelDir = os.path.dirname(filename) with open(filename,'r') as f: # TODO automatic DTD validation could go here, not available in python builtin ElementTree module modelXml = etree.parse(f).getroot() if self.name is None and "name" in modelXml.attrib: self.name = modelXml.attrib["name"] else: self.name = os.path.basename(filename) # units self.parseUnits(modelXml) # meshes for m in modelXml.iter("mesh"): if not m.find("source") is None: if m.attrib["id"] in self.meshes: Sofa.msg_warning("SofaPython.sml","Model: mesh id {0} already defined".format(m.attrib["id"]) ) mesh = Model.Mesh(m) sourceFullPath = os.path.join(self.modelDir,mesh.source) if os.path.exists(sourceFullPath): mesh.source=sourceFullPath else: Sofa.msg_warning("SofaPython.sml","Model: mesh not found: "+mesh.source ) self.meshes[m.attrib["id"]] = mesh # images for m in modelXml.iter("image"): if not m.find("source") is None: if m.attrib["id"] in self.images: Sofa.msg_warning("SofaPython.sml","Model: image id {0} already defined".format(m.attrib["id"]) ) image = Model.Image(m) sourceFullPath = os.path.join(self.modelDir,image.source) if os.path.exists(sourceFullPath): image.source=sourceFullPath else: Sofa.msg_warning("SofaPython.sml","Model: image not found: "+image.source ) self.images[m.attrib["id"]] = image # solids for objXml in modelXml.findall("solid"): if objXml.attrib["id"] in self.solids: Sofa.msg_error("SofaPython.sml","Model: solid defined twice, id:" + objXml.attrib["id"]) continue solid=Model.Solid(objXml) self.parseMeshes(solid, objXml) self.parseImages(solid, objXml) self.solids[solid.id]=solid # skinning for objXml in modelXml.findall("solid"): solid=self.solids[objXml.attrib["id"]] # TODO: support multiple meshes for skinning (currently only the first mesh is skinned) for s in objXml.findall("skinning"): if not s.attrib["solid"] in self.solids: Sofa.msg_error("SofaPython.sml","Model: skinning for solid {0}: solid {1} is not defined".format(solid.name, s.attrib["solid"]) ) continue skinning = Model.Skinning() if not s.attrib["solid"] in self.solids : Sofa.msg_error("SofaPython.sml","Model: skinning for solid {0}: bone (solid) {1} not defined".format(solid.name, s.attrib["solid"]) ) continue skinning.solid = self.solids[s.attrib["solid"]] if not s.attrib["mesh"] in self.meshes : Sofa.msg_error("SofaPython.sml","Model: skinning for solid {0}: mesh {1} not defined".format(solid.name, s.attrib["mesh"]) ) continue skinning.mesh = self.meshes[s.attrib["mesh"]] #TODO: check that this mesh is also part of the solid if not (s.attrib["group"] in skinning.mesh.group and s.attrib["weight"] in skinning.mesh.group[s.attrib["group"]].data): Sofa.msg_error("SofaPython.sml","Model: skinning for solid {0}: mesh {1} - group {2} - weight {3} is not defined".format(solid.name, s.attrib["mesh"], s.attrib["group"], s.attrib["weight"])) continue skinning.index = skinning.mesh.group[s.attrib["group"]].index skinning.weight = skinning.mesh.group[s.attrib["group"]].data[s.attrib["weight"]] solid.skinnings.append(skinning) # joints self.parseJointGenerics(modelXml) # contacts for c in modelXml.findall("surfaceLink"): if c.attrib["id"] in self.surfaceLinks: Sofa.msg_error("SofaPython.sml","Model: surfaceLink defined twice, id:", c.attrib["id"]) continue surfaceLink = Model.SurfaceLink(c) surfaces=c.findall("surface") for i,s in enumerate(surfaces): surfaceLink.surfaces[i] = Model.Surface() if s.attrib["solid"] in self.solids: surfaceLink.surfaces[i].solid = self.solids[s.attrib["solid"]] else: Sofa.msg_error("SofaPython.sml","Model: in contact {0}, unknown solid {1} referenced".format(surfaceLink.name, s.attrib["solid"])) if s.attrib["mesh"] in self.meshes: surfaceLink.surfaces[i].mesh = self.meshes[s.attrib["mesh"]] else: Sofa.msg_error("SofaPython.sml","Model: in contact {0}, unknown mesh {1} referenced".format(surfaceLink.name, s.attrib["mesh"])) if "group" in s.attrib: # optional if len(s.attrib["group"]): # discard empty string surfaceLink.surfaces[i].group = s.attrib["group"] # if "image" in s.attrib: # optional # if len(s.attrib["image"]): # discard empty string # if s.attrib["image"] in self.images: # reg.surfaces[i].image = self.images[s.attrib["image"]] self.surfaceLinks[surfaceLink.id]=surfaceLink
def PneumaticSensor(surfaceMeshFileName=None, attachedAsAChildOf=None, attachedTo=None, name="PneumaticSensor", rotation=[0.0, 0.0, 0.0], translation=[0.0, 0.0, 0.0], uniformScale=1, initialValue=0, valueType="volumeGrowth"): """Creates and adds a pneumatic sensor constraint. This initializes all the components necessary to implement a virtual volume sensor. For a given geometry this will initialize a cavity that will deform as the mesh deforms. The component calculates the volume, but doesn't apply any constraint, i.e. there is no forces coming from a change in pressure for instance. This is the case when in real life air flow sensors are being used. The constraint apply to a parent mesh. Args: cavityMeshFile (string): path to the cavity mesh (the mesh should be a surfacic mesh, ie only triangles or quads). name (string): name of the created node. initialValue (real): value to apply, default is 0. valueType (string): type of the parameter value (volumeGrowth or pressure), default is volumeGrowth. Structure: .. sourcecode:: qml Node : { name : "PneumaticSensor" MeshTopology, MechanicalObject, SurfacePressureConstraint, BarycentricMapping } """ if attachedAsAChildOf == None and attachedTo == None: Sofa.msg_error("Your PneumaticSensor isn't link/child of any node, please set the argument attachedTo or attachedAsAChildOf") return None if surfaceMeshFileName == None: Sofa.msg_error("No surfaceMeshFileName specified, please specify one") return None PneumaticSensor = getOrCreateTheTemplateNode(attachedAsAChildOf=attachedAsAChildOf, attachedTo=attachedTo, name=name) # This create a MeshSTLLoader, a componant loading the topology of the cavity. if surfaceMeshFileName.endswith(".stl"): PneumaticSensor.createObject('MeshSTLLoader', name='MeshLoader', filename=surfaceMeshFileName, rotation=rotation, translation=translation, scale=uniformScale) elif surfaceMeshFileName.endswith(".obj"): PneumaticSensor.createObject('MeshObjLoader', name='MeshLoader', filename=surfaceMeshFileName, rotation=rotation, translation=translation, scale=uniformScale) else : Sofa.msg_error("Your surfaceMeshFileName extension is not the right one, you have to give a surfacic mesh with .stl or .obj extension") return None # This create a MeshTopology, a componant holding the topology of the cavity. # PneumaticSensor.createObject('MeshTopology', name="topology", filename=surfaceMeshFileName) PneumaticSensor.createObject('MeshTopology', name='topology', src='@MeshLoader') # This create a MechanicalObject, a componant holding the degree of freedom of our # mechanical modelling. In the case of a cavity actuated with PneumaticSensor, it is a set of positions specifying # the points where the pressure is applied. PneumaticSensor.createObject('MechanicalObject', src="@topology") # Create a SurfacePressureConstraint object with a name. # the indices are referring to the MechanicalObject's positions. #PneumaticSensor.createObject('SurfacePressureConstraint', # value=initialValue, # valueType=valueType) # This create a BarycentricMapping. A BarycentricMapping is a key element as it will create a bi-directional link # between the cavity's DoFs and the parents's ones so that the pressure applied on the cavity wall will be mapped # to the volume structure and vice-versa; PneumaticSensor.createObject('BarycentricMapping', name="Mapping", mapForces=False, mapMasses=False) PneumaticSensor.createObject('SurfacePressureSensor', triangles='@topology.triangles') return PneumaticSensor