Пример #1
0
    def export_svg(self):
        ''' Exports an svg file at 90 DPI with per-object colors.
        '''
        xmin = self.cad.xmin*self.cad.mm_per_unit
        dx = (self.cad.xmax - self.cad.xmin)*self.cad.mm_per_unit
        ymax = self.cad.ymax*self.cad.mm_per_unit
        dy = (self.cad.ymax - self.cad.ymin)*self.cad.mm_per_unit
        stroke = max(dx, dy)/100.


        Path.write_svg_header(self.filename, dx, dy)

        i = 0
        for expr in self.cad.shapes:
            # Generate an ASDF
            if self.event.is_set(): return
            asdf = self.make_asdf(expr, flat=True)
            i += 1
            self.window.progress = i*33/len(self.cad.shapes)

            # Find the contours of the ASDF
            if self.event.is_set(): return
            contours = self.make_contour(asdf)
            i += 2
            self.window.progress = i*33/len(self.cad.shapes)

            # Write them out to the SVG file
            for c in contours:
                c.write_svg_contour(
                    self.filename, xmin, ymax, stroke=stroke,
                    color=expr.color if expr.color else (0,0,0)
                )

        Path.write_svg_footer(self.filename)
Пример #2
0
    def contour(self, bit_diameter, count=1, overlap=0.5):
        """ @brief Finds a set of isolines on a distance field image.
            @param bit_diameter Tool diameter (in mm)
            @param count Number of offsets
            @param overlap Overlap between offsets
            @returns A list of Paths
        """
        if self.depth != 'f' or self.channels != 1:
            raise ValueError('Invalid image type for contour cut '+
                '(requires floating-point, 1-channel image)')

        max_distance = max(self.array.flatten())
        levels = [bit_diameter/2]
        step = bit_diameter * overlap
        if count == -1:
            while levels[-1] < max_distance:
                levels.append(levels[-1] + step)
            levels[-1] = max_distance
        else:
            for i in range(count-1):
                levels.append(levels[-1] + step)
        levels = (ctypes.c_float*len(levels))(*levels)

        ptr = ctypes.POINTER(ctypes.POINTER(Path_))()
        path_count = libfab.find_paths(
            self.width, self.height, self.pixels,
            1./self.pixels_per_mm, len(levels),
            levels, ptr)

        paths = [Path.from_ptr(ptr[i]) for i in range(path_count)]
        libfab.free_paths(ptr, path_count)

        return Path.sort(paths)
Пример #3
0
    def export_svg(self):
        ''' Exports an svg file at 90 DPI with per-object colors.
        '''
        xmin = self.cad.xmin * self.cad.mm_per_unit
        dx = (self.cad.xmax - self.cad.xmin) * self.cad.mm_per_unit
        ymax = self.cad.ymax * self.cad.mm_per_unit
        dy = (self.cad.ymax - self.cad.ymin) * self.cad.mm_per_unit
        stroke = max(dx, dy) / 100.

        Path.write_svg_header(self.filename, dx, dy)

        i = 0
        for expr in self.cad.shapes:
            # Generate an ASDF
            if self.event.is_set(): return
            asdf = self.make_asdf(expr, flat=True)
            i += 1
            self.window.progress = i * 33 / len(self.cad.shapes)

            # Find the contours of the ASDF
            if self.event.is_set(): return
            contours = self.make_contour(asdf)
            i += 2
            self.window.progress = i * 33 / len(self.cad.shapes)

            # Write them out to the SVG file
            for c in contours:
                c.write_svg_contour(self.filename,
                                    xmin,
                                    ymax,
                                    stroke=stroke,
                                    color=expr.color if expr.color else
                                    (0, 0, 0))

        Path.write_svg_footer(self.filename)
Пример #4
0
    def finish_cut(self, bit_diameter, overlap, bit_type):
        ''' Calculates xy and yz finish cuts on a 16-bit heightmap
        '''

        if self.depth != 16 or self.channels != 1:
            raise ValueError('Invalid image type for finish cut ' +
                             '(requires 16-bit, 1-channel image)')

        ptr = ctypes.POINTER(ctypes.POINTER(Path_))()
        path_count = libfab.finish_cut(self.width, self.height, self.pixels,
                                       self.mm_per_pixel, self.mm_per_bit,
                                       bit_diameter, overlap, bit_type, ptr)

        paths = [Path.from_ptr(ptr[i]) for i in range(path_count)]
        libfab.free_paths(ptr, path_count)

        return paths
