Source code for importers

"""
build123d imports

name: importers.py
by:   Gumyr
date: March 1st, 2023

desc:
    This python module contains importers from multiple file formats.

license:

    Copyright 2022 Gumyr

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

"""

# pylint has trouble with the OCP imports
# pylint: disable=no-name-in-module, import-error

import os
from math import degrees
from pathlib import Path
from typing import TextIO, Union

import OCP.IFSelect
from OCP.BRep import BRep_Builder
from OCP.BRepTools import BRepTools
from OCP.RWStl import RWStl
from OCP.STEPControl import STEPControl_Reader
from OCP.TopoDS import TopoDS_Face, TopoDS_Shape, TopoDS_Wire
from ocpsvg import ColorAndLabel, import_svg_document
from svgpathtools import svg2paths

from build123d.geometry import Color
from build123d.topology import Compound, Face, Shape, ShapeList, Wire


[docs]def import_brep(file_name: str) -> Shape: """Import shape from a BREP file Args: file_name (str): brep file Raises: ValueError: file not found Returns: Shape: build123d object """ shape = TopoDS_Shape() builder = BRep_Builder() BRepTools.Read_s(shape, file_name, builder) if shape.IsNull(): raise ValueError(f"Could not import {file_name}") return Shape.cast(shape)
[docs]def import_step(file_name: str) -> Compound: """import_step Extract shapes from a STEP file and return them as a Compound object. Args: file_name (str): file path of STEP file to import Raises: ValueError: can't open file Returns: Compound: contents of STEP file """ # Now read and return the shape reader = STEPControl_Reader() read_status = reader.ReadFile(file_name) # pylint fails to understand OCP's module here, so suppress on the next line. if read_status != OCP.IFSelect.IFSelect_RetDone: # pylint: disable=no-member raise ValueError(f"STEP File {file_name} could not be loaded") for i in range(reader.NbRootsForTransfer()): reader.TransferRoot(i + 1) occ_shapes = [] for i in range(reader.NbShapes()): occ_shapes.append(reader.Shape(i + 1)) # Make sure that we extract all the solids solids = [] for shape in occ_shapes: solids.append(Shape.cast(shape)) return Compound(solids)
[docs]def import_stl(file_name: str) -> Face: """import_stl Extract shape from an STL file and return it as a Face reference object. Note that importing with this method and creating a reference is very fast while creating an editable model (with Mesher) may take minutes depending on the size of the STL file. Args: file_name (str): file path of STL file to import Raises: ValueError: Could not import file Returns: Face: STL model """ # Read and return the shape reader = RWStl.ReadFile_s(file_name) face = TopoDS_Face() BRep_Builder().MakeFace(face, reader) stl_obj = Face.cast(face) return stl_obj
[docs]def import_svg_as_buildline_code(file_name: str) -> tuple[str, str]: """translate_to_buildline_code Translate the contents of the given svg file into executable build123d/BuildLine code. Args: file_name (str): svg file name Returns: tuple[str, str]: code, builder instance name """ translator = { "Line": ["Line", "start", "end"], "CubicBezier": ["Bezier", "start", "control1", "control2", "end"], "QuadraticBezier": ["Bezier", "start", "control", "end"], "Arc": [ "EllipticalCenterArc", # "EllipticalStartArc", "start", "end", "radius", "rotation", "large_arc", "sweep", ], } paths_info = svg2paths(file_name) paths, _path_attributes = paths_info[0], paths_info[1] builder_name = os.path.basename(file_name).split(".")[0] builder_name = builder_name if builder_name.isidentifier() else "builder" buildline_code = [ "from build123d import *", f"with BuildLine() as {builder_name}:", ] for path in paths: for curve in path: class_name = type(curve).__name__ if class_name == "Arc": values = [ (curve.__dict__["center"].real, curve.__dict__["center"].imag) ] values.append(curve.__dict__["radius"].real) values.append(curve.__dict__["radius"].imag) start, end = sorted( [ curve.__dict__["theta"], curve.__dict__["theta"] + curve.__dict__["delta"], ] ) values.append(start) values.append(end) values.append(degrees(curve.__dict__["phi"])) if curve.__dict__["delta"] < 0.0: values.append("AngularDirection.CLOCKWISE") else: values.append("AngularDirection.COUNTER_CLOCKWISE") # EllipticalStartArc implementation # values = [p.__dict__[parm] for parm in translator[class_name][1:3]] # values.append(p.__dict__["radius"].real) # values.append(p.__dict__["radius"].imag) # values.extend([p.__dict__[parm] for parm in translator[class_name][4:]]) else: values = [curve.__dict__[parm] for parm in translator[class_name][1:]] values_str = ",".join( [ f"({v.real}, {v.imag})" if isinstance(v, complex) else str(v) for v in values ] ) buildline_code.append(f" {translator[class_name][0]}({values_str})") return ("\n".join(buildline_code), builder_name)
[docs]def import_svg( svg_file: Union[str, Path, TextIO], *, flip_y: bool = True, ignore_visibility: bool = False, label_by: str = "id", is_inkscape_label: bool = False, ) -> ShapeList[Union[Wire, Face]]: """import_svg Args: svg_file (Union[str, Path, TextIO]): svg file flip_y (bool, optional): flip objects to compensate for svg orientation. Defaults to True. ignore_visibility (bool, optional): Defaults to False. label_by (str, optional): xml attribute. Defaults to "id". is_inkscape_label (bool, optional): flag to indicate that the attribute is an Inkscape label like `inkscape:label` - label_by would be set to `label` in this case. Defaults to False. Raises: ValueError: unexpected shape type Returns: ShapeList[Union[Wire, Face]]: objects contained in svg """ shapes = [] label_by = ( "{http://www.inkscape.org/namespaces/inkscape}" + label_by if is_inkscape_label else label_by ) for face_or_wire, color_and_label in import_svg_document( svg_file, flip_y=flip_y, ignore_visibility=ignore_visibility, metadata=ColorAndLabel.Label_by(label_by), ): if isinstance(face_or_wire, TopoDS_Wire): shape = Wire(face_or_wire) elif isinstance(face_or_wire, TopoDS_Face): shape = Face(face_or_wire) else: # should not happen raise ValueError(f"unexpected shape type: {type(face_or_wire).__name__}") if shape.wrapped: shape.color = Color(*color_and_label.color_for(shape.wrapped)) shape.label = color_and_label.label shapes.append(shape) return ShapeList(shapes)