import math
import itertools

import Sofa

import SofaPython.Tools
from SofaPython.Tools import listToStr as concat
import SofaPython.Quaternion as Quat
import SofaPython.units
import Compliant.Frame
import Compliant.StructuralAPI

#def addRigid(parentNode, param, frame1, frame2, angle, axis=0):

class ProgressivePositionController:
    """ Set the joint position to the offset
    WARNING: for angular dof position, the value must be in ]-pi,pi]
    """
    def __init__(self, node, mask, target, compliance, isCompliance=True):
        self.node = node.createChild( "controller-mask" )

        self.dofs = self.node.createObject('MechanicalObject', template='Vec1', name='dofs')
        self.node.createObject('MaskMapping', dofs=concat(mask))
        self.progressiveTarget = self.node.createObject('ProgressiveTarget', template='Vec1', name="target", current="@dofs.position", target=concat(target))
        self.nodeTarget = self.node.createChild( "controller-target" )
        self.nodeTarget.createObject('MechanicalObject', template='Vec1', name='dofs')
        self.mapping = self.nodeTarget.createObject('DifferenceFromTargetMapping', targets="@../target.output")
        self.compliance = self.nodeTarget.createObject('UniformCompliance', name='compliance', compliance=compliance, isCompliance=isCompliance)

    def setTarget( self, target ):
        self.progressiveTarget.target = concat(target)



class RigidTransformation:
    """ Add a generic constraint between two Compliant.RigidBody or Compliant.RigidBody.Offset (or similar)
    if no target is given, then 0 is set as target
    """

    def __init__(self, name, frame1, frame2, mask, compliance=1e-12, isCompliance=True, target=None, progressive=False):
        self.node = frame1.node.createChild(name)
        frame2.node.addChild(self.node)
        self.dofs = self.node.createObject('MechanicalObject', template = 'Vec6', name = 'dofs')
        self.node.createObject('RigidJointMultiMapping', template = 'Rigid3,Vec6', name = 'mapping',
                                    input = "@"+frame1.node.getPathName()+" @"+frame2.node.getPathName(),
                                    output = '@dofs', pairs = "0 0",
                                    geometricStiffness = Compliant.StructuralAPI.geometric_stiffness)
        self.mask = mask
        maskNode = self.node.createChild("mask")
        maskDofs = maskNode.createObject('MechanicalObject', template='Vec1', name='dofs')
        maskNode.createObject('MaskMapping', name="mapping", dofs=concat(mask))
        targetNode = maskNode.createChild("target")
        targetNode.createObject('MechanicalObject', template='Vec1', name='dofs')

        if target is None:
            _target=list()
            for m in mask:
                if m==1: _target+=[0]
        else:
            _target=target
        self.progressiveTarget = None
        if progressive:
            self.progressiveTarget = targetNode.createObject('ProgressiveTarget', template='Vec1', name="target", current="@../dofs.position", target=concat(_target))
            self.mapping = targetNode.createObject('DifferenceFromTargetMapping', targets="@target.output", name="mapping")
        else:
            self.mapping = targetNode.createObject('DifferenceFromTargetMapping', targets=concat(_target), name="mapping")
        targetNode.createObject('UniformCompliance', name='compliance', compliance=compliance, isCompliance=isCompliance)

    def detachFromGraph(self):
        self.node.detachFromGraph()

    def setTarget(self, target):
        if not self.progressiveTarget is None:
            self.progressiveTarget.target = concat(target)
        else:
            self.mapping.targets = concat(target)



class RigidRigidRelativeTransformation:
    """ Add rigid transformation constraint between two Compliant.RigidBody or Compliant.RigidBody.Offset (or similar)
    transformation expressed in frame1 axis
    """
    def __init__(self, name, frame1, frame2, mask, compliance=1e-12, isCompliance=True, target=None, progressive=False):
        # rigidely map (attach) frame1 in frame2
        self.frame1AttachedToFrame2 = Compliant.StructuralAPI.RigidBody.Offset( frame2.node, name+"_frame1AttachedToFrame2", (Compliant.Frame.Frame(frame2.dofs.position[0]).inv()*Compliant.Frame.Frame(frame1.dofs.position[0])).offset() )
        self.rigidTransformation = RigidTransformation(name, frame1, self.frame1AttachedToFrame2, mask, compliance, isCompliance, target, progressive)
        self.dofs = self.rigidTransformation.dofs
        self.progressiveTarget = None
        if progressive:
            self.progressiveTarget = self.rigidTransformation.progressiveTarget
        self.mask = self.rigidTransformation.mask
        self.mapping = self.rigidTransformation.mapping

    def detachFromGraph(self):
        self.rigidTransformation.detachFromGraph()
        self.frame1AttachedToFrame2.node.detachFromGraph()

    def setTarget(self, target):
        if not self.progressiveTarget is None:
            self.progressiveTarget.target = concat(target)
        else:
            self.mapping.targets = concat(target)


