示例#1
0
def associate_inner_boundaries(fc_boundaries, doc):
    """Associate parent boundary and inner boundaries"""
    to_delete = []
    for fc_boundary in fc_boundaries:
        if not fc_boundary.IsHosted or fc_boundary.ParentBoundary:
            continue
        host_boundaries = []
        for host_element in fc_boundary.RelatedBuildingElement.HostElements:
            host_boundaries.extend(host_element.ProvidesBoundaries)

        candidates = set(fc_boundaries).intersection(host_boundaries)

        try:
            host = get_host(fc_boundary, candidates)
        except InvalidBoundary as err:
            logger.exception(err)
            to_delete.append(fc_boundary)
            continue

        fc_boundary.ParentBoundary = host
        if host:
            utils.append(host, "InnerBoundaries", fc_boundary)

    # Remove invalid boundary and corresponding inner wire
    updated_boundaries = fc_boundaries[:]
    for boundary in to_delete:
        remove_invalid_inner_wire(boundary, updated_boundaries)
        updated_boundaries.remove(boundary)
        doc.removeObject(boundary.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 merge_boundaries(boundary1, boundary2) -> bool:
    """Try to merge 2 boundaries. Retrun True if successfully merged"""
    wire1 = utils.get_outer_wire(boundary1)
    wire2 = utils.get_outer_wire(boundary2)

    new_wire, extra_inner_wires = merged_wires(wire1, wire2)
    if not new_wire:
        return False

    # Update shape
    if boundary1.IsHosted:
        utils.remove_inner_wire(boundary1.ParentBoundary, wire1)
        utils.remove_inner_wire(boundary2.ParentBoundary, wire2)
        utils.append_inner_wire(boundary1.ParentBoundary, new_wire)
    else:
        for inner_boundary in boundary2.InnerBoundaries:
            utils.append(boundary1, "InnerBoundaries", inner_boundary)
            inner_boundary.ParentBoundary = boundary1
    inner_wires = utils.get_inner_wires(boundary1)[:]
    inner_wires.extend(utils.get_inner_wires(boundary2))
    inner_wires.extend(extra_inner_wires)

    try:
        utils.generate_boundary_compound(boundary1, new_wire, inner_wires)
    except RuntimeError as error:
        logger.exception(error)
        return False
    RelSpaceBoundary.recompute_areas(boundary1)

    return True
示例#4
0
def ensure_hosted_element_are(space, doc):
    for boundary in space.SecondLevel.Group:
        try:
            ifc_type = boundary.RelatedBuildingElement.IfcType
        except AttributeError:
            continue

        if not is_typically_hosted(ifc_type):
            continue

        if boundary.IsHosted and boundary.ParentBoundary:
            continue

        def valid_hosts(boundary):
            """Guess valid hosts"""
            for boundary2 in space.SecondLevel.Group:
                if boundary is boundary2 or is_typically_hosted(
                        boundary2.IfcType):
                    continue

                if not boundary2.Area.Value - boundary.Area.Value >= 0:
                    continue

                if not utils.are_parallel_boundaries(boundary, boundary2):
                    continue

                if utils.are_too_far(boundary, boundary2):
                    continue

                yield boundary2

        def find_host(boundary):
            fallback_solution = None
            for boundary2 in valid_hosts(boundary):

                fallback_solution = boundary2
                for inner_wire in utils.get_inner_wires(boundary2):
                    if (not abs(
                            Part.Face(inner_wire).Area - boundary.Area.Value) <
                            TOLERANCE):
                        continue

                    return boundary2
            if not fallback_solution:
                raise HostNotFound(
                    f"No host found for RelSpaceBoundary Id<{boundary.Id}>")
            logger.warning(
                f"Using fallback solution to resolve host of RelSpaceBoundary Id<{boundary.Id}>"
            )
            return fallback_solution

        try:
            host = find_host(boundary)
        except HostNotFound as err:
            host = create_fake_host(boundary, space, doc)
            logger.exception(err)
        boundary.IsHosted = True
        boundary.ParentBoundary = host
        utils.append(host, "InnerBoundaries", boundary)
示例#5
0
def associate_host_element(ifc_file, elements_group):
    # Associate Host / Hosted elements
    ifc_elements = (e for e in ifc_file.by_type("IfcElement")
                    if e.ProvidesBoundaries)
    for ifc_entity in ifc_elements:
        if ifc_entity.FillsVoids:
            try:
                host = utils.get_element_by_guid(
                    utils.get_host_guid(ifc_entity), elements_group)
            except LookupError as err:
                logger.exception(err)
                continue
            hosted = utils.get_element_by_guid(ifc_entity.GlobalId,
                                               elements_group)
            utils.append(host, "HostedElements", hosted)
            utils.append(hosted, "HostElements", host)
示例#6
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