def __init__(self, person):
     super().__init__()
     self.person = person
     self.sub_node_map = {}
     self.label_string = person.name + '\n' + person.family_search_id
     self.label_box = AxisAlignedRectangle()
     self.bounding_box = None
Exemple #2
0
 def calculate_bounding_rectangle(self, targets=True):
     rect = AxisAlignedRectangle()
     rect.min_point = self.target_position if targets else self.position
     rect.max_point = self.target_position if targets else self.position
     rect.min_point -= Vector(0.5, 0.5)
     rect.max_point += Vector(0.5, 0.5)
     return rect
 def render_graph(self, draw, image, font, person_subset):
     image_rect = AxisAlignedRectangle(
         Vector(0.0, 0.0), Vector(float(image.width), float(image.height)))
     world_rect = self.bounding_box.Copy()
     world_rect.ExpandToMatchAspectRatioOf(image_rect)
     for node in self.all_nodes():
         node.render_edges(draw, font, image_rect, world_rect)
     for node in self.all_nodes():
         node.render_label_box(draw, font, image_rect, world_rect,
                               person_subset)
 def DrawPreviewImage(self,
                      graph,
                      preview_image,
                      rect,
                      anti_alias=False,
                      thickness=2.0):
     preview_image_rect = AxisAlignedRectangle(
         Vector(0.0, 0.0),
         Vector(float(preview_image.width), float(preview_image.height)))
     preview_draw = ImageDraw.Draw(preview_image)
     if anti_alias:
         for i in range(preview_image.width):
             for j in range(preview_image.height):
                 point = Vector(float(i),
                                float(preview_image.height - 1 - j))
                 smallest_distance = 999999.0
                 for edge in graph.GenerateEdgeSegments():
                     edge = rect.Map(edge, preview_image_rect)
                     distance = edge.Distance(point)
                     if distance < smallest_distance:
                         smallest_distance = distance
                 if smallest_distance <= thickness:
                     t = smallest_distance / thickness
                     current_pixel = preview_image.getpixel((i, j))
                     target_pixel = (0, 0, 0, 255)
                     pixel = (
                         int(
                             round(
                                 float(current_pixel[0]) * t +
                                 float(target_pixel[0]) * (1.0 - t))),
                         int(
                             round(
                                 float(current_pixel[1]) * t +
                                 float(target_pixel[1]) * (1.0 - t))),
                         int(
                             round(
                                 float(current_pixel[2]) * t +
                                 float(target_pixel[2]) * (1.0 - t))),
                         int(
                             round(
                                 float(current_pixel[3]) * t +
                                 float(target_pixel[3]) * (1.0 - t))),
                     )
                     preview_draw.point((i, j), fill=pixel)
     else:
         for edge in graph.GenerateEdgeSegments():
             edge = rect.Map(edge, preview_image_rect)
             edge = [
                 int(edge.point_a.x),
                 preview_image.height - 1 - int(edge.point_a.y),
                 int(edge.point_b.x),
                 preview_image.height - 1 - int(edge.point_b.y)
             ]
             preview_draw.line(edge, fill=(0, 0, 0, 255), width=2)
Exemple #5
0
 def _render_text(self, font, text, rect):
     total_width = 0.0
     for char in text:
         width = glutStrokeWidth(font, ord(char))
         total_width += float(width)
     text_rect = AxisAlignedRectangle()
     text_rect.min_point.x = 0.0
     text_rect.max_point.x = total_width
     text_rect.min_point.y = 0.0
     text_rect.max_point.y = 119.05
     original_height = text_rect.Height()
     text_rect.ExpandToMatchAspectRatioOf(rect)
     height = text_rect.Height()
     scale = rect.Width() / text_rect.Width()
     glPushMatrix()
     try:
         glTranslatef(
             rect.min_point.x,
             rect.min_point.y + (height - original_height) * 0.5 * scale,
             0.0)
         glScalef(scale, scale, 1.0)
         for char in text:
             glutStrokeCharacter(font, ord(char))
     finally:
         glPopMatrix()
Exemple #6
0
    def _recalc_projection_rect(self):

        viewport = glGetIntegerv(GL_VIEWPORT)
        viewport_rect = AxisAlignedRectangle()
        viewport_rect.min_point.x = 0.0
        viewport_rect.min_point.y = 0.0
        viewport_rect.max_point.x = float(viewport[2])
        viewport_rect.max_point.y = float(viewport[3])

        self.proj_rect = self.root_node.calculate_subtree_bounding_rectangle(
            targets=True)
        self.proj_rect.Scale(1.1)
        self.proj_rect.ExpandToMatchAspectRatioOf(viewport_rect)
