Ejemplo n.º 1
0
def getReinforcementDrawingSVGData(
    structure,
    rebars_list,
    view_direction,
    rebars_stroke_width,
    rebars_color_style,
    structure_stroke_width,
    structure_fill_style,
):
    """getReinforcementDrawingSVGData(Structure, RebarsList, ViewDirection,
    RebarsStrokeWidth, RebarsFillStyle, StructureStrokeWidth,
    StructureFillStyle):
    Generates Reinforcement Drawing View.

    view_direction is FreeCAD.Vector() or WorkingPlane.plane() corresponding to
    direction of view point.

    rebars_color_style can be:
        - "shape color" to select color of rebar shape
        - color name or hex value of color
    structure_fill_style can be:
        - "shape color" to select color of rebar shape
        - color name or hex value of color
        - "none" to not fill structure shape

    Returns dictionary format:
    {
        "svg": reinforcement_drawing_svg,
        "rebars": visible_rebars,
    }
    """
    if isinstance(view_direction, FreeCAD.Vector):
        if not DraftVecUtils.isNull(view_direction):
            view_plane = getSVGPlaneFromAxis(view_direction)
    elif isinstance(view_direction, WorkingPlane.Plane):
        view_plane = view_direction

    min_x, min_y, max_x, max_y = getDrawingMinMaxXY(structure, rebars_list,
                                                    view_plane)

    svg = getSVGRootElement()

    reinforcement_drawing = ElementTree.Element(
        "g", attrib={"id": "reinforcement_drawing"})
    svg.append(reinforcement_drawing)

    # Filter rebars created using Reinforcement Workbench
    stirrups = []
    bent_rebars = []
    u_rebars = []
    l_rebars = []
    straight_rebars = []
    helical_rebars = []
    custom_rebars = []
    for rebar in rebars_list:
        if not hasattr(rebar, "RebarShape"):
            custom_rebars.append(rebar)
        elif rebar.RebarShape == "Stirrup":
            stirrups.append(rebar)
        elif rebar.RebarShape == "BentShapeRebar":
            bent_rebars.append(rebar)
        elif rebar.RebarShape == "UShapeRebar":
            u_rebars.append(rebar)
        elif rebar.RebarShape == "LShapeRebar":
            l_rebars.append(rebar)
        elif rebar.RebarShape == "StraightRebar":
            straight_rebars.append(rebar)
        elif rebar.RebarShape == "HelicalRebar":
            helical_rebars.append(rebar)
        else:
            custom_rebars.append(rebar)

    rebars_svg = ElementTree.Element("g", attrib={"id": "Rebars"})
    reinforcement_drawing.append(rebars_svg)

    visible_rebars = []
    stirrups_svg = ElementTree.Element("g", attrib={"id": "Stirrup"})
    rebars_svg.append(stirrups_svg)
    for rebar in stirrups:
        rebar_data = getStirrupSVGData(
            rebar,
            view_plane,
            rebars_svg,
            rebars_stroke_width,
            rebars_color_style,
        )
        if rebar_data["visibility"]:
            stirrups_svg.append(rebar_data["svg"])
            visible_rebars.append(rebar)

    bent_rebars_svg = ElementTree.Element("g", attrib={"id": "BentShapeRebar"})
    rebars_svg.append(bent_rebars_svg)
    for rebar in bent_rebars:
        rebar_data = getUShapeRebarSVGData(
            rebar,
            view_plane,
            rebars_svg,
            rebars_stroke_width,
            rebars_color_style,
        )
        if rebar_data["visibility"]:
            bent_rebars_svg.append(rebar_data["svg"])
            visible_rebars.append(rebar)

    u_rebars_svg = ElementTree.Element("g", attrib={"id": "UShapeRebar"})
    rebars_svg.append(u_rebars_svg)
    for rebar in u_rebars:
        rebar_data = getUShapeRebarSVGData(
            rebar,
            view_plane,
            rebars_svg,
            rebars_stroke_width,
            rebars_color_style,
        )
        if rebar_data["visibility"]:
            u_rebars_svg.append(rebar_data["svg"])
            visible_rebars.append(rebar)

    l_rebars_svg = ElementTree.Element("g", attrib={"id": "LShapeRebar"})
    rebars_svg.append(l_rebars_svg)
    for rebar in l_rebars:
        rebar_data = getUShapeRebarSVGData(
            rebar,
            view_plane,
            rebars_svg,
            rebars_stroke_width,
            rebars_color_style,
        )
        if rebar_data["visibility"]:
            l_rebars_svg.append(rebar_data["svg"])
            visible_rebars.append(rebar)

    straight_rebars_svg = ElementTree.Element("g",
                                              attrib={"id": "StraightRebar"})
    rebars_svg.append(straight_rebars_svg)

    for rebar in straight_rebars:
        rebar_data = getStraightRebarSVGData(
            rebar,
            view_plane,
            rebars_svg,
            rebars_stroke_width,
            rebars_color_style,
        )
        if rebar_data["visibility"]:
            straight_rebars_svg.append(rebar_data["svg"])
            visible_rebars.append(rebar)

    helical_rebars_svg = ElementTree.Element("g",
                                             attrib={"id": "HelicalRebar"})
    rebars_svg.append(helical_rebars_svg)

    # SVG is generated for all helical rebars, because all helical rebars in
    # circular column are assumed to be visible, not overlapped by any other
    # rebar type (it makes sense for me). Please create an issue on github
    # repository if you think its wrong assumption
    for rebar in helical_rebars:
        rebars_color = getRebarColor(rebar, rebars_color_style)
        rebars_color = getcolor(rebars_color)
        rebar_svg_draft = Draft.get_svg(
            rebar,
            direction=view_plane,
            linewidth=rebars_stroke_width,
            fillstyle="none",
            color=rebars_color,
        )
        if rebar_svg_draft:
            helical_rebars_svg.append(ElementTree.fromstring(rebar_svg_draft))
            visible_rebars.append(rebar)

    custom_rebars_svg = ElementTree.Element("g", attrib={"id": "CustomRebar"})
    rebars_svg.append(custom_rebars_svg)
    for rebar in custom_rebars:
        rebars_color = getRebarColor(rebar, rebars_color_style)
        rebars_color = getcolor(rebars_color)
        rebar_svg_draft = Draft.get_svg(
            rebar,
            direction=view_plane,
            linewidth=rebars_stroke_width,
            fillstyle="none",
            color=rebars_color,
        )
        if rebar_svg_draft:
            custom_rebars_svg.append(ElementTree.fromstring(rebar_svg_draft))

    # Create Structure SVG
    _structure_svg = '<g id="structure">{}</g>'.format(
        Draft.get_svg(
            structure,
            direction=view_plane,
            linewidth=structure_stroke_width,
            fillstyle=structure_fill_style,
        ))

    # Fix structure transparency (useful in console mode where
    # obj.ViewObject.Transparency is not available OR in gui mode if
    # structure transparency is ~0)
    if structure_fill_style != "none":
        if _structure_svg.find("fill-opacity") == -1:
            _structure_svg = _structure_svg.replace(";fill:",
                                                    ";fill-opacity:0.2;fill:")
        else:
            import re

            _structure_svg = re.sub('(fill-opacity:)([^:]+)(;|")', r"\1 0.2\3",
                                    _structure_svg)

    structure_svg = ElementTree.fromstring(_structure_svg)
    reinforcement_drawing.append(structure_svg)
    reinforcement_drawing.set(
        "transform",
        "translate({}, {})".format(round(-min_x), round(-min_y)),
    )

    svg_width = round(max_x - min_x)
    svg_height = round(max_y - min_y)

    svg.set("width", "{}mm".format(svg_width))
    svg.set("height", "{}mm".format(svg_height))
    svg.set("viewBox", "0 0 {} {}".format(svg_width, svg_height))

    return {"svg": svg, "rebars": visible_rebars}
