BaseCutter.py 7.25 KB
Newer Older
1 2 3 4 5
# -*- coding: utf-8 -*-
"""
$Id$

Copyright 2008-2010 Lode Leroy
sumpfralle's avatar
sumpfralle committed
6
Copyright 2010-2011 Lars Kruse <devel@sumpfralle.de>
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

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 <http://www.gnu.org/licenses/>.
"""

lode_leroy's avatar
lode_leroy committed
24

25
from pycam.Geometry import IDGenerator
26
from pycam.Geometry.Point import Point
27
from pycam.Geometry.utils import number, INFINITE, epsilon
sumpfralle's avatar
sumpfralle committed
28 29
from pycam.Geometry.intersection import intersect_cylinder_point, \
        intersect_cylinder_line
30
import uuid
lode_leroy's avatar
lode_leroy committed
31

32

33 34
class BaseCutter(IDGenerator):

35
    vertical = Point(0, 0, -1)
lode_leroy's avatar
lode_leroy committed
36

37
    def __init__(self, radius, location=None, height=None):
38
        super(BaseCutter, self).__init__()
39 40
        if location is None:
            location = Point(0, 0, 0)
41 42
        if height is None:
            height = 10
43 44
        radius = number(radius)
        self.height = number(height)
lode_leroy's avatar
lode_leroy committed
45
        self.radius = radius
46
        self.radiussq = radius ** 2
47 48
        self.required_distance = 0
        self.distance_radius = self.radius
49
        self.distance_radiussq = self.distance_radius ** 2
50
        self.shape = {}
sumpfralle's avatar
sumpfralle committed
51 52
        self.location = location
        self.moveto(self.location)
53 54
        self.uuid = None
        self.update_uuid()
lode_leroy's avatar
lode_leroy committed
55

56 57 58
    def get_minx(self, start=None):
        if start is None:
            start = self.location
59
        return start.x - self.distance_radius
60

61 62 63
    def get_maxx(self, start=None):
        if start is None:
            start = self.location
64
        return start.x + self.distance_radius
65

66 67 68
    def get_miny(self, start=None):
        if start is None:
            start = self.location
69
        return start.y - self.distance_radius
70

71 72 73
    def get_maxy(self, start=None):
        if start is None:
            start = self.location
74
        return start.y + self.distance_radius
75

76 77 78
    def update_uuid(self):
        self.uuid = uuid.uuid4()

lode_leroy's avatar
lode_leroy committed
79 80 81
    def __repr__(self):
        return "BaseCutter"

82 83 84 85 86 87
    def __cmp__(self, other):
        """ Compare Cutters by shape and size (ignoring the location)
        This function should be overridden by subclasses, if they describe
        cutters with a shape depending on more than just the radius.
        See the ToroidalCutter for an example.
        """
88
        if self.__class__ == other.__class__:
89 90 91 92 93
            return cmp(self.radius, other.radius)
        else:
            # just return a string comparison
            return cmp(str(self), str(other))

94 95
    def set_required_distance(self, value):
        if value >= 0:
96
            self.required_distance = number(value)
97 98
            self.distance_radius = self.radius + self.get_required_distance()
            self.distance_radiussq = self.distance_radius * self.distance_radius
99
            self.update_uuid()
100 101 102 103

    def get_required_distance(self):
        return self.required_distance

104
    def moveto(self, location):
105
        # "moveto" is used for collision detection calculation.
lode_leroy's avatar
lode_leroy committed
106
        self.location = location
107
        for shape, set_pos_func in self.shape.values():
108
            set_pos_func(location.x, location.y, location.z)
lode_leroy's avatar
lode_leroy committed
109

110
    def intersect(self, direction, triangle, start=None):
111 112
        raise NotImplementedError("Inherited class of BaseCutter does not " \
                + "implement the required function 'intersect'.")
lode_leroy's avatar
lode_leroy committed
113

114 115 116
    def drop(self, triangle, start=None):
        if start is None:
            start = self.location
lode_leroy's avatar
lode_leroy committed
117
        # check bounding box collision
118
        if self.get_minx(start) > triangle.maxx + epsilon:
lode_leroy's avatar
lode_leroy committed
119
            return None
120
        if self.get_maxx(start) < triangle.minx - epsilon:
lode_leroy's avatar
lode_leroy committed
121
            return None
122
        if self.get_miny(start) > triangle.maxy + epsilon:
lode_leroy's avatar
lode_leroy committed
123
            return None
124
        if self.get_maxy(start) < triangle.miny - epsilon:
lode_leroy's avatar
lode_leroy committed
125 126
            return None

lode_leroy's avatar
lode_leroy committed
127
        # check bounding circle collision
128
        c = triangle.middle
129
        if (c.x - start.x) ** 2 + (c.y - start.y) ** 2 \
130
                > (self.distance_radiussq + 2 * self.distance_radius \
131
                    * triangle.radius + triangle.radiussq) + epsilon:
lode_leroy's avatar
lode_leroy committed
132 133
            return None

sumpfralle's avatar
sumpfralle committed
134
        return self.intersect(BaseCutter.vertical, triangle, start=start)[0]
lode_leroy's avatar
lode_leroy committed
135

136 137 138
    def intersect_circle_triangle(self, direction, triangle, start=None):
        (cl, ccp, cp, d) = self.intersect_circle_plane(direction, triangle,
                start=start)
139
        if cp and triangle.is_point_inside(cp):
140 141 142
            return (cl, d, cp)
        return (None, INFINITE, None)

143 144 145
    def intersect_circle_vertex(self, direction, point, start=None):
        (cl, ccp, cp, l) = self.intersect_circle_point(direction, point,
                start=start)
146 147
        return (cl, l, cp)

148 149 150
    def intersect_circle_edge(self, direction, edge, start=None):
        (cl, ccp, cp, l) = self.intersect_circle_line(direction, edge,
                start=start)
151 152 153
        if cp:
            # check if the contact point is between the endpoints
            m = cp.sub(edge.p1).dot(edge.dir)
154
            if (m < -epsilon) or (m > edge.len + epsilon):
155 156 157
                return (None, INFINITE, cp)
        return (cl, l, cp)

158 159 160 161 162
    def intersect_cylinder_point(self, direction, point, start=None):
        if start is None:
            start = self.location
        (ccp, cp, l) = intersect_cylinder_point(
                start.sub(self.location).add(self.center), self.axis,
163 164 165
                self.distance_radius, self.distance_radiussq, direction, point)
        # offset intersection
        if ccp:
166
            cl = cp.add(start.sub(ccp))
167 168 169
            return (cl, ccp, cp, l)
        return (None, None, None, INFINITE)

170 171 172 173 174 175
    def intersect_cylinder_vertex(self, direction, point, start=None):
        if start is None:
            start = self.location
        (cl, ccp, cp, l) = self.intersect_cylinder_point(direction, point,
                start=start)
        if ccp and ccp.z < start.sub(self.location).add(self.center).z:
176 177 178
            return (None, INFINITE, None)
        return (cl, l, cp)

179 180 181 182 183
    def intersect_cylinder_line(self, direction, edge, start=None):
        if start is None:
            start = self.location
        (ccp, cp, l) = intersect_cylinder_line(
                start.sub(self.location).add(self.center), self.axis,
184 185 186
                self.distance_radius, self.distance_radiussq, direction, edge)
        # offset intersection
        if ccp:
187
            cl = start.add(cp.sub(ccp))
188 189 190
            return (cl, ccp, cp, l)
        return (None, None, None, INFINITE)

191 192 193 194 195
    def intersect_cylinder_edge(self, direction, edge, start=None):
        if start is None:
            start = self.location
        (cl, ccp, cp, l) = self.intersect_cylinder_line(direction, edge,
                start=start)
196 197 198
        if not ccp:
            return (None, INFINITE, None)
        m = cp.sub(edge.p1).dot(edge.dir)
199
        if (m < -epsilon) or (m > edge.len + epsilon):
200
            return (None, INFINITE, None)
201
        if ccp.z < start.sub(self.location).add(self.center).z:
202 203 204
            return (None, INFINITE, None)
        return (cl, l, cp)