Source code for opstool.vis.save_tikz

import h5py
import numpy as np

COLORS = dict(
    point="Maroon",
    line="Blue",
    face="Green",
    solid="Violet",
    truss="pink",
    link="SkyBlue",
    constraint="Lime",
)


def _write_head(
    file, width=12, height=30, d3: bool = True, azimuth: float = 60, elevation: float = 135
):
    with open(file, "w", encoding="utf8") as f:
        f.write("% This file was created by opstool, all rights reserved!\n")
        f.write("\\documentclass{standalone}\n")
        f.write("\\usepackage[dvipsnames, svgnames, x11names]{xcolor}\n")
        f.write("\\usepackage[left=0cm,top=0cm,right=0cm,nohead,nofoot]{geometry}\n")
        f.write("\\usepackage{tikz}\n")
        if d3:
            f.write("\\usepackage{tikz-3dplot}\n")
        f.write("\\usepackage[siunitx]{circuitikz} %[symbols]\n")
        f.write("\\usepackage[outline]{contour} % glow around text\n")
        f.write("\\usetikzlibrary{arrows,arrows.meta}\n")
        f.write("\\usetikzlibrary{decorations.markings}\n")
        f.write("\\usepackage[active,tightpage]{preview}\n")
        f.write("\\PreviewEnvironment{tikzpicture}\n")
        if d3:
            f.write(f"\\tdplotsetmaincoords{{{azimuth}}}{{{elevation}}}\n")
        f.write("\\setlength\\PreviewBorder{2mm}\n\n")
        f.write(
            f"\\geometry{{paperwidth={width}cm, paperheight={height}cm, margin=0cm}}\n"
        )
        f.write(
            f"\n\\tikzstyle{{link}}=[R,color={COLORS['link']},thick,{COLORS['link']}]\n"
        )
        f.write(
            f"\\tikzstyle{{constraint}}=[short,color={COLORS['constraint']},thick,{COLORS['constraint']}]\n"
        )
        f.write("\n\\begin{document}\n")
        if d3:
            f.write("\n\\begin{tikzpicture}[tdplot_main_coords]\n\n")
        else:
            f.write("\n\\begin{tikzpicture}\n\n")


def _write_end(file):
    with open(file, "a", encoding="utf8") as f:
        f.write("\n\\end{tikzpicture}\n")
        f.write("\n\\end{document}\n")


def _def_points(file, points, d3, start_tag=0):
    with open(file, "a", encoding="utf8") as f:
        if d3:
            for i, p in enumerate(points):
                f.write(f"\\coordinate (P{start_tag+i}) at ({p[0]}, {p[1]}, {p[2]});\n")
        else:
            for i, p in enumerate(points):
                f.write(f"\\coordinate (P{start_tag+i}) at ({p[0]}, {p[1]});\n")


def _write_points(file, points, size, opacity=1.0, color="black", start_tag=0):
    with open(file, "a", encoding="utf8") as f:
        for i, p in enumerate(points):
            f.write(
                f"\\shade[ball color={color}, fill opacity={opacity}] (P{start_tag+i}) circle ({size}pt);\n"
            )


def _write_lines(file, cells, line_width, opacity, line_color="black", start_tag=0):
    cells = np.reshape(cells, (-1, 3))
    if len(cells) > 0:
        with open(file, "a", encoding="utf8") as f:
            for cell in cells:
                idx1 = cell[1] + start_tag
                idx2 = cell[2] + start_tag
                f.write(
                    f"\\draw[{line_color},line width={line_width}pt, opacity={opacity}] "
                    f"(P{idx1}) -- (P{idx2});\n"
                )
                # if line_type.lower() == "link":
                #     f.write(
                #         f"\draw[line width={line_width}pt] "
                #         f"(P{idx1}) to[link] (P{idx2});\n")
                # else:
                #     f.write(
                #         f"\draw[{line_color},line width={line_width}pt] "
                #         f"(P{idx1}) -- (P{idx2});\n")