class RigidRigidRelativeRotation(RigidRigidRelativeTransformation):
    """ Add rotation constraint between two Compliant.RigidBody or Compliant.RigidBody.Offset (or similar)
    rotation expressed in frame1 axis
    """
    def __init__(self, name, frame1, frame2, mask, compliance=1e-12, isCompliance=True, target=None, progressive=False):
        _mask = mask if len(mask)==6 else [0,0,0]+mask
        RigidRigidRelativeTransformation.__init__(self, name, frame1, frame2, _mask, compliance, isCompliance, target, progressive)



class RigidRigidRelativeTransformationAroundGivenFrame:
    """ Add rigid transformation constraint between two Compliant.RigidBody or Compliant.RigidBody.Offset (or similar)
    transformation expressed in a given frame
    """
    def __init__(self, name, frame1, frame2, mask, givenFrame=Compliant.Frame.Frame(), compliance=1e-12, isCompliance=True, target=None, progressive=False):
        # rigidely map (attach) given frame in frame1 and frame2
        self.frame1 = Compliant.StructuralAPI.RigidBody.Offset( frame1.node, name+"_frame1", (Compliant.Frame.Frame(frame1.dofs.position[0]).inv()*givenFrame).offset() )
        self.frame2 = Compliant.StructuralAPI.RigidBody.Offset( frame2.node, name+"_frame2", (Compliant.Frame.Frame(frame2.dofs.position[0]).inv()*givenFrame).offset() )

        self.rigidTransformation = RigidTransformation(name, self.frame1, self.frame2, mask, compliance, isCompliance, target, progressive)
        self.dofs = self.rigidTransformation.dofs
        self.progressiveTarget = None
        if progressive:
            self.progressiveTarget = self.rigidTransformation.progressiveTarget
        self.mask = self.rigidTransformation.mask
        self.mapping = self.rigidTransformation.mapping

    def detachFromGraph(self):
        self.rigidTransformation.detachFromGraph()
        self.frame1.node.detachFromGraph()
        self.frame2.node.detachFromGraph()

    def setTarget(self, target):
        if not self.progressiveTarget is None:
            self.progressiveTarget.target = concat(target)
        else:
            self.mapping.targets = concat(target)

    def addDebug(self,size=.5):
        self.frame1.dofs.showObject=1
        self.frame1.dofs.showObjectScale=size
        self.frame2.dofs.showObject=1
        self.frame2.dofs.showObjectScale=size


class RigidRigidRelativeRotationAroundGivenFrame(RigidRigidRelativeTransformationAroundGivenFrame):
    """ Add rotation constraint between two Compliant.RigidBody or Compliant.RigidBody.Offset (or similar)
    rotation expressed in a given frame
    """
    def __init__(self, name, frame1, frame2, mask, givenFrame=Compliant.Frame.Frame(), compliance=1e-12, isCompliance=True, target=None, progressive=False):
        _mask = mask if len(mask)==6 else [0,0,0]+mask
        RigidRigidRelativeTransformationAroundGivenFrame.__init__(self, name, frame1, frame2, _mask, givenFrame, compliance, isCompliance, target, progressive)