def makeBillOfMaterialSVG(
    # column_headers: Optional[
    #     OrderedDictType[
    #         Literal[
    #             "Host",
    #             "Mark",
    #             "RebarsCount",
    #             "Diameter",
    #             "RebarLength",
    #             "RebarsTotalLength",
    #         ],
    #         str,
    #     ]
    # ] = None,
    column_headers: Optional[OrderedDictType[str, str]] = None,
    # column_units: Optional[
    #     Dict[Literal["Diameter", "RebarLength", "RebarsTotalLength"], str]
    # ] = None,
    column_units: Optional[Dict[str, str]] = None,
    dia_weight_map: Optional[Dict[float, FreeCAD.Units.Quantity]] = None,
    # rebar_length_type: Optional[
    #     Literal["RealLength", "LengthWithSharpEdges"]
    # ] = None,
    rebar_length_type: Optional[str] = None,
    font_family: Optional[str] = None,
    font_filename: Optional[str] = None,
    font_size: Optional[float] = None,
    column_width: Optional[float] = None,
    row_height: Optional[float] = None,
    bom_left_offset: Optional[float] = None,
    bom_top_offset: Optional[float] = None,
    bom_min_right_offset: Optional[float] = None,
    bom_min_bottom_offset: Optional[float] = None,
    bom_table_svg_max_width: Optional[float] = None,
    bom_table_svg_max_height: Optional[float] = None,
    template_file: Optional[str] = None,
    output_file: Optional[str] = None,
    rebar_objects: Optional[List] = None,
    # reinforcement_group_by: Optional[Literal["Mark", "Host"]] = None,
    reinforcement_group_by: Optional[str] = None,
    return_svg_only: bool = False,
):
    """makeBillOfMaterialSVG([ColumnHeaders, ColumnUnits, DiaWeightMap,
    RebarLengthType, FontFamily, FontSize, FontFilename, ColumnWidth, RowHeight,
    BOMLeftOffset, BOMTopOffset, BOMMinRightOffset, BOMMinBottomOffset,
    BOMTableSVGMaxWidth, BOMTableSVGMaxHeight, TemplateFile, OutputFile,
    RebarObjects, ReinforcementGroupBy, ReturnSVGOnly]):
    Generates the Rebars Material Bill SVG.

    column_headers is an ordered dictionary with keys: "Host", "Mark",
    "RebarsCount", "Diameter", "RebarLength", "RebarsTotalLength" and values are
    column display header.
    e.g. {
            "Host": "Member",
            "Mark": "Mark",
            "RebarsCount": "No. of Rebars",
            "Diameter": "Diameter in mm",
            "RebarLength": "Length in m/piece",
            "RebarsTotalLength": "Total Length in m",
        }

    column_units is a dictionary with keys: "Diameter", "RebarLength",
    "RebarsTotalLength" and their corresponding units as value.
    e.g. {
            "Diameter": "mm",
            "RebarLength": "m",
            "RebarsTotalLength": "m",
         }

    dia_weight_map is a dictionary with diameter as key and corresponding weight
    (kg/m) as value.
    e.g. {
            6: FreeCAD.Units.Quantity("0.222 kg/m"),
            8: FreeCAD.Units.Quantity("0.395 kg/m"),
            10: FreeCAD.Units.Quantity("0.617 kg/m"),
            12: FreeCAD.Units.Quantity("0.888 kg/m"),
            ...,
         }

    rebar_length_type can be "RealLength" or "LengthWithSharpEdges".

    font_filename is required if you are working in pure console mode, without
    any gui.

    rebar_objects is the list of ArchRebar and/or rebar2 objects.

    reinforcement_group_by can be "Mark" or "Host".

    If return_svg_only is True, then neither BOMContent object is created nor
    svg is written to output_file. And it returns svg element.
    Default is False.

    Returns Bill Of Material svg code.
    """
    reinforcement_objects = getReinforcementRebarObjects(rebar_objects)
    if not reinforcement_objects:
        FreeCAD.Console.PrintWarning(
            "No rebar object in current selection/document. "
            "Returning without BillOfMaterial SVG.\n")
        return

    bom_preferences = BOMPreferences()
    column_headers = column_headers or bom_preferences.getColumnHeaders()
    column_units = column_units or bom_preferences.getColumnUnits()
    dia_weight_map = dia_weight_map or bom_preferences.getDiaWeightMap()
    rebar_length_type = (rebar_length_type
                         or bom_preferences.getRebarLengthType())
    reinforcement_group_by = (reinforcement_group_by
                              or bom_preferences.getReinforcementGroupBy())

    svg_pref = bom_preferences.getSVGPrefGroup()
    font_family = font_family or svg_pref.GetString("FontFamily")
    font_filename = font_filename or svg_pref.GetString("FontFilename")
    font_size = font_size or svg_pref.GetFloat("FontSize")
    column_width = column_width or svg_pref.GetFloat("ColumnWidth")
    row_height = row_height or svg_pref.GetFloat("RowHeight")
    if bom_left_offset is None:
        bom_left_offset = svg_pref.GetFloat("LeftOffset")
    if bom_top_offset is None:
        bom_top_offset = svg_pref.GetFloat("TopOffset")
    if bom_min_right_offset is None:
        bom_min_right_offset = svg_pref.GetFloat("MinRightOffset")
    if bom_min_bottom_offset is None:
        bom_min_bottom_offset = svg_pref.GetFloat("MinBottomOffset")
    bom_table_svg_max_width = bom_table_svg_max_width or svg_pref.GetFloat(
        "MaxWidth")
    bom_table_svg_max_height = bom_table_svg_max_height or svg_pref.GetFloat(
        "MaxHeight")
    if template_file is None:
        template_file = svg_pref.GetString("TemplateFile")

    # Fix column units
    column_units = fixColumnUnits(column_units)

    svg = getSVGRootElement()

    # Get user preferred unit precision
    precision = FreeCAD.ParamGet(
        "User parameter:BaseApp/Preferences/Units").GetInt("Decimals")

    bom_table_svg = ElementTree.Element("g")
    bom_table_svg.set("id", "BOM_table")

    # Get unique diameter list
    diameter_list = getUniqueDiameterList(reinforcement_objects)

    y_offset = 0
    column_headers_svg = getColumnHeadersSVG(
        column_headers,
        diameter_list,
        precision,
        y_offset,
        column_width,
        row_height,
        font_family,
        font_size,
    )
    bom_table_svg.append(column_headers_svg)

    # Dictionary to store total length of rebars corresponding to its dia
    dia_total_length_dict = {
        dia.Value: FreeCAD.Units.Quantity("0 mm")
        for dia in diameter_list
    }

    if "RebarsTotalLength" in column_headers:
        first_row = 3
        current_row = 3
        y_offset += 2 * row_height
    else:
        first_row = 2
        current_row = 2
        y_offset += row_height

    def getHostCellSVG(_host_label: str) -> ElementTree.Element:
        host_column_number = getColumnNumber(column_headers, diameter_list,
                                             "Host")
        host_column_offset = column_width * (host_column_number - 1)
        return getSVGDataCell(
            _host_label,
            host_column_offset,
            y_offset,
            column_width,
            row_height,
            font_family,
            font_size,
            "bom_table_cell_column_{}".format(host_column_number),
        )

    def getMarkCellSVG(rebar_mark: Union[str, float]) -> ElementTree.Element:
        mark_column_number = getColumnNumber(column_headers, diameter_list,
                                             "Mark")
        mark_column_offset = column_width * (mark_column_number - 1)
        return getSVGDataCell(
            rebar_mark,
            mark_column_offset,
            y_offset,
            column_width,
            row_height,
            font_family,
            font_size,
            "bom_table_cell_column_{}".format(mark_column_number),
        )

    def getDiameterCellSVG(
        rebar_diameter: FreeCAD.Units.Quantity, ) -> ElementTree.Element:
        disp_diameter = str(
            round(
                rebar_diameter.getValueAs(column_units["Diameter"]).Value,
                precision,
            ))
        if "." in disp_diameter:
            disp_diameter = disp_diameter.rstrip("0").rstrip(".")
        disp_diameter += " " + column_units["Diameter"]
        diameter_column_number = getColumnNumber(column_headers, diameter_list,
                                                 "Diameter")
        diameter_column_offset = column_width * (diameter_column_number - 1)
        return getSVGDataCell(
            disp_diameter,
            diameter_column_offset,
            y_offset,
            column_width,
            row_height,
            font_family,
            font_size,
            "bom_table_cell_column_{}".format(diameter_column_number),
        )

    def getRebarsCountCellSVG(reinforcement_objs: List) -> ElementTree.Element:
        rebars_count_column_number = getColumnNumber(column_headers,
                                                     diameter_list,
                                                     "RebarsCount")
        rebars_count_column_offset = column_width * (
            rebars_count_column_number - 1)
        return getSVGDataCell(
            sum(map(lambda x: x.Amount, reinforcement_objs)),
            rebars_count_column_offset,
            y_offset,
            column_width,
            row_height,
            font_family,
            font_size,
            "bom_table_cell_column_{}".format(rebars_count_column_number),
        )

    def getRebarLengthCellSVG(
        rebar_length: FreeCAD.Units.Quantity, ) -> ElementTree.Element:
        disp_base_rebar_length = str(
            round(
                rebar_length.getValueAs(column_units["RebarLength"]).Value,
                precision,
            ))
        if "." in disp_base_rebar_length:
            disp_base_rebar_length = disp_base_rebar_length.rstrip("0").rstrip(
                ".")
        disp_base_rebar_length += " " + column_units["RebarLength"]

        rebar_length_column_number = getColumnNumber(column_headers,
                                                     diameter_list,
                                                     "RebarLength")
        rebar_length_column_offset = column_width * (
            rebar_length_column_number - 1)
        return getSVGDataCell(
            disp_base_rebar_length,
            rebar_length_column_offset,
            y_offset,
            column_width,
            row_height,
            font_family,
            font_size,
            "bom_table_cell_column_{}".format(rebar_length_column_number),
        )

    def getRebarTotalLengthCellsSVG(
        _rebar_total_length: FreeCAD.Units.Quantity,
    ) -> List[ElementTree.Element]:
        disp_rebar_total_length = str(
            round(
                _rebar_total_length.getValueAs(
                    column_units["RebarsTotalLength"]).Value,
                precision,
            ))
        if "." in disp_rebar_total_length:
            disp_rebar_total_length = disp_rebar_total_length.rstrip(
                "0").rstrip(".")
        disp_rebar_total_length += " " + column_units["RebarsTotalLength"]

        rebar_total_length_column_number = getColumnNumber(
            column_headers, diameter_list, "RebarsTotalLength")
        rebar_total_length_column_offset = column_width * (
            rebar_total_length_column_number - 1)
        rebar_total_length_cells_svg = []
        for dia_index, dia in enumerate(diameter_list):
            if dia == base_rebar.Diameter:
                rebar_total_length_cells_svg.append(
                    getSVGDataCell(
                        disp_rebar_total_length,
                        rebar_total_length_column_offset +
                        (diameter_list.index(dia)) * column_width,
                        y_offset,
                        column_width,
                        row_height,
                        font_family,
                        font_size,
                        "bom_table_cell_column_{}".format(
                            rebar_total_length_column_number + dia_index),
                    ))
            else:
                rebar_total_length_cells_svg.append(
                    getSVGRectangle(
                        rebar_total_length_column_offset +
                        diameter_list.index(dia) * column_width,
                        y_offset,
                        column_width,
                        row_height,
                        "bom_table_cell_column_{}".format(
                            rebar_total_length_column_number + dia_index),
                    ))
        return rebar_total_length_cells_svg

    if reinforcement_group_by == "Mark":
        mark_reinforcements_dict = getMarkReinforcementsDict(rebar_objects)
        for mark_number in mark_reinforcements_dict:
            base_rebar = getBaseRebar(mark_reinforcements_dict[mark_number][0])

            bom_row_svg = ElementTree.Element("g")
            # TODO: Modify logic of str(current_row - first_row + 1)
            # first_row variable maybe eliminated
            bom_row_svg.set("id",
                            "BOM_table_row" + str(current_row - first_row + 1))

            if "Host" in column_headers:
                host_label = []
                for reinforcement in mark_reinforcements_dict[mark_number]:
                    if (reinforcement.Host
                            and reinforcement.Host.Label not in host_label):
                        host_label.append(reinforcement.Host.Label)
                bom_row_svg.append(getHostCellSVG(",".join(
                    sorted(host_label))))

            if "Mark" in column_headers:
                bom_row_svg.append(getMarkCellSVG(mark_number))

            if "RebarsCount" in column_headers:
                bom_row_svg.append(
                    getRebarsCountCellSVG(
                        mark_reinforcements_dict[mark_number]))

            if "Diameter" in column_headers:
                bom_row_svg.append(getDiameterCellSVG(base_rebar.Diameter))

            base_rebar_length = FreeCAD.Units.Quantity("0 mm")
            if "RebarLength" in column_headers:
                if rebar_length_type == "RealLength":
                    base_rebar_length = base_rebar.Length
                else:
                    base_rebar_length = getRebarSharpEdgedLength(base_rebar)
                bom_row_svg.append(getRebarLengthCellSVG(base_rebar_length))

            if "RebarsTotalLength" in column_headers:
                rebar_total_length = FreeCAD.Units.Quantity("0 mm")
                for reinforcement in mark_reinforcements_dict[mark_number]:
                    rebar_total_length += (reinforcement.Amount *
                                           base_rebar_length)
                dia_total_length_dict[
                    base_rebar.Diameter.Value] += rebar_total_length

                bom_row_svg.extend(
                    getRebarTotalLengthCellsSVG(rebar_total_length))

            bom_table_svg.append(bom_row_svg)
            y_offset += row_height
            current_row += 1
    else:
        host_reinforcements_dict = getHostReinforcementsDict(rebar_objects)
        for rebar_host, reinforcement_list in host_reinforcements_dict.items():
            mark_reinforcements_dict = getMarkReinforcementsDict(
                reinforcement_list)
            add_host_in_column = True
            for mark_number in mark_reinforcements_dict:
                base_rebar = getBaseRebar(
                    mark_reinforcements_dict[mark_number][0])

                bom_row_svg = ElementTree.Element("g")
                # TODO: Modify logic of str(current_row - first_row + 1)
                # first_row variable maybe eliminated
                bom_row_svg.set(
                    "id", "BOM_table_row" + str(current_row - first_row + 1))

                if "Host" in column_headers:
                    if add_host_in_column:
                        add_host_in_column = False
                        host_label = (rebar_host.Label if hasattr(
                            rebar_host, "Label") else str(rebar_host))
                    else:
                        host_label = ""
                    bom_row_svg.append(getHostCellSVG(host_label))

                if "Mark" in column_headers:
                    bom_row_svg.append(getMarkCellSVG(mark_number))

                if "RebarsCount" in column_headers:
                    bom_row_svg.append(
                        getRebarsCountCellSVG(
                            mark_reinforcements_dict[mark_number]))

                if "Diameter" in column_headers:
                    bom_row_svg.append(getDiameterCellSVG(base_rebar.Diameter))

                base_rebar_length = FreeCAD.Units.Quantity("0 mm")
                if "RebarLength" in column_headers:
                    if rebar_length_type == "RealLength":
                        base_rebar_length = base_rebar.Length
                    else:
                        base_rebar_length = getRebarSharpEdgedLength(
                            base_rebar)
                    bom_row_svg.append(
                        getRebarLengthCellSVG(base_rebar_length))

                rebar_total_length = FreeCAD.Units.Quantity("0 mm")
                for reinforcement in mark_reinforcements_dict[mark_number]:
                    rebar_total_length += (reinforcement.Amount *
                                           base_rebar_length)
                dia_total_length_dict[
                    base_rebar.Diameter.Value] += rebar_total_length

                if "RebarsTotalLength" in column_headers:
                    bom_row_svg.extend(
                        getRebarTotalLengthCellsSVG(rebar_total_length))

                bom_table_svg.append(bom_row_svg)
                y_offset += row_height
                current_row += 1

    if "RebarsTotalLength" in column_headers:
        bom_table_svg.append(
            getSVGRectangle(
                0,
                y_offset,
                column_width * (len(column_headers) + len(diameter_list) - 1),
                row_height,
                "bom_table_cell_column_separator",
            ))
        y_offset += row_height

    # Display total length, weight/m and total weight of all rebars
    if "RebarsTotalLength" in column_headers:
        bom_data_total_svg = ElementTree.Element("g")
        bom_data_total_svg.set("id", "BOM_data_total")
        if list(column_headers.keys()).index("RebarsTotalLength") != 0:
            column_number = getColumnNumber(column_headers, diameter_list,
                                            "RebarsTotalLength")
            rebar_total_length_offset = column_width * (column_number - 1)

            bom_data_total_svg.append(
                getSVGDataCell(
                    "Total length in " + column_units["RebarsTotalLength"] +
                    "/Diameter",
                    0,
                    y_offset,
                    rebar_total_length_offset,
                    row_height,
                    font_family,
                    font_size,
                    "bom_table_cell_column_multi_column",
                    "bold",
                ))
            bom_data_total_svg.append(
                getSVGDataCell(
                    "Weight in Kg/" + column_units["RebarsTotalLength"],
                    0,
                    y_offset + row_height,
                    rebar_total_length_offset,
                    row_height,
                    font_family,
                    font_size,
                    "bom_table_cell_column_multi_column",
                    "bold",
                ))
            bom_data_total_svg.append(
                getSVGDataCell(
                    "Total Weight in Kg/Diameter",
                    0,
                    y_offset + 2 * row_height,
                    rebar_total_length_offset,
                    row_height,
                    font_family,
                    font_size,
                    "bom_table_cell_column_multi_column",
                    "bold",
                ))

            for i, dia in enumerate(diameter_list):
                disp_dia_total_length = str(
                    round(
                        dia_total_length_dict[dia.Value].getValueAs(
                            column_units["RebarsTotalLength"]).Value,
                        precision,
                    ))
                if "." in disp_dia_total_length:
                    disp_dia_total_length = disp_dia_total_length.rstrip(
                        "0").rstrip(".")
                disp_dia_total_length += " " + column_units["RebarsTotalLength"]

                bom_data_total_svg.append(
                    getSVGDataCell(
                        disp_dia_total_length,
                        rebar_total_length_offset + i * column_width,
                        y_offset,
                        column_width,
                        row_height,
                        font_family,
                        font_size,
                        "bom_table_cell_column_{}".format(column_number + i),
                    ))

                if dia.Value in dia_weight_map:
                    disp_dia_weight = str(
                        round(
                            dia_weight_map[dia.Value].getValueAs(
                                "kg/" +
                                column_units["RebarsTotalLength"]).Value,
                            precision,
                        ))
                    if "." in disp_dia_weight:
                        disp_dia_weight = disp_dia_weight.rstrip("0").rstrip(
                            ".")
                    disp_dia_weight += (" kg/" +
                                        column_units["RebarsTotalLength"])

                    bom_data_total_svg.append(
                        getSVGDataCell(
                            disp_dia_weight,
                            rebar_total_length_offset + i * column_width,
                            y_offset + row_height,
                            column_width,
                            row_height,
                            font_family,
                            font_size,
                            "bom_table_cell_column_{}".format(column_number +
                                                              i),
                        ))
                    disp_total_weight = str(
                        round(
                            (dia_weight_map[dia.Value] *
                             dia_total_length_dict[dia.Value]).Value,
                            precision,
                        ))
                    if "." in disp_total_weight:
                        disp_total_weight = disp_total_weight.rstrip(
                            "0").rstrip(".")
                    bom_data_total_svg.append(
                        getSVGDataCell(
                            disp_total_weight + " kg",
                            rebar_total_length_offset + i * column_width,
                            y_offset + 2 * row_height,
                            column_width,
                            row_height,
                            font_family,
                            font_size,
                            "bom_table_cell_column_{}".format(column_number +
                                                              i),
                        ))
                else:
                    bom_data_total_svg.append(
                        getSVGRectangle(
                            rebar_total_length_offset + i * column_width,
                            y_offset + row_height,
                            column_width,
                            row_height,
                            "bom_table_cell_column_{}".format(column_number +
                                                              i),
                        ))
                    bom_data_total_svg.append(
                        getSVGRectangle(
                            rebar_total_length_offset + i * column_width,
                            y_offset + 2 * row_height,
                            column_width,
                            row_height,
                            "bom_table_cell_column_{}".format(column_number +
                                                              i),
                        ))

            for remColumn in range(
                    len(column_headers) -
                    list(column_headers.keys()).index("RebarsTotalLength") -
                    1):
                rem_column_number = (
                    list(column_headers.keys()).index("RebarsTotalLength") +
                    1 + len(diameter_list) + remColumn)
                rem_column_offset = (column_number - 1) * column_width
                for row in range(3):
                    bom_data_total_svg.append(
                        getSVGRectangle(
                            rem_column_offset,
                            y_offset + row * row_height,
                            column_width,
                            row_height,
                            "bom_table_cell_column_{}".format(
                                rem_column_number),
                        ))
        else:
            for i, dia in enumerate(diameter_list):
                disp_dia_total_length = str(
                    round(
                        dia_total_length_dict[dia.Value].getValueAs(
                            column_units["RebarsTotalLength"]).Value,
                        precision,
                    ))
                if "." in disp_dia_total_length:
                    disp_dia_total_length = disp_dia_total_length.rstrip(
                        "0").rstrip(".")
                disp_dia_total_length += " " + column_units["RebarsTotalLength"]

                bom_data_total_svg.append(
                    getSVGDataCell(
                        disp_dia_total_length,
                        i * column_width,
                        y_offset,
                        column_width,
                        row_height,
                        font_family,
                        font_size,
                        "bom_table_cell_column_{}".format(i + 1),
                    ))

                if dia.Value in dia_weight_map:
                    disp_dia_weight = str(
                        round(
                            dia_weight_map[dia.Value].getValueAs(
                                "kg/" +
                                column_units["RebarsTotalLength"]).Value,
                            precision,
                        ))
                    if "." in disp_dia_weight:
                        disp_dia_weight = disp_dia_weight.rstrip("0").rstrip(
                            ".")
                    disp_dia_weight += (" kg/" +
                                        column_units["RebarsTotalLength"])

                    bom_data_total_svg.append(
                        getSVGDataCell(
                            disp_dia_weight,
                            i * column_width,
                            y_offset + row_height,
                            column_width,
                            row_height,
                            font_family,
                            font_size,
                            "bom_table_cell_column_{}".format(i + 1),
                        ))
                    disp_total_weight = str(
                        round(
                            (dia_weight_map[dia.Value] *
                             dia_total_length_dict[dia.Value]).Value,
                            precision,
                        ))
                    if "." in disp_total_weight:
                        disp_total_weight = disp_total_weight.rstrip(
                            "0").rstrip(".")
                    bom_data_total_svg.append(
                        getSVGDataCell(
                            disp_total_weight + " kg",
                            i * column_width,
                            y_offset + 2 * row_height,
                            column_width,
                            row_height,
                            font_family,
                            font_size,
                            "bom_table_cell_column_{}".format(i + 1),
                        ))

            first_txt_column_offset = len(diameter_list) * column_width
            bom_data_total_svg.append(
                getSVGDataCell(
                    "Total length in " + column_units["RebarsTotalLength"] +
                    "/Diameter",
                    "multi_column",
                    y_offset,
                    column_width * (len(column_headers) - 1),
                    row_height,
                    font_family,
                    font_size,
                    "bom_table_cell_column_{}".format(first_txt_column_offset),
                    "bold",
                ))
            bom_data_total_svg.append(
                getSVGDataCell(
                    "Weight in Kg/" + column_units["RebarsTotalLength"],
                    "multi_column",
                    y_offset + row_height,
                    column_width * (len(column_headers) - 1),
                    row_height,
                    font_family,
                    font_size,
                    "bom_table_cell_column_{}".format(first_txt_column_offset),
                    "bold",
                ))
            bom_data_total_svg.append(
                getSVGDataCell(
                    "Total Weight in Kg/Diameter",
                    first_txt_column_offset,
                    y_offset + 2 * row_height,
                    column_width * (len(column_headers) - 1),
                    row_height,
                    font_family,
                    font_size,
                    "bom_table_cell_column_multi_column",
                    "bold",
                ))
        y_offset += 3 * row_height
        bom_table_svg.append(bom_data_total_svg)
    svg.append(bom_table_svg)

    bom_height = y_offset
    if "RebarsTotalLength" in column_headers:
        bom_width = column_width * (len(column_headers) + len(diameter_list) -
                                    1)
    else:
        bom_width = column_width * len(column_headers)
    svg.set("width", str(bom_width) + "mm")
    svg.set("height", str(bom_height) + "mm")
    svg.set("viewBox", "0 0 {} {}".format(bom_width, bom_height))
    if return_svg_only:
        return svg

    svg_output = minidom.parseString(
        ElementTree.tostring(svg, encoding="unicode")).toprettyxml(indent="  ")

    bom_obj = makeBOMObject(template_file)
    template_height = bom_obj.Template.Height.Value
    template_width = bom_obj.Template.Width.Value

    scaling_factor = getTechdrawViewScalingFactor(
        bom_width,
        bom_height,
        bom_left_offset,
        bom_top_offset,
        template_width,
        template_height,
        bom_min_right_offset,
        bom_min_bottom_offset,
        bom_table_svg_max_width,
        bom_table_svg_max_height,
    )

    bom_content_obj = bom_obj.Views[0]
    bom_content_obj.Symbol = svg_output
    bom_content_obj.Font = font_family
    bom_content_obj.FontFilename = font_filename
    bom_content_obj.FontSize = font_size
    bom_content_obj.Template = bom_obj.Template
    bom_content_obj.PrefColumnWidth = column_width
    bom_content_obj.RowHeight = row_height
    bom_content_obj.Width = bom_width
    bom_content_obj.Height = bom_height
    bom_content_obj.LeftOffset = bom_left_offset
    bom_content_obj.TopOffset = bom_top_offset
    bom_content_obj.MinRightOffset = bom_min_right_offset
    bom_content_obj.MinBottomOffset = bom_min_bottom_offset
    bom_content_obj.MaxWidth = bom_table_svg_max_width
    bom_content_obj.MaxHeight = bom_table_svg_max_height
    bom_content_obj.X = bom_width * scaling_factor / 2 + bom_left_offset
    bom_content_obj.Y = (template_height - bom_height * scaling_factor / 2 -
                         bom_top_offset)
    bom_content_obj.Scale = scaling_factor
    bom_content_obj.recompute()

    template_svg = ""
    try:
        with open(template_file, "r") as template:
            template_svg = template.read()
    except OSError:
        FreeCAD.Console.PrintError("Error reading template file " +
                                   str(template_file) + "\n")

    if output_file:
        svg_sheet = minidom.parseString(
            ElementTree.tostring(bom_table_svg,
                                 encoding="unicode")).toprettyxml(indent="  ")

        if template_svg:
            bom_table_svg.set(
                "transform",
                "translate({} {}) scale({})".format(bom_left_offset,
                                                    bom_top_offset,
                                                    scaling_factor),
            )
            bom_svg = minidom.parseString(
                ElementTree.tostring(
                    bom_table_svg,
                    encoding="unicode")).toprettyxml(indent="  ")
            # Remove xml declaration as XML declaration allowed only at the
            # start of the document
            if "<?xml" in bom_svg.splitlines()[0]:
                bom_svg = bom_svg.lstrip(bom_svg.splitlines()[0])
            svg_sheet = template_svg.replace("<!-- DrawingContent -->",
                                             bom_svg)

        try:
            with open(output_file, "w", encoding="utf-8") as svg_output_file:
                svg_output_file.write(svg_sheet)
        except OSError:
            FreeCAD.Console.PrintError("Error writing svg to file " +
                                       str(svg_output_file) + "\n")

    FreeCAD.ActiveDocument.recompute()
    return bom_obj
