Exemple #1
0
    def check_and_load_preset(self, preset_name):
        preset = load_preset(preset_name)
        if not preset:
            info_dialog(self,
                        _('Preset "%s" not found.') % preset_name,
                        caption=_('Preset'))

        return preset
Exemple #2
0
 def get_preset_name(self):
     preset_name = self.preset_chooser.GetValue().strip()
     if preset_name:
         return preset_name
     else:
         info_dialog(self,
                     _("Please enter or select a preset name first."),
                     caption=_('Preset'))
         return
Exemple #3
0
    def __init__(self, *args, **kwargs):
        self.print_server = kwargs.pop("print_server")
        wx.Frame.__init__(self, *args, **kwargs)

        panel = wx.Panel(self)
        sizer = wx.BoxSizer(wx.VERTICAL)

        text = wx.StaticText(
            panel,
            label=
            _("A print preview has been opened in your web browser.  This window will stay open in order to communicate with the JavaScript code running in your browser.\n\nThis window will close after you close the print preview in your browser, or you can close it manually if necessary."
              ))
        font = wx.Font(14, wx.DEFAULT, wx.NORMAL, wx.NORMAL)
        text.SetFont(font)
        sizer.Add(text, proportion=1, flag=wx.ALL | wx.EXPAND, border=20)

        stop_button = wx.Button(panel, id=wx.ID_CLOSE)
        stop_button.Bind(wx.EVT_BUTTON, self.close_button_clicked)
        sizer.Add(stop_button,
                  proportion=0,
                  flag=wx.ALIGN_CENTER | wx.ALL,
                  border=10)

        panel.SetSizer(sizer)
        panel.Layout()

        self.timer = wx.PyTimer(self.__watcher)
        self.timer.Start(250)
Exemple #4
0
    def add_preset(self, event, overwrite=False):
        preset_name = self.get_preset_name()
        if not preset_name:
            return

        if not overwrite and load_preset(preset_name):
            info_dialog(
                self,
                _('Preset "%s" already exists.  Please use another name or press "Overwrite"'
                  ) % preset_name,
                caption=_('Preset'))

        save_preset(preset_name, self.get_preset_data())
        self.update_preset_list()

        event.Skip()
Exemple #5
0
    def validate_satin_column(self):
        # The node should have exactly two paths with no fill.  Each
        # path should have the same number of points, meaning that they
        # will both be made up of the same number of bezier curves.

        node_id = self.node.get("id")

        if self.get_style("fill") is not None:
            self.fatal(
                _("satin column: object %s has a fill (but should not)") %
                node_id)

        if len(self.csp) == 2:
            if len(self.csp[0]) != len(self.csp[1]):
                self.fatal(_("satin column: object %(id)s has two paths with an unequal number of points (%(length1)d and %(length2)d)") % \
                             dict(id=node_id, length1=len(self.csp[0]), length2=len(self.csp[1])))
Exemple #6
0
    def flatten_beziers_with_rungs(self):
        input_paths = [self.flatten([path]) for path in self.csp]
        input_paths = [shgeo.LineString(path[0]) for path in input_paths]

        paths = input_paths[:]
        paths.sort(key=lambda path: path.length, reverse=True)

        # Imagine a satin column as a curvy ladder.
        # The two long paths are the "rails" of the ladder.  The remainder are
        # the "rungs".
        rails = paths[:2]
        rungs = shgeo.MultiLineString(paths[2:])

        # The rails should stay in the order they were in the original CSP.
        # (this lets the user control where the satin starts and ends)
        rails.sort(key=lambda rail: input_paths.index(rail))

        result = []

        for rail in rails:
            if not rail.is_simple:
                self.fatal(
                    _("One or more rails crosses itself, and this is not allowed.  Please split into multiple satin columns."
                      ))

            # handle null intersections here?
            linestrings = shapely.ops.split(rail, rungs)

            print >> dbg, "rails and rungs", [str(rail) for rail in rails
                                              ], [str(rung) for rung in rungs]
            if len(linestrings.geoms) < len(rungs.geoms) + 1:
                self.fatal(
                    _("satin column: One or more of the rungs doesn't intersect both rails."
                      ) + "  " +
                    _("Each rail should intersect both rungs once."))
            elif len(linestrings.geoms) > len(rungs.geoms) + 1:
                self.fatal(
                    _("satin column: One or more of the rungs intersects the rails more than once."
                      ) + "  " +
                    _("Each rail should intersect both rungs once."))

            paths = [[inkstitch.Point(*coord) for coord in ls.coords]
                     for ls in linestrings.geoms]
            result.append(paths)

        return zip(*result)
