예제 #1
0
def merge_coplanar_boundaries(boundaries: list, doc=FreeCAD.ActiveDocument):
    """Try to merge coplanar boundaries"""
    if len(boundaries) == 1:
        return
    boundary1 = max(boundaries, key=lambda x: x.Area)
    # Ensure all boundaries are coplanar
    plane = utils.get_plane(boundary1)
    for boundary in boundaries:
        utils.project_boundary_onto_plane(boundary, plane)
    boundaries.remove(boundary1)
    remove_from_doc = list()

    # Attempt to merge boundaries
    while True and boundaries:
        for boundary2 in boundaries:
            if merge_boundaries(boundary1, boundary2):
                merge_corresponding_boundaries(boundary1, boundary2)
                boundaries.remove(boundary2)
                remove_from_doc.append(boundary2)
                break
        else:
            logger.warning(
                f"""Unable to merge boundaries RelSpaceBoundary Id <{boundary1.Id}>
                with boundaries <{", ".join(str(b.Id) for b in boundaries)}>"""
            )
            break

    # Clean FreeCAD document if join operation was a success
    for fc_object in remove_from_doc:
        doc.removeObject(fc_object.Name)
예제 #2
0
def get_medial_axis(boundary1, boundary2, ei1, ei2) -> Optional[Part.Line]:
    line1 = utils.line_from_edge(utils.get_outer_wire(boundary1).Edges[ei1])
    try:
        line2 = utils.line_from_edge(
            utils.get_outer_wire(boundary2).Edges[ei2])
    except IndexError:
        logger.warning(
            f"""Cannot find closest edge index <{ei2}> in boundary <{boundary2.Label}>
            to rejoin boundary <{boundary1.Label}>""")
        return None

    # Case 2a : edges are not parallel
    if abs(line1.Direction.dot(line2.Direction)) < 1 - TOLERANCE:
        b1_plane = utils.get_plane(boundary1)
        line_intersect = line1.intersect2d(line2, b1_plane)
        if line_intersect:
            point1 = b1_plane.value(*line_intersect[0])
            if line1.Direction.dot(line2.Direction) > 0:
                point2 = point1 + line1.Direction + line2.Direction
            else:
                point2 = point1 + line1.Direction - line2.Direction
    # Case 2b : edges are parallel
    else:
        point1 = (line1.Location + line2.Location) * 0.5
        point2 = point1 + line1.Direction

    try:
        return Part.Line(point1, point2)
    except Part.OCCError:
        logger.exception(
            f"Failure in boundary id <{boundary1.SourceBoundary.Id}> {point1} and {point2} are equal"
        )
        return None
예제 #3
0
def handle_curtain_walls(space, doc) -> None:
    """Add an hosted window with full area in curtain wall boundaries as they are not handled
    by BEM softwares"""
    for boundary in space.SecondLevel.Group:
        if getattr(boundary.RelatedBuildingElement, "IfcType",
                   "") != "IfcCurtainWall":
            continue
        # Prevent Revit issue which produce curtain wall with an hole inside but no inner boundary
        if not boundary.InnerBoundaries:
            if len(boundary.Shape.SubShapes) > 2:
                outer_wire = boundary.Shape.SubShapes[1]
                utils.generate_boundary_compound(boundary, outer_wire, ())
        boundary.LesoType = "Wall"
        fake_window = doc.copyObject(boundary)
        fake_window.IsHosted = True
        fake_window.LesoType = "Window"
        fake_window.ParentBoundary = boundary
        fake_window.GlobalId = ifcopenshell.guid.new()
        fake_window.Id = IfcId.new(doc)
        RelSpaceBoundary.set_label(fake_window)
        space.SecondLevel.addObject(fake_window)
        # Host cannot be an empty face so inner wire is scaled down a little
        inner_wire = utils.get_outer_wire(boundary).scale(0.999)
        inner_wire = utils.project_wire_to_plane(inner_wire,
                                                 utils.get_plane(boundary))
        utils.append_inner_wire(boundary, inner_wire)
        utils.append(boundary, "InnerBoundaries", fake_window)
        if FreeCAD.GuiUp:
            fake_window.ViewObject.ShapeColor = (0.0, 0.7, 1.0)
예제 #4
0
def find_closest_by_intersection(boundary1, boundary2):
    intersect_line = utils.get_plane(boundary1).intersectSS(
        utils.get_plane(boundary2))[0]
    boundaries_distance = boundary1.Shape.distToShape(boundary2.Shape)[0]
    edges1 = utils.get_outer_wire(boundary1).Edges
    edges2 = utils.get_outer_wire(boundary2).Edges
    for (ei1, edge1), (ei2,
                       edge2) in itertools.product(enumerate(edges1),
                                                   enumerate(edges2)):
        distance1 = edge_distance_to_line(edge1,
                                          intersect_line) + boundaries_distance
        distance2 = edge_distance_to_line(edge2,
                                          intersect_line) + boundaries_distance

        min_distance = boundary1.Proxy.closest[ei1].distance
        if distance1 < min_distance:
            boundary1.Proxy.closest[ei1] = Closest(boundary2, -1, distance1)

        min_distance = boundary2.Proxy.closest[ei2].distance
        if distance2 < min_distance:
            boundary2.Proxy.closest[ei2] = Closest(boundary1, -1, distance2)
