Beispiel #1
0
def convert_eps2dxf(eps_filename, dxf_filename, location=None, unit="mm"):
    if location is None:
        location = get_external_program_location("pstoedit")
        if location is None:
            location = "pstoedit"
    args = [location, "-dt", "-nc", "-f", "dxf:-polyaslines"]
    if unit == "mm":
        # eps uses inch by default - we need to scale
        args.extend(("-xscale", "25.4", "-yscale", "25.4"))
    args.append(eps_filename)
    args.append(dxf_filename)
    try:
        process = subprocess.Popen(stdin=subprocess.PIPE,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   args=args)
    except OSError as err_msg:
        raise LoadFileError(
            "SVGImporter: failed to execute 'pstoedit' ({}): {}{}Maybe you need "
            "to install pstoedit (http://pstoedit.net)?".format(
                location, err_msg, os.linesep))
    returncode = process.wait()
    if returncode == 0:
        try:
            # pstoedit fails with exitcode=0 if ghostscript is not installed.
            # The resulting file seems to be quite small (268 byte). But it is
            # not certain, that this filesize is fixed in case of this problem.
            if os.path.getsize(dxf_filename) < 280:
                log.warn(
                    "SVGImporter: maybe there was a problem with the conversion from EPS "
                    "(%s) to DXF.\nProbably you need to install 'ghostscript' "
                    "(http://pages.cs.wisc.edu/~ghost).", str(eps_filename))
        except OSError:
            # The dxf file was not created.
            raise LoadFileError(
                "SVGImporter: no DXF file was created, even though no error code "
                "was returned. This seems to be a bug of 'pstoedit'. Please send "
                "the original model file to the PyCAM developers. Thanks!")
    elif returncode == -11:
        # just a warning - probably it worked fine
        log.warn(
            "SVGImporter: maybe there was a problem with the conversion from EPS (%s) to "
            "DXF.\n Users of Ubuntu 'lucid' should install the package 'libpstoedit0c2a' "
            "from the 'maverick' repository to avoid this warning.",
            str(eps_filename))
    else:
        raise LoadFileError(
            "SVGImporter: failed to convert EPS file ({}) to DXF file ({}): {}"
            .format(eps_filename, dxf_filename, process.stderr.read()))
Beispiel #2
0
def import_font(filename, callback=None):
    try:
        infile = pycam.Utils.URIHandler(filename).open()
    except IOError as exc:
        raise LoadFileError("CXFImporter: Failed to read file ({}): {}"
                            .format(filename, exc)) from exc
    try:
        parsed_font = CXFParser(infile, callback=callback)
    except _CXFParseError as exc:
        raise LoadFileError("CFXImporter: Skipped font definition file '{}'. Reason: {}."
                            .format(filename, exc)) from exc
    charset = Charset(**parsed_font.meta)
    for key, value in parsed_font.letters.items():
        charset.add_character(key, value)
    log.info("CXFImporter: Imported CXF font from '%s': %d letters",
             filename, len(parsed_font.letters))
    infile.close()
    return charset
Beispiel #3
0
def convert_svg2eps(svg_filename, eps_filename, location=None):
    if location is None:
        location = get_external_program_location("inkscape")
        if location is None:
            location = "inkscape"
    try:
        process = subprocess.Popen(stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   args=[location, "--export-area-page", "--export-eps",
                                         eps_filename, svg_filename])
    except OSError as exc:
        raise LoadFileError("SVGImporter: failed to execute 'inkscape' ({}): {}{}Maybe you need "
                            "to install Inkscape (http://inkscape.org)?"
                            .format(location, exc, os.linesep))
    returncode = process.wait()
    if returncode != 0:
        raise LoadFileError("SVGImporter: failed to convert SVG file ({}) to EPS file ({}): {}"
                            .format(svg_filename, eps_filename, process.stderr.read()))