Пример #5
0
    def finish_cut(self, bit_diameter, overlap, bit_type):
        ''' Calculates xy and yz finish cuts on a 16-bit heightmap
        '''

        if self.depth != 16 or self.channels != 1:
            raise ValueError('Invalid image type for finish cut '+
                '(requires 16-bit, 1-channel image)')

        ptr = ctypes.POINTER(ctypes.POINTER(Path_))()
        path_count = libfab.finish_cut(
            self.width, self.height, self.pixels,
            self.mm_per_pixel, self.mm_per_bit,
            bit_diameter, overlap, bit_type, ptr)

        paths = [Path.from_ptr(ptr[i]) for i in range(path_count)]
        libfab.free_paths(ptr, path_count)

        return paths
Пример #6
0
    def run(self, asdf):
        """ @brief Generates one or more toolpaths
            @param asdf Input ASDF data structure
            @returns Dictionary with 'planes' (list of list of paths) and 'axis_names' (list of strings) items
        """
        # Parameters used by all operators
        values = self.get_values([
            'mode', 'res', 'diameter', 'stepover_r', 'tool', 'step',
            'stepover_f', 'cut'
        ])
        if not values: return False

        # Get more parameters based on mode
        if values['mode'] == 1:
            v = self.get_values(['alpha', 'beta'])
            if not v: return False
            values.update(v)
        elif values['mode'] == 2:
            v = self.get_values(['cuts_per'])
            if not v: return False
            values.update(v)

        ## @var planes
        # List of lists of paths.  Each list of paths represent a set of cuts on a given plane.
        self.planes = []

        ## @var axis_names
        # List of strings representing axis names.
        self.axis_names = []

        if values['mode'] == 0:
            target_planes = [(0, 0, '+Z'), (180, 90, '+Y'), (0, 90, '-Y'),
                             (90, 90, '-X'), (270, 90, '+X')]
        elif values['mode'] == 1:
            target_planes = [(values['alpha'], values['beta'], 'view')]
        elif values['mode'] == 2:
            cuts = values['cuts_per']
            alphas = [-90 + 180 * v / float(cuts - 1) for v in range(cuts)]
            betas = [
                360 * v / float(cuts * 2 - 2) for v in range(2 * cuts - 2)
            ]
            target_planes = []
            for a in alphas:
                for b in betas:
                    target_planes.append((a, b, '%g, %g' % (a, b)))

        for a, b, axis in target_planes:
            koko.FRAME.status = 'Rendering ASDF on %s axis' % axis

            # Find endmill pointing vector
            v = -Vec3f(0, 0, 1).deproject(a, b)

            # Render the ASDF to a bitmap at the appropriate resolution
            img = asdf.render_multi(resolution=values['res'], alpha=a,
                                    beta=b)[0]

            # Find transformed ADSF bounds
            bounds = asdf.bounds(a, b)

            paths = []
            if values['cut'] in [0, 2]:
                paths += self.rough_cut(img, values['diameter'],
                                        values['stepover_r'], values['step'],
                                        bounds, axis)

            if values['cut'] in [1, 2]:
                paths += self.finish_cut(img, values['diameter'],
                                         values['stepover_f'], values['tool'],
                                         bounds, axis)

            # Helper function to decide whether a path is inside the
            # ASDF's bounding box
            def inside(pt, asdf=asdf):
                d = values['diameter']
                return (pt[0] >= -2 * d
                        and pt[0] <= asdf.X.upper - asdf.X.lower + 2 * d
                        and pt[1] >= -2 * d
                        and pt[1] <= asdf.Y.upper - asdf.Y.lower + 2 * d
                        and pt[2] <= 2 * d
                        and pt[2] >= -(asdf.Z.upper - asdf.Z.lower))

            culled = []
            for p in paths:
                for i in range(len(p.points)):
                    p.points[i, :] = list(
                        Vec3f(p.points[i, :]).deproject(a, b))
                    p.points[i, :] -= [asdf.xmin, asdf.ymin, asdf.zmax]
                p.points = np.hstack(
                    (p.points, np.array([list(v)] * p.points.shape[0])))
                current_path = []
                for pt in p.points:
                    if inside(pt):
                        current_path.append(pt)
                    elif current_path:
                        culled.append(Path(np.vstack(current_path)))
                        current_path = []
                if current_path:
                    culled.append(Path(np.vstack(current_path)))

            self.planes.append(culled)
            self.axis_names.append(axis)

        paths = reduce(operator.add, self.planes)
        koko.GLCANVAS.load_paths(paths, asdf.xmin, asdf.ymin, asdf.zmax)

        return {'planes': self.planes, 'axis_names': self.axis_names}