class RigidWorldTransformation:
    """ Add a generic constraint between a Compliant.RigidBody and the world frame
    if no target is given, then 0 is set as target
    """

    def __init__(self, name, frame, mask, compliance=1e-12, isCompliance=True, target=None, progressive=False):
        self.node = frame.node.createChild(name)

        self.dofs = self.node.createObject('MechanicalObject', template = 'Vec6', name = 'dofs')
        self.mappingRigidJoint = self.node.createObject('RigidJointFromWorldFrameMapping', template = 'Rigid3,Vec6', name = 'mapping')

        maskNode = self.node.createChild("mask")
        maskDofs = maskNode.createObject('MechanicalObject', template='Vec1', name='dofs')
        maskNode.createObject('MaskMapping', name="mapping", dofs=concat(mask))

        targetNode = maskNode.createChild("target")
        targetNode.createObject('MechanicalObject', template='Vec1', name='dofs')

        if target is None:
            _target=list()
            for m in mask:
                if m==1: _target+=[0]
        else:
            _target=target
        self.progressiveTarget = None
        if progressive:
            self.progressiveTarget = targetNode.createObject('ProgressiveTarget', template='Vec1', name="target", current="@../dofs.position", target=concat(_target))
            self.mapping = targetNode.createObject('DifferenceFromTargetMapping', targets="@target.output", name="mapping")
        else:
            self.mapping = targetNode.createObject('DifferenceFromTargetMapping', targets=concat(_target), name="mapping")
        targetNode.createObject('UniformCompliance', name='compliance', compliance=compliance, isCompliance=isCompliance)

    def detachFromGraph(self):
        self.node.detachFromGraph()

    def setTarget(self, target):
        if not self.progressiveTarget is None:
            self.progressiveTarget.target = concat(target)
        else:
            self.mapping.targets = concat(target)





class RigidWorldRelativeTransformation:
    """ Add a generic constraint between a Compliant.RigidBody and the world frame
    if no target is given, then 0 is set as target
    """

    def __init__(self, name, frame, mask, compliance=1e-12, isCompliance=True, target=None, progressive=False):
        self.mask = mask
        self.frame1AttachedToFrame2 = Compliant.StructuralAPI.RigidBody.Offset( frame.node, name+"_frame1AttachedToFrame2", Compliant.Frame.Frame(frame.dofs.position[0]).inv().offset() )
        self.rigidTransformation = RigidWorldTransformation(name, self.frame1AttachedToFrame2, self.mask, compliance, isCompliance, target, progressive)
        self.rigidTransformation.mappingRigidJoint.translation = False
        self.dofs = self.rigidTransformation.dofs
        self.progressiveTarget = None
        if progressive:
            self.progressiveTarget = self.rigidTransformation.progressiveTarget
        self.node = self.frame1AttachedToFrame2.node
        self.mapping = self.rigidTransformation.mapping

    def detachFromGraph(self):
        self.rigidTransformation.detachFromGraph()
        self.frame1AttachedToFrame2.node.detachFromGraph()

    def setTarget(self, target):
        if not self.progressiveTarget is None:
            self.progressiveTarget.target = concat(target)
        else:
            self.mapping.targets = concat(target)


class RigidWorldRelativeRotation(RigidWorldRelativeTransformation):
    """ Add a generic rotation constraint between a Compliant.RigidBody and the world frame
    if no target is given, then 0 is set as target
    """

    def __init__(self, name, frame, mask, compliance=1e-12, isCompliance=True, target=None, progressive=False):
        _mask = mask if len(mask)==6 else [0,0,0]+mask
        RigidWorldRelativeTransformation.__init__(self, name, frame, _mask, compliance, isCompliance, target, progressive)


