Source code for pycalculix.partmodule

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