"""This module stores the Part class. It is used to make 2D parts.
"""
import numpy as np # needed for linspace on hole creation
from . import base_classes
from . import geometry #point, line, area
[docs]class Part(base_classes.Idobj):
"""This makes a part.
Args:
parent: parent FeaModel
Attributes:
__fea (FeaModel): parent FeaModel
points (list): list or part points, excludes arc centers
allpoints (list): list or part points, includes arc centers
lines (list): list of all Line and Arc that make the part
signlines (list): list of all SignLine and SignArc that make the part
__cursor (Point): location of a cursor drawing the part
__holemode (bool): if True, lines will be added to holes, otherwise,
they'll be added to areas
areas (list): list of Area that make up the part
left (list): list the parts leftmost lines, they must be vertical
right (list): list the parts rightmost lines, they must be vertical
top (list): list the parts top lines, they must be horizontal
bottom (list): list the parts bottom lines, they must be horizontal
center (Point): the area centroid of the part
nodes (list): list of part's nodes
elements (list): list of part's elements
"""
def __init__(self, feamodel):
self.fea = feamodel
self.__cursor = geometry.Point(0, 0)
self.areas = [] # top area is the buffer
# make the buffer
area = self.fea.areas.append(geometry.Area(self, []))
self.areas.append(area)
base_classes.Idobj.__init__(self)
self.left = None
self.right = None
self.top = None
self.bottom = None
self.center = None
self.nodes = []
self.elements = []
self.__holemode = False
self.fea.parts.append(self)
def __hash__(self):
"""Returns the item's id as its hash."""
return self.id
@property
def lines(self):
"""Returns list of part lines."""
lines = set()
for area in self.areas:
lines.update(area.lines)
return list(lines)
@property
def signlines(self):
"""Returns list of part signline and signarc."""
lines = set()
for area in self.areas:
lines.update(area.signlines)
return list(lines)
@property
def points(self):
"""Returns list of part points, excludes arc centers."""
points = set()
lines = self.lines
for line in lines:
points.update(line.points)
return list(points)
@property
def allpoints(self):
"""Returns list of part points, includes arc centers."""
points = set()
lines = self.lines
for line in lines:
points.update(line.allpoints)
return list(points)
[docs] def get_item(self, item):
""""Returns the part's item(s) requested by the passed string.
Args:
item (str): string requesting item(s)
* Valid examples: 'P0', 'L0', 'left', 'A0'
Returns:
item(s) or None: If items are found they are returned
* If there is only one item it is returned
* If there are multiple items, they are returned as a list
* If no items are found None is returned
"""
if item in ['left', 'right', 'top', 'bottom']:
items = getattr(self, item)
return items
elif item[0] == 'P':
# get point
items = self.points
num = int(item[1:])
res = [a for a in items if a.id == num]
return res[0]
elif item[0] == 'L':
# get line
items = self.signlines
num = int(item[1:])
items = [a for a in items if a.id == num]
return items[0]
elif item[0] == 'A':
# get area
items = self.areas
num = int(item[1:])
items = [a for a in items if a.id == num]
return items[0]
else:
print('Unknown item! Please pass the name of a point, line or area!')
return None
[docs] def get_name(self):
"""Returns the part name based on id number."""
return 'PART'+str(self.id)
def __set_side(self, side):
"""Sets the part.side to a list of lines on that side of the part.
Used to set the part.left, part.right, part.top, part.bottom sides.
Args:
side (string): 'left', 'right', 'top','bottom'
"""
# set index and axis, ind=0 is low side, ind=-1 is high side
inds = {'left':0, 'right':-1, 'top':-1, 'bottom':0}
axes = {'left':'y', 'right':'y', 'top':'x', 'bottom':'x'}
ind = inds[side]
axis = axes[side]
# loc = 'left', ind = 0, axis = 'y'
points = self.points
# sort the points low to high
points = sorted(points, key=lambda pt: getattr(pt, axis))
# store the target value
target_value = getattr(points[ind], axis)
res = []
lines = self.signlines
for sline in lines:
if isinstance(sline, geometry.SignLine):
pt_axis_vals = [getattr(pt, axis) for pt in sline.points]
pt_dist_vals = [abs(target_value - pt_axis_val) for pt_axis_val in pt_axis_vals]
if all([pt_dist_val < geometry.ACC for pt_dist_val in pt_dist_vals]):
# line is on the left side
res.append(sline)
setattr(self, side, res)
[docs] def goto(self, x, y, holemode=False):
"""Moves the part cursor to a location.
If that location has a point at it, use it.
If not, make a new point at that location.
Args:
x (float): x-coordinate of the point to go to
y (float): y-coordinate of the point to go to
holemode (bool): if True, we start drawing a hole here, otherwise
we start drawing an area
Returns:
self.__cursor (Point): returns the updated cursor point
"""
[pnew, already_exists] = self.__make_get_pt(x, y)
if already_exists:
if self.areas[-1].closed == True:
# make a new area if the old area is already closed and we're
# going to an existing point
area = self.fea.areas.append(geometry.Area(self, []))
self.areas.append(area)
# return cursor
self.__cursor = pnew
self.__holemode = holemode
return self.__cursor
def __get_point(self, point):
"""Returns point if found, None otherwise."""
points = self.allpoints
found_point = None
for apoint in points:
dist = point - apoint
dist = dist.length()
if dist < geometry.ACC:
# point already exists in part, use it
found_point = apoint
break
return found_point
def __make_get_pt(self, x, y):
"""Gets a point if it exists, makes it if it doesn't. Returns the point.
Use this when you need a point made in the part, and you want to
use an extant point if one is available.
Args:
x (float): point x-coordinate
y (float): point y-coordinate
Returns:
list:
list[0]: Point
list[1]: boolean, True = the point already existed
"""
thept = geometry.Point(x, y)
pfound = self.__get_point(thept)
pexists = True
if pfound == None:
pfound = thept
self.fea.register(pfound)
pexists = False
return [pfound, pexists]
def __calc_area_center(self):
"""Calculates and returns the part and centroid Point.
Returns:
list: [area, Point]
"""
val_list = []
for area in self.areas:
if area.closed == True:
aval, cval = area.area, area.center
val_list.append([aval, cval])
a_sum = sum([aval[0] for aval in val_list])
cxa_sum = sum([center.x*aval for [aval, center] in val_list])
cya_sum = sum([center.y*aval for [aval, center] in val_list])
cx_val = cxa_sum/a_sum
cy_val = cya_sum/a_sum
center = geometry.Point(cx_val, cy_val)
return [a_sum, center]
def __make_get_sline(self, lnew):
"""Returns a signed line or arc, makes it if it needs to.
Args:
lnew (Line or Arc or SignLine or SignArc): Line or Arc to make
Returns:
list:
list[0]: SignLine or SignArc
list[1]: boolean, True = the line already existed
"""
lpos = lnew.signed_copy(1)
lneg = lnew.signed_copy(-1)
# get part's signed lines
slines = self.signlines
lexists = False
for sline in slines:
if lpos == sline:
lexists = True
signline_new = sline
break
elif lneg == sline:
lexists = True
signline_new = sline.signed_copy(-1)
signline_new.edge = False
self.fea.register(signline_new)
break
else:
# fired when we haven't broken out of the loop, the line is new
if isinstance(lnew, geometry.SignLine):
lnew = geometry.Line(lnew.pt(0), lnew.pt(1))
self.fea.register(lnew)
lnew.save_to_points()
signline_new = lnew.signed_copy(1)
self.fea.register(signline_new)
signline_new.line.add_signline(signline_new)
return [signline_new, lexists]
[docs] def draw_circle(self, center_x, center_y, radius, num_arcs=4):
"""Draws a circle area and adds it to the part.
Args:
center_x (float): x-axis hole center
center_y (float): y-axis hole center
radius (float): hole radius
num_arcs (int): number of arcs to use, must be >= 3
Returns:
loop (geometry.LineLoop): a LineLoop list of SignArc
"""
center = geometry.Point(center_x, center_y)
rvect = geometry.Point(0, radius)
start = center + rvect
self.goto(start.x, start.y)
angles = np.linspace(360/num_arcs,360,num_arcs, endpoint=True)
for ang in angles:
point = geometry.Point(0, radius).rot_ccw_deg(ang)
point = point + center
self.draw_arc(point.x, point.y, center.x, center.y)
loop = self.areas[-1].exlines
self.__update()
return loop
[docs] def draw_hole(self, center_x, center_y, radius, num_arcs=4, filled=False):
"""Makes a hole in the part.
Args:
center_x (float): x-axis hole center
center_y (float): y-axis hole center
radius (float): hole radius
num_arcs (int): number of arcs to use, must be >= 3
filled (bool): whether to fill the hole
* True: makes a new area in the part
Returns:
hole_lines (list or None): list of hole SignLine or SignArc
* Returns None if hole was not made.
"""
center = geometry.Point(center_x, center_y)
area = self.__area_from_pt(center)
if area == None:
print("You can't make a hole here until there's an area here!")
return None
else:
# make points
rvect = geometry.Point(0, radius)
start = center + rvect
self.goto(start.x, start.y, holemode=True)
angles = np.linspace(360/num_arcs,360,num_arcs, endpoint=True)
for ang in angles:
point = geometry.Point(0, radius).rot_ccw_deg(ang)
point = point + center
self.draw_arc(point.x, point.y, center.x, center.y)
# make new area
if filled:
# reverse order, reverse sline directions, store in feamodel
slines = list(area.holes[-1])
slines.reverse()
slines = [sline.signed_copy(-1) for sline in slines]
slines = [self.__make_get_sline(sline)[0] for sline in slines]
anew = self.fea.areas.append(geometry.Area(self, slines))
self.areas.append(anew)
self.__update()
return area.holes[-1]
[docs] def draw_arc_angle(self, degrees_ccw, center_x, center_y):
"""Makes an arc and adds it to the part.
| Current point is the first arc point.
| degrees_ccw is the swept angle in degrees, counterclockwise
| (center_x, center_y) is the arc center
| Degrees: Traversed angle of arc must be < 180 degrees
Args:
degrees_ccw (float): arc swept angle in degrees, counterclockwise
center_x (float): arc center point x-coordinate
center_y (float): arc center point y-coordinate
Returns:
list: [arc, arc_start_point, arc_end_point]
"""
center = geometry.Point(center_x, center_y)
radius_vector = self.__cursor - center
radius_vector.rot_ccw_deg(degrees_ccw)
end = center + radius_vector
return self.draw_arc(end.x, end.y, center_x, center_y)
[docs] def draw_arc(self, end_x, end_y, center_x, center_y):
"""Makes an arc and adds it to the part.
| Current point is the first arc point.
| (end_x, end_y) is the end point
| (center_x, center_y) is the arc center
| Degrees: Traversed angle of arc must be < 180 degrees
| Radians: Traversed angle of arc must be < Pi
Args:
end_x (float): arc end point x-coordinate
end_y (float): arc end point y-coordinate
center_x (float): arc center point x-coordinate
center_y (float): arc center point y-coordinate
Returns:
list: [arc, arc_start_point, arc_end_point]
"""
pold = self.__cursor
# make arc center point
ctr = self.__make_get_pt(center_x, center_y)[0]
# make arc end point
self.__cursor = self.__make_get_pt(end_x, end_y)[0]
# make arc
arc = self.__make_get_sline(geometry.Arc(pold, self.__cursor, ctr))[0]
if self.__holemode:
area = self.__area_from_pt(self.__cursor)
if area != None:
closed = area.add_hole_sline(arc)
if closed:
self.__holemode = False
else:
print('You must have a closed area here before making a hole!')
else:
self.areas[-1].add_sline(arc)
return [arc, pold, self.__cursor]
[docs] def draw_line_delta(self, delta_x, delta_y):
"""Draws a line a relative distance, and adds it to the part.
Args:
delta_x (float): x-axis delta distance to draw the line
delta_y (float): y-axis delta distance to draw the line
Returns:
list: [line, point_start, point_end]
"""
x = self.__cursor.x + delta_x
y = self.__cursor.y + delta_y
return self.draw_line_to(x, y)
[docs] def draw_line_rad(self, dx_rad):
"""Draws a line a relative radial distance, and adds it to the part.
Args:
dx_rad (float): x-axis delta distance to draw the line
Returns:
list: [line, point_start, point_end]
"""
return self.draw_line_delta(dx_rad, 0.0)
[docs] def draw_line_ax(self, dy_ax):
"""Draws a line a relative axial distance, and adds it to the part.
Args:
dy_ax (float): y-axis delta distance to draw the line
Returns:
list: [line, point_start, point_end]
"""
return self.draw_line_delta(0.0, dy_ax)
[docs] def draw_line_to(self, x, y):
"""Draws a line to the given location, and adds it to the part.
Args:
x (float): x-axis coordinate of the end point
y (float): y-axis coordinate of the end point
Returns:
list: [SignLine, point_start, point_end]
"""
pold = self.__cursor
self.__cursor = self.__make_get_pt(x, y)[0]
sline = self.__make_get_sline(geometry.Line(pold, self.__cursor))[0]
if self.__holemode:
area = self.__area_from_pt(self.__cursor)
if area != None:
closed = area.add_hole_sline(sline)
if closed:
self.__holemode = False
self.__update()
else:
print('You must have a closed area here before making a hole!')
else:
# drawing in last area
self.areas[-1].add_sline(sline)
# check for closure of the area
if self.areas[-1].closed:
self.__update()
return [sline, pold, self.__cursor]
def __get_maxlength(self):
"""Returns the max distance between points in the part."""
points = self.points
maxlen = 0.0
# loop through points checking dist to next point
for ind, point_1 in enumerate(points[:-1]):
for point_2 in points[ind:]:
vect = point_1 - point_2
dist = vect.length()
if dist > maxlen:
maxlen = dist
return maxlen
def __area_from_pt(self, point):
"""Returns the area that the point is inside.
Args:
point (Point): the point we are asking about
Returns:
Area or None:
Area is the found area
None is returned if the point is not in one of this part's areas
"""
for area in self.areas:
if area.contains_point(point):
return area
return None
[docs] def fillet_lines(self, line1, line2, radius):
"""Fillets the given lines in the part.
This inserts an arc in the part tangent to the two given lines.
Args:
line1 (SignLine): line that the arc starts on, arc is tangent
line2 (SignLine): line that the arc ends on, arc is tangent
radius (float): arc radius size
Returns:
list: [arc, start_point, end_point]
"""
# check if the lines are touching
if not line1.line.touches(line2.line):
print('ERROR: Cannot fillet! Lines must touch!')
return
if line1.line.pt(1) == line2.line.pt(0):
first_line = line1
second_line = line2
elif line2.line.pt(1) == line1.line.pt(0):
first_line = line2
second_line = line1
else:
print('ERROR: Sign lines must both be going in CW or CCW '
'direction. The two passed lines are going in '
'different directions. Unable to fillet them.')
return
tmp = self.__cursor
# offset the lines, assuming area is being traced clockwise
# get the intersection point
magnitude = radius
l1_off = first_line.offset(magnitude)
l2_off = second_line.offset(magnitude)
ctrpt = l1_off.intersects(l2_off)
if ctrpt == None:
# flip the offset direction if lines don't intersect
magnitude = -radius
l1_off = first_line.offset(magnitude)
l2_off = second_line.offset(magnitude)
ctrpt = l1_off.intersects(l2_off)
# now we have an intersecting point
p1_new = first_line.arc_tang_intersection(ctrpt, magnitude)
p2_new = second_line.arc_tang_intersection(ctrpt, magnitude)
rempt = first_line.pt(1)
p1_new = self.__make_get_pt(p1_new.x, p1_new.y)[0]
ctrpt = self.__make_get_pt(ctrpt.x, ctrpt.y)[0]
p2_new = self.__make_get_pt(p2_new.x, p2_new.y)[0]
# make the new arc
arc = self.__make_get_sline(geometry.Arc(p1_new, p2_new, ctrpt))[0]
# put the arc in the right location in the area
area = first_line.lineloop.parent
area.line_insert(first_line, arc)
print('Arc inserted into area %i' % (area.id))
# edit the adjacent lines to replace the removed pt
first_line.set_pt(1, arc.pt(0))
second_line.set_pt(0, arc.pt(1))
# del old pt, store new points for the arc
self.fea.points.remove(rempt)
# reset the cursor to where it should be
self.__cursor = tmp
return [arc, arc.pt(0), arc.pt(1)]
[docs] def fillet_all(self, radius):
"""Fillets all external lines not within 10 degrees of tangency
Args:
radius (float): the fillet radius to use
Returns:
arcs (list): list of SignArc
"""
pairs = []
for area in self.areas:
for ind, sline in enumerate(area.exlines):
prev_sline = area.exlines[ind-1]
this_point = sline.pt(0)
if len(this_point.lines) == 2:
# only fillet lines that are not shared by other areas
if (isinstance(sline, geometry.SignLine)
and isinstance(prev_sline, geometry.SignLine)):
# only fillet lines
perp1 = prev_sline.get_perp_vec(this_point)
perp2 = sline.get_perp_vec(this_point)
ang = perp1.ang_bet_deg(perp2)
is_tangent = (-10 <= ang <= 10)
if is_tangent == False:
pairs.append([prev_sline, sline])
arcs = []
for pair in pairs:
arc = self.fillet_lines(pair[0], pair[1], radius)[0]
arcs.append(arc)
return arcs
[docs] def label(self, axis):
"""Labels the part on a Matplotlib axis
Args:
axis (Matplotlib Axis): Matplotlib Axis
"""
axis.text(self.center.y, self.center.x, self.get_name(),
ha='center', va='center')
[docs] def plot(self, axis, label=True, color='yellow'):
"""Plots the part on the passed Matplotlib axis.
Args:
axis (Matplotlib axis): the axis we will plot on
label (bool): True displays the part label
color (tuple): r,g,b,a matplotlib color tuple
"""
patches = []
for area in self.areas:
if area.closed:
patches.append(area.get_patch())
for patch in patches:
patch.set_color(color)
axis.add_patch(patch)
# apply the label
if label:
self.label(axis)
def __cut_line(self, point, line):
"""Cuts the passed line at the passed point.
The passed line is cut into two lines. All areas that included the
original line are updated.
Args:
line (Line or Arc): the line to cut, must be Line or Arc
point (Point): the location on the line that we will cut it
Returns:
list: [pnew, lnew]
pnew: the new point we created to cut the original line
lnew: the new line we created, the end half of the orignal line
"""
pnew = self.__make_get_pt(point.x, point.y)[0]
if point.id != -1:
# if passed point already exists, use it
pnew = point
pend = line.pt(1)
line.set_pt(1, pnew) # shortens the line
new_prim = geometry.Line(pnew, pend)
if isinstance(line, geometry.Arc):
new_prim = geometry.Arc(pnew, pend, line.actr)
new_sline = self.__make_get_sline(new_prim)[0]
# insert the new line into existing areas
is_line = isinstance(line, geometry.Line) or isinstance(line, geometry.Arc)
is_sline = isinstance(line, geometry.SignLine) or isinstance(line, geometry.SignArc)
print('Cutting line (is_line, is_sline, signlines) (%s, %s, %i)' % (is_line, is_sline, len(line.signlines)))
slines = line.signlines
for sline in slines:
area = sline.lineloop.parent
if sline.sign == 1:
# cutting line in clockwise area, where line is pos
area.line_insert(sline, new_sline)
elif sline.sign == -1:
# cutting line in clockwise area, where line is neg
rev_sline = self.__make_get_sline(new_sline.signed_copy(-1))[0]
area.line_insert(sline, rev_sline, after=False)
return [pnew, new_sline]
def __cut_area(self, area, start_pt, end_pt):
"""Cuts the part area from start_pt to end_pt."""
# select the line portions that define the areas
# list[:low] excludes low index
# list [high:] includes high index
# we want the line which start with the point
lpre_start = area.line_from_startpt(start_pt)
lpre_end = area.line_from_startpt(end_pt)
if lpre_start == None or lpre_end == None:
self.fea.plot_geometry()
print(area.exlines)
istart = area.exlines.index(lpre_start)
iend = area.exlines.index(lpre_end)
low = min(istart, iend)
high = max(istart, iend)
# lists of lines for areas
beg = area.exlines[:low]
mid = area.exlines[low:high]
end = area.exlines[high:]
# make cut line for [beg + cut + end] area
start_pt = mid[0].pt(0)
end_pt = mid[-1].pt(1)
fwd = geometry.Line(start_pt, end_pt)
rev = geometry.Line(end_pt, start_pt)
# update existing area
cline = self.__make_get_sline(fwd)[0]
alist_curr = beg + [cline] + end
area.update(alist_curr)
# make new area
cline_rev = self.__make_get_sline(rev)[0]
alist_other = mid + [cline_rev]
anew = geometry.Area(self, alist_other)
self.fea.register(anew)
self.areas.append(anew)
# fix holes
self.__store_holes()
def __merge_hole(self, area, start_pt, end_pt):
"""Merges the hole into its area with a line between passed points."""
# line will be drawn from start point on exlines to end point on hole
hole_points = area.holepoints
if start_pt in hole_points:
tmp = start_pt
start_pt = end_pt
end_pt = tmp
lpre_start = area.line_from_startpt(start_pt)
hole_line = area.line_from_startpt(end_pt)
if lpre_start == None or hole_line == None:
self.fea.plot_geometry()
ind = area.exlines.index(lpre_start)
# store sections of the area
beg = area.exlines[:ind]
end = area.exlines[ind:]
thehole = None
mid = []
for hole in area.holes:
for sline in hole:
if sline == hole_line:
ind = hole.index(sline)
mid = hole[ind:] + hole[:ind]
thehole = hole
break
if mid != []:
break
fwd = geometry.Line(start_pt, end_pt)
fwd_sline = self.__make_get_sline(fwd)[0]
rev_sline = fwd_sline.signed_copy(-1)
self.fea.register(rev_sline)
rev_sline.line.add_signline(rev_sline)
alist_curr = beg + [fwd_sline] + mid + [rev_sline] + end
area.holes.remove(thehole)
area.update(alist_curr)
def __get_cut_line(self, cutline):
"""Returns a cut line beginning and ending on the part."""
# find all intersections
lines = self.lines
points = set()
# add line intersections
for line in lines:
newpt = line.intersects(cutline)
if newpt != None:
points.add(newpt)
# loop through intersection points, storing distance
points = list(points)
for (ind, point) in enumerate(points):
dist = point - cutline.pt(0)
dist = dist.length()
pdict = {'dist': dist, 'point': point}
points[ind] = pdict
# sort the points by dist, lowest to highest, return first cut
points = sorted(points, key=lambda k: k['dist'])
start = points[0]['point']
end = points[1]['point']
new_cut = geometry.Line(start, end)
return new_cut
def __cut_with_line(self, cutline, debug):
"""Cuts the part using the passed line.
Args:
cutline (Line): line to cut the area with
debug (list): bool for printing, bool for plotting after every cut
"""
# find all intersections
lines = self.lines
points = set()
# add line intersections
for line in lines:
if debug[0]:
print('Checking X between %s and cutline' % line.get_name())
newpt = line.intersects(cutline)
if debug[0]:
print(' Intersection: %s' % newpt)
if newpt != None:
points.add(newpt)
# loop through intersection points, storing distance and lines to cut
points = list(points)
for (ind, point) in enumerate(points):
dist = point - cutline.pt(0)
dist = dist.length()
pdict = {'dist': dist}
realpt = self.__get_point(point)
# we only want to store lines to cut here
if realpt == None or realpt.arc_center == True:
# we could have an existing arc center on a line that needs to
# be cut
if realpt == None:
realpt = point
for line in lines:
point_on_line = line.coincident(realpt)
if point_on_line and point not in line.points:
pdict['line'] = line
break
pdict['point'] = realpt
points[ind] = pdict
# sort the points by dist, lowest to highest
points = sorted(points, key=lambda k: k['dist'])
if debug[0]:
print('==================================')
print('Points on the cutline!------------')
for pdict in points:
print(pdict['point'])
print(' dist %.3f' % pdict['dist'])
if 'line' in pdict:
print(' X cut line: %s' % pdict['line'])
print('==================================')
# loop through the points cutting areas
for ind in range(len(points)):
pdict = points[ind]
start_pt = pdict['point']
if 'line' in pdict:
# cut the line and point to the real new point
print('Cut through line %s' % pdict['line'].get_name())
pnew = self.__cut_line(start_pt, pdict['line'])[0]
points[ind]['point'] = pnew
start_pt = pnew
end_pt = None
pavg = None
area = None
if ind > 0:
# find the area we're working on
end_pt = points[ind-1]['point']
pavg = start_pt + end_pt
pavg = pavg*0.5
area = self.__area_from_pt(pavg)
if area == None:
# stop cutting if we are trying to cut through a holes
print('No area found at point avg, no cut made')
break
start_hole = start_pt in area.holepoints
end_hole = end_pt in area.holepoints
if start_hole and end_hole and area != None:
print('Trying to join holes, no cut made')
break
# stop cutting if we are trying to join holes
if end_hole == True or start_hole == True:
print('Merging hole in %s' % area.get_name())
self.__merge_hole(area, start_pt, end_pt)
else:
print('Cutting %s' % area.get_name())
self.__cut_area(area, start_pt, end_pt)
if debug[1]:
self.fea.plot_geometry()
def __store_holes(self):
"""Puts all holes in their correct areas"""
holes = []
for area in self.areas:
holes += area.holes
for hole in holes:
hole_area = hole.parent
for area in self.areas:
is_inside = hole.inside(area.exlines)
if is_inside == True:
if area != hole_area:
# delete the hole from the old area, move it to the new
hole.set_parent(area)
hole_area.holes.remove(hole)
hole_area.close()
area.holes.append(hole)
area.close()
afrom, ato = hole_area.get_name(), area.get_name()
print('Hole moved from %s to %s' % (afrom, ato))
def __vect_to_line(self, point, cvect):
"""Returns a cutting line at a given point and cutting vector.
Args:
point (Point): the location we are cutting from
cvect (Point): the vector direction of the cut from pt
Returns:
cutline (Line): cut line
"""
cvect.make_unit()
vsize = self.__get_maxlength()
endpt = point + cvect*vsize
cutline = geometry.Line(point, endpt)
cutline = self.__get_cut_line(cutline)
return cutline
def __chunk_area(self, area, mode, exclude_convex, debug):
"""Cuts the passed area into regular smaller areas.
The cgx mesher only accepts areas which are 3-5 sides
so one may need to call this before using that mesher.
Cuts are made perpendicular to tangent points or at
internal corners.
At internal corners two perpendicular cuts are made.
Args:
area (Area): the area to cut into smaller areas
mode (str): 'both', 'holes' or 'ext' chunks the area using the
points form this set. See part.chunk
exclude_convex (bool): If true exclude cutting convex tangent points
debug (list): bool for printing, bool for plotting after every cut
"""
# store the cuts first, then cut after
cuts = [] # each item is a dict with a pt and vect in it
loops = []
cut_point_sets = []
if mode == 'holes':
loops = area.holes
elif mode == 'ext':
loops = [area.exlines]
elif mode == 'both':
loops = area.holes + [area.exlines]
for loop in loops:
for ind, line in enumerate(loop):
line_pre = loop[ind-1]
line_post = line
point = line_pre.pt(1)
perp1 = line_pre.get_perp_vec(point)
perp2 = line_post.get_perp_vec(point)
#tan1 = line_pre.get_tan_vec(point)
#tan2 = line_post.get_tan_vec(point)
# flip these vectors later to make them cut the area(s)
ang = perp1.ang_bet_deg(perp2)
cut = {}
make_cut = True
pre_arc = isinstance(line_pre, geometry.SignArc)
post_arc = isinstance(line_post, geometry.SignArc)
if pre_arc or post_arc:
if pre_arc and post_arc:
if (line_pre.concavity == 'convex'
and line_post.concavity == 'convex'
and exclude_convex == True):
make_cut = False
else:
# only one is an arc
arc = line_pre
if post_arc:
arc = line_post
if arc.concavity == 'convex' and exclude_convex == True:
make_cut = False
is_tangent = (-10 <= ang <= 10)
is_int_corner = (45 <= ang <= 135)
"""
print('-------------------')
print('%s' % point)
print('Angle is %.3f' % ang)
print('Make cut %s' % make_cut)
print('is_tangent %s' % is_tangent)
print('is_int_corner %s' % is_int_corner)
"""
if is_tangent:
if make_cut == True:
# tangent
cut = {'pt':point, 'vect':perp1*-1}
cut_line = self.__vect_to_line(cut['pt'], cut['vect'])
pset = set(cut_line.points)
if pset not in cut_point_sets:
cut_point_sets.append(pset)
cut['line'] = cut_line
cuts.append(cut)
elif is_int_corner:
# internal corner
cut = {'pt':point, 'vect':perp1*-1}
cut_line = self.__vect_to_line(cut['pt'], cut['vect'])
pset = set(cut_line.points)
if pset not in cut_point_sets:
cut_point_sets.append(pset)
cut['line'] = cut_line
cuts.append(cut)
cut = {'pt':point, 'vect':perp2*-1}
cut_line = self.__vect_to_line(cut['pt'], cut['vect'])
pset = set(cut_line.points)
if pset not in cut_point_sets:
cut_point_sets.append(pset)
cut['line'] = cut_line
cuts.append(cut)
elif ang < 0:
# external corner
# do not split these
pass
# do the cuts
for cut in cuts:
print('--------------------')
print('Cut point:', cut['pt'].get_name())
print('Cut line:', cut['line'])
self.__cut_with_line(cut['line'], debug)
[docs] def chunk(self, mode='both', exclude_convex = True, debug=[0, 0]):
"""Chunks all areas in the part.
Args:
mode (str): area chunking mode
- 'both': cuts areas using holes and exterior points
- 'holes': cut areas using holes points only
- 'ext': cut areas using exterior points only
exclude_convex (bool): If true exclude cutting convex tangent points
"""
for area in self.areas:
if area.closed:
min_sides = 5
has_holes = len(area.holes) > 0
ext_gr = len(area.exlines) > min_sides
both_false = (has_holes == False and ext_gr == False)
if mode == 'holes' and has_holes:
self.__chunk_area(area, mode, exclude_convex, debug)
elif (mode == 'both'
and (has_holes or ext_gr or not exclude_convex)):
self.__chunk_area(area, mode, exclude_convex, debug)
elif mode == 'ext' and (ext_gr or not exclude_convex):
self.__chunk_area(area, mode, exclude_convex, debug)
else:
aname = area.get_name()
val = 'Area %s was not chunked because it had' % aname
adder = ''
if mode == 'both' and both_false:
adder = '<= %i lines and no holes.' % min_sides
elif has_holes == False and (mode in ['both', 'holes']):
adder = 'no holes.'
elif ext_gr == False and (mode in ['both', 'ext']):
adder = '<= %i lines.' % min_sides
print('%s %s' % (val, adder))
# store the left, right, top, and bottom lines
self.__update()
def __update(self):
"""Updates the left, right, top, bottom sides and area and center"""
self.__set_side('left')
self.__set_side('right')
self.__set_side('top')
self.__set_side('bottom')
self.area, self.center = self.__calc_area_center()
def __str__(self):
"""Returns string listing object type, id number and name."""
val = 'Part, id=%i name=%s' % (self.id, self.get_name())
return val