Hatch Fill 2.4.0

- Revise GUI
- Add option to set hatch spacing and inset distances with selectable units, px, mm, or inch.
- Ignore non-fillable SVG elements (except text)
- Fill only visible layers
pull/175/head
Windell Oskay 2021-08-08 15:25:10 -07:00
rodzic 6e528683e4
commit 948d7f2157
2 zmienionych plików z 130 dodań i 86 usunięć

Wyświetl plik

@ -8,58 +8,71 @@
<param name="tab" type="notebook">
<page name="splash" _gui-text="Hatch Fill">
<_param name="Header" type="description" xml:space="preserve">
This extension fills each closed figure in your drawing
with a path consisting of back and forth drawn "hatch" lines.
If any objects are selected, then only those selected objects
will be filled.
Hatch Fill: An extension to fill all or selected closed figures in your
drawing with paths consisting of back-and-forth hatch line.
</_param>
Hatched figures will be grouped with their fills.
</_param>
<param name="hatchSpacing" type="float" min="0.1" max="1000" _gui-text=" Hatch spacing (px)">3.0</param>
<param name="hatchAngle" type="float" min="-360" max="360" _gui-text=" Hatch angle (degrees)">45</param>
<param name="crossHatch" type="boolean" _gui-text=" Crosshatch?">false</param>
<param name="hatchSpacing" type="float" min="0.001" max="1000" indent="2"
precision="3" _gui-text="Hatch Spacing">3.0</param>
<param name="reducePenLifts" type="boolean" _gui-text=" Connect nearby ends?">true</param>
<param name="hatchScope" type="float" min="1.1" max="5.0" _gui-text=" Range of end connections (default: 3)">3.0</param>
<param name="holdBackHatchFromEdges" type="boolean" _gui-text=" Inset fill from edges?">true</param>
<param name="holdBackSteps" type="float" min="0.1" max="10.0" _gui-text=" Inset distance (px) (default: 1)">1.0</param>
<param name="tolerance" type="float" min="0.1" max="100" _gui-text=" Tolerance (default: 3.0)">3.0</param>
<param name="units" type="optiongroup" appearance="minimal"
indent="4" _gui-text="Spacing Units :">
<_option value="2">px</_option>
<_option value="3">mm</_option>
<_option value="4">inch</_option>
</param>
<param name="footer" type="description" xml:space="preserve">
(v2.3.0, June 11, 2019)</param>
<param name="hatchAngle" type="float" min="-360" max="360" indent="4" _gui-text="Hatch Angle (degrees)">45</param>
<param name="crossHatch" type="boolean" indent="4" _gui-text="Crosshatch">false</param>
</page>
<page name="info" _gui-text="More info...">
<_param name="aboutpage" type="description" xml:space="preserve">
Hatch spacing is the distance between hatch lines,
measured in units of screen pixels (px). Angles are in
degrees from horizontal; for example 90 is vertical.
<separator />
The Crosshatch option will apply a second set of
hatches, perpendicular to the first.
<param name="connect_bool" type="boolean" indent="2" _gui-text="Connect Nearby Ends">true</param>
<param name="hatchScope" type="float" min="1.1" max="5.0" indent="2" _gui-text="Range of end connections (default: 3)">3.0</param>
The "Connect nearby ends" option will attempt to connect
nearby line ends with a smoothly flowing curve, to improve
the smoothness of plotting.
<separator />
<param name="inset_bool" type="boolean" indent="2" _gui-text="Inset Fill from Edges">true</param>
<param name="inset_dist" type="float" min="0.001" max="1000" precision="3" indent="2"
_gui-text="Inset Distance, in selected units">0.5</param>
The Range parameter sets the distance (in hatch widths)
over which that option searches for segments to join.
Large values may result in hatches where you don't want
them. Consider using a value in the range of 2-4.
<separator />
<param name="tolerance" type="float" min="0.1" max="100" indent="2"
_gui-text="Tolerance (default: 2.0)">2.0</param>
The Inset option allows you to hold back the edges of the
fill somewhat from the edge of your original object.
This can improve performance, as it allows you to more
reliably "color inside the lines" when using pens.
</page>
<page name="info" _gui-text="More info...">
<param name="aboutpage" type="description" xml:space="preserve">
Hatch spacing is the distance between hatch lines, measured in selected
units. Hatch angle is in degrees from horizontal; e.g., 90 for vertical.
The hatches will be the same color and width
as the original object.
The Crosshatch option will add a second set of hatches, perpendicular
to the first.
The Tolerance parameter affects how precisely
the hatches try to fill the input paths.</_param>
The Connect Nearby Ends option will join nearby line ends with a
smooth curve, giving a differnt appearance and reducing the number of
pen lifts when using robotic tools like pen plotters. The Range
parameter sets the distance (in hatch spacing) over which end
connections may be made. Large values may result in hatches where you
don't want them.
The Inset Fill from Edges option allows you to hold back the hatches
away from the edge of your original object. This can allow you to more
reliably "color inside the lines".
The Tolerance parameter affects how precisely the hatches try
to fill the input paths.
Hatches will have the same stroke color and width as the original
object. When filling more than one object, the hatches will be grouped
with their original (filled) objects.
v2.4.0, August 8, 2021</_param>
</page>
</param>
<effect needs-live-preview="true">
<object-type>all</object-type>
<effects-menu>