Ejemplo n.º 3
0
def getBarBendingSchedule(
    rebar_objects: Optional[List] = None,
    column_headers: Optional[Dict[str, Tuple[str, int]]] = None,
    column_units: Optional[Dict[str, str]] = None,
    dia_weight_map: Optional[Dict[float, FreeCAD.Units.Quantity]] = None,
    rebar_length_type: Optional[Literal["RealLength",
                                        "LengthWithSharpEdges"]] = None,
    reinforcement_group_by: Optional[Literal["Mark", "Host"]] = None,
    font_family: Optional[str] = None,
    font_size: float = 5,
    column_width: float = 60,
    row_height: float = 30,
    rebar_shape_column_header: str = "Rebar Shape (mm)",
    rebar_shape_view_directions: Union[
        Union[FreeCAD.Vector, WorkingPlane.Plane],
        List[Union[FreeCAD.Vector,
                   WorkingPlane.Plane]], ] = FreeCAD.Vector(0, 0, 0),
    rebar_shape_stirrup_extended_edge_offset: float = 2,
    rebar_shape_color_style: str = "shape color",
    rebar_shape_stroke_width: float = 0.35,
    rebar_shape_include_dimensions: bool = True,
    rebar_shape_dimension_font_size: float = 3,
    rebar_shape_edge_dimension_units: str = "mm",
    rebar_shape_edge_dimension_precision: int = 0,
    include_edge_dimension_units_in_dimension_label: bool = False,
    rebar_shape_bent_angle_dimension_exclude_list: Union[List[float],
                                                         Tuple[float,
                                                               ...]] = (45, 90,
                                                                        180),
    helical_rebar_dimension_label_format: str = "%L,r=%R,pitch=%P",
    output_file: Optional[str] = None,
) -> ElementTree.Element:
    """Generate Bar Bending Schedule svg.

    Parameters
    ----------
    rebar_objects : list of <ArchRebar._Rebar> and <rebar2.BaseRebar>, optional
        Rebars list to generate bar bending schedule.
        If None, then all ArchRebars and rebar2.BaseRebar objects with unique
        Mark from ActiveDocument will be selected and rebars with no Mark
        assigned will be ignored.
        Default is None.
    column_headers : Dict[str, Tuple[str, int]], optional
        A dictionary with keys: "Mark", "RebarsCount", "Diameter",
        "RebarLength", "RebarsTotalLength" and values are tuple of column_header
        and its sequence number.
            e.g. {
                    "Host": ("Member", 1),
                    "Mark": ("Mark", 2),
                    "RebarsCount": ("No. of Rebars", 3),
                    "Diameter": ("Diameter in mm", 4),
                    "RebarLength": ("Length in m/piece", 5),
                    "RebarsTotalLength": ("Total Length in m", 6),
                }
            set column sequence number to 0 to hide column.
        Default is None, to select from FreeCAD Reinforcement BOM preferences.
    column_units : Dict[str, str], optional
        column_units is a dictionary with keys: "Diameter", "RebarLength",
        "RebarsTotalLength" and their corresponding units as value.
            e.g. {
                    "Diameter": "mm",
                    "RebarLength": "m",
                    "RebarsTotalLength": "m",
                }
        Default is None, to select from FreeCAD Reinforcement BOM preferences.
    dia_weight_map : Dict[float, FreeCAD.Units.Quantity], optional
        A dictionary with diameter as key and corresponding weight (kg/m) as
        value.
            e.g. {
                    6: FreeCAD.Units.Quantity("0.222 kg/m"),
                    8: FreeCAD.Units.Quantity("0.395 kg/m"),
                    10: FreeCAD.Units.Quantity("0.617 kg/m"),
                    12: FreeCAD.Units.Quantity("0.888 kg/m"),
                    ...,
                }
        Default is None, to select from FreeCAD Reinforcement BOM preferences.
    rebar_length_type : {"RealLength", "LengthWithSharpEdges"}, optional
        The rebar length calculations type.
        "RealLength": length of rebar considering rounded edges.
        "LengthWithSharpEdges": length of rebar assuming sharp edges of rebar.
        Default is None, to select from FreeCAD Reinforcement BOM preferences.
    reinforcement_group_by: {"Mark", "Host"}, optional
        Specifies how reinforcement objects should be grouped.
        Default is None, to select from FreeCAD Reinforcement BOM preferences.
    font_family : str, optional
        The font-family of text.
        Default is None, to select from FreeCAD Reinforcement BOM preferences.
    font_size : float
        The font-size of text.
        Default is 5
    column_width : float
        The width of each column in bar shape cut list.
        Default is 60
    row_height : float
        The height of each row in bar shape cut list.
        Default is 30
    rebar_shape_column_header : str
        The column header for rebar shape column.
        Default is "Rebar Shape (mm)"
    rebar_shape_view_directions : FreeCAD.Vector or WorkingPlane.Plane
                                  OR their list
        The view point directions for each rebar shape.
        Default is FreeCAD.Vector(0, 0, 0) to automatically choose
        view_directions.
    rebar_shape_stirrup_extended_edge_offset : float
        The offset of extended end edges of stirrup, so that end edges of
        stirrup with 90 degree bent angle do not overlap with stirrup edges.
        Default is 2
    rebar_shape_color_style : {"shape color", "color_name","hex_value_of_color"}
        The color style of rebars in rebar shape svg.
        "shape color" means select color of rebar shape.
    rebar_shape_stroke_width : float
        The stroke-width of rebars in rebar shape svg.
        Default is 0.35
    rebar_shape_include_dimensions : bool
        If True, then each rebar edge dimensions and bent angle dimensions will
        be included in rebar shape svg.
        Default is True.
    rebar_shape_dimension_font_size: float
        The font size of dimension text in rebar shape svg.
        Default is 3
    rebar_shape_edge_dimension_units : str
        The units to be used for rebar length dimensions in rebar shape svg.
        Default is "mm".
    rebar_shape_edge_dimension_precision : int
        The number of decimals that should be shown for rebar length as
        dimension label in rebar shape svg. Set it to None to use user preferred
        unit precision from FreeCAD unit preferences.
        Default is 0
    include_edge_dimension_units_in_dimension_label : bool
        If it is True, then rebar length units will be shown in dimension label
        in rebar shape svg.
        Default is False.
    rebar_shape_bent_angle_dimension_exclude_list : tuple of float
        The tuple of bent angles to not include their dimensions in rebar shape.
        Default is (45, 90, 180).
    helical_rebar_dimension_label_format : str
        The format of helical rebar dimension label in rebar shape svg.
            %L -> Length of helical rebar
            %R -> Helix radius of helical rebar
            %P -> Helix pitch of helical rebar
        Default is "%L,r=%R,pitch=%P".
    output_file: str, optional
        The output file to write generated svg.

    Returns
    -------
    ElementTree.Element
        The generated bar bending schedule svg.
    """
    rebar_objects = getReinforcementRebarObjects(rebar_objects)
    bom_preferences = BOMPreferences()
    if not column_headers:
        column_headers = bom_preferences.getColumnHeaders()
    if not column_units:
        column_units = bom_preferences.getColumnUnits()
    column_units = fixColumnUnits(column_units or {})
    if not reinforcement_group_by:
        reinforcement_group_by = bom_preferences.getReinforcementGroupBy()

    svg_pref = bom_preferences.getSVGPrefGroup()
    if not font_family:
        font_family = svg_pref.GetString("FontFamily")
    if not font_size:
        font_size = svg_pref.GetFloat("FontSize")

    svg = getSVGRootElement()
    bbs_svg = ElementTree.Element("g", attrib={"id": "BBS"})
    svg.append(bbs_svg)

    bom_svg = makeBillOfMaterialSVG(
        column_headers,
        column_units,
        dia_weight_map,
        rebar_length_type,
        font_family,
        font_size=font_size,
        column_width=column_width,
        row_height=row_height,
        rebar_objects=rebar_objects,
        reinforcement_group_by=reinforcement_group_by,
        return_svg_only=True,
    )
    bom_table_svg = bom_svg.find("./g[@id='BOM_table']")
    bbs_svg.append(bom_table_svg)

    bom_width = float(bom_svg.get("width").replace("mm", ""))
    column_header_height = row_height * (2 if "RebarsTotalLength"
                                         in column_headers else 1)
    # Add column header for rebar shape cut list
    rebar_shape_cut_list_header = getSVGDataCell(
        rebar_shape_column_header,
        bom_width,
        0,
        column_width,
        column_header_height,
        font_family,
        font_size,
        font_weight="bold",
    )
    bbs_svg.append(rebar_shape_cut_list_header)

    base_rebars_list = []
    if reinforcement_group_by == "Mark":
        base_rebars_list = getBaseRebarsList(rebar_objects)
    else:
        host_reinforcement_dict = getHostReinforcementsDict(rebar_objects)
        for reinforcement_list in host_reinforcement_dict.values():
            base_rebars_list.extend(getBaseRebarsList(reinforcement_list))

    bar_cut_list_svg = getRebarShapeCutList(
        base_rebars_list,
        rebar_shape_view_directions,
        False if "Mark" in column_headers and column_headers["Mark"][1] != 0
        else True,
        rebar_shape_stirrup_extended_edge_offset,
        rebar_shape_stroke_width,
        rebar_shape_color_style,
        rebar_shape_include_dimensions,
        rebar_shape_edge_dimension_units,
        rebar_shape_edge_dimension_precision,
        include_edge_dimension_units_in_dimension_label,
        rebar_shape_bent_angle_dimension_exclude_list,
        font_family,
        rebar_shape_dimension_font_size,
        helical_rebar_dimension_label_format,
        row_height,
        column_width,
        column_count=1,
        horizontal_rebar_shape=True,
    ).find("./g[@id='RebarShapeCutList']")
    bbs_svg.append(bar_cut_list_svg)

    # Translate rebar shape cut list to last column and set top offset =
    # height of column headers
    bar_cut_list_svg.set(
        "transform", "translate({} {})".format(bom_width,
                                               column_header_height))

    total_separator = bom_svg.find(
        ".//*[@id='bom_table_cell_column_separator']", )
    total_separator_width = float(total_separator.get("width"))
    total_separator.set("width", str(total_separator_width + column_width))
    total_separator_y = float(total_separator.get("y"))
    total_separator_height = float(total_separator.get("height"))
    for x in range(0, 3):
        bbs_svg.append(
            getSVGRectangle(
                bom_width,
                total_separator_y + total_separator_height + x * row_height,
                column_width,
                row_height,
            ))

    bom_height = float(bom_svg.get("height").replace("mm", ""))
    svg_width = bom_width + column_width
    svg.set("width", str(svg_width) + "mm")
    svg.set("height", str(bom_height) + "mm")
    svg.set("viewBox", "0 0 {} {}".format(svg_width, bom_height))

    if output_file:
        svg_sheet = minidom.parseString(
            ElementTree.tostring(svg,
                                 encoding="unicode")).toprettyxml(indent="  ")
        try:
            with open(output_file, "w", encoding="utf-8") as svg_output_file:
                svg_output_file.write(svg_sheet)
        except OSError:
            FreeCAD.Console.PrintError("Error writing svg to file " +
                                       str(svg_output_file) + "\n")

    return svg