Exemple #7
0
    def __init__(self, *args, **kwargs):
        # begin wxGlade: MyFrame.__init__
        self.tabs_factory = kwargs.pop('tabs_factory', [])
        self.cancel_hook = kwargs.pop('on_cancel', None)
        wx.Frame.__init__(self, None, wx.ID_ANY, _("Embroidery Params"))
        self.notebook = wx.Notebook(self, wx.ID_ANY)
        self.tabs = self.tabs_factory(self.notebook)

        for tab in self.tabs:
            tab.on_change(self.update_simulator)

        self.simulate_window = None
        self.simulate_thread = None
        self.simulate_refresh_needed = Event()

        wx.CallLater(1000, self.update_simulator)

        self.presets_box = wx.StaticBox(self, wx.ID_ANY, label=_("Presets"))

        self.preset_chooser = wx.ComboBox(self, wx.ID_ANY)
        self.update_preset_list()

        self.load_preset_button = wx.Button(self, wx.ID_ANY, _("Load"))
        self.load_preset_button.Bind(wx.EVT_BUTTON, self.load_preset)

        self.add_preset_button = wx.Button(self, wx.ID_ANY, _("Add"))
        self.add_preset_button.Bind(wx.EVT_BUTTON, self.add_preset)

        self.overwrite_preset_button = wx.Button(self, wx.ID_ANY,
                                                 _("Overwrite"))
        self.overwrite_preset_button.Bind(wx.EVT_BUTTON, self.overwrite_preset)

        self.delete_preset_button = wx.Button(self, wx.ID_ANY, _("Delete"))
        self.delete_preset_button.Bind(wx.EVT_BUTTON, self.delete_preset)

        self.cancel_button = wx.Button(self, wx.ID_ANY, _("Cancel"))
        self.cancel_button.Bind(wx.EVT_BUTTON, self.cancel)
        self.Bind(wx.EVT_CLOSE, self.cancel)

        self.use_last_button = wx.Button(self, wx.ID_ANY,
                                         _("Use Last Settings"))
        self.use_last_button.Bind(wx.EVT_BUTTON, self.use_last)

        self.apply_button = wx.Button(self, wx.ID_ANY, _("Apply and Quit"))
        self.apply_button.Bind(wx.EVT_BUTTON, self.apply)

        self.__set_properties()
        self.__do_layout()
Exemple #8
0
    def refresh_simulator(self, patches):
        if self.simulate_window:
            self.simulate_window.stop()
            self.simulate_window.load(patches=patches)
        else:
            my_rect = self.GetRect()
            simulator_pos = my_rect.GetTopRight()
            simulator_pos.x += 5

            screen_rect = wx.Display(0).ClientArea
            max_width = screen_rect.GetWidth() - my_rect.GetWidth()
            max_height = screen_rect.GetHeight()

            try:
                self.simulate_window = EmbroiderySimulator(
                    None,
                    -1,
                    _("Preview"),
                    simulator_pos,
                    size=(300, 300),
                    patches=patches,
                    on_close=self.simulate_window_closed,
                    target_duration=5,
                    max_width=max_width,
                    max_height=max_height)
            except:
                error = traceback.format_exc()

                try:
                    # a window may have been created, so we need to destroy it
                    # or the app will never exit
                    wx.Window.FindWindowByName("Preview").Destroy()
                except:
                    pass

                info_dialog(self, error, _("Internal Error"))

            self.simulate_window.Show()
            wx.CallLater(10, self.Raise)

        wx.CallAfter(self.simulate_window.go)
Exemple #9
0
    def __do_layout(self):
        # just to add space around the settings
        box = wx.BoxSizer(wx.VERTICAL)

        summary_box = wx.StaticBox(self, wx.ID_ANY, label=_("Inkscape objects"))
        sizer = wx.StaticBoxSizer(summary_box, wx.HORIZONTAL)
#        sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.description = wx.StaticText(self, style=wx.TE_WORDWRAP)
        self.update_description()
        self.description.SetLabel(self.description_text)
        self.description_container = box
        self.Bind(wx.EVT_SIZE, self.resized)
        sizer.Add(self.description, proportion=0, flag=wx.EXPAND|wx.ALL, border=5)
        box.Add(sizer, proportion=0, flag=wx.ALL, border=5)

        if self.toggle:
            box.Add(self.toggle_checkbox, proportion=0, flag=wx.BOTTOM, border=10)

        for param in self.params:
            description = wx.StaticText(self, label=param.description)
            description.SetToolTip(param.tooltip)

            self.settings_grid.Add(description, proportion=1, flag=wx.EXPAND|wx.RIGHT, border=40)

            if param.type == 'boolean':

                if len(param.values) > 1:
                    input = wx.CheckBox(self, style=wx.CHK_3STATE)
                    input.Set3StateValue(wx.CHK_UNDETERMINED)
                else:
                    input = wx.CheckBox(self)
                    if param.values:
                        input.SetValue(param.values[0])

                input.Bind(wx.EVT_CHECKBOX, self.changed)
            elif len(param.values) > 1:
                input = wx.ComboBox(self, wx.ID_ANY, choices=sorted(param.values), style=wx.CB_DROPDOWN)
                input.Bind(wx.EVT_COMBOBOX, self.changed)
                input.Bind(wx.EVT_TEXT, self.changed)
            else:
                value = param.values[0] if param.values else ""
                input = wx.TextCtrl(self, wx.ID_ANY, value=str(value))
                input.Bind(wx.EVT_TEXT, self.changed)

            self.param_inputs[param.name] = input

            self.settings_grid.Add(input, proportion=1, flag=wx.ALIGN_CENTER_VERTICAL)
            self.settings_grid.Add(wx.StaticText(self, label=param.unit or ""), proportion=1, flag=wx.ALIGN_CENTER_VERTICAL)

        box.Add(self.settings_grid, proportion=1, flag=wx.ALL, border=10)
        self.SetSizer(box)

        self.Layout()