125
inkscape_driver/eggbot_hatch.py 100755 → 100644
Wyświetl plik

@ -84,8 +84,12 @@
# This prevents extremely complex plots from generating glitches
# Modifications are limited to recursivelyTraverseSvg and effect methods
# Updated by Windell H. Oskay, 2021
# Add option for selecting units.
# Make inset settable in selected units as well.
# Current software version:
# (v2.3.2, September 29, 2019)
# (v2.4.0, August 8, 2021)
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -104,7 +108,7 @@
import math
from lxml import etree
from plot_utils_import import from_dependency_import # plotink
from axidrawinternal.plot_utils_import import from_dependency_import # plotink
inkex = from_dependency_import('ink_extensions.inkex')
simplepath = from_dependency_import('ink_extensions.simplepath')
simpletransform = from_dependency_import('ink_extensions.simpletransform')
@ -471,7 +475,7 @@ def interstices(self, p1, p2, paths, hatches, b_hold_back_hatches, f_hold_back_s
# remove it from consideration by marking it as already drawn - a
# fiction, but is much quicker than actually removing the hatch from the list.
f_min_allowed_hatch_length = self.options.hatchSpacing * MIN_HATCH_FRACTION
f_min_allowed_hatch_length = self.hatch_spacing_px * MIN_HATCH_FRACTION
f_initial_hatch_length = math.hypot(x2 - x1, y2 - y1)
# We did as much as possible of the inset operation back when we were finding intersections.
# We did it back then because at that point we knew more about the geometry than we know now.
@ -610,19 +614,19 @@ class Eggbot_Hatch(inkex.Effect):
self.docTransform = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]
self.OptionParser.add_option(
"--holdBackSteps", action="store", type="float",
dest="holdBackSteps", default=3.0,
help="How far hatch strokes stay from boundary (steps)")
"--inset_dist", action="store", type="float",
dest="inset_dist", default=3.0,
help="How far hatch strokes stay from boundary")
self.OptionParser.add_option(
"--hatchScope", action="store", type="float",
dest="hatchScope", default=3.0,
help="Radius searched for segments to join (units of hatch width)")
self.OptionParser.add_option(
"--holdBackHatchFromEdges", action="store", dest="holdBackHatchFromEdges",
"--inset_bool", action="store", dest="inset_bool",
type="inkbool", default=True,
help="Stay away from edges, so no need for inset")
self.OptionParser.add_option(
"--reducePenLifts", action="store", dest="reducePenLifts",
"--connect_bool", action="store", dest="connect_bool",
type="inkbool", default=True,
help="Reduce plotting time by joining some hatches")
self.OptionParser.add_option(
@ -639,11 +643,18 @@ class Eggbot_Hatch(inkex.Effect):
help="Spacing between hatch lines")
self.OptionParser.add_option(
"--tolerance", action="store", type="float",
dest="tolerance", default=20.0,
dest="tolerance", default=3.0,
help="Allowed deviation from original paths")
self.OptionParser.add_option(
"--units",
action="store", type="int",
dest="units", default=1,
help="Units to use for hatches. 1: line width. 2: px. 3: mm. 4: inch")
self.OptionParser.add_option("--tab", # NOTE: value is not used.
action="store", type="string", dest="tab", default="splash",
action="store", type="string", dest="_tab", default="splash",
help="The active tab when Apply was pressed")
def getDocProps(self):
@ -753,11 +764,11 @@ class Eggbot_Hatch(inkex.Effect):
for vertex in subpath:
if vertex[0] < self.xmin:
self.xmin = vertex[0]
elif vertex[0] > self.xmax:
if vertex[0] > self.xmax:
self.xmax = vertex[0]
if vertex[1] < self.ymin:
self.ymin = vertex[1]
elif vertex[1] > self.ymax:
if vertex[1] > self.ymax:
self.ymax = vertex[1]
def recursivelyTraverseSvg(self, a_node_list, mat_current=None, parent_visibility='visible'):
@ -803,7 +814,17 @@ class Eggbot_Hatch(inkex.Effect):
if v == 'inherit':
v = parent_visibility
if v == 'hidden' or v == 'collapse':
pass
continue
style = simplestyle.parseStyle(node.get('style'))
# Check for "display:none" in the node's style attribute:
if 'display' in style.keys() and style['display'] == 'none':
continue # Do not hatch this object or its children
# The node may have a display="none" attribute as well:
if node.get('display') == 'none':
continue # Do not hatch this object or its children
# first apply the current matrix transform to this node's transform
mat_new = simpletransform.composeTransform(mat_current, simpletransform.parseTransform(node.get("transform")))
@ -849,13 +870,13 @@ class Eggbot_Hatch(inkex.Effect):
self.addPathVertices(path_data, node, mat_new)
# We now have a path we want to apply a (cross)hatch to
# Apply appropriate functions
b_have_grid = self.makeHatchGrid(float(self.options.hatchAngle), float(self.options.hatchSpacing), True)
b_have_grid = self.makeHatchGrid(float(self.options.hatchAngle), float(self.hatch_spacing_px), True)
if b_have_grid:
if self.options.crossHatch:
self.makeHatchGrid(float(self.options.hatchAngle + 90.0), float(self.options.hatchSpacing), False)
self.makeHatchGrid(float(self.options.hatchAngle + 90.0), float(self.hatch_spacing_px), False)
# Now loop over our hatch lines looking for intersections
for h in self.grid:
interstices(self, (h[0], h[1]), (h[2], h[3]), self.paths, self.hatches, self.options.holdBackHatchFromEdges, self.options.holdBackSteps)
interstices(self, (h[0], h[1]), (h[2], h[3]), self.paths, self.hatches, self.options.inset_bool, self.inset_dist_px)
elif node.tag in [inkex.addNS('rect', 'svg'), 'rect']:
@ -885,13 +906,13 @@ class Eggbot_Hatch(inkex.Effect):
self.addPathVertices(simplepath.formatPath(a), node, mat_new)
# We now have a path we want to apply a (cross)hatch to
# Apply appropriate functions
b_have_grid = self.makeHatchGrid(float(self.options.hatchAngle), float(self.options.hatchSpacing), True)
b_have_grid = self.makeHatchGrid(float(self.options.hatchAngle), float(self.hatch_spacing_px), True)
if b_have_grid:
if self.options.crossHatch:
self.makeHatchGrid(float(self.options.hatchAngle + 90.0), float(self.options.hatchSpacing), False)
self.makeHatchGrid(float(self.options.hatchAngle + 90.0), float(self.hatch_spacing_px), False)
# Now loop over our hatch lines looking for intersections
for h in self.grid:
interstices(self, (h[0], h[1]), (h[2], h[3]), self.paths, self.hatches, self.options.holdBackHatchFromEdges, self.options.holdBackSteps)
interstices(self, (h[0], h[1]), (h[2], h[3]), self.paths, self.hatches, self.options.inset_bool, self.inset_dist_px)
elif node.tag in [inkex.addNS('line', 'svg'), 'line']:
@ -914,13 +935,13 @@ class Eggbot_Hatch(inkex.Effect):
self.addPathVertices(simplepath.formatPath(a), node, mat_new)
# We now have a path we want to apply a (cross)hatch to
# Apply appropriate functions
b_have_grid = self.makeHatchGrid(float(self.options.hatchAngle), float(self.options.hatchSpacing), True)
b_have_grid = self.makeHatchGrid(float(self.options.hatchAngle), float(self.hatch_spacing_px), True)
if b_have_grid:
if self.options.crossHatch:
self.makeHatchGrid(float(self.options.hatchAngle + 90.0), float(self.options.hatchSpacing), False)
self.makeHatchGrid(float(self.options.hatchAngle + 90.0), float(self.hatch_spacing_px), False)
# Now loop over our hatch lines looking for intersections
for h in self.grid:
interstices(self, (h[0], h[1]), (h[2], h[3]), self.paths, self.hatches, self.options.holdBackHatchFromEdges, self.options.holdBackSteps)
interstices(self, (h[0], h[1]), (h[2], h[3]), self.paths, self.hatches, self.options.inset_bool, self.inset_dist_px)
elif node.tag in [inkex.addNS('polyline', 'svg'), 'polyline']:
@ -955,13 +976,13 @@ class Eggbot_Hatch(inkex.Effect):
# We now have a path we want to apply a (cross)hatch to
# Apply appropriate functions
b_have_grid = self.makeHatchGrid(float(self.options.hatchAngle), float(self.options.hatchSpacing), True)
b_have_grid = self.makeHatchGrid(float(self.options.hatchAngle), float(self.hatch_spacing_px), True)
if b_have_grid:
if self.options.crossHatch:
self.makeHatchGrid(float(self.options.hatchAngle + 90.0), float(self.options.hatchSpacing), False)
self.makeHatchGrid(float(self.options.hatchAngle + 90.0), float(self.hatch_spacing_px), False)
# Now loop over our hatch lines looking for intersections
for h in self.grid:
interstices(self, (h[0], h[1]), (h[2], h[3]), self.paths, self.hatches, self.options.holdBackHatchFromEdges, self.options.holdBackSteps)
interstices(self, (h[0], h[1]), (h[2], h[3]), self.paths, self.hatches, self.options.inset_bool, self.inset_dist_px)
elif node.tag in [inkex.addNS('polygon', 'svg'), 'polygon']:
# Convert
@ -982,13 +1003,13 @@ class Eggbot_Hatch(inkex.Effect):
self.addPathVertices(d, node, mat_new)
# We now have a path we want to apply a (cross)hatch to
# Apply appropriate functions
b_have_grid = self.makeHatchGrid(float(self.options.hatchAngle), float(self.options.hatchSpacing), True)
b_have_grid = self.makeHatchGrid(float(self.options.hatchAngle), float(self.hatch_spacing_px), True)
if b_have_grid:
if self.options.crossHatch:
self.makeHatchGrid(float(self.options.hatchAngle + 90.0), float(self.options.hatchSpacing), False)
self.makeHatchGrid(float(self.options.hatchAngle + 90.0), float(self.hatch_spacing_px), False)
# Now loop over our hatch lines looking for intersections
for h in self.grid:
interstices(self, (h[0], h[1]), (h[2], h[3]), self.paths, self.hatches, self.options.holdBackHatchFromEdges, self.options.holdBackSteps)
interstices(self, (h[0], h[1]), (h[2], h[3]), self.paths, self.hatches, self.options.inset_bool, self.inset_dist_px)
elif node.tag in [inkex.addNS('ellipse', 'svg'), 'ellipse',
inkex.addNS('circle', 'svg'), 'circle']:
@ -1033,34 +1054,33 @@ class Eggbot_Hatch(inkex.Effect):
self.addPathVertices(d, node, mat_new)
# We now have a path we want to apply a (cross)hatch to
# Apply appropriate functions
b_have_grid = self.makeHatchGrid(float(self.options.hatchAngle), float(self.options.hatchSpacing), True)
b_have_grid = self.makeHatchGrid(float(self.options.hatchAngle), float(self.hatch_spacing_px), True)
if b_have_grid:
if self.options.crossHatch:
self.makeHatchGrid(float(self.options.hatchAngle + 90.0), float(self.options.hatchSpacing), False)
self.makeHatchGrid(float(self.options.hatchAngle + 90.0), float(self.hatch_spacing_px), False)
# Now loop over our hatch lines looking for intersections
for h in self.grid:
interstices(self, (h[0], h[1]), (h[2], h[3]), self.paths, self.hatches, self.options.holdBackHatchFromEdges, self.options.holdBackSteps)
interstices(self, (h[0], h[1]), (h[2], h[3]), self.paths, self.hatches, self.options.inset_bool, self.inset_dist_px)
elif node.tag in [inkex.addNS('pattern', 'svg'), 'pattern']:
pass
continue
elif node.tag in [inkex.addNS('metadata', 'svg'), 'metadata']:
pass
continue
elif node.tag in [inkex.addNS('defs', 'svg'), 'defs']:
pass
continue
elif node.tag in [inkex.addNS('namedview', 'sodipodi'), 'namedview']:
pass
continue
elif node.tag in [inkex.addNS('eggbot', 'svg'), 'eggbot']:
pass
continue
elif node.tag in [inkex.addNS('WCB', 'svg'), 'WCB']:
pass
continue
elif node.tag in [inkex.addNS('image', 'svg'), 'image']:
continue
elif node.tag in [inkex.addNS('text', 'svg'), 'text']:
inkex.errormsg('Warning: unable to draw text, please convert it to a path first.')
pass
elif not isinstance(node.tag, basestring):
pass
inkex.errormsg('Warning: unable to hatch text, please convert it to a path first.')
continue
else:
inkex.errormsg('Warning: unable to hatch object <{0}>, please convert it to a path first.'.format(node.tag))
pass
continue # produce no error on other SVG elements.
def joinFillsWithNode(self, node, stroke_width, path):
@ -1193,8 +1213,19 @@ class Eggbot_Hatch(inkex.Effect):
# Viewbox handling
self.handleViewBox()
if self.options.hatchSpacing == 0:
self.options.hatchSpacing = 0.1 # Hardcode minimum value
# Default spacing values for hatches and inset; handle px units case:
self.hatch_spacing_px = self.options.hatchSpacing
self.inset_dist_px = self.options.inset_dist
if self.options.units == 3: # Units in mm
self.hatch_spacing_px = (96.0 / 25.4) * self.options.hatchSpacing
self.inset_dist_px = (96.0 / 25.4) * self.options.inset_dist
if self.options.units == 4: # Units in inches
self.hatch_spacing_px = 96.0 * self.options.hatchSpacing
self.inset_dist_px = 96.0 * self.options.inset_dist
if self.hatch_spacing_px < 0.1:
self.hatch_spacing_px = 0.1 # Hardcode minimum value
ref_count = 0
pt_last_position_abs = [0, 0]
@ -1256,14 +1287,14 @@ class Eggbot_Hatch(inkex.Effect):
stroke_width = 1.0
# The transform also applies to the hatch spacing we use when searching for end connections
transformed_hatch_spacing = stroke_width * self.options.hatchSpacing
transformed_hatch_spacing = stroke_width * self.hatch_spacing_px
path = '' # regardless of whether or not we're reducing pen lifts
pt_last_position_abs = [0, 0]
pt_last_position_abs[0] = 0
pt_last_position_abs[1] = 0
f_distance_moved_with_pen_up = 0
if not self.options.reducePenLifts:
if not self.options.connect_bool:
for segment in self.hatches[key]:
if len(segment) < 2:
continue