# -*- coding: utf-8 -*- """ $Id$ Copyright 2008-2009 Lode Leroy 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 import TransformableContainer, IDGenerator from pycam.Geometry.utils import INFINITE, epsilon from pycam.Geometry.Point import Vector # "Line" is imported later to avoid circular imports #from pycam.Geometry.Line import Line class Plane(IDGenerator, TransformableContainer): __slots__ = ["id", "p", "n"] def __init__(self, point, normal=None): super(Plane, self).__init__() if normal is None: normal = Vector(0, 0, 1) self.p = point self.n = normal if not isinstance(self.n, Vector): self.n = self.n.get_vector() def __repr__(self): return "Plane<%s,%s>" % (self.p, self.n) def __cmp__(self, other): if self.__class__ == other.__class__: if self.p == other.p: return cmp(self.n, other.n) else: return cmp(self.p, other.p) else: return cmp(str(self), str(other)) def copy(self): return self.__class__(self.p.copy(), self.n.copy()) def next(self): yield self.p yield self.n def get_children_count(self): # a plane always consists of two points return 2 def reset_cache(self): # we need to prevent the "normal" from growing norm = self.n.normalized() if norm: self.n = norm def intersect_point(self, direction, point): if (not direction is None) and (direction.norm != 1): # calculations will go wrong, if the direction is not a unit vector direction = direction.normalized() if direction is None: return (None, INFINITE) denom = self.n.dot(direction) if denom == 0: return (None, INFINITE) l = -(self.n.dot(point) - self.n.dot(self.p)) / denom cp = point.add(direction.mul(l)) return (cp, l) def intersect_triangle(self, triangle, counter_clockwise=False): """ Returns the line of intersection of a triangle with a plane. "None" is returned, if: - the triangle does not intersect with the plane - all vertices of the triangle are on the plane The line always runs clockwise through the triangle. """ # don't import Line in the header -> circular import from pycam.Geometry.Line import Line collisions = [] for edge, point in ((triangle.e1, triangle.p1), (triangle.e2, triangle.p2), (triangle.e3, triangle.p3)): cp, l = self.intersect_point(edge.dir, point) # filter all real collisions # We don't want to count vertices double -> thus we only accept # a distance that is lower than the length of the edge. if (not cp is None) and (-epsilon < l < edge.len - epsilon): collisions.append(cp) elif (cp is None) and (self.n.dot(edge.dir) == 0): cp, dist = self.intersect_point(self.n, point) if abs(dist) < epsilon: # the edge is on the plane collisions.append(point) if len(collisions) == 3: # All points of the triangle are on the plane. # We don't return a waterline, as there should be another non-flat # triangle with the same waterline. return None if len(collisions) == 2: collision_line = Line(collisions[0], collisions[1]) # no further calculation, if the line is zero-sized if collision_line.len == 0: return collision_line cross = self.n.cross(collision_line.dir) if (cross.dot(triangle.normal) < 0) == bool(not counter_clockwise): # anti-clockwise direction -> revert the direction of the line collision_line = Line(collision_line.p2, collision_line.p1) return collision_line elif len(collisions) == 1: # only one point is on the plane # This waterline (with zero length) should be of no use. return None else: return None def get_point_projection(self, point): return self.intersect_point(self.n, point)[0] def get_line_projection(self, line): # don't import Line in the header -> circular import from pycam.Geometry.Line import Line proj_p1 = self.get_point_projection(line.p1) proj_p2 = self.get_point_projection(line.p2) return Line(proj_p1, proj_p2)