class RigidRigidTransformation:
    """ Add a generic constraint between two Compliant.RigidBody or Compliant.RigidBody.Offset (or similar)
    if no target is given, then 0 is set as target
    It is similar to RigidTransformation but the target is given as a RigidCoord (translation+quaternion),
    Only the dof specified in mask are constrained, note that rotationnal dof are either all constrained or none of them
    This constraint does not suffer rotation instabilities of RigidTransformation
    """

    def __init__(self, name, frame1, frame2, mask, compliance=1e-12, isCompliance=True, progressive=False, isRelative=False):
        if not reduce(lambda x, y: x+y, mask[3:]) in (0,3):
            Sofa.msg_error("Anatomy.Constraint.RigidRigidTransformation", "Either all or none of the rotation dofs are constrained, requested mask is {0}".format(mask))
            return
        self.isRelative = isRelative
        if (isRelative) :
            frame1_rest_position = frame1.dofs.rest_position[0] if len(frame1.dofs.rest_position)>0 else frame1.dofs.position[0]
            frame2_rest_position = frame2.dofs.rest_position[0] if len(frame2.dofs.rest_position)>0 else frame2.dofs.position[0]
            # frame1 axis at frame2 origin
            frame_relative_1 = frame2_rest_position[0:3] + frame1_rest_position[3:]
            frame_relative_2 = frame2.dofs.position[0][0:3] + frame1.dofs.position[0][3:]
            self.frame1 = Compliant.StructuralAPI.RigidBody.Offset( frame1.node, name+"_frame1", (Compliant.Frame.Frame(frame1.dofs.position[0]).inv()*Compliant.Frame.Frame(frame_relative_1)).offset() )
            self.frame2 = Compliant.StructuralAPI.RigidBody.Offset( frame2.node, name+"_frame2", (Compliant.Frame.Frame(frame2.dofs.position[0]).inv()*Compliant.Frame.Frame(frame_relative_2)).offset() )
        else:
            self.frame1 = frame1
            self.frame2 = frame2

        self.mask = mask
        self.node = self.frame1.node.createChild(name)
        self.frame2.node.addChild(self.node)
        self.node.createObject('MechanicalObject', template = 'Rigid3', name = 'dofs')
        self.node.createObject('RigidJointMultiMapping', template = 'Rigid3,Rigid3', name = 'mapping',
                                    input = "@"+self.frame1.node.getPathName()+" @"+self.frame2.node.getPathName(),
                                    output = '@dofs', pairs = "0 0",
                                    geometricStiffness = Compliant.StructuralAPI.geometric_stiffness)

        f1 = Compliant.Frame.Frame(self.frame1.dofs.position[0])
        f2 = Compliant.Frame.Frame(self.frame2.dofs.position[0])
        target = f1.inv() * f2

        self.mappingT = None # if necessary, a specific mapping for translation
        self.mappingR = None # and one for rotation, when possible a single mapping is created
        self.complianceT = None # same philosophy as for mapping
        self.complianceR = None

        self.progressiveTarget = None # engine to reach the target step by step
        if progressive :
            self.progressiveTarget = self.node.createObject('ProgressiveTarget', template='Rigid3', name="progressiveTarget", current="@dofs.position", target=str(target))
        if reduce(lambda x, y: x+y, mask) == 6:
            # all dofs constrained in a single node
            nodeTarget = self.node.createChild("target")
            nodeTarget.createObject('MechanicalObject', template = 'Vec6', name = 'dofs')
            if progressive :
                self.mappingT = nodeTarget.createObject('RigidJointFromTargetMapping', template='Rigid3,Vec6', name="mapping",
                                                        rotation=True, translation=True,
                                                        targets="@../progressiveTarget.output")
            else :
                self.mappingT = nodeTarget.createObject('RigidJointFromTargetMapping', template='Rigid3,Vec6', name = 'mapping',
                                                        rotation=True, translation=True, targets=str(target))
            self.mappingR = self.mappingT
            self.complianceT = nodeTarget.createObject('UniformCompliance', name='compliance', compliance=compliance, isCompliance=isCompliance)
        else :
            if reduce(lambda x, y: x+y, mask[3:]) == 3:
                # full rotation constraint
                nodeTargetR = self.node.createChild("target_rotation")
                nodeTargetR.createObject('MechanicalObject', template = 'Vec6', name = 'dofs')
                if progressive :
                    self.mappingR = nodeTargetR.createObject('RigidJointFromTargetMapping', template = 'Rigid3,Vec6', name = 'mapping',
                                                             rotation=True, translation=False,
                                                             targets="@../progressiveTarget.output")
                else:
                    self.mappingR = nodeTargetR.createObject('RigidJointFromTargetMapping', template = 'Rigid3,Vec6', name = 'mapping',
                                                             rotation=True, translation=False, targets=str(target))
                self.complianceR = nodeTargetR.createObject('UniformCompliance', name='compliance', compliance=compliance, isCompliance=isCompliance)
            if reduce(lambda x, y: x+y, mask[0:3]) == 3:
                # full translation constraint
                nodeTargetT = self.node.createChild("target_translation")
                nodeTargetT.createObject('MechanicalObject', template = 'Vec6', name = 'dofs')
                if progressive :
                    self.mappingT = nodeTargetT.createObject('RigidJointFromTargetMapping', template = 'Rigid3,Vec6', name = 'mapping',
                                                             rotation=False, translation=True,
                                                             targets="@../progressiveTarget.output")
                else:
                    self.mappingT = nodeTargetT.createObject('RigidJointFromTargetMapping', template = 'Rigid3,Vec6', name = 'mapping',
                                                             rotation=False, translation=True, targets=str(target))
                self.complianceT = nodeTargetT.createObject('UniformCompliance', name='compliance', compliance=compliance, isCompliance=isCompliance)
            elif reduce(lambda x, y: x+y, mask[0:3]) > 0:
                # partial translation constraint
                target.rotation=[0,0,0,1] # translation to be expressed in the reference axis vectors
                nodeTargetT = self.node.createChild("target_translation")
                nodeTargetT.createObject('MechanicalObject', template = 'Vec6', name = 'dofs')
                if progressive :
                    self.mappingT = nodeTargetT.createObject('RigidJointFromTargetMapping', template = 'Rigid3,Vec6', name = 'mapping',
                                                             rotation=False, translation=True,
                                                             targets="@../progressiveTarget.output")
                else:
                    self.mappingT = nodeTargetT.createObject('RigidJointFromTargetMapping', template = 'Rigid3,Vec6', name = 'mapping',
                                                             rotation=False, translation=True, targets=str(target))
                maskNode = nodeTargetT.createChild("mask")
                maskDofs = maskNode.createObject('MechanicalObject', template='Vec1', name='dofs')
                maskNode.createObject('MaskMapping', name="mapping", dofs=concat(mask))
                self.complianceT = maskNode.createObject('UniformCompliance', name='compliance', compliance=compliance, isCompliance=isCompliance)

    def detachFromGraph(self):
        self.node.detachFromGraph()
        if self.isRelative:
            self.frame1.node.detachFromGraph()
            self.frame2.node.detachFromGraph()

    def getCurrentPosition(self):
        currentPosition = [0,0,0,0,0,0]
        currentPosition[0:3] = self.node.getObject("dofs").position[0][0:3]
        currentPosition[3:6] = list(Quat.quatToRotVec(Quat.normalized(self.node.getObject("dofs").position[0][3:7])))
        return currentPosition

    def getFullTarget(self):
        fullTarget = [0,0,0,0,0,0]
        if self.progressiveTarget is None :
            if not self.mappingT is None:
                fullTarget[0:3] = self.mappingT.targets[0][0:3]
                if not self.mappingR is None:
                    fullTarget[3:] = list(Quat.quatToRotVec(Quat.normalized(self.mappingR.targets[0][3:])))
        else :
            fullTarget[0:3] = self.progressiveTarget.target[0][0:3]
            fullTarget[3:] = list(Quat.quatToRotVec(Quat.normalized(self.progressiveTarget.target[0][3:])))
        return fullTarget

    def getTarget(self):
        """ get the current target, rotation dof as corresponding rotation vector coordinate
        """
        fullTarget = self.getFullTarget()
        target = list()
        for i in range(6):
            if self.mask[i]==1:
                target+=[fullTarget[i]]
        return target

    def setTarget(self, target):
        """ set the target for the constrained dof, rotation dof as corresponding rotation vector coordinate
        """
        if reduce(lambda x, y: x+y, self.mask) != len(target):
            Sofa.msg_error("Anatomy.Constraint.RigidRigidTransformation", "target {0} should give value for all masked {1} dofs".format(target, self.mask))
            return
        fullTarget = self.getFullTarget()
        iTarget=0
        for (i,m) in enumerate(self.mask):
            if m==1:
                fullTarget[i]=target[iTarget]
                iTarget+=1
        self.setTargetRigid(fullTarget[0:3] + Quat.rotVecToQuat(fullTarget[3:]))


    def setTargetRigid(self, rigidTarget):
        """ set the target as a full rigid transformation [translation, quaternion]
        """
        if self.progressiveTarget is None:
            if not self.mappingT is None:
                _rigidTarget=[0,0,0,0,0,0,1]
                _rigidTarget[0:3]=rigidTarget[0:3]
                self.mappingT.targets = concat(_rigidTarget)
            if not self.mappingR is None:
                self.mappingR.targets = concat(rigidTarget)
        else:
            self.progressiveTarget.target = concat(rigidTarget)

    def getTargetRigid(self):
        """ get the target as a full rigid transformation [translation, quaternion]
        """
        if self.progressiveTarget is None :
            if not self.mappingR is None:
                return self.mappingR.targets[0]
            else:
                return self.mappingT.targets[0]
        else:
            return self.progressiveTarget.target[0]

    def setCompliance(self, compliance):
        """ TODO: split between rotation and translation compliance
        """
        if not self.complianceT is None:
            self.complianceT.compliance = compliance
            self.complianceT.reinit()
        if not self.complianceR is None:
            self.complianceR.compliance = compliance
            self.complianceR.reinit()

