Source code for opstool.preprocessing.sec_mesh

"""
SecMesh: A module to mesh the cross-section with triangular fibers
"""

import matplotlib.pyplot as plt
import numpy as np
import openseespy.opensees as ops
import plotly.graph_objects as go
import plotly.io as pio
from matplotlib.collections import PatchCollection
from sectionproperties.analysis.section import Section
from sectionproperties.pre.geometry import CompoundGeometry, Geometry
from sectionproperties.pre.pre import Material
from shapely.geometry import LineString, Polygon


def _to_section(geom_obj, time_info=False):
    return Section(geom_obj, time_info)


[docs]class SecMesh: """A class to mesh the cross-section with triangular fibers. Parameters -------------- sec_name : str Assign a name to the section Returns ----------- None Examples -------- >>> outlines = [[0, 0], [-0.5, 1], [1.5, 1], [1, 0]] >>> outlines2 = offset(outlines, d=0.05) >>> holes1 = [[0.2, 0.2], [0.4, 0.2], [0.4, 0.4], [0.2, 0.4]] >>> holes2 = [[0.6, 0.2], [0.8, 0.2], [0.8, 0.5], [0.6, 0.5]] >>> cover = add_polygon(outlines, holes=[outlines2]) >>> core = add_polygon(outlines2, holes=[holes1, holes2]) >>> # create section obj >>> sec = SecMesh(sec_name="My Section") >>> sec.assign_group(dict(cover=cover, core=core)) >>> sec.assign_mesh_size(dict(cover=0.001, core=0.005)) >>> sec.assign_ops_matTag(dict(cover=1, core=2)) >>> sec.mesh() >>> # create rebars data >>> rebar_lines1 = offset(outlines, d=0.05 + 0.032 / 2) >>> rebar_lines2 = [[0.2, 0.2], [0.8, 0.8]] >>> rebars = Rebars() >>> rebars.add_rebar_line(points=rebar_lines1, dia=0.032, gap=0.1, color="red", matTag=3) >>> rebars.add_rebar_line(points=rebar_lines2, dia=0.020, gap=0.1, color="black", matTag=3) >>> sec.add_rebars(rebars) >>> # get section properties >>> sec.get_sec_props(plot_centroids=False) >>> sec.centring() >>> # sec.rotate(45) >>> sec.to_file("mysec.py", secTag=1, GJ=10000) >>> sec.view() """ def __init__(self, sec_name: str = "My Section"): self.sec_name = sec_name # * mesh obj self.mesh_obj = None self.section = None self.points = None self.cells_map = dict() self.centers_map = dict() self.areas_map = dict() # * data group self.group_map = dict() self.mat_ops_map = dict() self.mesh_size_map = dict() # *rebar data self.rebar_data = [] # * section geo props self.sec_props = dict() self.color_map = dict() self.colors_default = [ "#ffb900", "#037ef3", "#1aafd0", "#fc636b", "#11862f", "#832561", "#f48924", "#52565e", ] self.is_centring = False
[docs] def assign_group(self, group: dict[str, any]): """Assign the group dict for each mesh. Parameters ------------ group : dict A dict of name as key, mesh obj as value. Returns ---------- instance """ self.group_map = group.copy() return self
[docs] def assign_mesh_size(self, mesh_size: dict[str, float]): """Assign the mesh size dict for each mesh. Parameters ------------ mesh_size : dict[str, float] A dict of name as key, mesh size as value. Returns ------------ instance """ if not self.group_map: raise ValueError("The assign_group method should be run first!") for name in mesh_size.keys(): if name not in self.group_map.keys(): raise ValueError( f"{name} is not specified in the assign_group function!" ) self.mesh_size_map = mesh_size.copy() return self
[docs] def assign_ops_matTag(self, mat_tag: dict[str, int]): """Assign the mesh size dict for each mesh. Parameters -------------- mat_tag : dict[str, int] A dict of name as key, opensees matTag previous defined as value. Returns ---------- instance """ if not self.group_map: raise ValueError("The assign_group method should be run first!") for name in mat_tag.keys(): if name not in self.group_map.keys(): raise ValueError( f"{name} is not specified in the assign_group function!" ) self.mat_ops_map = mat_tag.copy() return self
[docs] def assign_group_color(self, colors): """Assign the color dict to plot the section. Parameters ------------- colors : dict[str, str] A dict of name as key, color as value. """ if not self.group_map: raise ValueError("The assign_group method should be run first!") for name in colors.keys(): if name not in self.group_map.keys(): raise ValueError( f"{name} is not specified in the assign_group function!" ) self.color_map = colors.copy() return self
[docs] def mesh(self): """Mesh the section. Returns ---------- None """ geoms = [] mesh_sizes = [] for name, geom in self.group_map.items(): geoms.append(geom) mesh_sizes.append(self.mesh_size_map[name] / 10) geom_obj = CompoundGeometry(geoms) mesh_obj = geom_obj.create_mesh(mesh_sizes=mesh_sizes) self.section = _to_section(geom_obj, time_info=False) self.mesh_obj = mesh_obj.mesh # * mesh data vertices = self.mesh_obj["vertices"] self.points = vertices triangles = self.mesh_obj["triangles"][:, :3] triangle_attributes = self.mesh_obj["triangle_attributes"] attributes = np.unique(triangle_attributes) for name, attri in zip(self.group_map.keys(), attributes): idx = triangle_attributes == attri self.cells_map[name] = triangles[idx[:, 0]] # * fiber data for name, faces in self.cells_map.items(): areas = [] centers = [] for face in faces: idx1, idx2, idx3 = face coord1, coord2, coord3 = vertices[idx1], vertices[idx2], vertices[idx3] xyo = (coord1 + coord2 + coord3) / 3 centers.append(xyo) x1, y1 = coord1[:2] x2, y2 = coord2[:2] x3, y3 = coord3[:2] area_ = 0.5 * np.abs( x2 * y3 + x1 * y2 + x3 * y1 - x3 * y2 - x2 * y1 - x1 * y3 ) areas.append(area_) self.areas_map[name] = np.array(areas) self.centers_map[name] = np.array(centers) return None
[docs] def add_rebars(self, rebars_obj): """Add rebars. Parameters ---------- rebars_obj : mesh obj The instance of Rebars class. Returns ---------- None """ self.rebar_data = rebars_obj.rebar_data
[docs] def get_fiber_data(self): """Return fiber data. Returns ------- Tuple(dict, dict) fiber center dict, fiber area dict """ return self.centers_map, self.areas_map
[docs] def get_sec_props(self, Eref: float = 1.0, display_results: bool = False, plot_centroids: bool = False): """ Solving Section Geometry Properties by Finite Element Method, by `sectionproperties` pacakge. Parameters ----------- Eref: float, default=1.0 Reference modulus of elasticity, it is important to analyze the composite section. See `sectionproperties doc <https://sectionproperties.readthedocs.io/en/latest/rst/post.html>`_ display_results : bool, default=True whether to display the results. plot_centroids : bool, default=False whether to plot centroids Returns ----------- sec_props: dict section props dict, including: * Cross-sectional area (A) * Shear area (Asy, Asz) * Elastic centroid (centroid) * Second moments of area about the centroidal axis (Iy, Iz, Iyz) * Torsion constant (J) * Principal axis angle (phi) * ratio of reinforcement (rho_rebar) * Effective Material Properties (Effective elastic modulus: E_eff; Effective shear modulus: G_eff; Effective Poisson’s ratio: Nu_eff) If materials are specified for the cross-section, the area, second moments of area and torsion constant are elastic modulus weighted. """ section = self.section section.calculate_geometric_properties() section.calculate_warping_properties() if Eref == 1: area = section.get_area() else: area = section.get_ea() / Eref # area, ixx_c, iyy_c, ixy_c, j, phi = section.calculate_frame_properties( # solver_type='direct') if display_results: section.display_results() if plot_centroids: section.plot_centroids() # Second moments of area centroidal axis (ixx_c, iyy_c, ixy_c) ixx_c, iyy_c, ixy_c = section.get_ic() cx, cy = section.get_c() # Elastic centroid (cx, cy) phi = section.get_phi() # Principal bending axis angle j = section.get_j() # St. Venant torsion constant # Shear area for loading about the centroidal axis (A_sx, A_sy) area_sx, area_sy = section.get_As() # Effective Material Properties E_eff = section.get_e_eff() G_eff = section.get_g_eff() Nu_eff = section.get_nu_eff() if self.rebar_data: all_rebar_area = 0 for data in self.rebar_data: rebar_xy = data["rebar_xy"] dia = data["dia"] rebar_coords = [] rebar_areas = [] for xy in rebar_xy: rebar_coords.append(xy) rebar_areas.append(np.pi / 4 * dia ** 2) all_rebar_area += np.sum(rebar_areas) rho_rebar = all_rebar_area / area else: rho_rebar = None # lump sec_props = dict( A=area, Asy=area_sx / Eref, Asz=area_sy / Eref, centroid=(cx, cy), Iy=ixx_c / Eref, Iz=iyy_c / Eref, Iyz=ixy_c / Eref, J=j / Eref, phi=phi, rho_rebar=rho_rebar, E_eff=E_eff, G_eff=G_eff, Nu_eff=Nu_eff ) self.sec_props = sec_props return sec_props
[docs] def centring(self): """ Move the section centroid to (0, 0). Returns --------- None """ centers_map, areas_map = self.get_fiber_data() centers = [] areas = [] for name in self.cells_map.keys(): centers.append(centers_map[name]) areas.append(areas_map[name]) centers = np.vstack(centers) areas = np.hstack(areas) center = areas @ centers / np.sum(areas) self.points -= center names = self.centers_map.keys() for name in names: self.centers_map[name] -= center # move rebar for i, data in enumerate(self.rebar_data): self.rebar_data[i]["rebar_xy"] -= center self.is_centring = True
[docs] def rotate(self, theta: float = 0): """Rotate the section clockwise. Parameters ------------ theta : float, default=0 Rotation angle, unit: degree. Returns --------- None """ theta = theta / 180 * np.pi if not self.is_centring: self.centring() x_rot, y_rot = sec_rotation( self.points[:, 0], self.points[:, 1], theta) self.points[:, 0], self.points[:, 1] = x_rot, y_rot names = self.centers_map.keys() for name in names: x_rot, y_rot = sec_rotation( self.centers_map[name][:, 0], self.centers_map[name][:, 1], theta ) self.centers_map[name][:, 0], self.centers_map[name][:, 1] = x_rot, y_rot # rebar for i, data in enumerate(self.rebar_data): rebar_xy = self.rebar_data[i]["rebar_xy"] x_rot, y_rot = sec_rotation(rebar_xy[:, 0], rebar_xy[:, 1], theta) ( self.rebar_data[i]["rebar_xy"][:, 0], self.rebar_data[i]["rebar_xy"][:, 1], ) = (x_rot, y_rot)
[docs] def opspy_cmds(self, secTag: int, GJ: float): """Generate openseespy fiber section command. Parameters ------------ secTag : int The section tag assigned in OpenSees. GJ : float Torsion stiffness. Returns ---------- None """ ops.section("Fiber", secTag, "-GJ", GJ) names = self.centers_map.keys() for name in names: centers = self.centers_map[name] areas = self.areas_map[name] matTag = self.mat_ops_map[name] for center, area in zip(centers, areas): ops.fiber(center[0], center[1], area, matTag) # rebars for data in self.rebar_data: rebar_xy = data["rebar_xy"] dia = data["dia"] matTag = data["matTag"] for xy in rebar_xy: area = np.pi / 4 * dia ** 2 ops.fiber(xy[0], xy[1], area, matTag)
[docs] def to_file(self, output_path: str, secTag: int, GJ: float): """Output the opensees fiber code to file. Parameters ------------- output_path : str The filepath to save, e.g., r"my_dir/my_section.py" secTag : int The section tag assigned in OpenSees. GJ : float Torsion stiffness. Returns --------- None Notes ----- Notes that output_path must be endswith .py or .tcl, function will create the file by a right style. """ if not (output_path.endswith(".tcl") or output_path.endswith(".py")): raise ValueError("output_path must endwith .tcl or .py!") names = self.centers_map.keys() with open(output_path, "w+") as output: output.write("# This document was created from SecMesh\n") output.write("# Author: Yexiang Yan yexiang_yan@outlook.com\n\n") if output_path.endswith(".tcl"): output.write(f"set secTag {secTag}\n") temp = "{" output.write( f"section fiberSec $secTag -GJ {GJ}{temp}; # Define the fiber section\n" ) for name in names: centers = self.centers_map[name] areas = self.areas_map[name] matTag = self.mat_ops_map[name] for center, area in zip(centers, areas): output.write( f" fiber {center[0]:.3E} {center[1]:.3E} {area:.3E} {matTag}\n" ) # rebar for data in self.rebar_data: output.write(" # Define Rebar\n") rebar_xy = data["rebar_xy"] dia = data["dia"] matTag = data["matTag"] for xy in rebar_xy: area = np.pi / 4 * dia ** 2 output.write( f" fiber {xy[0]:.3E} {xy[1]:.3E} {area:.3E} {matTag}\n" ) output.write("}; # end of fibersection definition") elif output_path.endswith(".py"): output.write("import openseespy.opensees as ops\n\n\n") output.write( f"ops.section('Fiber', {secTag}, '-GJ', {GJ}) # Define the fiber section\n" ) for name in names: centers = self.centers_map[name] areas = self.areas_map[name] matTag = self.mat_ops_map[name] for center, area in zip(centers, areas): output.write( f"ops.fiber({center[0]:.3E}, {center[1]:.3E}, {area:.3E}, {matTag})\n" ) # rebar for data in self.rebar_data: output.write("# Define Rebar\n") rebar_xy = data["rebar_xy"] dia = data["dia"] matTag = data["matTag"] for xy in rebar_xy: area = np.pi / 4 * dia ** 2 output.write( f"ops.fiber({xy[0]:.3E}, {xy[1]:.3E}, {area:.3E}, {matTag})\n" )
[docs] def view(self, fill: bool = True, engine: str = "plotly", save_html: str = "SecMesh.html", on_notebook: bool = False): """Display the section mesh. Parameters ----------- fill : bool, default=True Whether to fill the trangles. engine: str, default='plotly' Plot engine, optional "plotly" or "matplotlib". save_html: str, default="SecMesh.html" If set, the figure will save as a html file, only useful for engine="plotly". If False or None, this parameter will be ignored. on_notebook: bool, default=False If True, the figure will display in a notebook. Returns -------- None """ # self.section.display_mesh_info() # self.section.plot_mesh() if not self.color_map: for i, name in enumerate(self.group_map.keys()): self.color_map[name] = self.colors_default[i] vertices = self.points x = vertices[:, 0] y = vertices[:, 1] aspect_ratio = (np.max(y) - np.min(y)) / (np.max(x) - np.min(x)) if engine.lower().startswith("m"): # matplotlib plot fig, ax = plt.subplots(figsize=(6, 6 * aspect_ratio)) # ax.set_facecolor("#efefef") # view the mesh vertices = self.points # the coords of each triangle vertex for name, faces in self.cells_map.items(): # faces = faces.astype(np.int64) if not fill: x = vertices[:, 0] y = vertices[:, 1] ax.triplot( x, y, triangles=faces, color=self.color_map[name], lw=1, zorder=-10 ) ax.plot( [], [], "^", label=name, mec=self.color_map[name], mfc="white" ) # for legend illustration only else: x = vertices[:, 0] y = vertices[:, 1] ax.triplot(x, y, triangles=faces, lw=0.75, color="#516572") patches = [ plt.Polygon(vertices[face_link, :2], True) for face_link in faces ] coll = PatchCollection( patches, facecolors=self.color_map[name], edgecolors="#516572", linewidths=0.75, zorder=-10, ) ax.add_collection(coll) ax.plot([], [], "^", label=name, color=self.color_map[name]) for data in self.rebar_data: color = data["color"] rebar_xy = data["rebar_xy"] dia = data["dia"] rebar_coords = [] rebar_areas = [] for xy in rebar_xy: rebar_coords.append(xy) rebar_areas.append(np.pi / 4 * dia ** 2) patches = [ plt.Circle((xy[0], xy[1]), np.sqrt(area / np.pi)) for xy, area in zip(rebar_coords, rebar_areas) ] coll = PatchCollection(patches, facecolors=color) ax.add_collection(coll) # ax.set_aspect("equal") ax.set_title(self.sec_name, fontsize=26, fontfamily="SimSun") ax.legend( fontsize=18, shadow=False, markerscale=3, loc=10, ncol=len(self.group_map), bbox_to_anchor=(0.5, -0.2), bbox_transform=ax.transAxes, ) ax.tick_params(labelsize=18) plt.show() elif engine.lower().startswith("p"): vertices = self.points # the coords of each triangle vertex n_cells = 0 n_cells_map = dict() fig = go.Figure() tplot = [] for name, faces in self.cells_map.items(): if not self.mat_ops_map: label = f"<b>{name}</b>" else: label = f"<b>{name}</b><br>matTag:{self.mat_ops_map[name]}" face_points = [] areas = [] centers = [] for i, cell in enumerate(faces): n_cells += 1 points0 = vertices[cell] x1, y1 = points0[0, :2] x2, y2 = points0[1, :2] x3, y3 = points0[2, :2] area_ = 0.5 * np.abs( x2 * y3 + x1 * y2 + x3 * y1 - x3 * y2 - x2 * y1 - x1 * y3 ) areas.append(area_) centers.append(np.mean(points0, axis=0)) points = np.vstack( [points0, [points0[0]], [[np.NAN, np.NAN]]]) face_points.append(points) face_points = np.vstack(face_points) areas = np.array(areas).reshape((len(areas), 1)) center_areas = np.hstack([centers, areas]) center_areas_labels = [f"<b>xo:{d[0]:.2e}</b><br>yo:{d[1]:.2e}<br>area:{d[2]:.2e}" for d in center_areas] n_cells_map[name] = len(center_areas_labels) if fill: tplot.append(go.Scatter(x=face_points[:, 0], y=face_points[:, 1], fill="toself", fillcolor=self.color_map[name], line=dict( color='black', width=0.75), connectgaps=False, opacity=0.75, hoverinfo="skip", )) else: tplot.append(go.Scatter(x=face_points[:, 0], y=face_points[:, 1], mode='lines', line=dict( color=self.color_map[name], width=1.2), connectgaps=False, hoverinfo="skip", )) # hover label tplot.append( go.Scatter( x=center_areas[:, 0], y=center_areas[:, 1], marker=dict(size=0, color=self.color_map[name], symbol='diamond-open'), mode="markers", name=label, customdata=center_areas_labels, hovertemplate='%{customdata}', ) ) fig.add_traces(tplot) # rebars shapes = [] for data in self.rebar_data: color = data["color"] rebar_xy = data["rebar_xy"] r = data["dia"] / 2 for xo, yo in rebar_xy: shapes.append(dict(type="circle", xref="x", yref="y", x0=xo - r, y0=yo - r, x1=xo + r, y1=yo + r, line_color=color, fillcolor=color, )) # ------------------------------------- txt = "Num. of Mesh: " for k, v in n_cells_map.items(): txt += f"| {k}--{v} " txt += f"| total--{n_cells}" fig.update_layout( shapes=shapes, width=800, height=800 * aspect_ratio, template="plotly", autosize=True, showlegend=False, scene=dict(aspectratio=dict( x=1, y=aspect_ratio), aspectmode="data"), title=dict(font=dict(family="courier", color='black', size=20), text=f"<b>{self.sec_name}</b> <br>" + f"{txt}") ) fig.update_xaxes(tickfont_size=18, ticks="outside") fig.update_yaxes(tickfont_size=18, ticks="outside") if save_html: pio.write_html(fig, file=save_html, auto_open=True) if on_notebook: fig.show() else: raise ValueError( f"not supported engine {engine}! optional, 'plotly' or 'matplotlib'!")
[docs]class Rebars: """ A class to create the rebar point. """ def __init__(self) -> None: self.rebar_data = []
[docs] def add_rebar_line( self, points: list[list[float, float]], dia: float, gap: float, closure: bool = False, matTag: int = None, color: str = "black", group_name: str = None, ): """Add rebar along a line, can be a line or polygon. Parameters ---------- points : list[list[float, float]] A list of rebar coords, [(x1, y1), (x2, y2),...,(xn, yn)] dia : float Rebar dia. gap : float Rebar space. closure: bool, default=False If True, the rebar line is a closed loop. matTag : int OpenSees mat Tag for rebar previously defined. color : str or rgb tuple. Color to plot rebar. group_name : str Assign rebar group name Returns ------- None """ if closure: if points[-1] != points[0]: points = list(points) points.append(points[0]) rebar_lines = LineString(points) x, y = rebar_lines.xy # mesh rebar points based on spacing rebar_xy = _lines_subdivide(x, y, gap) data = dict( rebar_xy=rebar_xy, color=color, name=group_name, dia=dia, matTag=matTag ) self.rebar_data.append(data)
[docs] def add_rebar_circle( self, xo: list[float, float], radius: float, dia: float, gap: float, angle1=0.0, angle2=360, matTag: int = None, color: str = "black", group_name: str = None, ): """Add the rebars along a circle. Parameters ---------- xo : list[float, float] Center coords, [(xc, yc)]. radius: float radius. dia : float rebar dia. angle1 : float The start angle, degree angle2 : float The end angle, deree gap : float Rebar space matTag : int OpenSees mat Tag for rebar previously defined. color : str or rgb tuple. Color to plot rebar. group_name : str Assign rebar group name. Returns ------- None """ angle1 = angle1 / 180 * np.pi angle2 = angle2 / 180 * np.pi arc_len = (angle2 - angle1) * radius n_sub = int(arc_len / gap) xc, yc = xo[0], xo[1] angles = np.linspace(angle1, angle2, n_sub + 1) points = [ [xc + radius * np.cos(ang), yc + radius * np.sin(ang)] for ang in angles ] if np.abs(angle2 - angle1 - 2 * np.pi) < 1e-6: rebar_xy = points[:-1] else: rebar_xy = points data = dict( rebar_xy=rebar_xy, color=color, name=group_name, dia=dia, matTag=matTag ) self.rebar_data.append(data)
[docs]def add_material( name="default", elastic_modulus=1, poissons_ratio=0, yield_strength=1, density=1, color="w", ): """Add a meterial. Parameters ---------- name : str, default='default' meterial name. elastic_modulus : float, default==1 elastic_modulus. poissons_ratio : float, default=0 poissons_ratio yield_strength : float, default==1 yield_strength density : float, default=1 density color : str or rgb tuple, default=='w' color for plot this material. Returns ------- Material instance """ return Material( name=name, elastic_modulus=elastic_modulus, poissons_ratio=poissons_ratio, yield_strength=yield_strength, density=density, color=color, )
[docs]def add_polygon( outline: list[list[float, float]], holes: list[list[list[float, float]]] = None, material=None, ): """Add polygon plane geom obj. Parameters ---------- outline : list[list[float, float]] The coords list of polygon points, [(x1, y1), (x2, y2),...,(xn, yn)] holes: list[list[list[float, float]]]. Hole of the section, a list of multiple hole coords, [hole1, hole2,...holeN], holeN=[(x1, y1), (x2, y2),...,(xn, yn)]. material: material obj The instance from add_material(). Returns ------- polygon obj """ if material is None: material_ = add_material() else: material_ = material ply = Polygon(outline, holes) geometry = Geometry(geom=ply, material=material_) return geometry
[docs]def add_circle( xo: list[float, float], radius: float, holes=None, angle1=0.0, angle2=360, n_sub=40, material=None, ): """Add the circle geom obj. Parameters ---------- xo : list[float, float] Center coords, [(xc, yc)]. radius: float radius. holes: list[list[list[float, float]]]. Hole of the section, a list of multiple hole coords, [hole1, hole2,...holeN], holeN=[(x1, y1), (x2, y2),...,(xn, yn)]. angle1 : float The start angle, degree angle2 : float The end angle, deree n_sub: int The partition number of the perimeter. material: material obj The instance from add_material(). Returns ------- None """ if material is None: material_ = add_material() else: material_ = material angle1 = angle1 / 180 * np.pi angle2 = angle2 / 180 * np.pi x, y = xo[0], xo[1] angles = np.linspace(angle1, angle2, n_sub + 1) points = [[x + radius * np.cos(ang), y + radius * np.sin(ang)] for ang in angles] ply = Polygon(points, holes) geometry = Geometry(geom=ply, material=material_) return geometry
def _lines_subdivide(x, y, gap): """ The polylines consisting of coordinates x and y are divided by the gap. """ x_new = [] y_new = [] for i in range(len(x) - 1): x1, y1 = x[i], y[i] x2, y2 = x[i + 1], y[i + 1] length = np.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) n = int(np.ceil(length / gap)) x_new.extend(np.linspace(x1, x2, n, endpoint=True)[:-1].tolist()) y_new.extend(np.linspace(y1, y2, n, endpoint=True)[:-1].tolist()) x_new.append(x[-1]) y_new.append(y[-1]) new_line = np.column_stack((x_new, y_new)) return new_line
[docs]def offset(points: list[list[float, float]], d: float): """Offsets closed polygons Parameters ---------- points : list[list[float, float]] A list containing the coordinate points, [(x1, y1),(x2, y2),...,(xn.yn)]. d : float Offsets closed polygons, positive values offset inwards, negative values outwards. Returns ------- coords: list[[float, float]] """ ply = Polygon(points) ply_off = ply.buffer(-d, cap_style=3, join_style=2) return list(ply_off.exterior.coords)
def sec_rotation(x, y, theta): """ Rotate the section coordinates counterclockwise by theta """ x_new = x * np.cos(theta) + y * np.sin(theta) y_new = -x * np.sin(theta) + y * np.cos(theta) return x_new, y_new