Пример #7
0
    def run(self, paths):
        ''' Convert the path from the previous panel into a g-code file
        '''

        koko.FRAME.status = 'Converting to .g file'

        values = self.get_values()
        if not values: return False

        # Reverse direction for climb cutting
        if values['type']:
            paths = Path.sort([p.reverse() for p in paths])

        # Check to see if all of the z values are the same.  If so,
        # we can use 2D cutting commands; if not, we'll need
        # to do full three-axis motion control
        zmin = paths[0].points[0][2]
        flat = True
        for p in paths:
            if not all(pt[2] == zmin for pt in p.points):
                flat = False

        # Create a temporary file to store the .sbp instructions
        self.file = tempfile.NamedTemporaryFile(suffix=self.extension)

        self.file.write("%%\n")  # tape start
        self.file.write("G17\n")  # XY plane
        self.file.write("G20\n")  # Inch mode
        self.file.write("G40\n")  # Cancel tool diameter compensation
        self.file.write("G49\n")  # Cancel tool length offset
        self.file.write("G54\n")  # Coordinate system 1
        self.file.write("G80\n")  # Cancel motion
        self.file.write("G90\n")  # Absolute programming
        self.file.write("G94\n")  # Feedrate is per minute

        scale = 1 / 25.4  # inch units

        self.file.write("T%dM06\n" % values['tool'])  # Tool selection + change
        self.file.write("F%0.4f\n" %
                        (60 * scale * values['feed']))  # Feed rate
        self.file.write("S%0.4f\n" % values['spindle'])  # spindle speed
        if values['coolant']: self.file.write("M08\n")  # coolant on

        # Move up before starting spindle
        self.file.write("G00Z%0.4f\n" % (scale * values['jog']))
        self.file.write("M03\n")  # spindle on (clockwise)
        self.file.write("G04 P1\n")  # pause one second to spin up spindle

        xy = lambda x, y: (scale * x, scale * y)
        xyz = lambda x, y, z: (scale * x, scale * y, scale * z)

        for p in paths:

            # Move to the start of this path at the jog height
            self.file.write("G00X%0.4fY%0.4fZ%0.4f\n" %
                            xyz(p.points[0][0], p.points[0][1], values['jog']))

            # Plunge to the desired depth
            self.file.write(
                "G01Z%0.4f F%0.4f\n" %
                (p.points[0][2] * scale, 60 * scale * values['plunge']))

            # Restore XY feed rate
            self.file.write("F%0.4f\n" % (60 * scale * values['feed']))

            # Cut each point in the segment
            for pt in p.points:
                if flat: self.file.write("X%0.4fY%0.4f\n" % xy(*pt[0:2]))
                else: self.file.write("X%0.4fY%0.4fZ%0.4f\n" % xyz(*pt))

            # Lift the bit up to the jog height at the end of the segment
            self.file.write("Z%0.4f\n" % (scale * values['jog']))

        self.file.write("M05\n")  # spindle stop
        if values['coolant']: self.file.write("M09\n")  # coolant off
        self.file.write("M30\n")  # program end and reset
        self.file.write("%%\n")  # tape end
        self.file.flush()

        koko.FRAME.status = ''
        return True
