-
Notifications
You must be signed in to change notification settings - Fork 0
/
Blender to NC.py
228 lines (201 loc) · 6.9 KB
/
Blender to NC.py
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
216
217
218
219
220
221
222
223
224
225
226
227
228
# Numeric Control exporter for Blender
# Released to the Public Domain by Paul Spooner
# Designed for driving my laser engraver
# Exports only the mesh (with modifiers applied) of the active object
import bpy
from bpy import context as C
from pathlib import Path
from math import sqrt
JOGSPD = 1200 # laser traverse speed
LSRSPD = 700 # laser burn speed
LSRPOW = 255 # laser burn power (out of 255)
LaserPowerFromBevelWeight = True
LSRPOW = 188
LOOPDIV = 2 # fraction of points for loops, to speed up optimization
VERBOSE = False
ob = C.active_object
obname = ob.name
bpy.ops.object.duplicate()
bpy.ops.object.convert()
dta = C.active_object.data
bpy.ops.object.delete()
C.view_layer.objects.active = ob
ob.select_set(True)
savefile = f'//{obname}_bl.nc'
# store the edges as vert index pair tuples
edges = [(e.vertices[0], e.vertices[1], e.bevel_weight) for e in dta.edges]
# store the vertices as x,y tuples
verts = [[v.co[0], v.co[1]] for v in dta.vertices]
# store the edges connected to each vertex
for i, e in enumerate(edges):
verts[e[0]].append(i)
verts[e[1]].append(i)
# build an array for finding any "corner" verticies.
# These are verticies which have anything other than 2 edges connected to them.
vertedgecounts = {}
def vertfound(i, d):
if i in d: d[i] += 1
else: d[i] = 1
vertconnections = {}
def vertedge(i, c, d):
if i in d: d[i].append(c)
else: d[i] = [c]
for e in edges:
for id in (0,1): vertfound(e[id], vertedgecounts)
for id in (0,1):
connec = e[abs(id-1)]
vertedge(e[id], connec, vertconnections)
cornerverts = set()
for vt in vertedgecounts:
ct = vertedgecounts[vt]
if ct != 2: cornerverts.add(vt)
# build segments
segments = []
for corner in cornerverts:
edgecounts = vertedgecounts[corner]
while (corner in vertconnections) and (len(vertconnections[corner]) > 0):
newsegment = [corner]
thisvert = corner
while True:
nextvert = vertconnections[thisvert].pop()
if len(vertconnections[thisvert]) == 0:
del(vertconnections[thisvert])
vertconnections[nextvert].remove(thisvert)
newsegment.append(nextvert)
thisvert = nextvert
if thisvert in cornerverts:
if len(vertconnections[thisvert]) == 0:
del(vertconnections[thisvert])
break
if len(vertconnections[thisvert]) == 0:
print("corner counting went wrong")
raise
segments.append(newsegment)
if VERBOSE: print(obname)
#print(edges)
if VERBOSE: print(len(edges))
#print(verts)
if VERBOSE: print(len(verts))
#print(cornerverts)
#print(vertconnections)
#print(segments)
loops = []
# all of the verts should have exactly 2 connections now
while len(vertconnections):
loopvert, connections = vertconnections.popitem()
vertconnections[loopvert] = connections
newloop = [loopvert]
thisvert = loopvert
while True:
# print(thisvert)
nextvert = vertconnections[thisvert].pop()
del(vertconnections[thisvert])
if nextvert == loopvert:
break
vertconnections[nextvert].remove(thisvert)
newloop.append(nextvert)
thisvert = nextvert
loops.append(newloop)
#print(vertconnections)
#print(loops)
# generate the point search list
pointstosearch = []
def grabpointdata(pid, group = 0):
pointdata = {}
pointdata['P'] = pid
coords = verts[pid]
pointdata['X'] = coords[0]
pointdata['Y'] = coords[1]
if group: pointdata['G'] = group
return pointdata
for seg in segments:
for i in (0,-1): pointstosearch.append(grabpointdata(seg[i], seg))
for loop in loops:
# search only every other point to reduce the load on the optimization code.
LoopPointDivisor = 2
for i in range(len(loop)//LOOPDIV): pointstosearch.append(grabpointdata(loop[i*LOOPDIV], loop))
# Generate the header, and initialize the machine
OutString = "(Gcode generated by Tryop Blender Exporter)\n"
# G20 is inches, G21 is mm
OutString += "G21\n"
# G90 is absolute, G91 is incremental
OutString += "G90\n"
# M05 is spindle off
# M03 is spindle on, s is the spindle speed (or laser power in this case), from 0 to 255
OutString += "M03 S0\n"
# G00 is rapid move
OutString += f"G00 X0 Y0 F{JOGSPD}\n"
def dist(p1,p2):
return sqrt((p1["X"]-p2["X"])**2 + (p1["Y"]-p2["Y"])**2)
X = 0.
Y = 0.
curpos = {}
curpos['X'] = X
curpos['Y'] = Y
def close(p1): return dist(p1, curpos)
while len(pointstosearch):
pointstosearch.sort(key=close)
# if VERBOSE: print(pointstosearch)
curpos = pointstosearch[0]
pidx = curpos['P']
grp = curpos['G']
to_remove = []
for pnt in pointstosearch:
if pnt['G'] == grp: to_remove.append(pnt)
for pnt in to_remove:
pointstosearch.remove(pnt)
# re-order the group
if grp in loops:
grpidx = grp.index(pidx)
grp = grp[grpidx:] + grp[:grpidx]
grp.append(grp[0])
else:
if grp[-1] == pidx:
grp.reverse()
if VERBOSE: print(grp)
X = curpos['X']
Y = curpos['Y']
# for each segment or loop, jog to the start, turn on the laser, complete the path, and turn off again.
OutString += f"G00 X{X:.2f} Y{Y:.2f} F{JOGSPD}\n"
# M03 is spindle on, s is the spindle speed (or laser power in this case), from 0 to 255
if LaserPowerFromBevelWeight:
# Laser power is not uniform, so do nothing here
pass
else:
OutString += f"M03 S{LSRPOW}\n"
prevvertid = pidx
if LaserPowerFromBevelWeight:
for curidx in grp[1:]:
curpos = grabpointdata(curidx)
X = curpos['X']
Y = curpos['Y']
# G01 is linear motion
SegmentPower = LSRPOW
for eid in verts[curidx][2:]:
edge = edges[eid]
if prevvertid in edge[:2]:
bevelWeight = edge[2]
if bevelWeight > 0:
SegmentPower = int(LSRPOW*bevelWeight)
OutString += f"G01 X{X:.2f} Y{Y:.2f} F{LSRSPD} S{SegmentPower}\n"
prevvertid = curidx
else:
for curidx in grp[1:]:
curpos = grabpointdata(curidx)
X = curpos['X']
Y = curpos['Y']
# G01 is linear motion
OutString += f"G01 X{X:.2f} Y{Y:.2f} F{LSRSPD}\n"
# when done, turn the laser back off
OutString += "M03 S0\n"
# when all the engraving is done, turn the laser off and jog back to the origin
OutString += "M05\n"
OutString += f"G00 X0 Y0 F{JOGSPD}\n"
fp = Path(bpy.path.abspath(savefile))
try:
f = open(fp, 'w')
f.write(OutString)
f.close()
if VERBOSE: print('File Saved')
except:
if VERBOSE: print('exception found while saving file')