Exemple #10
0
    def update_description(self):
        if len(self.nodes) == 1:
            description = _("These settings will be applied to 1 object.")
        else:
            description = _("These settings will be applied to %d objects."
                            ) % len(self.nodes)

        if any(len(param.values) > 1 for param in self.params):
            description += "\n • " + _(
                "Some settings had different values across objects.  Select a value from the dropdown or enter a new one."
            )

        if self.dependent_tabs:
            if len(self.dependent_tabs) == 1:
                description += "\n • " + _(
                    "Disabling this tab will disable the following %d tabs."
                ) % len(self.dependent_tabs)
            else:
                description += "\n • " + _(
                    "Disabling this tab will disable the following tab.")

        if self.paired_tab:
            description += "\n • " + _(
                "Enabling this tab will disable %s and vice-versa."
            ) % self.paired_tab.name

        self.description_text = description
Exemple #11
0
 def __init__(self, *args, **kwargs):
     inkex.Effect.__init__(self)
     self.OptionParser.add_option("-c",
                                  "--collapse_len_mm",
                                  action="store",
                                  type="float",
                                  dest="collapse_length_mm",
                                  default=3.0,
                                  help="max collapse length (mm)")
     self.OptionParser.add_option(
         "--hide_layers",
         action="store",
         type="choice",
         choices=["true", "false"],
         dest="hide_layers",
         default="true",
         help="Hide all other layers when the embroidery layer is generated"
     )
     self.OptionParser.add_option(
         "-O",
         "--output_format",
         action="store",
         type="string",
         dest="output_format",
         default="csv",
         help="Output file extenstion (default: csv)")
     self.OptionParser.add_option(
         "-P",
         "--path",
         action="store",
         type="string",
         dest="path",
         default=".",
         help="Directory in which to store output file")
     self.OptionParser.add_option("-F",
                                  "--output-file",
                                  action="store",
                                  type="string",
                                  dest="output_file",
                                  help="Output filename.")
     self.OptionParser.add_option(
         "-b",
         "--max-backups",
         action="store",
         type="int",
         dest="max_backups",
         default=5,
         help="Max number of backups of output files to keep.")
     self.OptionParser.usage += _(
         "\n\nSeeing a 'no such option' message?  Please restart Inkscape to fix."
     )
Exemple #12
0
    def effect(self):
        # Printing anything other than a valid SVG on stdout blows inkscape up.
        old_stdout = sys.stdout
        sys.stdout = sys.stderr

        self.patch_list = []

        self.elements = get_elements(self)

        if not self.elements:
            if self.selected:
                inkex.errormsg(_("No embroiderable paths selected."))
            else:
                inkex.errormsg(_("No embroiderable paths found in document."))
            inkex.errormsg(
                _("Tip: use Path -> Object to Path to convert non-paths before embroidering."
                  ))
            return

        if self.options.hide_layers:
            self.hide_layers()

        patches = elements_to_patches(self.elements)
        stitches = patches_to_stitches(
            patches, self.options.collapse_length_mm * PIXELS_PER_MM)
        inkstitch.write_embroidery_file(self.get_output_path(), stitches,
                                        self.document.getroot())

        new_layer = inkex.etree.SubElement(self.document.getroot(),
                                           SVG_GROUP_TAG, {})
        new_layer.set('id', self.uniqueId("embroidery"))
        new_layer.set(inkex.addNS('label', 'inkscape'), _('Embroidery'))
        new_layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')

        emit_inkscape(new_layer, stitches)

        sys.stdout = old_stdout