def getRebarShapeCutList(
    base_rebars_list: Optional[List] = None,
    view_directions: Union[Union[FreeCAD.Vector, WorkingPlane.Plane],
                           List[Union[FreeCAD.Vector,
                                      WorkingPlane.Plane]], ] = FreeCAD.Vector(
                                          0, 0, 0),
    include_mark: bool = True,
    stirrup_extended_edge_offset: float = 2,
    rebars_stroke_width: float = 0.35,
    rebars_color_style: str = "shape color",
    include_dimensions: bool = True,
    rebar_edge_dimension_units: str = "mm",
    rebar_edge_dimension_precision: int = 0,
    include_units_in_dimension_label: bool = False,
    bent_angle_dimension_exclude_list: Union[Tuple[float, ...],
                                             List[float]] = (
                                                 45,
                                                 90,
                                                 180,
                                             ),
    dimension_font_family: str = "DejaVu Sans",
    dimension_font_size: float = 2,
    helical_rebar_dimension_label_format: str = "%L,r=%R,pitch=%P",
    row_height: float = 40,
    column_width: float = 60,
    column_count: Union[int, Literal["row_count"]] = "row_count",
    side_padding: float = 1,
    horizontal_rebar_shape: bool = True,
    output_file: Optional[str] = None,
) -> ElementTree.Element:
    """Generate and return rebar shape cut list svg.

    Parameters
    ----------
    base_rebars_list: list of <ArchRebar._Rebar> or <rebar2.BaseRebar>, optional
        Rebars list to generate RebarShape cut list.
        If None, then all ArchRebars and rebar2.BaseRebar objects with unique
        Mark from ActiveDocument will be selected and rebars with no Mark
        assigned will be ignored.
        Default is None.
    view_directions: FreeCAD.Vector or WorkingPlane.Plane OR their list
        The view point directions for each rebar shape.
        Default is FreeCAD.Vector(0, 0, 0) to automatically choose
        view_directions.
    include_mark: bool
        If it is set to True, then rebar.Mark will be included for each rebar
        shape in rebar shape cut list svg.
        Default is True.
    stirrup_extended_edge_offset: float
        The offset of extended end edges of stirrup, so that end edges of
        stirrup with 90 degree bent angle do not overlap with stirrup edges.
        Default is 2.
    rebars_stroke_width: float
        The stroke-width of rebars in rebar shape cut list svg.
        Default is 0.35
    rebars_color_style: {"shape color", "color_name", "hex_value_of_color"}
        The color style of rebars.
        "shape color" means select color of rebar shape.
    include_dimensions: bool
        If True, then each rebar edge dimensions and bent angle dimensions will
        be included in rebar shape cut list.
    rebar_edge_dimension_units: str
        The units to be used for rebar edge length dimensions.
        Default is "mm".
    rebar_edge_dimension_precision: int
        The number of decimals that should be shown for rebar edge length as
        dimension label. Set it to None to use user preferred unit precision
        from FreeCAD unit preferences.
        Default is 0
    include_units_in_dimension_label: bool
        If it is True, then rebar edge length units will be shown in dimension
        label.
        Default is False.
    bent_angle_dimension_exclude_list: list or tuple of float
        The list of bent angles to not include their dimensions.
        Default is (45, 90, 180).
    dimension_font_family: str
        The font-family of dimension text.
        Default is "DejaVu Sans".
    dimension_font_size: float
        The font-size of dimension text.
        Default is 2
    helical_rebar_dimension_label_format: str
        The format of helical rebar dimension label.
            %L -> Length of helical rebar
            %R -> Helix radius of helical rebar
            %P -> Helix pitch of helical rebar
        Default is "%L,r=%R,pitch=%P".
    row_height: float
        The height of each row of rebar shape in rebar shape cut list.
        Default is 40
    column_width: float
        The width of each column of rebar shape in rebar shape cut list.
        Default is 60
    column_count: int, {"row_count"}
        The number of columns in rebar shape cut list.
        "row_count" means column_count <= row_count
        Default is "row_count".
    side_padding: float
        The padding on each side of rebar shape in rebar shape cut list.
        Default is 1.
    horizontal_rebar_shape: bool
        If True, then rebar shape will be made horizontal by rotating max
        length edge of rebar shape.
        Default is True.
    output_file: str, optional
        The output file to write generated rebar shape cut list svg.

    Returns
    -------
    ElementTree.Element
        The rebar shape cut list svg.
    """
    if base_rebars_list is None:
        base_rebars_list = getBaseRebarsList()

    if not base_rebars_list:
        return ElementTree.Element(
            "svg",
            height="{}mm".format(row_height),
            width="{}mm".format(column_width),
            viewBox="0 0 {} {}".format(column_width, row_height),
        )

    if isinstance(view_directions, FreeCAD.Vector) or isinstance(
            view_directions, WorkingPlane.Plane):
        view_directions = len(base_rebars_list) * [view_directions]
    elif isinstance(view_directions, list):
        if len(view_directions) < len(base_rebars_list):
            view_directions.extend(
                (len(base_rebars_list) - len(view_directions)) *
                FreeCAD.Vector(0, 0, 0))
        else:
            view_directions = view_directions[len(base_rebars_list
                                                  ):  # noqa: E203
                                              ]

    rebar_shape_max_height = row_height
    if include_mark:
        rebar_shape_max_height -= 2 * dimension_font_size

    svg = getSVGRootElement()
    rebar_shape_cut_list = ElementTree.Element(
        "g", attrib={"id": "RebarShapeCutList"})
    svg.append(rebar_shape_cut_list)

    if column_count == "row_count":
        column_count = max(x for x in list(range(1,
                                                 len(base_rebars_list) + 1))
                           if x**2 <= len(base_rebars_list))
    else:
        column_count = min(column_count, len(base_rebars_list))

    row = 1
    for i, rebar in enumerate(base_rebars_list):
        column = (i % column_count) + 1
        row = int(i / column_count) + 1
        rebar_svg = getRebarShapeSVG(
            rebar,
            view_directions[i],
            False,
            stirrup_extended_edge_offset,
            rebars_stroke_width,
            rebars_color_style,
            include_dimensions,
            rebar_edge_dimension_units,
            rebar_edge_dimension_precision,
            include_units_in_dimension_label,
            bent_angle_dimension_exclude_list,
            dimension_font_family,
            dimension_font_size,
            helical_rebar_dimension_label_format,
            max_height=rebar_shape_max_height,
            max_width=column_width,
            side_padding=side_padding,
            horizontal_shape=horizontal_rebar_shape,
        )
        # Center align rebar shape svg horizontally and vertically in row cell
        rebar_shape_svg_width = float(rebar_svg.get("width").rstrip("mm"))
        rebar_shape_svg_height = float(rebar_svg.get("height").rstrip("mm"))
        rebar_shape_svg = ElementTree.Element(
            "g",
            transform="translate({} {})".format(
                (column_width - rebar_shape_svg_width) / 2,
                (rebar_shape_max_height - rebar_shape_svg_height) / 2 +
                (2 * dimension_font_size if include_mark else 0),
            ),
        )
        rebar_shape_svg.append(
            rebar_svg.find("./g[@id='{}']".format(rebar.Name)))
        # Create cell border svg
        cell_border_svg = getSVGRectangle(
            0,
            0,
            column_width,
            row_height,
            element_id="row_{}_column_{}".format(row, column),
        )
        # Create row svg and translate it horizontally and vertically to its
        # position
        cell_svg = ElementTree.Element(
            "g",
            transform="translate({} {})".format((column - 1) * column_width,
                                                (row - 1) * row_height),
        )
        cell_svg.extend([cell_border_svg, rebar_shape_svg])
        # Include mark label in each row
        if include_mark:
            if hasattr(rebar, "Mark"):
                mark = rebar.Mark
            elif hasattr(rebar, "MarkNumber"):
                mark = rebar.MarkNumber
            else:
                mark = ""
            cell_svg.append(
                getSVGTextElement(
                    mark,
                    2,
                    2 * dimension_font_size,
                    dimension_font_family,
                    1.5 * dimension_font_size,
                ))
        rebar_shape_cut_list.append(cell_svg)
        # Add rectangular cells to last row for unfilled columns
        if i == len(base_rebars_list) - 1:
            for rem_col_index in range(column + 1, column_count + 1):
                cell_border_svg = getSVGRectangle(
                    0,
                    0,
                    column_width,
                    row_height,
                    element_id="row_{}_column_{}".format(row, rem_col_index),
                )
                cell_svg = ElementTree.Element(
                    "g",
                    transform="translate({} {})".format(
                        (rem_col_index - 1) * column_width,
                        (row - 1) * row_height,
                    ),
                )
                cell_svg.append(cell_border_svg)
                rebar_shape_cut_list.append(cell_svg)

    svg_width = column_count * column_width
    svg_height = row * row_height
    svg.set("width", "{}mm".format(svg_width))
    svg.set("height", "{}mm".format(svg_height))
    svg.set(
        "viewBox",
        "0 0 {} {}".format(svg_width, svg_height),
    )

    if output_file:
        svg_sheet = minidom.parseString(
            ElementTree.tostring(svg,
                                 encoding="unicode")).toprettyxml(indent="  ")
        try:
            with open(output_file, "w", encoding="utf-8") as svg_output_file:
                svg_output_file.write(svg_sheet)
        except OSError:
            FreeCAD.Console.PrintError("Error writing svg to file " +
                                       str(svg_output_file) + "\n")

    return svg