def _write_constraint(
    file, model_info, scale, line_width, d3, line_color="black", start_tag=0
):
    points = model_info["ConstrainedCoords"] * scale
    cells = model_info["ConstrainedCells"]
    cells = np.reshape(cells, (-1, 3))
    midcoords = model_info["ConstrainedMidCoords"] * scale
    dofs = model_info["ConstrainedDofs"]
    dofs = ["".join([str(k) for k in dof]) for dof in dofs]
    if len(cells) > 0:
        if d3:
            with open(file, "a", encoding="utf8") as f:
                for cell, mp, dof in zip(cells, midcoords, dofs):
                    p1 = points[cell[1] + start_tag]
                    p2 = points[cell[2] + start_tag]
                    f.write(
                        f"\\draw[{line_color},line width={line_width}pt] "
                        f"({p1[0]}, {p1[1]}, {p1[2]}) to[constraint] ({p2[0]}, {p2[1]}, {p2[2]});\n"
                    )
                    # f.write(
                    #     f"\path ({mp[0]}, {mp[1]}, {mp[2]}) "
                    #     f"node[above,{line_color},font=\\fontsize{{{label_size}pt}}{{{label_size}pt}}] {{{dof}}};\n")
        else:
            with open(file, "a", encoding="utf8") as f:
                for cell, mp, dof in zip(cells, midcoords, dofs):
                    p1 = points[cell[1] + start_tag]
                    p2 = points[cell[2] + start_tag]
                    f.write(
                        f"\\draw[{line_color},line width={line_width}pt] "
                        f"({p1[0]}, {p1[1]}) to[constraint] ({p2[0]}, {p2[1]});\n"
                    )
                    # f.write(
                    #     f"\path ({mp[0]}, {mp[1]}) "
                    #     f"node[above,{line_color},font=\\fontsize{{{label_size}pt}}{{{label_size}pt}}] {{{dof}}};\n")


def _write_faces(file, cells, color, opacity, show_face_line=True, start_tag=0):
    if len(cells) > 0:
        lw = 1 if show_face_line else 0
        with open(file, "a", encoding="utf8") as f:
            i = 0
            while i < len(cells):
                num = cells[i]
                idxs = cells[i + 1 : i + num + 1]
                i += num + 1
                txt = ""
                for idx in idxs:
                    txt += f"(P{idx+start_tag}) -- "
                txt += "cycle;\n"
                f.write(
                    f"\\draw [line width={lw}pt, draw=black, "
                    f"fill={color}, fill opacity={opacity}]\n" + txt
                )


def _write_link(file, points, cells, lw, color, opacity, D3, start_tag=0):
    cells = np.reshape(cells, (-1, 3))
    points_zero = []
    points_nonzero = []
    cells_nonzero = []
    for cell in cells:
        idx1, idx2 = cell[1:]
        coord1, coord2 = points[idx1], points[idx2]
        length = np.sqrt(np.sum((coord2 - coord1) ** 2))
        if np.abs(length) < 1e-8:
            points_zero.append(coord1)
        else:
            xaxis = np.array(coord2 - coord1)
            global_z = [0.0, 0.0, 1.0]
            cos_angle = xaxis.dot(global_z) / (
                np.linalg.norm(xaxis) * np.linalg.norm(global_z)
            )
            if np.abs(1 - cos_angle**2) < 1e-10:
                yaxis = np.cross([-1.0, 0.0, 0.0], xaxis)
            else:
                yaxis = np.cross(global_z, xaxis)
            xaxis = xaxis / np.linalg.norm(xaxis)
            yaxis = yaxis / np.linalg.norm(yaxis)
            idx = len(points_nonzero)
            for i in range(5):
                cells_nonzero.extend([2, idx + i, idx + i + 1])
            points_nonzero.extend(
                [
                    coord1 + 0.25 * length * xaxis,
                    coord1 + 0.25 * length * xaxis - 0.25 * length * yaxis,
                    coord1 + 0.5 * length * xaxis + 0.25 * length * yaxis,
                    coord1 + 0.5 * length * xaxis - 0.25 * length * yaxis,
                    coord1 + 0.75 * length * xaxis + 0.25 * length * yaxis,
                    coord1 + 0.75 * length * xaxis,
                ]
            )
    # plot
    if len(points_zero) > 0:
        pass
    if len(points_nonzero) > 0:
        _def_points(file, points_nonzero, d3=D3, start_tag=start_tag)
        _write_lines(
            file,
            cells_nonzero,
            line_width=lw,
            opacity=opacity,
            line_color=color,
            start_tag=start_tag,
        )
    return len(points_nonzero)


def _write_beam_sec(file, model_info, cells, paras, start_tag=0):
    ext_points = model_info["line_sec_ext_points"]
    int_points = model_info["line_sec_int_points"]
    sec_points = model_info["line_sec_points"]
    ext_cells = cells["line_sec_ext"]
    int_cells = cells["line_sec_int"]
    sec_cells = cells["line_sec"]
    _def_points(file, ext_points, d3=True, start_tag=start_tag)
    _write_faces(
        file,
        ext_cells,
        color=paras["color"],
        opacity=paras["opacity"],
        start_tag=start_tag,
    )
    _def_points(file, int_points, d3=True, start_tag=start_tag + len(ext_points))
    _write_faces(
        file,
        int_cells,
        color=paras["color"],
        opacity=paras["opacity"],
        start_tag=start_tag + len(ext_points),
    )
    _def_points(
        file,
        sec_points,
        d3=True,
        start_tag=start_tag + len(ext_points) + len(int_points),
    )
    _write_faces(
        file,
        sec_cells,
        color=paras["color"],
        opacity=paras["opacity"],
        start_tag=start_tag + len(ext_points) + len(int_points),
    )