Exemple #7
0
    def __init__(self, parent):
        gl_format = QtOpenGL.QGLFormat()
        gl_format.setAlpha(True)
        gl_format.setDepth(False)
        gl_format.setDoubleBuffer(True)

        super().__init__(gl_format, parent)

        self.root_node = None
        self.proj_rect = None
        self.anim_proj_rect = AxisAlignedRectangle()

        self.animation_timer = QtCore.QTimer()
        self.animation_timer.start(1)
        self.animation_timer.timeout.connect(self.animation_tick)

        self.auto_simplify = False

        self.dragPos = None
        self.dragging = False
Exemple #8
0
 def Transform(self, object):
     from math2d_polygon import Polygon
     from math2d_region import Region, SubRegion
     from math2d_aa_rect import AxisAlignedRectangle
     if isinstance(object, Vector):
         return self.linear_transform.Transform(object) + self.translation
     elif isinstance(object, AffineTransform):
         transform = AffineTransform()
         transform.linear_transform.x_axis = self.linear_transform.Transform(
             object.linear_transform.x_axis)
         transform.linear_transform.y_axis = self.linear_transform.Transform(
             object.linear_transform.y_axis)
         transform.translation = self.Transform(object.translation)
         return transform
     elif isinstance(object, LineSegment):
         return LineSegment(self.Transform(object.point_a),
                            self.Transform(object.point_b))
     elif isinstance(object, Polygon):
         polygon = Polygon()
         for vertex in object.vertex_list:
             polygon.vertex_list.append(self.Transform(vertex))
         return polygon
     elif isinstance(object, Region):
         region = Region()
         for sub_region in object.sub_region_list:
             region.sub_region_list.append(self.Transform(sub_region))
         return region
     elif isinstance(object, SubRegion):
         sub_region = SubRegion()
         sub_region.polygon = self.Transform(object.polygon)
         for hole in object.hole_list:
             sub_region.hole_list.append(self.Transform(hole))
         return sub_region
     elif isinstance(object, AxisAlignedRectangle):
         return AxisAlignedRectangle(
             min_point=self.Transform(object.min_point),
             max_point=self.Transform(object.max_point))
    def Generate(self, puzzle_folder, calc_solution, preview=None):
               
        # Go calculate all the needed symmetries of the cut-shape.
        # We need enough to generate the symmetry group of the shape, but we
        # also want those symmetries that are convenient for the user too.
        print('Generating symmetries...')
        for cut_region in self.cut_region_list:
            cut_region.GenerateSymmetryList()
        
        # Add the cut-regions to a planar graph.
        print('Adding cut regions...')
        graph = PlanarGraph()
        for cut_region in self.cut_region_list:
            graph.Add(cut_region.region)
        
        # For debugging purposes...
        if preview == 'graph_pre_cut':
            DebugDraw(graph)
        
        # Make sure that all cut-regions are tessellated.  This lets us do point tests against the regions.
        print('Tessellating cut regions...')
        for cut_region in self.cut_region_list:
            cut_region.region.Tessellate()
        
        # Now comes the process of cutting the puzzle.  The only sure algorithm I can think
        # of would use a stabilizer chain to enumerate all elements of the desired group, but
        # that is completely impractical.  The idea here is that if after max_count iteration
        # we have not added any more edges to our graph, then we can be reasonably sure that
        # there are no more cuts to be made.
        print('Generating cuts...')
        random.seed(0)
        max_count = 20
        count = 0
        while count < max_count:
            
            # Copy all edges of the graph contained in and not on the border of a randomly chosen region.
            i = random.randint(0, len(self.cut_region_list) - 1)
            cut_region = self.cut_region_list[i]
            region = cut_region.region
            line_segment_list = []
            for edge in graph.edge_list:
                edge_segment = graph.EdgeSegment(edge)
                for point in [edge_segment.point_a, edge_segment.point_b, edge_segment.Lerp(0.5)]:
                    if region.ContainsPoint(point) and not region.ContainsPointOnBorder(point):
                        line_segment_list.append(edge_segment)
                        break

            # Now let all copied edges, if any, undergo a random symmetry of the chosen region, then add them to the graph.
            edge_count_before = len(graph.edge_list)
            if len(line_segment_list) > 0:
                i = random.randint(0, len(cut_region.symmetry_list) - 1)
                symmetry = cut_region.symmetry_list[i]
                for line_segment in line_segment_list:
                    line_segment = symmetry * line_segment
                    graph.Add(line_segment)
            edge_count_after = len(graph.edge_list)
            
            # Count how long we've gone without adding any new edges.
            added_edge_count = edge_count_after - edge_count_before
            if added_edge_count > 0:
                count = 0
                print('Edge count: %d' % edge_count_after)
            else:
                count += 1

        # For debugging purposes...
        if preview == 'graph_post_cut':
            DebugDraw(graph)

        # This can be used to generate a solution to the puzzle.
        generator_list = []
        cloud = PointCloud()
        if self.PopulatePointCloudForPermutationGroup(cloud, graph):
            print('Generating permutations that generate the group...')
            for cut_region in self.cut_region_list:
                cut_region.permutation_list = []
                for symmetry in cut_region.symmetry_list:
                    permutation = []
                    for i, point in enumerate(cloud.point_list):
                        if cut_region.region.ContainsPoint(point):
                            point = symmetry * point
                        j = cloud.FindPoint(point)
                        if j is None:
                            nearest_points, smallest_distance = cloud.FindNearestPoints(point)
                            nearest_points = '[' + ','.join(['%d' % i for i in nearest_points]) + ']'
                            raise Exception('Failed to generate permutation!  Nearest points: %s; Smallest distance: %f' % (nearest_points, smallest_distance))
                        permutation.append(j)
                    cut_region.permutation_list.append(permutation)
                    if calc_solution:
                        generator_list.append(Perm(permutation))

        # If asked, try to find a stab-chain that can be used to solve the puzzle.
        stab_chain = None
        if calc_solution:
            # This is not necessarily the best base for solving the puzzle.
            base_array = [i for i in range(len(cloud.point_list))]
    
            # Try to generate a solution.
            print('Generating stab-chain...')
            stab_chain = StabChain()
            stab_chain.generate(generator_list, base_array)
            order = stab_chain.order()
            print('Group order = %d' % order)
            stab_chain.solve(self.SolveCallback)

        # Calculate a bounding rectangle for the graph, size it up a bit, then add it to the graph.
        # Also, generate a preview icon for the puzzle that can be used for selection purposes in the web-app.
        print('Adding border and drawing icon...')
        rect = AxisAlignedRectangle()
        rect.GrowFor(graph)
        rect.Scale(1.1)
        rect.ExpandToMatchAspectRatioOf(AxisAlignedRectangle(Vector(0.0, 0.0), Vector(1.0, 1.0)))
        preview_image = Image.new('RGBA', (128, 128), (255, 255, 255, 0))
        preview_image_rect = AxisAlignedRectangle(Vector(0.0, 0.0), Vector(float(preview_image.width), float(preview_image.height)))
        preview_draw = ImageDraw.Draw(preview_image)
        for edge in graph.GenerateEdgeSegments():
            edge = rect.Map(edge, preview_image_rect)
            edge = [
                int(edge.point_a.x),
                preview_image.height - 1 - int(edge.point_a.y),
                int(edge.point_b.x),
                preview_image.height - 1 - int(edge.point_b.y)
            ]
            preview_draw.line(edge, fill=(0, 0, 0, 255), width=2)
        graph.Add(rect)

        # Before we can pull the empty cycles out of the graph, we need to merge all connected components into one.
        print('Coalescing all connected components...')
        while True:
            sub_graph_list, dsf_set_list = graph.GenerateConnectedComponents()
            if len(sub_graph_list) == 1:
                break
            smallest_distance = None
            line_segment = None
            for i in range(len(graph.vertex_list)):
                dsf_set_a = dsf_set_list[i]
                for j in range(i + 1, len(graph.vertex_list)):
                    dsf_set_b = dsf_set_list[j]
                    if dsf_set_a != dsf_set_b:
                        distance = (graph.vertex_list[i] - graph.vertex_list[j]).Length()
                        if smallest_distance is None or distance < smallest_distance:
                            smallest_distance = distance
                            line_segment = LineSegment(graph.vertex_list[i], graph.vertex_list[j])
            graph.Add(line_segment)

        # For debugging purposes...
        if preview == 'graph_coalesced':
            DebugDraw(graph)

        # The desired meshes are now simply all of the empty cycles of the graph.
        print('Reading all empty cycles...')
        polygon_list = graph.GeneratePolygonCycles(epsilon=0.1)
        for polygon in polygon_list:
            polygon.Tessellate()
        
        # For debugging purposes...
        if preview == 'meshes':
            mesh_list = [polygon.mesh for polygon in polygon_list]
            DebugDraw(mesh_list)
        
        # Finally, write out the level file along with its accompanying mesh files.
        # Note that I think we can calculate UVs as a function of the object-space coordinates in the shader.
        print('Writing level files...')
        outline_thickness = rect.Width() / 100.0
        preview_image.save(puzzle_folder + '/' + self.Name() + '_Icon.png')
        mesh_list = []
        for i, cut_region in enumerate(self.cut_region_list):
            mesh_file = self.Name() + '_CaptureMesh%d.json' % i
            mesh = cut_region.region.GenerateMesh()
            outline_mesh = cut_region.region.GenerateLineMesh(outline_thickness)
            with open(puzzle_folder + '/' + mesh_file, 'w') as mesh_handle:
                mesh_handle.write(json.dumps({
                    'mesh': mesh.Serialize(),
                    'outline_mesh': outline_mesh.Serialize()
                }, sort_keys=True, indent=4, separators=(',', ': ')))
            mesh_list.append({
                'file': mesh_file,
                'type': 'capture_mesh',
                # Note that if the symmetry list has one entry, it's a reflection.
                # If it has 2 or more entries, then the first 2 are always rotations, the rest reflections.
                'symmetry_list': [symmetry.Serialize() for symmetry in cut_region.symmetry_list],
                'permutation_list': [permutation for permutation in cut_region.permutation_list]
            })
        # A good draw-order is probably largest area to smallest area.
        polygon_list.sort(key=lambda polygon: polygon.Area(), reverse=True)
        for i, polygon in enumerate(polygon_list):
            mesh_file = self.Name() + '_PictureMesh%d.json' % i
            with open(puzzle_folder + '/' + mesh_file, 'w') as mesh_handle:
                mesh_handle.write(json.dumps({
                    'mesh': polygon.mesh.Serialize()
                }, sort_keys=True, indent=4, separators=(',', ': ')))
            mesh_list.append({
                'file': mesh_file,
                'type': 'picture_mesh',
            })
        puzzle_file = self.Name() + '.json'
        with open(puzzle_folder + '/' + puzzle_file, 'w') as puzzle_handle:
            puzzle_handle.write(json.dumps({
                'mesh_list': mesh_list,
                'window': rect.Serialize()
            }, sort_keys=True, indent=4, separators=(',', ': ')))
        if stab_chain is not None:
            stab_chain_file = puzzle_folder + '/' + self.Name() + '_StabChain.json'
            with open(stab_chain_file, 'w') as handle:
                json_data = stab_chain.to_json()
                handle.write(json_data)
