Skip to content

Commit

Permalink
preparing for 0.15 Fixed #1 (comment)
Browse files Browse the repository at this point in the history
Line width of V7 code became a minimum line width,
rendering is now based on stroke-width
Refactored LengthWithUnit() from getLength()
Still missing: poly*_points[] refactoring in scad code - needed for V7 merge
  • Loading branch information
jnweiger committed Apr 15, 2017
1 parent 94c4b8f commit a4a8da9
Show file tree
Hide file tree
Showing 2 changed files with 170 additions and 48 deletions.
40 changes: 28 additions & 12 deletions paths2openscad.inx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@
<page name="splash" _gui-text="Paths to OpenSCAD">
<param name="fname" type="string" _gui-text="Output file">~/inkscape.scad</param>
<param name="height" type="float" min="0" max="1000" precision="2" _gui-text="Height (mm)">5.0</param>
<_param name="height_help" type="description">Specify how high in millimeters to extrude the polygons.</_param>

<param name="smoothness" type="float" min="0.0001" max="5" _gui-text="Smoothing">0.2</param>
<_param name="smoothness_help" type="description">Define how smoothly to render curves. Use smaller values for smoother curves.
<_param name="height_help" type="description">Standard 3D extrude height (when Auto Height is unused)
</_param>

<param name="autoheight" type="boolean" _gui-text="Auto Height">false</param>
Expand All @@ -38,10 +35,30 @@
<_param name="stlpost_space" type="description"><p/>
</_param>

</page>
<page name="tuning" _gui-text="Fine Tuning">
<_param name="tuning" type="description" xml:space="preserve">
</_param>
<param name="smoothness" type="float" min="0.0001" max="5" _gui-text="Smoothing">0.2</param>
<_param name="smoothness_help" type="description">Used when rendering curves. Smaller values are smoother.
</_param>
<param name="min_line_width" type="float" min="0.1" max="10" _gui-text="Minimum line width [mm]">1.0</param>
<param name="line_fn" type="optiongroup" appearance="minimal" _gui-text="Line width precision">
<option value="4">Rough (fast)</option>
<option value="8">Medium (slow)</option>
<option value="16">Fine (very slow)</option>
</param>
<_param name="line_width_help" type="description">Minimum Line width and precision for outline mode. Lines that are not a closed loop and objects no fill are rendered in outline mode.
</_param>
<param name="force_line" type="boolean" _gui-text="Force outline mode">False</param>
<_param name="force_line_help" type="description">Force outline mode for all objects. Ignore fill.
When outline mode is not appliend, the filled surface of a polygon is extruded as a 3D object without adding any line width.
</_param>


</page>
<page name="autoheight" _gui-text="Auto Height">
<_param name="autoheight" type="description" xml:space="preserve">
The auto height option sets the extruded height for
<_param name="autoheight" type="description" xml:space="preserve">The auto height option sets the extruded height for
a specific polygon based on its description or ID.

If the polygon's ID is in either name_XXX_mm or name_XXXmm
Expand All @@ -52,9 +69,9 @@ Note that all whitespace or comma are saved as '_' in ID.
If the height is prefixed with a letter 'a', the
object is anti-matter, that is, it is cut away (subtracted)
from all normal objects.

Syntax for the polygon's description field:
height: XXX mm
</_param>
<_param name="autoheight_2" type="description" appearance="header">Syntax for the polygon's description field</_param>
<_param name="autoheight_3" type="description" xml:space="preserve">height: XXX mm
where XXX is any decimal number (optionally prefixed
with a letter 'a' for anti-matter) then the height
is set to XXX millimeters.
Expand All @@ -65,8 +82,7 @@ raise: XXX mm

Note that the description has preference over ID.

Otherwise, the height parameter is used.
</_param>
Otherwise, the height parameter is used.</_param>
</page>
<page name="info" _gui-text="About...">
<_param name="aboutpage" type="description" xml:space="preserve">
Expand All @@ -88,7 +104,7 @@ to millimeters using the SVG Standard's
definition of 90 pixels = 1 inch -- since
inkscape 0.92: 96 pixels = 1 inch.