Beispiel #4
0
def import_model(filename,
                 color_as_height=False,
                 fonts_cache=None,
                 callback=None,
                 **kwargs):
    if hasattr(filename, "read"):
        infile = filename
    else:
        try:
            infile = pycam.Utils.URIHandler(filename).open()
        except IOError as exc:
            raise LoadFileError(
                "DXFImporter: Failed to read file ({}): {}".format(
                    filename, exc))

    result = DXFParser(infile,
                       color_as_height=color_as_height,
                       fonts_cache=fonts_cache,
                       callback=callback)

    model_data = result.get_model()
    lines = model_data["lines"]
    triangles = model_data["triangles"]

    if callback and callback():
        raise AbortOperationException(
            "DXFImporter: load model operation was cancelled")

    # 3D models are preferred over 2D models
    if triangles:
        if lines:
            log.warn("DXFImporter: Ignoring 2D elements in DXF file: %d lines",
                     len(lines))
        model = pycam.Geometry.Model.Model()
        for index, triangle in enumerate(triangles):
            model.append(triangle)
            # keep the GUI smooth
            if callback and (index % 50 == 0):
                callback()
        log.info("DXFImporter: Imported DXF model (3D): %d triangles",
                 len(model.triangles()))
        return model
    elif lines:
        model = pycam.Geometry.Model.ContourModel()
        for index, line in enumerate(lines):
            model.append(line)
            # keep the GUI smooth
            if callback and (index % 50 == 0):
                callback()
        # z scaling is always targeted at the 0..1 range
        if color_as_height and (model.minz != model.maxz):
            # scale z to 1
            scale_z = 1.0 / (model.maxz - model.minz)
            if callback:
                callback(text="Scaling height for multi-layered 2D model")
            log.info("DXFImporter: scaling height for multi-layered 2D model")
            model.scale(scale_x=1.0,
                        scale_y=1.0,
                        scale_z=scale_z,
                        callback=callback)
        # shift the model down to z=0
        if model.minz != 0:
            if callback:
                callback(text="Shifting 2D model down to to z=0")
            model.shift(0, 0, -model.minz, callback=callback)
        log.info(
            "DXFImporter: Imported DXF model (2D): %d lines / %d polygons",
            len(lines), len(model.get_polygons()))
        return model
    else:
        link = "http://pycam.sourceforge.net/supported-formats"
        raise LoadFileError(
            'DXFImporter: No supported elements found in DXF file!\n'
            '<a href="%s">Read PyCAM\'s modeling hints.</a>'.format(link))
Beispiel #5
0
def import_model(filename,
                 program_locations=None,
                 unit="mm",
                 callback=None,
                 **kwargs):
    local_file = False
    if hasattr(filename, "read"):
        infile = filename
        ps_file_handle, ps_file_name = tempfile.mkstemp(suffix=".ps")
        try:
            temp_file = os.fdopen(ps_file_handle, "w")
            temp_file.write(infile.read())
            temp_file.close()
        except IOError as exc:
            raise LoadFileError(
                "PSImporter: Failed to create temporary local file ({}): {}".
                format(ps_file_name, exc))
        filename = ps_file_name
    else:
        uri = pycam.Utils.URIHandler(filename)
        if not uri.exists():
            raise LoadFileError(
                "PSImporter: file ({}) does not exist".format(filename))
        if not uri.is_local():
            # non-local file - write it to a temporary file first
            ps_file_handle, ps_file_name = tempfile.mkstemp(suffix=".ps")
            os.close(ps_file_handle)
            log.debug("Retrieving PS file for local access: %s -> %s", uri,
                      ps_file_name)
            if not uri.retrieve_remote_file(ps_file_name, callback=callback):
                raise LoadFileError(
                    "PSImporter: Failed to retrieve the PS model file: {} -> {}"
                    .format(uri, ps_file_name))
            filename = ps_file_name
        else:
            filename = uri.get_local_path()
            local_file = True

    if program_locations and "pstoedit" in program_locations:
        pstoedit_path = program_locations["pstoedit"]
    else:
        pstoedit_path = None

    def remove_temp_file(filename):
        if os.path.isfile(filename):
            try:
                os.remove(filename)
            except OSError as exc:
                log.warning(
                    "PSImporter: failed to remove temporary file ({}): {}".
                    format(filename, exc))

    # convert eps to dxf via pstoedit
    with create_named_temporary_file(suffix=".dxf") as dxf_file_name:
        success = convert_eps2dxf(filename,
                                  dxf_file_name,
                                  unit=unit,
                                  location=pstoedit_path)
        if not local_file:
            remove_temp_file(ps_file_name)
        if not success:
            raise LoadFileError("Failed to convert EPS to DXF file")
        elif callback and callback():
            raise AbortOperationException(
                "PSImporter: load model operation cancelled")
        else:
            log.info("Successfully converted PS file to DXF file")
            # pstoedit uses "inch" -> force a scale operation
            return pycam.Importers.DXFImporter.import_model(dxf_file_name,
                                                            unit=unit,
                                                            callback=callback)