class PointTranslation:
    """ Absolute point position
    """

    def __init__(self, name, point, mask, compliance=1e-12, isCompliance=True, progressive=False):
        self.node = point.node.createChild(name)
        self.mask = mask
        maskNode = self.node.createChild("mask")
        maskDofs = maskNode.createObject('MechanicalObject', template='Vec1', name='dofs')
        maskNode.createObject('MaskMapping', name="mapping", dofs=concat(mask))

        targetNode = maskNode.createChild("target")
        targetNode.createObject('MechanicalObject', template='Vec1', name='dofs')

        self.progressiveTarget = None
        if progressive:
            self.progressiveTarget = targetNode.createObject('ProgressiveTarget', template='Vec1', name="target", current="@../dofs.position")
            self.mapping = targetNode.createObject('DifferenceFromTargetMapping', name="mapping", targets="@target.output")
        else:
            self.mapping = targetNode.createObject('DifferenceFromTargetMapping', name="mapping")
        self.compliance = targetNode.createObject('UniformCompliance', name='compliance', compliance=compliance, isCompliance=isCompliance)

    def detachFromGraph(self):
        self.node.detachFromGraph()

    def setTarget(self, target):
        """ set the target as a full rigid transformation [translation, quaternion]
        """
        if not self.progressiveTarget is None:
            self.progressiveTarget.target = concat(target)
        else:
            self.mapping.targets = concat(target)

    def getTarget(self):
        """ set the target as a full rigid transformation [translation, quaternion]
        """
        if not self.progressiveTarget is None:
            return list(itertools.chain.from_iterable(self.progressiveTarget.target))
        else:
            return list(itertools.chain.from_iterable(self.mapping.targets))

