Source code for pycalculix.cadimporter

"""This module stores the CadImporter class, which is used to load CAD parts."""

import collections
import math
import os
# needed to prevent dxfgrabber from crashing on import
os.environ['DXFGRABBER_CYTHON'] = 'OFF'
import dxfgrabber # needed for dxf files
import subprocess # needed to run gmsh to make geo files

from . import geometry
from . import partmodule

[docs]class CadImporter(object): """Makes an object which can import cad parts. Args: feamodel (FeaModel): model that we want to import parts into layer (int): layer to import, all entities will be flattened to one plane. -1 mean all layers, any other number is a specific layer swapxy (bool): True rotates the part from x axial to y axial scale (str): string telling the unit conversion scalar * Any option of 'fromunit-tounit' using the below units * mm, m, in, ft * Examples 'mm-m' 'm-in' 'ft-mm' 'in-ft' * Default value is '' and does not apply a scale factor Attributes: __fea (FeaModel): model __fname (str): file we want to import __layer (int): layer to import __swapxy (bool): If true, swap part from xy to yx orientation """ def __init__(self, feamodel, fname='', layer=-1, swapxy=False, scale=''): self.__fea = feamodel self.__fname = fname self.__layer = layer self.__swapxy = swapxy self.__scale = scale
[docs] def load(self): """Loads the self.__fname cad file Returns: list: list of Part """ if self.__fname == '': print('You must pass in a file name to load!') return [] ext = os.path.splitext(self.__fname)[1] first_pt = None if len(self.__fea.points) > 0: first_pt = self.__fea.points[0] if ext == '.dxf': parts = self.__load_dxf() elif ext in ['.brep', '.brp', '.iges', '.igs', '.step', '.stp']: self.__make_geo() parts = self.__load_geo() last_pt = None if first_pt != None: if len(self.__fea.points) > 2: last_pt = self.__fea.points[-1] if self.__scale != '': # call scale pass return parts
def __fix_tuple(self, xy_tup): """Adjusts the point to be in the right plane (yx)""" if self.__swapxy: return xy_tup[::-1] return xy_tup @staticmethod def __find_make_pt(xy_tup, points_dict): """ Returns a point if it exists within geometry.ACC or makes, stores and returns the point if it doesn't exist """ point = points_dict.get(xy_tup) if point is not None: return point xy_point = geometry.Point(xy_tup[0], xy_tup[1]) for realpoint in points_dict.values(): dist = (xy_point - realpoint).length() if dist < geometry.ACC: return realpoint points_dict[xy_tup] = xy_point return xy_point def __get_pts_lines(self, lines, arcs): """Returns a set of points, and a list of Lines and Arcs Args: lines (dxfgrabber LINE list): dxf lines arcs (dxfgrabber ARC list): dxf arcs Returns: list: [list of points, list of Line and Arc] """ # store unique points points_dict = {} all_lines = [] for ind, line in enumerate(lines): tup = self.__fix_tuple((line.start[0], line.start[1])) start = self.__find_make_pt(tup, points_dict) tup = self.__fix_tuple((line.end[0], line.end[1])) end = self.__find_make_pt(tup, points_dict) line = geometry.Line(start, end) all_lines.append(line) for ind, arc in enumerate(arcs): # dxfgrabber arcs are stored ccw when looking at xy plane # x horizontal # y vertical tup = self.__fix_tuple((arc.center[0], arc.center[1])) center = self.__find_make_pt(tup, points_dict) sign = -1 if self.__swapxy: sign = 1 startangle = arc.start_angle*sign endangle = arc.end_angle*sign angle = endangle - startangle if arc.end_angle < arc.start_angle: angle = angle + 360*sign """ print('---------------------------------------') print('| ARC') print('center: %s' % center) print('startangle: %f' % startangle) print('endangle: %f' % endangle) print('traversed_angle: %f' % angle) """ start_vect = geometry.Point(0, arc.radius) if self.__swapxy == False: start_vect = geometry.Point(arc.radius, 0) start_vect.rot_ccw_deg(arc.start_angle*sign) end_vect = geometry.Point(0, arc.radius) if self.__swapxy == False: end_vect = geometry.Point(arc.radius, 0) end_vect.rot_ccw_deg(arc.end_angle*sign) start = center + start_vect start_tup = (start.x, start.y) end = center + end_vect end_tup = (end.x, end.y) start = self.__find_make_pt(start_tup, points_dict) end = self.__find_make_pt(end_tup, points_dict) rvect = start - center if abs(angle) <= 90: arc = geometry.Arc(start, end, center) all_lines.append(arc) print('1 arc made') continue #print(' %s' % arc) pieces = math.ceil(abs(angle)/90) print('%i arcs being made' % pieces) points = [start, end] # 2 pieces need 3 points, we have start + end already --> 1 pt inserts = pieces + 1 - 2 piece_ang = angle/pieces #print('piece_ang = %f' % piece_ang) while inserts > 0: rvect.rot_ccw_deg(piece_ang) point = center + rvect tup = (point.x, point.y) point = self.__find_make_pt(tup, points_dict) points.insert(-1, point) inserts = inserts - 1 for ind in range(len(points)-1): #print(' %s' % arc) arc = geometry.Arc(points[ind], points[ind+1], center) all_lines.append(arc) for line in all_lines: line.save_to_points() return [list(points_dict.values()), all_lines] def __make_geo(self): """Makes a gmsh geo file given a step, iges, or brep input""" # gmsh freecad_part.iges -o out_iges.geo -0 fname_list = self.__fname.split('.') geo_file = fname_list[0]+'.geo' runstr = "%s %s -o %s -0" % (environment.GMSH, self.__fname, geo_file) print(runstr) subprocess.call(runstr, shell=True) print('Wrote file: %s' % geo_file) def __load_geo(self): """Loads in a gmsh geo file and returns a list of parts Returns: list: list of Part """ pass # process any splines? and turn them into arcs # http://www.mathopenref.com/constcirclecenter.html # find max dist between points # double it # select two segments # draw normal lines # find intersections, that is the center @staticmethod def __dangling_points(all_points): return [point for point in all_points if len(point.lines) == 1 and not point.arc_center] def __load_dxf(self): """Loads in a dxf file and returns a list of parts Returns: list: list of Part """ print('Loading file: %s' % self.__fname) dwg = dxfgrabber.readfile(self.__fname) lines = [item for item in dwg.entities if item.dxftype == 'LINE'] arcs = [item for item in dwg.entities if item.dxftype == 'ARC'] if self.__layer > -1: lines = [item for item in lines if item.layer == self.__layer] arcs = [item for item in arcs if item.layer == self.__layer] print('File read.') print('Loaded %i lines' % len(lines)) print('Loaded %i arcs' % len(arcs)) print('Loaded %i line segments, lines or arcs' % (len(lines)+len(arcs))) # get all points and Line and Arc using pycalculix entities print('Converting to pycalculix lines arcs and points ...') all_points, all_lines = self.__get_pts_lines(lines, arcs) print('Loaded %i line segments, lines or arcs' % len(all_lines)) print('Loaded %i points' % len(all_points)) # for point in all_points: # print('%s %s' % (point, point.lines)) # for line in all_lines: # print('%s %s' % (line, line.points)) # remove all lines that are not part of areas dangling_points = self.__dangling_points(all_points) pruned_geometry = bool(dangling_points) while dangling_points: for point in dangling_points: all_points.remove(point) print('Removed point= %s' % point) dangling_line = list(point.lines)[0] point.unset_line(dangling_line) if dangling_line in all_lines: all_lines.remove(dangling_line) print('Removed line= %s' % dangling_line) dangling_points = self.__dangling_points(all_points) if pruned_geometry: print('Remaining line segments: %i' % len(all_lines)) print('Remaining points: %i' % len(all_points)) # make line all_loops now all_loops = [] line = all_lines[0] this_loop = geometry.LineLoop() while len(all_lines) > 0: this_loop.append(line) all_lines.remove(line) if this_loop.closed == True: all_loops.append(this_loop) this_loop = geometry.LineLoop() if all_lines: line = all_lines[0] continue point = line.pt(1) other_lines = point.lines - set([line]) if len(other_lines) > 1: # note: one could exclude connected segment nodes # make disconnected line all_loops, then have another # loop to connect thos disconnected line all_loops print('One point was connected to > 2 lines.') print('Only import simple part all_loops, or surfaces.') raise Exception('Import geometry is too complex') next_line = list(other_lines)[0] if line.pt(1) != next_line.pt(0): next_line.reverse() line = next_line # find exterior loops exterior_loops = [] for ind, loop in enumerate(all_loops): other_loops = all_loops[ind+1:] other_loops.extend(exterior_loops) is_exterior = True for other_loop in other_loops: if loop.inside(other_loop): is_exterior = False break if is_exterior: # exterior must be clockwise if loop.ccw: loop.reverse() exterior_loops.append(loop) # remove the found part exterior loops from all_loops for exterior_loop in exterior_loops: all_loops.remove(exterior_loop) # each part in parts is a list of line all_loops # [exterior, hole1, hole2] parts = [[exterior_loop] for exterior_loop in exterior_loops] # now place the child hole loops after the part exterior loop for part_loops in parts: exterior_loop = part_loops[0] # find child holes for hole_loop in all_loops: if hole_loop.inside(exterior_loop): hole_loop.hole = True # holes must be ccw if not hole_loop.ccw: hole_loop.reverse() part_loops.append(hole_loop) # remove child holes from loop list for hole_loop in part_loops[1:]: all_loops.remove(hole_loop) # make parts parts_list = [] for part_loops in parts: this_part = partmodule.Part(self.__fea) for ind, loop in enumerate(part_loops): is_hole = loop.hole start = loop[0].pt(0) this_part.goto(start.x, start.y, is_hole) for item in loop: if isinstance(item, geometry.Line): end = item.pt(1) this_part.draw_line_to(end.x, end.y) elif isinstance(item, geometry.Arc): end = item.pt(1) center = item.actr this_part.draw_arc(end.x, end.y, center.x, center.y) parts_list.append(this_part) print('Parts created: %i' % len(parts_list)) return parts_list