File: parallel_finish_zig.py

package info (click to toggle)
opencamlib 2023.01.11-6
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 29,800 kB
  • sloc: cpp: 8,722; python: 5,530; sh: 604; javascript: 310; makefile: 209
file content (215 lines) | stat: -rw-r--r-- 8,448 bytes parent folder | download | duplicates (2)
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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215

# simple parallel finish toolpath example
# Anders Wallin 2014-02-23

import time

from opencamlib import ocl, camvtk
import ngc_writer # G-code output is produced by this module

# create a simple "Zig" pattern where we cut only in one direction.
# the first line is at ymin
# the last line is at ymax
def YdirectionZigPath(xmin,xmax,ymin,ymax,Ny):
    paths = []
    dy = float(ymax-ymin)/(Ny-1)  # the y step-over
    for n in range(0,Ny):
        path = ocl.Path() 
        y = ymin+n*dy              # current y-coordinate 
        if (n==Ny-1):
            assert( y==ymax)
        elif (n==0):
            assert( y==ymin)
        p1 = ocl.Point(xmin,y,0)   # start-point of line
        p2 = ocl.Point(xmax,y,0)   # end-point of line
        l = ocl.Line(p1,p2)        # line-object
        path.append( l )           # add the line to the path
        paths.append(path)
    return paths

# run the actual drop-cutter algorithm
def adaptive_path_drop_cutter(surface, cutter, paths):
    apdc = ocl.AdaptivePathDropCutter()
    apdc.setSTL(surface)
    apdc.setCutter(cutter)
    apdc.setSampling(0.04)      # maximum sampling or "step-forward" distance
                                # should be set so that we don't loose any detail of the STL model
                                # i.e. this number should be similar or smaller than the smallest triangle
    apdc.setMinSampling(0.01) # minimum sampling or step-forward distance
                                # the algorithm subdivides "steep" portions of the toolpath
                                # until we reach this limit.
    # 0.0008
    cl_paths=[]
    n_points=0
    for path in paths:
        apdc.setPath( path )
        apdc.run()
        cl_points = apdc.getCLPoints()
        n_points = n_points + len( cl_points )
        cl_paths.append( apdc.getCLPoints() )
    return (cl_paths, n_points)

# this could be any source of triangles
# as long as it produces an ocl.STLSurf() we can work with
def STLSurfaceSource(filename):
    stl = camvtk.STLSurf(filename)
    polydata = stl.src.GetOutput()
    s = ocl.STLSurf()
    camvtk.vtkPolyData2OCLSTL(polydata, s)
    return s

# filter a single path
def filter_path(path,tol):
    f = ocl.LineCLFilter()
    f.setTolerance(tol)
    for p in path:
        p2 = ocl.CLPoint(p.x,p.y,p.z)
        f.addCLPoint(p2)
    f.run()
    return  f.getCLPoints()

# to reduce the G-code size we filter here. (this is not strictly required and could be omitted)
# we could potentially detect G2/G3 arcs here, if there was a filter for that.
# idea:
# if a path in the raw toolpath has three points (p1,p2,p3) 
# and point p2 lies within tolerance of the straight line p1-p3
# then we simplify the path to (p1,p3) 
def filterCLPaths(cl_paths, tolerance=0.001):
    cl_filtered_paths = []
    t_before = time.time()
    n_filtered=0
    for cl_path in cl_paths:
        cl_filtered = filter_path(cl_path,tol)
        
        n_filtered = n_filtered + len(cl_filtered)
        cl_filtered_paths.append(cl_filtered)
    return (cl_filtered_paths, n_filtered)

# this uses ngc_writer and writes G-code to stdout or a file
def write_zig_gcode_file(filename, n_triangles, t1,n1,tol,t2,n2, toolpath):
    ngc_writer.clearance_height= 5 # XY rapids at this height
    ngc_writer.feed_height = 3     # use z plunge-feed below this height
    ngc_writer.feed = 200          # feedrate 
    ngc_writer.plunge_feed = 100   # plunge feedrate
    ngc_writer.metric = False      # metric/inch flag
    ngc_writer.comment( " OpenCAMLib %s" % ocl.version() ) # git version-tag
    # it is probably useful to include this in all g-code output, so that bugs/problems can be tracked
    
    ngc_writer.comment( " STL surface: %s" % filename )
    ngc_writer.comment( "   triangles: %d" % n_triangles )
    ngc_writer.comment( " OpenCAMLib::AdaptivePathDropCutter run took %.2f s" % t1 )
    ngc_writer.comment( " got %d raw CL-points " % n1 )
    ngc_writer.comment( " filtering to tolerance %.4f " % ( tol )  )
    ngc_writer.comment( " got %d filtered CL-points. Filter done in %.3f s " % ( n2 , t2 ) )
    ngc_writer.preamble()
    # a "Zig" or one-way parallel finish path
    # 1) lift to clearance height
    # 2) XY rapid to start of path
    # 3) plunge to correct z-depth
    # 4) feed along path until end 
    for path in toolpath:
        ngc_writer.pen_up()  
        first_pt = path[0]
        ngc_writer.xy_rapid_to( first_pt.x, first_pt.y )
        ngc_writer.pen_down( first_pt.z )
        for p in path[1:]:
            ngc_writer.line_to(p.x,p.y,p.z)
    ngc_writer.postamble() # end of program

