def __init__(self, io_filename = None, mode = 'w', broadphase=None, osi=None, shape_filename=None, set_external_forces=None, length_scale=None): if io_filename is None: self._io_filename = '{0}.hdf5'.format( os.path.splitext(os.path.basename(sys.argv[0]))[0]) else: self._io_filename = io_filename self._mode = mode self._broadphase = broadphase self._osi = osi self._static_origins = [] self._static_orientations = [] self._static_transforms = [] self._static_cobjs = [] self._shape = None self._shapeid = dict() self._static_data = None self._velocities_data = None self._dynamic_data = None self._cf_data = None self._solv_data = None self._input = None self._nslaws = None self._out = None self._data = None self._joints = None self._io = MechanicsIO() self._set_external_forces = set_external_forces self._shape_filename = shape_filename self._number_of_shapes = 0 self._number_of_dynamic_objects = 0 self._number_of_static_objects = 0 self._length_scale = length_scale self._keep = []
class Hdf5(): """a Hdf5 context manager reads at instantiation the translations and orientations of collision objects from hdf5 file It provides functions to output translations and orientations in the same file during simulation (output is done by default in pos.dat) with: time : float object_id : the object id (int) px, py, pz : components of the translation (float) ow, ox, oy oz : components of an unit quaternion (float) """ def __init__(self, io_filename = None, mode = 'w', broadphase=None, osi=None, shape_filename=None, set_external_forces=None, length_scale=None): if io_filename is None: self._io_filename = '{0}.hdf5'.format( os.path.splitext(os.path.basename(sys.argv[0]))[0]) else: self._io_filename = io_filename self._mode = mode self._broadphase = broadphase self._osi = osi self._static_origins = [] self._static_orientations = [] self._static_transforms = [] self._static_cobjs = [] self._shape = None self._shapeid = dict() self._static_data = None self._velocities_data = None self._dynamic_data = None self._cf_data = None self._solv_data = None self._input = None self._nslaws = None self._out = None self._data = None self._joints = None self._io = MechanicsIO() self._set_external_forces = set_external_forces self._shape_filename = shape_filename self._number_of_shapes = 0 self._number_of_dynamic_objects = 0 self._number_of_static_objects = 0 self._length_scale = length_scale self._keep = [] def __enter__(self): if self._set_external_forces is None: self._set_external_forces = self.apply_gravity if self._length_scale is None: self._length_scale = 1 # 1 => m, 1/100. => cm self._out = h5py.File(self._io_filename, self._mode) self._data = group(self._out, 'data') self._ref = group(self._data, 'ref') self._joints = group(self._data, 'joints') self._static_data = data(self._data, 'static', 9) self._velocities_data = data(self._data, 'velocities', 8) self._dynamic_data = data(self._data, 'dynamic', 9) self._cf_data = data(self._data, 'cf', 15) self._solv_data = data(self._data, 'solv', 4) self._input = group(self._data, 'input') self._nslaws = group(self._data, 'nslaws') if self._shape_filename is None: self._shape = ShapeCollection(self) else: self._shape = ShapeCollection(self._shape_filename) return self def __exit__(self, type_, value, traceback): self._out.close() def apply_gravity(self, body): g = constants.g / self._length_scale weight = [0, 0, - body.massValue() * g] body.setFExtPtr(weight) # hdf5 structure def shapes(self): """ Shapes : parameterized primitives or user defined (convex set or meshes) """ return self._ref def static_data(self): """ Coordinates and orientations of static objects. """ return self._static_data def dynamic_data(self): """ Coordinates and orientations of dynamics objects. """ return self._dynamic_data def contact_forces_data(self): """ Contact points informations. """ return self._cf_data def solver_data(self): """ Solver output """ return self._solv_data def instances(self): """ Scene objects. """ return self._input def nonsmooth_laws(self): """ Non smooth laws between group of contactors. """ return self._nslaws def joints(self): """ Joints between dynamic objects or between an object and the scenery. """ return self._joints def importNonSmoothLaw(self, name): if self._broadphase is not None: nslawClass = getattr(Kernel, self._nslaws[name].attrs['type']) # only this one at the moment assert(nslawClass == Kernel.NewtonImpactFrictionNSL) nslaw = nslawClass(float(self._nslaws[name].attrs['e']), 0., float(self._nslaws[name].attrs['mu']), 3) self._broadphase.insert(nslaw, int(self._nslaws[name].attrs['gid1']), int(self._nslaws[name].attrs['gid2'])) def importBRepObject(self, name, translation, orientation, velocity, contactors, mass, given_inertia, body_class, shape_class, face_class, edge_class): if body_class is None: body_class = OccBody if given_inertia is not None: inertia = given_inertia else: inertia = np.eye(3) if mass == 0.: # a static object pass else: body = body_class(translation + orientation, velocity, mass, inertia) for contactor in contactors: # /!\ shared pointer <-> python ref ... shape_instantiated = self._shape.get(contactor.name, shape_class, face_class, edge_class) self._keep.append(shape_instantiated) body.addContactShape(shape_instantiated, contactor.translation, contactor.orientation, contactor.group) self._set_external_forces(body) # add the dynamical system to the non smooth # dynamical system nsds = self._broadphase.model().nonSmoothDynamicalSystem() nsds.insertDynamicalSystem(body) nsds.setOSI(body, self._osi) nsds.setName(body, str(name)) def importObject(self, name, translation, orientation, velocity, contactors, mass, inertia, body_class, shape_class): if body_class is None: body_class = BulletDS if self._broadphase is not None and 'input' in self._data: if mass == 0.: # a static object rbase = btQuaternion(orientation[1], orientation[2], orientation[3], orientation[0]) for c in contactors: (w, x, y, z) = c.orientation c_orientation = btQuaternion(x, y, z, w) rc_orientation = mul(rbase, c_orientation) c_origin = btVector3(c.translation[0], c.translation[1], c.translation[2]) rc_origin = quatRotate(rbase, c_origin) rc_sorigin = btVector3(rc_origin.x() + translation[0], rc_origin.y() + translation[1], rc_origin.z() + translation[2]) static_cobj = btCollisionObject() static_cobj.setCollisionFlags( btCollisionObject.CF_STATIC_OBJECT) self._static_origins.append(rc_sorigin) self._static_orientations.append(rc_orientation) transform = btTransform(rc_orientation) transform.setOrigin(rc_sorigin) self._static_transforms.append(transform) static_cobj.setWorldTransform(transform) static_cobj.setCollisionShape( self._shape.get(c.name)) self._static_cobjs.append(static_cobj) self._broadphase.addStaticObject(static_cobj, int(c.group)) else: # a moving object bws = BulletWeightedShape( self._shape.get(contactors[0].name), mass) if inertia is not None: bws.setInertia(inertia[0], inertia[1], inertia[2]) body = body_class(bws, translation + orientation, velocity, contactors[0].translation, contactors[0].orientation, contactors[0].group) for contactor in contactors[1:]: shape_id = self._shapeid[contactor.name] body.addCollisionShape(self._shape.get(contactor.name), contactor.translation, contactor.orientation, contactor.group) # set external forces self._set_external_forces(body) # add the dynamical system to the non smooth # dynamical system nsds = self._broadphase.model().nonSmoothDynamicalSystem() nsds.insertDynamicalSystem(body) nsds.setOSI(body, self._osi) nsds.setName(body, str(name)) def importJoint(self, name): if self._broadphase is not None: topo = self._broadphase.model().nonSmoothDynamicalSystem().\ topology() joint_class = getattr(Joints, self.joints()[name].attrs['type']) joint_nslaw = EqualityConditionNSL(5) ds1_name = self.joints()[name].attrs['object1'] ds1 = topo.getDynamicalSystem(ds1_name) if 'object2' in self.joints()[name].attrs: ds2_name = self.joints()[name].attrs['object2'] ds2 = topo.getDynamicalSystem(ds2_name) joint = joint_class(ds1, ds2, self.joints()[name].attrs['pivot_point'], self.joints()[name].attrs['axis']) joint_inter = Interaction(5, joint_nslaw, joint) self._broadphase.model().nonSmoothDynamicalSystem().\ link(joint_inter, ds1, ds2) else: joint = joint_class(ds1, self.joints()[name].attrs['pivot_point'], self.joints()[name].attrs['axis']) joint_inter = Interaction(5, joint_nslaw, joint) self._broadphase.model().nonSmoothDynamicalSystem().\ link(joint_inter, ds1) def importScene(self, body_class, shape_class, face_class, edge_class): """ Import into the broadphase object all the static and dynamic objects from hdf5 file """ for shape_name in self._ref: self._shapeid[shape_name] = self._ref[shape_name].attrs['id'] self._number_of_shapes += 1 def floatv(v): return [float(x) for x in v] # import dynamical systems if self._broadphase is not None and 'input' in self._data: for (name, obj) in self._input.items(): input_ctrs = [ctr for _n_, ctr in obj.items()] mass = obj.attrs['mass'] translation = obj.attrs['translation'] orientation = obj.attrs['orientation'] velocity = obj.attrs['velocity'] contactors = [Contactor(ctr.attrs['name'], int(ctr.attrs['group']), floatv(ctr.attrs['translation']), floatv(ctr.attrs['orientation'])) for ctr in input_ctrs] if 'inertia' in obj.attrs: inertia = obj.attrs['inertia'] else: inertia = None if True in ('type' in self.shapes()[ctr.attrs['name']].attrs and self.shapes()[ctr.attrs['name']].attrs['type'] in ['brep', 'step'] for ctr in input_ctrs): # Occ object self.importBRepObject(name, floatv(translation), floatv(orientation), floatv(velocity), contactors, float(mass), inertia, body_class, shape_class, face_class, edge_class) else: # Bullet object self.importObject(name, floatv(translation), floatv(orientation), floatv(velocity), contactors, float(mass), inertia, body_class, shape_class) # import nslaws for name in self._nslaws: self.importNonSmoothLaw(name) for name in self.joints(): self.importJoint(name) def outputStaticObjects(self): """ Outputs translations and orientations of static objects """ time = self._broadphase.model().simulation().nextTime() idd = -1 p = 0 self._static_data.resize(len(self._static_transforms), 0) for transform in self._static_transforms: translation = transform.getOrigin() rotation = transform.getRotation() self._static_data[p, :] = \ [time, idd, translation.x(), translation.y(), translation.z(), rotation.w(), rotation.x(), rotation.y(), rotation.z()] idd -= 1 p += 1 def outputDynamicObjects(self): """ Outputs translations and orientations of dynamic objects """ current_line = self._dynamic_data.shape[0] time = self._broadphase.model().simulation().nextTime() positions = self._io.positions(self._broadphase.model()) self._dynamic_data.resize(current_line + positions.shape[0], 0) times = np.empty((positions.shape[0], 1)) times.fill(time) tidd = np.arange(1, positions.shape[0] + 1).reshape( positions.shape[0], 1) self._dynamic_data[current_line:, :] = np.concatenate((times, tidd, positions), axis=1) def outputVelocities(self): """ Output velocities of dynamic objects """ current_line = self._dynamic_data.shape[0] time = self._broadphase.model().simulation().nextTime() velocities = self._io.velocities(self._broadphase.model()) self._velocities_data.resize(current_line + velocities.shape[0], 0) times = np.empty((velocities.shape[0], 1)) times.fill(time) tidd = np.arange(1, velocities.shape[0] + 1).reshape( velocities.shape[0], 1) self._velocities_data[current_line:, :] = np.concatenate((times, tidd, velocities), axis=1) def outputContactForces(self): """ Outputs contact forces """ if self._broadphase.model().nonSmoothDynamicalSystem().\ topology().indexSetsSize() > 1: time = self._broadphase.model().simulation().nextTime() contact_points = self._io.contactPoints(self._broadphase.model()) if contact_points is not None: current_line = self._cf_data.shape[0] self._cf_data.resize(current_line + contact_points.shape[0], 0) times = np.empty((contact_points.shape[0], 1)) times.fill(time) self._cf_data[current_line:, :] = \ np.concatenate((times, contact_points), axis=1) def outputSolverInfos(self): """ Outputs solver #iterations & precision reached """ time = self._broadphase.model().simulation().nextTime() so = self._broadphase.model().simulation().oneStepNSProblem(0).\ numericsSolverOptions() current_line = self._solv_data.shape[0] self._solv_data.resize(current_line + 1, 0) if so.solverId == Numerics.SICONOS_GENERIC_MECHANICAL_NSGS: iterations = so.iparam[3] precision = so.dparam[2] local_precision = so.dparam[3] elif so.solverId == Numerics.SICONOS_FRICTION_3D_NSGS: iterations = so.iparam[7] precision = so.dparam[1] local_precision = 0. # maybe wrong for others else: iterations = so.iparam[1] precision = so.dparam[1] local_precision = so.dparam[2] self._solv_data[current_line, :] = [time, iterations, precision, local_precision] def addMeshFromString(self, name, shape_data): """ Add a mesh shape from a string. Accepted format : mesh encoded in VTK .vtp format """ if name not in self._ref: shape = self._ref.create_dataset(name, (1,), dtype=h5py.new_vlen(str)) shape[:] = shape_data shape.attrs['id'] = self._number_of_shapes shape.attrs['type'] = 'vtp' self._shapeid[name] = shape.attrs['id'] self._number_of_shapes += 1 def addMeshFromFile(self, name, filename): """ Add a mesh shape from a file. Accepted format : .stl or mesh encoded in VTK .vtp format """ if name not in self._ref: if os.path.splitext(filename)[-1][1:] == 'stl': reader = vtk.vtkSTLReader() reader.SetFileName(filename) reader.Update() with tmpfile() as tmpf: writer=vtk.vtkXMLPolyDataWriter() writer.SetInputData(reader.GetOutput()) writer.SetFileName(tmpf[1]) writer.Write() shape_data = str_of_file(tmpf[1]) else: assert os.path.splitext(filename)[-1][1:] == 'vtp' shape_data = str_of_file(filename) self.addMeshShapeFromString(name, shape_data) def addBRepFromString(self, name, shape_data): """ Add a brep contained in a string. """ if name not in self._ref: shape = self._ref.create_dataset(name, (1,), dtype=h5py.new_vlen(str)) if type(shape_data) == str: # raw str shape[:] = shape_data else: # __getstate__ as with pythonocc shape[:] = shape_data[0] shape.attrs['occ_indx'] = shape_data[1] shape.attrs['id'] = self._number_of_shapes shape.attrs['type'] = 'brep' self._shapeid[name] = shape.attrs['id'] self._number_of_shapes += 1 def addOccShape(self, name, occ_shape): """ Add an OpenCascade TopoDS_Shape """ if name not in self._ref: from OCC.STEPControl import STEPControl_Writer, STEPControl_AsIs # step format is used for the storage. step_writer = STEPControl_Writer() step_writer.Transfer(occ_shape, STEPControl_AsIs) shape_data = None with tmpfile() as tmpf: status = step_writer.Write(tmpf[1]) tmpf[0].flush() shape_data = str_of_file(tmpf[1]) shape = self._ref.create_dataset(name, (1,), dtype=h5py.new_vlen(str)) shape[:] = shape_data shape.attrs['id'] = self._number_of_shapes shape.attrs['type'] = 'step' self._shapeid[name] = shape.attrs['id'] self._number_of_shapes += 1 def addShapeDataFromFile(self, name, filename): """ Add shape data from a file. """ if name not in self._ref: shape = self._ref.create_dataset(name, (1,), dtype=h5py.new_vlen(str)) shape[:] = str_of_file(filename) shape.attrs['id'] = self._number_of_shapes try: shape.attrs['type'] = os.path.splitext(filename)[1][1:] except: shape.attrs['type'] = 'unknown' self._shapeid[name] = shape.attrs['id'] self._number_of_shapes += 1 def addContactFromBRep(self, name, brepname, contact_type, index, collision_group=0, associated_shape=None): """ Add contact reference from a previously added brep. """ if name not in self._ref: shape = self._ref.create_dataset(name, (1,), dtype=h5py.new_vlen(str)) shape.attrs['id'] = self._number_of_shapes shape.attrs['type'] = 'brep' shape.attrs['contact'] = contact_type shape.attrs['brep'] = brepname shape.attrs['index'] = index if associated_shape is not None: shape.attrs['associated_shape'] = associated_shape self._shapeid[name] = shape.attrs['id'] self._number_of_shapes += 1 def addContactFromOccShape(self, name, occ_shape_name, contact_type, index, collision_group=0, associated_shape=None): """ Add contact reference from a previously added brep. """ if name not in self._ref: shape = self._ref.create_dataset(name, (1,), dtype=h5py.new_vlen(str)) shape.attrs['id'] = self._number_of_shapes shape.attrs['type'] = 'step' shape.attrs['contact'] = contact_type shape.attrs['step'] = occ_shape_name shape.attrs['index'] = index if associated_shape is not None: shape.attrs['associated_shape'] = associated_shape self._shapeid[name] = shape.attrs['id'] self._number_of_shapes += 1 def addConvexShape(self, name, points): """ Add a convex shape defined by a list of points. """ if name not in self._ref: apoints = np.array(points) shape = self._ref.create_dataset(name, (apoints.shape[0], apoints.shape[1])) shape[:] = points[:] shape.attrs['type'] = 'convex' shape.attrs['id'] = self._number_of_shapes self._shapeid[name] = shape.attrs['id'] self._number_of_shapes += 1 def addPrimitiveShape(self, name, primitive, params): """ Add a primitive shape. """ if name not in self._ref: shape = self._ref.create_dataset(name, (1, len(params))) shape.attrs['id'] = self._number_of_shapes shape.attrs['type'] = 'primitive' shape.attrs['primitive'] = primitive shape[:] = params self._shapeid[name] = shape.attrs['id'] self._number_of_shapes += 1 def addObject(self, name, shapes, translation, orientation=[1, 0, 0, 0], velocity=[0, 0, 0, 0, 0, 0], mass=0, inertia=None): """ Add an object with associated shapes. Contact detection is defined by a list of contactors. The initial translation is mandatory : [x, y z]. If the mass is zero this is a static object. """ if len(orientation) == 2: # axis + angle axis = orientation[0] assert len(axis) == 3 angle = orientation[1] assert type(angle) is float n = sin(angle / 2.) / np.linalg.norm(axis) ori = [cos(angle / 2.), axis[0] * n, axis[1] * n, axis[2] * n] else: # a given quaternion ori = orientation if name not in self._input: obj = group(self._input, name) obj.attrs['mass'] = mass obj.attrs['translation'] = translation obj.attrs['orientation'] = ori obj.attrs['velocity'] = velocity if inertia is not None: obj.attrs['inertia'] = inertia for num, shape in enumerate(shapes): dat = data(obj, '{0}-{1}'.format(shape.name, num), 0) dat.attrs['name'] = shape.name if hasattr(shape, 'group'): dat.attrs['group'] = shape.group if hasattr(shape, 'parameters') and \ shape.parameters is not None: dat.attrs['parameters'] = shape.parameters dat.attrs['translation'] = shape.translation dat.attrs['orientation'] = shape.orientation if mass == 0: obj.attrs['id'] = - (self._number_of_static_objects + 1) self._number_of_static_objects += 1 else: obj.attrs['id'] = (self._number_of_dynamic_objects + 1) self._number_of_dynamic_objects += 1 def addNewtonImpactFrictionNSL(self, name, mu, e=0, collision_group1=0, collision_group2=0): """ Add a nonsmooth law for contact between 2 groups. Only NewtonImpactFrictionNSL are supported. name is a user identifiant and must be unique, mu is the coefficient of friction, e is the coefficient of restitution on the contact normal, gid1 and gid2 define the group identifiants. """ if name not in self._nslaws: nslaw = self._nslaws.create_dataset(name, (0,)) nslaw.attrs['type'] = 'NewtonImpactFrictionNSL' nslaw.attrs['mu'] = mu nslaw.attrs['e'] = e nslaw.attrs['gid1'] = collision_group1 nslaw.attrs['gid2'] = collision_group2 def addJoint(self, name, object1, object2=None, pivot_point=[0, 0, 0], axis=[0, 1, 0], joint_class='PivotJointR'): """ add a pivot joint between two objects """ if name not in self.joints(): joint = self.joints().create_dataset(name, (0,)) joint.attrs['object1'] = object1 if object2 is not None: joint.attrs['object2'] = object2 joint.attrs['type'] = joint_class joint.attrs['pivot_point'] = pivot_point joint.attrs['axis'] = axis def run(self, with_timer=False, time_stepping=None, space_filter=None, body_class=None, shape_class=None, face_class=None, edge_class=None, length_scale=1, t0=0, T=10, h=0.0005, multipoints_iterations=True, theta=0.50001, Newton_max_iter=20, set_external_forces=None, solver=Numerics.SICONOS_FRICTION_3D_NSGS, itermax=100000, tolerance=1e-8): """ Run a simulation from inputs in hdf5 file. parameters are: with_timer : use a timer for log output (default False) length_scale : gravity is multiplied by this factor. 1. for meters (default). 1./100 for centimeters. This parameter may be needed for small objects because of Bullet collision margin (0.04). t0 : starting time (default 0) T : end time (default 10) h : timestep (default 0.0005) multiPointIterations : use bullet "multipoint iterations" (default True) theta : parameter for Moreau-Jean OSI (default 0.50001) Newton_max_iter : maximum number of iterations for integrator Newton loop (default 20) set_external_forces : method for external forces (default earth gravity) solver : default Numerics.SICONOS_FRICTION_3D_NSGS itermax : maximum number of iteration for solver tolerance : friction contact solver tolerance """ from Siconos.Kernel import \ Model, MoreauJeanOSI, TimeDiscretisation,\ GenericMechanical, FrictionContact, NewtonImpactFrictionNSL from Siconos.Numerics import SICONOS_FRICTION_3D_AlartCurnierNewton from Siconos.Mechanics.ContactDetection.Bullet import \ btConvexHullShape, btCollisionObject, \ btBoxShape, btQuaternion, btTransform, btConeShape, \ BulletSpaceFilter, cast_BulletR, \ BulletWeightedShape, BulletDS, BulletTimeStepping if set_external_forces is not None: self._set_external_forces = set_external_forces if time_stepping is None: time_stepping = BulletTimeStepping if space_filter is None: space_filter = BulletSpaceFilter # Model # model = Model(t0, T) # (1) OneStepIntegrators joints = list(self.joints()) self._osi = MoreauJeanOSI(theta) # (2) Time discretisation -- timedisc = TimeDiscretisation(t0, h) if len(joints) > 0: osnspb = GenericMechanical(SICONOS_FRICTION_3D_AlartCurnierNewton) else: osnspb = FrictionContact(3, solver) osnspb.numericsSolverOptions().iparam[0] = itermax osnspb.numericsSolverOptions().dparam[0] = tolerance osnspb.setMaxSize(16384) osnspb.setMStorageType(1) #osnspb.setNumericsVerboseMode(False) # keep previous solution osnspb.setKeepLambdaAndYState(True) # (5) broadphase contact detection self._broadphase = space_filter(model) if not multipoints_iterations: print(""" ConvexConvexMultipointIterations and PlaneConvexMultipointIterations are unset """) else: if hasattr(self._broadphase, 'collisionConfiguration'): self._broadphase.collisionConfiguration().\ setConvexConvexMultipointIterations() self._broadphase.collisionConfiguration().\ setPlaneConvexMultipointIterations() # (6) Simulation setup with (1) (2) (3) (4) (5) simulation = time_stepping(timedisc) simulation.insertIntegrator(self._osi) simulation.insertNonSmoothProblem(osnspb) simulation.setNewtonMaxIteration(Newton_max_iter) k = 1 self.importScene(body_class, shape_class, face_class, edge_class) model.initialize(simulation) self.outputStaticObjects() self.outputDynamicObjects() while simulation.hasNextEvent(): print ('step', k) log(self._broadphase.buildInteractions, with_timer)\ (model.currentTime()) log(simulation.computeOneStep, with_timer)() log(self.outputDynamicObjects, with_timer)() log(self.outputVelocities, with_timer)() log(self.outputContactForces, with_timer)() log(self.outputSolverInfos, with_timer)() log(simulation.nextStep, with_timer)() log(self._out.flush)() print ('') k += 1