v0.13
v0.15
Dan Newman (dan newman @ mtbaldy us)
Josef Skladanka (jskladan @ redhat com)
Juergen Weigert (juergen @ fabmail org)
Expand Down
178 changes: 142 additions & 36 deletions paths2openscad.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@
# raise: Offset along Z axis, to make cut-outs and balconies.
# Refactored object_merge_auto_values() from convertPath().
# Inheriting auto values from enclosing groups.
# 2017-04-10, [email protected]
# 0.14 Started merging V7 outline mode by Neon22.
# 2017-04-16, [email protected]
# 0.15 Fixed https://github.com/fablabnbg/inkscape-paths2openscad/issues/1#issuecomment-294257592
# Line width of V7 code became a minimum line width,
# rendering is now based on stroke-width
# Refactored LengthWithUnit() from getLength()
#
# CAUTION: keep the version numnber in sync with paths2openscad.inx about page
# CAUTION: indentation and whitespace issues due to pep8 compatibility.
Expand Down Expand Up @@ -75,7 +82,7 @@
INX_STL_POSTPROCESSING = os.getenv('INX_STL_POSTPROCESSING', "cura '{STL}' &")


def parseLengthWithUnits(str):
def parseLengthWithUnits(str, default_unit='px'):
'''
Parse an SVG value which may or may not have units attached
This version is greatly simplified in that it only allows: no units,
Expand All @@ -85,7 +92,7 @@ def parseLengthWithUnits(str):
With inkscape 0.91 we need other units too: e.g. svg:width="400mm"
'''

u = 'px'
u = default_unit
s = str.strip()
if s[-2:] in ('px', 'pt', 'pc', 'mm', 'cm', 'in', 'ft'):
u = s[-2:]
Expand All @@ -101,7 +108,6 @@ def parseLengthWithUnits(str):

return v, u


def pointInBBox(pt, bbox):
'''
Determine if the point pt=[x, y] lies on or within the bounding
Expand Down Expand Up @@ -259,9 +265,46 @@ def subdivideCubicPath(sp, flat, i=1):
sp[i:1] = [p]


def closed_p(path):
""" path[0] is the path to check """
result = False
thepath = path[0]
if isinstance(thepath[0][0], list):
result = True
if (thepath[0][0] == thepath[-1][0]) and (thepath[0][1] == thepath[-1][1]):
result = True
return result


def msg_linear_extrude(id, prefix):
msg = ' linear_extrude(height=h)\n' + \
' polygon(%s_%d_points);\n' % (id, prefix)
return msg


def msg_linear_extrude_by_paths(id, prefix):
msg = ' linear_extrude(height=h)\n' + \
' polygon(%s_%d_points, %s_%d_paths);\n' % \
(id, prefix, id, prefix)
return msg


def msg_extrude_by_hull(id, prefix):
msg = ' for (t = [0: len(%s_%d_points)-2]) {\n' % (id, prefix) + \
' hull() {\n' + \
' translate(%s_%d_points[t]) \n' % (id, prefix) + \
' cylinder(h=h, r=w/2, $fn=res);\n' + \
' translate(%s_%d_points[t + 1]) \n' % (id, prefix) + \
' cylinder(h=h, r=w/2, $fn=res);\n' + \
' }\n' + \
' }\n'
return msg


class OpenSCAD(inkex.Effect):
def __init__(self):

inkex.localize() # does not help for localizing my *.inx file
inkex.Effect.__init__(self)

self.OptionParser.add_option(
Expand All @@ -278,6 +321,20 @@ def __init__(self):
'--height', dest='height', type='string', default='5',
action='store', help='Height (mm)')

self.OptionParser.add_option(
'--min_line_width', dest='min_line_width', type='float',
default=float(1), action='store',
help='Line width for non closed curves (mm)')

self.OptionParser.add_option("-n",'--line_fn', dest='line_fn',
type='int', default=int( 4 ), action='store',
help='Line width precision ($fn when constructing hull)' )

self.OptionParser.add_option(
"--force_line", action="store", type="inkbool",
dest="force_line", default=False,
help="Force outline mode.")

self.OptionParser.add_option(
'--fname', dest='fname', type='string', default='~/inkscape.scad',
action='store', help='Curve smoothing (less for more)')
Expand Down Expand Up @@ -307,6 +364,7 @@ def __init__(self):
'(typically a slicer). Use {STL} for the STL filename.')

self.dpi = 90.0 # factored out for inkscape-0.92
self.px_used = False # raw px unit depends on correct dpi.
self.cx = float(DEFAULT_WIDTH) / 2.0
self.cy = float(DEFAULT_HEIGHT) / 2.0
self.xmin, self.xmax = (1.0E70, -1.0E70)
Expand Down Expand Up @@ -348,38 +406,42 @@ def getLength(self, name, default):
Note that SVG defines 90 px = 1 in = 25.4 mm.
Note: Since inkscape 0.92 we use the CSS standard of 96 px = 1 in.
'''