예제 #5
0
def ensure_hosted_are_coplanar(space):
    for boundary in space.SecondLevel.Group:
        inner_wires = utils.get_inner_wires(boundary)
        missing_inner_wires = False
        if len(inner_wires) < len(boundary.InnerBoundaries):
            missing_inner_wires = True
        outer_wire = utils.get_outer_wire(boundary)
        for inner_boundary in boundary.InnerBoundaries:
            if utils.is_coplanar(inner_boundary,
                                 boundary) and not missing_inner_wires:
                continue
            utils.project_boundary_onto_plane(inner_boundary,
                                              utils.get_plane(boundary))
            inner_wire = utils.get_outer_wire(inner_boundary)
            inner_wires.append(inner_wire)
        try:
            utils.generate_boundary_compound(boundary, outer_wire, inner_wires)
        except RuntimeError:
            continue
예제 #6
0
def create_fake_host(boundary, space, doc):
    fake_host = doc.copyObject(boundary)
    fake_host.IsHosted = False
    fake_host.LesoType = "Wall"
    fake_host.GlobalId = ifcopenshell.guid.new()
    fake_host.Id = IfcId.new(doc)
    RelSpaceBoundary.set_label(fake_host)
    space.SecondLevel.addObject(fake_host)
    inner_wire = utils.get_outer_wire(boundary)
    outer_wire = inner_wire.scaled(1.001, inner_wire.CenterOfMass)
    plane = utils.get_plane(boundary)
    outer_wire = utils.project_wire_to_plane(outer_wire, plane)
    inner_wire = utils.project_wire_to_plane(inner_wire, plane)
    utils.generate_boundary_compound(fake_host, outer_wire, [inner_wire])
    boundary.ParentBoundary = fake_host
    fake_building_element = doc.copyObject(boundary.RelatedBuildingElement)
    fake_building_element.Id = IfcId.new(doc)
    fake_host.RelatedBuildingElement = fake_building_element
    utils.append(fake_host, "InnerBoundaries", boundary)
    if FreeCAD.GuiUp:
        fake_host.ViewObject.ShapeColor = (0.7, 0.3, 0.0)
    return fake_host
예제 #7
0
def rejoin_boundaries(space, sia_type):
    """
    Rejoin boundaries after their translation to get a correct close shell surfaces.
    1 Fill gaps between boundaries (2b)
    2 Fill gaps gerenate by translation to make a boundary on the inside or outside boundary of
    building elements
    https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/schema/ifcproductextension/lexical/ifcrelspaceboundary2ndlevel.htm # pylint: disable=line-too-long
    """
    base_boundaries = space.SecondLevel.Group
    for base_boundary in base_boundaries:
        boundary1 = getattr(base_boundary, sia_type)
        if not boundary1:
            continue
        lines = []
        fallback_lines = [
            utils.line_from_edge(edge)
            for edge in utils.get_outer_wire(boundary1).Edges
        ]

        # bound_box used to make sure line solution is in a reallistic scope (distance <= 5 m)
        bound_box = boundary1.Shape.BoundBox
        bound_box.enlarge(5000)

        if (base_boundary.IsHosted
                or base_boundary.PhysicalOrVirtualBoundary == "VIRTUAL"
                or not base_boundary.RelatedBuildingElement):
            continue

        b1_plane = utils.get_plane(boundary1)
        for b2_id, (ei1, ei2), fallback_line in zip(
                base_boundary.ClosestBoundaries,
                enumerate(base_boundary.ClosestEdges),
                fallback_lines,
        ):
            base_boundary2 = utils.get_in_list_by_id(base_boundaries, b2_id)
            boundary2 = getattr(base_boundary2, sia_type, None)
            if not boundary2:
                logger.warning(
                    f"Cannot find corresponding boundary with id <{b2_id}>")
                lines.append(fallback_line)
                continue
            # Case 1 : boundaries are not parallel
            line = get_intersecting_line(boundary1, boundary2)
            if line:
                if not is_valid_join(line, fallback_line):
                    line = fallback_line
                if not bound_box.intersect(line.Location, line.Direction):
                    line = fallback_line
                lines.append(line)
                continue
            # Case 2 : boundaries are parallel
            line = get_medial_axis(boundary1, boundary2, ei1, ei2)
            if line and is_valid_join(line, fallback_line):
                lines.append(line)
                continue

            lines.append(fallback_line)

        # Generate new shape
        try:
            outer_wire = utils.polygon_from_lines(lines, b1_plane)
        except (Part.OCCError, utils.ShapeCreationError):
            logger.exception(
                f"Invalid geometry while rejoining boundary Id <{base_boundary.Id}>"
            )
            continue
        try:
            Part.Face(outer_wire)
        except Part.OCCError:
            logger.exception(
                f"Unable to rejoin boundary Id <{base_boundary.Id}>")
            continue

        inner_wires = utils.get_inner_wires(boundary1)
        try:
            utils.generate_boundary_compound(boundary1, outer_wire,
                                             inner_wires)
        except RuntimeError as err:
            logger.exception(err)
            continue

        boundary1.Area = area = boundary1.Shape.Area
        for inner_boundary in base_boundary.InnerBoundaries:
            area = area + inner_boundary.Shape.Area
        boundary1.AreaWithHosted = area
예제 #8
0
def get_intersecting_line(boundary1, boundary2) -> Optional[Part.Line]:
    plane_intersect = utils.get_plane(boundary1).intersectSS(
        utils.get_plane(boundary2))
    return plane_intersect[0] if plane_intersect else None