[docs] def save_tikz( input_file: str, output_file: str = "ModelData.tex", point_size: float = 5, line_width: float = 3, face_opacity: float = 0.6, solid_opacity: float = 0.6, point_opacity: float = 0.8, line_opacity: float = 1.0, azimuth: float = 60, elevation: float = 135, show_beam_sec: bool = False, beam_sec_paras: dict = None, color_dict: dict = None, ): """Save the ``OpenSeesPy`` model data as a ``tikz`` command file in ``latex``, and then you can open it in your local tex editor, or run it online in ``overleaf``. .. tip:: You can adjust the 3D perspective via parameters `azimuth` and `elevation`. Or find the following command ``\\tdplotsetmaincoords{}{}`` in the ``.tex`` file, you can change it manually, the first is azimuth, the second is elevation. Parameters ---------- input_file : str The input model data file, e.g., "opstool_output/ModelData.hdf5". output_file : str, optional The output tex file contains the tikz commands, by default "ModelData.tex" point_size : float, optional The point size in ``pt`` unit, by default 5 line_width : float, optional The line width in ``pt`` unit, by default 3 face_opacity : float, optional The opacity of face elements, by default 0.75 solid_opacity : float, optional The opacity of solid elements, by default 0.75 point_opacity : float, optional The opacity of point, by default 0.75 line_opacity : float, optional The opacity of line elements, by default 1.0 azimuth: float = 60, optional Set the azimuth of the camera, by default 60 elevation: float = 135, optional Set the elevation of the camera, by default 135 show_beam_sec: bool default = False Whether to render the 3d section of beam or truss elements. If True, the Arg `beam_sec` in :py:meth:`opstool.vis.GetFEMdata.get_model_data` must be assigned in advance. beam_sec_paras: dict defalut = None, A dict to control beam section render, optional key: color, opacity. color_dict : dict, optional The color of each type of element, by default None. The valid color string must be one of the ``xcolor`` package. If None, color_dict = dict(point="Maroon", line="Blue", face="Green", solid="Violet", truss="pink", link="SkyBlue", constraint="Lime") """ if color_dict: COLORS.update(color_dict) # get hdf5 data model_info = dict() cells = dict() with h5py.File(input_file, "r") as f: grp1 = f["ModelInfo"] for name in grp1.keys(): model_info[name] = grp1[name][...] grp2 = f["Cell"] for name in grp2.keys(): cells[name] = grp2[name][...] points = model_info["coord_no_deform"] D3 = False if np.max(np.abs(points[:, -1])) < 1e-5 else True bound = np.max(points, axis=0) - np.min(points, axis=0) aspect_ratio = np.max([bound[1] / bound[0], bound[2] / bound[0]]) if aspect_ratio > 1: paperwidth, paperheight = 100 / aspect_ratio, 100 else: paperwidth, paperheight = 100, 100 * aspect_ratio scale = paperwidth / bound[0] / 1.2 points *= scale # write head _write_head( output_file, width=paperwidth, height=paperheight, d3=D3, azimuth=azimuth, elevation=elevation ) # plot _def_points(output_file, points, d3=D3) cell_types = ["truss", "link", "beam", "other_line"] keys = ["truss", "link", "line", "line"] widths = [line_width, line_width / 3, line_width, line_width] for ctype, key, width in zip(cell_types, keys, widths): _write_lines( output_file, cells[ctype], line_width=width, opacity=line_opacity, line_color=COLORS[key], ) if len(cells["link"]) > 0: link_num = _write_link( output_file, points, cells["link"], lw=line_width / 4, color=COLORS["link"], opacity=line_opacity, D3=D3, start_tag=len(points), ) else: link_num = 0 _write_constraint( output_file, model_info, scale, line_width=line_width / 3, d3=D3, line_color=COLORS["constraint"], ) _write_faces( output_file, cells["plane"], color=COLORS["face"], opacity=face_opacity ) _write_faces( output_file, cells["tetrahedron"], color=COLORS["solid"], opacity=solid_opacity ) _write_faces( output_file, cells["brick"], color=COLORS["solid"], opacity=solid_opacity ) _write_points( output_file, points, size=point_size, opacity=point_opacity, color=COLORS["point"], ) if show_beam_sec: paras = dict(color="gray", opacity=0.25, texture=False) if beam_sec_paras is not None: paras.update(beam_sec_paras) _write_beam_sec( output_file, model_info, cells, paras=paras, start_tag=len(points) + link_num, ) # write end _write_end(output_file)