diff --git a/scripts/py/common/__init__.py b/scripts/py/common/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/scripts/py/common/drawing.py b/scripts/py/common/drawing.py deleted file mode 100644 index 212c359..0000000 --- a/scripts/py/common/drawing.py +++ /dev/null @@ -1,793 +0,0 @@ -#!/bin/env python -# -*- coding: utf-8; -*- -# -# (c) 2016 FABtotum, http://www.fabtotum.com -# -# This file is part of FABUI. -# -# FABUI 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 2 of the License, or -# (at your option) any later version. -# -# FABUI 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 FABUI. If not, see . - -__authors__ = "Daniel Kesler" -__license__ = "GPL - https://opensource.org/licenses/GPL-3.0" -__version__ = "1.0" - -# Import external modules -import numpy as np -from loaders import dxfgrabber -import loaders.librecadfont1 as lff - -class Layer2D(object): - def __init__(self, name, color = 255): - self.name = name - self.color = color - self.primitives = [] - - def addPrimitive(self, data): - self.primitives.append(data) - -class Drawing2D(object): - """ - :todo: - - filled circle - - filled ellipse - - filled polyline - """ - - # Text attachment point. - TOP_LEFT = 1 - TOP_CENTER = 2 - TOP_RIGHT = 3 - MIDDLE_LEFT = 4 - MIDDLE_CENTER = 5 - MIDDLE_RIGHT = 6 - BOTTOM_LEFT = 7 - BOTTOM_CENTER = 8 - BOTTOM_RIGHT = 9 - - # Text alignment - ALIGN_LEFT = 1 - ALIGN_CENTER = 2 - ALIGN_RIGHT = 3 - - def __init__(self): - self.clear() - self.fonts = {} - self.add_layer(name='Default', color=7) - - def extend_bounds(self, points): - """ - Extend boundries to fit the points. - """ - if type(points) is list: - pass - elif type(points) is tuple: - points = [ points ] - else: - print 'neither a tuple or a list' - return - - x1 = 1.0e10 - y1 = 1.0e10 - x2 = -1.0e10 - y2 = -1.0e10 - - for pt in points: - if pt[0] > x2: - x2 = pt[0] - - if pt[0] < x1: - x1 = pt[0] - - if pt[1] > y2: - y2 = pt[1] - - if pt[1] < y1: - y1 = pt[1] - - if x2 > self.max_x: - self.max_x = x2 - - if x1 < self.min_x: - self.min_x = x1 - - if y2 > self.max_y: - self.max_y = y2 - - if y1 < self.min_y: - self.min_y = y1 - - return x1,y1, np.absolute(x2-x1), np.absolute(y2-y1) - - def get_font(self, font): - filename = '/var/lib/fabui/plugins/fab_laser/fonts/lff/{0}.lff'.format(font) - - print "loading font", filename - - if font not in self.fonts: - f = lff.readfile(filename) - if f: - self.fonts[font] = f - return f - else: - return self.fonts[font] - - def add_layer(self, name = None, color = 7): - """ - Add a new layer. - """ - idx = len(self.layers) - if name is None: - name = 'Layer_{0}'.format(idx) - - layer = Layer2D(name, color) - self.layers.append(layer) - return idx - - def __bulge2arc(self, p1, p2, b): - center = (0,0) - start = 0.0 - end = 0.0 - radius = 0.0 - reverse = False - theta_half = 2*np.arctan(b) - - dx = p2[0] - p1[0] - dy = p2[1] - p1[1] - - chord = np.sqrt(dx * dx + dy* dy) - - dx_norm = dx / chord - dy_norm = dy / chord - - radius = chord / (2*np.sin(theta_half)) - - p3 = ( p1[0] + dx*0.5, p1[1] + dy*0.5 ) - - x = np.cos(theta_half) * radius - - center = ( p3[0] - dy_norm*x, p3[1] + dx_norm*x ) - - if b < 0: - # CW, flip start end - - theta = 2*theta_half - - dx = center[0] - p2[0] - dy = center[1] - p2[1] - - a1 = np.arctan2(dy,dx) - - A = np.rad2deg( a1 ) - B = A - np.rad2deg( theta ) - - end = B - start = A - reverse = True - else: - # CCW - - theta = 2*theta_half - - dx = center[0] - p1[0] - dy = center[1] - p1[1] - - a1 = np.arctan2(dy,dx) - - start = np.rad2deg( a1 ) - 180 - end = start + np.rad2deg( theta ) - - return center, radius, start, end, reverse - - def __translate_points(self, points, tx, ty): - tpoints = [] - for pt in points: - x = pt[0] + tx - y = pt[y] + ty - tpoints.append( (x,y) ) - - return tpoints - - def add_rect(self, x1, y1, x2, y2, layer = 0, filled = False): - points = [ - (x1,y1), - (x2,y1), - (x2,y2), - (x1,y2), - (x1,y1) - ] - data = { 'type' : 'rect', 'first': (x1,y1), 'second' : (x2,y2), 'points' : points, 'filled' : filled } - self.layers[layer].addPrimitive(data) - return self.extend_bounds(points) - - def add_rect2(self, x1, y1, w, h, layer = 0, filled = False): - points = [ - (x1,y1), - (x1+w,y1), - (x1+w,y1+h), - (x1,y1+h), - (x1,y1), - ] - data = { 'type' : 'rect', 'first': (x1,y1), 'second' : (x1+w,y1+h), 'points' : points, 'filled' : filled } - self.layers[layer].addPrimitive(data) - return self.extend_bounds(points) - - def add_line(self, start, end, layer = 0): - data = { 'type' : 'line', 'points': [start, end] } - self.layers[layer].addPrimitive(data) - - return self.extend_bounds( [start, end] ) - - def add_polyline(self, points, bulges, closed = False, layer = 0, filled = False, dummy = False): - - if closed: - points.append(points[0]) - - points2 = [] - - cnt = 0 - - idx = 0 - has_prev = False - for pt in points: - if has_prev: - b = bulges[idx] - if b != 0: - # Construct and arc - center, radius, start, end, reverse = self.__bulge2arc(p0, pt, b) - arc = self.__arc(center, radius, start, end, step = 10.0, reverse=reverse) - - points2 += arc - else: - # Use a straight line - points2.append(pt) - - idx += 1 - else: - points2.append(pt) - - p0 = pt - has_prev = True - - data = { 'type' : 'polyline', 'points' : points2, 'bulges' : bulges, 'closed' : closed, 'filled' : filled } - if not dummy: - self.layers[layer].addPrimitive(data) - return self.extend_bounds(points2) - - def add_circle(self, center, radius, layer = 0, filled = False): - points = self.__circle(center, radius) - data = { 'type' : 'circle', 'center' : center, 'radius' : radius, 'points' : points, 'filled' : filled } - self.layers[layer].addPrimitive(data) - return self.extend_bounds(points) - - def add_arc(self, center, radius, start, end, layer = 0): - points = self.__arc(center, radius, start, end) - data = { 'type' : 'arc', 'center' : center, 'radius' : radius, 'start' : start, 'end': end, 'points' : points } - self.layers[layer].addPrimitive(data) - return self.extend_bounds(points) - - def add_spline(self, control_points, knots, degree, layer = 0): - - npts = len(control_points) - k = degree + 1 - #~ p1 = (dxf.header['$SPLINESEGS'] or 8) * npts - p1 = (8) * npts - - points = self.__rbspline(npts, k, p1, control_points, knots) - - data = { 'type' : 'spline', 'control_points' : control_points, 'knots' : knots, 'degree' : degree, 'points' : points} - self.layers[layer].addPrimitive(data) - return self.extend_bounds(points) - - def add_ellipse(self, center, major_axis, ratio, start, end, layer = 0, filled = False): - points = self.__ellipse(center, major_axis, ratio, start, end) - data = { 'type' : 'ellipse', 'center' : center, 'major_axis' : major_axis, 'ratio' : ratio, 'start' : start, 'end' : end, 'points' : points, 'filled' : filled} - self.layers[layer].addPrimitive(data) - self.extend_bounds(points) - - def add_text(self, position, align, width, height, direction, font_name, text_lines, layer = 0): - # TODO: - # - alignment - # - direction - #~ font_name = 'OpenGostTypeA-Regular' - #~ font_name = 'iso' - font = self.get_font(font_name) - - if not font: - print "Font '{0}' not found".format(font_name) - return - - print - print "direction", direction - print "height", height - - scale = height / 9.0 - print "scale",scale - wordSpacing = float(font.meta['WordSpacing']) * scale - letterSpacing = float(font.meta['LetterSpacing']) * scale - letterHeight = height - - print "letterSpacing", letterSpacing - print "wordSpacing", wordSpacing - - print text_lines - - off_x = position[0] - off_y = position[1] - - for text in text_lines: - #~ off_x = position[0] - - for c in text: - if c == ' ': - off_x += wordSpacing - else: - sym = font.getSymbol(c) - # TODO: load reference character - if sym.ref: - #~ print "has ref to '{0}'".format(sym.ref) - sym_ref = font.getSymbol(sym.ref) - lines = sym_ref.lines + sym.lines - else: - lines = sym.lines - cnt = 0 - - max_x = -1e10 - max_y = -1e10 - - for line in lines: - eat_one = True - points = [] - bulges = [] - for pt in line: - x = pt[0]*scale + off_x - y = pt[1]*scale + off_y - p1 = (x, y) - points.append(p1) - - if len(pt) == 3: - bulges.append(pt[2]) - else: - if len(points) > 0: - if eat_one: - eat_one = False - pass - else: - bulges.append(0.0) - - #~ max_x = max(x, max_x) - #~ max_y = max(y, max_y) - - if eat_one == False: - bulges.append(0.0) - - x,y,w,h = self.add_polyline(points, bulges, layer=layer) - #~ print x,y,w,h - max_x = max(x+w, max_x) - max_y = max(y+h,max_y) - - #~ print "R",off_x,off_y,max_x,max_y - #~ self.add_rect(off_x,off_y,max_x,max_y,layer=layer) - - print "off_x", off_x, position[0] - - off_x = max_x + letterSpacing - print "+off_x", off_x - - off_y -= letterHeight - off_x = position[0] - - def __ellipse_point(self, center, r1, r2, rotM, t): - x1 = r1 * np.cos( np.radians(t) ) - y1 = r2 * np.sin( np.radians(t) ) - tmp = np.array([x1,y1]) - p1 = center + (tmp * rotM) - return p1.A1[0], p1.A1[1] - - def __ellipse(self, center, axis, ratio, start, end, step = 10.0): - - points = [] - x0 = center[0] - y0 = center[1] - # Get length of axis vector - r1 = np.linalg.norm(axis) - # Get second radius - r2 = r1 * ratio - - # Get axis angle - if axis[0] == 0: - a = np.radians(90.0) - elif axis[1] == 0: - a = np.radians(0.0) - else: - a = np.arctan(axis[1] / axis[0]) - - # Prepare rotation matrix - center = np.array([x0,y0]) - c1 = np.cos(a) - c2 = np.sin(a) - rotM = np.matrix([ - [c1,c2], - [-c2,c1] - ]) - rotMCCW = np.matrix([ - [c1,-c2], - [c2,c1] - ]) - - eye = np.matrix([ [1.0, 0.0], [0.0, 1.0] ]) - - fix = 0 - if start > end: - start += 180.0 - if start > 360.0: - start -= 360.0 - end += 180.0 - - have_prev = False - - tl = np.arange(start, end, step) - - for t in tl: - x2,y2 = self.__ellipse_point(center, r1, r2, rotM, t) - - points.append( (x2,y2) ) - - x1 = x2 - y1 = y2 - have_prev = True - - x2,y2 = self.__ellipse_point(center, r1, r2, rotM, end) - points.append( (x2,y2) ) - - return points - - def __circle(self, center, radius, step = 10.0): - x0 = center[0] - y0 = center[1] - r = radius - points = [] - start = 0.0 - end = 360.0 - steps = int( 360.0 / step ) - - have_prev = False - - for a in xrange(steps): - angle = np.deg2rad(start + a*step) - x2 = x0 + np.cos(angle)*r - y2 = y0 + np.sin(angle)*r - - points.append( (x2,y2) ) - - x1 = x2 - y1 = y2 - have_prev = True - - if (start + (steps-1)*step) != end: - angle = np.deg2rad(end) - x2 = x0 + np.cos(angle)*r - y2 = y0 + np.sin(angle)*r - - points.append( (x2,y2) ) - - return points - - def __wrapTo360(self, angle): - angle = np.fmod(angle,360); - if angle < 0: - angle += 360; - return angle; - - def __arc(self, center, radius, start, end, step = 10.0, reverse=False): - """ - Draw an arc. - - :param center: Tuple (x,y) representing the arc center point - :param radius: Arc radius - :param start: Arc start angle (degree) - :param end: Arc end angle (degree) - :param step: Angle change step - :param reverse: Reverse arc points - - :returns: Arc points - """ - x0 = center[0] - y0 = center[1] - points = [] - r = radius - angle = self.__wrapTo360(end - start) - - steps = int(abs(angle / step)) - - have_prev = False - - start = self.__wrapTo360(start) - end = self.__wrapTo360(end) - - a1 = start - a2 = end - sign = 1 - - if reverse: - a1 = end - a2 = start - sign = -1 - - for a in xrange(steps): - angle = np.deg2rad(a1 + sign*a*step) - x2 = x0 + np.cos(angle)*r - y2 = y0 + np.sin(angle)*r - - points.append( (x2,y2) ) - - x1 = x2 - y1 = y2 - have_prev = True - - if (a1 + sign*(steps-1)*step) != a2: - angle = np.deg2rad(a2) - x2 = x0 + np.cos(angle)*r - y2 = y0 + np.sin(angle)*r - - points.append( (x2,y2) ) - - return points - - def __rbasis(self, c, t, npts, x, h): - """ - Generates rational B-spline basis functions for an open knot vector. - :note: Source code converted from LibreCad (rs_spline.cpp) - - """ - nplusc = npts + c - temp = np.zeros(nplusc) - - # calculate the first order nonrational basis functions n[i] - for i in xrange(nplusc-1): - if t >= x[i] and t < x[i+1]: - temp[i] = 1 - - # calculate the higher order nonrational basis functions - for k in xrange(2,c+1): - for i in xrange(nplusc-k): - # if the lower order basis function is zero skip the calculation - if temp[i] != 0: - temp[i] = ((t-x[i])*temp[i])/(x[i+k-1]-x[i]) - - # if the lower order basis function is zero skip the calculation - if temp[i+1] != 0: - temp[i] += ((x[i+k]-t)*temp[i+1])/(x[i+k]-x[i+1]) - - # pick up last point - if t >= x[nplusc-1]: - temp[npts-1] = 1 - - # calculate sum for denominator of rational basis functions - sum = 0.0 - for i in xrange(npts): - sum += temp[i]*h[i] - - r = np.zeros(npts) - # form rational basis functions and put in r vector - if sum != 0: - for i in xrange(npts): - r[i] = (temp[i]*h[i])/sum - return r - - def __rbspline(self, npts, k, p1, b, knot): - """ - Generates a rational B-spline curve using a uniform open knot vector. - :note: Source code converted from LibreCad (rs_spline.cpp) - - :param npts: Number of control points - :param k: Spline degree - :param b: Control point list - :param knot: knot list - """ - p = [] - h = np.ones(npts+1) - nplusc = npts + k - - # generate the open knot vector (we have one already) - x = knot - - # calculate the points on the rational B-spline curve - t = 0.0 - step = x[nplusc-1] / (p1-1) - - vp = np.zeros(shape=(p1,2)) - - for i in xrange(p1): - if x[nplusc-1] - t < 5e-6: - t = x[nplusc-1] - # generate the basis function for this value of t - nbasis = self.__rbasis(k, t, npts, x, h) - - # generate a point on the curve - for j in xrange(npts): - x0 = b[j][0] * nbasis[j] - y0 = b[j][1] * nbasis[j] - vp[i] += ( x0, y0 ) - - t += step - - p.append( vp[i] ) - - return p - - def transform(self, sx = 1.0, sy = 1.0, ox = 0.0, oy = 0.0): - self.max_x *= sx - self.max_y *= sy - self.min_x *= sx - self.min_y *= sy - - for l in self.layers: - for e in l.primitives: - t = e['type'] - - if 'points' in e: - points = [] - for p in e['points']: - points.append( ( (ox + p[0])*sx, (oy + p[1])*sy) ) - e['points'] = points - - if t == 'polyline' or t == 'spline': - pass - - elif t == 'circle' or t == 'arc': - p = e['center'] - e['center'] = ( (ox + p[0])*sx, (oy + p[1])*sy) - e['radius'] *= (sx+sy) / 2.0 - - elif t == 'ellipse': - p = e['center'] - e['center'] = ( (ox + p[0])*sx, (oy + p[1])*sy) - m = e['major_axis'] - e['major_axis'] = ( m[0] * sx, m[1] * sy, 0.0) - # TODO scale other parameters - - def scale(self, sx = 1.0, sy = 1.0): - self.transform(sx, sy) - - def width(self): - return self.max_x - self.min_x - - def height(self): - return self.max_y - self.min_y - - def scale_to(self, target_width = 0.0, target_height = 0.0): - width = self.width() - height = self.height() - - sx = 1.0 - sy = 1.0 - - if target_width != 0.0: - sx = target_width / width - sy = sx - - if target_height != 0.0: - sy = target_height / height - sx = sy - - self.scale(sx, sy) - - def normalize(self, margin = 0.1): - self.transform(1.0, 1.0, -self.min_x+margin, -self.min_y+margin) - - width = self.max_x - self.min_x - height = self.max_y - self.min_y - - self.min_x = 0 - self.min_y = 0 - self.max_x = width - self.max_y = height - - def clear(self): - """ - Clear all values. - """ - self.layers = [] - self.max_x = 0 - self.max_y = 0 - self.min_x = 0 - self.min_y = 0 - - def load_from_dxf(self, filename, clear=True): - dxf = dxfgrabber.readfile(filename) - - if clear: - self.clear() - - layer_map = {} - - print "- Layers:" - for l in dxf.layers: - print " ", l.name, l.color, l.linetype - color = 255 - layer_map[l.name] = self.add_layer(l.name, color) - - #~ print "- Blocks:" - #~ for b in dxf.blocks: - #~ print b.name - - #~ print "- Objects:" - for o in dxf.objects: - print o - - print "- Entries:" - for e in dxf.entities: - t = e.dxftype - print "== ", t - if t == 'LWPOLYLINE' or t == 'POLYLINE': - is_closed = False - if e.is_closed: - is_closed = True - self.add_polyline(e.points, e.bulge, is_closed, layer_map[e.layer]) - - elif t == 'LINE': - self.add_line(e.start, e.end, layer_map[e.layer]) - - elif t == 'CIRCLE': - self.add_circle(e.center, e.radius, layer_map[e.layer]) - - elif t == 'ELLIPSE': - self.add_ellipse(e.center, e.major_axis, e.ratio, np.rad2deg(e.start_param), np.rad2deg(e.end_param), layer_map[e.layer] ) - - elif t == 'ARC': - self.add_arc(e.center, e.radius, e.start_angle, e.end_angle, layer_map[e.layer]) - - elif t == 'SPLINE': - self.add_spline(e.control_points, e.knots, e.degree, layer_map[e.layer]) - - elif t == 'MTEXT': - ln = len(e.lines()) - lh = e.height - w = e.rect_width - x = e.insert[0] - y = e.insert[1] - lh - align = self.ALIGN_LEFT - - if e.attachment_point == self.TOP_LEFT: - pass - elif e.attachment_point == self.TOP_CENTER: - #~ x -= w * 0.5 - align = self.ALIGN_CENTER - elif e.attachment_point == self.TOP_RIGHT: - #~ x -= w - align = self.ALIGN_RIGHT - elif e.attachment_point == self.MIDDLE_LEFT: - y += ln*lh * 0.5 - elif e.attachment_point == self.MIDDLE_CENTER: - #~ x -= w * 0.5 - y += ln*lh * 0.5 - align = self.ALIGN_CENTER - elif e.attachment_point == self.MIDDLE_RIGHT: - #~ x -= w - y += ln*lh * 0.5 - align = self.ALIGN_RIGHT - elif e.attachment_point == self.BOTTOM_LEFT: - y += ln*lh - pass - elif e.attachment_point == self.BOTTOM_CENTER: - #~ x -= w * 0.5 - y += ln*lh - align = self.ALIGN_CENTER - elif e.attachment_point == self.BOTTOM_RIGHT: - #~ x -= w - y += ln*lh - align = self.ALIGN_RIGHT - - self.add_text((x,y), align, e.rect_width, e.height, e.xdirection, e.font, e.lines(), layer=layer_map[e.layer]) - diff --git a/scripts/py/img2gcode.py b/scripts/py/img2gcode.py deleted file mode 100755 index 34f544e..0000000 --- a/scripts/py/img2gcode.py +++ /dev/null @@ -1,642 +0,0 @@ -#!/bin/env python -# -*- coding: utf-8; -*- -# -# (c) 2016 FABtotum, http://www.fabtotum.com -# -# This file is part of FABUI. -# -# FABUI 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 2 of the License, or -# (at your option) any later version. -# -# FABUI 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 FABUI. If not, see . - -# Import standard python module -import argparse -import time -import gettext -import json -import os - -# Import external modules -import numpy as np -import cv2,cv -from loaders import dxfgrabber - -# Import internal modules -from common.drawing import Drawing2D -from output.laser import LaserEngraver -from output.debug import DebugEngraver - -# Set up message catalog access -tr = gettext.translation('img2gcode', 'locale', fallback=True) -_ = tr.ugettext - -def preprocess_dxf_image(filename): - output = Drawing2D() - output.load_from_dxf(filename) - return output - -def preprocess_raster_image(image_file, target_width, target_height, dot_size, levels = 6, invert = False, crop = '', debug=False): - """ - Convert a raster image to horizontal line list classified into levels by color intensity. - Only width or height has to be non-zero as the other value is automatically calculated - based on the image width/height ration. - - :param image_file: Raster image filename. - :param target_width: Target width in mm. - :param target_height: Target height in mm. - :param dot_size: Smallest engraver detail (mm). - :param levels: Number of gray levels. - :param invert: Invert gray intensity. - :param crop: Crop image (x,y,w,h) (pixels) - """ - - img = cv2.imread(image_file) - - #### Resize: BEGIN #### - - h, w = img.shape[:2] - - # Minimal dots per mm - min_dpm = 1 #int(1 / dot_size) - - dpm = int(1 / dot_size) - - # Default scaling factor for Width - sx = 1.0 - # Default scaling factor for Height - sy = 1.0 - - new_w = w - new_h = h - - if target_width: - # Dots per mm based on given parameters - dpm_x = target_width / float(w) - - # If DPM is less then minimal, scale the image so that pixels - # are of proper size - if dpm_x < 1.0: - max_w = float(target_width) - new_w = int( w * dpm_x * dpm ) - sx = 1.0 / (dpm_x * dpm) - print "new_w", new_w - - if target_height: - # Dots per mm based on given parameters - dpm_y = float(target_height) / float(h) - ppm_y = float(h) / float(target_height) - - # If DPM is less then minimal, scale the image so that pixels - # are of proper size - if dpm_y < 1.0: - max_h = float(target_height) - new_h = int( h * dpm_y * dpm ) - sy = 1.0 / (dpm_y * dpm) - print "new_h", new_h - - sx = w / float(new_w) - sy = h / float(new_h) - scale = max(sx,sy) - print "scale", scale - - w = int(w / scale) - h = int(h / scale) - - if target_width == 0: - target_width = (w * float(target_height)) / h - - if target_height == 0: - target_height = (h * float(target_width)) / w - - if scale > 1: - work_width = w - work_height = h - else: - work_width = int(target_width / dot_size) - work_height = int(target_height / dot_size) - - print "Target (mm)", target_width, target_height - - # Resize image if needed - if scale != 1: - print "Resize to {0}x{1}".format(w,h) - img = cv2.resize(img,(w, h), interpolation = cv2.INTER_CUBIC) - - #### Resize: END #### - - if crop: - crop = crop.split(',') - x1 = int(crop[0]) - x2 = x1 + int(crop[2]) - y1 = int(crop[1]) - y2 = y1 + int(crop[3]) - print "Crop {0} {1} {2} {3}".format(x1, x2, y1, y2) - img = img[y1:y2, x1:x2] - #~ if debug: - #~ cv2.imwrite('cropped.png', img) - - # Flip image on the Y axis to compensate for image Y axis and - # machine Y axis orientation - img = cv2.flip(img,0) - - # ? - Z = img.reshape((-1,3)) - # Convert to np.float32 - Z = np.float32(Z) - # Define criteria, number of clusters(K) and apply kmeans() - criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0) - # Define number of levels - K = levels - ret,label,center = cv2.kmeans(Z, K, criteria, 10, cv2.KMEANS_RANDOM_CENTERS) - - shades_of_gray = np.ndarray( (center.shape[0], 1) , dtype=int) - i = 0 - for c in center: - x = c[0] * 0.3 + c[1] * 0.59 + c[2] * 0.11 - if invert: - x = 255 - int(x) - shades_of_gray[i] = int(x) - i += 1 - - # Center is an array of cluster representative colors - # label is an array of cluster labels for every pixel (label=[0..K-1] ) - h, w = img.shape[:2] - - flat_labels = label.flatten() - - res = shades_of_gray[flat_labels] - res2 = res.reshape( (h,w,1) ) - img = res2 - - # Invert image if requested - if invert: - img = 255 - img - - # save a preview for internal use - #~ if debug: - #~ cv2.imwrite('img_preprocess.png', img) - - sorted_geays = shades_of_gray.copy() - sorted_geays.sort(axis=0) - mapped = range(shades_of_gray.shape[0]) - rmapped = range(shades_of_gray.shape[0]) - - for i in xrange(sorted_geays.shape[0]): - value = sorted_geays[i] - idx = np.nonzero(shades_of_gray == value) - j = int(idx[0]) - mapped[i] = j - rmapped[j] = i - - results = { - 'level' : range(levels), - 'width' : w, - 'height' : h, - 'target_width' : target_width, - 'target_height' : target_height - } - - for lvl in xrange(levels): - - value = int(shades_of_gray[ rmapped[lvl] ]) - - results['level'][lvl] = { - 'lines' : [], - 'value' : value, - 'percentage' : float(value) / 255.0, - } - - mask = np.zeros((shades_of_gray.shape), dtype=np.int) - mask[ lvl ] = 255 - - res = mask[flat_labels] - res2 = res.reshape( (h,w,1) ) - - # Invert image if requested - if invert: - res2 = 255 - res2 - - # save a preview for internal use - #~ if debug: - #~ cv2.imwrite('img_preprocess_{0}.png'.format(lvl), res2) - - for ii in xrange(len(mapped)): - results['level'][ii]['lines'] = [] - - results['work_width'] = work_width - results['work_height'] = work_height - - for y in xrange(h): - for ii in xrange(len(mapped)): - results['level'][ii]['lines'].append([]) - - old_lbl = -1 - new_lbl = -1 - start_x = 0 - - for x in xrange(w): - idx = y * w + x - new_lbl = flat_labels[idx] - if new_lbl != old_lbl: - if x > start_x: - lbl = mapped[old_lbl] - - x1 = int( float(start_x * work_width) / w ) - x2 = int( float(x * work_width) / w ) - - results['level'][lbl]['lines'][y].append( (x1, x2) ) - old_lbl = new_lbl - start_x = x - - if x >= start_x: - lbl = mapped[old_lbl] - x1 = int( float(start_x * work_width) / w ) - x2 = int( float(x * work_width) / w )+1 - results['level'][lbl]['lines'][y].append( (x1, x2) ) - - return results - -def preprocess_raster_image_bw(image_file, target_width, target_height, dot_size, threshold = 127, invert = False, crop = '', debug=False): - """ - Convert a raster image to horizontal line list classified into levels by color intensity. - Only width or height has to be non-zero as the other value is automatically calculated - based on the image width/height ration. - - :param image_file: Raster image filename. - :param target_width: Target width in mm. - :param target_height: Target height in mm. - :param dot_size: Smallest engraver detail (mm). - :param levels: Number of gray levels. - :param invert: Invert gray intensity. - :param crop: Crop image (x,y,w,h) (pixels) - """ - - img = cv2.imread(image_file) - - #### Resize: BEGIN #### - - h, w = img.shape[:2] - - # Minimal dots per mm - min_dpm = 1 #int(1 / dot_size) - - dpm = int(1 / dot_size) - - # Default scaling factor for Width - sx = 1.0 - # Default scaling factor for Height - sy = 1.0 - - new_w = w - new_h = h - - if target_width: - # Dots per mm based on given parameters - dpm_x = target_width / float(w) - - # If DPM is less then minimal, scale the image so that pixels - # are of proper size - if dpm_x < 1.0: - max_w = float(target_width) - new_w = int( w * dpm_x * dpm ) - sx = 1.0 / (dpm_x * dpm) - print "new_w", new_w - - if target_height: - # Dots per mm based on given parameters - dpm_y = float(target_height) / float(h) - ppm_y = float(h) / float(target_height) - - # If DPM is less then minimal, scale the image so that pixels - # are of proper size - if dpm_y < 1.0: - max_h = float(target_height) - new_h = int( h * dpm_y * dpm ) - sy = 1.0 / (dpm_y * dpm) - print "new_h", new_h - - sx = w / float(new_w) - sy = h / float(new_h) - scale = max(sx,sy) - print "scale", scale - - w = int(w / scale) - h = int(h / scale) - - if target_width == 0: - target_width = (w * float(target_height)) / h - - if target_height == 0: - target_height = (h * float(target_width)) / w - - if scale > 1: - work_width = w - work_height = h - else: - work_width = int(target_width / dot_size) - work_height = int(target_height / dot_size) - - print "Target (mm)", target_width, target_height - - # Resize image if needed - if scale != 1: - print "Resize to {0}x{1}".format(w,h) - img = cv2.resize(img,(w, h), interpolation = cv2.INTER_CUBIC) - - #### Resize: END #### - - if crop: - crop = crop.split(',') - x1 = int(crop[0]) - x2 = x1 + int(crop[2]) - y1 = int(crop[1]) - y2 = y1 + int(crop[3]) - print "Crop {0} {1} {2} {3}".format(x1, x2, y1, y2) - img = img[y1:y2, x1:x2] - if debug: - cv2.imwrite('cropped.png', img) - - # Flip image on the Y axis to compensate for image Y axis and - # machine Y axis orientation - img = cv2.flip(img,0) - - t = cv2.THRESH_BINARY - if invert: - t = cv2.THRESH_BINARY_INV - - ACTIVE_LABEL = 1 - - gray_image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) - ret,thresh1 = cv2.threshold(gray_image, threshold, ACTIVE_LABEL, t) - - flat_labels = thresh1.flatten() - - # Center is an array of cluster representative colors - # label is an array of cluster labels for every pixel (label=[0..K-1] ) - h, w = img.shape[:2] - - # save a preview for internal use - if debug: - # Invert image if requested - img = thresh1 * 255 - cv2.imwrite('img_preprocess.png', img) - - results = { - 'level' : { - 0 : { - 'lines' : [], - 'value' : 255, - 'percentage' : 1.0, - } - }, - 'width' : w, - 'height' : h, - 'target_width' : target_width, - 'target_height' : target_height - } - - results['work_width'] = work_width - results['work_height'] = work_height - - for y in xrange(h): - results['level'][0]['lines'].append([]) - - old_lbl = -1 - new_lbl = -1 - start_x = 0 - - for x in xrange(w): - idx = y * w + x - new_lbl = flat_labels[idx] - if new_lbl != old_lbl: - if x > start_x: - lbl = old_lbl - - x1 = int( float(start_x * work_width) / w ) - x2 = int( float(x * work_width) / w ) - if lbl == ACTIVE_LABEL: - results['level'][0]['lines'][y].append( (x1, x2) ) - - start_x = x - old_lbl = new_lbl - - if x >= start_x: - lbl = old_lbl - x1 = int( float(start_x * work_width) / w ) - x2 = int( float(x * work_width) / w )+1 - if lbl == ACTIVE_LABEL: - results['level'][0]['lines'][y].append( (x1, x2) ) - - return results - -def vectorize_raster(data, preset): - """ - Draw horizontal lines. - """ - work_width = data['work_width'] - width = data['width'] - work_height = data['work_height'] - height = data['height'] - - output = Drawing2D() - - reverse = False - interleave = False - - lvl = 0 - for level in data['level']: - value = data['level'][lvl]['value'] - #~ percent = data['level'][lvl]['percentage'] - - lyr_idx = output.add_layer('level_{0}'.format(lvl), value) - - #~ self.comment(" Level {0}".format(lvl)) - #self.level(value) - - old_img_y = -1 - y1 = 0 - - #~ if 'interleave' in preset['skip']: - #~ if preset['skip']['interleave']: - #~ interleave = not interleave - - for y in xrange(work_height): - # Get the pixel row corresponding to the burning y - # Note: this is needed as a pixel might contain multiple laser lines (fat pixels) - img_y = int((float(height) * y * 100.0) / float(work_height)) / 100 - - # Detect the boundry of the pixel rows and create a fill_rect - # command from it. This way it's easier to handle it as a bulk command. - if old_img_y != img_y: - - #~ self.comment(' Row {0}'.format(y)) - - old_img_y = img_y - # Update row end - y2 = y+1 - did_something = False - - # Get all the lines in this row - lines = data['level'][lvl]['lines'][img_y] - - # Change the direction of burning to reduce laser movement - if reverse: - lines = reversed(lines) - - for line in lines: - if reverse: - # Swap x1/x2 if reversed - x1 = line[1] - x2 = line[0] - else: - x1 = line[0] - x2 = line[1] - - output.add_rect(x1, y1, x2, y2, lyr_idx, filled=True) - did_something = True - - # If any burning happened, reverse the burning direction - if did_something: - reverse = not reverse - - # Next row starts where this one ended - y1 = y2 - - lvl += 1 - - return output - -def main(): - # SETTING EXPECTED ARGUMENTS - parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument("preset_file", help=_("Preset file [json file]")) - parser.add_argument("image_file", help=_("Image file [raster: jpg, png; vector: dxf]")) - parser.add_argument("-o", "--output", help=_("Output gcode file."), default='laser.gcode') - # Image preprocessing - parser.add_argument("-l", "--levels", help=_("Laser power levels [used for raster, range: 2..10]"), default=6) - parser.add_argument("-i", "--invert", action='store_true', help=_("Invert color values"), default=False) - parser.add_argument("-d", "--debug", action='store_true', help=_("Enable debug output"), default=False) - parser.add_argument("-C", "--crop", help=_("Crop image. Use x,y,w,h'. Example: -c 0,0,100,100"), default='') - # Vectorization - parser.add_argument("-W", "--width", help=_("Engraving width"), default=0) - parser.add_argument("-H", "--height", help=_("Engraving height"), default=0) - parser.add_argument("-D", "--dot-size", help=_("Engraving dot size [mm]"), default=0.1) - parser.add_argument("-S", "--shortest-line", help=_("Ignore lines shorter then this value [mm]"), default=0.0) - # Toolpath - parser.add_argument("-O", "--optimize", help=_("Optimize toolpath to reduce travel distance [0=off, 1=closest, 2=closes+reverse]"), default=1) - parser.add_argument("-s", "--statistics", action='store_true', help=_("Output toolpath statistics"), default=False) - - - # GET ARGUMENTS - args = parser.parse_args() - - # INIT VARs - gcode_file = args.output - image_file = args.image_file - preset_file = args.preset_file - target_width = float(args.width) - target_height = float(args.height) - levels = int(args.levels) - invert = bool(args.invert) - debug = bool(args.debug) - crop = args.crop - dot_size = float(args.dot_size) - shortest_line = float(args.shortest_line) - optimize = int(args.optimize) - statistics = bool(args.statistics) - - if levels < 1 or levels > 10: - print "Level is out of allowed range" - parser.print_help() - exit(1) - - filename, ext = os.path.splitext(image_file) - ext = ext.lower() - - with open(preset_file) as f: - preset = json.load(f) - - if ext == '.dxf': - drawing = preprocess_dxf_image(image_file) - drawing.normalize() - drawing.scale_to(target_width, target_height) - - lsr = LaserEngraver(gcode_file, (1.0, 1.0), preset, statistics=statistics) - lsr.start() - lsr.draw(drawing, optimize=optimize) - lsr.end() - - if statistics: - print lsr.stats - - if debug: - #~ os.system('LC_NUMERIC=C camotics output.gcode') - work_path = os.path.dirname(gcode_file) - dbg_file = os.path.join(work_path,'debug.png') - - dpm = 1.0 / dot_size - - work_width = int(drawing.width() * dpm) + 5 - work_height = int(drawing.height() * dpm) + 5 - - print "Debug (WxH):", work_width, work_height - - color = 0 - #~ if invert: - #~ color = 255 - - drawing.transform(dpm, dpm, 0, 0) - - dbg = DebugEngraver(dbg_file, work_width, work_height, color, dot_size, True, preset) - dbg.start() - dbg.draw(drawing) - dbg.end() - - elif ext == '.jpg' or ext == '.jpeg' or ext == '.png': - - if levels > 1: - result = preprocess_raster_image(image_file, target_width, target_height, dot_size, levels, invert, crop, debug=debug) - else: - result = preprocess_raster_image_bw(image_file, target_width, target_height, dot_size, 127, invert, crop, debug=debug) - - drawing = vectorize_raster(result, preset) - - s = dot_size - - lsr = LaserEngraver(gcode_file, (s,s), preset, statistics=statistics) - lsr.start() - lsr.draw(drawing, optimize=0) - lsr.end() - - if statistics: - print lsr.stats - - if debug: - #~ os.system('LC_NUMERIC=C camotics output.gcode') - - work_path = os.path.dirname(gcode_file) - dbg_file = os.path.join(work_path,'debug.png') - - color = 0 - #~ if invert: - #~ color = 0 - - dbg = DebugEngraver(dbg_file, result['work_width'], result['work_height'], color, dot_size, True, preset) - dbg.start() - dbg.draw(drawing) - dbg.end() - else: - print "Unsupported filetype" - exit(1) - -if __name__ == "__main__": - main() diff --git a/scripts/py/loaders/__init__.py b/scripts/py/loaders/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/scripts/py/loaders/dxfgrabber/__init__.py b/scripts/py/loaders/dxfgrabber/__init__.py deleted file mode 100644 index 105bc46..0000000 --- a/scripts/py/loaders/dxfgrabber/__init__.py +++ /dev/null @@ -1,67 +0,0 @@ -# dxfgrabber - copyright (C) 2012 by Manfred Moitzi (mozman) -# Purpose: grab information from DXF drawings - all DXF versions supported -# Created: 21.07.2012 -# License: MIT License - -version = (0, 8, 1) -VERSION = "%d.%d.%d" % version - -__author__ = "mozman " -__doc__ = """A Python library to grab information from DXF drawings - all DXF versions supported.""" - - -# Python27/3x support should be done here -import sys - -PYTHON3 = sys.version_info.major > 2 - -if PYTHON3: - tostr = str -else: # PYTHON27 - tostr = unicode - -# end of Python 2/3 adaption -# if tostr does not work, look at package 'dxfwrite' for escaping unicode chars - -from .const import BYBLOCK, BYLAYER - -import io -from .tags import dxfinfo -from .color import aci_to_true_color - - -def read(stream, options=None): - if hasattr(stream, 'readline'): - from .drawing import Drawing - return Drawing(stream, options) - else: - raise AttributeError('stream object requires a readline() method.') - - -def readfile(filename, options=None): - try: # is it ascii code-page encoded? - return readfile_as_asc(filename, options) - except UnicodeDecodeError: # try unicode and ignore errors - return readfile_as_utf8(filename, options, errors='ignore') - - -def readfile_as_utf8(filename, options=None, errors='strict'): - return _read_encoded_file(filename, options, encoding='utf-8', errors=errors) - - -def readfile_as_asc(filename, options=None): - def get_encoding(): - with io.open(filename) as fp: - info = dxfinfo(fp) - return info.encoding - - return _read_encoded_file(filename, options, encoding=get_encoding()) - - -def _read_encoded_file(filename, options=None, encoding='utf-8', errors='strict'): - from .drawing import Drawing - - with io.open(filename, encoding=encoding, errors=errors) as fp: - dwg = Drawing(fp, options) - dwg.filename = filename - return dwg diff --git a/scripts/py/loaders/dxfgrabber/acdsdata.py b/scripts/py/loaders/dxfgrabber/acdsdata.py deleted file mode 100644 index 29e70e4..0000000 --- a/scripts/py/loaders/dxfgrabber/acdsdata.py +++ /dev/null @@ -1,88 +0,0 @@ -# Purpose: acdsdata section manager -# Created: 05.05.2014 -# Copyright (C) 2014, Manfred Moitzi -# License: MIT License - -from __future__ import unicode_literals -__author__ = "mozman " - -from itertools import islice -from .tags import TagGroups, DXFStructureError, Tags, binary_encoded_data_to_bytes - - -class AcDsDataSection(object): - name = 'acdsdata' - - def __init__(self): - # Standard_ACIS_Binary (SAB) data store, key = handle of DXF Entity in the ENTITIES section: BODY, 3DSOLID - # SURFACE, PLANESURFACE, REGION - self.sab_data = {} - - @classmethod - def from_tags(cls, tags, drawing): - data_section = cls() - data_section._build(tags) - return data_section - - def _build(self, tags): - if len(tags) == 3: # empty entities section - return - - for group in TagGroups(islice(tags, 2, len(tags)-1)): - data_record = AcDsDataRecord(Tags(group)) - if data_record.dxftype == 'ACDSRECORD': - asm_data = data_record.get_section('ASM_Data', None) - if asm_data is not None: - self.add_asm_data(data_record) - - def add_asm_data(self, acdsrecord): - """ Store SAB data as binary string in the sab_data dict, with handle to owner Entity as key. - """ - try: - asm_data = acdsrecord.get_section('ASM_Data') - entity_id = acdsrecord.get_section('AcDbDs::ID') - except ValueError: - return - else: - handle = entity_id[2].value - binary_data_text = (tag.value for tag in asm_data if tag.code == 310) - binary_data = binary_encoded_data_to_bytes(binary_data_text) - self.sab_data[handle] = binary_data - - -class Section(Tags): - @property - def name(self): - return self[0].value - - @property - def type(self): - return self[1].value - - @property - def data(self): - return self[2:] - - -class AcDsDataRecord(object): - def __init__(self, tags): - self.dxftype = tags[0].value - start_index = 2 - while tags[start_index].code != 2: - start_index += 1 - self.sections = [Section(tags) for tags in TagGroups(islice(tags, start_index, None), split_code=2)] - - def has_section(self, name): - return self.get_section(name, default=None) is not None - - def get_section(self, name, default=KeyError): - for section in self.sections: - if section.name == name: - return section - if default is KeyError: - raise KeyError(name) - else: - return default - - def __getitem__(self, name): - return self.get_section(name) diff --git a/scripts/py/loaders/dxfgrabber/blockssection.py b/scripts/py/loaders/dxfgrabber/blockssection.py deleted file mode 100644 index c4336a7..0000000 --- a/scripts/py/loaders/dxfgrabber/blockssection.py +++ /dev/null @@ -1,57 +0,0 @@ -# Purpose: blocks section -# Created: 09.08.2012, taken from my package ezdxf -# Copyright (C) 2011, Manfred Moitzi -# License: MIT-License -from __future__ import unicode_literals -__author__ = "mozman " - -from itertools import islice - -from .tags import TagGroups -from .entitysection import build_entities - - -class BlocksSection(object): - name = 'blocks' - - def __init__(self): - self._blocks = dict() - - @staticmethod - def from_tags(tags, drawing): - blocks_section = BlocksSection() - if drawing.grab_blocks: - blocks_section._build(tags) - return blocks_section - - def _build(self, tags): - if len(tags) == 3: # empty block section - return - groups = list() - for group in TagGroups(islice(tags, 2, len(tags)-1)): - groups.append(group) - if group[0].value == 'ENDBLK': - entities = build_entities(groups) - block = entities[0] - block.set_entities(entities[1:-1]) - self._add(block) - groups = list() - - def _add(self, block): - self._blocks[block.name] = block - - # start of public interface - def __len__(self): - return len(self._blocks) - - def __iter__(self): - return iter(self._blocks.values()) - - def __contains__(self, name): - return name in self._blocks - - def __getitem__(self, name): - return self._blocks[name] - - def get(self, name, default=None): - return self._blocks.get(name, default) diff --git a/scripts/py/loaders/dxfgrabber/codepage.py b/scripts/py/loaders/dxfgrabber/codepage.py deleted file mode 100644 index 76baa54..0000000 --- a/scripts/py/loaders/dxfgrabber/codepage.py +++ /dev/null @@ -1,37 +0,0 @@ -# Purpose: codepage handling -# Created: 21.07.2012, taken from my ezdxf project -# Copyright (C) 2012, Manfred Moitzi -# License: MIT License -from __future__ import unicode_literals -__author__ = "mozman " - -codepages = { - '874': 'cp874', # Thai, - '932': 'cp932', # Japanese - '936': 'gbk', # UnifiedChinese - '949': 'cp949', # Korean - '950': 'cp950', # TradChinese - '1250': 'cp1250', # CentralEurope - '1251': 'cp1251', # Cyrillic - '1252': 'cp1252', # WesternEurope - '1253': 'cp1253', # Greek - '1254': 'cp1254', # Turkish - '1255': 'cp1255', # Hebrew - '1256': 'cp1256', # Arabic - '1257': 'cp1257', # Baltic - '1258': 'cp1258', # Vietnam -} - - -def toencoding(dxfcodepage): - for codepage, encoding in codepages.items(): - if dxfcodepage.endswith(codepage): - return encoding - return 'cp1252' - - -def tocodepage(encoding): - for codepage, enc in codepages.items(): - if enc == encoding: - return 'ANSI_'+codepage - return 'ANSI_1252' diff --git a/scripts/py/loaders/dxfgrabber/color.py b/scripts/py/loaders/dxfgrabber/color.py deleted file mode 100644 index 832e4ae..0000000 --- a/scripts/py/loaders/dxfgrabber/color.py +++ /dev/null @@ -1,301 +0,0 @@ -__author__ = 'manfred' - - -class TrueColor(int): - def rgb(self): - return (self >> 16) & 0xFF, (self >> 8) & 0xFF, self & 0xFF - - @property - def r(self): - return (self >> 16) & 0xFF - - @property - def g(self): - return (self >> 8) & 0xFF - - @property - def b(self): - return self & 0xFF - - def __getitem__(self, item): - if item == 0: - return self.r - elif item == 1: - return self.g - elif item == 2: - return self.b - raise IndexError(item) - - @staticmethod - def from_rgb(r, g, b): - return TrueColor(((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff)) - - @staticmethod - def from_aci(index): - if index < 1: - raise IndexError(index) - return dxf_default_colors[index] - - -def aci_to_true_color(index): - return TrueColor.from_aci(index) - - -dxf_default_colors = [ - TrueColor(0x000000), - TrueColor(0xff0000), - TrueColor(0xffff00), - TrueColor(0x00ff00), - TrueColor(0x00ffff), - TrueColor(0x0000ff), - TrueColor(0xff00ff), - TrueColor(0xffffff), - TrueColor(0x414141), - TrueColor(0x808080), - TrueColor(0xff0000), - TrueColor(0xffaaaa), - TrueColor(0xbd0000), - TrueColor(0xbd7e7e), - TrueColor(0x810000), - TrueColor(0x815656), - TrueColor(0x680000), - TrueColor(0x684545), - TrueColor(0x4f0000), - TrueColor(0x4f3535), - TrueColor(0xff3f00), - TrueColor(0xffbfaa), - TrueColor(0xbd2e00), - TrueColor(0xbd8d7e), - TrueColor(0x811f00), - TrueColor(0x816056), - TrueColor(0x681900), - TrueColor(0x684e45), - TrueColor(0x4f1300), - TrueColor(0x4f3b35), - TrueColor(0xff7f00), - TrueColor(0xffd4aa), - TrueColor(0xbd5e00), - TrueColor(0xbd9d7e), - TrueColor(0x814000), - TrueColor(0x816b56), - TrueColor(0x683400), - TrueColor(0x685645), - TrueColor(0x4f2700), - TrueColor(0x4f4235), - TrueColor(0xffbf00), - TrueColor(0xffeaaa), - TrueColor(0xbd8d00), - TrueColor(0xbdad7e), - TrueColor(0x816000), - TrueColor(0x817656), - TrueColor(0x684e00), - TrueColor(0x685f45), - TrueColor(0x4f3b00), - TrueColor(0x4f4935), - TrueColor(0xffff00), - TrueColor(0xffffaa), - TrueColor(0xbdbd00), - TrueColor(0xbdbd7e), - TrueColor(0x818100), - TrueColor(0x818156), - TrueColor(0x686800), - TrueColor(0x686845), - TrueColor(0x4f4f00), - TrueColor(0x4f4f35), - TrueColor(0xbfff00), - TrueColor(0xeaffaa), - TrueColor(0x8dbd00), - TrueColor(0xadbd7e), - TrueColor(0x608100), - TrueColor(0x768156), - TrueColor(0x4e6800), - TrueColor(0x5f6845), - TrueColor(0x3b4f00), - TrueColor(0x494f35), - TrueColor(0x7fff00), - TrueColor(0xd4ffaa), - TrueColor(0x5ebd00), - TrueColor(0x9dbd7e), - TrueColor(0x408100), - TrueColor(0x6b8156), - TrueColor(0x346800), - TrueColor(0x566845), - TrueColor(0x274f00), - TrueColor(0x424f35), - TrueColor(0x3fff00), - TrueColor(0xbfffaa), - TrueColor(0x2ebd00), - TrueColor(0x8dbd7e), - TrueColor(0x1f8100), - TrueColor(0x608156), - TrueColor(0x196800), - TrueColor(0x4e6845), - TrueColor(0x134f00), - TrueColor(0x3b4f35), - TrueColor(0x00ff00), - TrueColor(0xaaffaa), - TrueColor(0x00bd00), - TrueColor(0x7ebd7e), - TrueColor(0x008100), - TrueColor(0x568156), - TrueColor(0x006800), - TrueColor(0x456845), - TrueColor(0x004f00), - TrueColor(0x354f35), - TrueColor(0x00ff3f), - TrueColor(0xaaffbf), - TrueColor(0x00bd2e), - TrueColor(0x7ebd8d), - TrueColor(0x00811f), - TrueColor(0x568160), - TrueColor(0x006819), - TrueColor(0x45684e), - TrueColor(0x004f13), - TrueColor(0x354f3b), - TrueColor(0x00ff7f), - TrueColor(0xaaffd4), - TrueColor(0x00bd5e), - TrueColor(0x7ebd9d), - TrueColor(0x008140), - TrueColor(0x56816b), - TrueColor(0x006834), - TrueColor(0x456856), - TrueColor(0x004f27), - TrueColor(0x354f42), - TrueColor(0x00ffbf), - TrueColor(0xaaffea), - TrueColor(0x00bd8d), - TrueColor(0x7ebdad), - TrueColor(0x008160), - TrueColor(0x568176), - TrueColor(0x00684e), - TrueColor(0x45685f), - TrueColor(0x004f3b), - TrueColor(0x354f49), - TrueColor(0x00ffff), - TrueColor(0xaaffff), - TrueColor(0x00bdbd), - TrueColor(0x7ebdbd), - TrueColor(0x008181), - TrueColor(0x568181), - TrueColor(0x006868), - TrueColor(0x456868), - TrueColor(0x004f4f), - TrueColor(0x354f4f), - TrueColor(0x00bfff), - TrueColor(0xaaeaff), - TrueColor(0x008dbd), - TrueColor(0x7eadbd), - TrueColor(0x006081), - TrueColor(0x567681), - TrueColor(0x004e68), - TrueColor(0x455f68), - TrueColor(0x003b4f), - TrueColor(0x35494f), - TrueColor(0x007fff), - TrueColor(0xaad4ff), - TrueColor(0x005ebd), - TrueColor(0x7e9dbd), - TrueColor(0x004081), - TrueColor(0x566b81), - TrueColor(0x003468), - TrueColor(0x455668), - TrueColor(0x00274f), - TrueColor(0x35424f), - TrueColor(0x003fff), - TrueColor(0xaabfff), - TrueColor(0x002ebd), - TrueColor(0x7e8dbd), - TrueColor(0x001f81), - TrueColor(0x566081), - TrueColor(0x001968), - TrueColor(0x454e68), - TrueColor(0x00134f), - TrueColor(0x353b4f), - TrueColor(0x0000ff), - TrueColor(0xaaaaff), - TrueColor(0x0000bd), - TrueColor(0x7e7ebd), - TrueColor(0x000081), - TrueColor(0x565681), - TrueColor(0x000068), - TrueColor(0x454568), - TrueColor(0x00004f), - TrueColor(0x35354f), - TrueColor(0x3f00ff), - TrueColor(0xbfaaff), - TrueColor(0x2e00bd), - TrueColor(0x8d7ebd), - TrueColor(0x1f0081), - TrueColor(0x605681), - TrueColor(0x190068), - TrueColor(0x4e4568), - TrueColor(0x13004f), - TrueColor(0x3b354f), - TrueColor(0x7f00ff), - TrueColor(0xd4aaff), - TrueColor(0x5e00bd), - TrueColor(0x9d7ebd), - TrueColor(0x400081), - TrueColor(0x6b5681), - TrueColor(0x340068), - TrueColor(0x564568), - TrueColor(0x27004f), - TrueColor(0x42354f), - TrueColor(0xbf00ff), - TrueColor(0xeaaaff), - TrueColor(0x8d00bd), - TrueColor(0xad7ebd), - TrueColor(0x600081), - TrueColor(0x765681), - TrueColor(0x4e0068), - TrueColor(0x5f4568), - TrueColor(0x3b004f), - TrueColor(0x49354f), - TrueColor(0xff00ff), - TrueColor(0xffaaff), - TrueColor(0xbd00bd), - TrueColor(0xbd7ebd), - TrueColor(0x810081), - TrueColor(0x815681), - TrueColor(0x680068), - TrueColor(0x684568), - TrueColor(0x4f004f), - TrueColor(0x4f354f), - TrueColor(0xff00bf), - TrueColor(0xffaaea), - TrueColor(0xbd008d), - TrueColor(0xbd7ead), - TrueColor(0x810060), - TrueColor(0x815676), - TrueColor(0x68004e), - TrueColor(0x68455f), - TrueColor(0x4f003b), - TrueColor(0x4f3549), - TrueColor(0xff007f), - TrueColor(0xffaad4), - TrueColor(0xbd005e), - TrueColor(0xbd7e9d), - TrueColor(0x810040), - TrueColor(0x81566b), - TrueColor(0x680034), - TrueColor(0x684556), - TrueColor(0x4f0027), - TrueColor(0x4f3542), - TrueColor(0xff003f), - TrueColor(0xffaabf), - TrueColor(0xbd002e), - TrueColor(0xbd7e8d), - TrueColor(0x81001f), - TrueColor(0x815660), - TrueColor(0x680019), - TrueColor(0x68454e), - TrueColor(0x4f0013), - TrueColor(0x4f353b), - TrueColor(0x333333), - TrueColor(0x505050), - TrueColor(0x696969), - TrueColor(0x828282), - TrueColor(0xbebebe), - TrueColor(0xffffff), -] diff --git a/scripts/py/loaders/dxfgrabber/const.py b/scripts/py/loaders/dxfgrabber/const.py deleted file mode 100644 index fc3a747..0000000 --- a/scripts/py/loaders/dxfgrabber/const.py +++ /dev/null @@ -1,110 +0,0 @@ -# Purpose: constant values -# Created: 21.07.2012, taken from my ezdxf project -# Copyright (C) 2012, Manfred Moitzi -# License: MIT License -from __future__ import unicode_literals -__author__ = "mozman " - -ENV_CYTHON = 'DXFGRABBER_CYTHON' - -BYBLOCK = 0 -BYLAYER = 256 - -XTYPE_NONE = 0 -XTYPE_2D = 1 -XTYPE_3D = 2 -XTYPE_2D_3D = 3 - -acadrelease = { - 'AC1009': 'R12', - 'AC1012': 'R13', - 'AC1014': 'R14', - 'AC1015': 'R2000', - 'AC1018': 'R2004', - 'AC1021': 'R2007', - 'AC1024': 'R2010', -} - -dxfversion = { - acad: dxf for dxf, acad in acadrelease.items() -} - -# Entity: Polyline, Polymesh -# 70 flags -POLYLINE_CLOSED = 1 -POLYLINE_MESH_CLOSED_M_DIRECTION = POLYLINE_CLOSED -POLYLINE_CURVE_FIT_VERTICES_ADDED = 2 -POLYLINE_SPLINE_FIT_VERTICES_ADDED = 4 -POLYLINE_3D_POLYLINE = 8 -POLYLINE_3D_POLYMESH = 16 -POLYLINE_MESH_CLOSED_N_DIRECTION = 32 -POLYLINE_POLYFACE = 64 -POLYLINE_GENERATE_LINETYPE_PATTERN =128 - -# Entity: Polymesh -# 75 surface smooth type -POLYMESH_NO_SMOOTH = 0 -POLYMESH_QUADRIC_BSPLINE = 5 -POLYMESH_CUBIC_BSPLINE = 6 -POLYMESH_BEZIER_SURFACE = 8 - -#Entity: Vertex -# 70 flags -VERTEXNAMES = ('vtx0', 'vtx1', 'vtx2', 'vtx3') -VTX_EXTRA_VERTEX_CREATED = 1 ## Extra vertex created by curve-fitting -VTX_CURVE_FIT_TANGENT = 2 ## Curve-fit tangent defined for this vertex. -## A curve-fit tangent direction of 0 may be omitted from the DXF output, but is -## significant if this bit is set. -## 4 = unused, never set in dxf files -VTX_SPLINE_VERTEX_CREATED = 8 ##Spline vertex created by spline-fitting -VTX_SPLINE_FRAME_CONTROL_POINT = 16 -VTX_3D_POLYLINE_VERTEX = 32 -VTX_3D_POLYGON_MESH_VERTEX = 64 -VTX_3D_POLYFACE_MESH_VERTEX = 128 - -VERTEX_FLAGS = { - 'polyline2d': 0, - 'polyline3d': VTX_3D_POLYLINE_VERTEX, - 'polymesh': VTX_3D_POLYGON_MESH_VERTEX, - 'polyface': VTX_3D_POLYGON_MESH_VERTEX | VTX_3D_POLYFACE_MESH_VERTEX, -} -POLYLINE_FLAGS = { - 'polyline2d': 0, - 'polyline3d': POLYLINE_3D_POLYLINE, - 'polymesh': POLYLINE_3D_POLYMESH, - 'polyface': POLYLINE_POLYFACE, -} - -#---block-type flags (bit coded values, may be combined): -# Entity: BLOCK -# 70 flags -BLK_ANONYMOUS = 1 # This is an anonymous block generated by hatching, associative dimensioning, other internal operations, or an application -BLK_NON_CONSTANT_ATTRIBUTES = 2 # This block has non-constant attribute definitions (this bit is not set if the block has any attribute definitions that are constant, or has no attribute definitions at all) -BLK_XREF = 4 # This block is an external reference (xref) -BLK_XREF_OVERLAY = 8 # This block is an xref overlay -BLK_EXTERNAL = 16 # This block is externally dependent -BLK_RESOLVED = 32 # This is a resolved external reference, or dependent of an external reference (ignored on input) -BLK_REFERENCED = 64 # This definition is a referenced external reference (ignored on input) - -LWPOLYLINE_CLOSED = 1 -LWPOLYLINE_PLINEGEN = 128 - -SPLINE_CLOSED = 1 -SPLINE_PERIODIC = 2 -SPLINE_RATIONAL = 4 -SPLINE_PLANAR = 8 -SPLINE_LINEAR = 16 # planar bit is also set - -MTEXT_TOP_LEFT = 1 -MTEXT_TOP_CENTER = 2 -MTEXT_TOP_RIGHT = 3 -MTEXT_MIDDLE_LEFT = 4 -MTEXT_MIDDLE_CENTER = 5 -MTEXT_MIDDLE_RIGHT = 6 -MTEXT_BOTTOM_LEFT = 7 -MTEXT_BOTTOM_CENTER = 8 -MTEXT_BOTTOM_RIGHT = 9 - -MTEXT_LEFT_TO_RIGHT = 1 -MTEXT_TOP_TO_BOTTOM = 2 -MTEXT_BY_STYLE = 5 diff --git a/scripts/py/loaders/dxfgrabber/decode.py b/scripts/py/loaders/dxfgrabber/decode.py deleted file mode 100644 index 57ee424..0000000 --- a/scripts/py/loaders/dxfgrabber/decode.py +++ /dev/null @@ -1,38 +0,0 @@ -# Purpose: decode DXF proprietary data -# Created: 01.05.2014 -# Copyright (C) 2014, Manfred Moitzi -# License: MIT License -from __future__ import unicode_literals -__author__ = "mozman " - -from . import PYTHON3 - -_replacement_table = { - 0x20: ' ', - 0x40: '_', - 0x5F: '@', -} -for c in range(0x41, 0x5F): - _replacement_table[c] = chr(0x41 + (0x5E - c)) # 0x5E -> 'A', 0x5D->'B', ... - - -def decode(text_lines): - def _decode(text): - s = [] - skip = False - if PYTHON3: - text = bytes(text, 'ascii') - else: - text = map(ord, text) - - for c in text: - if skip: - skip = False - continue - if c in _replacement_table: - s += _replacement_table[c] - skip = (c == 0x5E) # skip space after 'A' - else: - s += chr(c ^ 0x5F) - return ''.join(s) - return [_decode(line) for line in text_lines] diff --git a/scripts/py/loaders/dxfgrabber/defaultchunk.py b/scripts/py/loaders/dxfgrabber/defaultchunk.py deleted file mode 100644 index fda30fa..0000000 --- a/scripts/py/loaders/dxfgrabber/defaultchunk.py +++ /dev/null @@ -1,37 +0,0 @@ -# Purpose: handle default chunk -# Created: 21.07.2012, taken from my ezdxf project -# Copyright (C) 2012, Manfred Moitzi -# License: MIT License -from __future__ import unicode_literals -__author__ = "mozman " - -from .tags import Tags, DXFTag - - -class DefaultChunk(object): - def __init__(self, tags): - assert isinstance(tags, Tags) - self.tags = tags - - @staticmethod - def from_tags(tags, drawing): - return DefaultChunk(tags) - - @property - def name(self): - return self.tags[1].value.lower() - - -def iterchunks(tagreader, stoptag='EOF', endofchunk='ENDSEC'): - while True: - tag = next(tagreader) - if tag == DXFTag(0, stoptag): - return - - tags = Tags([tag]) - append = tags.append - end_tag = DXFTag(0, endofchunk) - while tag != end_tag: - tag = next(tagreader) - append(tag) - yield tags diff --git a/scripts/py/loaders/dxfgrabber/drawing.py b/scripts/py/loaders/dxfgrabber/drawing.py deleted file mode 100644 index 7ded631..0000000 --- a/scripts/py/loaders/dxfgrabber/drawing.py +++ /dev/null @@ -1,65 +0,0 @@ -# Purpose: handle drawing data of DXF files -# Created: 21.07.12 -# Copyright (C) 2012, Manfred Moitzi -# License: MIT License - -__author__ = "mozman " - -from .tags import stream_tagger -from .sections import Sections - -DEFAULT_OPTIONS = { - "grab_blocks": True, # import block definitions True=yes, False=No - "assure_3d_coords": False, # guarantees (x, y, z) tuples for ALL coordinates - "resolve_text_styles": True, # Text, Attrib, Attdef and MText attributes will be set by the associated text style if necessary -} - - -class Drawing(object): - def __init__(self, stream, options=None): - if options is None: - options = DEFAULT_OPTIONS - self.grab_blocks = options.get('grab_blocks', True) - self.assure_3d_coords = options.get('assure_3d_coords', False) - self.resolve_text_styles = options.get('resolve_text_styles', True) - - tagreader = stream_tagger(stream, self.assure_3d_coords) - self.dxfversion = 'AC1009' - self.encoding = 'cp1252' - self.filename = None - sections = Sections(tagreader, self) - self.header = sections.header - self.layers = sections.tables.layers - self.styles = sections.tables.styles - self.linetypes = sections.tables.linetypes - self.blocks = sections.blocks - self.entities = sections.entities - self.objects = sections.objects if ('objects' in sections) else [] - if 'acdsdata' in sections: - self.acdsdata = sections.acdsdata - # sab data introduced with DXF version AC1027 (R2013) - if self.dxfversion >= 'AC1027': - self.collect_sab_data() - - if self.resolve_text_styles: - resolve_text_styles(self.entities, self.styles) - for block in self.blocks: - resolve_text_styles(block, self.styles) - - def modelspace(self): - return (entity for entity in self.entities if not entity.paperspace) - - def paperspace(self): - return (entity for entity in self.entities if entity.paperspace) - - def collect_sab_data(self): - for entity in self.entities: - if hasattr(entity, 'set_sab_data'): - sab_data = self.acdsdata.sab_data[entity.handle] - entity.set_sab_data(sab_data) - - -def resolve_text_styles(entities, text_styles): - for entity in entities: - if hasattr(entity, 'resolve_text_style'): - entity.resolve_text_style(text_styles) \ No newline at end of file diff --git a/scripts/py/loaders/dxfgrabber/dxfentities.py b/scripts/py/loaders/dxfgrabber/dxfentities.py deleted file mode 100644 index c8770ca..0000000 --- a/scripts/py/loaders/dxfgrabber/dxfentities.py +++ /dev/null @@ -1,1352 +0,0 @@ -# encoding: utf-8 -# Purpose: entity classes, new implementation without dxf12/dxf13 layer -# Created: 17.04.2016 -# Copyright (C) 2016, Manfred Moitzi -# License: MIT License -from __future__ import unicode_literals -__author__ = "mozman " - -import math - -from . import const -from .color import TrueColor -from .styles import default_text_style -from .decode import decode - -SPECIAL_CHARS = { - 'd': '°' -} - - -basic_attribs = { - 5: 'handle', - 6: 'linetype', - 8: 'layer', - 39: 'thickness', - 48: 'ltscale', - 62: 'color', - 67: 'paperspace', - 210: 'extrusion', - 284: 'shadow_mode', - 330: 'owner', - 370: 'line_weight', - 410: 'layout_tab_name', -} - - -class DXFEntity(object): - def __init__(self): - self.dxftype = 'ENTITY' - self.handle = None - self.owner = None - self.paperspace = None - self.layer = '0' - self.linetype = None - self.thickness = 0.0 - self.extrusion = None - self.ltscale = 1.0 - self.line_weight = 0 - self.invisible = 0 - self.color = const.BYLAYER - self.true_color = None - self.transparency = None - self.shadow_mode = None - self.layout_tab_name = None - - def setup_attributes(self, tags): - self.dxftype = tags.get_type() - for code, value in tags.plain_tags(): - if code in basic_attribs: - self.__setattr__(basic_attribs[code], value) - elif code == 420: - self.true_color = TrueColor(value) - elif code == 440: - self.transparency = 1. - float(value & 0xFF) / 255. - else: - yield code, value # chain of generators - - def set_default_extrusion(self): # call only for 2d entities with extrusion vector - if self.extrusion is None: - self.extrusion = (0., 0., 1.) - - def __str__(self): - return "{} [{}]".format(self.dxftype, self.handle) - - -class Point(DXFEntity): - def __init__(self): - super(Point, self).__init__() - self.point = (0, 0, 0) - - def setup_attributes(self, tags): - for code, value in super(Point, self).setup_attributes(tags): - if code == 10: - self.point = value - else: - yield code, value # chain of generators - self.set_default_extrusion() - - -class Line(DXFEntity): - def __init__(self): - super(Line, self).__init__() - self.start = (0, 0, 0) - self.end = (0, 0, 0) - - def setup_attributes(self, tags): - for code, value in super(Line, self).setup_attributes(tags): - if code == 10: - self.start = value - elif code == 11: - self.end = value - else: - yield code, value # chain of generators - - -class Circle(DXFEntity): - def __init__(self): - super(Circle, self).__init__() - self.center = (0, 0, 0) - self.radius = 1.0 - - def setup_attributes(self, tags): - for code, value in super(Circle, self).setup_attributes(tags): - if code == 10: - self.center = value - elif code == 40: - self.radius = value - else: - yield code, value # chain of generators - self.set_default_extrusion() - - -class Arc(Circle): - def __init__(self): - super(Arc, self).__init__() - self.start_angle = 0. - self.end_angle = 360. - - def setup_attributes(self, tags): - for code, value in super(Arc, self).setup_attributes(tags): - if code == 50: - self.start_angle = value - elif code == 51: - self.end_angle = value - else: - yield code, value # chain of generators - self.set_default_extrusion() - -TRACE_CODES = frozenset((10, 11, 12, 13)) - - -class Trace(DXFEntity): - def __init__(self): - super(Trace, self).__init__() - self.points = [] - - def setup_attributes(self, tags): - for code, value in super(Trace, self).setup_attributes(tags): - if code in TRACE_CODES: - self.points.append(value) - else: - yield code, value # chain of generators - self.set_default_extrusion() - - -Solid = Trace - - -class Face(Trace): - def __init__(self): - super(Face, self).__init__() - self.points = [] - self.invisible_edge = 0 - - def setup_attributes(self, tags): - for code, value in super(Face, self).setup_attributes(tags): - if code == 70: - self.invisible_edge = value - else: - yield code, value # chain of generators - self.set_default_extrusion() - - def is_edge_invisible(self, edge): - # edges 0 .. 3 - return bool(self.invisible_edge & (1 << edge)) - - -class Text(DXFEntity): - def __init__(self): - super(Text, self).__init__() - self.insert = (0., 0.) - self.height = 1.0 - self.text = "" - self.rotation = 0. - self.oblique = 0. - self.style = "STANDARD" - self.width = 1. - self.is_backwards = False - self.is_upside_down = False - self.halign = 0 - self.valign = 0 - self.align_point = None - self.font = "" - self.big_font = "" - - def setup_attributes(self, tags): - for code, value in super(Text, self).setup_attributes(tags): - if code == 10: - self.insert = value - elif code == 11: - self.align_point = value - elif code == 1: - self.text = value - elif code == 7: - self.style = value - elif code == 40: - self.height = value - elif code == 41: - self.width = value - elif code == 50: - self.rotation = value - elif code == 51: - self.oblique = value - elif code == 71: - self.is_backwards = bool(value & 2) - self.is_upside_down = bool(value & 4) - elif code == 72: - self.halign = value - elif code == 73: - self.valign = value - else: - yield code, value # chain of generators - self.set_default_extrusion() - - def resolve_text_style(self, text_styles): - style = text_styles.get(self.style, None) - if style is None: - style = default_text_style - if self.height == 0: - self.height = style.height - if self.width == 0: - self.width = style.width - if self.oblique is None: - self.oblique = style.oblique - if self.is_backwards is None: - self.is_backwards = style.is_backwards - if self.is_upside_down is None: - self.is_upside_down = style.is_upside_down - if self.font is None: - self.font = style.font - if self.big_font is None: - self.big_font = style.big_font - - def plain_text(self): - chars = [] - raw_chars = list(reversed(self.text)) # text splitted into chars, in reversed order for efficient pop() - while len(raw_chars): - char = raw_chars.pop() - if char == '%': # formatting codes and special characters - if len(raw_chars) and raw_chars[-1] == '%': - raw_chars.pop() # '%' - if len(raw_chars): - special_char = raw_chars.pop() # command char - chars.append(SPECIAL_CHARS.get(special_char, "")) - else: # char is just a single '%' - chars.append(char) - else: # char is what it is, a character - chars.append(char) - return "".join(chars) - - -class Attrib(Text): - def __init__(self): - super(Attrib, self).__init__() - self.field_length = 0 - self.tag = "" - - def setup_attributes(self, tags): - for code, value in super(Attrib, self).setup_attributes(tags): - if code == 2: - self.tag = value - elif code == 73: - self.field_length = value - else: - yield code, value - - -class Insert(DXFEntity): - def __init__(self): - super(Insert, self).__init__() - self.name = "" - self.insert = (0., 0., 0.) - self.rotation = 0. - self.scale = (1., 1., 1.) - self.row_count = 1 - self.row_spacing = 0. - self.col_count = 1 - self.col_spacing = 0. - self.attribsfollow = False - self.attribs = [] - - def setup_attributes(self, tags): - xscale = 1. - yscale = 1. - zscale = 1. - for code, value in super(Insert, self).setup_attributes(tags): - if code == 2: - self.name = value - elif code == 10: - self.insert = value - elif code == 41: - xscale = value - elif code == 42: - yscale = value - elif code == 43: - zscale = value - elif code == 44: - self.col_spacing = value - elif code == 45: - self.row_spacing = value - elif code == 50: - self.rotation = value - elif code == 66: - self.attribsfollow = bool(value) - elif code == 70: - self.col_count = value - elif code == 71: - self.row_count = value - else: - yield code, value # chain of generators - self.scale = (xscale, yscale, zscale) - self.set_default_extrusion() - - def find_attrib(self, attrib_tag): - for attrib in self.attribs: - if attrib.tag == attrib_tag: - return attrib - return None - - def append_data(self, attribs): - self.attribs = attribs - - -class Polyline(DXFEntity): - LINE_TYPES = frozenset(('spline2d', 'polyline2d', 'polyline3d')) - - def __init__(self): - super(Polyline, self).__init__() - self.vertices = [] # set in append data - self.points = [] # set in append data - self.control_points = [] # set in append data - self.width = [] # set in append data - self.bulge = [] # set in append data - self.tangents = [] # set in append data - self.flags = 0 - self.mode = 'polyline2d' - self.mcount = 0 - self.ncount = 0 - self.default_start_width = 0. - self.default_end_width = 0. - self.is_mclosed = False - self.is_nclosed = False - self.is_closed = False - self.elevation = (0., 0., 0.) - self.m_smooth_density = 0. - self.n_smooth_density = 0. - self.smooth_type = 0 - self.spline_type = None - - def setup_attributes(self, tags): - def get_mode(): - flags = self.flags - if flags & const.POLYLINE_SPLINE_FIT_VERTICES_ADDED: - return 'spline2d' - elif flags & const.POLYLINE_3D_POLYLINE: - return 'polyline3d' - elif flags & const.POLYLINE_3D_POLYMESH: - return 'polymesh' - elif flags & const.POLYLINE_POLYFACE: - return 'polyface' - else: - return 'polyline2d' - - for code, value in super(Polyline, self).setup_attributes(tags): - if code == 10: - self.elevation = value - elif code == 40: - self.default_start_width = value - elif code == 41: - self.default_end_width = value - elif code == 70: - self.flags = value - elif code == 71: - self.mcount = value - elif code == 72: - self.ncount = value - elif code == 73: - self.m_smooth_density = value - elif code == 73: - self.n_smooth_density = value - elif code == 75: - self.smooth_type = value - else: - yield code, value # chain of generators - - self.mode = get_mode() - if self.mode == 'spline2d': - if self.smooth_type == const.POLYMESH_CUBIC_BSPLINE: - self.spline_type = 'cubic_bspline' - elif self.smooth_type == const.POLYMESH_QUADRIC_BSPLINE: - self.spline_type = 'quadratic_bspline' - elif self.smooth_type == const.POLYMESH_BEZIER_SURFACE: - self.spline_type = 'bezier_curve' # is this a valid spline type for DXF12? - self.is_mclosed = bool(self.flags & const.POLYLINE_MESH_CLOSED_M_DIRECTION) - self.is_nclosed = bool(self.flags & const.POLYLINE_MESH_CLOSED_N_DIRECTION) - self.is_closed = self.is_mclosed - self.set_default_extrusion() - - def __len__(self): - return len(self.vertices) - - def __getitem__(self, item): - return self.vertices[item] - - def __iter__(self): - return iter(self.vertices) - - def append_data(self, vertices): - def default_width(start_width, end_width): - if start_width == 0.: - start_width = self.default_start_width - if end_width == 0.: - end_width = self.default_end_width - return start_width, end_width - - self.vertices = vertices - if self.mode in Polyline.LINE_TYPES: - for vertex in self.vertices: - if vertex.flags & const.VTX_SPLINE_FRAME_CONTROL_POINT: - self.control_points.append(vertex.location) - else: - self.points.append(vertex.location) - self.width.append(default_width(vertex.start_width, vertex.end_width)) - self.bulge.append(vertex.bulge) - self.tangents.append(vertex.tangent if vertex.flags & const.VTX_CURVE_FIT_TANGENT else None) - - def cast(self): - if self.mode == 'polyface': - return PolyFace(self) - elif self.mode == 'polymesh': - return PolyMesh(self) - else: - return self - - -class SubFace(object): - def __init__(self, face_record, vertices): - self._vertices = vertices - self.face_record = face_record - - def __len__(self): - return len(self.face_record.vtx) - - def __getitem__(self, item): - return self._vertices[self._vertex_index(item)] - - def __iter__(self): - return (self._vertices[index].location for index in self.indices()) - - def _vertex_index(self, pos): - return abs(self.face_record.vtx[pos]) - 1 - - def indices(self): - return tuple(abs(i)-1 for i in self.face_record.vtx if i != 0) - - def is_edge_visible(self, pos): - return self.face_record.vtx[pos] > 0 - - -class PolyShape(object): - def __init__(self, polyline, dxftype): - # copy all dxf attributes from polyline - for key, value in polyline.__dict__.items(): - self.__dict__[key] = value - self.dxftype = dxftype - - def __str__(self): - return "{} [{}]".format(self.dxftype, self.handle) - - -class PolyFace(PolyShape): - def __init__(self, polyline): - VERTEX_FLAGS = const.VTX_3D_POLYFACE_MESH_VERTEX + const.VTX_3D_POLYGON_MESH_VERTEX - - def is_vertex(flags): - return flags & VERTEX_FLAGS == VERTEX_FLAGS - - super(PolyFace, self).__init__(polyline, 'POLYFACE') - vertices = [] - face_records = [] - for vertex in polyline.vertices: - (vertices if is_vertex(vertex.flags) else face_records).append(vertex) - - self._face_records = face_records - - def __getitem__(self, item): - return SubFace(self._face_records[item], self.vertices) - - def __len__(self): - return len(self._face_records) - - def __iter__(self): - return (SubFace(f, self.vertices) for f in self._face_records) - - -class PolyMesh(PolyShape): - def __init__(self, polyline): - super(PolyMesh, self).__init__(polyline, 'POLYMESH') - - def __iter__(self): - return iter(self.vertices) - - def get_location(self, pos): - return self.get_vertex(pos).location - - def get_vertex(self, pos): - m, n = pos - if 0 <= m < self.mcount and 0 <= n < self.ncount: - pos = m * self.ncount + n - return self.vertices[pos] - else: - raise IndexError(repr(pos)) - - -class Vertex(DXFEntity): - def __init__(self): - super(Vertex, self).__init__() - self.location = (0., 0., 0.) - self.flags = 0 - self.start_width = 0. - self.end_width = 0. - self.bulge = 0. - self.tangent = None - self.vtx = None - - def setup_attributes(self, tags): - vtx0 = 0 - vtx1 = 0 - vtx2 = 0 - vtx3 = 0 - for code, value in super(Vertex, self).setup_attributes(tags): - if code == 10: - self.location = value - elif code == 40: - self.start_width = value - elif code == 41: - self.end_width = value - elif code == 42: - self.bulge = value - elif code == 50: - self.tangent = value - elif code == 70: - self.flags = value - elif code == 71: - vtx0 = value - elif code == 72: - vtx1 = value - elif code == 73: - vtx2 = value - elif code == 74: - vtx3 = value - else: - yield code, value # chain of generators - indices = (vtx0, vtx1, vtx2, vtx3) - if any(indices): - self.vtx = indices - - def __getitem__(self, item): - return self.location[item] - - def __iter__(self): - return iter(self.location) - - -class Block(DXFEntity): - def __init__(self): - super(Block, self).__init__() - self.basepoint = (0, 0, 0) - self.name = '' - self.description = '' - self.flags = 0 - self.xrefpath = "" - self._entities = [] - - def setup_attributes(self, tags): - for code, value in super(Block, self).setup_attributes(tags): - if code == 2: - self.name = value - elif code == 4: - self.description = value - elif code == 1: - self.xrefpath = value - elif code == 10: - self.basepoint = value - elif code == 70: - self.flags = value - else: - yield code, value # chain of generators - - @property - def is_xref(self): - return bool(self.flags & const.BLK_XREF) - - @property - def is_xref_overlay(self): - return bool(self.flags & const.BLK_XREF_OVERLAY) - - @property - def is_anonymous(self): - return bool(self.flags & const.BLK_ANONYMOUS) - - def set_entities(self, entities): - self._entities = entities - - def __iter__(self): - return iter(self._entities) - - def __getitem__(self, item): - return self._entities[item] - - def __len__(self): - return len(self._entities) - - -class LWPolyline(DXFEntity): - def __init__(self): - super(LWPolyline, self).__init__() - self.points = [] - self.width = [] - self.bulge = [] - self.elevation = 0. - self.const_width = 0. - self.flags = 0 - - def setup_attributes(self, tags): - bulge, start_width, end_width = 0., 0., 0. - init = True - - for code, value in super(LWPolyline, self).setup_attributes(tags): - if code == 10: - if not init: - self.bulge.append(bulge) - self.width.append((start_width, end_width)) - bulge, start_width, end_width = 0., 0., 0. - self.points.append(value) - init = False - elif code == 40: - start_width = value - elif code == 41: - end_width = value - elif code == 42: - bulge = value - elif code == 38: - self.elevation = value - elif code == 39: - self.thickness = value - elif code == 43: - self.const_width = value - elif code == 70: - self.flags = value - elif code == 210: - self.extrusion = value - else: - yield code, value # chain of generators - - # add values for the last point - self.bulge.append(bulge) - self.width.append((start_width, end_width)) - - if self.const_width != 0.: - self.width = [] - self.set_default_extrusion() - - @property - def is_closed(self): - return bool(self.flags & 1) - - def __len__(self): - return len(self.points) - - def __getitem__(self, item): - return self.points[item] - - def __iter__(self): - return iter(self.points) - - -class Ellipse(DXFEntity): - def __init__(self): - super(Ellipse, self).__init__() - self.center = (0., 0., 0.) - self.major_axis = (1., 0., 0.) - self.ratio = 1.0 - self.start_param = 0. - self.end_param = 6.283185307179586 - - def setup_attributes(self, tags): - for code, value in super(Ellipse, self).setup_attributes(tags): - if code == 10: - self.center = value - elif code == 11: - self.major_axis = value - elif code == 40: - self.ratio = value - elif code == 41: - self.start_param = value - elif code == 42: - self.end_param = value - else: - yield code, value # chain of generators - self.set_default_extrusion() - - -class Ray(DXFEntity): - def __init__(self): - super(Ray, self).__init__() - self.start = (0, 0, 0) - self.unit_vector = (1, 0, 0) - - def setup_attributes(self, tags): - for code, value in super(Ray, self).setup_attributes(tags): - if code == 10: - self.start = value - elif code == 11: - self.unit_vector = value - else: - yield code, value # chain of generators - - -def deg2vec(deg): - rad = float(deg) * math.pi / 180.0 - return math.cos(rad), math.sin(rad), 0. - - -def normalized(vector): - x, y, z = vector - m = (x**2 + y**2 + z**2)**0.5 - return x/m, y/m, z/m - -################################################## -# MTEXT inline codes -# \L Start underline -# \l Stop underline -# \O Start overstrike -# \o Stop overstrike -# \K Start strike-through -# \k Stop strike-through -# \P New paragraph (new line) -# \pxi Control codes for bullets, numbered paragraphs and columns -# \X Paragraph wrap on the dimension line (only in dimensions) -# \Q Slanting (obliquing) text by angle - e.g. \Q30; -# \H Text height - e.g. \H3x; -# \W Text width - e.g. \W0.8x; -# \F Font selection -# -# e.g. \Fgdt;o - GDT-tolerance -# e.g. \Fkroeger|b0|i0|c238|p10 - font Kroeger, non-bold, non-italic, codepage 238, pitch 10 -# -# \S Stacking, fractions -# -# e.g. \SA^B: -# A -# B -# e.g. \SX/Y: -# X -# - -# Y -# e.g. \S1#4: -# 1/4 -# -# \A Alignment -# -# \A0; = bottom -# \A1; = center -# \A2; = top -# -# \C Color change -# -# \C1; = red -# \C2; = yellow -# \C3; = green -# \C4; = cyan -# \C5; = blue -# \C6; = magenta -# \C7; = white -# -# \T Tracking, char.spacing - e.g. \T2; -# \~ Non-wrapping space, hard space -# {} Braces - define the text area influenced by the code -# \ Escape character - e.g. \\ = "\", \{ = "{" -# -# Codes and braces can be nested up to 8 levels deep - -ESCAPED_CHARS = "\\{}" -GROUP_CHARS = "{}" -ONE_CHAR_COMMANDS = "PLlOoKkX" - - -class MText(DXFEntity): - def __init__(self): - super(MText, self).__init__() - self.insert = (0., 0., 0.) - self.raw_text = "" - self.height = 0. - self.rect_width = None - self.horizontal_width = None - self.vertical_height = None - self.line_spacing = 1. - self.attachment_point = 1 - self.style = 'STANDARD' - self.xdirection = (1., 0., 0.) - self.font = None - self.big_font = None - - def setup_attributes(self, tags): - text = "" - lines = [] - rotation = 0. - xdir = None - for code, value in super(MText, self).setup_attributes(tags): - if code == 10: - self.insert = value - elif code == 11: - xdir = value - elif code == 1: - text = value - elif code == 3: - lines.append(value) - elif code == 7: - self.style = value - elif code == 40: - self.height = value - elif code == 41: - self.rect_width = value - elif code == 42: - self.horizontal_width = value - elif code == 43: - self.vertical_height = value - elif code == 44: - self.line_spacing = value - elif code == 50: - rotation = value - elif code == 71: - self.attachment_point = value - else: - yield code, value # chain of generators - - lines.append(text) - self.raw_text = "".join(lines) - if xdir is None: - xdir = deg2vec(rotation) - self.xdirection = normalized(xdir) - self.set_default_extrusion() - - def lines(self): - return self.raw_text.split('\P') - - def plain_text(self, split=False): - chars = [] - raw_chars = list(reversed(self.raw_text)) # text splitted into chars, in reversed order for efficient pop() - while len(raw_chars): - char = raw_chars.pop() - if char == '\\': # is a formatting command - try: - char = raw_chars.pop() - except IndexError: - break # premature end of text - just ignore - - if char in ESCAPED_CHARS: # \ { } - chars.append(char) - elif char in ONE_CHAR_COMMANDS: - if char == 'P': # new line - chars.append('\n') - # discard other commands - else: # more character commands are terminated by ';' - stacking = char == 'S' # stacking command surrounds user data - try: - while char != ';': # end of format marker - char = raw_chars.pop() - if stacking and char != ';': - chars.append(char) # append user data of stacking command - except IndexError: - break # premature end of text - just ignore - elif char in GROUP_CHARS: # { } - pass # discard group markers - elif char == '%': # special characters - if len(raw_chars) and raw_chars[-1] == '%': - raw_chars.pop() # discard next '%' - if len(raw_chars): - special_char = raw_chars.pop() - # replace or discard formatting code - chars.append(SPECIAL_CHARS.get(special_char, "")) - else: # char is just a single '%' - chars.append(char) - else: # char is what it is, a character - chars.append(char) - - plain_text = "".join(chars) - return plain_text.split('\n') if split else plain_text - - def resolve_text_style(self, text_styles): - style = text_styles.get(self.style, None) - if style is None: - style = default_text_style - if self.height == 0: - self.height = style.height - if self.font is None: - self.font = style.font - if self.big_font is None: - self.big_font = style.font - - -class Light(DXFEntity): - def __init__(self): - super(Light, self).__init__() - self.version = 1 - self.name = "" - self.light_type = 1 # distant = 1; point = 2; spot = 3 - self.status = False # on/off ? - self.light_color = 0 # 0 is unset - self.true_color = None # None is unset - self.plot_glyph = 0 - self.intensity = 0 - self.position = (0., 0., 1.) - self.target = (0., 0., 0.) - self.attenuation_type = 0 # 0 = None; 1 = Inverse Linear; 2 = Inverse Square - self.use_attenuation_limits = False - self.attenuation_start_limit = 0 - self.attenuation_end_limit = 0 - self.hotspot_angle = 0 - self.fall_off_angle = 0. - self.cast_shadows = False - self.shadow_type = 0 # 0 = Ray traced shadows; 1 = Shadow maps - self.shadow_map_size = 0 - self.shadow_softness = 0 - - def setup_attributes(self, tags): - for code, value in super(Light, self).setup_attributes(tags): - if code == 1: - self.name = value - elif code == 10: - self.position = value - elif code == 11: - self.target = value - elif code == 40: - self.intensity = value - elif code == 41: - self.attenuation_start_limit = value - elif code == 42: - self.attenuation_end_limit = value - elif code == 50: - self.hotspot_angle = value - elif code == 51: - self.fall_off_angle = value - elif code == 63: - self.light_color = value - elif code == 70: - self.light_type = value - elif code == 72: - self.attenuation_type = value - elif code == 73: - self.shadow_type = value - elif code == 90: - self.version = value - elif code == 91: - self.shadow_map_size = value - elif code == 280: - self.shadow_softness = value - elif code == 290: - self.status = value - elif code == 291: - self.plot_glyph = value - elif code == 292: - self.use_attenuation_limits = value - elif code == 293: - self.cast_shadows = value - elif code == 421: - self.true_color = value - else: - yield code, value # chain of generators - - -class Body(DXFEntity): - def __init__(self): - super(Body, self).__init__() - # need handle to get SAB data in DXF version AC1027 and later - self.version = 1 - self.acis = [] - - def setup_attributes(self, tags): - sat = [] - for code, value in super(Body, self).setup_attributes(tags): - if code == 70: - self.version = value - elif code in (1, 3): - sat.append(value) - else: - yield code, value # chain of generators - self.acis = decode(sat) - - def set_sab_data(self, sab_data): - self.acis = sab_data - - @property - def is_sat(self): - return isinstance(self.acis, list) # but could be an empty list - - @property - def is_sab(self): - return not self.is_sat # has binary encoded ACIS data - - -class Surface(Body): - def __init__(self): - super(Body, self).__init__() - self.u_isolines = 0 - self.v_isolines = 0 - - def setup_attributes(self, tags): - for code, value in super(Surface, self).setup_attributes(tags): - if code == 71: - self.u_isolines = value - elif code == 72: - self.v_isolines = value - else: - yield code, value # chain of generators - - -class Mesh(DXFEntity): - def __init__(self): - super(Mesh, self).__init__() - self.version = 2 - self.blend_crease = False - self.subdivision_levels = 1 - # rest are mostly positional tags - self.vertices = [] - self.faces = [] - self.edges = [] - self.edge_crease_list = [] - - def setup_attributes(self, tags): - status = 0 - count = 0 - index_tags = [] - for code, value in super(Mesh, self).setup_attributes(tags): - if code == 10: - self.vertices.append(value) - elif status == -1: # ignore overridden properties at the end of the mesh entity - pass # property override uses also group codes 90, 91, 92 but only at the end of the MESH entity - elif code == 71: - self.version = value - elif code == 72: - self.blend_crease = bool(value) - elif code == 91: - self.subdivision_levels = value - elif 92 <= code <= 95: # 92 = vertices, 93 = faces; 94 = edges 95 = edge creases - if code in (92, 95): - continue # ignore vertices and edge creases count - status = code - count = value - if status == 94: # edge count - count *= 2 - elif code == 140: - self.edge_crease_list.append(value) - elif code == 90 and count > 0: # faces or edges - count -= 1 - index_tags.append(value) - if count < 1: - if status == 93: - self.setup_faces(index_tags) - elif status == 94: - self.setup_edges(index_tags) - index_tags = [] - elif code == 90: # count == 0; start of overridden properties (group code 90 after face or edge list) - status = -1 - else: - yield code, value # chain of generators - - def get_face(self, index): - return tuple(self.vertices[vertex_index] for vertex_index in self.faces[index]) - - def get_edge(self, index): - return tuple(self.vertices[vertex_index] for vertex_index in self.edges[index]) - - def setup_faces(self, tags): - face = [] - count = 0 - for value in tags: - if count == 0: - if len(face): - self.faces.append(tuple(face)) - del face[:] - count = value - else: - count -= 1 - face.append(value) - - if len(face): - self.faces.append(tuple(face)) - - def setup_edges(self, tags): - self.edges = list(zip(tags[::2], tags[1::2])) - - -class Spline(DXFEntity): - def __init__(self): - super(Spline, self).__init__() - self.normal_vector = None - self.flags = 0 - self.degree = 3 - self.start_tangent = None - self.end_tangent = None - self.knots = [] - self.weights = [] - self.tol_knot = .0000001 - self.tol_control_point = .0000001 - self.tol_fit_point = .0000000001 - self.control_points = [] - self.fit_points = [] - - def setup_attributes(self, tags): - subclass = 'AcDbSpline' - for code, value in super(Spline, self).setup_attributes(tags): - if subclass == 'AcDbHelix': - yield code, value # chain of generators - elif code == 10: - self.control_points.append(value) - elif code == 11: - self.fit_points.append(value) - elif code == 12: - self.start_tangent = value - elif code == 13: - self.end_tangent = value - elif code == 40: - self.knots.append(value) - elif code == 41: - self.weights.append(value) - elif code == 42: - self.tol_knot = value - elif code == 43: - self.tol_control_point = value - elif code == 44: - self.tol_fit_point = value - elif code == 70: - self.flags = value - elif code == 71: - self.degree = value - elif 72 <= code < 75: - pass # ignore knot-, control- and fit point count - elif code == 100: - subclass = value - self.normal_vector = self.extrusion - if len(self.weights) == 0: - self.weights = [1.0] * len(self.control_points) - - @property - def is_closed(self): - return bool(self.flags & const.SPLINE_CLOSED) - - @property - def is_periodic(self): - return bool(self.flags & const.SPLINE_PERIODIC) - - @property - def is_rational(self): - return bool(self.flags & const.SPLINE_RATIONAL) - - @property - def is_planar(self): - return bool(self.flags & const.SPLINE_PLANAR) - - @property - def is_linear(self): - return bool(self.flags & const.SPLINE_LINEAR) - - -class Helix(Spline): - def __init__(self): - super(Helix, self).__init__() - self.helix_version = (1, 1) - self.axis_base_point = None - self.start_point = None - self.axis_vector = None - self.radius = 0 - self.turns = 0 - self.turn_height = 0 - self.handedness = 0 # 0 = left, 1 = right - self.constrain = 0 - # 0 = Constrain turn height; - # 1 = Constrain turns; - # 2 = Constrain height - - def setup_attributes(self, tags): - helix_major_version = 1 - helix_maintainance_version = 1 - for code, value in super(Helix, self).setup_attributes(tags): - if code == 10: - self.axis_base_point = value - elif code == 11: - self.start_point = value - elif code == 12: - self.axis_vector = value - elif code == 90: - helix_major_version = value - elif code == 91: - helix_maintainance_version = value - elif code == 91: - helix_maintainance_version = value - elif code == 40: - self.radius = value - elif code == 41: - self.turns = value - elif code == 42: - self.turn_height = value - elif code == 290: - self.handedness = value - elif code == 280: - self.constrain = value - else: - yield code, value # chain of generators - self.helix_version = (helix_major_version, helix_maintainance_version) - -class Hatch(DXFEntity): - def __init__(self): - super(Hatch, self).__init__() - self.pattern_name = '' - self.style = 0 - self.pattern_type = 1 - self.pattern_angle = 0.0 - self.pattern_scale = 1.0 - self.pattern_double = 0 - self.solid_fill = 0 - self.pattern_fill_color = 7 # White - self.number_of_pattern_line = 0 - self.number_of_boundries = 0 - #~ self.flags = 0 - - def setup_attributes(self, tags): - subclass = 'AcDbHatch' - #~ bulge, start_width, end_width = 0., 0., 0. - #~ init = True - boundries = 0 - - for code, value in super(Hatch, self).setup_attributes(tags): - print "Hatch : ", code, value - - if code == 2: - self.pattern_name = value - elif code == 70: - self.solid_fill = bool(value == 1) - elif code == 71: - pass - elif code == 64: - self.patter_fill_color = int(value) - elif code == 91: - self.number_of_boundries = int(value) - boundries = int(value) - # Boundry data - - elif code == 75: - self.style = int(value) - elif code == 76: - self.pattern_type = int(value) - elif code == 52: - self.pattern_angle = float(value) - elif code == 41: - self.pattern_scale = float(value) - elif code == 77: - self.pattern_double = int(value) - elif code == 78: - self.number_of_pattern_line = int(value) - # Pattern data - else: - print "? Hatch : ", code, value - yield code, value # chain of generators - - #~ elif code == 38: - #~ self.elevation = value - #~ elif code == 39: - #~ self.thickness = value - #~ elif code == 43: - #~ self.const_width = value - #~ elif code == 70: - #~ self.flags = value - #~ elif code == 210: - #~ self.extrusion = value - #~ else: - #~ yield code, value # chain of generators - - # add values for the last point - #~ self.bulge.append(bulge) - #~ self.width.append((start_width, end_width)) - - #~ if self.const_width != 0.: - #~ self.width = [] - #~ self.set_default_extrusion() - - @property - def is_solid(self): - return bool(self.solid_fill) - - #~ def __len__(self): - #~ return len(self.points) - - #~ def __getitem__(self, item): - #~ return self.points[item] - - #~ def __iter__(self): - #~ return iter(self.points) - -EntityTable = { - 'LINE': Line, - 'POINT': Point, - 'CIRCLE': Circle, - 'ARC': Arc, - 'TRACE': Trace, - 'SOLID': Solid, - '3DFACE': Face, - 'TEXT': Text, - 'INSERT': Insert, - 'SEQEND': DXFEntity, - 'ATTRIB': Attrib, - 'ATTDEF': Attrib, - 'POLYLINE': Polyline, - 'VERTEX': Vertex, - 'BLOCK': Block, - 'ENDBLK': DXFEntity, - 'LWPOLYLINE': LWPolyline, - 'ELLIPSE': Ellipse, - 'RAY': Ray, - 'XLINE': Ray, - 'SPLINE': Spline, - 'HELIX': Helix, - 'HATCH': Hatch, - 'MTEXT': MText, - 'MESH': Mesh, - 'LIGHT': Light, - 'BODY': Body, - 'REGION': Body, - '3DSOLID': Body, - 'SURFACE': Surface, - 'PLANESURFACE': Surface, -} - - -def entity_factory(tags): - dxftype = tags.get_type() - cls = EntityTable[dxftype] # get entity class or raise KeyError - entity = cls() # call constructor - list(entity.setup_attributes(tags)) # setup dxf attributes - chain of generators - return entity - diff --git a/scripts/py/loaders/dxfgrabber/dxfobjects.py b/scripts/py/loaders/dxfgrabber/dxfobjects.py deleted file mode 100644 index c6d8a93..0000000 --- a/scripts/py/loaders/dxfgrabber/dxfobjects.py +++ /dev/null @@ -1,99 +0,0 @@ -# encoding: utf-8 -# Purpose: objects classes -# Created: 17.04.2016 -# Copyright (C) 2016, Manfred Moitzi -# License: MIT License -from __future__ import unicode_literals -__author__ = "mozman " - -from datetime import datetime -from .juliandate import calendar_date - - -class DXFObject(object): - def __init__(self): - self.dxftype = 'ENTITY' - self.handle = None - self.owner = None - - def setup_attributes(self, tags): - self.dxftype = tags.get_type() - for code, value in tags.plain_tags(): - if code == 5: # special case 105 handled by STYLE TABLE - self.handle = value - elif code == 330: - self.owner = value - else: - yield code, value # chain of generators - - -def unpack_seconds(seconds): - seconds = int(seconds / 1000) # remove 1/1000 part - hours = int(seconds / 3600) - seconds = int(seconds % 3600) - minutes = int(seconds / 60) - seconds = int(seconds % 60) - return hours, minutes, seconds - - -class Sun(DXFObject): - def __init__(self): - super(Sun, self).__init__() - self.version = 1 - self.status = False - self.sun_color = None - self.intensity = 0. - self.shadows = False - self.date = datetime.now() - self.daylight_savings_time = False - self.shadow_type = 0 - self.shadow_map_size = 0 - self.shadow_softness = 0. - - def setup_attributes(self, tags): - date = 0 - time = 0 - for code, value in super(Sun, self).setup_attributes(tags): - if code == 40: - self.intensity = value - elif code == 63: - self.sun_color = value - elif code == 70: - self.shadow_type = value - elif code == 71: - self.shadow_map_size = value - elif code == 90: - self.version = value - elif code == 91: - date = value - elif code == 92: - time = value - elif code == 280: - self.shadow_softness = value - elif code == 290: - self.status = bool(value) - elif code == 291: - self.shadows = bool(value) - elif code == 292: - self.daylight_savings_time = bool(value) - else: - yield code, value # chain of generators - - if date > 0: - date = calendar_date(date) - else: - date = datetime.now() - hours, minutes, seconds = unpack_seconds(time) - self.date = datetime(date.year, date.month, date.day, hours, minutes, seconds) - -ObjectsTable = { - 'SUN': Sun, -} - - -def objects_factory(tags): - dxftype = tags.get_type() - cls = ObjectsTable.get(dxftype, DXFObject) # get object class - entity = cls() # call constructor - list(entity.setup_attributes(tags)) # setup dxf attributes - chain of generators - return entity diff --git a/scripts/py/loaders/dxfgrabber/entitysection.py b/scripts/py/loaders/dxfgrabber/entitysection.py deleted file mode 100644 index f0e4ecc..0000000 --- a/scripts/py/loaders/dxfgrabber/entitysection.py +++ /dev/null @@ -1,94 +0,0 @@ -# Purpose: handle entity section -# Created: 21.07.2012, taken from my ezdxf project -# Copyright (C) 2012, Manfred Moitzi -# License: MIT License -from __future__ import unicode_literals -__author__ = "mozman " - -from itertools import islice - -from .tags import TagGroups, DXFStructureError -from .tags import Tags -from .dxfentities import entity_factory - - -class EntitySection(object): - name = 'entities' - - def __init__(self): - self._entities = list() - - @classmethod - def from_tags(cls, tags, drawing): - entity_section = cls() - entity_section._build(tags) - return entity_section - - def get_entities(self): - return self._entities - - # start of public interface - - def __len__(self): - return len(self._entities) - - def __iter__(self): - return iter(self._entities) - - def __getitem__(self, index): - return self._entities[index] - - # end of public interface - - def _build(self, tags): - if len(tags) == 3: # empty entities section - return - groups = TagGroups(islice(tags, 2, len(tags)-1)) - self._entities = build_entities(groups) - - -class ObjectsSection(EntitySection): - name = 'objects' - - -def build_entities(tag_groups): - def build_entity(group): - try: - entity = entity_factory(Tags(group)) - except KeyError: - entity = None # ignore unsupported entities - return entity - - entities = list() - collector = None - for group in tag_groups: - entity = build_entity(group) - if entity is not None: - if collector: - if entity.dxftype == 'SEQEND': - collector.stop() - entities.append(collector.entity) - collector = None - else: - collector.append(entity) - elif entity.dxftype in ('POLYLINE', 'POLYFACE', 'POLYMESH'): - collector = _Collector(entity) - elif entity.dxftype == 'INSERT' and entity.attribsfollow: - collector = _Collector(entity) - else: - entities.append(entity) - return entities - - -class _Collector: - def __init__(self, entity): - self.entity = entity - self._data = list() - - def append(self, entity): - self._data.append(entity) - - def stop(self): - self.entity.append_data(self._data) - if hasattr(self.entity, 'cast'): - self.entity = self.entity.cast() diff --git a/scripts/py/loaders/dxfgrabber/headersection.py b/scripts/py/loaders/dxfgrabber/headersection.py deleted file mode 100644 index dd0d5ce..0000000 --- a/scripts/py/loaders/dxfgrabber/headersection.py +++ /dev/null @@ -1,34 +0,0 @@ -# Purpose: handle header section -# Created: 21.07.2012, taken from my ezdxf project -# Copyright (C) 2012, Manfred Moitzi -# License: MIT License -from __future__ import unicode_literals -__author__ = "mozman " - -from .tags import TagGroups, DXFTag - - -class HeaderSection(dict): - name = "header" - - def __init__(self): - super(HeaderSection, self).__init__() - self._create_default_vars() - - @staticmethod - def from_tags(tags): - header = HeaderSection() - if tags[1] == DXFTag(2, 'HEADER'): # DXF12 without a HEADER section is valid! - header._build(tags) - return header - - def _create_default_vars(self): - self['$ACADVER'] = 'AC1009' - self['$DWGCODEPAGE'] = 'ANSI_1252' - - def _build(self, tags): - if len(tags) == 3: # empty header section! - return - groups = TagGroups(tags[2:-1], split_code=9) - for group in groups: - self[group[0].value] = group[1].value diff --git a/scripts/py/loaders/dxfgrabber/juliandate.py b/scripts/py/loaders/dxfgrabber/juliandate.py deleted file mode 100644 index 480c8f2..0000000 --- a/scripts/py/loaders/dxfgrabber/juliandate.py +++ /dev/null @@ -1,73 +0,0 @@ -# Purpose: julian date -# Created: 21.03.2011 -# Copyright (C) 2011, Manfred Moitzi -# License: MIT License -from __future__ import unicode_literals -__author__ = "mozman " - -from math import floor -from datetime import datetime - - -def frac(number): - return number - floor(number) - - -class JulianDate: - def __init__(self, date): - self.date = date - self.result = self.julian_date() + self.fractional_day() - - def fractional_day(self): - seconds = self.date.hour * 3600. + self.date.minute * 60. + self.date.second - return seconds / 86400. - - def julian_date(self): - y = self.date.year + (float(self.date.month) - 2.85) / 12. - A = floor(367. * y) - 1.75 * floor(y) + self.date.day - B = floor(A) - 0.75 * floor(y / 100.) - return floor(B) + 1721115. - - -class CalendarDate: - def __init__(self, juliandate): - self.jdate = juliandate - year, month, day = self.get_date() - hour, minute, second = frac2time(self.jdate) - self.result = datetime(year, month, day, hour, minute, second) - - def get_date(self): - Z = floor(self.jdate) - - if Z < 2299161: - A = Z # julian calender - else: - g = floor((Z - 1867216.25) / 36524.25) # gregorian calendar - A = Z + 1. + g - floor(g / 4.) - - B = A + 1524. - C = floor((B - 122.1) / 365.25) - D = floor(365.25 * C) - E = floor((B - D) / 30.6001) - - day = B - D - floor(30.6001 * E) - month = E - 1 if E < 14 else E - 13 - year = C - 4716 if month > 2 else C - 4715 - return int(year), int(month), int(day) - - -def frac2time(jdate): - seconds = int(frac(jdate) * 86400.) - hour = int(seconds / 3600) - seconds = seconds % 3600 - minute = int(seconds / 60) - second = seconds % 60 - return hour, minute, second - - -def julian_date(date): - return JulianDate(date).result - - -def calendar_date(juliandate): - return CalendarDate(juliandate).result \ No newline at end of file diff --git a/scripts/py/loaders/dxfgrabber/layers.py b/scripts/py/loaders/dxfgrabber/layers.py deleted file mode 100644 index f21fce9..0000000 --- a/scripts/py/loaders/dxfgrabber/layers.py +++ /dev/null @@ -1,90 +0,0 @@ -# Purpose: handle layers -# Created: 21.07.12 -# Copyright (C) 2012, Manfred Moitzi -# License: MIT License - -__author__ = "mozman " - -from .tags import TagGroups -from .tags import Tags - -LOCK = 0b00000100 -FROZEN = 0b00000001 - - -class Layer(object): - def __init__(self, tags): - self.name = "" - self.color = 7 - self.linetype = "" - self.locked = False - self.frozen = False - self.on = True - for code, value in tags.plain_tags(): - if code == 2: - self.name = value - elif code == 70: - self.frozen = bool(value & FROZEN) - self.locked = bool(value & LOCK) - elif code == 62: - if value < 0: - self.on = False - self.color = abs(value) - else: - self.color = value - elif code == 6: - self.linetype = value - - -class Table(object): - - def __init__(self): - self._table_entries = dict() - - # start public interface - - def get(self, name, default=KeyError): - try: - return self._table_entries[name] - except KeyError: - if default is KeyError: - raise - else: - return default - - def __getitem__(self, item): - return self.get(item) - - def __contains__(self, name): - return name in self._table_entries - - def __iter__(self): - return iter(self._table_entries.values()) - - def __len__(self): - return len(self._table_entries) - - def names(self): - return sorted(self._table_entries.keys()) - - # end public interface - - def entry_tags(self, tags): - groups = TagGroups(tags) - assert groups.get_name(0) == 'TABLE' - assert groups.get_name(-1) == 'ENDTAB' - for entrytags in groups[1:-1]: - yield Tags(entrytags) - - -class LayerTable(Table): - name = 'layers' - - @staticmethod - def from_tags(tags): - layers = LayerTable() - for entrytags in layers.entry_tags(tags): - layer = Layer(entrytags) - layers._table_entries[layer.name] = layer - return layers - diff --git a/scripts/py/loaders/dxfgrabber/linetypes.py b/scripts/py/loaders/dxfgrabber/linetypes.py deleted file mode 100644 index 837cdda..0000000 --- a/scripts/py/loaders/dxfgrabber/linetypes.py +++ /dev/null @@ -1,38 +0,0 @@ -# Purpose: handle linetypes table -# Created: 06.01.2014 -# Copyright (C) 2014, Manfred Moitzi -# License: MIT License - -__author__ = "mozman " - -from .layers import Table - - -class Linetype(object): - def __init__(self, tags): - self.name = "" - self.description = "" - self.length = 0 # overall length of the pattern - self.pattern = [] # list of floats: value>0: line, value<0: gap, value=0: dot - for code, value in tags.plain_tags(): - if code == 2: - self.name = value - elif code == 3: - self.description = value - elif code == 40: - self.length = value - elif code == 49: - self.pattern.append(value) - - -class LinetypeTable(Table): - name = 'linetypes' - - @staticmethod - def from_tags(tags): - styles = LinetypeTable() - for entry_tags in styles.entry_tags(tags): - style = Linetype(entry_tags) - styles._table_entries[style.name] = style - return styles - diff --git a/scripts/py/loaders/dxfgrabber/sections.py b/scripts/py/loaders/dxfgrabber/sections.py deleted file mode 100644 index 7ddb2bd..0000000 --- a/scripts/py/loaders/dxfgrabber/sections.py +++ /dev/null @@ -1,70 +0,0 @@ -# Purpose: handle dxf sections -# Created: 21.07.2012, taken from my ezdxf project -# Copyright (C) 2012, Manfred Moitzi -# License: MIT License -from __future__ import unicode_literals -__author__ = "mozman " - -from .codepage import toencoding -from .defaultchunk import DefaultChunk, iterchunks -from .headersection import HeaderSection -from .tablessection import TablesSection -from .entitysection import EntitySection, ObjectsSection -from .blockssection import BlocksSection -from .acdsdata import AcDsDataSection - - -class Sections(object): - def __init__(self, tagreader, drawing): - self._sections = {} - self._create_default_sections() - self._setup_sections(tagreader, drawing) - - def __contains__(self, name): - return name in self._sections - - def _create_default_sections(self): - self._sections['header'] = HeaderSection() - for cls in SECTIONMAP.values(): - section = cls() - self._sections[section.name] = section - - def _setup_sections(self, tagreader, drawing): - def name(section): - return section[1].value - - bootstrap = True - for section in iterchunks(tagreader, stoptag='EOF', endofchunk='ENDSEC'): - if bootstrap: - new_section = HeaderSection.from_tags(section) - drawing.dxfversion = new_section.get('$ACADVER', 'AC1009') - codepage = new_section.get('$DWGCODEPAGE', 'ANSI_1252') - drawing.encoding = toencoding(codepage) - bootstrap = False - else: - section_name = name(section) - if section_name in SECTIONMAP: - section_class = get_section_class(section_name) - new_section = section_class.from_tags(section, drawing) - else: - new_section = None - if new_section is not None: - self._sections[new_section.name] = new_section - - def __getattr__(self, key): - try: - return self._sections[key] - except KeyError: - raise AttributeError(key) - -SECTIONMAP = { - 'TABLES': TablesSection, - 'ENTITIES': EntitySection, - 'OBJECTS': ObjectsSection, - 'BLOCKS': BlocksSection, - 'ACDSDATA': AcDsDataSection, -} - - -def get_section_class(name): - return SECTIONMAP.get(name, DefaultChunk) diff --git a/scripts/py/loaders/dxfgrabber/styles.py b/scripts/py/loaders/dxfgrabber/styles.py deleted file mode 100644 index 84b0a9d..0000000 --- a/scripts/py/loaders/dxfgrabber/styles.py +++ /dev/null @@ -1,77 +0,0 @@ -# Purpose: handle text styles -# Created: 06.01.2014 -# Copyright (C) 2014, Manfred Moitzi -# License: MIT License -from __future__ import unicode_literals -__author__ = "mozman " - -from .layers import Table -from .tags import Tags - - -class Style(object): - def __init__(self, tags): - self.name = "" - self.height = 1.0 - self.width = 1.0 - self.oblique = 0. - self.is_backwards = False - self.is_upside_down = False - self.font = "" - self.big_font = "" - for code, value in tags.plain_tags(): - if code == 2: - self.name = value - elif code == 70: - self.flags = value - elif code == 40: - self.height = value - elif code == 41: - self.width = value - elif code == 50: - self.oblique = value - elif code == 71: - self.is_backwards = bool(value & 2) - self.is_upside_down = bool(value & 4) - self.oblique = value - elif code == 3: - self.font = value - elif code == 4: - self.big_font = value - - -class StyleTable(Table): - name = 'styles' - - @staticmethod - def from_tags(tags): - styles = StyleTable() - for entry_tags in styles.entry_tags(tags): - style = Style(entry_tags) - styles._table_entries[style.name] = style - return styles - - -DEFAULT_STYLE = """ 0 -STYLE - 2 -STANDARD - 70 -0 - 40 -0.0 - 41 -1.0 - 50 -0.0 - 71 -0 - 42 -1.0 - 3 -Arial - 4 - -""" - -default_text_style = Style(Tags.from_text(DEFAULT_STYLE)) diff --git a/scripts/py/loaders/dxfgrabber/tablessection.py b/scripts/py/loaders/dxfgrabber/tablessection.py deleted file mode 100644 index 25ec5ed..0000000 --- a/scripts/py/loaders/dxfgrabber/tablessection.py +++ /dev/null @@ -1,86 +0,0 @@ -# Purpose: handle tables section -# Created: 21.07.2012, taken from my ezdxf project -# Copyright (C) 2012, Manfred Moitzi -# License: MIT License -from __future__ import unicode_literals -__author__ = "mozman " - -from .defaultchunk import iterchunks, DefaultChunk -from .layers import LayerTable -from .styles import StyleTable -from .linetypes import LinetypeTable - -TABLENAMES = { - 'layer': 'layers', - 'ltype': 'linetypes', - 'appid': 'appids', - 'dimstyle': 'dimstyles', - 'style': 'styles', - 'ucs': 'ucs', - 'view': 'views', - 'vport': 'viewports', - 'block_record': 'block_records', - } - - -def tablename(dxfname): - """ Translate DXF-table-name to attribute-name. ('LAYER' -> 'layers') """ - name = dxfname.lower() - return TABLENAMES.get(name, name+'s') - - -class DefaultDrawing(object): - dxfversion = 'AC1009' - encoding = 'cp1252' - - -class TablesSection(object): - name = 'tables' - - def __init__(self): - self._tables = dict() - self._create_default_tables() - - def _create_default_tables(self): - for cls in TABLESMAP.values(): - table = cls() - self._tables[table.name] = table - - @staticmethod - def from_tags(tags, drawing): - tables_section = TablesSection() - tables_section._setup_tables(tags) - return tables_section - - def _setup_tables(self, tags): - def name(table): - return table[1].value - - def skiptags(tags, count): - for i in range(count): - next(tags) - return tags - - itertags = skiptags(iter(tags), 2) # (0, 'SECTION'), (2, 'TABLES') - for table in iterchunks(itertags, stoptag='ENDSEC', endofchunk='ENDTAB'): - table_class = table_factory(name(table)) - if table_class is not None: - new_table = table_class.from_tags(table) - self._tables[new_table.name] = new_table - - def __getattr__(self, key): - try: - return self._tables[key] - except KeyError: - raise AttributeError(key) - -# support for further tables types are possible -TABLESMAP = { - 'LAYER': LayerTable, - 'STYLE': StyleTable, - 'LTYPE': LinetypeTable, -} - - -def table_factory(name): - return TABLESMAP.get(name, None) diff --git a/scripts/py/loaders/dxfgrabber/tags.py b/scripts/py/loaders/dxfgrabber/tags.py deleted file mode 100644 index ff4472f..0000000 --- a/scripts/py/loaders/dxfgrabber/tags.py +++ /dev/null @@ -1,468 +0,0 @@ -# Purpose: tag reader -# Created: 21.07.2012, taken from my ezdxf project -# Copyright (C) 2012, Manfred Moitzi -# License: MIT License -from __future__ import unicode_literals -__author__ = "mozman " - -import sys -from .codepage import toencoding -from .const import acadrelease -from array import array - -from io import StringIO -from collections import namedtuple -from itertools import chain, islice -from . import tostr - - -DXFTag = namedtuple('DXFTag', 'code value') -NONE_TAG = DXFTag(999999, 'NONE') -APP_DATA_MARKER = 102 -SUBCLASS_MARKER = 100 -XDATA_MARKER = 1001 - - -class DXFStructureError(Exception): - pass - - -def point_tuple(value): - return tuple(float(f) for f in value) - - -POINT_CODES = frozenset(chain(range(10, 20), (210, ), range(110, 113), range(1010, 1020))) - - -def is_point_tag(tag): - return tag[0] in POINT_CODES - - -infinite = float('inf') -neg_infinite = float('-inf') - - -def to_float_with_infinite(value): - try: - return float(value) - except ValueError: - value = value.lower().strip() - if value.startswith('inf'): - return infinite - if value.startswith('-inf'): - return neg_infinite - else: - raise - - -class TagCaster: - def __init__(self): - self._cast = self._build() - - def _build(self): - table = {} - for caster, codes in TYPES: - for code in codes: - table[code] = caster - return table - - def cast(self, tag): - code, value = tag - typecaster = self._cast.get(code, tostr) - try: - value = typecaster(value) - except ValueError: - if typecaster is int: # convert float to int - value = int(float(value)) - else: - raise - return DXFTag(code, value) - - def cast_value(self, code, value): - typecaster = self._cast.get(code, tostr) - try: - return typecaster(value) - except ValueError: - if typecaster is int: # convert float to int - return int(float(value)) - else: - raise - -TYPES = [ - (tostr, range(0, 10)), - (point_tuple, range(10, 20)), - (to_float_with_infinite, range(20, 60)), - (int, range(60, 100)), - (tostr, range(100, 106)), - (point_tuple, range(110, 113)), - (to_float_with_infinite, range(113, 150)), - (int, range(170, 180)), - (point_tuple, [210]), - (to_float_with_infinite, range(211, 240)), - (int, range(270, 290)), - (int, range(290, 300)), # bool 1=True 0=False - (tostr, range(300, 370)), - (int, range(370, 390)), - (tostr, range(390, 400)), - (int, range(400, 410)), - (tostr, range(410, 420)), - (int, range(420, 430)), - (tostr, range(430, 440)), - (int, range(440, 460)), - (to_float_with_infinite, range(460, 470)), - (tostr, range(470, 480)), - (tostr, range(480, 482)), - (tostr, range(999, 1010)), - (point_tuple, range(1010, 1020)), - (to_float_with_infinite, range(1020, 1060)), - (int, range(1060, 1072)), -] - -_TagCaster = TagCaster() -cast_tag = _TagCaster.cast -cast_tag_value = _TagCaster.cast_value - - -def stream_tagger(stream, assure_3d_coords=False): - """ Generates DXFTag() from a stream (untrusted external source). Does not skip comment tags 999. - """ - class Counter: - def __init__(self): - self.counter = 0 - - undo_tag = None - line = Counter() # writeable line counter for next_tag(), Python 2.7 does not support the nonlocal statement - - def next_tag(): - code = stream.readline() - value = stream.readline() - line.counter += 2 - if code and value: # StringIO(): empty strings indicates EOF - return DXFTag(int(code[:-1]), value[:-1]) # without '\n' - else: # StringIO(): missing '\n' indicates EOF - raise EOFError() - - while True: - try: - if undo_tag is not None: - x = undo_tag - undo_tag = None - else: - x = next_tag() - code = x.code - if code == 999: # skip comments - continue - if code in POINT_CODES: - y = next_tag() # y coordinate is mandatory - if y.code != code + 10: - raise DXFStructureError("Missing required y coordinate near line: {}.".format(line.counter)) - z = next_tag() # z coordinate just for 3d points - try: - if z.code == code + 20: - point = (float(x.value), float(y.value), float(z.value)) - else: - if assure_3d_coords: - point = (float(x.value), float(y.value), 0.) - else: - point = (float(x.value), float(y.value)) - undo_tag = z - except ValueError: - raise DXFStructureError('Invalid floating point values near line: {}.'.format(line.counter)) - yield DXFTag(code, point) - else: # just a single tag - try: - yield cast_tag(x) - except ValueError: - raise DXFStructureError('Invalid tag (code={code}, value="{value}") near line: {line}.'.format( - line=line.counter, - code=x.code, - value=x.value, - )) - except EOFError: - return - - -def string_tagger(s): - return stream_tagger(StringIO(s)) - - -class Tags(list): - """ DXFTag() chunk as flat list. """ - def find_all(self, code): - """ Returns a list of DXFTag(code, ...). """ - return [tag for tag in self if tag.code == code] - - def tag_index(self, code, start=0, end=None): - """Return first index of DXFTag(code, value). - """ - if end is None: - end = len(self) - index = start - while index < end: - if self[index].code == code: - return index - index += 1 - raise ValueError(code) - - def get_value(self, code): - for tag in self: - if tag.code == code: - return tag.value - raise ValueError(code) - - @staticmethod - def from_text(text): - return Tags(string_tagger(text)) - - def get_type(self): - return self.__getitem__(0).value - - def plain_tags(self): # yield no app data and no xdata - is_app_data = False - for tag in self: - if tag.code >= 1000: # skip xdata - continue - if tag.code == APP_DATA_MARKER: - if is_app_data: # tag.value == '}' - is_app_data = False # end of app data - else: # value == '{APPID' - is_app_data = True # start of app data - continue - if not is_app_data: - yield tag - - def xdata(self): - index = 0 - end = len(self) - while index < end: - if self[index].code > 999: # all xdata tag codes are >= 1000 - return self[index:] # xdata is always at the end of the DXF entity - index += 1 - return [] - - def app_data(self): - app_data = {} - app_tags = None - for tag in self: - if tag.code == APP_DATA_MARKER: - if tag.value == '}': # end of app data - app_tags.append(tag) - app_data[app_tags[0].value] = app_tags - app_tags = None - else: - app_tags = [tag] - else: - if app_tags is not None: # collection app data - app_tags.append(tag) - return app_data - - def subclasses(self): - classes = {} - name = 'noname' - tags = [] - for tag in self.plain_tags(): - if tag.code == SUBCLASS_MARKER: - classes[name] = tags - tags = [] - name = tag.value - else: - tags.append(tag) - classes[name] = tags - return classes - - def get_subclass(self, name): - classes = self.subclasses() - return classes.get(name, 'noname') - - -class TagGroups(list): - """ - Group of tags starting with a SplitTag and ending before the next SplitTag. - - A SplitTag is a tag with code == splitcode, like (0, 'SECTION') for splitcode=0. - - """ - def __init__(self, tags, split_code=0): - super(TagGroups, self).__init__() - self._build_groups(tags, split_code) - - def _build_groups(self, tags, splitcode): - def append(tag): # first do nothing, skip tags in front of the first split tag - pass - group = None - for tag in tags: # has to work with iterators/generators - if tag.code == splitcode: - if group is not None: - self.append(group) - group = Tags([tag]) - append = group.append # redefine append: add tags to this group - else: - append(tag) - if group is not None: - self.append(group) - - def get_name(self, index): - return self[index][0].value - - @staticmethod - def from_text(text, split_code=0): - return TagGroups(Tags.from_text(text), split_code) - - -class ClassifiedTags: - """ Manage Subclasses, AppData and Extended Data """ - - def __init__(self, iterable=None): - self.appdata = list() # code == 102, keys are "{", values are Tags() - self.subclasses = list() # code == 100, keys are "subclassname", values are Tags() - self.xdata = list() # code >= 1000, keys are "APPNAME", values are Tags() - if iterable is not None: - self._setup(iterable) - - @property - def noclass(self): - return self.subclasses[0] - - def _setup(self, iterable): - tagstream = iter(iterable) - - def collect_subclass(start_tag): - """ a subclass can contain appdata, but not xdata, ends with - SUBCLASSMARKER or XDATACODE. - """ - data = Tags() if start_tag is None else Tags([start_tag]) - try: - while True: - tag = next(tagstream) - if tag.code == APP_DATA_MARKER and tag.value[0] == '{': - app_data_pos = len(self.appdata) - data.append(DXFTag(tag.code, app_data_pos)) - collect_appdata(tag) - elif tag.code in (SUBCLASS_MARKER, XDATA_MARKER): - self.subclasses.append(data) - return tag - else: - data.append(tag) - except StopIteration: - pass - self.subclasses.append(data) - return NONE_TAG - - def collect_appdata(starttag): - """ appdata, can not contain xdata or subclasses """ - data = Tags([starttag]) - while True: - try: - tag = next(tagstream) - except StopIteration: - raise DXFStructureError("Missing closing DXFTag(102, '}') for appdata structure.") - data.append(tag) - if tag.code == APP_DATA_MARKER: - break - self.appdata.append(data) - - def collect_xdata(starttag): - """ xdata are always at the end of the entity and can not contain - appdata or subclasses - """ - data = Tags([starttag]) - try: - while True: - tag = next(tagstream) - if tag.code == XDATA_MARKER: - self.xdata.append(data) - return tag - else: - data.append(tag) - except StopIteration: - pass - self.xdata.append(data) - return NONE_TAG - - tag = collect_subclass(None) # preceding tags without a subclass - while tag.code == SUBCLASS_MARKER: - tag = collect_subclass(tag) - while tag.code == XDATA_MARKER: - tag = collect_xdata(tag) - - if tag is not NONE_TAG: - raise DXFStructureError("Unexpected tag '%r' at end of entity." % tag) - - def __iter__(self): - for subclass in self.subclasses: - for tag in subclass: - if tag.code == APP_DATA_MARKER and isinstance(tag.value, int): - for subtag in self.appdata[tag.value]: - yield subtag - else: - yield tag - - for xdata in self.xdata: - for tag in xdata: - yield tag - - def get_subclass(self, name): - for subclass in self.subclasses: - if len(subclass) and subclass[0].value == name: - return subclass - raise KeyError("Subclass '%s' does not exist." % name) - - def get_xdata(self, appid): - for xdata in self.xdata: - if xdata[0].value == appid: - return xdata - raise ValueError("No extended data for APPID '%s'" % appid) - - def get_appdata(self, name): - for appdata in self.appdata: - if appdata[0].value == name: - return appdata - raise ValueError("Application defined group '%s' does not exist." % name) - - def get_type(self): - return self.noclass[0].value - - @staticmethod - def from_text(text): - return ClassifiedTags(string_tagger(text)) - - -class DXFInfo(object): - def __init__(self): - self.release = 'R12' - self.version = 'AC1009' - self.encoding = 'cp1252' - self.handseed = '0' - - def DWGCODEPAGE(self, value): - self.encoding = toencoding(value) - - def ACADVER(self, value): - self.version = value - self.release = acadrelease.get(value, 'R12') - - def HANDSEED(self, value): - self.handseed = value - - -def dxfinfo(stream): - info = DXFInfo() - tag = DXFTag(999999, '') - tagreader = stream_tagger(stream) - while tag != DXFTag(0, 'ENDSEC'): - tag = next(tagreader) - if tag.code != 9: - continue - name = tag.value[1:] - method = getattr(info, name, None) - if method is not None: - method(next(tagreader).value) - return info - - -def binary_encoded_data_to_bytes(data): - PY3 = sys.version_info[0] >= 3 - byte_array = array('B' if PY3 else b'B') - for text in data: - byte_array.extend(int(text[index:index+2], 16) for index in range(0, len(text), 2)) - return byte_array.tobytes() if PY3 else byte_array.tostring() diff --git a/scripts/py/loaders/librecadfont1.py b/scripts/py/loaders/librecadfont1.py deleted file mode 100644 index d2c1db2..0000000 --- a/scripts/py/loaders/librecadfont1.py +++ /dev/null @@ -1,94 +0,0 @@ -import os - -class Symbol: - def __init__(self, char, code): - self.char = char - self.code = code - self.ref = None - self.lines = [] - -class LibreCADFont1(object): - - def __init__(self): - self.meta = {} - self.symbols = {} - - def getSymbol(self, char): - return self.symbols[char] - - def load_from_file(self, filename): - """ - Load font data from a file. - """ - with open(filename) as f: - lines = f.readlines() - #print lines - symbol = None - lastmeta = None - valid_meta = ['Format','Creator','Name','Version','Encoding', - 'LetterSpacing','WordSpacing','LineSpacingFactor','Created', - 'Last modified','Author','License','Notes'] - - for line in lines: - - if line == '\n': - if symbol: - self.symbols[symbol.char] = symbol - symbol = None - continue - - if line[0] == '#': - data = line[1:].strip().split(':',1) - - if (data[0] not in valid_meta) or len(data) < 2: - if data[0] and lastmeta: - self.meta[ lastmeta ] += ' ' + line[1:].strip() - continue - - self.meta[ data[0] ] = data[1].strip() - lastmeta = data[0] - elif line[0] == '[': - if line[1] == '#': - code = str(line[2:6]) - code_num = int(code, 16) - sym = unichr(code_num) - symbol = Symbol(sym, code) - else: - code = str(line[1:5]) - sym = line[7] - symbol = Symbol(sym, code) - else: - if symbol: - points = [] - segments = line.strip().split(';') - skip = False - for seg in segments: - if seg[0] == 'C': - ref = seg[1:5] - code_num = int(ref, 16) - sym = unichr(code_num) - symbol.ref = sym - skip = True - break - - else: - pts = seg.split(',') - if len(pts) == 3: - points.append( (float(pts[0]), float(pts[1]), float(pts[2][1:])) ) - else: - points.append( (float(pts[0]), float(pts[1])) ) - if not skip: - symbol.lines.append( points ) - - print self.meta - -def readfile(filename): - """ - Create a LibreCADFont1 object and load it's data from a file. - """ - lff = LibreCADFont1() - if os.path.exists(filename): - lff.load_from_file(filename) - return lff - else: - return None diff --git a/scripts/py/output/__init__.py b/scripts/py/output/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/scripts/py/output/common.py b/scripts/py/output/common.py deleted file mode 100644 index f37ad43..0000000 --- a/scripts/py/output/common.py +++ /dev/null @@ -1,424 +0,0 @@ -#!/bin/env python -# -*- coding: utf-8; -*- -# -# (c) 2016 FABtotum, http://www.fabtotum.com -# -# This file is part of FABUI. -# -# FABUI 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 2 of the License, or -# (at your option) any later version. -# -# FABUI 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 FABUI. If not, see . - -__authors__ = "Daniel Kesler" -__license__ = "GPL - https://opensource.org/licenses/GPL-3.0" -__version__ = "1.0" - -# Import external modules -import numpy as np - -# Import internal modules - -class EngraverOutput(object): - - def __init__(self, preset): - self.preset = preset - self.interleave = False - - self.skip_function = { - 'modulo' : self.get_y_list_modulo, - } - - def __sort_elements(self, elements, sort_list = [], reverse_list = [], use_reverse = False): - - if len(elements) == 0: - return [], [] - - if len(sort_list) == 0: - cur_x = 0.0 - cur_y = 0.0 - else: - last = sort_list[-1] - if last in reverse_list: - p0 = elements[last]['points'][0] - else: - p0 = elements[last]['points'][-1] - cur_x = p0[0] - cur_y = p0[1] - - closest = None - reverse = None - dist = 1e20 - - for i in xrange( len(elements) ): - #~ print elements[i] - if i not in sort_list: - e = elements[i] - p0 = e['points'][0] - dx = p0[0] - cur_x - dy = p0[1] - cur_y - d0 = np.sqrt(dx*dx + dy*dy) - - d1 = d0 - if use_reverse: - p0 = e['points'][-1] - dx = p0[0] - cur_x - dy = p0[1] - cur_y - d1 = np.sqrt(dx*dx + dy*dy) - - if d0 < dist: - dist = d0 - closest = i - elif d1 < dist and use_reverse: - dist = d1 - closest = i - reverse = i - - if reverse is not None: - reverse_list.append(reverse) - - if closest is None: - return sort_list, reverse_list - else: - #~ print "added" , closest - sort_list.append(closest) - - if len(sort_list) == len(elements): - return sort_list, reverse_list - else: - return self.__sort_elements(elements, sort_list, reverse_list) - - def draw(self, data, optimize=False): - #~ print 'draw-area', data.min_x, data.min_y, data.max_x, data.max_y - width = data.max_x - data.min_x - height = data.max_y - data.min_y - #~ print 'draw', width, height - #~ print 'offset', data.min_x, data.min_y - - for lyr in data.layers: - color = lyr.color - - if not self.skip_level(color): - - self.level(color) - - elements = lyr.primitives - - if optimize > 0: - idx_list, reverse_list = self.__sort_elements(elements, use_reverse=(optimize>1) ) - else: - idx_list = range(len(elements)) - reverse_list = [] - - for i in idx_list: - e = elements[i] - t = e['type'] - filled = False - if 'filled' in e: - filled = e['filled'] - - if not filled: - if 'points' in e: - if i in reverse_list: - points = [] - for pt in reversed(e['points']): - points.append(pt) - else: - points = e['points'] - - self.draw_polyline(points, color) - else: - if t == 'rect': - p1 = e['points'][0] - p2 = e['points'][2] - self.fill_rect(p1[0], p1[1], p2[0], p2[1], color) - else: - print 'TODO (filled)', t - - #~ if t == 'line': - #~ pass - #~ elif t == 'polyline' or t == 'spline' or t == 'ellipse' or t == 'circle' or t == 'arc': - #~ self.draw_polyline(e['points'], color) - #~ elif t == 'circle': - #~ c = e['center'] - #~ self.draw_circle( c[0], c[1], e['radius'], color) - #~ elif t == 'arc': - #~ c = e['center'] - #~ self.draw_arc( c[0], c[1], e['radius'], e['start'], e['end'], color) - #elif t == 'ellipse': - #~ c = e['center'] - #~ self.draw_ellipse(c[0], c[1], e['ratio'], e['major_axis'], e['start'], e['end'], color) - #~ elif t == 'rect': - #~ if e['filled']: - #~ p1 = e['points'][0] - #~ p2 = e['points'][2] - #~ self.fill_rect(p1[0], p1[1], p2[0], p2[1], color) - #~ else: - #~ pass - #~ else: - #~ print 'TODO', t - - def get_y_list_modulo(self, color, y1, y2): - """ - Generate a list of Y for drawing a primitive between y1 and y2. - If lines have to skipped, only the Y values that have to be drawn - will be in the output list. - - :param color: - :param y1: - :param y2: - :type color: uint8 - :type y1: float - :type y2: float - :returns: List of Y values that should be drawn - :rtype: list - """ - in_list = range(y1,y2) - out_list = [] - mod = int(self.preset['skip']['mod']) - tmp = self.preset['skip']['on'] - on = [] - for o in tmp: - on.append( int(o) ) - - for i in in_list: - if self.interleave: - v = (i+1) % mod - else: - v = i % mod - if v in on: - out_list.append(i) - - return out_list - - def __point_on_line(self, p1, p2, t): - """ for future use """ - dx = p2[0] - p1[0] - dy = p2[1] - p1[1] - - x = p1[0] + float(dx*t) - y = p1[1] + float(dy*t) - - return (x, y) - - def __sub_bezier(self, points, t): - """ for future use """ - sub_points = [] - - p1 = points[0] - for p2 in points[1:]: - pt = self.__point_on_line(p1, p2, t) - sub_points.append(pt) - p1 = p2 - - if len(sub_points) > 1: - return self.__sub_bezier(sub_points, t) - else: - return sub_points - - def __bezier_points(self, points, t): - """ for future use """ - if type(t) == list or type(t) == np.ndarray: - pts = [] - for t1 in t: - p = self.__sub_bezier(points, t1) - pts = pts + p - return pts - else: - return self.__sub_bezier(points, t) - - def fill_rect(self, x1, y1, x2, y2, color = 0): - """ - Draw a rectangle out of hlines. Skip lines if needed. - """ - y_list = self.skip_function[ self.preset['skip']['type'] ](color, y1, y2) - - for y in y_list: - self.draw_hline(x1, x2, y, color) - - def draw_polyline(self, points, color = 0): - #~ p0 = points[0] - have_prev = False - - for p in points: - p1 = ( int(p[0]), int(p[1])) - #cv2.circle(self.dbg_img, p1, 2, 0) - if have_prev: - self.draw_line( p0[0], p0[1], p[0], p[1], color) - - p0 = p - have_prev = True - - def draw_line(self, x1, y1, x2, y2, color = 0): - raise NotImplementedError('"draw_line" function must be implemented') - - def draw_circle(self, x0, y0, r, color = 0, step = 10.0): - """ - Draw an arc. - """ - - start = 0.0 - end = 360.0 - steps = int( 360.0 / step ) - - have_prev = False - - for a in xrange(steps): - angle = np.deg2rad(start + a*step) - x2 = x0 + np.cos(angle)*r - y2 = y0 + np.sin(angle)*r - - if have_prev: - self.draw_line(x1,y1, x2,y2, color) - - x1 = x2 - y1 = y2 - have_prev = True - - if (start + (steps-1)*step) != end: - angle = np.deg2rad(end) - x2 = x0 + np.cos(angle)*r - y2 = y0 + np.sin(angle)*r - - self.draw_line(x1,y1, x2,y2, color) - - def __ellipse_point(self, center, r1, r2, rotM, t): - x1 = r1 * np.cos( np.radians(t) ) - y1 = r2 * np.sin( np.radians(t) ) - tmp = np.array([x1,y1]) - p1 = center + (tmp * rotM) - return p1.A1[0], p1.A1[1] - - def draw_ellipse(self, x0, y0, ratio, axis, start, end, color = 0, step = 10.0): - # Get length of axis vector - r1 = np.linalg.norm(axis) - # Get second radius - r2 = r1 * ratio - - # Get axis angle - if axis[0] == 0: - a = np.radians(90.0) - elif axis[1] == 0: - a = np.radians(0.0) - else: - a = np.arctan(axis[1] / axis[0]) - - # Prepare rotation matrix - center = np.array([x0,y0]) - c1 = np.cos(a) - c2 = np.sin(a) - rotM = np.matrix([ - [c1,c2], - [-c2,c1] - ]) - rotMCCW = np.matrix([ - [c1,-c2], - [c2,c1] - ]) - - eye = np.matrix([ [1.0, 0.0], [0.0, 1.0] ]) - - fix = 0 - if start > end: - start += 180.0 - if start > 360.0: - start -= 360.0 - end += 180.0 - - have_prev = False - - tl = np.arange(start, end, step) - - for t in tl: - x2,y2 = self.__ellipse_point(center, r1, r2, rotM, t) - - if have_prev: - self.draw_line(x1,y1, x2,y2) - - x1 = x2 - y1 = y2 - have_prev = True - - x2,y2 = self.__ellipse_point(center, r1, r2, rotM, end) - self.draw_line(x1,y1, x2,y2) - - def draw_arc(self, x0, y0, r, start, end, color = 0, step = 10.0): - """ - Draw an arc. - """ - - angle = end - start - if angle < 0: - angle += 360 - - steps = int(abs(angle / step)) - - have_prev = False - - for a in xrange(steps): - angle = np.deg2rad(start + a*step) - x2 = x0 + np.cos(angle)*r - y2 = y0 + np.sin(angle)*r - - if have_prev: - self.draw_line(x1,y1, x2,y2, color) - - x1 = x2 - y1 = y2 - have_prev = True - - if (start + (steps-1)*step) != end: - angle = np.deg2rad(end) - x2 = x0 + np.cos(angle)*r - y2 = y0 + np.sin(angle)*r - - self.draw_line(x1,y1, x2,y2, color) - - def draw_hline(self, x1, x2, y, color = 0): - """ - Draw a horizontal line. - - :param x1: Start X - :param x2: End X - :param y: Start and end Y - :param color: Line color - """ - self.draw_line(x1, y, x2, y, color) - - def start(self): - """ - Engraver initialization callback. - """ - pass - - def skip_level(self, color): - return False - - def level(self, color): - """ - Level change callback. - - :param color: Level color - :type color: uint8 - """ - pass - - def comment(self, comment): - """ - Line comment callback. - """ - pass - - def end(self): - """ - Engraver finalization callback. - """ - pass diff --git a/scripts/py/output/debug.py b/scripts/py/output/debug.py deleted file mode 100644 index 74effc9..0000000 --- a/scripts/py/output/debug.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/bin/env python -# -*- coding: utf-8; -*- -# -# (c) 2016 FABtotum, http://www.fabtotum.com -# -# This file is part of FABUI. -# -# FABUI 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 2 of the License, or -# (at your option) any later version. -# -# FABUI 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 FABUI. If not, see . - -__authors__ = "Daniel Kesler" -__license__ = "GPL - https://opensource.org/licenses/GPL-3.0" -__version__ = "1.0" - -# Import external modules -import numpy as np -import cv2,cv - -# Import internal modules -from .common import EngraverOutput - -class DebugEngraver(EngraverOutput): - """ - Engrever Debug class. Stores the result in an image with - resolution of one dot per pixel. - """ - - def __init__(self, filename, width, height, color = 0, dot_size = 0.1, invert = False, preset = {}): - self.filename = filename - self.dot_size = dot_size - - self.invert = invert - - if invert: - color = 255 - color - - self.dbg_img = np.ones((int(height), int(width), 1), np.uint8)*color - super(DebugEngraver, self).__init__(preset) - - def draw_hline(self, x1, x2, y, color = 0): - tx1 = min(x1,x2) - tx2 = max(x1,x2) - if self.invert: - color = 255 - color - self.dbg_img[y:y+1, tx1:tx2] = color - - def draw_line(self, x1, y1, x2, y2, color = 0): - if self.invert: - color = 255 - color - cv2.line(self.dbg_img, (int(x1),int(y1)), (int(x2),int(y2)), color) - - def draw_circle(self, x1, y1, r, color = 0): - if self.invert: - color = 255 - color - cv2.circle(self.dbg_img, (int(x1), int(y1)), int(r), color) - - def end(self): - print "Saving output to file '{0}'".format(self.filename) - self.dbg_img = cv2.flip(self.dbg_img,0) - cv2.imwrite(self.filename, self.dbg_img) - - def show(self): - self.dbg_img = cv2.flip(self.dbg_img,0) - cv2.imshow('image', self.dbg_img) - cv2.waitKey(0) - cv2.destroyAllWindows() diff --git a/scripts/py/output/laser.py b/scripts/py/output/laser.py deleted file mode 100644 index d1015fc..0000000 --- a/scripts/py/output/laser.py +++ /dev/null @@ -1,301 +0,0 @@ -#!/bin/env python -# -*- coding: utf-8; -*- -# -# (c) 2016 FABtotum, http://www.fabtotum.com -# -# This file is part of FABUI. -# -# FABUI 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 2 of the License, or -# (at your option) any later version. -# -# FABUI 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 FABUI. If not, see . - -__authors__ = "Daniel Kesler" -__license__ = "GPL - https://opensource.org/licenses/GPL-3.0" -__version__ = "1.0" - -# Import standard python module -import time - -# Import external modules -import numpy as np - -# Import internal modules -from .common import EngraverOutput - -class LaserEngraver(EngraverOutput): - """ - Laser engraver gcode generator class. - """ - - def __init__(self, filename, scale, preset, statistics=False): - self.filename = filename - self.scale = scale - self.cur_x = 0.0 - self.cur_y = 0.0 - self.laser_pwm = 0 - super(LaserEngraver, self).__init__(preset) - - self.pwm_function = { - 'const' : self.get_pwm_value_const, - 'linear' : self.get_pwm_value_linear - } - - self.speed_function = { - 'const' : self.get_burn_speed_value_const, - 'linear' : self.get_burn_speed_value_linear, - 'travel' : self.get_travel_speed_value_const - } - - self.get_pwm_value = self.pwm_function[ preset['pwm']['type'] ] - self.get_burn_speed = self.speed_function[ preset['speed']['type'] ] - self.get_travel_speed = self.speed_function[ 'travel' ] - - self.laser_is_on = False - self.off_during_travel = False - if 'off_during_travel' in preset['pwm']: - self.off_during_travel = preset['pwm']['off_during_travel'] - - self.statistics = statistics - self.stats = { - 'travel_length' : 0.0, - 'burn_length' : 0.0, - 'travel_time' : 0.0, - 'burn_time' : 0.0 - } - - self.pwm_gocde = 'M4 S{0}\r\n' - #~ self.pwm_gocde = 'M4 S{0}\r\nM400\r\n' - #~ self.fd.write('M400 - - def comment(self, comment): - """ - Add comment to the gcode output. - :param comment: Comment to be added. - - """ - self.fd.write(";{0}\r\n".format(comment)) - - def get_pwm_value_const(self, color): - """ - Returns constant PWM value. - - :returns: PWM value - :rtype: uint8 - """ - return int(self.preset['pwm']['value']) - - def get_pwm_value_linear(self, color): - """ - Returns PWM value using a linear function to convert color - to PWM value. - - :param color: - :type color: uint8 - :returns: PWM value. - :rtype: uint8 - """ - x_min = int(self.preset['pwm']['in_min']) - x_max = int(self.preset['pwm']['in_max']) - y_min = int(self.preset['pwm']['out_min']) - y_max = int(self.preset['pwm']['out_max']) - - dx = float(x_max - x_min) - dy = float(y_max - y_min) - k = dy / dx - - y = 0 - - if color >= x_min and color < x_max: - y = y_min + (color-x_min) * k - - if color >= x_max: - y = y_max - - return int(y) - - def get_burn_speed_value_const(self, color): - """ - Returns constant burning speed. - - :returns: Burn speed value. - :rtype: uint8 - """ - return int(self.preset['speed']['burn']) - - def get_burn_speed_value_linear(self, color): - """ - Returns burning speed using a linear function to convert color - to speed value. - - :param color: - :type color: uint8 - :returns: Burn speed value. - :rtype: int - """ - x_min = int(self.preset['speed']['in_min']) - x_max = int(self.preset['speed']['in_max']) - y_min = int(self.preset['speed']['out_min']) - y_max = int(self.preset['speed']['out_max']) - - dx = float(x_max - x_min) - dy = float(y_max - y_min) - k = dy / dx - - y = 0 - - if color >= x_min and color < x_max: - y = y_min + (color-x_min) * k - - if color >= x_max: - y = y_max - - return int(y) - - def get_travel_speed_value_const(self, color = 0): - """ - Returns constant Travel speed. - - :returns: Travel speed. - :rtype: uint8 - """ - return self.preset['speed']['travel'] - - def level(self, color): - """ - Level change setup gcode. - """ - pwm = self.get_pwm_value(color) - self.laser_pwm = pwm - speed = self.get_burn_speed(color) - print "Color {0} => PWM: {1}, Speed: {2}".format(color, pwm, speed) - self.comment(' Applying PWM value {0}'.format(pwm) ) - self.fd.write(self.pwm_gocde.format(pwm)) - self.fd.write('M400 ;Make sure all previous moves are finished\r\n') - - def skip_level(self, color): - pwm = self.get_pwm_value(color) - return (pwm == 0) - - def start(self): - """ - Engraver start function. - """ - self.fd = open(self.filename, 'w') - self.add_start_code() - - def add_start_code(self): - """ - Engraver start code. - """ - now = time.strftime("%c") - self.fd.write("""\ -;FABtotum laser engraving, coded on {0} -G4 S1 ;1 millisecond pause to buffer the bep bep -M450 S2 ; Activate laser module -M793 S4 ;set laser head -M728 ;FAB bep bep -G90 ; absolute mode -G4 S1 ;1 second pause to reach the printer (run fast) -G1 F10000 ;Set travel speed -M107 -""".format(now)) - - def add_engrave_move(self, x, y, color): - """ - Add gcode for an engraving move. - - :param x: Target X. - :param y: Target y. - :param color: Engrave color. - :type x: float - :type y: float - :type color: uint8 - """ - - # mm/min - feed = self.get_burn_speed(color) - pwm = self.get_pwm_value(color) - - if self.statistics: - dx = self.cur_x - x - dy = self.cur_y - y - travel = np.sqrt(dx*dx + dy*dy) - self.stats['burn_length'] += travel - self.stats['burn_time'] += travel / (feed/60) - - #~ if not self.laser_is_on: - #~ self.laser_is_on = True - if self.off_during_travel: - self.fd.write('M400\r\n') - self.fd.write(self.pwm_gocde.format(pwm)) - - - self.fd.write("G1 X{0} Y{1} F{2}\r\n".format(x*self.scale[0], y*self.scale[1], feed) ) - self.cur_x = x - self.cur_y = y - - def add_travel_move(self, x, y): - """ - Add gcode for a travel move. - - :param x: Target X - :param y: Target y - :type x: float - :type y: float - """ - - # mm/min - feed = self.get_travel_speed() - - if x != self.cur_x or y != self.cur_y: - - #~ if self.off_during_travel and self.laser_is_on: - #~ self.laser_is_on = False - #~ self.fd.write(self.pwm_gocde.format(0)) - - if self.off_during_travel: - self.fd.write('M400\r\n') - self.fd.write(self.pwm_gocde.format(0)) - - - if self.statistics: - dx = self.cur_x - x - dy = self.cur_y - y - travel = np.sqrt(dx*dx + dy*dy) - self.stats['travel_length'] += travel - self.stats['travel_time'] += travel / (feed/60) - - self.fd.write("G0 X{0} Y{1} F{2}\r\n".format(x*self.scale[0], y*self.scale[1], feed) ) - self.cur_x = x - self.cur_y = y - - def end(self): - """ - Engraver end function. - """ - self.add_end_code() - self.fd.close() - - def add_end_code(self): - """ - Shutdown code. - """ - self.fd.write("""\ -M400 ;Wait for all moves to finish -M728 ;FAB bep bep (end print) -G4 S1 ;pause -M5 ;shutdown -""") - - def draw_line(self, x1, y1, x2, y2, color = 0): - self.add_travel_move(x1, y1) - self.add_engrave_move(x2, y2, color)