str = self.document.getroot().get(name)
if str:
v, u = parseLengthWithUnits(str)
if not v:
# Couldn't parse the value
return None
elif (u == 'mm'):
return float(v) * (self.dpi / 25.4)
elif (u == 'cm'):
return float(v) * (self.dpi * 10.0 / 25.4)
elif (u == 'm'):
return float(v) * (self.dpi * 1000.0 / 25.4)
elif (u == 'in'):
return float(v) * self.dpi
elif (u == 'ft'):
return float(v) * 12.0 * self.dpi
elif (u == 'pt'):
# Use modern "Postscript" points of 72 pt = 1 in instead
# of the traditional 72.27 pt = 1 in
return float(v) * (self.dpi / 72.0)
elif (u == 'pc'):
return float(v) * (self.dpi / 6.0)
elif (u == 'px'):
return float(v)
else:
# Unsupported units
return None
return self.LengthWithUnit(str)
else:
# No width specified; assume the default value
return float(default)

def LengthWithUnit(self, str, default_unit='px'):
v, u = parseLengthWithUnits(str, default_unit)
if not v:
# Couldn't parse the value
return None
elif (u == 'mm'):
return float(v) * (self.dpi / 25.4)
elif (u == 'cm'):
return float(v) * (self.dpi * 10.0 / 25.4)
elif (u == 'm'):
return float(v) * (self.dpi * 1000.0 / 25.4)
elif (u == 'in'):
return float(v) * self.dpi
elif (u == 'ft'):
return float(v) * 12.0 * self.dpi
elif (u == 'pt'):
# Use modern "Postscript" points of 72 pt = 1 in instead
# of the traditional 72.27 pt = 1 in
return float(v) * (self.dpi / 72.0)
elif (u == 'pc'):
return float(v) * (self.dpi / 6.0)
elif (u == 'px'):
self.px_used = True
return float(v)
else:
# Unsupported units
return None


def getDocProps(self):

'''
Expand All @@ -388,21 +450,37 @@ def getDocProps(self):
expressed in units of percentages.
'''

self.docHeight = self.getLength('height', DEFAULT_HEIGHT)
self.docWidth = self.getLength('width', DEFAULT_WIDTH)
inkscape_version = self.document.getroot().get(
"{http://www.inkscape.org/namespaces/inkscape}version")
sodipodi_docname = self.document.getroot().get(
"{http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd}docname")
# a simple 'inkscape:version' does not work here. sigh....
#
# BUG:
# inkscape 0.92 uses 96 dpi, inkscape 0.91 uses 90 dpi.
# From inkscape 0.92 we receive an svg document that has
# both inkscape:version and sodipodi:docname if the document
# was ever saved before. If not, both elements are missing.
#
import lxml.etree
inkex.errormsg( lxml.etree.tostring(self.document.getroot()) )
if inkscape_version:
'''
inkscape:version="0.91 r"
inkscape:version="0.92.0 ..."
See also https://github.com/fablabnbg/paths2openscad/issues/1
See also https://github.com/fablabnbg/paths2openscad/issues/1
'''
inkex.errormsg("inkscape:version="+inkscape_version)
m = re.match(r"(\d+)\.(\d+)", inkscape_version)
if m:
if int(m.group(1)) > 0 or int(m.group(2)) > 91:
self.dpi = 96 # 96dpi since inkscape 0.92
inkex.errormsg("switching to 96 dpi")

