# -*- coding: utf-8 -*- """ $Id$ Copyright 2010 Lars Kruse This file is part of PyCAM. PyCAM is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. PyCAM is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with PyCAM. If not, see . """ from pycam.Geometry.Triangle import Triangle from pycam.Geometry.utils import number import uuid try: import ode except ImportError: ode = None ShapeCylinder = lambda radius, height: ode.GeomCylinder(None, radius, height) ShapeCapsule = lambda radius, height: \ ode.GeomCapsule(None, radius, height - (2 * radius)) _ode_override_state = None def generate_physics(models, cutter, physics=None): if physics is None: physics = PhysicalWorld() physics.reset() if not isinstance(models, (list, set, tuple)): models = [models] for model in models: physics.add_mesh(model.triangles()) shape_info = cutter.get_shape("ODE") physics.set_drill(shape_info[0], (0.0, 0.0, 0.0)) return physics def is_ode_available(): if not _ode_override_state is None: return _ode_override_state else: if ode is None: return False else: return True def override_ode_availability(state): global _ode_override_state _ode_override_state = state def convert_triangles_to_vertices_faces(triangles): corners = [] faces = [] id_index_map = {} for t in triangles: coords = [] # TODO: check if we need to change the order of points for non-AOI # models as well. for p in (t.p1, t.p3, t.p2): # add the point to the id/index mapping, if necessary if not id_index_map.has_key(p.id): corners.append((p.x, p.y, p.z)) id_index_map[p.id] = len(corners) - 1 coords.append(id_index_map[p.id]) faces.append(coords) return corners, faces def get_parallelepiped_geom(low_points, high_points, space=None): triangles = ( # front side Triangle(low_points[0], low_points[1], high_points[0]), Triangle(low_points[1], high_points[1], high_points[0]), # right side Triangle(low_points[1], low_points[2], high_points[1]), Triangle(low_points[2], high_points[2], high_points[1]), # back side Triangle(low_points[2], low_points[3], high_points[2]), Triangle(low_points[3], high_points[3], high_points[2]), # left side Triangle(low_points[3], low_points[0], high_points[3]), Triangle(low_points[0], high_points[0], high_points[3]), # bottom side Triangle(low_points[1], low_points[0], low_points[2]), Triangle(low_points[3], low_points[2], low_points[0]), # high side Triangle(high_points[0], high_points[1], high_points[2]), Triangle(high_points[2], high_points[3], high_points[0]), ) mesh = ode.TriMeshData() vertices, faces = convert_triangles_to_vertices_faces(triangles) mesh.build(vertices, faces) geom = ode.GeomTriMesh(mesh, space) return geom class PhysicalWorld(object): def __init__(self): self._world = ode.World() self._space = ode.Space() self._obstacles = [] self._contacts = ode.JointGroup() self._drill = None self._drill_offset = None self._collision_detected = False self._dirty = True self.__uuid = None @property def uuid(self): if (self.__uuid is None) or self._dirty: self.__uuid = str(uuid.uuid4()) self._dirty = False return self.__uuid def reset(self): self._world = ode.World() self._space = ode.Space() self._obstacles = [] self._contacts = ode.JointGroup() self._drill = None self._drill_offset = None self._collision_detected = False self._dirty = True def _add_geom(self, geom, position, append=True): body = ode.Body(self._world) body.setPosition(position) body.setGravityMode(False) geom.setBody(body) if append: self._obstacles.append(geom) self._dirty = True def add_mesh(self, triangles, position=None): if position is None: position = (0, 0, 0) mesh = ode.TriMeshData() vertices, faces = convert_triangles_to_vertices_faces(triangles) mesh.build(vertices, faces) geom = ode.GeomTriMesh(mesh, self._space) self._add_geom(geom, position) self._dirty = True def set_drill(self, shape, position): #geom = ode.GeomTransform(self._space) #geom.setOffset(position) #geom.setGeom(shape) #shape.setOffset(position) self._space.add(shape) # sadly PyODE forgets to update the "space" attribute that we need in # the cutters' "extend" functions shape.space = self._space self._add_geom(shape, position, append=False) self._drill_offset = [number(value) for value in position] self._drill = shape self.reset_drill() self._dirty = True def extend_drill(self, diff_x, diff_y, diff_z): try: func = self._drill.extend_shape except ValueError: return func(diff_x, diff_y, diff_z) self._dirty = True def reset_drill(self): try: func = self._drill.reset_shape except ValueError: return func() self._dirty = True def set_drill_position(self, position): if self._drill: position = (position[0] + self._drill_offset[0], position[1] + self._drill_offset[1], position[2] + self._drill_offset[2]) self._drill.setPosition(position) def _get_rays_for_geom(self, geom): """ TODO: this is necessary due to a bug in the trimesh collision detection code of ODE v0.11.1. Remove this as soon as the code is fixed. http://sourceforge.net/tracker/index.php?func=detail&aid=2973876&group_id=24884&atid=382799 """ minz, maxz = geom.getAABB()[-2:] currx, curry = geom.getPosition()[0:2] ray = ode.GeomRay(self._space, maxz-minz) ray.set((currx, curry, maxz), (0.0, 0.0, -1.0)) return [ray] def check_collision(self): # get all drill shapes try: drill_shapes = self._drill.children[:] except AttributeError: drill_shapes = [] drill_shapes.append(self._drill) # add a ray to each shape collision_shapes = [] for drill_shape in drill_shapes: collision_shapes.extend(self._get_rays_for_geom(drill_shape)) collision_shapes.append(drill_shape) # go through all obstacles and check for collisions with a drill shape for body in self._obstacles: for drill_shape in collision_shapes: if ode.collide(drill_shape, body): return True return False def get_space(self): return self._space