|
| 1 | +import bpy |
| 2 | +from bpy import context as C |
| 3 | +from pathlib import Path |
| 4 | +from math import sqrt |
| 5 | + |
| 6 | + |
| 7 | +JOGSPD = 1200 |
| 8 | +LSRSPD = 700 |
| 9 | +LSRPOW = 255 |
| 10 | + |
| 11 | +VERBOSE = False |
| 12 | + |
| 13 | +ob = C.active_object |
| 14 | +obname = ob.name |
| 15 | +bpy.ops.object.duplicate() |
| 16 | +bpy.ops.object.convert() |
| 17 | +dta = C.active_object.data |
| 18 | +bpy.ops.object.delete() |
| 19 | +C.view_layer.objects.active = ob |
| 20 | +ob.select_set(True) |
| 21 | + |
| 22 | +savefile = f'//{obname}_bl.nc' |
| 23 | + |
| 24 | +# store the edges as vert index pair tuples |
| 25 | +edges = [(e.vertices[0], e.vertices[1]) for e in dta.edges] |
| 26 | +# store the vertices as x,y tuples |
| 27 | +verts = [(v.co[0], v.co[1]) for v in dta.vertices] |
| 28 | + |
| 29 | +# build an array for finding any "corner" verticies. |
| 30 | +# These are verticies which have anything other than 2 edges connected to them. |
| 31 | + |
| 32 | +vertedgecounts = {} |
| 33 | +def vertfound(i, d): |
| 34 | + if i in d: d[i] += 1 |
| 35 | + else: d[i] = 1 |
| 36 | +vertconnections = {} |
| 37 | +def vertedge(i, c, d): |
| 38 | + if i in d: d[i].append(c) |
| 39 | + else: d[i] = [c] |
| 40 | +for e in edges: |
| 41 | + for id in (0,1): vertfound(e[id], vertedgecounts) |
| 42 | + for id in (0,1): |
| 43 | + connec = e[abs(id-1)] |
| 44 | + vertedge(e[id], connec, vertconnections) |
| 45 | + |
| 46 | +cornerverts = set() |
| 47 | +for vt in vertedgecounts: |
| 48 | + ct = vertedgecounts[vt] |
| 49 | + if ct != 2: cornerverts.add(vt) |
| 50 | + |
| 51 | +# build segments |
| 52 | +segments = [] |
| 53 | +for corner in cornerverts: |
| 54 | + edgecounts = vertedgecounts[corner] |
| 55 | + while (corner in vertconnections) and (len(vertconnections[corner]) > 0): |
| 56 | + newsegment = [corner] |
| 57 | + thisvert = corner |
| 58 | + while True: |
| 59 | + nextvert = vertconnections[thisvert].pop() |
| 60 | + if len(vertconnections[thisvert]) == 0: |
| 61 | + del(vertconnections[thisvert]) |
| 62 | + vertconnections[nextvert].remove(thisvert) |
| 63 | + newsegment.append(nextvert) |
| 64 | + thisvert = nextvert |
| 65 | + if thisvert in cornerverts: |
| 66 | + if len(vertconnections[thisvert]) == 0: |
| 67 | + del(vertconnections[thisvert]) |
| 68 | + break |
| 69 | + if len(vertconnections[thisvert]) == 0: |
| 70 | + print("corner counting went wrong") |
| 71 | + raise |
| 72 | + segments.append(newsegment) |
| 73 | + |
| 74 | +if VERBOSE: print(obname) |
| 75 | +#print(edges) |
| 76 | +if VERBOSE: print(len(edges)) |
| 77 | +#print(verts) |
| 78 | +if VERBOSE: print(len(verts)) |
| 79 | +#print(cornerverts) |
| 80 | +#print(vertconnections) |
| 81 | +#print(segments) |
| 82 | + |
| 83 | +loops = [] |
| 84 | +# all of the verts should have exactly 2 connections now |
| 85 | +while len(vertconnections): |
| 86 | + loopvert, connections = vertconnections.popitem() |
| 87 | + vertconnections[loopvert] = connections |
| 88 | + newloop = [loopvert] |
| 89 | + thisvert = loopvert |
| 90 | + while True: |
| 91 | +# print(thisvert) |
| 92 | + nextvert = vertconnections[thisvert].pop() |
| 93 | + del(vertconnections[thisvert]) |
| 94 | + if nextvert == loopvert: |
| 95 | + break |
| 96 | + vertconnections[nextvert].remove(thisvert) |
| 97 | + newloop.append(nextvert) |
| 98 | + thisvert = nextvert |
| 99 | + loops.append(newloop) |
| 100 | + |
| 101 | +#print(vertconnections) |
| 102 | +#print(loops) |
| 103 | + |
| 104 | +# generate the point search list |
| 105 | +pointstosearch = [] |
| 106 | +def grabpointdata(pid, group = 0): |
| 107 | + pointdata = {} |
| 108 | + pointdata['P'] = pid |
| 109 | + coords = verts[pid] |
| 110 | + pointdata['X'] = coords[0] |
| 111 | + pointdata['Y'] = coords[1] |
| 112 | + if group: pointdata['G'] = group |
| 113 | + return pointdata |
| 114 | +for seg in segments: |
| 115 | + for i in (0,-1): pointstosearch.append(grabpointdata(seg[i], seg)) |
| 116 | +for loop in loops: |
| 117 | + for i in range(len(loop)//2): pointstosearch.append(grabpointdata(loop[i*2], loop)) |
| 118 | + |
| 119 | +# Generate the header, and initialize the machine |
| 120 | +OutString = "(Gcode generated by Tryop Blender Exporter)\n" |
| 121 | +# G20 is inches, G21 is mm |
| 122 | +OutString += "G21\n" |
| 123 | +# G90 is absolute, G91 is incremental |
| 124 | +OutString += "G90\n" |
| 125 | +# M05 is spindle off |
| 126 | +# M03 is spindle on, s is the spindle speed (or laser power in this case), from 0 to 255 |
| 127 | +OutString += "M03 S0\n" |
| 128 | +# G00 is rapid move |
| 129 | +OutString += f"G00 X0 Y0 F{JOGSPD}\n" |
| 130 | + |
| 131 | +def dist(p1,p2): |
| 132 | + return sqrt((p1["X"]-p2["X"])**2 + (p1["Y"]-p2["Y"])**2) |
| 133 | + |
| 134 | +X = 0. |
| 135 | +Y = 0. |
| 136 | +curpos = {} |
| 137 | +curpos['X'] = X |
| 138 | +curpos['Y'] = Y |
| 139 | + |
| 140 | +def close(p1): return dist(p1, curpos) |
| 141 | + |
| 142 | +while len(pointstosearch): |
| 143 | + pointstosearch.sort(key=close) |
| 144 | + # if VERBOSE: print(pointstosearch) |
| 145 | + curpos = pointstosearch[0] |
| 146 | + pidx = curpos['P'] |
| 147 | + grp = curpos['G'] |
| 148 | + to_remove = [] |
| 149 | + for pnt in pointstosearch: |
| 150 | + if pnt['G'] == grp: to_remove.append(pnt) |
| 151 | + for pnt in to_remove: |
| 152 | + pointstosearch.remove(pnt) |
| 153 | + # re-order the group |
| 154 | + if grp in loops: |
| 155 | + grpidx = grp.index(pidx) |
| 156 | + grp = grp[grpidx:] + grp[:grpidx] |
| 157 | + grp.append(grp[0]) |
| 158 | + else: |
| 159 | + if grp[-1] == pidx: |
| 160 | + grp.reverse() |
| 161 | + if VERBOSE: print(grp) |
| 162 | + X = curpos['X'] |
| 163 | + Y = curpos['Y'] |
| 164 | + # for each segment or loop, jog to the start, turn on the laser, complete the path, and turn off again. |
| 165 | + OutString += f"G00 X{X:.2f} Y{Y:.2f} F{JOGSPD}\n" |
| 166 | + # M03 is spindle on, s is the spindle speed (or laser power in this case), from 0 to 255 |
| 167 | + OutString += f"M03 S{LSRPOW}\n" |
| 168 | + for curidx in grp[1:]: |
| 169 | + curpos = grabpointdata(curidx) |
| 170 | + X = curpos['X'] |
| 171 | + Y = curpos['Y'] |
| 172 | + # G01 is linear motion |
| 173 | + OutString += f"G01 X{X:.2f} Y{Y:.2f} F{LSRSPD}\n" |
| 174 | + # when done, turn the laser back off |
| 175 | + OutString += "M03 S0\n" |
| 176 | + |
| 177 | +# when all the engraving is done, turn the laser off and jog back to the origin |
| 178 | +OutString += "M05\n" |
| 179 | +OutString += f"G00 X0 Y0 F{JOGSPD}\n" |
| 180 | + |
| 181 | + |
| 182 | +fp = Path(bpy.path.abspath(savefile)) |
| 183 | +try: |
| 184 | + f = open(fp, 'w') |
| 185 | + f.write(OutString) |
| 186 | + f.close() |
| 187 | + if VERBOSE: print('File Saved') |
| 188 | +except: |
| 189 | + if VERBOSE: print('exception found while saving file') |
0 commit comments