1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# -*- coding: utf-8 -*-
"""
$Id$
Copyright 2010 Lars Kruse <devel@sumpfralle.de>
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/>.
"""
from pycam.Geometry.Point import Point
from pycam.Geometry.utils import epsilon
import math
GRID_DIRECTION_X = 0
GRID_DIRECTION_Y = 1
GRID_DIRECTION_XY = 2
MILLING_STYLE_IGNORE = 0
MILLING_STYLE_CONVENTIONAL = 1
MILLING_STYLE_CLIMB = 2
START_X = 0x1
START_Y = 0x2
START_Z = 0x4
def isiterable(obj):
try:
iter(obj)
return True
except TypeError:
return False
def floatrange(start, end, inc=None, steps=None, reverse=False):
if reverse:
start, end = end, start
# 'inc' will be adjusted below anyway
if abs(start - end) < epsilon:
yield start
elif inc is None and steps is None:
raise ValueError("floatrange: either 'inc' or 'steps' must be provided")
elif (not steps is None) and (steps < 2):
raise ValueError("floatrange: 'steps' must be greater than 1")
else:
# the input is fine
# reverse increment, if it does not suit start/end
if steps is None:
if ((end - start) > 0) != (inc > 0):
inc = -inc
steps = int(math.ceil(float(end - start) / inc) + 1)
inc = float(end - start) / (steps - 1)
for index in range(steps):
yield start + inc * index
def get_fixed_grid_line(start, end, line_pos, z, step_width=None,
grid_direction=GRID_DIRECTION_X):
if step_width is None:
# useful for PushCutter operations
steps = (start, end)
elif isiterable(step_width):
steps = step_width
else:
steps = floatrange(start, end, inc=step_width)
if grid_direction == GRID_DIRECTION_X:
get_point = lambda pos: Point(pos, line_pos, z)
else:
get_point = lambda pos: Point(line_pos, pos, z)
for pos in steps:
yield get_point(pos)
def get_fixed_grid_layer(minx, maxx, miny, maxy, z, line_distance,
step_width=None, grid_direction=GRID_DIRECTION_X,
milling_style=MILLING_STYLE_IGNORE, start_position=0):
if grid_direction == GRID_DIRECTION_XY:
raise ValueError("'get_one_layer_fixed_grid' does not accept XY " \
+ "direction")
# zigzag is only available if the milling
zigzag = (milling_style == MILLING_STYLE_IGNORE)
# If we happen to start at a position that collides with the milling style,
# then we need to move to the closest other corner. Here we decide, which
# would be the best alternative.
def get_alternative_start_position(start):
if (maxx - minx) <= (maxy - miny):
# toggle the X position bit
return start ^ START_X
else:
# toggle the Y position bit
return start ^ START_Y
if grid_direction == GRID_DIRECTION_X:
primary_dir = START_X
secondary_dir = START_Y
else:
primary_dir = START_Y
secondary_dir = START_X
# Determine the starting direction (assuming we begin at the lower x/y
# coordinates.
if milling_style == MILLING_STYLE_IGNORE:
# just move forward - milling style is not important
pass
elif (milling_style == MILLING_STYLE_CLIMB) == (grid_direction == GRID_DIRECTION_X):
if bool(start_position & START_X) == bool(start_position & START_Y):
# we can't start from here - choose an alternative
start_position = get_alternative_start_position(start_position)
elif (milling_style == MILLING_STYLE_CONVENTIONAL) == (grid_direction == GRID_DIRECTION_X):
if bool(start_position & START_X) != bool(start_position & START_Y):
# we can't start from here - choose an alternative
start_position = get_alternative_start_position(start_position)
else:
raise ValueError("Invalid milling style given: %s" % str(milling_style))
# sort out the coordinates (primary/secondary)
if grid_direction == GRID_DIRECTION_X:
start, end = minx, maxx
line_start, line_end = miny, maxy
else:
start, end = miny, maxy
line_start, line_end = minx, maxx
# switch start/end if we move from high to low
if start_position & primary_dir:
start, end = end, start
if start_position & secondary_dir:
line_start, line_end = line_end, line_start
# calculate the line positions
if isiterable(line_distance):
lines = line_distance
else:
lines = floatrange(line_start, line_end, inc=line_distance)
# at the end of the layer we will be on the other side of the 2nd direction
end_position = start_position ^ secondary_dir
# the final position will probably be on the other side (primary)
if not zigzag:
end_position ^= primary_dir
# calculate each line
def get_lines(start, end, end_position):
result = []
for line_pos in lines:
result.append(get_fixed_grid_line(start, end, line_pos, z,
step_width=step_width, grid_direction=grid_direction))
if zigzag:
start, end = end, start
end_position ^= primary_dir
return result, end_position
return get_lines(start, end, end_position)
def get_fixed_grid(bounds, layer_distance, line_distance, step_width=None,
grid_direction=GRID_DIRECTION_X, milling_style=MILLING_STYLE_IGNORE,
start_position=START_Z):
""" Calculate the grid positions for toolpath movements
"""
low, high = bounds.get_absolute_limits()
if isiterable(layer_distance):
layers = layer_distance
elif layer_distance is None:
# useful for DropCutter
layers = [low[2]]
else:
layers = floatrange(low[2], high[2], inc=layer_distance,
reverse=bool(start_position & START_Z))
def get_layers_with_direction(layers):
for layer in layers:
if grid_direction != GRID_DIRECTION_Y:
yield (layer, GRID_DIRECTION_X)
if grid_direction != GRID_DIRECTION_X:
yield (layer, GRID_DIRECTION_Y)
for z, direction in get_layers_with_direction(layers):
result, start_position = get_fixed_grid_layer(low[0], high[0],
low[1], high[1], z, line_distance, step_width=step_width,
grid_direction=direction, milling_style=milling_style,
start_position=start_position)
yield result