Exemple #13
0
class SatinColumn(EmbroideryElement):
    element_name = _("Satin Column")

    def __init__(self, *args, **kwargs):
        super(SatinColumn, self).__init__(*args, **kwargs)

    @property
    @param('satin_column', _('Custom satin column'), type='toggle')
    def satin_column(self):
        return self.get_boolean_param("satin_column")

    @property
    def color(self):
        return self.get_style("stroke")

    @property
    @param('zigzag_spacing_mm',
           _('Zig-zag spacing (peak-to-peak)'),
           unit='mm',
           type='float',
           default=0.4)
    def zigzag_spacing(self):
        # peak-to-peak distance between zigzags
        return max(self.get_float_param("zigzag_spacing_mm", 0.4), 0.01)

    @property
    @param('pull_compensation_mm',
           _('Pull compensation'),
           unit='mm',
           type='float')
    def pull_compensation(self):
        # In satin stitch, the stitches have a tendency to pull together and
        # narrow the entire column.  We can compensate for this by stitching
        # wider than we desire the column to end up.
        return self.get_float_param("pull_compensation_mm", 0)

    @property
    @param('contour_underlay',
           _('Contour underlay'),
           type='toggle',
           group=_('Contour Underlay'))
    def contour_underlay(self):
        # "Contour underlay" is stitching just inside the rectangular shape
        # of the satin column; that is, up one side and down the other.
        return self.get_boolean_param("contour_underlay")

    @property
    @param('contour_underlay_stitch_length_mm',
           _('Stitch length'),
           unit='mm',
           group=_('Contour Underlay'),
           type='float',
           default=1.5)
    def contour_underlay_stitch_length(self):
        return max(
            self.get_float_param("contour_underlay_stitch_length_mm", 1.5),
            0.01)

    @property
    @param('contour_underlay_inset_mm',
           _('Contour underlay inset amount'),
           unit='mm',
           group=_('Contour Underlay'),
           type='float',
           default=0.4)
    def contour_underlay_inset(self):
        # how far inside the edge of the column to stitch the underlay
        return self.get_float_param("contour_underlay_inset_mm", 0.4)

    @property
    @param('center_walk_underlay',
           _('Center-walk underlay'),
           type='toggle',
           group=_('Center-Walk Underlay'))
    def center_walk_underlay(self):
        # "Center walk underlay" is stitching down and back in the centerline
        # between the two sides of the satin column.
        return self.get_boolean_param("center_walk_underlay")

    @property
    @param('center_walk_underlay_stitch_length_mm',
           _('Stitch length'),
           unit='mm',
           group=_('Center-Walk Underlay'),
           type='float',
           default=1.5)
    def center_walk_underlay_stitch_length(self):
        return max(
            self.get_float_param("center_walk_underlay_stitch_length_mm", 1.5),
            0.01)

    @property
    @param('zigzag_underlay',
           _('Zig-zag underlay'),
           type='toggle',
           group=_('Zig-zag Underlay'))
    def zigzag_underlay(self):
        return self.get_boolean_param("zigzag_underlay")

    @property
    @param('zigzag_underlay_spacing_mm',
           _('Zig-Zag spacing (peak-to-peak)'),
           unit='mm',
           group=_('Zig-zag Underlay'),
           type='float',
           default=3)
    def zigzag_underlay_spacing(self):
        return max(self.get_float_param("zigzag_underlay_spacing_mm", 3), 0.01)

    @property
    @param('zigzag_underlay_inset_mm',
           _('Inset amount (default: half of contour underlay inset)'),
           unit='mm',
           group=_('Zig-zag Underlay'),
           type='float')
    def zigzag_underlay_inset(self):
        # how far in from the edge of the satin the points in the zigzags
        # should be

        # Default to half of the contour underlay inset.  That is, if we're
        # doing both contour underlay and zigzag underlay, make sure the
        # points of the zigzag fall outside the contour underlay but inside
        # the edges of the satin column.
        return self.get_float_param(
            "zigzag_underlay_inset_mm") or self.contour_underlay_inset / 2.0

    @property
    @cache
    def csp(self):
        return self.parse_path()

    @property
    @cache
    def flattened_beziers(self):
        if len(self.csp) == 2:
            return self.simple_flatten_beziers()
        else:
            return self.flatten_beziers_with_rungs()

    def flatten_beziers_with_rungs(self):
        input_paths = [self.flatten([path]) for path in self.csp]
        input_paths = [shgeo.LineString(path[0]) for path in input_paths]

        paths = input_paths[:]
        paths.sort(key=lambda path: path.length, reverse=True)

        # Imagine a satin column as a curvy ladder.
        # The two long paths are the "rails" of the ladder.  The remainder are
        # the "rungs".
        rails = paths[:2]
        rungs = shgeo.MultiLineString(paths[2:])

        # The rails should stay in the order they were in the original CSP.
        # (this lets the user control where the satin starts and ends)
        rails.sort(key=lambda rail: input_paths.index(rail))

        result = []

        for rail in rails:
            if not rail.is_simple:
                self.fatal(
                    _("One or more rails crosses itself, and this is not allowed.  Please split into multiple satin columns."
                      ))

            # handle null intersections here?
            linestrings = shapely.ops.split(rail, rungs)

            print >> dbg, "rails and rungs", [str(rail) for rail in rails
                                              ], [str(rung) for rung in rungs]
            if len(linestrings.geoms) < len(rungs.geoms) + 1:
                self.fatal(
                    _("satin column: One or more of the rungs doesn't intersect both rails."
                      ) + "  " +
                    _("Each rail should intersect both rungs once."))
            elif len(linestrings.geoms) > len(rungs.geoms) + 1:
                self.fatal(
                    _("satin column: One or more of the rungs intersects the rails more than once."
                      ) + "  " +
                    _("Each rail should intersect both rungs once."))

            paths = [[inkstitch.Point(*coord) for coord in ls.coords]
                     for ls in linestrings.geoms]
            result.append(paths)

        return zip(*result)

    def simple_flatten_beziers(self):
        # Given a pair of paths made up of bezier segments, flatten
        # each individual bezier segment into line segments that approximate
        # the curves.  Retain the divisions between beziers -- we'll use those
        # later.

        paths = []

        for path in self.csp:
            # See the documentation in the parent class for parse_path() for a
            # description of the format of the CSP.  Each bezier is constructed
            # using two neighboring 3-tuples in the list.

            flattened_path = []

            # iterate over pairs of 3-tuples
            for prev, current in zip(path[:-1], path[1:]):
                flattened_segment = self.flatten([[prev, current]])
                flattened_segment = [
                    inkstitch.Point(x, y) for x, y in flattened_segment[0]
                ]
                flattened_path.append(flattened_segment)

            paths.append(flattened_path)

        return zip(*paths)

    def validate_satin_column(self):
        # The node should have exactly two paths with no fill.  Each
        # path should have the same number of points, meaning that they
        # will both be made up of the same number of bezier curves.

        node_id = self.node.get("id")

        if self.get_style("fill") is not None:
            self.fatal(
                _("satin column: object %s has a fill (but should not)") %
                node_id)

        if len(self.csp) == 2:
            if len(self.csp[0]) != len(self.csp[1]):
                self.fatal(_("satin column: object %(id)s has two paths with an unequal number of points (%(length1)d and %(length2)d)") % \
                             dict(id=node_id, length1=len(self.csp[0]), length2=len(self.csp[1])))

    def offset_points(self, pos1, pos2, offset_px):
        # Expand or contract two points about their midpoint.  This is
        # useful for pull compensation and insetting underlay.

        distance = (pos1 - pos2).length()

        if distance < 0.0001:
            # if they're the same point, we don't know which direction
            # to offset in, so we have to just return the points
            return pos1, pos2

        # don't contract beyond the midpoint, or we'll start expanding
        if offset_px < -distance / 2.0:
            offset_px = -distance / 2.0

        pos1 = pos1 + (pos1 - pos2).unit() * offset_px
        pos2 = pos2 + (pos2 - pos1).unit() * offset_px

        return pos1, pos2

    def walk(self, path, start_pos, start_index, distance):
        # Move <distance> pixels along <path>, which is a sequence of line
        # segments defined by points.

        # <start_index> is the index of the line segment in <path> that
        # we're currently on.  <start_pos> is where along that line
        # segment we are.  Return a new position and index.

        # print >> dbg, "walk", start_pos, start_index, distance

        pos = start_pos
        index = start_index
        last_index = len(path) - 1
        distance_remaining = distance

        while True:
            if index >= last_index:
                return pos, index

            segment_end = path[index + 1]
            segment = segment_end - pos
            segment_length = segment.length()

            if segment_length > distance_remaining:
                # our walk ends partway along this segment
                return pos + segment.unit() * distance_remaining, index
            else:
                # our walk goes past the end of this segment, so advance
                # one point
                index += 1
                distance_remaining -= segment_length
                pos = segment_end

    def walk_paths(self, spacing, offset):
        # Take a bezier segment from each path in turn, and plot out an
        # equal number of points on each bezier.  Return the points plotted.
        # The points will be contracted or expanded by offset using
        # offset_points().

        points = [[], []]

        def add_pair(pos1, pos2):
            pos1, pos2 = self.offset_points(pos1, pos2, offset)
            points[0].append(pos1)
            points[1].append(pos2)

        # We may not be able to fit an even number of zigzags in each pair of
        # beziers.  We'll store the remaining bit of the beziers after handling
        # each section.
        remainder_path1 = []
        remainder_path2 = []

        for segment1, segment2 in self.flattened_beziers:
            subpath1 = remainder_path1 + segment1
            subpath2 = remainder_path2 + segment2

            len1 = shgeo.LineString(subpath1).length
            len2 = shgeo.LineString(subpath2).length

            # Base the number of stitches in each section on the _longest_ of
            # the two beziers. Otherwise, things could get too sparse when one
            # side is significantly longer (e.g. when going around a corner).
            # The risk here is that we poke a hole in the fabric if we try to
            # cram too many stitches on the short bezier.  The user will need
            # to avoid this through careful construction of paths.
            #
            # TODO: some commercial machine embroidery software compensates by
            # pulling in some of the "inner" stitches toward the center a bit.

            # note, this rounds down using integer-division
            num_points = max(len1, len2) / spacing

            spacing1 = len1 / num_points
            spacing2 = len2 / num_points

            pos1 = subpath1[0]
            index1 = 0

            pos2 = subpath2[0]
            index2 = 0

            for i in xrange(int(num_points)):
                add_pair(pos1, pos2)

                pos1, index1 = self.walk(subpath1, pos1, index1, spacing1)
                pos2, index2 = self.walk(subpath2, pos2, index2, spacing2)

            if index1 < len(subpath1) - 1:
                remainder_path1 = [pos1] + subpath1[index1 + 1:]
            else:
                remainder_path1 = []

            if index2 < len(subpath2) - 1:
                remainder_path2 = [pos2] + subpath2[index2 + 1:]
            else:
                remainder_path2 = []

        # We're off by one in the algorithm above, so we need one more
        # pair of points.  We also want to add points at the very end to
        # make sure we match the vectors on screen as best as possible.
        # Try to avoid doing both if they're going to stack up too
        # closely.

        end1 = remainder_path1[-1]
        end2 = remainder_path2[-1]

        if (end1 - pos1).length() > 0.3 * spacing:
            add_pair(pos1, pos2)

        add_pair(end1, end2)

        return points

    def do_contour_underlay(self):
        # "contour walk" underlay: do stitches up one side and down the
        # other.
        forward, back = self.walk_paths(self.contour_underlay_stitch_length,
                                        -self.contour_underlay_inset)
        return Patch(color=self.color,
                     stitches=(forward + list(reversed(back))))

    def do_center_walk(self):
        # Center walk underlay is just a running stitch down and back on the
        # center line between the bezier curves.

        # Do it like contour underlay, but inset all the way to the center.
        forward, back = self.walk_paths(
            self.center_walk_underlay_stitch_length, -100000)
        return Patch(color=self.color,
                     stitches=(forward + list(reversed(back))))

    def do_zigzag_underlay(self):
        # zigzag underlay, usually done at a much lower density than the
        # satin itself.  It looks like this:
        #
        # \/\/\/\/\/\/\/\/\/\/|
        # /\/\/\/\/\/\/\/\/\/\|
        #
        # In combination with the "contour walk" underlay, this is the
        # "German underlay" described here:
        #   http://www.mrxstitch.com/underlay-what-lies-beneath-machine-embroidery/

        patch = Patch(color=self.color)

        sides = self.walk_paths(self.zigzag_underlay_spacing / 2.0,
                                -self.zigzag_underlay_inset)

        # This organizes the points in each side in the order that they'll be
        # visited.
        sides = [
            sides[0][::2] + list(reversed(sides[0][1::2])),
            sides[1][1::2] + list(reversed(sides[1][::2]))
        ]

        # This fancy bit of iterable magic just repeatedly takes a point
        # from each side in turn.
        for point in chain.from_iterable(izip(*sides)):
            patch.add_stitch(point)

        return patch

    def do_satin(self):
        # satin: do a zigzag pattern, alternating between the paths.  The
        # zigzag looks like this to make the satin stitches look perpendicular
        # to the column:
        #
        # /|/|/|/|/|/|/|/|

        # print >> dbg, "satin", self.zigzag_spacing, self.pull_compensation

        patch = Patch(color=self.color)

        sides = self.walk_paths(self.zigzag_spacing, self.pull_compensation)

        # Like in zigzag_underlay(): take a point from each side in turn.
        for point in chain.from_iterable(izip(*sides)):
            patch.add_stitch(point)

        return patch

    def to_patches(self, last_patch):
        # Stitch a variable-width satin column, zig-zagging between two paths.

        # The algorithm will draw zigzags between each consecutive pair of
        # beziers.  The boundary points between beziers serve as "checkpoints",
        # allowing the user to control how the zigzags flow around corners.

        # First, verify that we have valid paths.
        self.validate_satin_column()

        patches = []

        if self.center_walk_underlay:
            patches.append(self.do_center_walk())

        if self.contour_underlay:
            patches.append(self.do_contour_underlay())

        if self.zigzag_underlay:
            # zigzag underlay comes after contour walk underlay, so that the
            # zigzags sit on the contour walk underlay like rail ties on rails.
            patches.append(self.do_zigzag_underlay())

        patches.append(self.do_satin())

        return patches