class RenderNode(object):
    def __init__(self, person):
        super().__init__()
        self.person = person
        self.sub_node_map = {}
        self.label_string = person.name + '\n' + person.family_search_id
        self.label_box = AxisAlignedRectangle()
        self.bounding_box = None

    def calculate_size(self):
        size = 0
        for node in self.all_nodes():
            size += 1
        return size

    def render_graph(self, draw, image, font, person_subset):
        image_rect = AxisAlignedRectangle(
            Vector(0.0, 0.0), Vector(float(image.width), float(image.height)))
        world_rect = self.bounding_box.Copy()
        world_rect.ExpandToMatchAspectRatioOf(image_rect)
        for node in self.all_nodes():
            node.render_edges(draw, font, image_rect, world_rect)
        for node in self.all_nodes():
            node.render_label_box(draw, font, image_rect, world_rect,
                                  person_subset)

    def render_edges(self, draw, font, image_rect, world_rect):
        point_a = world_rect.Map(self.label_box.Center(), image_rect)
        for key in self.sub_node_map:
            sub_node = self.sub_node_map[key]
            point_b = world_rect.Map(sub_node.label_box.Center(), image_rect)
            draw.line((point_a.x, point_a.y, point_b.x, point_b.y),
                      fill=(50, 100, 100),
                      width=1)
            point_c = (point_a + point_b) * 0.5
            text_size = draw.textsize(text=key, font=font)
            draw.text(
                (point_c.x - text_size[0] / 2, point_c.y - text_size[1] / 2),
                text=key,
                font=font,
                fill=(0, 0, 0))

    def render_label_box(self, draw, font, image_rect, world_rect,
                         person_subset):
        point_a = world_rect.Map(self.label_box.min_point, image_rect)
        point_b = world_rect.Map(self.label_box.max_point, image_rect)
        color = (80, 32, 32) if self.person in person_subset else (32, 32, 32)
        if self.person.any_proxy_work_available:
            color = (32, 80, 23)
        draw.rectangle((point_a.x, point_a.y, point_b.x, point_b.y),
                       fill=color)
        polygon = self.label_box.GeneratePolygon()
        for world_line in polygon.GenerateLineSegments():
            image_line = world_rect.Map(world_line, image_rect)
            draw.line((image_line.point_a.x, image_line.point_a.y,
                       image_line.point_b.x, image_line.point_b.y),
                      fill=(255, 0, 0),
                      width=1)
            point = world_rect.Map(self.label_box.Center(), image_rect)
        text_size = draw.textsize(text=self.label_string, font=font)
        draw.text((point.x - text_size[0] / 2, point.y - text_size[1] / 2),
                  text=self.label_string,
                  font=font,
                  fill=(200, 200, 200))

    def transform_graph_layout(self, transform):
        for node in self.all_nodes():
            node.label_box = transform.Transform(node.label_box)
            node.bounding_box = transform.Transform(node.bounding_box)

    def calculate_graph_layout(self, draw, font):
        text_size = draw.textsize(text=self.label_string, font=font)
        text_width = text_size[0]
        text_height = text_size[1]

        total_width = 0.0
        margin = 2.0
        for key in self.sub_node_map:
            sub_node = self.sub_node_map[key]
            sub_node.calculate_graph_layout(draw, font)
            sub_node.calculate_bounding_box()
            total_width += sub_node.bounding_box.Width() + 2.0 * margin

        location = Vector(-total_width / 2.0 + margin, -2.0 * text_height)
        for key in self.sub_node_map:
            sub_node = self.sub_node_map[key]
            transform = AffineTransform()
            upper_left_corner = Vector(sub_node.bounding_box.min_point.x,
                                       sub_node.bounding_box.max_point.y)
            transform.Translation(location - upper_left_corner)
            sub_node.transform_graph_layout(transform)
            location.x += sub_node.bounding_box.Width() + 2.0 * margin

        self.label_box.min_point = Vector(-text_width / 2.0 - 3.0,
                                          -text_height / 2.0 - 3.0)
        self.label_box.max_point = Vector(text_width / 2.0 + 3.0,
                                          text_height / 2.0 + 3.0)

    def calculate_bounding_box(self):
        self.bounding_box = self.label_box.Copy()
        for node in self.all_nodes():
            self.bounding_box.GrowFor(node.label_box)

    def all_nodes(self):
        queue = [self]
        while len(queue) > 0:
            node = queue.pop()
            yield node
            for key in node.sub_node_map:
                sub_node = node.sub_node_map[key]
                queue.append(sub_node)

    def prune_tree(self, person_set):
        del_key_list = []
        for key in self.sub_node_map:
            sub_node = self.sub_node_map[key]
            if not sub_node.any_person_found_in(person_set):
                del_key_list.append(key)
        for key in del_key_list:
            del self.sub_node_map[key]
        for key in self.sub_node_map:
            sub_node = self.sub_node_map[key]
            sub_node.prune_tree(person_set)

    def any_person_found_in(self, person_set):
        for node in self.all_nodes():
            if node.person in person_set:
                return True

    # TODO: Write an optimizer for the tree.  E.g., your father's spouse's son is just your brother.
    #       Note that some people can't be removed from the tree if they fall within a given set.
    #       I.e., the set of people we want to see in the tree.

    def construct_using_path(self, path, i=0):
        if i < len(path):
            component = path[i]
            if component[0] == 'mother':
                next_person = self.person.mother
                key = 'Mother'
            elif component[0] == 'father':
                next_person = self.person.father
                key = 'Father'
            elif component[0] == 'child':
                next_person = self.person.child_list[component[1]]
                key = 'Child %d' % (component[1] + 1)
            elif component[0] == 'spouse':
                next_person = self.person.spouse_list[component[1]]
                key = 'Spouse %d' % (component[1] + 1)
            else:
                raise Exception('Unknown component: %s' % component[0])

            if key not in self.sub_node_map:
                self.sub_node_map[key] = RenderNode(person=next_person)

            self.sub_node_map[key].construct_using_path(path, i + 1)