def addAngular(parentNode, frame1, frame2, axis_angle):
    """ Add an angular constraint between frame1 and frame2 axis (0:x, 1:y, 2:z)
    angle is in radians
    ex: axis_angle=[[0, pi/4], [2, -pi/2]]
    """
    parentNode.createObject('MechanicalObject', template = 'Vec6', name = 'dofs', position = '0 0 0 0 0 0' )
    parentNode.createObject('RigidJointMultiMapping', template = 'Rigid3,Vec6', name = 'mapping', input = frame1+" "+frame2, output = '@dofs', pairs = "0 0", geometricStiffness = Compliant.StructuralAPI.geometric_stiffness)

    mask = [0] * 6
    for axis in axis_angle:
        mask[3+axis[0]]=1
    maskNode = parentNode.createChild("mask")
    maskNode.createObject('MechanicalObject', template='Vec1', name='dofs')
    maskNode.createObject('MaskMapping', dofs=concat(mask))
    targetNode = maskNode.createChild("target")
    targetNode.createObject('MechanicalObject', template='Vec1', name='dofs')
    angle=[]
    for axis in axis_angle:
        angle.append(axis[1])
    progressiveTarget = targetNode.createObject('ProgressiveTarget', template='Vec1', name="target", current="@../dofs.position", target=concat(angle))
    targetNode.createObject('DifferenceFromTargetMapping', targets="@target.output",)
    targetNode.createObject('UniformCompliance', name='compliance', compliance=0)

def addVolume(rootNode, entityNodePath):
    """ Add a constant volume constraint to the entity at entityNodePath
    """
    entityNode = SofaPython.Tools.getNode(rootNode, entityNodePath)
    if entityNode is None:
        print "Anatomy.Constraint.addVolume: entity node not found", entityNodePath
        return

    volumeNode = entityNode.createChild("VolumeConstraint")
    volumeNode.createObject('MeshTopology', name="topology", src="@../loader")
    volumeNode.createObject('MechanicalObject', template="Vec1", name="Volume")
    volumeNode.createObject('VolumeMapping', template="Vec3,Vec1", triangles="@topology.triangles", quads="@topology.quads")
    volumeNode.createObject('RestShapeSpringsForceField')

