1#!/usr/local/bin/python3.8 2''' 3Copyright (C) 2017-2021 Scorch www.scorchworks.com 4Derived from dxf_outlines.py by Aaron Spike and Alvin Penner 5 6This program is free software; you can redistribute it and/or modify 7it under the terms of the GNU General Public License as published by 8the Free Software Foundation; either version 2 of the License, or 9(at your option) any later version. 10 11This program is distributed in the hope that it will be useful, 12but WITHOUT ANY WARRANTY; without even the implied warranty of 13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14GNU General Public License for more details. 15 16You should have received a copy of the GNU General Public License 17along with this program; if not, write to the Free Software 18Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19''' 20 21# standard library 22import math 23import tempfile, os, sys, shutil 24 25import zipfile 26import re 27# local library 28import inkex 29import simplestyle 30import simpletransform 31import cubicsuperpath 32import cspsubdiv 33import traceback 34 35from PIL import Image 36Image.MAX_IMAGE_PIXELS = None 37 38from lxml import etree 39 40try: 41 inkex.localize() 42except: 43 pass 44 45#### Subprocess timout stuff ###### 46from subprocess import Popen, PIPE 47from threading import Timer 48def run_external(cmd, timeout_sec): 49 stdout=None 50 stderr=None 51 FLAG=[True] 52 try: 53 proc = Popen(cmd, shell=False, stdout=PIPE, stderr=PIPE, stdin=PIPE, startupinfo=None) 54 except Exception as e: 55 raise Exception("\n%s\n\nExecutable Path:\n%s" %(e,cmd[0])) 56 if timeout_sec > 0: 57 kill_proc = lambda p: kill_sub_process(p,timeout_sec, FLAG) 58 59 timer = Timer(timeout_sec, kill_proc, [proc]) 60 try: 61 timer.start() 62 stdout,stderr = proc.communicate() 63 finally: 64 timer.cancel() 65 if not FLAG[0]: 66 raise Exception("\nInkscape sub-process terminated after %d seconds." %(timeout_sec)) 67 return (stdout,stderr) 68 69def kill_sub_process(p,timeout_sec, FLAG): 70 FLAG[0]=False 71 p.kill() 72 73################################## 74class SVG_TEXT_EXCEPTION(Exception): 75 def __init__(self, value): 76 self.value = value 77 def __str__(self): 78 return repr(self.value) 79 80class SVG_ENCODING_EXCEPTION(Exception): 81 def __init__(self, value): 82 self.value = value 83 def __str__(self): 84 return repr(self.value) 85 86class SVG_PXPI_EXCEPTION(Exception): 87 def __init__(self, value): 88 self.value = value 89 def __str__(self): 90 return repr(self.value) 91 92class CSS_value_class(): 93 def __init__(self,type_name,value): 94 type_name_list = type_name.split('.') 95 try: 96 self.object_type = type_name_list[0] 97 except: 98 self.object_type = "" 99 100 try: 101 self.value_name = type_name_list[1] 102 except: 103 self.value_name = "" 104 self.data_string = value 105 106 107class CSS_values_class(): 108 def __init__(self): 109 self.CSS_value_list = [] 110 111 def add(self,type_name,value): 112 self.CSS_value_list.append(CSS_value_class(type_name,value)) 113 114 def get_css_value(self,tag_type,class_val): 115 value = "" 116 for entry in self.CSS_value_list: 117 if entry.object_type == "": 118 if entry.value_name == class_val: 119 value = value + entry.data_string 120 if entry.object_type == tag_type: 121 if entry.value_name == class_val: 122 value = entry.data_string 123 break 124 return value 125 126 127class SVG_READER(inkex.Effect): 128 def __init__(self): 129 inkex.Effect.__init__(self) 130 self.flatness = 0.01 131 self.image_dpi = 1000 132 self.inkscape_exe_list = [] 133 self.inkscape_exe_list.append("C:\\Program Files\\Inkscape\\bin\\inkscape.exe") 134 self.inkscape_exe_list.append("C:\\Program Files (x86)\\Inkscape\\bin\\inkscape.exe") 135 self.inkscape_exe_list.append("C:\\Program Files\\Inkscape\\inkscape.exe") 136 self.inkscape_exe_list.append("C:\\Program Files (x86)\\Inkscape\\inkscape.exe") 137 self.inkscape_exe_list.append("/usr/bin/inkscape") 138 self.inkscape_exe_list.append("/usr/local/bin/inkscape") 139 self.inkscape_exe_list.append("/Applications/Inkscape.app/Contents/Resources/bin/inkscape") 140 self.inkscape_exe_list.append("/Applications/Inkscape.app/Contents/MacOS/Inkscape") 141 self.inkscape_exe = None 142 self.lines =[] 143 self.Cut_Type = {} 144 self.Xsize=40 145 self.Ysize=40 146 self.raster = True 147 self.SVG_dpi = None 148 149 self.SVG_inkscape_version = None 150 self.SVG_size_mm = None 151 self.SVG_size_px = None 152 self.SVG_ViewBox = None 153 154 self.raster_PIL = None 155 self.cut_lines = [] 156 self.eng_lines = [] 157 self.id_cnt = 0 158 159 self.png_area = "--export-area-page" 160 self.timout = 180 #timeout time for external calls to Inkscape in seconds 161 162 self.layers = ['0'] 163 self.layer = '0' 164 self.layernames = [] 165 self.txt2paths = False 166 self.CSS_values = CSS_values_class() 167 168 def parse_svg(self,filename): 169 try: 170 self.parse(filename) 171 #self.parse(filename, encoding='utf-8') 172 except Exception as e: 173 exception_msg = "%s" %(e) 174 if exception_msg.find("encoding"): 175 self.parse(filename,encoding="ISO-8859-1") 176 else: 177 raise Exception(e) 178 179 def set_inkscape_path(self,PATH): 180 if PATH!=None: 181 self.inkscape_exe_list.insert(0,PATH) 182 for location in self.inkscape_exe_list: 183 if ( os.path.isfile( location ) ): 184 self.inkscape_exe=location 185 break 186 187 def colmod(self,r,g,b,path_id): 188 changed=False 189 k40_action = 'raster' 190 delta = 10 191 # Check if the color is Red (or close to it) 192 if (r >= 255-delta) and (g <= delta) and (b <= delta): 193 k40_action = "cut" 194 self.Cut_Type[path_id]=k40_action 195 (r,g,b) = (255,255,255) 196 changed=True 197 # Check if the color is Blue (or close to it) 198 elif (r <= delta) and (g <= delta) and (b >= 255-delta): 199 k40_action = "engrave" 200 self.Cut_Type[path_id]=k40_action 201 (r,g,b) = (255,255,255) 202 changed=True 203 else: 204 k40_action = "raster" 205 self.Cut_Type[path_id]=k40_action 206 changed=False 207 color_out = '#%02x%02x%02x' %(r,g,b) 208 return (color_out, changed, k40_action) 209 210 211 def process_shape(self, node, mat, group_stroke = None): 212 ################################# 213 ### Determine the shape type ### 214 ################################# 215 try: 216 i = node.tag.find('}') 217 if i >= 0: 218 tag_type = node.tag[i+1:] 219 except: 220 tag_type="" 221 222 ############################################## 223 ### Set a unique identifier for each shape ### 224 ############################################## 225 self.id_cnt=self.id_cnt+1 226 path_id = "ID%d"%(self.id_cnt) 227 sw_flag = False 228 changed = False 229 ####################################### 230 ### Handle references to CSS data ### 231 ####################################### 232 class_val = node.get('class') 233 if class_val: 234 css_data = "" 235 for cv in class_val.split(' '): 236 if css_data!="": 237 css_data = self.CSS_values.get_css_value(tag_type,cv)+";"+css_data 238 else: 239 css_data = self.CSS_values.get_css_value(tag_type,cv) 240 241 # Remove the reference to the CSS data 242 del node.attrib['class'] 243 244 # Check if a style entry already exists. If it does 245 # append the the existing style data to the CSS data 246 # otherwise create a new style entry. 247 if node.get('style'): 248 if css_data!="": 249 css_data = css_data + ";" + node.get('style') 250 node.set('style', css_data) 251 else: 252 node.set('style', css_data) 253 254 style = node.get('style') 255 self.Cut_Type[path_id]="raster" # Set default type to raster 256 257 text_message_warning = "SVG File with Color Coded Text Outlines Found: (i.e. Blue: engrave/ Red: cut)" 258 line1 = "SVG File with color coded text outlines found (i.e. Blue: engrave/ Red: cut)." 259 line2 = "Automatic conversion to paths failed: Try upgrading to Inkscape .90 or later" 260 line3 = "To convert manually in Inkscape: select the text then select \"Path\"-\"Object to Path\" in the menu bar." 261 text_message_fatal = "%s\n\n%s\n\n%s" %(line1,line2,line3) 262 263 ############################################## 264 ### Handle 'style' data outside of style ### 265 ############################################## 266 stroke_outside = node.get('stroke') 267 if not stroke_outside: 268 stroke_outside = group_stroke 269 if stroke_outside: 270 stroke_width_outside = node.get('stroke-width') 271 272 col = stroke_outside 273 col= col.strip() 274 if simplestyle.isColor(col): 275 c=simplestyle.parseColor(col) 276 (new_val,changed,k40_action)=self.colmod(c[0],c[1],c[2],path_id) 277 else: 278 new_val = col 279 if changed: 280 node.set('stroke',new_val) 281 node.set('stroke-width',"0.0") 282 node.set('k40_action', k40_action) 283 sw_flag = True 284 285 if sw_flag == True: 286 if node.tag == inkex.addNS('text','svg') or node.tag == inkex.addNS('flowRoot','svg'): 287 if (self.txt2paths==False): 288 raise SVG_TEXT_EXCEPTION(text_message_warning) 289 else: 290 raise Exception(text_message_fatal) 291 292 ################################################### 293 ### Handle 'k40_action' data outside of style ### 294 ################################################### 295 if node.get('k40_action'): 296 k40_action = node.get('k40_action') 297 changed=True 298 self.Cut_Type[path_id]=k40_action 299 300 ############################################## 301 ### Handle 'style' data ### 302 ############################################## 303 if style: 304 declarations = style.split(';') 305 i_sw = -1 306 307 sw_prop = 'stroke-width' 308 for i,decl in enumerate(declarations): 309 parts = decl.split(':', 2) 310 if len(parts) == 2: 311 (prop, col) = parts 312 prop = prop.strip().lower() 313 314 if prop == 'display' and col == "none": 315 # display is 'none' return without processing group 316 return 317 318 if prop == 'k40_action': 319 changed = True 320 self.Cut_Type[path_id]=col 321 322 #if prop in color_props: 323 if prop == sw_prop: 324 i_sw = i 325 if prop == 'stroke': 326 col= col.strip() 327 if simplestyle.isColor(col): 328 c=simplestyle.parseColor(col) 329 (new_val,changed,k40_action)=self.colmod(c[0],c[1],c[2],path_id) 330 else: 331 new_val = col 332 if changed: 333 declarations[i] = prop + ':' + new_val 334 declarations.append('k40_action' + ':' + k40_action) 335 sw_flag = True 336 if sw_flag == True: 337 if node.tag == inkex.addNS('text','svg') or node.tag == inkex.addNS('flowRoot','svg'): 338 if (self.txt2paths==False): 339 raise SVG_TEXT_EXCEPTION(text_message_warning) 340 else: 341 raise Exception(text_message_fatal) 342 343 if i_sw != -1: 344 declarations[i_sw] = sw_prop + ':' + "0.0" 345 else: 346 declarations.append(sw_prop + ':' + "0.0") 347 node.set('style', ';'.join(declarations)) 348 ############################################## 349 350 ##################################################### 351 ### If vector data was found save the path data ### 352 ##################################################### 353 if changed: 354 if node.get('display')=='none': 355 return 356 if node.tag == inkex.addNS('path','svg'): 357 d = node.get('d') 358 if not d: 359 return 360 p = cubicsuperpath.parsePath(d) 361 elif node.tag == inkex.addNS('rect','svg'): 362 x = 0.0 363 y = 0.0 364 if node.get('x'): 365 x=float(node.get('x')) 366 if node.get('y'): 367 y=float(node.get('y')) 368 369 width = float(node.get('width')) 370 height = float(node.get('height')) 371 rx = 0.0 372 ry = 0.0 373 if node.get('rx'): 374 rx=float(node.get('rx')) 375 if node.get('ry'): 376 ry=float(node.get('ry')) 377 378 if max(rx,ry) > 0.0: 379 if rx==0.0 or ry==0.0: 380 rx = max(rx,ry) 381 ry = rx 382 Rxmax = abs(width)/2.0 383 Rymax = abs(height)/2.0 384 rx = min(rx,Rxmax) 385 ry = min(ry,Rymax) 386 L1 = "M %f,%f %f,%f " %(x+rx , y , x+width-rx , y ) 387 C1 = "A %f,%f 0 0 1 %f,%f" %(rx , ry , x+width , y+ry ) 388 L2 = "M %f,%f %f,%f " %(x+width , y+ry , x+width , y+height-ry) 389 C2 = "A %f,%f 0 0 1 %f,%f" %(rx , ry , x+width-rx , y+height ) 390 L3 = "M %f,%f %f,%f " %(x+width-rx , y+height , x+rx , y+height ) 391 C3 = "A %f,%f 0 0 1 %f,%f" %(rx , ry , x , y+height-ry) 392 L4 = "M %f,%f %f,%f " %(x , y+height-ry, x , y+ry ) 393 C4 = "A %f,%f 0 0 1 %f,%f" %(rx , ry , x+rx , y ) 394 d = L1 + C1 + L2 + C2 + L3 + C3 + L4 + C4 395 else: 396 d = "M %f,%f %f,%f %f,%f %f,%f Z" %(x,y, x+width,y, x+width,y+height, x,y+height) 397 p = cubicsuperpath.parsePath(d) 398 399 elif node.tag == inkex.addNS('circle','svg'): 400 cx = 0.0 401 cy = 0.0 402 if node.get('cx'): 403 cx=float(node.get('cx')) 404 if node.get('cy'): 405 cy=float(node.get('cy')) 406 if node.get('r'): 407 r = float(node.get('r')) 408 d = "M %f,%f A %f,%f 0 0 1 %f,%f %f,%f 0 0 1 %f,%f %f,%f 0 0 1 %f,%f %f,%f 0 0 1 %f,%f Z" %(cx+r,cy, r,r,cx,cy+r, r,r,cx-r,cy, r,r,cx,cy-r, r,r,cx+r,cy) 409 else: #if there is no radius assume it is a path 410 if node.get('d'): 411 d = node.get('d') 412 p = cubicsuperpath.parsePath(d) 413 else: 414 raise Exception("Radius of SVG circle is not defined.") 415 p = cubicsuperpath.parsePath(d) 416 417 elif node.tag == inkex.addNS('ellipse','svg'): 418 cx = 0.0 419 cy = 0.0 420 if node.get('cx'): 421 cx=float(node.get('cx')) 422 if node.get('cy'): 423 cy=float(node.get('cy')) 424 if node.get('r'): 425 r = float(node.get('r')) 426 rx = r 427 ry = r 428 if node.get('rx'): 429 rx = float(node.get('rx')) 430 if node.get('ry'): 431 ry = float(node.get('ry')) 432 433 d = "M %f,%f A %f,%f 0 0 1 %f,%f %f,%f 0 0 1 %f,%f %f,%f 0 0 1 %f,%f %f,%f 0 0 1 %f,%f Z" %(cx+rx,cy, rx,ry,cx,cy+ry, rx,ry,cx-rx,cy, rx,ry,cx,cy-ry, rx,ry,cx+rx,cy) 434 p = cubicsuperpath.parsePath(d) 435 436 elif (node.tag == inkex.addNS('polygon','svg')) or (node.tag == inkex.addNS('polyline','svg')): 437 points = node.get('points') 438 if not points: 439 return 440 441 points = points.replace(',', ' ') 442 while points.find(' ') > -1: 443 points = points.replace(' ', ' ') 444 445 points = points.strip().split(" ") 446 d = "M " 447 for i in range(0,len(points),2): 448 x = float(points[i]) 449 y = float(points[i+1]) 450 d = d + "%f,%f " %(x,y) 451 452 #Close the loop if it is a ploygon 453 if node.tag == inkex.addNS('polygon','svg'): 454 d = d + "Z" 455 p = cubicsuperpath.parsePath(d) 456 457 elif node.tag == inkex.addNS('line','svg'): 458 x1 = float(node.get('x1')) 459 y1 = float(node.get('y1')) 460 x2 = float(node.get('x2')) 461 y2 = float(node.get('y2')) 462 d = "M " 463 d = "M %f,%f %f,%f" %(x1,y1,x2,y2) 464 p = cubicsuperpath.parsePath(d) 465 else: 466 #print("something was ignored") 467 #print(node.tag) 468 return 469 470 trans = node.get('transform') 471 if trans: 472 mat = simpletransform.composeTransform(mat, simpletransform.parseTransform(trans)) 473 simpletransform.applyTransformToPath(mat, p) 474 475 ########################################## 476 ## Break Curves down into small lines ### 477 ########################################## 478 f = self.flatness 479 is_flat = 0 480 while is_flat < 1: 481 try: 482 cspsubdiv.cspsubdiv(p, f) 483 is_flat = 1 484 except IndexError: 485 break 486 except: 487 f += 0.1 488 if f>2 : 489 break 490 #something has gone very wrong. 491 ########################################## 492 rgb=(0,0,0) 493 for sub in p: 494 for i in range(len(sub)-1): 495 x1 = sub[i][1][0] 496 y1 = sub[i][1][1] 497 x2 = sub[i+1][1][0] 498 y2 = sub[i+1][1][1] 499 self.lines.append([x1,y1,x2,y2,rgb,path_id]) 500 ##################################################### 501 ### End of saving the vector path data ### 502 ##################################################### 503 504 505 def process_clone(self, node): 506 trans = node.get('transform') 507 x = node.get('x') 508 y = node.get('y') 509 mat = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]] 510 if trans: 511 mat = simpletransform.composeTransform(mat, simpletransform.parseTransform(trans)) 512 if x: 513 mat = simpletransform.composeTransform(mat, [[1.0, 0.0, float(x)], [0.0, 1.0, 0.0]]) 514 if y: 515 mat = simpletransform.composeTransform(mat, [[1.0, 0.0, 0.0], [0.0, 1.0, float(y)]]) 516 # push transform 517 if trans or x or y: 518 self.groupmat.append(simpletransform.composeTransform(self.groupmat[-1], mat)) 519 # get referenced node 520 refid = node.get(inkex.addNS('href','xlink')) 521 #print(refid,node.get('id'),node.get('layer')) 522 refnode = self.getElementById(refid[1:]) 523 if refnode is not None: 524 if refnode.tag == inkex.addNS('g','svg') or refnode.tag == inkex.addNS('switch','svg'): 525 self.process_group(refnode) 526 elif refnode.tag == inkex.addNS('use', 'svg'): 527 #print(refnode,'1') 528 self.process_clone(refnode) 529 else: 530 self.process_shape(refnode, self.groupmat[-1]) 531 # pop transform 532 if trans or x or y: 533 self.groupmat.pop() 534 535 def process_group(self, group): 536 ############################################## 537 ### Get color set at group level 538 stroke_group = group.get('stroke') 539 if group.get('display')=='none': 540 return 541 ############################################## 542 ### Handle 'style' data 543 style = group.get('style') 544 if style: 545 declarations = style.split(';') 546 for i,decl in enumerate(declarations): 547 parts = decl.split(':', 2) 548 if len(parts) == 2: 549 (prop, val) = parts 550 prop = prop.strip().lower() 551 if prop == 'stroke': 552 stroke_group = val.strip() 553 if prop == 'display' and val == "none": 554 #group display is 'none' return without processing group 555 return 556 ############################################## 557 if group.get(inkex.addNS('groupmode', 'inkscape')) == 'layer': 558 style = group.get('style') 559 if style: 560 style = simplestyle.parseStyle(style) 561 if 'display' in style: 562 if style['display'] == 'none': 563 #layer display is 'none' return without processing layer 564 return 565 layer = group.get(inkex.addNS('label', 'inkscape')) 566 567 layer = layer.replace(' ', '_') 568 if layer in self.layers: 569 self.layer = layer 570 trans = group.get('transform') 571 if trans: 572 self.groupmat.append(simpletransform.composeTransform(self.groupmat[-1], simpletransform.parseTransform(trans))) 573 for node in group: 574 if node.tag == inkex.addNS('g','svg') or node.tag == inkex.addNS('switch','svg'): 575 self.process_group(node) 576 elif node.tag == inkex.addNS('use', 'svg'): 577 #print(node.get('id'),'2',node.get('href')) 578 self.process_clone(node) 579 580 elif node.tag == inkex.addNS('style', 'svg'): 581 if node.get('type')=="text/css": 582 self.parse_css(node.text) 583 584 elif node.tag == inkex.addNS('defs', 'svg'): 585 for sub in node: 586 if sub.tag == inkex.addNS('style','svg'): 587 self.parse_css(sub.text) 588 else: 589 self.process_shape(node, self.groupmat[-1], group_stroke = stroke_group) 590 if trans: 591 self.groupmat.pop() 592 593 def parse_css(self,css_string): 594 if css_string == None: 595 return 596 name_list=[] 597 value_list=[] 598 name="" 599 value="" 600 i=0 601 while i < len(css_string): 602 c=css_string[i] 603 if c==",": 604 i=i+1 605 name_list.append(name) 606 value_list.append(value) 607 name="" 608 value="" 609 continue 610 if c=="{": 611 i=i+1 612 while i < len(css_string): 613 c=css_string[i] 614 i=i+1 615 if c=="}": 616 break 617 else: 618 value = value+c 619 620 if len(value_list)>0: 621 len_value_list = len(value_list) 622 k=-1 623 while abs(k) <= len_value_list and value_list[k]=="": 624 value_list[k]=value 625 k=k-1 626 name_list.append(name) 627 value_list.append(value) 628 name="" 629 value="" 630 continue 631 name=name+c 632 i=i+1 633 for i in range(len(name_list)): 634 name_list[i]=" ".join(name_list[i].split()) 635 self.CSS_values.add(name_list[i],value_list[i]) 636 637 638 def unit2mm(self, string): #,dpi=None): 639 if string==None: 640 return None 641 # Returns mm given a string representation of units in another system 642 # a dictionary of unit to user unit conversion factors 643 uuconv = {'in': 25.4, 644 'pt': 25.4/72.0, 645 'mm': 1.0, 646 'cm': 10.0, 647 'm' : 1000.0, 648 'km': 1000.0*1000.0, 649 'pc': 25.4/6.0, 650 'yd': 25.4*12*3, 651 'ft': 25.4*12} 652 653 unit = re.compile('(%s)$' % '|'.join(uuconv.keys())) 654 param = re.compile(r'(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)') 655 656 string = string.replace(' ','') 657 p = param.match(string) 658 u = unit.search(string) 659 660 if p: 661 retval = float(p.string[p.start():p.end()]) 662 else: 663 return None 664 if u: 665 retunit = u.string[u.start():u.end()] 666 else: 667 return None 668 try: 669 return retval * uuconv[retunit] 670 except KeyError: 671 return None 672 673 def unit2px(self, string): 674 if string==None: 675 return None 676 string = string.replace(' ','') 677 string = string.replace('px','') 678 try: 679 retval = float(string) 680 except: 681 retval = None 682 return retval 683 684 685 def Make_PNG(self): 686 #create OS temp folder 687 tmp_dir = tempfile.mkdtemp() 688 689 if self.inkscape_exe != None: 690 try: 691 svg_temp_file = os.path.join(tmp_dir, "k40w_temp.svg") 692 png_temp_file = os.path.join(tmp_dir, "k40w_image.png") 693 dpi = "%d" %(self.image_dpi) 694 self.document.write(svg_temp_file) 695 #self.document.write("svg_temp_file.svg", encoding='utf-8') 696 697 # Check Version of Inkscape 698 cmd = [ self.inkscape_exe, "-V"] 699 (stdout,stderr)=run_external(cmd, self.timout) 700 if stdout.find(b'Inkscape 1.')==-1: 701 cmd = [ self.inkscape_exe, self.png_area, "--export-dpi", dpi, \ 702 "--export-background","rgb(255, 255, 255)","--export-background-opacity", \ 703 "255" ,"--export-png", png_temp_file, svg_temp_file ] 704 else: 705 cmd = [ self.inkscape_exe, self.png_area, "--export-dpi", dpi, \ 706 "--export-background","rgb(255, 255, 255)","--export-background-opacity", \ 707 "255" ,"--export-type=png", "--export-filename=%s" %(png_temp_file), svg_temp_file ] 708 709 run_external(cmd, self.timout) 710 self.raster_PIL = Image.open(png_temp_file) 711 self.raster_PIL = self.raster_PIL.convert("L") 712 except Exception as e: 713 try: 714 shutil.rmtree(tmp_dir) 715 except: 716 pass 717 error_text = "%s" %(e) 718 raise Exception("Inkscape Execution Failed (while making raster data).\n%s" %(error_text)) 719 else: 720 raise Exception("Inkscape Not found.") 721 try: 722 shutil.rmtree(tmp_dir) 723 except: 724 raise Exception("Temp dir failed to delete:\n%s" %(tmp_dir) ) 725 726 727## def open_cdr_file(self,filename): 728## #create OS temp folder 729## svg_temp_file=filename 730## tmp_dir = tempfile.mkdtemp() 731## if self.inkscape_exe != None: 732## try: 733## #svg_temp_file = os.path.join(tmp_dir, "k40w_temp.svg") 734## txt2path_file = os.path.join(tmp_dir, "txt2path.svg") 735## #self.document.write(svg_temp_file) 736## cmd = [ self.inkscape_exe, "--export-text-to-path","--export-plain-svg",txt2path_file, svg_temp_file ] 737## run_external(cmd, self.timout) 738## self.parse_svg(txt2path_file) 739## except Exception as e: 740## raise Exception("Inkscape Execution Failed (while converting text to paths).\n\n"+str(e)) 741## else: 742## raise Exception("Inkscape Not found.") 743## try: 744## shutil.rmtree(tmp_dir) 745## except: 746## raise Exception("Temp dir failed to delete:\n%s" %(tmp_dir) ) 747 748 def convert_text2paths(self): 749 #create OS temp folder 750 tmp_dir = tempfile.mkdtemp() 751 if self.inkscape_exe != None: 752 try: 753 svg_temp_file = os.path.join(tmp_dir, "k40w_temp.svg") 754 txt2path_file = os.path.join(tmp_dir, "txt2path.svg") 755 self.document.write(svg_temp_file) 756 757 # Check Version of Inkscape 758 cmd = [ self.inkscape_exe, "-V"] 759 (stdout,stderr)=run_external(cmd, self.timout) 760 if stdout.find(b'Inkscape 1.')==-1: 761 cmd = [ self.inkscape_exe, "--export-text-to-path","--export-plain-svg", \ 762 txt2path_file, svg_temp_file, ] 763 else: 764 cmd = [ self.inkscape_exe, "--export-text-to-path","--export-plain-svg", \ 765 "--export-filename=%s" %(txt2path_file), svg_temp_file, ] 766 767 (stdout,stderr)=run_external(cmd, self.timout) 768 self.parse_svg(txt2path_file) 769 except Exception as e: 770 raise Exception("Inkscape Execution Failed (while converting text to paths).\n\n"+str(e)) 771 else: 772 raise Exception("Inkscape Not found.") 773 try: 774 shutil.rmtree(tmp_dir) 775 except: 776 raise Exception("Temp dir failed to delete:\n%s" %(tmp_dir) ) 777 778 779 def set_size(self,pxpi,viewbox): 780 width_mm = viewbox[2]/pxpi*25.4 781 height_mm = viewbox[3]/pxpi*25.4 782 self.document.getroot().set('width', '%fmm' %(width_mm)) 783 self.document.getroot().set('height','%fmm' %(height_mm)) 784 self.document.getroot().set('viewBox', '%f %f %f %f' %(viewbox[0],viewbox[1],viewbox[2],viewbox[3])) 785 786 787 def make_paths(self, txt2paths=False ): 788 self.txt2paths = txt2paths 789 msg = "" 790 791 if (self.txt2paths): 792 self.convert_text2paths() 793 794 ################# 795 ## GET VIEWBOX ## 796 ################# 797 view_box_array = self.document.getroot().xpath('@viewBox', namespaces=inkex.NSS) #[0] 798 if view_box_array == []: 799 view_box_str = None 800 else: 801 view_box_str=view_box_array[0] 802 ################# 803 ## GET SIZE ## 804 ################# 805 h_array = self.document.getroot().xpath('@height', namespaces=inkex.NSS) 806 w_array = self.document.getroot().xpath('@width' , namespaces=inkex.NSS) 807 if h_array == []: 808 h_string = None 809 else: 810 h_string = h_array[0] 811 if w_array == []: 812 w_string = None 813 else: 814 w_string = w_array[0] 815 ################# 816 w_mm = self.unit2mm(w_string) 817 h_mm = self.unit2mm(h_string) 818 w_px = self.unit2px(w_string) 819 h_px = self.unit2px(h_string) 820 self.SVG_Size = [w_mm, h_mm, w_px, h_px] 821 822 if view_box_str!=None: 823 view_box_list = view_box_str.split(' ') 824 DXpix= float(view_box_list[0]) 825 DYpix= float(view_box_list[1]) 826 Wpix = float(view_box_list[2]) 827 Hpix = float(view_box_list[3]) 828 self.SVG_ViewBox = [DXpix, DYpix, Wpix, Hpix] 829 else: 830 SVG_ViewBox = None 831 832 if h_mm==None or w_mm==None or self.SVG_ViewBox==None: 833 line1 = "Cannot determine SVG size. Viewbox missing or Units not set." 834 raise SVG_PXPI_EXCEPTION("%s" %(line1)) 835 836 scale_h = h_mm/Hpix 837 scale_w = w_mm/Wpix 838 Dx = DXpix * scale_w 839 Dy = DYpix * scale_h 840 841 if abs(1.0-scale_h/scale_w) > .01: 842 line1 ="SVG Files with different scales in X and Y are not supported.\n" 843 line2 ="In Inkscape (v0.92): 'File'-'Document Properties'" 844 line3 ="on the 'Page' tab adjust 'Scale x:' in the 'Scale' section" 845 raise Exception("%s\n%s\n%s" %(line1,line2,line3)) 846 847 for node in self.document.getroot().xpath('//svg:g', namespaces=inkex.NSS): 848 if node.get(inkex.addNS('groupmode', 'inkscape')) == 'layer': 849 layer = node.get(inkex.addNS('label', 'inkscape')) 850 self.layernames.append(layer.lower()) 851 layer = layer.replace(' ', '_') 852 if layer and not layer in self.layers: 853 self.layers.append(layer) 854 855 self.groupmat = [[[scale_w, 0.0, 0.0-Dx], 856 [0.0 , -scale_h, h_mm+Dy]]] 857 858 self.process_group(self.document.getroot()) 859 860 861 ################################################# 862 xmin= 0.0 863 xmax= w_mm 864 ymin= -h_mm 865 ymax= 0.0 866 self.Make_PNG() 867 868 self.Xsize=xmax-xmin 869 self.Ysize=ymax-ymin 870 Xcorner=xmin 871 Ycorner=ymax 872 for ii in range(len(self.lines)): 873 self.lines[ii][0] = self.lines[ii][0]-Xcorner 874 self.lines[ii][1] = self.lines[ii][1]-Ycorner 875 self.lines[ii][2] = self.lines[ii][2]-Xcorner 876 self.lines[ii][3] = self.lines[ii][3]-Ycorner 877 878 self.cut_lines = [] 879 self.eng_lines = [] 880 for line in self.lines: 881 ID=line[5] 882 if (self.Cut_Type[ID]=="engrave"): 883 self.eng_lines.append([line[0],line[1],line[2],line[3]]) 884 elif (self.Cut_Type[ID]=="cut"): 885 self.cut_lines.append([line[0],line[1],line[2],line[3]]) 886 else: 887 pass 888 889if __name__ == '__main__': 890 svg_reader = SVG_READER() 891 #svg_reader.parse("test.svg") 892 #svg_reader.make_paths() 893 tests=["100 mm ",".1 m ","4 in ","100 px ", "100 "] 894 for line in tests: 895 print(svg_reader.unit2mm(line),svg_reader.unit2px(line)) 896