# BUGFIX https://github.com/fablabnbg/inkscape-paths2openscad/issues/1
# get height and width after dpi. This is needed in case of e.g. mm units.
self.docHeight = self.getLength('height', DEFAULT_HEIGHT)
self.docWidth = self.getLength('width', DEFAULT_WIDTH)

if (self.docHeight is None) or (self.docWidth is None):
return False
Expand Down Expand Up @@ -523,6 +601,15 @@ def getPathVertices(self, path, node=None, transform=None):
if len(subpath_list) > 0:
self.paths[node] = subpath_list

def getPathStyle(self, node):
style = node.get('style', '')
ret = {}
# fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1
for elem in style.split(';'):
(key, val) = elem.strip().split(':')
ret[key] = val
return ret

def convertPath(self, node):

def object_merge_auto_values(auto, node):
Expand Down Expand Up @@ -583,7 +670,21 @@ def object_merge_auto_values(auto, node):
self.pathid += 1
else:
id = re.sub('[^A-Za-z0-9_]+', '', rawid)
self.f.write('module poly_' + id + '(h)\n{\n')

style = self.getPathStyle(node)
stroke_width = style.get('stroke-width', '1')
# FIXME: works with document units == 'mm', but otherwise untested..
stroke_width_mm = self.LengthWithUnit(stroke_width, default_unit='mm')
stroke_width_mm = str(stroke_width_mm * 25.4 / self.dpi) # px to mm
fill_color = style.get('fill', '#FFF')
if (fill_color == 'none') or self.options.force_line:
filled = False
else:
filled = True
inkex.errormsg('filled='+str(filled))
# inkex.errormsg(id+': style='+str(style))

self.f.write('module poly_' + id + '(h, w, res=line_fn)\n{\n')
self.f.write(' scale([25.4/%g, -25.4/%g, 1]) union()\n {\n' %
(self.dpi, self.dpi))

Expand All @@ -594,8 +695,9 @@ def object_merge_auto_values(auto, node):
if self.options.autoheight == 'true':
object_merge_auto_values(auto, node)

call_item = 'translate ([0,0,%s]) poly_%s(%s);\n' % (
auto['raise'], id, auto['height'])
call_item = 'translate ([0,0,%s]) poly_%s(%s, min(%s, %s));\n' % (
auto['raise'], id, auto['height'],
'min_line_width', stroke_width_mm)

if auto['neg']:
self.call_list_neg.append(call_item)
Expand Down Expand Up @@ -1061,6 +1163,10 @@ def effect(self):
// keep the resulting .stl file manifold.
fudge = 0.1;
''')
# writeout users parameters
self.f.write( 'height = %s;\n' % ( self.options.height ) )
self.f.write( 'min_line_width = %s;\n' % ( self.options.min_line_width ) )
self.f.write( 'line_fn = %d;\n\n' % ( self.options.line_fn ) )

for key in self.paths:
self.f.write('\n')
Expand All @@ -1084,7 +1190,7 @@ def effect(self):
self.f.write(' }\n }\n')

# The module that calls all the other ones.
self.f.write('}\n\n%s(%s);\n' % (name, self.options.height))
self.f.write('}\n\n%s(height);\n' % (name))
self.f.close()

except IOError as e:
Expand Down

0 comments on commit a4a8da9

Please sign in to comment.