Exemple #14
0
class Fill(EmbroideryElement):
    element_name = _("Fill")

    def __init__(self, *args, **kwargs):
        super(Fill, self).__init__(*args, **kwargs)

    @property
    @param('auto_fill',
           _('Manually routed fill stitching'),
           type='toggle',
           inverse=True,
           default=True)
    def auto_fill(self):
        return self.get_boolean_param('auto_fill', True)

    @property
    @param('angle',
           _('Angle of lines of stitches'),
           unit='deg',
           type='float',
           default=0)
    @cache
    def angle(self):
        return math.radians(self.get_float_param('angle', 0))

    @property
    def color(self):
        return self.get_style("fill")

    @property
    @param('flip',
           _('Flip fill (start right-to-left)'),
           type='boolean',
           default=False)
    def flip(self):
        return self.get_boolean_param("flip", False)

    @property
    @param('row_spacing_mm',
           _('Spacing between rows'),
           unit='mm',
           type='float',
           default=0.25)
    def row_spacing(self):
        return max(self.get_float_param("row_spacing_mm", 0.25), 0.01)

    @property
    def end_row_spacing(self):
        return self.get_float_param("end_row_spacing_mm")

    @property
    @param('max_stitch_length_mm',
           _('Maximum fill stitch length'),
           unit='mm',
           type='float',
           default=3.0)
    def max_stitch_length(self):
        return max(self.get_float_param("max_stitch_length_mm", 3.0), 0.01)

    @property
    @param('staggers',
           _('Stagger rows this many times before repeating'),
           type='int',
           default=4)
    def staggers(self):
        return self.get_int_param("staggers", 4)

    @property
    @cache
    def paths(self):
        return self.flatten(self.parse_path())

    @property
    @cache
    def shape(self):
        poly_ary = []
        for sub_path in self.paths:
            point_ary = []
            last_pt = None
            for pt in sub_path:
                if (last_pt is not None):
                    vp = (pt[0] - last_pt[0], pt[1] - last_pt[1])
                    dp = math.sqrt(math.pow(vp[0], 2.0) + math.pow(vp[1], 2.0))
                    # dbg.write("dp %s\n" % dp)
                    if (dp > 0.01):
                        # I think too-close points confuse shapely.
                        point_ary.append(pt)
                        last_pt = pt
                else:
                    last_pt = pt
            if point_ary:
                poly_ary.append(point_ary)

        # shapely's idea of "holes" are to subtract everything in the second set
        # from the first. So let's at least make sure the "first" thing is the
        # biggest path.
        # TODO: actually figure out which things are holes and which are shells
        poly_ary.sort(key=lambda point_list: shgeo.Polygon(point_list).area,
                      reverse=True)

        polygon = shgeo.MultiPolygon([(poly_ary[0], poly_ary[1:])])
        # print >> sys.stderr, "polygon valid:", polygon.is_valid
        return polygon

    def to_patches(self, last_patch):
        stitch_lists = legacy_fill(self.shape, self.angle, self.row_spacing,
                                   self.end_row_spacing,
                                   self.max_stitch_length, self.flip,
                                   self.staggers)
        return [
            Patch(stitches=stitch_list, color=self.color)
            for stitch_list in stitch_lists
        ]

        rows_of_segments = fill.intersect_region_with_grating(
            self.shape, self.angle, self.row_spacing, self.end_row_spacing,
            self.flip)
        groups_of_segments = fill.pull_runs(rows_of_segments)

        return [fill.section_to_patch(group) for group in groups_of_segments]
