Root/sfc/slicer.py

1#!/usr/bin/python
2#
3# slicer.py - FreeCAD-based STL to Gnuplot slicer
4#
5# Written 2015 by Werner Almesberger
6# Copyright 2015 by Werner Almesberger
7#
8# This program//library is free software; you can redistribute it and/or
9# modify it under the terms of the GNU Lesser General Public
10# License as published by the Free Software Foundation; either
11# version 2.1 of the License, or (at your option) any later version.
12#
13
14
15import sys
16
17sys.path.append("/usr/lib/freecad/lib")
18
19
20import FreeCAD, Part, Mesh
21import os, getopt
22from FreeCAD import Base
23from math import hypot
24
25
26epsilon = 0.0001 # acceptable math rounding error and slicing offset
27mech_eps = 0.01 # acceptable mechanical deviation
28margin = None # draw a rectangular workpiece at the specified xy
29            # distance around the model (default: none)
30z_step = None # maximum Z step (default: unlimited)
31flip = False # flip around X center (default: don't)
32height = None # height of the workpiece above the Z plane (can be
33            # negative). Default: use model dimensions.
34align_top = None # align the Z position of the model to the workpiece
35align_bottom = None
36end = 0 # Z adjustment of final layer
37
38
39def dist(a, b):
40    pa = a.Point
41    pb = b.Point
42    return hypot(pa[0] - pb[0], pa[1] - pb[1])
43
44
45def print_vec(v, z):
46    p = v.Point
47    print p[0], " ", p[1], " ", z
48
49
50# Make a vector from a point. While we're at it, also apply flipping (if
51# requested).
52
53def vec(p):
54    if flip:
55        return Base.Vector(p[0],
56            bb.YMax - p[1] + bb.YMin, bb.ZMax - p[2] + bb.ZMin)
57    else:
58        return Base.Vector(p[0], p[1], p[2])
59
60#
61# Dump the current Z level (plateau or intermediate level).
62#
63
64
65def dump_level(wires, z):
66    print "# level z = ", z
67
68    if margin is not None:
69        print bb.XMin - margin, " ", bb.YMin - margin, " ", z
70        print bb.XMax + margin, " ", bb.YMin - margin, " ", z
71        print bb.XMax + margin, " ", bb.YMax + margin, " ", z
72        print bb.XMin - margin, " ", bb.YMax + margin, " ", z
73        print bb.XMin - margin, " ", bb.YMin - margin, " ", z
74        print
75
76    for wire in wires:
77        print "# wire = ", wire
78        first = None
79        last = None
80        for e in wire.Edges:
81            v = e.Vertexes[0]
82            if first is None:
83                first = v
84            if last is None or dist(v, last) >= mech_eps:
85                print_vec(v, z)
86                last = v
87        if first is not None:
88            print_vec(first, z)
89            print
90    print
91
92
93def usage():
94    print >>sys.stderr, "usage:", sys.argv[0], \
95        "[-a (top|bottom)(+|-)offset] [-f] [-h height]\n" + \
96        "\t[-m tolerance] [-p piece_distance] [-o z_offset] "+ \
97        "[-s max_step] file.stl"
98    sys.exit(1)
99
100
101#
102# FreeCAD prints progress information to stdout instead of stderr.
103# We don't want that ...
104#
105
106stdout = os.dup(1)
107os.dup2(2, 1)
108sys.stdout = os.fdopen(stdout, "w")
109
110opts, args = getopt.getopt(sys.argv[1:], "a:e:fh:m:o:p:s:")
111for opt, arg in opts:
112    if opt == "-a":
113        if arg[0:3] == "top":
114            align_top = float(arg[3:])
115        elif arg[0:6] == "bottom":
116            align_bottom = float(arg[6:])
117        else:
118            usage()
119    elif opt == "-m":
120        mech_eps = float(arg)
121    elif opt == "-o":
122        end = float(arg)
123    elif opt == "-f":
124        flip = True
125    elif opt == "-h":
126        height = float(arg)
127    elif opt == "-p":
128        margin = float(arg)
129    elif opt == "-s":
130        z_step = float(arg)
131    else:
132        assert False
133
134if len(args) != 1:
135    usage()
136
137#
138# Read the STL mesh and determine its bounding box
139#
140
141mesh = Mesh.Mesh(args[0])
142bb = mesh.BoundBox
143
144#
145# The 2.5D model consists of "plateaus" (facets parallel to the xy plane) and
146# "walls" (facets parallel to the z axis). Anything else is an error and will
147# produce incorrect results.
148#
149# We use plateau facets only for their z position, as indication where to mill
150# a plateau. Wall facets are kept for later use.
151#
152
153vert = Mesh.Mesh()
154z_raw = {}
155max_nz = 0
156inclined = 0
157for facet in mesh.Facets:
158    if abs(facet.Normal.z) >= 1 - epsilon:
159        z = facet.Points[0][2]
160        if flip:
161            z = bb.ZMax - z + bb.ZMin
162        z_raw[z] = 1
163    else:
164        nz = abs(facet.Normal.z)
165        if nz > epsilon:
166            inclined += 1
167            max_nz = max(max_nz, nz)
168        vert.addFacet(vec(facet.Points[0]), vec(facet.Points[1]),
169            vec(facet.Points[2]))
170
171if inclined:
172    print >>sys.stderr # FreeCAD progress reporting messes up newlines
173    print >>sys.stderr, inclined, "inclined facets, maximum normal", max_nz
174
175#
176# @@@ This is perhaps a bit too paranoid
177#
178# I wrote the Z noise filtering because I had mis-read perfectly good
179# distinct coordinates as being essentially the same value but with
180# rounding errors.
181#
182
183z_levels = []
184last = None
185for z in sorted(z_raw.keys(), reverse = True):
186    if last is None or last - z > epsilon:
187        z_levels.append(z)
188        last = z
189
190#
191# Convert the walls to a FreeCAD shape
192#
193
194shape = Part.Shape()
195shape.makeShapeFromMesh(vert.Topology, mech_eps)
196
197z_off = 0
198if height is not None:
199    if align_top is not None:
200        z_off = align_top - bb.ZMax
201        if height > 0:
202            z_off += height
203    if align_bottom is not None:
204        z_off = align_bottom - bb.ZMin
205        if height < 0:
206            z_off += height
207
208#
209# Iterate over all plateaus and determine how they intersect with the walls.
210# For this, we add a small offset to the z position so that we intersect above
211# the plateau.
212#
213# We advance by at most z_step and insert intermediate layers if needed.
214#
215
216if height is not None and height > 0:
217    last_z = height - z_off
218else:
219    last_z = None
220if height is not None and height < 0 and z_levels[-1] > height:
221    z_levels.append(height - z_off)
222
223for i in range(0, len(z_levels)):
224    next_z = z_levels[i]
225    print >>sys.stderr, "next_z = ", next_z
226    wires = shape.slice(Base.Vector(0, 0, 1), next_z + epsilon)
227    if i == len(z_levels) - 1:
228        next_z += end
229    if z_step is None or last_z is None or last_z - z_step <= next_z:
230        dump_level(wires, next_z + z_off)
231    else:
232        d = last_z - next_z
233        n = int(d // z_step) + 1
234        for j in range(0, n):
235            dump_level(wires, last_z - (j + 1) * (d / n) + z_off)
236    last_z = next_z
237    
238#
239# That's all, folks !
240#
241

Archive Download this file

Branches:
master



interactive