def change_print(self, prev_player_info, prefix="", incremental=True): """ Print change in player information :prev_player: previous player information :prefix: optional prefix string for printout if present space is added :incremental: changes only else all fields """ if prefix != "": prefix += " " min_id = min(self.get_min_id(), prev_player_info.get_min_id()) max_id = max(self.get_max_id(), prev_player_info.get_max_id()) for pl_id in range(min_id, max_id+1): if self.has_player(pl_id) and prev_player_info.has_player(pl_id): self.player_diff_print(prev_player_info.get_player(pl_id), self.get_player(pl_id), prefix, incremental=incremental) elif self.has_player(pl_id): player = self.get_player(pl_id) SlTrace.lg(f"{prefix} added: {player}") elif prev_player_info.has_player(pl_id): player = prev_player_info.get_player(pl_id) SlTrace.lg(f"{prefix} dropped {player}")
def setCell(self, row=None, col=None, val=None): """ Set cell value :returns: updated cell """ ret = self.vals.setCell(row=row, col=col, val=val) if not self.isValid(): SlTrace.lg(f"setCell: row={row}, col={col}, val={val} not valid") self.display("Invalid arrangement") SlTrace.lg("by rows") for ri in range(self.nRow): nr = ri + 1 SlTrace.lg( f"row:{nr} vals:{self.getRowVals(nr, include_nones=True)}") SlTrace.lg("by columns") for ci in range(self.nCol): nc = ci + 1 SlTrace.lg( f"col:{nc} vals:{self.getColVals(nc, include_nones=True)}") self.display("After listing") raise SelectError("Invalid arrangement") return ret
def mouse_down(self, event): if SlTrace.trace("part_info"): cnv = event.widget x, y = cnv.canvasx(event.x), cnv.canvasy(event.y) parts = self.get_parts_at(x, y) if parts: SlTrace.lg("x=%d y=%d" % (x, y)) for part in parts: SlTrace.lg(" %s\n%s" % (part, part.str_edges())) self.is_down = True if self.inside: SlTrace.lg("Click in canvas event:%s" % event, "motion") cnv = event.widget x, y = cnv.canvasx(event.x), cnv.canvasy(event.y) SlTrace.lg("x=%d y=%d" % (x, y), "down")
def cmd_position_adjust(self, keysym): """ Positioning commands c - center within screen m - move to some place ??? Left - move to left Right - move to right Up - move up Down - move down Movement depends on previous command. 1. If previous heading unchanged then move one side in current direction 2. Else if previous marker is a line,shape or image then set heading and location that the next such object will be flush with the previous objec but have the new heading :keysym: key event keysym """ if (keysym == "Up" or keysym == "Down" or keysym == "Left" or keysym == "Right"): prev_heading = self.get_heading() marker = DmMoveKey(self, keysym=keysym) new_heading = marker.get_heading() if new_heading != prev_heading: marker = DmHeading(self, heading=new_heading) cmd = DrawingCommand(f"cmd_{keysym}") cmd.add_marker(marker) SlTrace.lg(f"cmd={cmd}", "cmd_trace") return cmd.do_cmd() if keysym == 'c': marker = DmPosition(self, x_cor=0, y_cor=0) cmd = DrawingCommand(f"cmd_{keysym}") cmd.add_marker(marker) return cmd.do_cmd() else: raise SelectError(f"Don't understand keysym:{keysym}")
def get_component_next_val(self, name, nrange=50, inc_dir=1, default_value=None): """ Next value for this component :name: control name :nrange: - number of samples for incremental :default_value: default value """ cur_value = self.get_current_val(name, 1) next_direction = self.get_component_val(name, "next", "same") if SlTrace.trace("get_next_val"): SlTrace.lg("get_next_val %s cur_val=%s next=%s" % (name, cur_value, next_direction)) if isinstance(cur_value, str): next_value = self.ctl_list_value(name) SlTrace.lg( "get_value %s str next=%s val=%s" % (name, next_direction, next_value), "get_next_val") return next_value else: min_value = float(self.get_component_val(name, "min", cur_value)) max_value = float(self.get_component_val(name, "max", cur_value)) inc_value = (max_value - min_value) / nrange if next_direction == "ascend": new_value = cur_value + inc_dir * inc_value elif next == "descend": new_value = cur_value - inc_dir * inc_value elif next_direction == "random": new_value = random.randint(min_value, max_value) else: new_value = cur_value + inc_value if new_value < min_value or new_value > max_value: new_value = self.step_end(name, new_value) self.set_current_val(name, new_value)
def move_edge(self, edge, xinc, yinc, adjusts=None): """ Move selected edge, adjusting connected parts Direction of movement is constrained to perpendicular to edge Connected parts are: the corners at each edge end the end-points of the edges, not including this edge, connected to the end-corners :edge: selected edge :xinc: x destination delta :yinc: y destination delta :adjusts: adjusted connections Default: all connections :highlight: True - highlight after move """ self.display_clear(edge) # Clear display before moveing delta_x = xinc delta_y = yinc edge_dx, edge_dy = edge.edge_dxy() if edge_dx == 0: delta_y = 0 # No movement parallel to edge if edge_dy == 0: delta_x = 0 # No movement parallel to edge coord = edge.loc.coord p1, p2 = coord[0], coord[1] SlTrace.lg( "move_edge: %s by delta_x=%d,delta_y=%d" % (edge, delta_x, delta_y), "move_part") """ Collect moves group: Collect all parts which need to be moved in whole or part. Each part is present once. If an edge end is moved only the other edge's ends need to be adjusted """ mover = SelectMover(self, delta_x=delta_x, delta_y=delta_y) mover.add_moves(parts=edge) ###mover.add_moves(parts=edge.connecteds) ###mover.add_adjusts(edge.connecteds) # Adjust those connected to corners and so on mover.move_list(delta_x, delta_y)
def select_copy(obj, levels=4, use_select_copy=True): """ Copy object to allow most manipulations of the copy without affecting the original Checks for member function "select_copy" and if present and if more_copy is True uses it for the copy if present. Iterates at most levels with 1. if member function "select_copy" return 2. if hasattr __dict__ iterate over __dict__.keys() 3. direct copy attribute :levels: maximum number of levels of recursion :use_select_copy: Use select_copy member, if present default: True """ if levels < 1: return obj # Above max level, return self ###if use_select_copy and hasattr(obj, "select_copy"): ### return obj.select_copy(levels=levels-1) global select_copy_depth select_copy_depth += 1 SlTrace.lg( "select_copy depth=%d obj:type(%s) %s" % (select_copy_depth, type(obj), str(obj)), "select_copy") if hasattr(obj, "copy"): if hasattr(obj, "part_check"): obj.part_check(prefix="select_copy") res = obj.copy() else: res = copy.deepcopy(obj) #res = copy.copy(obj) SlTrace.lg("select_copy depth=%d return" % select_copy_depth, "select_copy") select_copy_depth -= 1 return res
def properties_sect_print(self, base_prefix, snapshot=None, sect_name="undo", max_len=None, incremental=True, title=None): """ Print properties sect :base_prefix: section prefix, with or without trailing "." e.g. "player_control" :snapshot: properties snapshot default: current properties :sect_name: section name e.g. "undo" :max_len: print at most this many entries :incremental: just changes from previous entry :title: printed before listing default: sect_name """ if title is None: title = sect_name SlTrace.lg(title) if not base_prefix.endswith("."): base_prefix += "." player_info = self.player_info(base_prefix, snapshot) player_infos = self.player_infos(base_prefix, snapshot, sect_name=sect_name) nprint = 0 for i in range(len(player_infos)): prev_player_info = player_infos[i] if max_len is None or nprint < max_len: player_info.change_print(prev_player_info, prefix=f"{i:2}", incremental=incremental) SlTrace.lg("") nprint += 1 player_info = prev_player_info
def delete_window(self): """ Process Trace Control window close """ self.mw.eval('::ttk::CancelRepeat') self.playing = False # Accept no more activity self.running = False self.run = False SlTrace.lg("delete_window - wait for call backs to die out") self.mw.after(2000) SlTrace.lg("Closing windows") if self.score_win is not None: self.score_win.destroy() self.score_win = None if self.mw is not None: self.mw.destroy() self.mw = None if self.on_exit is not None: self.on_exit() sys.exit() # Else quit
def setup_scores_frame(self): """ Set/Reset players' scores frame """ if self.player_control is None: SlTrace.lg("player control not yet set") return if self.scores_frame is not None: self.scores_frame.pack_forget() self.scores_frame.destroy() self.scores_frame = None self.players = {} # Clear, to add currently playing self.scores_frame = Frame(self.scores_container_frame) self.scores_frame.pack() players = self.player_control.get_players() headings_frame = Frame(self.scores_frame) headings_frame.pack() self.set_field_headings(headings_frame) self.players_frame = Frame(self.scores_frame) self.players_frame.pack() for player in players: self.add_player(player)
def set_play_level(self, play_level=None): """ Set players' level via comma separated string :play_level: comma separated string of playing player's Labels """ players = self.get_players(all=True) play_levels = [x.strip() for x in play_level.split(',')] def_level = "2" if len(play_levels) == 0: SlTrace.lg("Setting to default level: {}".format(def_level)) play_levels = def_level # Default player_idx = -1 for player in players: player_idx += 1 if player_idx < len(play_levels): player_level = play_levels[player_idx] else: player_level = play_levels[-1] # Use last level plevel = int(player_level) playing_var = player.ctls_vars["level"] player.level = plevel playing_var.set(plevel) SlTrace.lg("Setting {} play level to {:d}".format(player, plevel))
def display(self, msg=None): display_str = "" if msg is None: msg = "Data Display" display_str += f"{msg}\n" if self.vals is None: raise SelectError("data gone") horiz_grp_divider = " " + "-" * (2*self.nCol+self.nSubCol-1) + "\n" for nr in range(1, self.nRow+1): if nr % self.nSubRow == 1: display_str += horiz_grp_divider for nc in range(1, self.nCol+1): if nc == 1: display_str +="|" val = self.getCellVal(row=nr, col=nc) disp = " " if self.isEmpty(val) else f"{val} " display_str += disp if nc % self.nSubCol == 0: display_str += "|" display_str += "\n" display_str += horiz_grp_divider SlTrace.lg(display_str)
def down_click(self, part, event=None): """ Process down click over highlighted part All highlighted parts elicit a call :part: highlighted part :event: event if available :Returns: True if processing is complete """ if self.down_click_call is not None: return self.down_click_call(part, event=event) """ Self processing """ if part.is_edge() and not part.is_turned_on(): SlTrace.lg("turning on %s" % part, "turning_on") self.drawn_lines.append(part) part.turn_on(player=self.get_player()) regions = part.get_adjacents() # Look if we completed any squares for square in regions: if square.is_complete(): self.complete_square(part, square) return True # Indicate processing is done return False
def annotate_squares(self, squares, player=None): """ Annotate squares in board with players info Updates select_cmd: prev_parts, new_parts as appropriate :squares: list of squares to annotate :player: player whos info is used Default: use current player """ if player is None: player = self.get_player() if not isinstance(squares, list): squares = [squares] for square in squares: square.part_check(prefix="annotate_squares") for square in squares: sc = select_copy(square) self.add_prev_parts(square) sc.set_centered_text(player.label, color=player.color, color_bg=player.color_bg) if SlTrace.trace("annotate_square"): SlTrace.lg("annotate_square: %s\n%s" % (sc, sc.str_edges())) self.add_new_parts(sc) return
def is_square_complete(self, part, squares=None, ifadd=False): """ Determine if this edge completes one or more square(s) region(s) :part: - possibly completing edge :squares: - any squares completed are appended to this list default: no regions are appended :ifadd: If part is added default: part must already be added """ if part.is_edge(): SlTrace.lg("complete square testing %s" % part, "square_completion") regions = part.get_adjacents() # Look if we completed any square ncomp = 0 for square in regions: if (ifadd and square.is_complete(added=part) or square.is_complete()): ncomp += 1 if squares is not None: squares.append(square) if ncomp > 0: return True return False
def get_component_val(self, name, comp_name=None, default=None): """ Get component value of named control If widget is present, get from widget else if control is present, get from control else if properties is present, get from properties :name: - control name :comp_name: if present, append with "_" comp_name :default: - use if value not found - MANDATORY data type is used if control entry data type is unknown """ if default is None: raise SelectError( "get_component_val: %s_%s mandatory default parameter missing" % (name, comp_name)) name_comp = name if comp_name is not None: name_comp += "_" + comp_name comp_entry = self.get_ctl_entry(name_comp) if comp_entry is None: raise SelectError( "get_component_value(%s, %s - component NOT FOUD" % (name, comp_name)) if comp_entry is not None: if comp_entry.value_type is None: comp_entry.value_type = type(default) ctl_widget = comp_entry.ctl_widget if ctl_widget is not None: widget_val = comp_entry.get_input() if widget_val is not None: vt = comp_entry.value_type if vt == int: if widget_val == "": widget_val = "0" # Treat undefined as 0 value = int(widget_val) elif vt == float: if widget_val == "": widget_val = "0" # Treat undefined as 0 value = float(widget_val) elif vt == str: value = widget_val else: value = widget_val # No change - should we check? SlTrace.lg( "get_component_val: %s ??? value_type(%s) widget_val=%s type(%s)" % (name_comp, vt, widget_val, type(widget_val))) SlTrace.lg(" type(int)=%s comp_entry.value_type=%s" % (type(int), comp_entry.value_type)) return value else: SlTrace.lg( "get_component_val: %s None return from comp_entry.get_input" % comp_name) return comp_entry.value return default # No component entry - return default
def test_is_covering_but_once(): SlTrace.lg("\ntest_covering_but_once") good1 = ['g7', 'g8', 'h8', 'h7'] dgood1 = DisplayedPath(path=good1, disp_board=dboard) ctv = ChessTourValidation(locs=good1) if ctv.is_covering_but_once(dgood1): SlTrace.lg(f"expected: pass of {good1}") else: SlTrace.lg(f"UNEXPECTED FAIL of {good1}")
def get_selected(cls, block=None): """ Get selected info, block if selected, else None :block: block to check. """ if block is None: raise SelectError("get_selected: with block is None") selected = None for sid in cls.selects: selected = cls.selects[sid] if selected.block.id == sid: break if selected.block is None: raise SelectError("selected with None for block") if selected.x_coord is None: SlTrace.lg("selected with None for x_coord") cv_width = cls.get_cv_width() cv_height = cls.get_cv_height() selected = SelectInfo(block=None, x_coord=cv_width / 2, y_coord=cv_height / 2) # HACK return selected
def __init__(self, road_width=.05, road_length=.05 * 2, surface=SurfaceType.DEFAULT, **kwargs): """ Setup object :road_width: road width as a fraction of track's width dimensions' e.g. 1,1 with container==None ==> block is whole canvas :road_length: road width as a fraction of track's width dimensions' e.g. 1,1 with container==None ==> block is whole canvas :surface: road surface type - determines look and operation/handeling collisions) """ SlTrace.lg("RoadTrack: %s" % (self)) super().__init__(**kwargs) if self.container is None: canvas = self.get_canvas() if canvas is None: self.canvas = Canvas(width=self.cv_width, height=self.cv_height) self.roads = [] # List of components # Parts of track self.road_width = road_width self.road_length = road_length self.surface = surface self.background = "lightgreen"
def set_check_box(self, frame=None, field=None, label=None, value=False, command=None): """ Set up check box for field :field: local field name :label: button label - default final section of field name :value: value to set "string" :command: function to call with new value when box changes default: no call """ if frame is None: frame = self.base_frame if label is None: label = field if label is not None: wlabel = Label(frame, text=label) wlabel.pack(side="left") content = BooleanVar() full_field = self.field_name(field) value = self.get_prop_val(full_field, value) content.set(value) cmd = None if command is not None: # HACK - only works for ONE checkbox self.check_box_change_content = content self.check_box_change_callback = command cmd = self.check_box_change widget = Checkbutton(frame, variable=content, command=cmd) widget.pack(side="left", fill="none", expand=True) self.ctls[full_field] = widget self.ctls_vars[full_field] = content SlTrace.lg(f"set_check_box adding field:{full_field}") self.set_prop_val(full_field, value)
def get_image_files(self, file_dir=None, name=None): """ Get list of image files given name :file_dir: file directory default: self.image_path :name: selection name default: all images :returns: list of absolute paths to image files """ if file_dir is None: file_dir = self.image_path if name is not None: file_dir = os.path.join(file_dir, name) names = os.listdir(file_dir) image_files = [] for name in sorted(names): path = os.path.join(file_dir, name) if (os.path.exists(path) and not os.path.isdir(path)): try: im = Image.open(path) image_files.append(path) im.close() except IOError: SlTrace.lg(f"Not an image file: {path}") return image_files
def __init__(self, car_bin): """ Setup car bin :car_bin: car bin instance """ self.car_bin = car_bin SlTrace.lg("CarBinSetup: car_bin pts: %s" % self.car_bin.get_absolute_points()) car_rot = 0 # HACK nentries = 4 # Number of entries (space) entry_space = .08 # between entries entry_width = 5 * (1. - nentries * entry_space) / nentries entry_height = .25 * (1. - 2 * entry_space) # height of entry pos_inc = Pt(0., 1.2 * entry_height) # to next entry pos = Pt(entry_space, entry_space) # HACK - Add extra to move to right entry = CarSimple(self.car_bin, rotation=car_rot, position=pos, width=entry_width, height=entry_height, base_color="red") SlTrace.lg("entry pts: %s" % entry.get_absolute_points()) self.add_entry(entry) pos += pos_inc entry = CarSimple(self.car_bin, rotation=car_rot, position=pos, width=entry_width, height=entry_height, base_color="blue") SlTrace.lg("entry pts: %s" % entry.get_absolute_points()) self.add_entry(entry) pos += pos_inc entry = CarSimple(self.car_bin, rotation=car_rot, position=pos, width=entry_width, height=entry_height, base_color="green") SlTrace.lg("entry pts: %s" % entry.get_absolute_points()) self.add_entry(entry)
def filelist_proc(filename): """ Process file containing list of puzzle files :filename: filename of file containing list of puzzle files Default directory for files in list is dir(filename) """ with open(filename) as f: file_list_files[filename] = 1 # Record as being used lines = f.readlines() filedir = os.path.dirname(filename) for i in range(len(lines)): line = lines[i] ml = re.match(r'^(\.*)#.*$', line) if ml: line = ml[1] # Before comment line = line.strip() if re.match(r'^\s*$', line): continue # Skip blank lines name = line if not os.path.isabs(name): name = os.path.join(filedir, name) if name in file_list_files: SlTrace.lg(f"file: {file} already used - avoiding recursive use ") continue file_proc(filename=name, run_after_load=True)
def open(self, src_file=None): """ Open command file Opens output files, if specified :src_file: Source file default, if no extension ext="csrc" If no extension: look for .py, then .csrc in source_directories else look for file in source_directories :returns: True iff successful open (note that py type flles are are opened when first get_tok is called """ self.lineno = 0 # Src line number self.eof = True # Cleared on open, set on EOF if src_file is None: if self.src_file_name is None: raise SelectError( "open: no src_file and no self.src_file_name") src_file = self.src_file_name self.src_file_name = src_file if not self.check_file(self.src_file_name): return False self.new_file = False if self.file_type == "csrc": try: if self.src_lst or SlTrace.trace("csrc"): SlTrace.lg("Open %s" % self.src_file_path) self.in_file = open(self.src_file_path, "r") except IOError as e: errno, strerror = e.args SlTrace.lg("Can't open command source file %s: %d %s" % (self.src_file_path, errno, strerror)) self.eof = True return False self.eof = False return True
def trace_item(self, rec_id=None, track_no=None, prefix="", begin=-3, end=-1): """ Trace(list) entry :rec_id: record id :trac_no: tracing number :begin: start stack list (-99 99 most recent entries) :end: end stack list (-1 most recent entry) """ if rec_id is not None: if rec_id in self.by_rec_id: track = self.by_rec_id[rec_id] else: return # Ignore if not tracked elif track_no is not None: if track_no in self.by_track_no: track = self.by_track_no[track] else: return # Ignore if not tracked else: raise("trace_item has neither rec_id nor track_no") rec_id = track[0] tags = self.gettags(rec_id) track_no = track[1] stack = track[2] entries = traceback.format_list(stack) # Remove the last two entries for the call to extract_stack() and to # the one before that, this function. Each entry consists of single # string with consisting of two lines, the script file path then the # line of source code making the call to this function. ###del entries[-2:] part = self.get_part_from_tags(tags) SlTrace.lg("%s%s %s %s" % (prefix, rec_id, tags, part)) for entry in entries[begin:end]: SlTrace.lg("%s" % (entry))
def get_part(self, id=None, type=None, sub_type=None, row=None, col=None): """ Get basic part :id: unique part id :type: part type e.g., edge, region, corner default: "edge" :sub_type: must match if present e.g. v for vertical, h for horizontal :row: part row :col: part column :returns: part, None if not found """ if id is not None: if id not in self.parts_by_id: SlTrace.lg("part not in parts_by_id") return None part = self.parts_by_id[id] return part if type is None: type = "edge" for part in self.parts: if part.part_type == type and (sub_type is None or part.sub_type() == sub_type): if part.row == row and part.col == col: return part
def next_color(self, colr=None, changing=None): """ Get next color based on color_current, color_changing :colr: color default: use self.color_current :changing: is color changing default: use self.color_changing """ if colr is None: colr = self.color_current self.color_current = colr if changing is None: changing = self.color_changing self.color_changing = changing if self.color_current == 'w': if self.color_changing: self.color_index += 1 if self.color_index >= len(self.colors): self.color_index = 0 # wrap around new_color = self.colors[self.color_index] elif self.color_current == '2': if changing: self.color_index += 1 if self.color_index >= len(self.custom_colors): self.color_index = 0 new_color = self.custom_colors[self.color_index] else: new_color = self.color_current if type(new_color) != str: SlTrace.report(f"new_color:{new_color} not str") return SlTrace.lg( f"new_color:{new_color}" f" color_current:{self.color_current}" f" color_index:{self.color_index}" f" color_changing:{self.color_changing}", "color") return new_color
def fill_points(self, point_list, point_resolution=None): """ Fill surrounding points assuming points are connected and enclose an area. - we will, eventualy, do "what turtle would do". Our initial technique assumes a convex region: given every sequential group of points (pn, pn+1,pn+2), pn+1 is within the fill region. We divide up the fill region into triangles: ntriangle = len(point_list)-2 for i in rage(1,ntriangle): fill_triangle(pl[0],pl[i],pl[i+1]) :point_list: list (iterable) of surrounding points :point_resolution: distance under which cells containing each point will cover region with no gaps default: self.point_resolution :returns: set of points (ix,iy) whose cells cover fill region """ if point_resolution is None: point_resolution = self.point_resolution SlTrace.lg(f"fill_points: {pl(point_list)} res:{point_resolution}", "xpoint") fill_point_set = set() if len(point_list) < 3: return set() plist = iter(point_list) p1 = point_list[0] for i in range(2, len(point_list)): p2 = point_list[i] p3 = point_list[i - 1] points = self.get_points_triangle( p1, p2, p3, point_resolution=point_resolution) fill_point_set.update(points) return fill_point_set
def test_attr(name): """ Test/exercise attribute :name: attribute name """ global nrun nrun += 1 SlTrace.lg(f"\n{nrun:3} Testing Attribute: {name}") attr = atc.get_attr(name) SlTrace.lg(f" {attr.get_name()}:" f" changes: {attr.get_changes()}" f" index: {attr.get_index()}") atc.add_value(name=name, value=f"{name}_test_value_{nrun}") values = attr.get_values() SlTrace.lg(f"values: {values}") for n in range(len(values) * 3): SlTrace.lg(f" {n}: {atc.get_next(name)}" f" index: {attr.get_index()}")
def get_drawn_line_points(self, p1, p2, width=None, point_resolution=None): """ Get drawn line fill points Find perimeter of surrounding points of a rectangle For simplicity we consider vertical width :p1: beginning point :p2: end point :width: width of line default: self.line_width :point_resolution: fill point spacing default: self.point_resolution :returns: set of fill points """ ###pts = self.get_line_points(p1,p2) ###return set(pts) if width is None: width = self.line_width if point_resolution is None: point_resolution = self.point_resolution pr = point_resolution SlTrace.lg( f"get_drawn_Line_points {p1} {p2}" f" width: {width} res: {pr}", "xpoint") p1x, p1y = p1 p2x, p2y = p2 self.set_line_funs(p1, p2) dx = self.lfun_unorm_x * width / 2 # draw width offsets dy = self.lfun_unorm_y * width / 2 pp1 = (p1x + dx, p1y + dy) # upper left corner pp2 = (p2x + dx, p2y + dy) # upper right corner pp3 = (p2x - dx, p2y - dy) # lower right corner pp4 = (p1x - dx, p1y - dy) # lower left corner perim_list = [pp1, pp2, pp3, pp4] filled_points = self.fill_points(perim_list) return filled_points