Exemple #15
0
class Stroke(EmbroideryElement):
    element_name = "Stroke"

    @property
    @param('satin_column',
           _('Satin stitch along paths'),
           type='toggle',
           inverse=True)
    def satin_column(self):
        return self.get_boolean_param("satin_column")

    @property
    def color(self):
        return self.get_style("stroke")

    @property
    @cache
    def width(self):
        stroke_width = self.get_style("stroke-width")

        if stroke_width.endswith("px"):
            stroke_width = stroke_width[:-2]

        return float(stroke_width)

    @property
    def dashed(self):
        return self.get_style("stroke-dasharray") is not None

    @property
    @param('running_stitch_length_mm',
           _('Running stitch length'),
           unit='mm',
           type='float',
           default=1.5)
    def running_stitch_length(self):
        return max(self.get_float_param("running_stitch_length_mm", 1.5), 0.01)

    @property
    @param('zigzag_spacing_mm',
           _('Zig-zag spacing (peak-to-peak)'),
           unit='mm',
           type='float',
           default=0.4)
    @cache
    def zigzag_spacing(self):
        return max(self.get_float_param("zigzag_spacing_mm", 0.4), 0.01)

    @property
    @param('repeats', _('Repeats'), type='int', default="1")
    def repeats(self):
        return self.get_int_param("repeats", 1)

    @property
    def paths(self):
        return self.flatten(self.parse_path())

    def is_running_stitch(self):
        # stroke width <= 0.5 pixels is deprecated in favor of dashed lines
        return self.dashed or self.width <= 0.5

    def stroke_points(self, emb_point_list, zigzag_spacing, stroke_width):
        patch = Patch(color=self.color)
        p0 = emb_point_list[0]
        rho = 0.0
        side = 1
        last_segment_direction = None

        for repeat in xrange(self.repeats):
            if repeat % 2 == 0:
                order = range(1, len(emb_point_list))
            else:
                order = range(-2, -len(emb_point_list) - 1, -1)

            for segi in order:
                p1 = emb_point_list[segi]

                # how far we have to go along segment
                seg_len = (p1 - p0).length()
                if (seg_len == 0):
                    continue

                # vector pointing along segment
                along = (p1 - p0).unit()

                # vector pointing to edge of stroke width
                perp = along.rotate_left() * (stroke_width * 0.5)

                if stroke_width == 0.0 and last_segment_direction is not None:
                    if abs(1.0 - along * last_segment_direction) > 0.5:
                        # if greater than 45 degree angle, stitch the corner
                        rho = zigzag_spacing
                        patch.add_stitch(p0)

                # iteration variable: how far we are along segment
                while (rho <= seg_len):
                    left_pt = p0 + along * rho + perp * side
                    patch.add_stitch(left_pt)
                    rho += zigzag_spacing
                    side = -side

                p0 = p1
                last_segment_direction = along
                rho -= seg_len

            if (p0 - patch.stitches[-1]).length() > 0.1:
                patch.add_stitch(p0)

        return patch

    def to_patches(self, last_patch):
        patches = []

        for path in self.paths:
            path = [inkstitch.Point(x, y) for x, y in path]
            if self.is_running_stitch():
                patch = self.stroke_points(path,
                                           self.running_stitch_length,
                                           stroke_width=0.0)
            else:
                patch = self.stroke_points(path,
                                           self.zigzag_spacing / 2.0,
                                           stroke_width=self.width)

            patches.append(patch)

        return patches
