def test_step_exporter_overwrite(box_shape): r"""Happy path with a subclass of TopoDS_Shape""" filename = path_from_file(__file__, "./models_out/box.stp") exporter = StepExporter(filename) solid = shape_to_topology(box_shape) assert isinstance(solid, TopoDS_Solid) exporter.add_shape(solid) exporter.write_file() initial_timestamp = os.path.getmtime(filename) assert os.path.isfile(filename) # read the written box.stp importer = StepImporter(filename) topo_compound = Topo(importer.compound, return_iter=False) assert topo_compound.number_of_faces == 6 assert len(topo_compound.faces) == 6 assert topo_compound.number_of_edges == 12 # add a sphere and write again with same exporter sphere = BRepPrimAPI_MakeSphere(10) exporter.add_shape(sphere.Shape()) exporter.write_file() # this creates a file with a box and a sphere intermediate_timestamp = os.path.getmtime(filename) assert intermediate_timestamp > initial_timestamp # check that the file contains the box and the sphere importer = StepImporter(filename) # 6 from box + 1 from sphere assert len(Topo(importer.compound, return_iter=False).faces) == 7 assert len(Topo(importer.compound, return_iter=False).solids) == 2 # create a new exporter and overwrite with a box only filename = path_from_file(__file__, "./models_out/box.stp") exporter = StepExporter(filename) solid = shape_to_topology(box_shape) exporter.add_shape(solid) exporter.write_file() assert os.path.isfile(filename) last_timestamp = os.path.getmtime(filename) assert last_timestamp > intermediate_timestamp # check the file only contains a box importer = StepImporter(filename) # 6 from box assert len(Topo(importer.compound, return_iter=False).faces) == 6 assert len(Topo(importer.compound, return_iter=False).solids) == 1
def view_topology_with_aocutils(step_filename): r"""View the STEP file contents in the aocutils wx viewer. The aocutils wx viewer is good to visualize topology. Parameters ---------- step_filename : str Path to the STEP file """ importer = StepImporter(filename=step_filename) class MyFrame(wx.Frame): r"""Frame for testing""" def __init__(self): wx.Frame.__init__(self, None, -1) self.p = Wx3dViewer(self) for shape in importer.shapes: self.p.display_shape(shape) self.Show() app = wx.App() frame = MyFrame() app.SetTopWindow(frame) app.MainLoop()
def anchorable_part_from_stepzip(stepzip_filepath): r"""Create an anchorable part (AnchorablePart instance) from a stepzip file Parameters ---------- stepzip_filepath : str Path to the stepzip file Returns ------- AnchorablePart """ if stepzip_filepath in cache: shape, stepfile_path, anchors_dict, properties = cache[ stepzip_filepath] else: stepfile_path, part_data_file_path = extract_stepzip(stepzip_filepath) step_imp = StepImporter(stepfile_path) shape = step_imp.compound anchors_dict, properties = read_part_data(part_data_file_path) cache[stepzip_filepath] = (shape, stepfile_path, anchors_dict, properties) anchors_list = anchors_dict_to_list(anchors_dict) return AnchorablePart(shape=shape, name=splitext(basename(stepfile_path))[0], anchors=anchors_list, properties=properties)
def test_step_importer_happy_path(): r"""happy path""" importer = StepImporter( path_from_file(__file__, "./models_in/aube_pleine.stp")) assert isinstance(importer.compound, TopoDS_Compound) assert isinstance(importer.shapes, list) for shape in importer.shapes: assert isinstance(shape, TopoDS_Shape) assert len(importer.shapes) == 1
def test_step_importer_2_boxes(): r"""Import an step file containing 2 distinct boxes and test topology""" importer = StepImporter( path_from_file(__file__, "./models_in/2_boxes_203.stp")) assert len(importer.shapes) == 1 assert importer.shapes[0].ShapeType() == TopAbs_COMPOUND topo = Topo(importer.shapes[0]) assert topo.number_of_compounds == 1 assert topo.number_of_comp_solids == 0 assert topo.number_of_solids == 2 assert topo.number_of_shells == 2
def test_step_importer_happy_topology(): r"""import step file containing a box and test topology""" importer = StepImporter(path_from_file(__file__, "./models_in/box_203.stp")) assert len(importer.shapes) == 1 assert isinstance(importer.shapes[0], TopoDS_Shape) assert importer.shapes[0].ShapeType() == TopAbs_SOLID topo = Topo(importer.shapes[0]) assert topo.number_of_compounds == 0 assert topo.number_of_comp_solids == 0 assert topo.number_of_solids == 1 assert topo.number_of_shells == 1
def test_better_bounds_complex_shape(): r"""Test BetterBoundingBox on a complex shape""" hullshape = StepImporter(filename=os.path.join(os.path.dirname(__file__), "test_files/G10.stp")).shapes[0] # From Rhinoceros bounding box # min = 0.000,-82.263,-52.092 # max = 990.000,82.263,102.210 in World coordinates # dimensions = 990.000,164.527,154.302 # # z max has to be replaced by 102.20984 for the test to pass tolerance = 0.01 bbb = BetterBoundingBox(hullshape, tol=tolerance) assert -tolerance <= bbb.x_min <= 0. assert 990. <= bbb.x_max <= 990. + tolerance assert -82.263 - tolerance <= bbb.y_min <= -82.263 assert 82.263 <= bbb.y_max <= 82.263 + tolerance assert -52.092 - tolerance <= bbb.z_min <= -52.092 assert 102.20984 <= bbb.z_max <= 102.210 + tolerance
def handle_cad_file_open(): r"""Handle the logic of cad file opening Returns ------- path : str type_ : str iges, step, stl brep shapes: list[OCC.TopoDS.TopoDS_Shape] """ with OpenCadDialog() as open_cad_file_dialog: if open_cad_file_dialog.ShowModal() == wx.ID_OK: # cast the path from the FileDialog to st to avoid # TypeError: in method 'XSControl_Reader_ReadFile', # argument 2 of type 'char const *' path = str(open_cad_file_dialog.GetPath()) extension = extract_file_extension(path) if extension.lower() in step_extensions: type_ = "step" shapes = StepImporter(path).shapes elif extension.lower() in iges_extensions: type_ = "iges" shapes = IgesImporter(path).shapes elif extension.lower() in stl_extensions: type_ = "stl" shapes = list() shapes.append(StlImporter(path).shape) elif extension.lower() in brep_extensions: type_ = "brep" shapes = list() shapes.append(BrepImporter(path).shape) else: raise ValueError("File extension indicates a file type that " "is not supported") # return filepath + type (iges ...) + list of shapes return path, type_, shapes else: # cancel button return None, None, None
def convert_step_file(step_filename, target_folder, remove_original=True): r"""Convert a STEP file (.step, .stp) for web display Parameters ---------- step_filename : str Full path to the STEP file target_folder : str Full path to the target folder for the conversion remove_original : bool Should the input file be deleted after conversion? It should be deleted on a web platform to save disk space, but, for testing, it might be useful not to delete it. Returns ------- Nothing, it is a procedure """ if not isdir(target_folder): mkdir(target_folder) converted_basenames = [] importer = StepImporter(step_filename) shapes = importer.shapes max_dim = BoundingBox(importer.compound).max_dimension for i, shape in enumerate(shapes): converted_filename = _conversion_filename(step_filename, target_folder, i) _convert_shape(shape, converted_filename) converted_basenames.append(basename(converted_filename)) _write_descriptor( max_dim, converted_basenames, _descriptor_filename(target_folder, basename(step_filename))) if remove_original is True: remove(step_filename)
from OCC.Display.SimpleGui import init_display from aocutils.display.topology import solids, edges from aocutils.display.defaults import backend from aocxchange.step import StepImporter from corelib.core.files import path_from_file logging.basicConfig(level=logging.DEBUG, format='%(asctime)s :: %(levelname)6s :: %(module)20s :: ' '%(lineno)3d :: %(message)s') backend = backend display, start_display, add_menu, add_function_to_menu = init_display(backend) filename = path_from_file(__file__, "./models_in/step/aube_pleine.stp") step_importer = StepImporter(filename) # step_importer.read_file() -> automatic read_file !! print("Nb shapes: %i" % len(step_importer.shapes)) for shape in step_importer.shapes: print(shape.ShapeType()) # 2 -> solid # print("number_of_shapes: %i" % step_importer.number_of_shapes) # 0 ?? # display.DisplayShape(step_importer.shapes) solids(display, step_importer.shapes[0], transparency=0.8) edges(display, step_importer.shapes[0]) display.FitAll() display.View_Iso() start_display()
def on_selected_change(self, change): """Callback function for listener""" # TODO: investigate why importing Part # at top of file causes an app crash from cadracks_core.model import Part logger.debug("Selection changed") sel = self.model.selected logger.debug("sel : %s" % sel) if not isdir(sel): # what extension ? ext = splitext(sel)[1].lower() logger.info("File extension : %s" % ext) if ext == ".py": with open(sel) as f: content = f.read() if is_valid_python(content) is True: busy_info = wx.BusyInfo( "Loading Python defined geometry ...") try: module_ = imp.load_source(sel, sel) has_shape = hasattr(module_, "__shape__") has_shapes = hasattr(module_, "__shapes__") has_anchors = hasattr(module_, "__anchors__") has_properties = hasattr(module_, "__properties__") has_assembly = hasattr(module_, "__assembly__") has_assemblies = hasattr(module_, "__assemblies__") self.erase_all() if has_shapes is True: logger.info("%s has shapeS" % sel) for i, shape in enumerate(module_.__shapes__): self.display_shape(shape, color_=colour_wx_to_occ( color_from_sequence( i, "colors"))) elif has_assembly is True: logger.info("%s has assembly" % sel) try: self.display_assembly(module_.__assembly__) self.viewer_display.FitAll() except KeyError as ke: self.erase_all() logger.exception(ke) elif has_assemblies is True: logger.info("%s has assemblies" % sel) try: self.display_assemblies(module_.__assemblies__) self.viewer_display.FitAll() except KeyError as ke: self.erase_all() logger.exception(ke) # elif has_shape is True and has_anchors is True: elif has_shape is True: if has_anchors is False: logger.info("%s has shape" % sel) self.display_shape(module_.__shape__) else: logger.info("%s has shape and anchors" % sel) p = anchorable_part_from_py_script(sel) self.display_part(p) self.viewer_display.FitAll() else: self.erase_all() logger.warning("Nothing to display") except Exception as e: logger.error(str(e)) finally: del busy_info else: # the file is not a valid Python file logger.warning("Not a valid python file") self.erase_all() elif ext in [".step", ".stp"]: self.erase_all() busy_info = wx.BusyInfo("Loading STEP ...") try: shapes = StepImporter(sel).shapes logger.info("%i shapes in %s" % (len(shapes), sel)) for shape in shapes: color_255 = (255, 255, 255) self.display_shape(shape, color_=colour_wx_to_occ(color_255), transparency=0.1) self.viewer_display.FitAll() except Exception as e: logger.error(str(e)) finally: del busy_info elif ext in [".iges", ".igs"]: self.erase_all() busy_info = wx.BusyInfo("Loading IGES ...") try: shapes = IgesImporter(sel).shapes logger.info("%i shapes in %s" % (len(shapes), sel)) for shape in shapes: color_255 = (51, 255, 255) self.display_shape(shape, color_=colour_wx_to_occ(color_255), transparency=0.1) self.viewer_display.FitAll() except Exception as e: logger.error(str(e)) finally: del busy_info elif ext == ".stl": self.erase_all() busy_info = wx.BusyInfo("Loading STL ...") try: shape = StlImporter(sel).shape color_255 = (0, 255, 0) self.display_shape(shape, color_=colour_wx_to_occ(color_255), transparency=0.1) self.viewer_display.FitAll() except Exception as e: logger.error(str(e)) finally: del busy_info elif ext == ".json": # parts library self.erase_all() is_library_file = False with open(sel) as json_file: if "metadata" in json_file.read(): is_library_file = True if is_library_file is True: busy_info = wx.BusyInfo("Loading parts library ...") try: with open(sel) as json_file: json_file_content = json.load(json_file) # print(json_file_content["data"].keys()) # find the biggest bounding box biggest_bb = [0, 0, 0] # smallest_bb = [0, 0, 0] for i, k in enumerate( json_file_content["data"].keys()): library_part = anchorable_part_from_library( sel, k) bb = BoundingBox(library_part.shape) if bb.x_span > biggest_bb[0]: biggest_bb[0] = bb.x_span # if bb.x_span < smallest_bb[0]: # smallest_bb[0] = bb.x_span if bb.y_span > biggest_bb[1]: biggest_bb[1] = bb.y_span # if bb.y_span < smallest_bb[1]: # smallest_bb[1] = bb.y_span if bb.z_span > biggest_bb[2]: biggest_bb[2] = bb.z_span # if bb.z_span < smallest_bb[2]: # smallest_bb[2] = bb.y_span biggest_dimension = max(biggest_bb) # smallest_dimension = min(smallest_bb) nb_per_row = int( math.sqrt(len( json_file_content["data"].keys()))) for i, k in enumerate( json_file_content["data"].keys()): library_part = anchorable_part_from_library( sel, k) x_pos = biggest_dimension * 2 * (i % nb_per_row) y_pos = biggest_dimension * 2 * (i // nb_per_row) # Translate using an AnchorTransformation library_part.add_matrix_generator( AnchorTransformation( Anchor(p=(0, 0, 0), u=(1, 0, 0), v=(0, 1, 0), name='a'), Anchor(p=(x_pos, y_pos, 0), u=(1, 0, 0), v=(0, 1, 0), name='b'))) self.display_part(library_part, transparency=0.2, anchor_names_height=10) pnt_message = gp_Pnt( x_pos + biggest_dimension / 5, y_pos + biggest_dimension / 5, 0) self.display_message(pnt_message, k, message_color=(1, 1, 1), height=20) self.viewer_display.FitAll() except Exception as e: logger.error(str(e)) finally: del busy_info elif ext == ".stepzip": self.erase_all() busy_info = wx.BusyInfo("Loading STEPZIP ...") try: self.display_part(anchorable_part_from_stepzip(sel), transparency=0.2) self.viewer_display.FitAll() except Exception as e: logger.error(str(e)) finally: del busy_info else: logger.error("File has an extension %s that is not " "handled by the 3D panel" % ext) self.erase_all() else: # a directory is selected self.erase_all() self.Layout() logger.debug("code change detected in 3D panel")
def test_step_importer_wrong_file_content(): r"""wrong file content""" with pytest.raises(StepFileReadException): StepImporter(path_from_file(__file__, "./models_in/empty.stp"))
def test_step_importer_wrong_extension(): r"""wrong file format (i.e. trying to read a iges file with step importer)""" with pytest.raises(IncompatibleFileFormatException): filename = path_from_file(__file__, "./models_in/aube_pleine.iges") StepImporter(filename)
def test_step_importer_wrong_path(): r"""Wrong filename""" with pytest.raises(FileNotFoundError): StepImporter("C:/stupid-filename.bad_extension")
def import_step(): """ Imports a STEP file. """ my_importer = StepImporter("./models_in/step/box_203.stp") print(my_importer.shapes)
def step_to_stl(step_file_path, stl_file_path, scale=1., factor=4000., use_min_dim=False, ascii_mode=True, multi_shape_mode=ONE_SHAPE_PER_FILE): r"""Convert a STEP file to a STL file Parameters ---------- step_file_path : str Path to the input STEP file stl_file_path : str Path to the output STL file factor : float Meshing factor, optional (default is 4000.) The higher, the finer the mesh and the bigger the resulting file use_min_dim : bool, optional (default is False) Use the minimum dimension of the shape as a base for meshing This is useful for shapes with a high aspect ratio ascii_mode : bool, optional (default is True) Write STL in ascii mode if True, in binary mode if False multi_shape_mode : int, optional (default is 0 (ONE_SHAPE_PER_FILE) Mode to use in case there is more than 1 shape in the STEP file Returns ------- List of STL files creates, total number of shapes If there is only 1 file in the liste and more than 1 shape, it is a STL with multiple entities """ def shape_to_stl(shape_, stl_file_, scale, ascii_mode_, factor_, use_min_dim_): r"""Write a single shape to an STL file Parameters ---------- shape_ stl_file_ ascii_mode_ factor_ use_min_dim_ """ exporter = StlExporter(filename=stl_file_, ascii_mode=ascii_mode_) shape_ = scale_uniform(shape_, (0, 0, 0), scale, False) # Must mesh ! Otherwise the exporter does not write anything! mesh(shape_, factor=factor_, use_min_dim=use_min_dim_) exporter.set_shape(shape_) exporter.write_file() # Read STEP shapes = StepImporter(filename=step_file_path).shapes if len(shapes) == 0: msg = "The STEP file contains no shape" logger.error(msg) raise ValueError(msg) elif len(shapes) == 1: # 99.9 % of practical cases shape_to_stl(shapes[0], stl_file_path, scale, ascii_mode, factor, use_min_dim) # assert isfile(stl_file_path) if not isfile(stl_file_path): msg = "%s was not created" % stl_file_path logger.error(msg) raise ValueError(msg) return [stl_file_path], 1 elif len(shapes) > 1: if multi_shape_mode == ONE_SHAPE_PER_FILE: f, e = os.path.splitext(stl_file_path) partial_files = list() for i, shape in enumerate(shapes): filename = f + "_" + str(i) + e shape_to_stl(shape, filename, scale, ascii_mode, factor, use_min_dim) partial_files.append(filename) return partial_files, len(shapes) elif multi_shape_mode == ALL_SHAPES_IN_ONE_FILE: partial_files = list for i, shape in enumerate(shapes): filename = f + "_" + str(i) + e shape_to_stl(shape, filename, scale, ascii_mode, factor, use_min_dim) partial_files.append(filename) with open(stl_file_path, "w") as stl_file: for partial_file in partial_files: with open(partial_file) as pf: content = pf.readlines() stl_file.writelines(content) for partial_file in partial_files: os.remove(partial_file) return [stl_file_path], len(shapes) else: msg = "Unknown mode for multi shape STEP conversion to STL" raise ValueError(msg)