def vtk_visualize_parallel_finish_zig(stlfile, toolpaths):
	myscreen = camvtk.VTKScreen()
	stl = camvtk.STLSurf(stlfile)
	myscreen.addActor(stl)
	stl.SetSurface() # try also SetWireframe()
	stl.SetColor(camvtk.cyan)
	myscreen.camera.SetPosition(15, 13, 7)
	myscreen.camera.SetFocalPoint(5, 5, 0)
	
	rapid_height= 5 # XY rapids at this height
	feed_height = 3 
	rapidColor = camvtk.pink
	XYrapidColor = camvtk.green
	plungeColor = camvtk.red
	feedColor = camvtk.yellow
	# zig path algorithm:
	# 1) lift to clearance height
	# 2) XY rapid to start of path
	# 3) plunge to correct z-depth
	# 4) feed along path until end
	pos = ocl.Point(0,0,0) # keep track of the current position of the tool
	first = True 
	for path in toolpaths:
		first_pt = path[0]
		if (first == True): # green sphere at path start
			myscreen.addActor( camvtk.Sphere(center=(first_pt.x,first_pt.y,rapid_height) , radius=0.1, color=camvtk.green) ) 
			pos = ocl.Point(first_pt.x,first_pt.y,first_pt.z) # at start of program, assume we have already a rapid move here
			first = False
		else: # not the very first move
			# retract up to rapid_height
			myscreen.addActor( camvtk.Line(p1=( pos.x,pos.y,pos.z),p2=(pos.x,pos.y,feed_height),color=plungeColor) )
			myscreen.addActor( camvtk.Line(p1=( pos.x,pos.y,feed_height),p2=(pos.x,pos.y,rapid_height),color=rapidColor) )
			# XY rapid into position
			myscreen.addActor( camvtk.Line(p1=( pos.x,pos.y,rapid_height),p2=( first_pt.x,first_pt.y,rapid_height),color=XYrapidColor) )
			pos = ocl.Point(first_pt.x,first_pt.y,first_pt.z)
		
		# rapid down to the feed_height
		myscreen.addActor( camvtk.Line(p1=( pos.x,pos.y,rapid_height),p2=(pos.x,pos.y,feed_height),color=rapidColor) )
		# feed down to CL
		myscreen.addActor( camvtk.Line(p1=( pos.x,pos.y,feed_height),p2=(pos.x,pos.y,pos.z),color=plungeColor) )
		
		# feed along the path
		for p in path[1:]:
			myscreen.addActor( camvtk.Line(p1=( pos.x,pos.y,pos.z),p2=(p.x,p.y,p.z),color=feedColor) )
			pos = ocl.Point(p.x,p.y,p.z)
		
	# END retract up to rapid_height
	myscreen.addActor( camvtk.Line(p1=( pos.x,pos.y,pos.z),p2=(pos.x,pos.y,feed_height),color=plungeColor) )
	myscreen.addActor( camvtk.Line(p1=( pos.x,pos.y,feed_height),p2=(pos.x,pos.y,rapid_height),color=rapidColor) )
	myscreen.addActor( camvtk.Sphere(center=(pos.x,pos.y,rapid_height) , radius=0.1, color=camvtk.red) ) 

	camvtk.drawArrows(myscreen,center=(-0.5,-0.5,-0.5)) # XYZ coordinate arrows
	camvtk.drawOCLtext(myscreen)
	myscreen.render()    
	myscreen.iren.Start()


if __name__ == "__main__":     
    stlfile = "../../../stl/gnu_tux_mod.stl"
    surface = STLSurfaceSource(stlfile)
    
    
    # choose a cutter for the operation:
    # http://www.anderswallin.net/2011/08/opencamlib-cutter-shapes/
    diameter=0.25
    length=5
    #cutter = ocl.BallCutter(diameter, length)
    cutter = ocl.CylCutter(diameter, length)
    #corner_radius = 0.05
    #cutter = ocl.BullCutter(diameter, corner_radius, length)
    #angle = math.pi/4
    #cutter = ocl.ConeCutter(diameter, angle, length)
    #cutter = cutter.offsetCutter( 0.4 )
 
    # toolpath is contained in this simple box
    ymin=0
    ymax=12
    xmin=0
    xmax=10
    Ny=40  # number of lines in the y-direction
    
    paths = YdirectionZigPath(xmin,xmax,ymin,ymax,Ny)

    # now project onto the STL surface
    t_before = time.time()
    (raw_toolpath, n_raw) = adaptive_path_drop_cutter(surface,cutter,paths)
    t1 = time.time()
    
    # filter raw toolpath to reduce size
    tol = 0.001    
    (toolpaths, n_filtered) = filterCLPaths(raw_toolpath, tolerance=0.001)    
    t2 = time.time()-t_before
    
    # output a g-code file
    write_zig_gcode_file( stlfile, surface.size() , t1, n_raw ,tol,t2,n_filtered, toolpaths )
    # and/or visualize with VTK
    vtk_visualize_parallel_finish_zig(stlfile, toolpaths)