"""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