Exemple #16
0
class AutoFill(Fill):
    element_name = _("Auto-Fill")

    @property
    @param('auto_fill',
           _('Automatically routed fill stitching'),
           type='toggle',
           default=True)
    def auto_fill(self):
        return self.get_boolean_param('auto_fill', True)

    @property
    @cache
    def outline(self):
        return self.shape.boundary[0]

    @property
    @cache
    def outline_length(self):
        return self.outline.length

    @property
    def flip(self):
        return False

    @property
    @param('running_stitch_length_mm',
           _('Running stitch length (traversal between sections)'),
           unit='mm',
           type='float',
           default=1.5)
    def running_stitch_length(self):
        return max(self.get_float_param("running_stitch_length_mm", 1.5), 0.01)

    @property
    @param('fill_underlay',
           _('Underlay'),
           type='toggle',
           group=_('AutoFill Underlay'),
           default=False)
    def fill_underlay(self):
        return self.get_boolean_param("fill_underlay", default=False)

    @property
    @param('fill_underlay_angle',
           _('Fill angle (default: fill angle + 90 deg)'),
           unit='deg',
           group=_('AutoFill Underlay'),
           type='float')
    @cache
    def fill_underlay_angle(self):
        underlay_angle = self.get_float_param("fill_underlay_angle")

        if underlay_angle:
            return math.radians(underlay_angle)
        else:
            return self.angle + math.pi / 2.0

    @property
    @param('fill_underlay_row_spacing_mm',
           _('Row spacing (default: 3x fill row spacing)'),
           unit='mm',
           group=_('AutoFill Underlay'),
           type='float')
    @cache
    def fill_underlay_row_spacing(self):
        return self.get_float_param(
            "fill_underlay_row_spacing_mm") or self.row_spacing * 3

    @property
    @param('fill_underlay_max_stitch_length_mm',
           _('Max stitch length'),
           unit='mm',
           group=_('AutoFill Underlay'),
           type='float')
    @cache
    def fill_underlay_max_stitch_length(self):
        return self.get_float_param(
            "fill_underlay_max_stitch_length_mm") or self.max_stitch_length

    @property
    @param('fill_underlay_inset_mm',
           _('Inset'),
           unit='mm',
           group=_('AutoFill Underlay'),
           type='float',
           default=0)
    def fill_underlay_inset(self):
        return self.get_float_param('fill_underlay_inset_mm', 0)

    @property
    def underlay_shape(self):
        if self.fill_underlay_inset:
            shape = self.shape.buffer(-self.fill_underlay_inset)
            if not isinstance(shape, shgeo.MultiPolygon):
                shape = shgeo.MultiPolygon([shape])
            return shape
        else:
            return self.shape

    def to_patches(self, last_patch):
        stitches = []

        if last_patch is None:
            starting_point = None
        else:
            starting_point = last_patch.stitches[-1]

        if self.fill_underlay:
            stitches.extend(
                auto_fill(self.underlay_shape, self.fill_underlay_angle,
                          self.fill_underlay_row_spacing,
                          self.fill_underlay_row_spacing,
                          self.fill_underlay_max_stitch_length,
                          self.running_stitch_length, self.staggers,
                          starting_point))
            starting_point = stitches[-1]

        stitches.extend(
            auto_fill(self.shape, self.angle, self.row_spacing,
                      self.end_row_spacing, self.max_stitch_length,
                      self.running_stitch_length, self.staggers,
                      starting_point))

        return [Patch(stitches=stitches, color=self.color)]
    def effect(self):
        # It doesn't really make sense to print just a couple of selected
        # objects.  It's almost certain they meant to print the whole design.
        # If they really wanted to print just a few objects, they could set
        # the rest invisible temporarily.
        self.selected = {}

        if not self.get_elements():
            return

        self.hide_all_layers()

        patches = self.elements_to_patches(self.elements)
        stitch_plan = patches_to_stitch_plan(patches)
        render_stitch_plan(self.document.getroot(), stitch_plan)

        self.strip_namespaces()

        # Now the stitch plan layer will contain a set of groups, each
        # corresponding to a color block.  We'll create a set of SVG files
        # corresponding to each individual color block and a final one
        # for all color blocks together.

        svg = self.document.getroot()
        layers = svg.findall("./g[@{http://www.inkscape.org/namespaces/inkscape}groupmode='layer']")
        stitch_plan_layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']")

        # First, delete all of the other layers.  We don't need them and they'll
        # just bulk up the SVG.
        for layer in layers:
            if layer is not stitch_plan_layer:
                svg.remove(layer)

        overview_svg = inkex.etree.tostring(self.document)

        color_block_groups = stitch_plan_layer.getchildren()

        for i, group in enumerate(color_block_groups):
            # clear the stitch plan layer
            del stitch_plan_layer[:]

            # add in just this group
            stitch_plan_layer.append(group)

            # save an SVG preview
            stitch_plan.color_blocks[i].svg_preview = inkex.etree.tostring(self.document)

        env = self.build_environment()
        template = env.get_template('index.html')

        html = template.render(
            view = {'client_overview': False, 'client_detailedview': False, 'operator_overview': True, 'operator_detailedview': True},
            logo = {'src' : '', 'title' : 'LOGO'},
            date = date.today(),
            client = "",
            job = {
                    'title': '',
                    'num_colors': stitch_plan.num_colors,
                    'num_color_blocks': len(stitch_plan),
                    'num_stops': stitch_plan.num_stops,
                    'num_trims': stitch_plan.num_trims,
                    'dimensions': stitch_plan.dimensions_mm,
                    'num_stitches': stitch_plan.num_stitches,
                    'estimated_time': '', # TODO
                    'estimated_thread': '', # TODO
                  },
            svg_overview = overview_svg,
            svg_scale = '100%',
            color_blocks = stitch_plan.color_blocks,
        )

        print_server = PrintPreviewServer(html=html)
        print_server.start()

        time.sleep(1)
        open_url("http://%s:%s/" % (print_server.host, print_server.port))

        app = wx.App()
        info_frame = PrintInfoFrame(None, title=_("Ink/Stitch Print"), size=(450, 350), print_server=print_server)
        info_frame.Show()
        app.MainLoop()

        # don't let inkex print the document out
        sys.exit(0)
Exemple #18
0
 def shutdown():
     self.shutting_down = True
     request.environ.get('werkzeug.server.shutdown')()
     return _('Closing...') + '<br/><br/>' + _(
         'It is safe to close this window now.')