Пример #8
0
    def run(self, paths):
        """ @brief Convert the path from the previous panel into a shopbot file.
            @param paths List of Paths
        """

        koko.FRAME.status = 'Converting to .sbp file'

        values = self.get_values()
        if not values:  return False

        # Reverse direction for climb cutting
        if values['type']:
            paths = Path.sort([p.reverse() for p in paths])

        # Check to see if all of the z values are the same.  If so,
        # we can use 2D cutting commands; if not, we'll need
        # to do full three-axis motion control
        zmin = paths[0].points[0][2]
        flat = True
        for p in paths:
            if not all(pt[2] == zmin for pt in p.points):
                flat = False

        ## @var file
        # tempfile.NamedTemporaryFile to store OpenSBP commands
        self.file = tempfile.NamedTemporaryFile(suffix=self.extension)

        self.file.write("SA\r\n")   # plot absolute
        self.file.write("TR,%s,1,\r\n" % values['spindle']) # spindle speed
        self.file.write("SO,1,1\r\n") # set output number 1 to on
        self.file.write("pause,2,\r\n") # pause for spindle to spin up

        scale = 1 if values['units'] else 1/25.4 # mm vs inch units

        # Cut and jog speeds
        self.file.write("MS,%f,%f\r\n" %
            (values['cut_speed']*scale, values['cut_speed']*scale))
        self.file.write("JS,%f,%f\r\n" %
            (values['jog_speed']*scale, values['jog_speed']*scale))

        self.file.write("JZ,%f\r\n" % (values['jog']*scale)) # Move up

        xy  = lambda x,y:   (scale*x, scale*y)
        xyz = lambda x,y,z: (scale*x, scale*y, scale*z)


        for p in paths:

            # Move to the start of this path with the pen up
            self.file.write("J2,%f,%f\r\n" % xy(*p.points[0][0:2]))

            if flat:    self.file.write("MZ,%f\r\n" % (zmin*scale))
            else:       self.file.write("M3,%f,%f,%f\r\n" % xyz(*p.points[0]))

            # Cut each point in the segment
            for pt in p.points:
                if flat:    self.file.write("M2,%f,%f\r\n" % xy(*pt[0:2]))
                else:       self.file.write("M3,%f,%f,%f\r\n" % xyz(*pt))

            # Lift then pen up at the end of the segment
            self.file.write("MZ,%f\r\n" % (values['jog']*scale))

        self.file.flush()

        koko.FRAME.status = ''
        return True
Пример #9
0
    def run(self, paths):
        ''' Convert the path from the previous panel into a g-code file
        '''

        koko.FRAME.status = 'Converting to .g file'

        values = self.get_values()
        if not values:  return False

        # Reverse direction for climb cutting
        if values['type']:
            paths = Path.sort([p.reverse() for p in paths])


        # Check to see if all of the z values are the same.  If so,
        # we can use 2D cutting commands; if not, we'll need
        # to do full three-axis motion control
        zmin = paths[0].points[0][2]
        flat = True
        for p in paths:
            if not all(pt[2] == zmin for pt in p.points):
                flat = False

        # Create a temporary file to store the .sbp instructions
        self.file = tempfile.NamedTemporaryFile(suffix=self.extension)

        self.file.write("%%\n")     # tape start
        self.file.write("G17\n")    # XY plane
        self.file.write("G20\n")    # Inch mode
        self.file.write("G40\n")    # Cancel tool diameter compensation
        self.file.write("G49\n")    # Cancel tool length offset
        self.file.write("G54\n")    # Coordinate system 1
        self.file.write("G80\n")    # Cancel motion
        self.file.write("G90\n")    # Absolute programming
        self.file.write("G94\n")    # Feedrate is per minute

        scale = 1/25.4 # inch units

        self.file.write("T%dM06\n" % values['tool']) # Tool selection + change
        self.file.write("F%0.4f\n" % (60*scale*values['feed']))  # Feed rate
        self.file.write("S%0.4f\n" % values['spindle']) # spindle speed
        if values['coolant']:   self.file.write("M08\n") # coolant on

        # Move up before starting spindle
        self.file.write("G00Z%0.4f\n" % (scale*values['jog']))
        self.file.write("M03\n") # spindle on (clockwise)
        self.file.write("G04 P1\n") # pause one second to spin up spindle

        xy  = lambda x,y:   (scale*x, scale*y)
        xyz = lambda x,y,z: (scale*x, scale*y, scale*z)


        for p in paths:

            # Move to the start of this path at the jog height
            self.file.write("G00X%0.4fY%0.4fZ%0.4f\n" %
                            xyz(p.points[0][0], p.points[0][1], values['jog']))

            # Plunge to the desired depth
            self.file.write("G01Z%0.4f F%0.4f\n" %
                            (p.points[0][2]*scale, 60*scale*values['plunge']))

            # Restore XY feed rate
            self.file.write("F%0.4f\n" % (60*scale*values['feed']))

            # Cut each point in the segment
            for pt in p.points:
                if flat:    self.file.write("X%0.4fY%0.4f\n" % xy(*pt[0:2]))
                else:       self.file.write("X%0.4fY%0.4fZ%0.4f\n" % xyz(*pt))

            # Lift the bit up to the jog height at the end of the segment
            self.file.write("Z%0.4f\n" % (scale*values['jog']))

        self.file.write("M05\n") # spindle stop
        if values['coolant']:   self.file.write("M09\n") # coolant off
        self.file.write("M30\n") # program end and reset
        self.file.write("%%\n")  # tape end
        self.file.flush()

        koko.FRAME.status = ''
        return True