def getRebarShapeSVG(
    rebar,
    view_direction: Union[FreeCAD.Vector,
                          WorkingPlane.Plane] = FreeCAD.Vector(0, 0, 0),
    include_mark: bool = True,
    stirrup_extended_edge_offset: float = 2,
    rebar_stroke_width: float = 0.35,
    rebar_color_style: str = "shape color",
    include_dimensions: bool = True,
    rebar_dimension_units: str = "mm",
    rebar_length_dimension_precision: int = 0,
    include_units_in_dimension_label: bool = False,
    bent_angle_dimension_exclude_list: Union[Tuple[float, ...],
                                             List[float]] = (
                                                 45,
                                                 90,
                                                 180,
                                             ),
    dimension_font_family: str = "DejaVu Sans",
    dimension_font_size: float = 2,
    helical_rebar_dimension_label_format: str = "%L,r=%R,pitch=%P",
    scale: float = 1,
    max_height: float = 0,
    max_width: float = 0,
    side_padding: float = 1,
    horizontal_shape: bool = False,
) -> ElementTree.Element:
    """Generate and return rebar shape svg.

    Parameters
    ----------
    rebar: <ArchRebar._Rebar> or <rebar2.BaseRebar>
        Rebar to generate its shape svg.
    view_direction: FreeCAD.Vector or WorkingPlane.Plane, optional
        The view point direction for rebar shape.
        Default is FreeCAD.Vector(0, 0, 0) to automatically choose
        view_direction.
    include_mark: bool, optional
        If True, then rebar.Mark will be included in rebar shape svg.
        Default is True.
    stirrup_extended_edge_offset: float, optional
        The offset of extended end edges of stirrup, so that end edges of
        stirrup with 90 degree bent angle do not overlap with stirrup edges.
        Default is 2.
    rebar_stroke_width: float, optional
        The stroke-width of rebar in svg.
        Default is 0.35
    rebar_color_style: {"shape color", "color_name", "hex_value_of_color"}
        The color style of rebar.
        "shape color" means select color of rebar shape.
    include_dimensions: bool, optional
        If True, then each rebar edge dimensions and bent angle dimensions will
        be included in rebar shape svg.
    rebar_dimension_units: str, optional
        The units to be used for rebar length dimensions.
        Default is "mm".
    rebar_length_dimension_precision: int, optional
        The number of decimals that should be shown for rebar length as
        dimension label. Set it to None to use user preferred unit precision
        from FreeCAD unit preferences.
        Default is 0
    include_units_in_dimension_label: bool, optional
        If it is True, then rebar length units will be shown in dimension label.
        Default is False.
    bent_angle_dimension_exclude_list: list or tuple of float, optional
        The list of bent angles to not include their dimensions.
        Default is (45, 90, 180).
    dimension_font_family: str, optional
        The font-family of dimension text.
        Default is "DejaVu Sans".
    dimension_font_size: float, optional
        The font-size of dimension text.
        Default is 2
    helical_rebar_dimension_label_format: str, optional
        The format of helical rebar dimension label.
            %L -> Length of helical rebar
            %R -> Helix radius of helical rebar
            %P -> Helix pitch of helical rebar
        Default is "%L,r=%R,pitch=%P".
    scale: float, optional
        The scale value to scale rebar svg. The scale parameter helps to
        scale down rebar_stroke_width and dimension_font_size to make them
        resolution independent.
        If max_height or max_width is set to non-zero value, then scale
        parameter will be ignored.
        Default is 1
    max_height: float, optional
        The maximum height of rebar shape svg.
        Default is 0 to set rebar shape svg height based on scale parameter.
    max_width: float, optional
        The maximum width of rebar shape svg.
        Default is 0 to set rebar shape svg width based on scale parameter.
    side_padding: float, optional
        The padding on each side of rebar shape.
        Default is 1.
    horizontal_shape: bool, optional
        If True, then rebar shape will be made horizontal by rotating max
        length edge of rebar shape.
        Default is False.

    Returns
    -------
    ElementTree.Element
        The generated rebar shape svg.
    """
    if isinstance(view_direction, FreeCAD.Vector):
        if DraftVecUtils.isNull(view_direction):
            if (hasattr(rebar, "RebarShape")
                    and rebar.RebarShape == "HelicalRebar"):
                view_direction = rebar.Base.Placement.Rotation.multVec(
                    FreeCAD.Vector(0, -1, 0))
                if hasattr(rebar, "Direction") and not DraftVecUtils.isNull(
                        rebar.Direction):
                    view_direction = FreeCAD.Vector(rebar.Direction)
                    view_direction.normalize()
            else:
                view_direction = getRebarsSpanAxis(rebar)
        view_plane = getSVGPlaneFromAxis(view_direction)
    elif isinstance(view_direction, WorkingPlane.Plane):
        view_plane = view_direction
    else:
        FreeCAD.Console.PrintError(
            "Invalid view_direction type. Supported view_direction types: "
            "FreeCAD.Vector, WorkingPlane.Plane\n")
        return ElementTree.Element("g")

    if rebar_length_dimension_precision is None:
        # Get user preferred unit precision
        precision: int = FreeCAD.ParamGet(
            "User parameter:BaseApp/Preferences/Units").GetInt("Decimals")
    else:
        precision = abs(int(rebar_length_dimension_precision))

    rebar_color = getRebarColor(rebar, rebar_color_style)

    # Create required svg elements
    svg = getSVGRootElement()
    rebar_shape_svg = ElementTree.Element("g", attrib={"id": str(rebar.Name)})
    svg.append(rebar_shape_svg)
    rebar_edges_svg = ElementTree.Element("g")
    edge_dimension_svg = ElementTree.Element("g")
    rebar_shape_svg.extend([rebar_edges_svg, edge_dimension_svg])

    # Get basewire and fillet_basewire (basewire with round edges)
    basewire = rebar.Base.Shape.Wires[0].copy()
    fillet_radius = rebar.Rounding * rebar.Diameter.Value
    if fillet_radius:
        fillet_basewire = DraftGeomUtils.filletWire(basewire, fillet_radius)
    else:
        fillet_basewire = basewire

    (
        rebar_shape_min_x,
        rebar_shape_min_y,
        rebar_shape_max_x,
        rebar_shape_max_y,
    ) = getVertexesMinMaxXY(fillet_basewire.Vertexes, view_plane)

    # If rebar shape should be horizontal and its width is less than its
    # height, then we should rotate basewire to make rebar shape horizontal
    rebar_shape_rotation_angle = 0
    if horizontal_shape:
        line_type_edges = [
            edge for edge in basewire.Edges
            if DraftGeomUtils.geomType(edge) == "Line"
        ]
        if line_type_edges:
            max_length_edge = max(line_type_edges, key=lambda x: x.Length)
            rebar_shape_rotation_angle = math.degrees(
                DraftVecUtils.angle(
                    max_length_edge.lastVertex().Point.sub(
                        max_length_edge.firstVertex().Point),
                    view_plane.u,
                    view_plane.axis,
                ))
        elif (rebar_shape_max_x - rebar_shape_min_x) < (rebar_shape_max_y -
                                                        rebar_shape_min_y):
            rebar_shape_rotation_angle = -90
        basewire.rotate(basewire.CenterOfMass, view_plane.axis,
                        rebar_shape_rotation_angle)

        fillet_radius = rebar.Rounding * rebar.Diameter.Value
        if fillet_radius:
            fillet_basewire = DraftGeomUtils.filletWire(
                basewire, fillet_radius)
        else:
            fillet_basewire = basewire

        (
            rebar_shape_min_x,
            rebar_shape_min_y,
            rebar_shape_max_x,
            rebar_shape_max_y,
        ) = getVertexesMinMaxXY(fillet_basewire.Vertexes, view_plane)

    # Check if stirrup will be having extended edges separated apart
    if (hasattr(rebar, "RebarShape") and rebar.RebarShape == "Stirrup"
            and hasattr(rebar, "BentAngle") and rebar.BentAngle == 90):
        apply_stirrup_extended_edge_offset = True
    else:
        apply_stirrup_extended_edge_offset = False

    # Apply max_height and max_width of rebar shape svg And calculate scaling
    # factor
    rebar_shape_height = (rebar_shape_max_y - rebar_shape_min_y) or 1
    rebar_shape_width = (rebar_shape_max_x - rebar_shape_min_x) or 1
    h_scaling_factor = v_scaling_factor = scale
    if max_height:
        v_scaling_factor = (
            max_height - dimension_font_size *
            ((2 if include_mark else 0) +
             (2 if include_dimensions else 0)) - 2 * side_padding -
            (stirrup_extended_edge_offset
             if apply_stirrup_extended_edge_offset and (round(
                 getProjectionToSVGPlane(
                     Part.__sortEdges__(basewire.Edges)[0].firstVertex().Point,
                     view_plane,
                 ).y) in (round(rebar_shape_min_y), round(rebar_shape_max_y)))
             else 0)) / rebar_shape_height
    if max_width:
        h_scaling_factor = (
            max_width - dimension_font_size *
            (2 if include_dimensions else 0) - 2 * side_padding -
            (stirrup_extended_edge_offset
             if apply_stirrup_extended_edge_offset and (round(
                 getProjectionToSVGPlane(
                     Part.__sortEdges__(basewire.Edges)[0].firstVertex().Point,
                     view_plane,
                 ).x) in (round(rebar_shape_min_x), round(rebar_shape_max_x)))
             else 0)) / rebar_shape_width
    scale = min(h_scaling_factor, v_scaling_factor)
    svg_height = (
        rebar_shape_height * scale + dimension_font_size *
        ((2 if include_mark else 0) +
         (2 if include_dimensions else 0)) + 2 * side_padding +
        (stirrup_extended_edge_offset if apply_stirrup_extended_edge_offset and
         (round(
             getProjectionToSVGPlane(
                 Part.__sortEdges__(basewire.Edges)[0].firstVertex().Point,
                 view_plane,
             ).y) in (round(rebar_shape_min_y), round(rebar_shape_max_y))) else
         0))
    svg_width = (
        rebar_shape_width * scale + dimension_font_size *
        (2 if include_dimensions else 0) + 2 * side_padding +
        (stirrup_extended_edge_offset if apply_stirrup_extended_edge_offset and
         (round(
             getProjectionToSVGPlane(
                 Part.__sortEdges__(basewire.Edges)[0].firstVertex().Point,
                 view_plane,
             ).x) in (round(rebar_shape_min_x), round(rebar_shape_max_x))) else
         0))

    # Move (min_x, min_y) point in svg plane to (0, 0) so that entire basewire
    # should be visible in svg view box and apply required scaling
    translate_x = round(
        -(rebar_shape_min_x -
          (dimension_font_size if include_dimensions else 0) / scale -
          side_padding / scale -
          (stirrup_extended_edge_offset /
           scale if apply_stirrup_extended_edge_offset and (round(
               getProjectionToSVGPlane(
                   Part.__sortEdges__(basewire.Edges)[0].firstVertex().Point,
                   view_plane,
               ).x) == round(rebar_shape_min_x)) else 0)))
    translate_y = round(
        -(rebar_shape_min_y -
          ((2 if include_mark else 0) +
           (1 if include_dimensions else 0)) * dimension_font_size / scale -
          side_padding / scale -
          (stirrup_extended_edge_offset /
           scale if apply_stirrup_extended_edge_offset and (round(
               getProjectionToSVGPlane(
                   Part.__sortEdges__(basewire.Edges)[0].firstVertex().Point,
                   view_plane,
               ).y) == round(rebar_shape_min_y)) else 0)))
    rebar_shape_svg.set(
        "transform",
        "scale({}) translate({} {})".format(scale, translate_x, translate_y),
    )

    svg.set("width", "{}mm".format(round(svg_width)))
    svg.set("height", "{}mm".format(round(svg_height)))
    svg.set("viewBox", "0 0 {} {}".format(round(svg_width), round(svg_height)))

    # Scale down rebar_stroke_width and dimension_font_size to make them
    # resolution independent
    rebar_stroke_width /= scale
    dimension_font_size /= scale

    # Include rebar.Mark in rebar shape svg
    if include_mark:
        if hasattr(rebar, "Mark"):
            mark = rebar.Mark
        elif hasattr(rebar, "MarkNumber"):
            mark = rebar.MarkNumber
        else:
            mark = ""
        rebar_shape_svg.append(
            getSVGTextElement(
                mark,
                rebar_shape_min_x,
                rebar_shape_min_y -
                (0.5 + bool(include_dimensions)) * dimension_font_size,
                dimension_font_family,
                1.5 * dimension_font_size,
            ))

    if hasattr(rebar, "RebarShape") and rebar.RebarShape == "HelicalRebar":
        helical_rebar_shape_svg = Draft.getSVG(
            rebar,
            direction=view_plane,
            linewidth=rebar_stroke_width,
            fillstyle="none",
            color=rebar_color,
        )
        if helical_rebar_shape_svg:
            helical_rebar_shape_svg_element = ElementTree.fromstring(
                "<g>{}</g>".format(helical_rebar_shape_svg))
            rebar_edges_svg.append(helical_rebar_shape_svg_element)
            helical_rebar_center = getProjectionToSVGPlane(
                rebar.Base.Shape.CenterOfMass, view_plane)
            helical_rebar_shape_svg_element.set(
                "transform",
                "rotate({} {} {})".format(
                    rebar_shape_rotation_angle,
                    helical_rebar_center.x,
                    helical_rebar_center.y,
                ),
            )

        if include_dimensions:
            # Create rebar dimension svg
            top_mid_point = FreeCAD.Vector(
                (rebar_shape_min_x + rebar_shape_max_x) / 2, rebar_shape_min_y)
            helical_rebar_length = str(
                round(
                    FreeCAD.Units.Quantity("{}mm".format(
                        rebar.Base.Shape.Wires[0].Length)).getValueAs(
                            rebar_dimension_units).Value,
                    precision,
                ))
            helix_radius = str(
                round(
                    rebar.Base.Radius.getValueAs(rebar_dimension_units).Value,
                    precision,
                ))
            helix_pitch = str(
                round(
                    rebar.Base.Pitch.getValueAs(rebar_dimension_units).Value,
                    precision,
                ))
            if "." in helical_rebar_length:
                helical_rebar_length = helical_rebar_length.rstrip("0").rstrip(
                    ".")
            if "." in helix_radius:
                helix_radius = helix_radius.rstrip("0").rstrip(".")
            if "." in helix_pitch:
                helix_pitch = helix_pitch.rstrip("0").rstrip(".")
            if include_units_in_dimension_label:
                helical_rebar_length += rebar_dimension_units
                helix_radius += rebar_dimension_units
                helix_pitch += rebar_dimension_units
            edge_dimension_svg.append(
                getSVGTextElement(
                    helical_rebar_dimension_label_format.replace(
                        "%L", helical_rebar_length).replace(
                            "%R", helix_radius).replace("%P", helix_pitch),
                    top_mid_point.x,
                    top_mid_point.y - rebar_stroke_width * 2,
                    dimension_font_family,
                    dimension_font_size,
                    "middle",
                ))
    else:
        if stirrup_extended_edge_offset and apply_stirrup_extended_edge_offset:
            basewire = getBasewireOfStirrupWithExtendedEdges(
                rebar, view_plane, stirrup_extended_edge_offset / scale)
            basewire.rotate(
                basewire.CenterOfMass,
                view_plane.axis,
                rebar_shape_rotation_angle,
            )

            fillet_radius = rebar.Rounding * rebar.Diameter.Value
            if fillet_radius:
                fillet_basewire = DraftGeomUtils.filletWire(
                    basewire, fillet_radius)
            else:
                fillet_basewire = basewire

        edges = Part.__sortEdges__(fillet_basewire.Edges)
        straight_edges = Part.__sortEdges__(rebar.Base.Shape.Wires[0].Edges)
        for edge in list(straight_edges):
            if DraftGeomUtils.geomType(edge) != "Line":
                straight_edges.remove(edge)

        current_straight_edge_index = 0
        for edge_index, edge in enumerate(edges):
            if DraftGeomUtils.geomType(edge) == "Line":
                p1 = getProjectionToSVGPlane(edge.Vertexes[0].Point,
                                             view_plane)
                p2 = getProjectionToSVGPlane(edge.Vertexes[1].Point,
                                             view_plane)
                # Create Edge svg
                if round(p1.x) == round(p2.x) and round(p1.y) == round(p2.y):
                    edge_svg = getPointSVG(p1,
                                           radius=2 * rebar_stroke_width,
                                           fill=rebar_color)
                else:
                    edge_svg = getLineSVG(p1, p2, rebar_stroke_width,
                                          rebar_color)

                if include_dimensions:
                    # Create edge dimension svg
                    mid_point = FreeCAD.Vector((p1.x + p2.x) / 2,
                                               (p1.y + p2.y) / 2)
                    dimension_rotation = (math.degrees(
                        math.atan((p2.y - p1.y) / (p2.x - p1.x))) if
                                          round(p2.x) != round(p1.x) else -90)
                    edge_length = str(
                        round(
                            FreeCAD.Units.Quantity("{}mm".format(
                                straight_edges[current_straight_edge_index].
                                Length)).getValueAs(
                                    rebar_dimension_units).Value,
                            precision,
                        ))
                    if "." in edge_length:
                        edge_length = edge_length.rstrip("0").rstrip(".")
                    if include_units_in_dimension_label:
                        edge_length += rebar_dimension_units
                    edge_dimension_svg.append(
                        getSVGTextElement(
                            edge_length,
                            mid_point.x,
                            mid_point.y - rebar_stroke_width * 2,
                            dimension_font_family,
                            dimension_font_size,
                            "middle",
                        ))
                    edge_dimension_svg[-1].set(
                        "transform",
                        "rotate({} {} {})".format(
                            dimension_rotation,
                            round(mid_point.x),
                            round(mid_point.y),
                        ),
                    )
                    current_straight_edge_index += 1
                    if (0 <= edge_index - 1 and DraftGeomUtils.geomType(
                            edges[edge_index - 1]) == "Line"):
                        radius = max(fillet_radius, dimension_font_size * 0.8)
                        bent_angle_svg = getEdgesAngleSVG(
                            edges[edge_index - 1],
                            edge,
                            radius,
                            view_plane,
                            dimension_font_family,
                            dimension_font_size * 0.8,
                            bent_angle_dimension_exclude_list,
                            0.2 / scale,
                        )
                        edge_dimension_svg.append(bent_angle_svg)
            elif DraftGeomUtils.geomType(edge) == "Circle":
                p1 = getProjectionToSVGPlane(edge.Vertexes[0].Point,
                                             view_plane)
                p2 = getProjectionToSVGPlane(edge.Vertexes[1].Point,
                                             view_plane)
                if round(p1.x) == round(p2.x) or round(p1.y) == round(p2.y):
                    edge_svg = getLineSVG(p1, p2, rebar_stroke_width,
                                          rebar_color)
                else:
                    edge_svg = getRoundEdgeSVG(edge, view_plane,
                                               rebar_stroke_width, rebar_color)
                    if include_dimensions:
                        # Create bent angle svg
                        if 0 <= edge_index - 1 and edge_index + 1 < len(edges):
                            prev_edge = edges[edge_index - 1]
                            next_edge = edges[edge_index + 1]
                            if (DraftGeomUtils.geomType(prev_edge) ==
                                    DraftGeomUtils.geomType(next_edge) ==
                                    "Line"):
                                radius = max(fillet_radius,
                                             dimension_font_size * 0.8)
                                bent_angle_svg = getEdgesAngleSVG(
                                    prev_edge,
                                    next_edge,
                                    radius,
                                    view_plane,
                                    dimension_font_family,
                                    dimension_font_size * 0.8,
                                    bent_angle_dimension_exclude_list,
                                    0.2 / scale,
                                )
                                edge_dimension_svg.append(bent_angle_svg)
            else:
                edge_svg = ElementTree.Element("g")
            rebar_edges_svg.append(edge_svg)

    return svg
    def execute(self, obj):
        """This function is executed to recompute ReinforcementDimensioning
        object."""
        if not obj.ParentDrawingView:
            FreeCAD.Console.PrintError(
                "No ParentDrawingView, return without a reinforcement "
                "dimensioning for {}.\n".format(obj.Name)
            )
            return

        if obj.WayPointsType == "Automatic":
            if not obj.Rebar:
                FreeCAD.Console.PrintError(
                    "No Rebar, return without a reinforcement dimensioning for "
                    "{}.\n".format(obj.Name)
                )
                return
            elif obj.Rebar not in obj.ParentDrawingView.VisibleRebars:
                FreeCAD.Console.PrintError(
                    "Rebar is either not visible or not present in "
                    "reinforcement drawing.\n"
                )
                return
            elif not hasattr(obj.Rebar, "RebarShape"):
                FreeCAD.Console.PrintError(
                    "Unable to find rebar shape type. Automatic dimensioning "
                    "is not supported for custom rebars.\n"
                )
                return
            elif obj.Rebar.RebarShape not in RebarTypes.tolist():
                FreeCAD.Console.PrintError(
                    "Unsupported rebar type {}. Automatic dimensioning is only "
                    "supported for: {}\n".format(
                        obj.Rebar.RebarShape, ", ".join(RebarTypes.tolist())
                    )
                )
                return

        if obj.WayPointsType == "Custom" and len(obj.WayPoints) < 2:
            FreeCAD.Console.PrintError(
                "Empty WayPoints list, return without a reinforcement "
                "dimensioning for {}."
                "\n".format(obj.Name)
            )
            return

        obj.Scale = obj.ParentDrawingView.Scale
        obj.X = obj.ParentDrawingView.X
        obj.Y = obj.ParentDrawingView.Y
        root_svg = getSVGRootElement()

        view_plane = getViewPlane(obj.ParentDrawingView.View)
        min_x, min_y, max_x, max_y = getDrawingMinMaxXY(
            obj.ParentDrawingView.Structure,
            obj.ParentDrawingView.Rebars,
            view_plane,
        )

        if obj.WayPointsType == "Automatic":
            dimension_data_list, dimension_align = getRebarDimensionData(
                obj.Rebar,
                obj.DimensionFormat,
                view_plane,
                obj.DimensionLeftOffset.Value / obj.Scale,
                obj.DimensionRightOffset.Value / obj.Scale,
                obj.DimensionTopOffset.Value / obj.Scale,
                obj.DimensionBottomOffset.Value / obj.Scale,
                min_x,
                min_y,
                max_x,
                max_y,
                obj.Scale,
                obj.SingleRebar_OuterDimension,
                obj.MultiRebar_OuterDimension,
            )
            if hasattr(self, "FirstExecute") and self.FirstExecute is True:
                self.FirstExecute = False
                parent_drawing = obj.ParentDrawingView
                if dimension_align == "Left":
                    parent_drawing.DimensionLeftOffset.Value += (
                        self.DimensionLeftOffsetIncrement
                    )
                elif dimension_align == "Right":
                    parent_drawing.DimensionRightOffset.Value += (
                        self.DimensionRightOffsetIncrement
                    )
                elif dimension_align == "Top":
                    parent_drawing.DimensionTopOffset.Value += (
                        self.DimensionTopOffsetIncrement
                    )
                elif dimension_align == "Bottom":
                    parent_drawing.DimensionBottomOffset.Value += (
                        self.DimensionBottomOffsetIncrement
                    )
            for dimension_data in dimension_data_list:
                if (
                    "LabelOnly" in dimension_data
                    and dimension_data["LabelOnly"] is True
                ):
                    dimensions_svg = getSVGTextElement(
                        dimension_data["DimensionLabel"],
                        dimension_data["LabelPosition"].x,
                        dimension_data["LabelPosition"].y,
                        obj.Font,
                        obj.FontSize.Value / obj.Scale,
                        "middle",
                    )
                    dimensions_svg.set("fill", getrgb(obj.TextColor))
                else:
                    way_points = dimension_data["WayPoints"]
                    dimension_label = dimension_data["DimensionLabel"]
                    if dimension_data["VisibleRebars"] == "Single":
                        line_start_symbol = obj.SingleRebar_LineStartSymbol
                        line_end_symbol = obj.SingleRebar_LineEndSymbol
                        text_position_type = obj.SingleRebar_TextPositionType
                    elif dimension_data["VisibleRebars"] == "Multiple":
                        line_start_symbol = obj.MultiRebar_LineStartSymbol
                        line_end_symbol = obj.MultiRebar_LineEndSymbol
                        text_position_type = obj.MultiRebar_TextPositionType

                    dimensions_svg = getDimensionLineSVG(
                        [(point.x, point.y) for point in way_points],
                        dimension_label,
                        obj.Font,
                        obj.FontSize.Value / obj.Scale,
                        getrgb(obj.TextColor),
                        text_position_type,
                        obj.StrokeWidth.Value / obj.Scale,
                        obj.LineStyle,
                        getrgb(obj.LineColor),
                        line_start_symbol,
                        obj.LineMidPointSymbol,
                        line_end_symbol,
                    )

                # Apply translation so that (0,0) in dimensioning corresponds to
                # (0,0) in ParentDrawingView
                dimensions_svg.set(
                    "transform",
                    "translate({}, {})".format(-min_x, -min_y),
                )
                root_svg.append(dimensions_svg)
        else:
            if obj.Rebar:
                dimension_label = getRebarDimensionLabel(
                    obj.Rebar, obj.DimensionFormat
                )
            else:
                dimension_label = obj.DimensionFormat
            dimensions_svg = getDimensionLineSVG(
                [(point.x, point.y) for point in obj.WayPoints],
                dimension_label,
                obj.Font,
                obj.FontSize.Value / obj.Scale,
                getrgb(obj.TextColor),
                obj.TextPositionType,
                obj.StrokeWidth.Value / obj.Scale,
                obj.LineStyle,
                getrgb(obj.LineColor),
                obj.LineStartSymbol,
                obj.LineMidPointSymbol,
                obj.LineEndSymbol,
            )
            # Apply translation so that (0,0) in dimensioning corresponds to
            # (0,0) in ParentDrawingView
            dimensions_svg.set(
                "transform",
                "translate({}, {})".format(-min_x, -min_y),
            )
            root_svg.append(dimensions_svg)

        # Set svg height and width same as ParentDrawingView
        root_svg.set("width", "{}mm".format(obj.ParentDrawingView.Width.Value))
        root_svg.set(
            "height", "{}mm".format(obj.ParentDrawingView.Height.Value)
        )
        root_svg.set(
            "viewBox",
            "0 0 {} {}".format(
                obj.ParentDrawingView.Width.Value,
                obj.ParentDrawingView.Height.Value,
            ),
        )

        obj.Symbol = ElementTree.tostring(root_svg, encoding="unicode")

        if FreeCAD.GuiUp:
            obj.ViewObject.update()