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
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 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
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