Beispiel #6
0
def import_model(filename, use_kdtree=True, callback=None, **kwargs):
    global vertices, edges, kdtree
    vertices = 0
    edges = 0
    kdtree = None

    normal_conflict_warning_seen = False

    if hasattr(filename, "read"):
        # make sure that the input stream can seek and has ".len"
        f = BufferedReader(filename)
        # useful for later error messages
        filename = "input stream"
    else:
        try:
            url_file = pycam.Utils.URIHandler(filename).open()
            # urllib.urlopen objects do not support "seek" - so we need a buffered reader
            # Is there a better approach than consuming the whole file at once?
            f = BufferedReader(BytesIO(url_file.read()))
            url_file.close()
        except IOError as exc:
            raise LoadFileError(
                "STLImporter: Failed to read file ({}): {}".format(
                    filename, exc))

    # the facet count is only available for the binary format
    facet_count = get_facet_count_if_binary_format(f)
    is_binary = (facet_count is not None)

    if use_kdtree:
        kdtree = PointKdtree([], 3, 1, epsilon)
    model = Model(use_kdtree)

    t = None
    p1 = None
    p2 = None
    p3 = None

    if is_binary:
        # Skip the header and count fields of binary stl file
        f.seek(HEADER_SIZE + COUNT_SIZE)

        for i in range(1, facet_count + 1):
            if callback and callback():
                raise AbortOperationException(
                    "STLImporter: load model operation cancelled")
            a1 = unpack("<f", f.read(4))[0]
            a2 = unpack("<f", f.read(4))[0]
            a3 = unpack("<f", f.read(4))[0]

            n = (float(a1), float(a2), float(a3), 'v')

            v11 = unpack("<f", f.read(4))[0]
            v12 = unpack("<f", f.read(4))[0]
            v13 = unpack("<f", f.read(4))[0]

            p1 = get_unique_vertex(float(v11), float(v12), float(v13))

            v21 = unpack("<f", f.read(4))[0]
            v22 = unpack("<f", f.read(4))[0]
            v23 = unpack("<f", f.read(4))[0]

            p2 = get_unique_vertex(float(v21), float(v22), float(v23))

            v31 = unpack("<f", f.read(4))[0]
            v32 = unpack("<f", f.read(4))[0]
            v33 = unpack("<f", f.read(4))[0]

            p3 = get_unique_vertex(float(v31), float(v32), float(v33))

            # not used (additional attributes)
            f.read(2)

            dotcross = pdot(n, pcross(psub(p2, p1), psub(p3, p1)))
            if a1 == a2 == a3 == 0:
                dotcross = pcross(psub(p2, p1), psub(p3, p1))[2]
                n = None

            if dotcross > 0:
                # Triangle expects the vertices in clockwise order
                t = Triangle(p1, p3, p2)
            elif dotcross < 0:
                if not normal_conflict_warning_seen:
                    log.warn(
                        "Inconsistent normal/vertices found in facet definition %d of '%s'. "
                        "Please validate the STL file!", i, filename)
                    normal_conflict_warning_seen = True
                t = Triangle(p1, p2, p3)
            else:
                # the three points are in a line - or two points are identical
                # usually this is caused by points, that are too close together
                # check the tolerance value in pycam/Geometry/PointKdtree.py
                log.warn(
                    "Skipping invalid triangle: %s / %s / %s (maybe the resolution of the "
                    "model is too high?)", p1, p2, p3)
                continue
            if n:
                t.normal = n

            model.append(t)
    else:
        # from here on we want to use a text based input stream (not bytes)
        f = TextIOWrapper(f, encoding="utf-8")
        solid = re.compile(r"\s*solid\s+(\w+)\s+.*")
        endsolid = re.compile(r"\s*endsolid\s*")
        facet = re.compile(r"\s*facet\s*")
        normal = re.compile(
            r"\s*facet\s+normal" +
            r"\s+(?P<x>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)" +
            r"\s+(?P<y>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)" +
            r"\s+(?P<z>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)\s+")
        endfacet = re.compile(r"\s*endfacet\s+")
        loop = re.compile(r"\s*outer\s+loop\s+")
        endloop = re.compile(r"\s*endloop\s+")
        vertex = re.compile(
            r"\s*vertex" +
            r"\s+(?P<x>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)" +
            r"\s+(?P<y>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)" +
            r"\s+(?P<z>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)\s+")

        current_line = 0

        for line in f:
            if callback and callback():
                raise AbortOperationException(
                    "STLImporter: load model operation cancelled")
            current_line += 1
            m = solid.match(line)
            if m:
                model.name = m.group(1)
                continue

            m = facet.match(line)
            if m:
                m = normal.match(line)
                if m:
                    n = (float(m.group('x')), float(m.group('y')),
                         float(m.group('z')), 'v')
                else:
                    n = None
                continue
            m = loop.match(line)
            if m:
                continue
            m = vertex.match(line)
            if m:
                p = get_unique_vertex(float(m.group('x')), float(m.group('y')),
                                      float(m.group('z')))
                if p1 is None:
                    p1 = p
                elif p2 is None:
                    p2 = p
                elif p3 is None:
                    p3 = p
                else:
                    log.error(
                        "STLImporter: more then 3 points in facet (line %d)",
                        current_line)
                continue
            m = endloop.match(line)
            if m:
                continue
            m = endfacet.match(line)
            if m:
                if None in (p1, p2, p3):
                    log.warn(
                        "Invalid facet definition in line %d of '%s'. Please validate the "
                        "STL file!", current_line, filename)
                    n, p1, p2, p3 = None, None, None, None
                    continue
                if not n:
                    n = pnormalized(pcross(psub(p2, p1), psub(p3, p1)))

                # validate the normal
                # The three vertices of a triangle in an STL file are supposed
                # to be in counter-clockwise order. This should match the
                # direction of the normal.
                if n is None:
                    # invalid triangle (zero-length vector)
                    dotcross = 0
                else:
                    # make sure the points are in ClockWise order
                    dotcross = pdot(n, pcross(psub(p2, p1), psub(p3, p1)))
                if dotcross > 0:
                    # Triangle expects the vertices in clockwise order
                    t = Triangle(p1, p3, p2, n)
                elif dotcross < 0:
                    if not normal_conflict_warning_seen:
                        log.warn(
                            "Inconsistent normal/vertices found in line %d of '%s'. Please "
                            "validate the STL file!", current_line, filename)
                        normal_conflict_warning_seen = True
                    t = Triangle(p1, p2, p3, n)
                else:
                    # The three points are in a line - or two points are
                    # identical. Usually this is caused by points, that are too
                    # close together. Check the tolerance value in
                    # pycam/Geometry/PointKdtree.py.
                    log.warn(
                        "Skipping invalid triangle: %s / %s / %s (maybe the resolution of "
                        "the model is too high?)", p1, p2, p3)
                    n, p1, p2, p3 = (None, None, None, None)
                    continue
                n, p1, p2, p3 = (None, None, None, None)
                model.append(t)
                continue
            m = endsolid.match(line)
            if m:
                continue

    # TODO display unique vertices and edges count - currently not counted
    log.info("Imported STL model: %d triangles", len(model.triangles()))
    vertices = 0
    edges = 0
    kdtree = None

    if not model:
        # no valid items added to the model
        raise LoadFileError(
            "Failed to load model from STL file: no elements found")
    else:
        return model