Repository: qiskit-community/qiskit-metal (language: python)

Issue:
Design Fails to Render in Metal GUI
<!-- ⚠️ Please abide by this template, otherwise you run the risk of the issue being closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

### Information

- **Qiskit Metal version**: 0.0.4 (pulled master branch)
- **Python version**: Python 3.9.7 | packaged by conda-forge
- **Operating system**: Tested on Windows 10 and Kubuntu 20.04

### What is the current behavior?
On trying to render a design, the metal-GUI does not draw the design, but updates the other things

Traceback (most recent call last):

  File "$HOME/testing_metal/qiskit-metal/qiskit_metal/renderers/renderer_mpl/mpl_canvas.py", line 494, in plot
    main_plot()

  File "$HOME/testing_metal/qiskit-metal/qiskit_metal/renderers/renderer_mpl/mpl_canvas.py", line 479, in main_plot
    self.clear_axis(ax)

  File "$HOME/testing_metal/qiskit-metal/qiskit_metal/renderers/renderer_mpl/mpl_canvas.py", line 541, in clear_axis
    clear_axis(ax)

  File "$HOME/testing_metal/qiskit-metal/qiskit_metal/renderers/renderer_mpl/mpl_toolbox.py", line 412, in clear_axis
    ax.lines = []

AttributeError: can't set attribute

Plotting error: can't set attribute [utility_functions.log_error_easy]


### Steps to reproduce the problem
Pull the qiskit-metal master branch
Setup metal as described in the README_developers.md using the conda method in a new environment
Try to draw any design on the gui. For example, run tutorials/Appendix A Full design flow examples/Example full chip design.ipynb
Look at metal-GUI to see the design missing

### What is the expected behavior?
The GUI should show the design

### Suggested solutions
This can be traced back to the clear_axis function in  qiskit-metal/qiskit_metal/renderers/renderer_mpl/mpl_toolbox.py
To correct this, the clear_axis method can just call axis.clear() as it did earlier 


Discussion / hints:

Fix patch:
diff --git a/qiskit_metal/renderers/renderer_ansys/ansys_renderer.py b/qiskit_metal/renderers/renderer_ansys/ansys_renderer.py
index 6dc89c59c..0b7b36bc8 100644
--- a/qiskit_metal/renderers/renderer_ansys/ansys_renderer.py
+++ b/qiskit_metal/renderers/renderer_ansys/ansys_renderer.py
@@ -1,1787 +1,1687 @@
-# -*- coding: utf-8 -*-
-
-# This code is part of Qiskit.
-#
-# (C) Copyright IBM 2017, 2021.
-#
-# This code is licensed under the Apache License, Version 2.0. You may
-# obtain a copy of this license in the LICENSE.txt file in the root directory
-# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
-#
-# Any modifications or derivative works of this code must retain this
-# copyright notice, and modified files need to carry a notice indicating
-# that they have been altered from the originals.
-# pylint: disable=too-many-lines
-
-from typing import List, Tuple, Union
-
-import re
-import os
-from pathlib import Path
-import math
-import geopandas
-import numpy as np
-import pandas as pd
-from numpy.linalg import norm
-from collections import defaultdict
-from platform import system
-from scipy.spatial import distance
-
-import shapely
-import pyEPR as epr
-from pyEPR.ansys import parse_units, HfssApp, release
-
-from qiskit_metal.draw.utility import to_vec3D
-from qiskit_metal.draw.basic import is_rectangle
-from qiskit_metal.renderers.renderer_base import QRendererAnalysis
-from qiskit_metal.toolbox_metal.parsing import is_true
-
-from qiskit_metal import Dict
-
-from .. import config
-
-if not config.is_building_docs():
-    from qiskit_metal.toolbox_python.utility_functions import (
-        toggle_numbers,
-        bad_fillet_idxs,
-    )
-
-
-def good_fillet_idxs(coords: list,
-                     fradius: float,
-                     precision: int = 9,
-                     isclosed: bool = False):
-    """
-    Get list of vertex indices in a linestring (isclosed = False) or polygon (isclosed = True)
-    that can be filleted based on proximity to neighbors.
-
-    Args:
-        coords (list): Ordered list of tuples of vertex coordinates.
-        fradius (float): User-specified fillet radius from QGeometry table.
-        precision (int, optional): Digits of precision used for round(). Defaults to 9.
-        isclosed (bool, optional): Boolean denoting whether the shape is a linestring or
-            polygon. Defaults to False.
-
-    Returns:
-        list: List of indices of vertices that can be filleted.
-    """
-    if isclosed:
-        return toggle_numbers(
-            bad_fillet_idxs(coords, fradius, precision, isclosed=True),
-            len(coords))
-    return toggle_numbers(
-        bad_fillet_idxs(coords, fradius, precision, isclosed=False),
-        len(coords))[1:-1]
-
-
-def get_clean_name(name: str) -> str:
-    """Create a valid variable name from the given one by removing having it
-    begin with a letter or underscore followed by an unlimited string of
-    letters, numbers, and underscores.
-
-    Args:
-        name (str): Initial, possibly unusable, string to be modified.
-
-    Returns:
-        str: Variable name consistent with Python naming conventions.
-    """
-    # Remove invalid characters
-    name = re.sub("[^0-9a-zA-Z_]", "", name)
-    # Remove leading characters until we find a letter or underscore
-    name = re.sub("^[^a-zA-Z_]+", "", name)
-    return name
-
-
-class QAnsysRenderer(QRendererAnalysis):
-    """Extends QRenderer to export designs to Ansys using pyEPR. The methods
-        which a user will need for Ansys export should be found within this class.
-
-    Default Options:
-        * Lj: '10nH' -- Lj has units of nanoHenries (nH)
-        * Cj: 0 -- Cj *must* be 0 for pyEPR analysis! Cj has units of femtofarads (fF)
-        * _Rj: 0 -- _Rj *must* be 0 for pyEPR analysis! _Rj has units of Ohms
-        * max_mesh_length_jj: '7um' -- Maximum mesh length for Josephson junction elements
-        * project_path: None -- Default project path; if None --> get active
-        * project_name: None -- Default project name
-        * design_name: None -- Default design name
-        * ansys_file_extension: '.aedt' -- Ansys file extension for 2016 version and newer
-        * x_buffer_width_mm: 0.2 -- Buffer between max/min x and edge of ground plane, in mm
-        * y_buffer_width_mm: 0.2 -- Buffer between max/min y and edge of ground plane, in mm
-        * wb_threshold:'400um' -- the minimum distance between two vertices of a path for a
-          wirebond to be added.
-        * wb_offset:'0um' -- offset distance for wirebond placement (along the direction
-          of the cpw)
-        * wb_size: 3 -- scalar which controls the width of the wirebond (wb_size * path['width'])
-    """
-
-    #: Default options, over-written by passing ``options` dict to render_options.
-    #: Type: Dict[str, str]
-    # yapf: disable
-    default_options = Dict(
-        Lj='10nH',  # Lj has units of nanoHenries (nH)
-        Cj=0,  # Cj *must* be 0 for pyEPR analysis! Cj has units of femtofarads (fF)
-        _Rj=0,  # _Rj *must* be 0 for pyEPR analysis! _Rj has units of Ohms
-        max_mesh_length_jj='7um',  # maximum mesh length for Josephson junction elements
-        project_path=None,  # default project path; if None --> get active
-        project_name=None,  # default project name
-        design_name=None,  # default design name
-        # bounding_box_scale_x = 1.2, # Ratio of 'main' chip width to bounding box width
-        # bounding_box_scale_y = 1.2, # Ratio of 'main' chip length to bounding box length
-        x_buffer_width_mm=0.2,  # Buffer between max/min x and edge of ground plane, in mm
-        y_buffer_width_mm=0.2,  # Buffer between max/min y and edge of ground plane, in mm
-        wb_threshold = '400um',
-        wb_offset = '0um',
-        wb_size = 5,
-        plot_ansys_fields_options = Dict(
-            name="NAME:Mag_E1",
-            UserSpecifyName='0',
-            UserSpecifyFolder='0',
-            QuantityName= "Mag_E",
-            PlotFolder= "E Field",
-            StreamlinePlot= "False",
-            AdjacentSidePlot= "False",
-            FullModelPlot= "False",
-            IntrinsicVar= "Phase=\'0deg\'",
-            PlotGeomInfo_0= "1",
-            PlotGeomInfo_1= "Surface",
-            PlotGeomInfo_2= "FacesList",
-            PlotGeomInfo_3= "1",
-        ),
-    )
-    """Default options"""
-    # yapf: enable
-
-    NAME_DELIM = r"_"
-    """Name delimiter"""
-
-    name = "ansys"
-    """Name"""
-
-    default_setup = Dict(
-        drivenmodal=Dict(
-            name="Setup",
-            freq_ghz="5.0",
-            max_delta_s="0.1",
-            max_passes="10",
-            min_passes="1",
-            min_converged="1",
-            pct_refinement="30",
-            basis_order="1",
-        ),
-        eigenmode=Dict(
-            name="Setup",
-            min_freq_ghz="1",
-            n_modes="1",
-            max_delta_f="0.5",
-            max_passes="10",
-            min_passes="1",
-            min_converged="1",
-            pct_refinement="30",
-            basis_order="-1",
-        ),
-        q3d=Dict(
-            name="Setup",
-            freq_ghz="5.0",
-            save_fields="False",
-            enabled="True",
-            max_passes="15",
-            min_passes="2",
-            min_converged_passes="2",
-            percent_error="0.5",
-            percent_refinement="30",
-            auto_increase_solution_order="True",
-            solution_order="High",
-            solver_type="Iterative",
-        ),
-        port_inductor_gap=
-        "10um",  # spacing between port and inductor if junction is drawn both ways
-    )
-    """Default setup."""
-
-    # When additional columns are added to QGeometry, this is the example to populate it.
-    # e.g. element_extensions = dict(
-    #         base=dict(color=str, klayer=int),
-    #         path=dict(thickness=float, material=str, perfectE=bool),
-    #         poly=dict(thickness=float, material=str), )
-    """Element extensions dictionary   element_extensions = dict() from base class"""
-
-    # Add columns to junction table during QAnsysRenderer.load()
-    # element_extensions  is now being populated as part of load().
-    # Determined from element_table_data.
-
-    # Dict structure MUST be same as  element_extensions!!!!!!
-    # This dict will be used to update QDesign during init of renderer.
-    # Keeping this as a cls dict so could be edited before renderer is instantiated.
-    # To update component.options junction table.
-
-    element_table_data = dict(
-        path=dict(wire_bonds=False),
-        junction=dict(
-            inductance=default_options["Lj"],
-            capacitance=default_options["Cj"],
-            resistance=default_options["_Rj"],
-            mesh_kw_jj=parse_units(default_options["max_mesh_length_jj"]),
-        ),
-    )
-    """Element table data."""
-
-    def __init__(self, design: "QDesign", initiate=True, options: Dict = None):
-        """Create a QRenderer for Ansys.
-
-        Args:
-            design (QDesign): Use QGeometry within QDesign to obtain elements for Ansys.
-            initiate (bool, optional): True to initiate the renderer. Defaults to True.
-            options (Dict, optional):  Used to override all options. Defaults to None.
-        """
-        # Variables to connect to Ansys
-        self._rapp = None
-        self._rdesktop = None
-
-        # Initialize renderer
-        super().__init__(design=design, initiate=initiate, options=options)
-
-        # Default behavior is to render all components unless a strict subset was chosen
-        self.render_everything = True
-
-        self._pinfo = None
-
-    @property
-    def initialized(self):
-        """Returns True if initialized, False otherwise."""
-        if self._pinfo:
-            if self._pinfo.project:
-                try:
-                    # this is because after previous pyEPR close(),
-                    # the pinfo.project becomes None, but the type is not None (method)
-                    # TODO: fix where appropriate, then remove this patch.
-                    self._pinfo.project.name
-                except AttributeError:
-                    return False
-                return True
-        return False
-
-    @property
-    def rapp(self):
-        return self._rapp
-
-    @rapp.setter
-    def rapp(self, app_com):
-        if self._rapp:
-            self._rapp.release()
-        self._rapp = app_com
-
-    @property
-    def rdesktop(self):
-        return self._rdesktop
-
-    @rdesktop.setter
-    def rdesktop(self, desktop_com):
-        if self._rdesktop:
-            self._rdesktop.release()
-        self._rdesktop = desktop_com
-
-    def _initiate_renderer(self):
-        """
-        Open a session of the default Ansys EDT.
-        Establishes the connection to the App and Desktop only.
-        """
-        # test if ansys is open
-        # import psutil
-        # booted = False
-        # for proc in psutil.process_iter():
-        #     if 'ansysedt' in proc.name():
-        #         booted = True
-
-        # if not booted:
-        #    self._open_ansys(*args, **kwargs)
-        # need to make it so that it waits for the Ansys boot to end
-        # after opening, should establish a connection (able to create a new project)
-
-        self.rapp = HfssApp()
-        self.rdesktop = self.rapp.get_app_desktop()
-        if self.rdesktop.project_count() == 0:
-            self.rdesktop.new_project()
-        self.connect_ansys()  # TODO: can this be done differently?
-
-        # return True to indicate successful completion
-        return True
-
-    def _close_renderer(self):
-        """Not used by the gds renderer at this time. only returns True.
-
-        Returns:
-            bool: True
-        """
-        # wipe local variables
-        self.epr_distributed_analysis = None
-        self.epr_quantum_analysis = None
-
-        # close COM connections to ansys
-        if self.rdesktop is not None:
-            self.rdesktop.release()
-        if self.rapp is not None:
-            self.rapp.release()
-        if self.pinfo:
-            self.disconnect_ansys()
-
-        return True
-
-    def close(self):
-        """Alias of _close_renderer()
-
-        Returns:
-            bool: True
-        """
-        return self._close_renderer()
-
-    def open_ansys(
-        self,
-        path: str = None,
-        executable: str = "reg_ansysedt.exe",
-        path_var: str = "ANSYSEM_ROOT202",
-    ):
-        """Alternative method to open an Ansys session that allows to specify
-                which version to use. Default is version 2020 R2, but can be overridden.
-
-        Args:
-            path (str): Path to the Ansys executable. Defaults to None
-            executable (str): Name of the ansys executable. Defaults to 'reg_ansysedt.exe'
-            path_var (str): Name of the OS environment variable that contains the path to the
-                            Ansys executable. Only used when path=None.
-                            Defaults to 'ANSYSEM_ROOT202' (Ansys ver. 2020 R2)
-        """
-        if not system() == "Windows":
-            self.logger.warning(
-                "You are using %s, but this is a renderer to Ansys, which only runs on Windows. "
-                "Expect any sort of Errors if you try to work with this renderer beyond this point."
-                % system())
-
-        import subprocess
-
-        if path is None:
-            try:
-                path = os.environ[path_var]
-            except KeyError:
-                self.logger.error(
-                    "environment variable %s not found. Is Ansys 2020 R2 installed on this "
-                    "machine? If yes, then create said environment variable. If you have a "
-                    "different version of Ansys, then pass to open_ansys() the path to its "
-                    "binary, or the env var that stores it." % path_var)
-                raise
-        else:
-            path = os.path.abspath(path)
-        cmdlist = [os.path.sep.join([path, executable]), "-shortcut"]
-        subprocess.call(cmdlist, cwd=path)
-
-    def connect_ansys(
-        self,
-        project_path: str = None,
-        project_name: str = None,
-        design_name: str = None,
-    ):
-        """If none of the optional parameters are provided: connects to the
-        Ansys COM, then checks for, and grab if present, an active project,
-        design, and design setup.
-
-        If the optional parameters are provided: if present, opens the project file and design in
-        Ansys.
-
-        Args:
-            project_path (str, optional): Path without file name
-            project_name (str, optional): File name (with or without extension)
-            design_name (str, optional): Name of the default design to open from the project file
-        """
-        if not system() == "Windows":
-            self.logger.warning(
-                "You are using %s, but this is a renderer to Ansys, which only runs on Windows. "
-                "Expect any sort of Errors if you try to work with this renderer beyond this point."
-                % system())
-
-        # pyEPR does not like extensions
-        if project_name:
-            project_name = project_name.replace(".aedt", "")
-        # open connection through pyEPR
-        import pythoncom
-
-        try:
-            self._pinfo = epr.ProjectInfo(
-                do_connect=True,
-                project_path=self._options["project_path"]
-                if not project_path else project_path,
-                project_name=self._options["project_name"]
-                if not project_name else project_name,
-                design_name=self._options['design_name']
-                if not design_name else design_name)
-        except pythoncom.com_error as error:  # pylint: disable=no-member
-            print("com_error: ", error)
-            hr, msg, exc, arg = error.args
-            if (msg == "Invalid class string"
-               ):  # and hr == -2147221005 and exc is None and arg is None
-                self.logger.error(
-                    "pyEPR cannot find the Ansys COM. Ansys installation might not have registered it. "
-                    "To verify if this is the problem, execute the following: "
-                    "`print(win32com.client.Dispatch('AnsoftHfss.HfssScriptInterface'))` "
-                    "If the print-out is not `<COMObject ...>` then Ansys COM is not registered, "
-                    "and you will need to look into correcting your Ansys installation."
-                )
-            raise error
-
-    def disconnect_ansys(self):
-        """Disconnect Ansys."""
-        if self.pinfo:
-            self.pinfo.disconnect()
-        else:
-            self.logger.warning(
-                "This renderer appears to be already disconnected from Ansys")
-
-    def new_ansys_project(self):
-        """Creates a new empty project in Ansys."""
-        here = HfssApp()
-        here.get_app_desktop().new_project()
-
-    def connect_ansys_design(self, design_name: str = None):
-        """Used to switch between existing designs.
-
-        Args:
-            design_name (str, optional): Name within the active project. Defaults to None.
-        """
-
-        if self.pinfo:
-            if self.pinfo.project:
-                all_designs_names = self.pinfo.project.get_design_names()
-                if design_name not in all_designs_names:
-                    self.logger.warning(
-                        f"The design_name={design_name} is not in project.  Connection did not happen."
-                    )
-                    return
-
-                try:
-                    self.pinfo.connect_design(design_name)
-                    self.pinfo.connect_setup()
-                except AttributeError:
-                    self.logger.error(
-                        "Please install a more recent version of pyEPR (>=0.8.4.3)"
-                    )
-            else:
-                self.logger.warning(
-                    "Either you do not have a project loaded in Ansys, or you are not connected "
-                    "to it. Try executing hfss.connect_ansys(), or creating a new Ansys project. "
-                    "Also check the help file and other guide notebooks")
-        else:
-            self.logger.warning(
-                "It does not look like you are connected to Ansys. Please use connect_ansys() "
-                "and make sure self.pinfo is set. There must be a project open in Ansys first."
-            )
-
-    def get_active_design_name(self):
-        """Returns the name of the Ansys Design Object
-
-        Returns:
-            (str): Name of the active Ansys Design
-        """
-        if self.pinfo:
-            if self.pinfo.project:
-                return self.pinfo.project.get_active_design().name
-
-    @property
-    def pinfo(self) -> epr.ProjectInfo:
-        """Project info for Ansys renderer (class: pyEPR.ProjectInfo)."""
-        return self._pinfo
-
-    @property
-    def modeler(self):
-        """The modeler from pyEPR HfssModeler.
-
-        Returns:
-            pyEPR.ansys.HfssModeler: Reference to  design.HfssModeler in Ansys.
-        """
-        if self.pinfo:
-            if self.pinfo.design:
-                return self.pinfo.design.modeler
-
-    def plot_ansys_fields(self, *args, **kwargs):
-        """
-        (deprecated) use plot_fields()
-        """
-        self.logger.warning(
-            "This method is deprecated. Change your scripts to use plot_fields()"
-        )
-        return self.plot_fields(*args, **kwargs)
-
-    def plot_fields(
-        self,
-        object_name: str,
-        name: str = None,
-        UserSpecifyName: int = None,
-        UserSpecifyFolder: int = None,
-        QuantityName: str = None,
-        PlotFolder: str = None,
-        StreamlinePlot: bool = None,
-        AdjacentSidePlot: bool = None,
-        FullModelPlot: bool = None,
-        IntrinsicVar: str = None,
-        PlotGeomInfo_0: int = None,
-        PlotGeomInfo_1: str = None,
-        PlotGeomInfo_2: str = None,
-        PlotGeomInfo_3: int = None,
-    ):
-        """Plot fields in Ansys. The options are populated by the component's
-        options.
-
-        Args:
-            object_name (str): Used to plot on faces of.
-            name (str, optional): "NAME:<PlotName>" Defaults to None.
-            UserSpecifyName (int, optional): 0 if default name for plot is used, 1 otherwise.
-                Defaults to None.
-            UserSpecifyFolder (int, optional): 0 if default folder for plot is used, 1 otherwise.
-                Defaults to None.
-            QuantityName (str, optional): Type of plot to create. Possible values are
-                Mesh plots - "Mesh";
-                Field plots - "Mag_E", "Mag_H", "Mag_Jvol", "Mag_Jsurf","ComplexMag_E",
-                "ComplexMag_H", "ComplexMag_Jvol", "ComplexMag_Jsurf", "Vector_E", "Vector_H",
-                "Vector_Jvol", "Vector_Jsurf", "Vector_RealPoynting","Local_SAR", "Average_SAR".
-                Defaults to None.
-            PlotFolder (str, optional): Name of the folder to which the plot should be added.
-                Possible values are: "E Field",  "H Field", "Jvol", "Jsurf", "SARField", and
-                "MeshPlots". Defaults to None.
-            StreamlinePlot (bool, optional): Passed to CreateFieldPlot. Defaults to None.
-            AdjacentSidePlot (bool, optional): Passed to CreateFieldPlot. Defaults to None.
-            FullModelPlot (bool, optional): Passed to CreateFieldPlot. Defaults to None.
-            IntrinsicVar (str, optional): Formatted string that specifies the frequency and phase
-                at which to make the plot.  For example: "Freq='1GHz' Phase='30deg'".
-                Defaults to None.
-            PlotGeomInfo_0 (int, optional): 0th entry in list for "PlotGeomInfo:=",
-                <PlotGeomArray>. Defaults to None.
-            PlotGeomInfo_1 (str, optional): 1st entry in list for "PlotGeomInfo:=",
-                <PlotGeomArray>. Defaults to None.
-            PlotGeomInfo_2 (str, optional): 2nd entry in list for "PlotGeomInfo:=",
-                <PlotGeomArray>. Defaults to None.
-            PlotGeomInfo_3 (int, optional): 3rd entry in list for "PlotGeomInfo:=",
-                <PlotGeomArray>. Defaults to None.
-
-        Returns:
-            NoneType: Return information from oFieldsReport.CreateFieldPlot().
-            The method CreateFieldPlot() always returns None.
-        """
-        self.modeler._modeler.ShowWindow()
-        if not self.pinfo:
-            self.logger.warning("pinfo is None.")
-            return
-
-        if self.pinfo.design:
-            if not self.pinfo.design._fields_calc:
-                self.logger.warning("The _fields_calc in design is None.")
-                return
-            if not self.pinfo.design._modeler:
-                self.logger.warning("The _modeler in design is None.")
-                return
-        else:
-            self.logger.warning("The design in pinfo is None.")
-            return
-
-        if not self.pinfo.setup:
-            self.logger.warning("The setup in pinfo is None.")
-            return
-
-        # TODO: This is just a prototype - should add features and flexibility.
-        oFieldsReport = (self.pinfo.design._fields_calc
-                        )  # design.GetModule("FieldsReporter")
-        oModeler = self.pinfo.design._modeler  # design.SetActiveEditor("3D Modeler")
-        setup = self.pinfo.setup
-
-        # Object ID - use to plot on faces of
-        object_id = oModeler.GetObjectIDByName(object_name)
-        # Can also use hfss.pinfo.design._modeler.GetFaceIDs("main")
-
-        paf = self.options["plot_ansys_fields_options"]
-
-        if not name:
-            name = self.parse_value(paf["name"])
-
-        # Name of the solution setup and solution formatted as:"<SolveSetupName> : <WhichSolution>",
-        # where <WhichSolution> can be "Adaptive_<n>", "LastAdaptive", or "PortOnly".
-        # HFSS requires a space on either side of the ‘:’ character.
-        # If it is missing, the plot will not be created.
-        SolutionName = f"{setup.name} : LastAdaptive"
-        if not UserSpecifyName:
-            UserSpecifyName = int(self.parse_value(paf["UserSpecifyName"]))
-        if not UserSpecifyFolder:
-            UserSpecifyFolder = int(self.parse_value(paf["UserSpecifyFolder"]))
-        if not QuantityName:
-            QuantityName = self.parse_value(paf["QuantityName"])
-        if not PlotFolder:
-            PlotFolder = self.parse_value(paf["PlotFolder"])
-        if not StreamlinePlot:
-            StreamlinePlot = is_true(self.parse_value(paf["StreamlinePlot"]))
-        if not AdjacentSidePlot:
-            AdjacentSidePlot = is_true(self.parse_value(
-                paf["AdjacentSidePlot"]))
-        if not FullModelPlot:
-            FullModelPlot = is_true(self.parse_value(paf["FullModelPlot"]))
-        if not IntrinsicVar:
-            IntrinsicVar = self.parse_value(paf["IntrinsicVar"])
-        if not PlotGeomInfo_0:
-            PlotGeomInfo_0 = int(self.parse_value(paf["PlotGeomInfo_0"]))
-        if not PlotGeomInfo_1:
-            PlotGeomInfo_1 = self.parse_value(paf["PlotGeomInfo_1"])
-        if not PlotGeomInfo_2:
-            PlotGeomInfo_2 = self.parse_value(paf["PlotGeomInfo_2"])
-        if not PlotGeomInfo_3:
-            PlotGeomInfo_3 = int(self.parse_value(paf["PlotGeomInfo_3"]))
-
-        # used to pass to CreateFieldPlot
-        # Copied from  pdf at http://www.ece.uprm.edu/~rafaelr/inel6068/HFSS/scripting.pdf
-        # <PlotGeomArray>Array(<NumGeomTypes>, <GeomTypeData>,<GeomTypeData>, ...)
-        # For example:
-        # Array(4, "Volume", "ObjList", 1, "Box1","Surface", "FacesList", 1, "12", "Line", 1,"Polyline1",
-        #       "Point", 2, "Point1", "Point2"
-        PlotGeomInfo = [
-            PlotGeomInfo_0,
-            PlotGeomInfo_1,
-            PlotGeomInfo_2,
-            PlotGeomInfo_3,
-            str(object_id),
-        ]
-
-        # yapf: disable
-        args_list = [
-                name                 ,
-                "SolutionName:="     , SolutionName,  # name of the setup
-                "UserSpecifyName:="  , UserSpecifyName ,
-                "UserSpecifyFolder:=", UserSpecifyFolder,
-                "QuantityName:="     , QuantityName,
-                "PlotFolder:="       , PlotFolder,
-                "StreamlinePlot:="   , StreamlinePlot,
-                "AdjacentSidePlot:=" , AdjacentSidePlot,
-                "FullModelPlot:="    , FullModelPlot,
-                "IntrinsicVar:="     , IntrinsicVar,
-                "PlotGeomInfo:="     , PlotGeomInfo,
-            ]
-        # yapf: enable
-        return oFieldsReport.CreateFieldPlot(args_list, "Field")
-
-    def plot_ansys_delete(self, names: list):
-        """
-        (deprecated) Use clear_fields()
-        """
-        self.logger.warning(
-            "This method is deprecated. Change your scripts to use clear_fields()"
-        )
-        self.clear_fields(names)
-
-    def clear_fields(self, names: list):
-        """
-        Delete field plots from modeler window in Ansys.
-        Does not throw an error if names are missing.
-
-        Can give multiple names, for example:
-        hfss.plot_ansys_delete(['Mag_E1', 'Mag_E1_2'])
-
-        Args:
-            names (list): Names of plots to delete from modeler window.
-        """
-        if not names:
-            names = list(self.pinfo.design._fields_calc.GetFieldPlotNames())
-        return self.pinfo.design._fields_calc.DeleteFieldPlot(names)
-
-    def add_message(self, msg: str, severity: int = 0):
-        """Add message to Message Manager box in Ansys.
-
-        Args:
-            msg (str): Message to add.
-            severity (int): 0 = Informational, 1 = Warning, 2 = Error, 3 = Fatal.
-        """
-        self.pinfo.design.add_message(msg, severity)
-
-    def save_screenshot(self, path: str = None, show: bool = True):
-        """Save the screenshot.
-
-        Args:
-            path (str, optional): Path to save location.  Defaults to None.
-            show (bool, optional): Whether or not to display the screenshot.  Defaults to True.
-
-        Returns:
-            pathlib.WindowsPath: path to png formatted screenshot.
-        """
-        self.modeler._modeler.ShowWindow()
-        try:
-            return self.pinfo.design.save_screenshot(path, show)
-        except AttributeError:
-            self.logger.error(
-                "Please install a more recent version of pyEPR (>=0.8.4.3)")
-
-    def execute_design(
-        self,
-        design_name: str,
-        solution_type: str,
-        vars_to_initialize: Dict,
-        force_redraw: bool = False,
-        **design_selection,
-    ) -> str:
-        """It wraps the render_design() method to
-        1. skip rendering if the "selection" of components is left empty (re-uses selected design)
-        2. force design clearing and redraw if force_Redraw is set
-
-        Args:
-            design_name (str): Name to assign to the renderer design
-            solution_type (str): eigenmode, capacitive or drivenmodal
-            vars_to_initialize (Dict): Variables to initialize, i.e. Ljx, Cjx
-            force_redraw (bool, optional): Force re-render the design. Defaults to False.
-
-        Returns:
-            str: final design name (a suffix might have been added to the provided name,
-                in case of conflicts)
-        """
-        # If a selection of components is not specified, use the active renderer-design
-        if "selection" in design_selection:
-            if design_selection["selection"] is None:
-                try:
-                    return self.pinfo.design.name
-                except AttributeError:
-                    # if no design exists, then we will proceed and render the full design instead
-                    pass
-
-        # either create a new one, or clear the active one, depending on force_redraw.
-        if force_redraw and (design_name
-                             in self.pinfo.project.get_design_names()):
-            self.activate_ansys_design(design_name, solution_type)
-            self.clean_active_design()
-        else:
-            self.new_ansys_design(design_name, solution_type)
-        self.set_variables(vars_to_initialize)
-        self.render_design(**design_selection)
-        return self.pinfo.design.name
-
-    def new_ansys_design(self,
-                         design_name: str,
-                         solution_type: str,
-                         connect: bool = True):
-        """Add an Ansys design with the given name to the Ansys project.
-        Valid solutions_type values are: 'capacitive' (q3d), 'eignemode' and 'drivenmodal' (hfss)
-
-        Args:
-            design_name (str): name of the Design to be created in Ansys
-            solution_type (str): defines type of Design and solution to be created in Ansys
-            connect (bool, optional): Should we connect qiskit-metal to this Ansy design? Defaults to True.
-
-        Returns(pyEPR.ansys.HfssDesign): The pointer to the design within Ansys.
-
-        """
-        if self.pinfo:
-            try:
-                if solution_type == "capacitive":
-                    adesign = self.pinfo.project.new_q3d_design(design_name)
-                elif solution_type == "eigenmode":
-                    adesign = self.pinfo.project.new_em_design(design_name)
-                elif solution_type == "drivenmodal":
-                    adesign = self.pinfo.project.new_dm_design(design_name)
-                else:
-                    self.logger.error(
-                        f"The solution_type = {solution_type} is not supported by this renderer"
-                    )
-            except AttributeError:
-                if self.pinfo.project is None:
-                    self.logger.error("Project not found")
-                else:
-                    self.logger.error(
-                        "Please install a more recent version of pyEPR (>=0.8.4.4)"
-                    )
-                raise
-            if connect:
-                self.connect_ansys_design(adesign.name)
-            return adesign
-        else:
-            self.logger.info(
-                "You have to first connect to Ansys and to a project "
-                "before creating a new design. You can use renderer.connect_ansys()"
-            )
-
-    def activate_ansys_design(self,
-                              design_name: str,
-                              solution_type: str = None):
-        """Select a design with the given name from the open project.
-        If the design exists, that will be added WITHOUT altering the suffix of the design name.
-
-        Args:
-            name (str): Name of the new Ansys design
-        """
-
-        if self.pinfo:
-            if self.pinfo.project:
-                try:
-                    names_in_design = self.pinfo.project.get_design_names()
-                except AttributeError:
-                    self.logger.error(
-                        "Please install a more recent version of pyEPR (>=0.8.4.5)"
-                    )
-
-                if design_name in names_in_design:
-                    self.pinfo.connect_design(design_name)
-                    oDesktop = (self.pinfo.design.parent.parent._desktop
-                               )  # self.pinfo.design does not work
-                    oProject = oDesktop.SetActiveProject(
-                        self.pinfo.project_name)
-                    oDesign = oProject.SetActiveDesign(design_name)
-                    current_solution_type = self.pinfo.design.solution_type.lower(
-                    )
-                    if current_solution_type == "q3d":
-                        current_solution_type = "capacitive"
-                    if (current_solution_type != solution_type and
-                            solution_type is not None):
-                        self.logger.warning(
-                            f"The design_name={design_name} already exists, but it has solution_type=="
-                            f"{current_solution_type}, which is different from the requested=={solution_type}. "
-                            f"If you want a design with solution type=={solution_type}, please change the name "
-                            "requested for your design to one that does not exist. Alternatively, manually modify "
-                            f"the solution_type for design {design_name} from the Ansys GUI."
-                        )
-                else:
-                    self.logger.warning(
-                        f"The design_name={design_name} was not in active project.  "
-                        f"Designs in active project are: \n{names_in_design}.  "
-                        "A new design will be added to the project.  ")
-                    if solution_type is not None:
-                        adesign = self.new_ansys_design(
-                            design_name=design_name,
-                            solution_type=solution_type,
-                            connect=True,
-                        )
-                    else:
-                        self.logger.error(
-                            "Please specify the solution_type, to determine what design to create"
-                        )
-            else:
-                self.logger.warning(
-                    "Project not found, have you opened a project?")
-        else:
-            self.logger.warning(
-                "Have you run start()?  Cannot find a reference to Ansys in QRenderer."
-            )
-
-    def new_ansys_setup(self, name: str, **other_setup):
-        """Determines the appropriate setup to be created based on the pinfo.design.solution_type.
-        make sure to set this variable before executing this method
-
-        Args:
-            name (str): name to give to the new setup
-
-        Returns:
-            pyEPR.ansys.HfssEMSetup: Pointer to the ansys setup object
-        """
-        # TODO: only use activate_ansys_setup?
-        if self.pinfo:
-            if self.pinfo.design:
-                if "reuse_setup" in other_setup:
-                    if other_setup["reuse_setup"]:
-                        # delete_setup will check if setup exists, before deleting.
-                        self.pinfo.design.delete_setup(name)
-
-                if self.pinfo.design.solution_type == "Eigenmode":
-                    setup = self.add_eigenmode_setup(name, **other_setup)
-                elif self.pinfo.design.solution_type == "DrivenModal":
-                    setup = self.add_drivenmodal_setup(name, **other_setup)
-                elif self.pinfo.design.solution_type == "Q3D":
-                    setup = self.add_q3d_setup(name, **other_setup)
-        return setup
-
-    def initialize_cap_extract(self, **kwargs):
-        """Any task that needs to occur before running a simulation, such as creating a setup
-
-        Returns:
-            str: Name of the setup that has been updated
-        """
-
-        setup = self.new_ansys_setup(**kwargs)  # TODO: activate_ansys_setup?
-        return setup.name
-
-    def initialize_eigenmode(self, vars: Dict = {}, **kwargs):
-        """Any task that needs to occur before running a simulation, such as creating a setup
-
-        Args:
-            vars (Dict, optional): list of parametric variables to set in the renderer.
-                Defaults to {}.
-
-        Returns:
-            str: Name of the setup that has been updated
-        """
-        self.set_variables(vars)
-        setup = self.new_ansys_setup(**kwargs)  # TODO: activate_ansys_setup?
-        return setup.name
-
-    def initialize_drivenmodal(self,
-                               sweep_setup: Dict,
-                               vars: Dict = {},
-                               **kwargs):
-        """Any task that needs to occur before running a simulation, such as creating a setup
-
-        Args:
-            sweep_setup (Dict): list of parametric variables to set the frequency sweep.
-            vars (Dict, optional): list of parametric variables to set in the renderer.
-                Defaults to {}.
-
-        Returns:
-            str: Name of the setup that has been updated
-        """
-        self.set_variables(vars)
-        setup = self.new_ansys_setup(**kwargs)  # TODO: activate_ansys_setup?
-        sweep = self.add_sweep(setup.name, **sweep_setup)
-        return setup.name, sweep.name
-
-    def activate_ansys_setup(self, setup_name: str):
-        """For active design, either get existing setup, make new setup with name,
-        or make new setup with default name.
-
-        Args:
-            setup_name (str, optional): If name exists for setup, then have pinfo reference it.
-              If name for setup does not exist, create a new setup with the name.
-              If name is None, create a new setup with default name.
-        """
-        if self.pinfo:
-            if self.pinfo.project:
-                if self.pinfo.design:
-                    # look for setup name, if not there, then add a new one
-                    if setup_name:
-                        all_setup_names = self.pinfo.design.get_setup_names()
-                        self.pinfo.setup_name = setup_name
-                        if setup_name in all_setup_names:
-                            # When name is given and in design. So have pinfo reference existing setup.
-                            self.pinfo.setup = self.pinfo.get_setup(setup_name)
-                        else:
-                            # When name is given, but not in design. So make a new setup with given name.
-                            self.logger.warning(
-                                f"The setup_name={setup_name} was not in active design.  "
-                                f"Setups in active design are: \n{all_setup_names}.  "
-                                "A new setup will default values will be added to the design.  "
-                            )
-                            self.pinfo.setup = self.new_ansys_setup(
-                                name=setup_name)
-                    else:
-                        self.logger.warning(f"Please specify a setup_name.")
-                else:
-                    self.logger.warning(
-                        "Design not found in selected project, have you opened a design?"
-                    )
-            else:
-                self.logger.warning(
-                    "Project not found, have you opened a project?")
-        else:
-            self.logger.warning(
-                "Have you run connect_ansys()?  Cannot find a reference to Ansys in QRenderer."
-            )
-
-    def render_design(
-        self,
-        selection: Union[list, None] = None,
-        open_pins: Union[list, None] = None,
-        box_plus_buffer: bool = True,
-    ):
-        """Initiate rendering of components in design contained in selection,
-        assuming they're valid. Components are rendered before the chips they
-        reside on, and subtraction of negative shapes is performed at the very
-        end.
-
-        First obtain a list of IDs of components to render and a corresponding case, denoted by self.qcomp_ids
-        and self.case, respectively. If self.case == 1, all components in QDesign are to be rendered.
-        If self.case == 0, a strict subset of components in QDesign are to be rendered. Otherwise, if
-        self.case == 2, one or more component names in selection cannot be found in QDesign.
-
-        Chip_subtract_dict consists of component names (keys) and a set of all elements within each component that
-        will eventually be subtracted from the ground plane. Add objects that are perfect conductors and/or have
-        meshing to self.assign_perfE and self.assign_mesh, respectively; both are initialized as empty lists. Note
-        that these objects are "refreshed" each time render_design is called (as opposed to in the init function)
-        to clear QAnsysRenderer of any leftover items from the last call to render_design.
-
-        Among the components selected for export, there may or may not be unused (unconnected) pins.
-        The second parameter, open_pins, contains tuples of the form (component_name, pin_name) that
-        specify exactly which pins should be open rather than shorted during the simulation. Both the
-        component and pin name must be specified because the latter could be shared by multiple
-        components. All pins in this list are rendered with an additional endcap in the form of a
-        rectangular cutout, to be subtracted from its respective plane.
-
-        The final parameter, box_plus_buffer, determines how the chip is drawn. When set to True, it takes the
-        minimum rectangular bounding box of all rendered components and adds a buffer of x_buffer_width_mm and
-        y_buffer_width_mm horizontally and vertically, respectively, to the chip size. The center of the chip
-        lies at the midpoint x/y coordinates of the minimum rectangular bounding box and may change depending
-        on which components are rendered and how they're positioned. If box_plus_buffer is False, however, the
-        chip position and dimensions are taken from the chip info dictionary found in QDesign, irrespective
-        of what's being rendered. While this latter option is faster because it doesn't require calculating a
-        bounding box, it runs the risk of rendered components being too close to the edge of the chip or even
-        falling outside its boundaries.
-
-        Args:
-            selection (Union[list, None], optional): List of components to render. Defaults to None.
-            open_pins (Union[list, None], optional): List of tuples of pins that are open. Defaults to None.
-            box_plus_buffer (bool): Either calculate a bounding box based on the location of rendered geometries
-                                     or use chip size from design class.
-        """
-        self.qcomp_ids, self.case = self.get_unique_component_ids(selection)
-
-        if self.case == 2:
-            self.logger.warning(
-                "Unable to proceed with rendering. Please check selection.")
-            return
-
-        self.chip_subtract_dict = defaultdict(set)
-        self.assign_perfE = []
-        self.assign_mesh = []
-
-        self.render_tables()
-        self.add_endcaps(open_pins)
-
-        self.render_chips(box_plus_buffer=box_plus_buffer)
-        self.subtract_from_ground()
-        self.add_mesh()
-
-    def render_chip(self):
-        pass
-
-    def render_component(self):
-        pass
-
-    def render_tables(self, skip_junction: bool = False):
-        """
-        Render components in design grouped by table type (path, poly, or junction).
-        """
-        for table_type in self.design.qgeometry.get_element_types():
-            if table_type != "junction" or not skip_junction:
-                self.render_components(table_type)
-
-    def render_components(self, table_type: str):
-        """
-        Render components by breaking them down into individual elements.
-
-        Args:
-            table_type (str): Table type (poly, path, or junction).
-        """
-        table = self.design.qgeometry.tables[table_type]
-
-        if self.case == 0:  # Render a subset of components using mask
-            mask = table["component"].isin(self.qcomp_ids)
-            table = table[mask]
-
-        for _, qgeom in table.iterrows():
-            self.render_element(qgeom, bool(table_type == "junction"))
-
-        if table_type == "path":
-            self.auto_wirebonds(table)
-
-    def render_element(self, qgeom: pd.Series, is_junction: bool):
-        """Render an individual shape whose properties are listed in a row of
-        QGeometry table. Junction elements are handled separately from non-
-        junction elements, as the former consist of two rendered shapes, not
-        just one.
-
-        Args:
-            qgeom (pd.Series): GeoSeries of element properties.
-            is_junction (bool): Whether or not qgeom belongs to junction table.
-        """
-        qc_shapely = qgeom.geometry
-        if is_junction:
-            self.render_element_junction(qgeom)
-        else:
-            if isinstance(qc_shapely, shapely.geometry.Polygon):
-                self.render_element_poly(qgeom)
-            elif isinstance(qc_shapely, shapely.geometry.LineString):
-                self.render_element_path(qgeom)
-
-    def render_element_junction(self, qgeom: pd.Series):
-        """
-        Render a Josephson junction consisting of
-            1. A rectangle of length pad_gap and width inductor_width. Defines lumped element
-               RLC boundary condition.
-            2. A line that is later used to calculate the voltage in post-processing analysis.
-
-        Args:
-            qgeom (pd.Series): GeoSeries of element properties.
-        """
-        ansys_options = dict(transparency=0.0)
-
-        qc_name = "Lj_" + str(qgeom["component"])
-        qc_elt = get_clean_name(qgeom["name"])
-        qc_shapely = qgeom.geometry
-        qc_chip_z = parse_units(self.design.get_chip_z(qgeom.chip))
-        qc_width = parse_units(qgeom.width)
-
-        name = f"{qc_name}{QAnsysRenderer.NAME_DELIM}{qc_elt}"
-
-        endpoints = parse_units(list(qc_shapely.coords))
-        endpoints_3d = to_vec3D(endpoints, qc_chip_z)
-        x0, y0, z0 = endpoints_3d[0]
-        x1, y1, z0 = endpoints_3d[1]
-        if abs(y1 - y0) > abs(x1 - x0):
-            # Junction runs vertically up/down
-            x_min, x_max = x0 - qc_width / 2, x0 + qc_width / 2
-            y_min, y_max = min(y0, y1), max(y0, y1)
-        else:
-            # Junction runs horizontally left/right
-            x_min, x_max = min(x0, x1), max(x0, x1)
-            y_min, y_max = y0 - qc_width / 2, y0 + qc_width / 2
-
-        # Draw rectangle
-        self.logger.debug(f"Drawing a rectangle: {name}")
-        poly_ansys = self.modeler.draw_rect_corner(
-            [x_min, y_min, qc_chip_z],
-            x_max - x_min,
-            y_max - y_min,
-            qc_chip_z,
-            **ansys_options,
-        )
-        axis = "x" if abs(x1 - x0) > abs(y1 - y0) else "y"
-        self.modeler.rename_obj(poly_ansys, "JJ_rect_" + name)
-        self.assign_mesh.append("JJ_rect_" + name)
-
-        # Draw line
-        poly_jj = self.modeler.draw_polyline(
-            [endpoints_3d[0], endpoints_3d[1]],
-            closed=False,
-            **dict(color=(128, 0, 128)),
-        )
-        poly_jj = poly_jj.rename("JJ_" + name + "_")
-        poly_jj.show_direction = True
-
-    def render_element_poly(self, qgeom: pd.Series):
-        """Render a closed polygon.
-
-        Args:
-            qgeom (pd.Series): GeoSeries of element properties.
-        """
-        ansys_options = dict(transparency=0.0)
-
-        qc_name = self.design._components[qgeom["component"]].name
-        qc_elt = get_clean_name(qgeom["name"])
-
-        qc_shapely = qgeom.geometry  # shapely geom
-        qc_chip_z = parse_units(self.design.get_chip_z(qgeom.chip))
-        qc_fillet = round(qgeom.fillet, 7)
-
-        name = f"{qc_elt}{QAnsysRenderer.NAME_DELIM}{qc_name}"
-
-        points = parse_units(list(
-            qc_shapely.exterior.coords))  # list of 2d point tuples
-        points_3d = to_vec3D(points, qc_chip_z)
-
-        if is_rectangle(qc_shapely):  # Draw as rectangle
-            self.logger.debug(f"Drawing a rectangle: {name}")
-            x_min, y_min, x_max, y_max = qc_shapely.bounds
-            poly_ansys = self.modeler.draw_rect_corner(
-                *parse_units([
-                    [x_min, y_min,
-                     self.design.get_chip_z(qgeom.chip)],
-                    x_max - x_min,
-                    y_max - y_min,
-                    0,
-                ]),
-                **ansys_options,
-            )
-            self.modeler.rename_obj(poly_ansys, name)
-
-        else:
-            # Draw general closed poly
-            poly_ansys = self.modeler.draw_polyline(points_3d[:-1],
-                                                    closed=True,
-                                                    **ansys_options)
-            # rename: handle bug if the name of the cut already exits and is used to make a cut
-            poly_ansys = poly_ansys.rename(name)
-
-        qc_fillet = round(qgeom.fillet, 7)
-        if qc_fillet > 0:
-            qc_fillet = parse_units(qc_fillet)
-            idxs_to_fillet = good_fillet_idxs(
-                points,
-                qc_fillet,
-                precision=self.design._template_options.PRECISION,
-                isclosed=True,
-            )
-            if idxs_to_fillet:
-                self.modeler._fillet(qc_fillet, idxs_to_fillet, poly_ansys)
-
-        # Subtract interior shapes, if any
-        if len(qc_shapely.interiors) > 0:
-            for i, x in enumerate(qc_shapely.interiors):
-                interior_points_3d = to_vec3D(parse_units(list(x.coords)),
-                                              qc_chip_z)
-                inner_shape = self.modeler.draw_polyline(
-                    interior_points_3d[:-1], closed=True)
-                self.modeler.subtract(name, [inner_shape])
-
-        # Input chip info into self.chip_subtract_dict
-        if qgeom.chip not in self.chip_subtract_dict:
-            self.chip_subtract_dict[qgeom.chip] = set()
-
-        if qgeom["subtract"]:
-            self.chip_subtract_dict[qgeom.chip].add(name)
-
-        # Potentially add to list of elements to metallize
-        elif not qgeom["helper"]:
-            self.assign_perfE.append(name)
-
-    def render_element_path(self, qgeom: pd.Series):
-        """Render a path-type element.
-
-        Args:
-            qgeom (pd.Series): GeoSeries of element properties.
-        """
-        ansys_options = dict(transparency=0.0)
-
-        qc_name = self.design._components[qgeom["component"]].name
-        qc_elt = get_clean_name(qgeom["name"])
-
-        qc_shapely = qgeom.geometry  # shapely geom
-        qc_chip_z = parse_units(self.design.get_chip_z(qgeom.chip))
-
-        name = f"{qc_elt}{QAnsysRenderer.NAME_DELIM}{qc_name}"
-
-        qc_width = parse_units(qgeom.width)
-
-        points = parse_units(list(qc_shapely.coords))
-        points_3d = to_vec3D(points, qc_chip_z)
-
-        try:
-            poly_ansys = self.modeler.draw_polyline(points_3d,
-                                                    closed=False,
-                                                    **ansys_options)
-        except AttributeError:
-            if self.modeler is None:
-                self.logger.error(
-                    "No modeler was found. Are you connected to an active Ansys Design?"
-                )
-            raise
-
-        poly_ansys = poly_ansys.rename(name)
-
-        qc_fillet = round(qgeom.fillet, 7)
-        if qc_fillet > 0:
-            qc_fillet = parse_units(qc_fillet)
-            idxs_to_fillet = good_fillet_idxs(
-                points,
-                qc_fillet,
-                precision=self.design._template_options.PRECISION,
-                isclosed=False,
-            )
-            if idxs_to_fillet:
-                self.modeler._fillet(qc_fillet, idxs_to_fillet, poly_ansys)
-
-        if qc_width:
-            x0, y0 = points[0]
-            x1, y1 = points[1]
-            vlen = math.sqrt((x1 - x0)**2 + (y1 - y0)**2)
-            p0 = np.array([
-                x0, y0, qc_chip_z
-            ]) + qc_width / (2 * vlen) * np.array([y0 - y1, x1 - x0, 0])
-            p1 = np.array([
-                x0, y0, qc_chip_z
-            ]) + qc_width / (2 * vlen) * np.array([y1 - y0, x0 - x1, 0])
-            shortline = self.modeler.draw_polyline([p0, p1],
-                                                   closed=False)  # sweepline
-            import pythoncom
-
-            try:
-                self.modeler._sweep_along_path(shortline, poly_ansys)
-            except pythoncom.com_error as error:
-                print("com_error: ", error)
-                hr, msg, exc, arg = error.args
-                if msg == "Exception occurred." and hr == -2147352567:
-                    self.logger.error(
-                        "We cannot find a writable design. \n  Either you are trying to use a Ansys "
-                        "design that is not empty, in which case please clear it manually or with the "
-                        "renderer method clean_active_design(). \n  Or you accidentally deleted "
-                        "the design in Ansys, in which case please create a new one."
-                    )
-                raise error
-
-        if qgeom.chip not in self.chip_subtract_dict:
-            self.chip_subtract_dict[qgeom.chip] = set()
-
-        if qgeom["subtract"]:
-            self.chip_subtract_dict[qgeom.chip].add(name)
-
-        elif qgeom["width"] and (not qgeom["helper"]):
-            self.assign_perfE.append(name)
-
-    def add_endcaps(self, open_pins: Union[list, None] = None):
-        """Create endcaps (rectangular cutouts) for all pins in the list
-        open_pins and add them to chip_subtract_dict. Each element in open_pins
-        takes on the form (component_name, pin_name) and corresponds to a
-        single pin.
-
-        Args:
-            open_pins (Union[list, None], optional): List of tuples of pins that are open. Defaults to None.
-        """
-        open_pins = open_pins if open_pins else []
-
-        for comp, pin in open_pins:
-            pin_dict = self.design.components[comp].pins[pin]
-            width, gap = parse_units([pin_dict["width"], pin_dict["gap"]])
-            mid, normal = parse_units(pin_dict["middle"]), pin_dict["normal"]
-            chip_name = self.design.components[comp].options.chip
-            qc_chip_z = parse_units(self.design.get_chip_z(chip_name))
-            rect_mid = np.append(mid + normal * gap / 2, [qc_chip_z])
-            # Assumption: pins only point in x or y directions
-            # If this assumption is not satisfied, draw_rect_center no longer works -> must use draw_polyline
-            endcap_name = f"endcap_{comp}_{pin}"
-            if abs(normal[0]) > abs(normal[1]):
-                self.modeler.draw_rect_center(rect_mid,
-                                              x_size=gap,
-                                              y_size=width + 2 * gap,
-                                              name=endcap_name)
-            else:
-                self.modeler.draw_rect_center(rect_mid,
-                                              x_size=width + 2 * gap,
-                                              y_size=gap,
-                                              name=endcap_name)
-            self.chip_subtract_dict[pin_dict["chip"]].add(endcap_name)
-
-    def get_chip_names(self) -> List[str]:
-        """
-        Obtain a list of chips on which the selection of components, if valid, resides.
-
-        Returns:
-            List[str]: Chips to render.
-        """
-        if self.case == 2:  # One or more components not in QDesign.
-            self.logger.warning("One or more components not found.")
-            return []
-        chip_names = set()
-        if self.case == 1:  # All components rendered.
-            comps = self.design.components
-            for qcomp in comps:
-                if "chip" not in comps[qcomp].options:
-                    self.chip_designation_error()
-                    return []
-                # elif comps[qcomp].options.chip != 'main':
-                #    self.chip_not_main()
-                #    return []
-                chip_names.add(comps[qcomp].options.chip)
-        else:  # Strict subset rendered.
-            icomps = self.design._components
-            for qcomp_id in self.qcomp_ids:
-                if "chip" not in icomps[qcomp_id].options:
-                    self.chip_designation_error()
-                    return []
-                # elif icomps[qcomp_id].options.chip != 'main':
-                #    self.chip_not_main()
-                #    return []
-                chip_names.add(icomps[qcomp_id].options.chip)
-
-        for unique_name in chip_names:
-            if unique_name not in self.design.chips:
-                self.chip_not_in_design_error(unique_name)
-
-        return list(chip_names)
-
-    def chip_designation_error(self):
-        """
-        Warning message that appears when the Ansys renderer fails to locate a component's chip designation.
-        Provides instructions for a temporary workaround until the layer stack is finalized.
-        """
-        self.logger.warning(
-            "This component currently lacks a chip designation. Please add chip='main' to the component's default_options dictionary, restart the kernel, and try again."
-        )
-
-    def chip_not_in_design_error(self, missing_chip: str):
-        """
-        Warning message that appears when the Ansys renderer fails to locate a component's chip designation in DesignPlanar (or any child of QDesign).
-        Provides instructions for a temporary workaround until the layer stack is finalized.
-        """
-        self.logger.warning(
-            f'This component currently lacks a chip designation in DesignPlanar, or any child of QDesign. '
-            f'Please add dict for chip=\'{missing_chip}\' in DesignPlanar, or child of QDesign. Then restart the kernel, and try again.'
-        )
-
-    def chip_not_main(self):
-        """
-        Warning message that appears when a component's chip designation is not 'main'.
-        As of 05/10/21, all chip designations should be 'main' until the layer stack is finalized.
-        Provides instructions for a temporary workaround until the layer stack is finalized.
-        """
-        self.logger.warning(
-            "The chip designation for this component is not 'main'. Please set chip='main' in its default_options dictionary, restart the kernel, and try again."
-        )
-
-    def get_min_bounding_box(self) -> Tuple[float]:
-        """
-        Determine the max/min x/y coordinates of the smallest rectangular, axis-aligned
-        bounding box that will enclose a selection of components to render, given by
-        self.qcomp_ids. This method is only used when box_plus_buffer is True.
-
-        Returns:
-            Tuple[float]: min x, min y, max x, and max y coordinates of bounding box.
-        """
-        min_x_main = min_y_main = float("inf")
-        max_x_main = max_y_main = float("-inf")
-        if self.case == 2:  # One or more components not in QDesign.
-            self.logger.warning("One or more components not found.")
-        elif self.case == 1:  # All components rendered.
-            for qcomp in self.design.components:
-                min_x, min_y, max_x, max_y = self.design.components[
-                    qcomp].qgeometry_bounds()
-                min_x_main = min(min_x, min_x_main)
-                min_y_main = min(min_y, min_y_main)
-                max_x_main = max(max_x, max_x_main)
-                max_y_main = max(max_y, max_y_main)
-        else:  # Strict subset rendered.
-            for qcomp_id in self.qcomp_ids:
-                min_x, min_y, max_x, max_y = self.design._components[
-                    qcomp_id].qgeometry_bounds()
-                min_x_main = min(min_x, min_x_main)
-                min_y_main = min(min_y, min_y_main)
-                max_x_main = max(max_x, max_x_main)
-                max_y_main = max(max_y, max_y_main)
-        return min_x_main, min_y_main, max_x_main, max_y_main
-
-    def render_chips(self,
-                     draw_sample_holder: bool = True,
-                     box_plus_buffer: bool = True):
-        """
-        Render all chips containing components in self.qcomp_ids.
-
-        Args:
-            draw_sample_holder (bool, optional): Option to draw vacuum box around chip. Defaults to True.
-            box_plus_buffer (bool, optional): Whether or not to use a box plus buffer. Defaults to True.
-        """
-        chip_list = self.get_chip_names()
-        # added this quick hack for the case of flipchip device.
-        # current self.get_chip_names only renders chips whose components are to be rendered.
-        # so if you happen to only draw a qubit only, then the other chip (C_chip) does not get rendered because get_chip_names only returns Q_chip
-        # I hope this gets a prettier fix in the future
-        if self.design._metadata.design_name == 'FlipChip_Device':
-            chip_list = self.design.chips.keys()
-        self.cw_x, self.cw_y = Dict(), Dict()
-        self.cc_x, self.cc_y = Dict(), Dict()
-
-        for chip_name in chip_list:
-            if box_plus_buffer:  # Get bounding box of components first
-                min_x_main, min_y_main, max_x_main, max_y_main = parse_units(
-                    self.get_min_bounding_box())
-                self.cw_x.update({chip_name: max_x_main - min_x_main
-                                 })  # chip width along x
-                self.cw_y.update({chip_name: max_y_main - min_y_main
-                                 })  # chip width along y
-                self.cw_x[chip_name] += 2 * parse_units(
-                    self._options["x_buffer_width_mm"])
-                self.cw_y[chip_name] += 2 * parse_units(
-                    self._options["y_buffer_width_mm"])
-                self.cc_x.update({chip_name:
-                    (max_x_main + min_x_main) / 2})  # x coord of chip center
-                self.cc_y.update({chip_name:
-                    (max_y_main + min_y_main) / 2})  # y coord of chip center
-            else:  # Adhere to chip placement and dimensions in QDesign
-                p = self.design.get_chip_size(
-                    chip_name)  # x/y center/width same for all chips
-                self.cw_x.update({chip_name: parse_units(p["size_x"])})
-                self.cw_y.update({chip_name: parse_units(p["size_y"])})
-                self.cc_x.update({chip_name: parse_units(p["center_x"])})
-                self.cc_y.update({chip_name: parse_units(p["center_y"])})
-                # self.cw_x, self.cw_y, _ = parse_units(
-                #    [p['size_x'], p['size_y'], p['size_z']])
-                # self.cc_x, self.cc_y, _ = parse_units(
-                #    [p['center_x'], p['center_y'], p['center_z']])
-            self.render_chip(chip_name, draw_sample_holder)
-
-        if draw_sample_holder:  # HFSS
-            if "sample_holder_top" in self.design.variables.keys():
-                p = self.design.variables
-            else:
-                p = self.design.get_chip_size(chip_list[0])
-            vac_height = parse_units(
-                [p["sample_holder_top"], p["sample_holder_bottom"]])
-            # very simple algorithm to build the vacuum box. It could be made better in the future
-            # assuming that both
-            cc_x = np.array([item for item in self.cc_x.values()])
-            cc_y = np.array([item for item in self.cc_y.values()])
-            cw_x = np.array([item for item in self.cw_x.values()])
-            cw_y = np.array([item for item in self.cw_y.values()])
-
-            cc_x_left, cc_x_right = np.min(cc_x - cw_x / 2), np.max(cc_x +
-                                                                    cw_x / 2)
-            cc_y_left, cc_y_right = np.min(cc_y - cw_y / 2), np.max(cc_y +
-                                                                    cw_y / 2)
-
-            cc_x = (cc_x_left + cc_x_right) / 2
-            cc_y = (cc_y_left + cc_y_right) / 2
-            cw_x = cc_x_right - cc_x_left
-            cw_y = cc_y_right - cc_y_left
-
-            vacuum_box = self.modeler.draw_box_center(
-                [cc_x, cc_y, (vac_height[0] - vac_height[1]) / 2],
-                [cw_x, cw_y, sum(vac_height)],
-                name="sample_holder",
-            )
-
-    def render_chip(self, chip_name: str, draw_sample_holder: bool):
-        """
-        Render individual chips.
-
-        Args:
-            chip_name (str): Name of chip.
-            draw_sample_holder (bool): Option to draw vacuum box around chip.
-        """
-        ansys_options = dict(transparency=0.0)
-        ops = self.design._chips[chip_name]
-        p = self.design.get_chip_size(chip_name)
-        z_coord, height = parse_units([p["center_z"], p["size_z"]])
-        plane = self.modeler.draw_rect_center(
-            [self.cc_x[chip_name], self.cc_y[chip_name], z_coord],
-            x_size=self.cw_x[chip_name],
-            y_size=self.cw_y[chip_name],
-            z_size=0,
-            name=f"ground_{chip_name}_plane",
-            **ansys_options,
-        )
-        whole_chip = self.modeler.draw_box_center(
-            [self.cc_x[chip_name], self.cc_y[chip_name], z_coord + height / 2],
-            [self.cw_x[chip_name], self.cw_y[chip_name], -height],
-            name=chip_name,
-            material=ops["material"],
-            color=(186, 186, 205),
-            transparency=0.2,
-            wireframe=False,
-        )
-        if self.chip_subtract_dict[chip_name]:
-            # Any layer which has subtract=True qgeometries will have a ground plane
-            # TODO: Material property assignment may become layer-dependent.
-            self.assign_perfE.append(f"ground_{chip_name}_plane")
-
-    def subtract_from_ground(self):
-        """For each chip, subtract all "negative" shapes residing on its
-        surface if any such shapes exist."""
-        for chip, shapes in self.chip_subtract_dict.items():
-            if shapes:
-                import pythoncom
-
-                try:
-                    self.modeler.subtract(f"ground_{chip}_plane", list(shapes))
-                except pythoncom.com_error as error:
-                    print("com_error: ", error)
-                    hr, msg, exc, arg = error.args
-                    if msg == "Exception occurred." and hr == -2147352567:
-                        self.logger.error(
-                            "This error might indicate that a component was not correctly rendered in Ansys. \n"
-                            "This might have been caused by floating point numerical corrections. \n For example "
-                            "Ansys will inconsistently render (or not) routing that has 180deg jogs with the two "
-                            "adjacent segments spaced 'exactly' twice the fillet radius (U shaped routing). \n"
-                            "In this example, changing your fillet radius to a smaller number would solve the issue."
-                        )
-                    raise error
-
-    def add_mesh(self):
-        """Add mesh to all elements in self.assign_mesh."""
-        if self.assign_mesh:
-            self.modeler.mesh_length(
-                "small_mesh",
-                self.assign_mesh,
-                MaxLength=self._options["max_mesh_length_jj"],
-            )
-
-    # Still implementing
-    def auto_wirebonds(self, table):
-        """
-        Adds wirebonds to the Ansys model for path elements where;
-        subtract = True and wire_bonds = True.
-
-        Uses render options for determining of the:
-        * wb_threshold -- the minimum distance between two vertices of a path for a
-        wirebond to be added.
-        * wb_offset -- offset distance for wirebond placement (along the direction
-        of the cpw)
-        * wb_size -- controls the width of the wirebond (wb_size * path['width'])
-        """
-        norm_z = np.array([0, 0, 1])
-
-        wb_threshold = parse_units(self._options["wb_threshold"])
-        wb_offset = parse_units(self._options["wb_offset"])
-
-        # selecting only the qgeometry which meet criteria
-        wb_table = table.loc[table["hfss_wire_bonds"] == True]
-        wb_table2 = wb_table.loc[wb_table["subtract"] == True]
-
-        # looping through each qgeometry
-        for _, row in wb_table2.iterrows():
-            geom = row["geometry"]
-            width = row["width"]
-            # looping through the linestring of the path to determine where WBs should be
-            for index, i_p in enumerate(geom.coords[:-1], start=0):
-                j_p = np.asarray(geom.coords[:][index + 1])
-                vert_distance = parse_units(distance.euclidean(i_p, j_p))
-                if vert_distance > wb_threshold:
-                    # Gets number of wirebonds to fit in section of path
-                    wb_count = int(vert_distance // wb_threshold)
-                    # finds the position vector
-                    wb_pos = (j_p - i_p) / (wb_count + 1)
-                    # gets the norm vector for finding the orthonormal of path
-                    wb_vec = wb_pos / np.linalg.norm(wb_pos)
-                    # finds the orthonormal (for orientation)
-                    wb_perp = np.cross(norm_z, wb_vec)[:2]
-                    # finds the first wirebond to place (rest are in the loop)
-                    wb_pos_step = parse_units(wb_pos + i_p) + (wb_vec *
-                                                               wb_offset)
-                    # Other input values could be modified, kept to minimal selection for automation
-                    # for the time being. Loops to place N wirebonds based on length of path section.
-                    for wb_i in range(wb_count):
-                        self.modeler.draw_wirebond(
-                            pos=wb_pos_step + parse_units(wb_pos * wb_i),
-                            ori=wb_perp,
-                            width=parse_units(width * self._options["wb_size"]),
-                            height=parse_units(width *
-                                               self._options["wb_size"]),
-                            z=0,
-                            wire_diameter="0.015mm",
-                            NumSides=6,
-                            name="g_wb",
-                            material="pec",
-                            solve_inside=False,
-                        )
-
-    def clean_active_design(self):
-        """Remove all elements from Ansys Modeler."""
-        if self.pinfo:
-            if self.pinfo.get_all_object_names():
-                project_name = self.pinfo.project_name
-                design_name = self.pinfo.design_name
-                select_all = ",".join(self.pinfo.get_all_object_names())
-
-                # self.pinfo.design does not work, thus the following line
-                oDesktop = self.pinfo.design.parent.parent._desktop
-                oProject = oDesktop.SetActiveProject(project_name)
-                oDesign = oProject.SetActiveDesign(design_name)
-
-                # The available editors: "Layout", "3D Modeler", "SchematicEditor"
-                oEditor = oDesign.SetActiveEditor("3D Modeler")
-
-                oEditor.Delete(["NAME:Selections", "Selections:=", select_all])
-
-    def set_variables(self, variables: Dict):
-        """Fixes the junction properties before setup. This is necessary becasue the eigenmode
-        analysis only considers the junction as a lumped CL element.
-
-        Args:
-            variables (Dict): dictionary of variables to set in Ansys. For example it could
-                contain 'Lj': '10 nH'
-        """
-        if self.pinfo:
-            if self.pinfo.design:
-                for k, v in variables.items():
-                    self.pinfo.design.set_variable(k, v)
-            else:
-                self.logger.warning(
-                    "Please create a design before setting variables, otherwise all variables will be set to 0 during rendering by default."
-                )
-
-    # TODO: epr methods below should not be in the renderer, but in the analysis files.
-    #  Thus needs to remove the dependency from pinfo, which is Ansys-specific.
-
-    def epr_start(self, junctions: dict = None, dissipatives: dict = None):
-        """Use to initialize the epr analysis package by first identifying which are the junctions,
-        their electrical properties and their reference plane; then initialize the
-        DistributedAnalysis package, which can execute microwave analysis on eigenmode results.
-
-        Args:
-            junctions (dict, optional): Each element of this dictionary describes one junction.
-                Defaults to dict().
-            dissipatives (dict, optional): Each element of this dictionary describes one dissipative.
-                Defaults to dict().
-        """
-        if self.pinfo:
-            if junctions:
-                for k, v in junctions.items():
-                    self.pinfo.junctions[k] = v
-                # Check that valid names of variables and objects have been supplied
-                self.pinfo.validate_junction_info()
-            if dissipatives:
-                for k, v in dissipatives.items():
-                    self.pinfo.dissipative[k] = v
-
-            # Class handling microwave analysis on eigenmode solutions
-            self.epr_distributed_analysis = epr.DistributedAnalysis(self.pinfo)
-
-    def epr_get_stored_energy(self,
-                              junctions: dict = None,
-                              dissipatives: dict = None):
-        """Computes the energy stored in the system
-        pinfo must have a valid list of junctions and dissipatives to compute the energy stored
-        in the system. So please provide them here, or using epr_start()
-
-        Args:
-            junctions (dict, optional): Each element of this dictionary describes one junction.
-                Defaults to dict().
-            dissipatives (dict, optional): Each element of this dictionary describes one dissipative.
-                Defaults to dict().
-
-        Returns:
-            (float, float, float): energy_elec, energy_elec_substrate, energy_mag
-        """
-        if junctions is not None or dissipatives is not None:
-            self.epr_start(junctions, dissipatives)
-        elif self.epr_distributed_analysis is None:
-            self.epr_start()
-
-        if self.pinfo.dissipative["dielectrics_bulk"] is not None:
-            eprd = self.epr_distributed_analysis
-            energy_elec = eprd.calc_energy_electric()
-            energy_elec_substrate = eprd.calc_energy_electric(
-                None, self.pinfo.dissipative["dielectrics_bulk"][0])
-            energy_mag = eprd.calc_energy_magnetic()
-
-            return energy_elec, energy_elec_substrate, energy_mag
-        self.logger.error("dielectrics_bulk needs to be defined")
-
-    def epr_run_analysis(self,
-                         junctions: dict = None,
-                         dissipatives: dict = None):
-        """Executes the EPR analysis
-        pinfo must have a valid list of junctions and dissipatives to compute the energy stored
-        in the system. So please provide them here, or using epr_start()
-
-        Args:
-            junctions (dict, optional): Each element of this dictionary describes one junction.
-                Defaults to dict().
-            dissipatives (dict, optional): Each element of this dictionary describes one dissipative.
-                Defaults to dict().
-        """
-        if junctions is not None or dissipatives is not None:
-            self.epr_start(junctions, dissipatives)
-        self.epr_distributed_analysis.do_EPR_analysis()
-
-    def epr_spectrum_analysis(self, cos_trunc: int = 8, fock_trunc: int = 7):
-        """Core epr analysis method.
-
-        Args:
-            cos_trunc (int, optional): truncation of the cosine. Defaults to 8.
-            fock_trunc (int, optional): truncation of the fock. Defaults to 7.
-        """
-        self.epr_quantum_analysis = epr.QuantumAnalysis(
-            self.epr_distributed_analysis.data_filename)
-        self.epr_quantum_analysis.analyze_all_variations(cos_trunc=cos_trunc,
-                                                         fock_trunc=fock_trunc)
-
-    def epr_report_hamiltonian(self,
-                               swp_variable: str = "variation",
-                               numeric=True):
-        """Reports in a markdown friendly table the hamiltonian results.
-
-        Args:
-            swp_variable (str, optional): Variable against which we swept. Defaults to 'variation'.
-        """
-        self.epr_quantum_analysis.plot_hamiltonian_results(
-            swp_variable=swp_variable)
-        self.epr_quantum_analysis.report_results(swp_variable=swp_variable,
-                                                 numeric=numeric)
-
-    def epr_get_frequencies(self,
-                            junctions: dict = None,
-                            dissipatives: dict = None) -> pd.DataFrame:
-        """Returns all frequencies and quality factors vs a variation.
-        It also initializes the systems for the epr analysis in terms of junctions and dissipatives
-
-        Args:
-            junctions (dict, optional): Each element of this dictionary describes one junction.
-                Defaults to dict().
-            dissipatives (dict, optional): Each element of this dictionary describes one dissipative.
-                Defaults to dict().
-
-        Returns:
-            pd.DataFrame: multi-index, frequency and quality factors for each variation point.
-        """
-        # TODO: do I need to reset self.pinfo.junctions (does it keep the older analysis one)
-        self.epr_start(junctions, dissipatives)
-        return self.epr_distributed_analysis.get_ansys_frequencies_all()
+# -*- coding: utf-8 -*-
+
+# This code is part of Qiskit.
+#
+# (C) Copyright IBM 2017, 2021.
+#
+# This code is licensed under the Apache License, Version 2.0. You may
+# obtain a copy of this license in the LICENSE.txt file in the root directory
+# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+#
+# Any modifications or derivative works of this code must retain this
+# copyright notice, and modified files need to carry a notice indicating
+# that they have been altered from the originals.
+# pylint: disable=too-many-lines
+
+from typing import List, Tuple, Union
+
+import re
+import os
+from pathlib import Path
+import math
+import geopandas
+import numpy as np
+import pandas as pd
+from numpy.linalg import norm
+from collections import defaultdict
+from platform import system
+from scipy.spatial import distance
+
+import shapely
+import pyEPR as epr
+from pyEPR.ansys import parse_units, HfssApp, release
+
+from qiskit_metal.draw.utility import to_vec3D
+from qiskit_metal.draw.basic import is_rectangle
+from qiskit_metal.renderers.renderer_base import QRendererAnalysis
+from qiskit_metal.toolbox_metal.parsing import is_true
+
+from qiskit_metal import Dict
+
+from .. import config
+if not config.is_building_docs():
+    from qiskit_metal.toolbox_python.utility_functions import toggle_numbers, bad_fillet_idxs
+
+
+def good_fillet_idxs(coords: list,
+                     fradius: float,
+                     precision: int = 9,
+                     isclosed: bool = False):
+    """
+    Get list of vertex indices in a linestring (isclosed = False) or polygon (isclosed = True)
+    that can be filleted based on proximity to neighbors.
+
+    Args:
+        coords (list): Ordered list of tuples of vertex coordinates.
+        fradius (float): User-specified fillet radius from QGeometry table.
+        precision (int, optional): Digits of precision used for round(). Defaults to 9.
+        isclosed (bool, optional): Boolean denoting whether the shape is a linestring or
+            polygon. Defaults to False.
+
+    Returns:
+        list: List of indices of vertices that can be filleted.
+    """
+    if isclosed:
+        return toggle_numbers(
+            bad_fillet_idxs(coords, fradius, precision, isclosed=True),
+            len(coords))
+    return toggle_numbers(
+        bad_fillet_idxs(coords, fradius, precision, isclosed=False),
+        len(coords))[1:-1]
+
+
+def get_clean_name(name: str) -> str:
+    """Create a valid variable name from the given one by removing having it
+    begin with a letter or underscore followed by an unlimited string of
+    letters, numbers, and underscores.
+
+    Args:
+        name (str): Initial, possibly unusable, string to be modified.
+
+    Returns:
+        str: Variable name consistent with Python naming conventions.
+    """
+    # Remove invalid characters
+    name = re.sub('[^0-9a-zA-Z_]', '', name)
+    # Remove leading characters until we find a letter or underscore
+    name = re.sub('^[^a-zA-Z_]+', '', name)
+    return name
+
+
+class QAnsysRenderer(QRendererAnalysis):
+    """Extends QRenderer to export designs to Ansys using pyEPR. The methods
+	which a user will need for Ansys export should be found within this class.
+
+    Default Options:
+        * Lj: '10nH' -- Lj has units of nanoHenries (nH)
+        * Cj: 0 -- Cj *must* be 0 for pyEPR analysis! Cj has units of femtofarads (fF)
+        * _Rj: 0 -- _Rj *must* be 0 for pyEPR analysis! _Rj has units of Ohms
+        * max_mesh_length_jj: '7um' -- Maximum mesh length for Josephson junction elements
+        * max_mesh_length_port: '7um' -- Maximum mesh length for Ports in Eigenmode Simulations
+        * project_path: None -- Default project path; if None --> get active
+        * project_name: None -- Default project name
+        * design_name: None -- Default design name
+        * ansys_file_extension: '.aedt' -- Ansys file extension for 2016 version and newer
+        * x_buffer_width_mm: 0.2 -- Buffer between max/min x and edge of ground plane, in mm
+        * y_buffer_width_mm: 0.2 -- Buffer between max/min y and edge of ground plane, in mm
+        * wb_threshold:'400um' -- the minimum distance between two vertices of a path for a
+          wirebond to be added.
+        * wb_offset:'0um' -- offset distance for wirebond placement (along the direction
+          of the cpw)
+        * wb_size: 3 -- scalar which controls the width of the wirebond (wb_size * path['width'])
+    """
+
+    #: Default options, over-written by passing ``options` dict to render_options.
+    #: Type: Dict[str, str]
+    # yapf: disable
+    default_options = Dict(
+        Lj='10nH',  # Lj has units of nanoHenries (nH)
+        Cj=0,  # Cj *must* be 0 for pyEPR analysis! Cj has units of femtofarads (fF)
+        _Rj=0,  # _Rj *must* be 0 for pyEPR analysis! _Rj has units of Ohms
+        max_mesh_length_jj='7um',  # maximum mesh length for Josephson junction elements
+        max_mesh_length_port='7um', # maximum mesh length for Ports in Eigenmode Simulations
+        project_path=None,  # default project path; if None --> get active
+        project_name=None,  # default project name
+        design_name=None,  # default design name
+        # bounding_box_scale_x = 1.2, # Ratio of 'main' chip width to bounding box width
+        # bounding_box_scale_y = 1.2, # Ratio of 'main' chip length to bounding box length
+        x_buffer_width_mm=0.2,  # Buffer between max/min x and edge of ground plane, in mm
+        y_buffer_width_mm=0.2,  # Buffer between max/min y and edge of ground plane, in mm
+        wb_threshold = '400um',
+        wb_offset = '0um',
+        wb_size = 5,
+        plot_ansys_fields_options = Dict(
+            name="NAME:Mag_E1",
+            UserSpecifyName='0',
+            UserSpecifyFolder='0',
+            QuantityName= "Mag_E",
+            PlotFolder= "E Field",
+            StreamlinePlot= "False",
+            AdjacentSidePlot= "False",
+            FullModelPlot= "False",
+            IntrinsicVar= "Phase=\'0deg\'",
+            PlotGeomInfo_0= "1",
+            PlotGeomInfo_1= "Surface",
+            PlotGeomInfo_2= "FacesList",
+            PlotGeomInfo_3= "1",
+        ),
+    )
+    """Default options"""
+    # yapf: enable
+
+    NAME_DELIM = r'_'
+    """Name delimiter"""
+
+    name = 'ansys'
+    """Name"""
+
+    default_setup = Dict(
+        drivenmodal=Dict(name="Setup",
+                         freq_ghz='5.0',
+                         max_delta_s='0.1',
+                         max_passes='10',
+                         min_passes='1',
+                         min_converged='1',
+                         pct_refinement='30',
+                         basis_order='1'),
+        eigenmode=Dict(name="Setup",
+                       min_freq_ghz='1',
+                       n_modes='1',
+                       max_delta_f='0.5',
+                       max_passes='10',
+                       min_passes='1',
+                       min_converged='1',
+                       pct_refinement='30',
+                       basis_order='-1'),
+        q3d=Dict(name='Setup',
+                 freq_ghz='5.0',
+                 save_fields='False',
+                 enabled='True',
+                 max_passes='15',
+                 min_passes='2',
+                 min_converged_passes='2',
+                 percent_error='0.5',
+                 percent_refinement='30',
+                 auto_increase_solution_order='True',
+                 solution_order='High',
+                 solver_type='Iterative'),
+        port_inductor_gap=
+        '10um'  # spacing between port and inductor if junction is drawn both ways
+    )
+    """Default setup."""
+
+    # When additional columns are added to QGeometry, this is the example to populate it.
+    # e.g. element_extensions = dict(
+    #         base=dict(color=str, klayer=int),
+    #         path=dict(thickness=float, material=str, perfectE=bool),
+    #         poly=dict(thickness=float, material=str), )
+    """Element extensions dictionary   element_extensions = dict() from base class"""
+
+    # Add columns to junction table during QAnsysRenderer.load()
+    # element_extensions  is now being populated as part of load().
+    # Determined from element_table_data.
+
+    # Dict structure MUST be same as  element_extensions!!!!!!
+    # This dict will be used to update QDesign during init of renderer.
+    # Keeping this as a cls dict so could be edited before renderer is instantiated.
+    # To update component.options junction table.
+
+    element_table_data = dict(path=dict(wire_bonds=False),
+                              junction=dict(
+                                  inductance=default_options['Lj'],
+                                  capacitance=default_options['Cj'],
+                                  resistance=default_options['_Rj'],
+                                  mesh_kw_jj=parse_units(
+                                      default_options['max_mesh_length_jj'])))
+    """Element table data."""
+
+    def __init__(self, design: 'QDesign', initiate=True, options: Dict = None):
+        """Create a QRenderer for Ansys.
+
+        Args:
+            design (QDesign): Use QGeometry within QDesign to obtain elements for Ansys.
+            initiate (bool, optional): True to initiate the renderer. Defaults to True.
+            options (Dict, optional):  Used to override all options. Defaults to None.
+        """
+        # Variables to connect to Ansys
+        self._rapp = None
+        self._rdesktop = None
+
+        # Initialize renderer
+        super().__init__(design=design, initiate=initiate, options=options)
+
+        # Default behavior is to render all components unless a strict subset was chosen
+        self.render_everything = True
+
+        self._pinfo = None
+
+    @property
+    def initialized(self):
+        """Returns True if initialized, False otherwise."""
+        if self._pinfo:
+            if self._pinfo.project:
+                try:
+                    # this is because after previous pyEPR close(),
+                    # the pinfo.project becomes None, but the type is not None (method)
+                    # TODO: fix where appropriate, then remove this patch.
+                    self._pinfo.project.name
+                except AttributeError:
+                    return False
+                return True
+        return False
+
+    @property
+    def rapp(self):
+        return self._rapp
+
+    @rapp.setter
+    def rapp(self, app_com):
+        if self._rapp:
+            self._rapp.release()
+        self._rapp = app_com
+
+    @property
+    def rdesktop(self):
+        return self._rdesktop
+
+    @rdesktop.setter
+    def rdesktop(self, desktop_com):
+        if self._rdesktop:
+            self._rdesktop.release()
+        self._rdesktop = desktop_com
+
+    def _initiate_renderer(self):
+        """
+        Open a session of the default Ansys EDT.
+        Establishes the connection to the App and Desktop only.
+        """
+        # test if ansys is open
+        # import psutil
+        # booted = False
+        # for proc in psutil.process_iter():
+        #     if 'ansysedt' in proc.name():
+        #         booted = True
+
+        # if not booted:
+        #    self._open_ansys(*args, **kwargs)
+        # need to make it so that it waits for the Ansys boot to end
+        # after opening, should establish a connection (able to create a new project)
+
+        self.rapp = HfssApp()
+        self.rdesktop = self.rapp.get_app_desktop()
+        if self.rdesktop.project_count() == 0:
+            self.rdesktop.new_project()
+        self.connect_ansys()  # TODO: can this be done differently?
+
+        # return True to indicate successful completion
+        return True
+
+    def _close_renderer(self):
+        """Not used by the gds renderer at this time. only returns True.
+
+        Returns:
+            bool: True
+        """
+        # wipe local variables
+        self.epr_distributed_analysis = None
+        self.epr_quantum_analysis = None
+
+        # close COM connections to ansys
+        if self.rdesktop is not None:
+            self.rdesktop.release()
+        if self.rapp is not None:
+            self.rapp.release()
+        if self.pinfo:
+            self.disconnect_ansys()
+
+        return True
+
+    def close(self):
+        """Alias of _close_renderer()
+
+        Returns:
+            bool: True
+        """
+        return self._close_renderer()
+
+    def open_ansys(self,
+                   path: str = None,
+                   executable: str = 'reg_ansysedt.exe',
+                   path_var: str = 'ANSYSEM_ROOT202'):
+        """Alternative method to open an Ansys session that allows to specify
+		which version to use. Default is version 2020 R2, but can be overridden.
+
+        Args:
+            path (str): Path to the Ansys executable. Defaults to None
+            executable (str): Name of the ansys executable. Defaults to 'reg_ansysedt.exe'
+            path_var (str): Name of the OS environment variable that contains the path to the
+                            Ansys executable. Only used when path=None.
+                            Defaults to 'ANSYSEM_ROOT202' (Ansys ver. 2020 R2)
+        """
+        if not system() == 'Windows':
+            self.logger.warning(
+                'You are using %s, but this is a renderer to Ansys, which only runs on Windows. '
+                'Expect any sort of Errors if you try to work with this renderer beyond this point.'
+                % system())
+
+        import subprocess
+        if path is None:
+            try:
+                path = os.environ[path_var]
+            except KeyError:
+                self.logger.error(
+                    'environment variable %s not found. Is Ansys 2020 R2 installed on this '
+                    'machine? If yes, then create said environment variable. If you have a '
+                    'different version of Ansys, then pass to open_ansys() the path to its '
+                    'binary, or the env var that stores it.' % path_var)
+                raise
+        else:
+            path = os.path.abspath(path)
+        cmdlist = [os.path.sep.join([path, executable]), '-shortcut']
+        subprocess.call(cmdlist, cwd=path)
+
+    def connect_ansys(self,
+                      project_path: str = None,
+                      project_name: str = None,
+                      design_name: str = None):
+        """If none of the optional parameters are provided: connects to the
+        Ansys COM, then checks for, and grab if present, an active project,
+        design, and design setup.
+
+        If the optional parameters are provided: if present, opens the project file and design in
+        Ansys.
+
+        Args:
+            project_path (str, optional): Path without file name
+            project_name (str, optional): File name (with or without extension)
+            design_name (str, optional): Name of the default design to open from the project file
+        """
+        if not system() == 'Windows':
+            self.logger.warning(
+                'You are using %s, but this is a renderer to Ansys, which only runs on Windows. '
+                'Expect any sort of Errors if you try to work with this renderer beyond this point.'
+                % system())
+
+        # pyEPR does not like extensions
+        if project_name:
+            project_name = project_name.replace(".aedt", "")
+        # open connection through pyEPR
+        import pythoncom
+        try:
+            self._pinfo = epr.ProjectInfo(
+                do_connect=True,
+                project_path=self._options['project_path']
+                if not project_path else project_path,
+                project_name=self._options['project_name']
+                if not project_name else project_name,
+                design_name=self._options['design_name']
+                if not design_name else design_name)
+        except pythoncom.com_error as error:  # pylint: disable=no-member
+            print("com_error: ", error)
+            hr, msg, exc, arg = error.args
+            if msg == "Invalid class string":  # and hr == -2147221005 and exc is None and arg is None
+                self.logger.error(
+                    "pyEPR cannot find the Ansys COM. Ansys installation might not have registered it. "
+                    "To verify if this is the problem, execute the following: "
+                    "`print(win32com.client.Dispatch('AnsoftHfss.HfssScriptInterface'))` "
+                    "If the print-out is not `<COMObject ...>` then Ansys COM is not registered, "
+                    "and you will need to look into correcting your Ansys installation."
+                )
+            raise error
+
+    def disconnect_ansys(self):
+        """Disconnect Ansys."""
+        if self.pinfo:
+            self.pinfo.disconnect()
+        else:
+            self.logger.warning(
+                'This renderer appears to be already disconnected from Ansys')
+
+    def new_ansys_project(self):
+        """Creates a new empty project in Ansys."""
+        here = HfssApp()
+        here.get_app_desktop().new_project()
+
+    def connect_ansys_design(self, design_name: str = None):
+        """Used to switch between existing designs.
+
+        Args:
+            design_name (str, optional): Name within the active project. Defaults to None.
+        """
+
+        if self.pinfo:
+            if self.pinfo.project:
+                all_designs_names = self.pinfo.project.get_design_names()
+                if design_name not in all_designs_names:
+                    self.logger.warning(
+                        f'The design_name={design_name} is not in project.  Connection did not happen.'
+                    )
+                    return
+
+                try:
+                    self.pinfo.connect_design(design_name)
+                    self.pinfo.connect_setup()
+                except AttributeError:
+                    self.logger.error(
+                        'Please install a more recent version of pyEPR (>=0.8.4.3)'
+                    )
+            else:
+                self.logger.warning(
+                    'Either you do not have a project loaded in Ansys, or you are not connected '
+                    'to it. Try executing hfss.connect_ansys(), or creating a new Ansys project. '
+                    'Also check the help file and other guide notebooks')
+        else:
+            self.logger.warning(
+                'It does not look like you are connected to Ansys. Please use connect_ansys() '
+                'and make sure self.pinfo is set. There must be a project open in Ansys first.'
+            )
+
+    def get_active_design_name(self):
+        """Returns the name of the Ansys Design Object
+
+        Returns:
+            (str): Name of the active Ansys Design
+        """
+        if self.pinfo:
+            if self.pinfo.project:
+                return self.pinfo.project.get_active_design().name
+
+    @property
+    def pinfo(self) -> epr.ProjectInfo:
+        """Project info for Ansys renderer (class: pyEPR.ProjectInfo)."""
+        return self._pinfo
+
+    @property
+    def modeler(self):
+        """The modeler from pyEPR HfssModeler.
+
+        Returns:
+            pyEPR.ansys.HfssModeler: Reference to  design.HfssModeler in Ansys.
+        """
+        if self.pinfo:
+            if self.pinfo.design:
+                return self.pinfo.design.modeler
+
+    def plot_ansys_fields(self, *args, **kwargs):
+        """
+        (deprecated) use plot_fields()
+        """
+        self.logger.warning(
+            'This method is deprecated. Change your scripts to use plot_fields()'
+        )
+        return self.plot_fields(*args, **kwargs)
+
+    def plot_fields(
+        self,
+        object_name: str,
+        name: str = None,
+        UserSpecifyName: int = None,
+        UserSpecifyFolder: int = None,
+        QuantityName: str = None,
+        PlotFolder: str = None,
+        StreamlinePlot: bool = None,
+        AdjacentSidePlot: bool = None,
+        FullModelPlot: bool = None,
+        IntrinsicVar: str = None,
+        PlotGeomInfo_0: int = None,
+        PlotGeomInfo_1: str = None,
+        PlotGeomInfo_2: str = None,
+        PlotGeomInfo_3: int = None,
+    ):
+        """Plot fields in Ansys. The options are populated by the component's
+        options.
+
+        Args:
+            object_name (str): Used to plot on faces of.
+            name (str, optional): "NAME:<PlotName>" Defaults to None.
+            UserSpecifyName (int, optional): 0 if default name for plot is used, 1 otherwise.
+                Defaults to None.
+            UserSpecifyFolder (int, optional): 0 if default folder for plot is used, 1 otherwise.
+                Defaults to None.
+            QuantityName (str, optional): Type of plot to create. Possible values are
+                Mesh plots - "Mesh";
+                Field plots - "Mag_E", "Mag_H", "Mag_Jvol", "Mag_Jsurf","ComplexMag_E",
+                "ComplexMag_H", "ComplexMag_Jvol", "ComplexMag_Jsurf", "Vector_E", "Vector_H",
+                "Vector_Jvol", "Vector_Jsurf", "Vector_RealPoynting","Local_SAR", "Average_SAR".
+                Defaults to None.
+            PlotFolder (str, optional): Name of the folder to which the plot should be added.
+                Possible values are: "E Field",  "H Field", "Jvol", "Jsurf", "SARField", and
+                "MeshPlots". Defaults to None.
+            StreamlinePlot (bool, optional): Passed to CreateFieldPlot. Defaults to None.
+            AdjacentSidePlot (bool, optional): Passed to CreateFieldPlot. Defaults to None.
+            FullModelPlot (bool, optional): Passed to CreateFieldPlot. Defaults to None.
+            IntrinsicVar (str, optional): Formatted string that specifies the frequency and phase
+                at which to make the plot.  For example: "Freq='1GHz' Phase='30deg'".
+                Defaults to None.
+            PlotGeomInfo_0 (int, optional): 0th entry in list for "PlotGeomInfo:=",
+                <PlotGeomArray>. Defaults to None.
+            PlotGeomInfo_1 (str, optional): 1st entry in list for "PlotGeomInfo:=",
+                <PlotGeomArray>. Defaults to None.
+            PlotGeomInfo_2 (str, optional): 2nd entry in list for "PlotGeomInfo:=",
+                <PlotGeomArray>. Defaults to None.
+            PlotGeomInfo_3 (int, optional): 3rd entry in list for "PlotGeomInfo:=",
+                <PlotGeomArray>. Defaults to None.
+
+        Returns:
+            NoneType: Return information from oFieldsReport.CreateFieldPlot().
+            The method CreateFieldPlot() always returns None.
+        """
+        self.modeler._modeler.ShowWindow()
+        if not self.pinfo:
+            self.logger.warning('pinfo is None.')
+            return
+
+        if self.pinfo.design:
+            if not self.pinfo.design._fields_calc:
+                self.logger.warning('The _fields_calc in design is None.')
+                return
+            if not self.pinfo.design._modeler:
+                self.logger.warning('The _modeler in design is None.')
+                return
+        else:
+            self.logger.warning('The design in pinfo is None.')
+            return
+
+        if not self.pinfo.setup:
+            self.logger.warning('The setup in pinfo is None.')
+            return
+
+        #TODO: This is just a prototype - should add features and flexibility.
+        oFieldsReport = self.pinfo.design._fields_calc  #design.GetModule("FieldsReporter")
+        oModeler = self.pinfo.design._modeler  #design.SetActiveEditor("3D Modeler")
+        setup = self.pinfo.setup
+
+        # Object ID - use to plot on faces of
+        object_id = oModeler.GetObjectIDByName(object_name)
+        # Can also use hfss.pinfo.design._modeler.GetFaceIDs("main")
+
+        paf = self.options['plot_ansys_fields_options']
+
+        if not name:
+            name = self.parse_value(paf['name'])
+
+        # Name of the solution setup and solution formatted as:"<SolveSetupName> : <WhichSolution>",
+        # where <WhichSolution> can be "Adaptive_<n>", "LastAdaptive", or "PortOnly".
+        # HFSS requires a space on either side of the ‘:’ character.
+        # If it is missing, the plot will not be created.
+        SolutionName = f"{setup.name} : LastAdaptive"
+        if not UserSpecifyName:
+            UserSpecifyName = int(self.parse_value(paf['UserSpecifyName']))
+        if not UserSpecifyFolder:
+            UserSpecifyFolder = int(self.parse_value(paf['UserSpecifyFolder']))
+        if not QuantityName:
+            QuantityName = self.parse_value(paf['QuantityName'])
+        if not PlotFolder:
+            PlotFolder = self.parse_value(paf['PlotFolder'])
+        if not StreamlinePlot:
+            StreamlinePlot = is_true(self.parse_value(paf['StreamlinePlot']))
+        if not AdjacentSidePlot:
+            AdjacentSidePlot = is_true(self.parse_value(
+                paf['AdjacentSidePlot']))
+        if not FullModelPlot:
+            FullModelPlot = is_true(self.parse_value(paf['FullModelPlot']))
+        if not IntrinsicVar:
+            IntrinsicVar = self.parse_value(paf['IntrinsicVar'])
+        if not PlotGeomInfo_0:
+            PlotGeomInfo_0 = int(self.parse_value(paf['PlotGeomInfo_0']))
+        if not PlotGeomInfo_1:
+            PlotGeomInfo_1 = self.parse_value(paf['PlotGeomInfo_1'])
+        if not PlotGeomInfo_2:
+            PlotGeomInfo_2 = self.parse_value(paf['PlotGeomInfo_2'])
+        if not PlotGeomInfo_3:
+            PlotGeomInfo_3 = int(self.parse_value(paf['PlotGeomInfo_3']))
+
+        # used to pass to CreateFieldPlot
+        # Copied from  pdf at http://www.ece.uprm.edu/~rafaelr/inel6068/HFSS/scripting.pdf
+        #<PlotGeomArray>Array(<NumGeomTypes>, <GeomTypeData>,<GeomTypeData>, ...)
+        # For example:
+        # Array(4, "Volume", "ObjList", 1, "Box1","Surface", "FacesList", 1, "12", "Line", 1,"Polyline1",
+        #       "Point", 2, "Point1", "Point2"
+        PlotGeomInfo = [
+            PlotGeomInfo_0, PlotGeomInfo_1, PlotGeomInfo_2, PlotGeomInfo_3,
+            str(object_id)
+        ]
+
+        # yapf: disable
+        args_list = [
+                name                 ,
+                "SolutionName:="     , SolutionName,  # name of the setup
+                "UserSpecifyName:="  , UserSpecifyName ,
+                "UserSpecifyFolder:=", UserSpecifyFolder,
+                "QuantityName:="     , QuantityName,
+                "PlotFolder:="       , PlotFolder,
+                "StreamlinePlot:="   , StreamlinePlot,
+                "AdjacentSidePlot:=" , AdjacentSidePlot,
+                "FullModelPlot:="    , FullModelPlot,
+                "IntrinsicVar:="     , IntrinsicVar,
+                "PlotGeomInfo:="     , PlotGeomInfo,
+            ]
+        #yapf: enable
+        return oFieldsReport.CreateFieldPlot(args_list, "Field")
+
+    def plot_ansys_delete(self, names: list):
+        """
+        (deprecated) Use clear_fields()
+        """
+        self.logger.warning(
+            'This method is deprecated. Change your scripts to use clear_fields()'
+        )
+        self.clear_fields(names)
+
+    def clear_fields(self, names: list):
+        """
+        Delete field plots from modeler window in Ansys.
+        Does not throw an error if names are missing.
+
+        Can give multiple names, for example:
+        hfss.plot_ansys_delete(['Mag_E1', 'Mag_E1_2'])
+
+        Args:
+            names (list): Names of plots to delete from modeler window.
+        """
+        if not names:
+            names = list(self.pinfo.design._fields_calc.GetFieldPlotNames())
+        return self.pinfo.design._fields_calc.DeleteFieldPlot(names)
+
+    def add_message(self, msg: str, severity: int = 0):
+        """Add message to Message Manager box in Ansys.
+
+        Args:
+            msg (str): Message to add.
+            severity (int): 0 = Informational, 1 = Warning, 2 = Error, 3 = Fatal.
+        """
+        self.pinfo.design.add_message(msg, severity)
+
+    def save_screenshot(self, path: str = None, show: bool = True):
+        """Save the screenshot.
+
+        Args:
+            path (str, optional): Path to save location.  Defaults to None.
+            show (bool, optional): Whether or not to display the screenshot.  Defaults to True.
+
+        Returns:
+            pathlib.WindowsPath: path to png formatted screenshot.
+        """
+        self.modeler._modeler.ShowWindow()
+        try:
+            return self.pinfo.design.save_screenshot(path, show)
+        except AttributeError:
+            self.logger.error(
+                'Please install a more recent version of pyEPR (>=0.8.4.3)')
+
+    def execute_design(self,
+                       design_name: str,
+                       solution_type: str,
+                       vars_to_initialize: Dict,
+                       force_redraw: bool = False,
+                       **design_selection) -> str:
+        """It wraps the render_design() method to
+        1. skip rendering if the "selection" of components is left empty (re-uses selected design)
+        2. force design clearing and redraw if force_Redraw is set
+
+        Args:
+            design_name (str): Name to assign to the renderer design
+            solution_type (str): eigenmode, capacitive or drivenmodal
+            vars_to_initialize (Dict): Variables to initialize, i.e. Ljx, Cjx
+            force_redraw (bool, optional): Force re-render the design. Defaults to False.
+
+        Returns:
+            str: final design name (a suffix might have been added to the provided name,
+                in case of conflicts)
+        """
+        # If a selection of components is not specified, use the active renderer-design
+        if 'selection' in design_selection:
+            if design_selection['selection'] is None:
+                try:
+                    return self.pinfo.design.name
+                except AttributeError:
+                    # if no design exists, then we will proceed and render the full design instead
+                    pass
+
+        # either create a new one, or clear the active one, depending on force_redraw.
+        if force_redraw and (design_name
+                             in self.pinfo.project.get_design_names()):
+            self.activate_ansys_design(design_name, solution_type)
+            self.clean_active_design()
+        else:
+            self.new_ansys_design(design_name, solution_type)
+
+        # Save the solution type in order to choose between Lumped Port and RLC port for simulations
+        self.solution_type = solution_type
+
+        self.set_variables(vars_to_initialize)
+        self.render_design(**design_selection)
+        return self.pinfo.design.name
+
+    def new_ansys_design(self,
+                         design_name: str,
+                         solution_type: str,
+                         connect: bool = True):
+        """Add an Ansys design with the given name to the Ansys project.
+        Valid solutions_type values are: 'capacitive' (q3d), 'eignemode' and 'drivenmodal' (hfss)
+
+        Args:
+            design_name (str): name of the Design to be created in Ansys
+            solution_type (str): defines type of Design and solution to be created in Ansys
+            connect (bool, optional): Should we connect qiskit-metal to this Ansy design? Defaults to True.
+
+        Returns(pyEPR.ansys.HfssDesign): The pointer to the design within Ansys.
+
+        """
+        if self.pinfo:
+            try:
+                if solution_type == 'capacitive':
+                    adesign = self.pinfo.project.new_q3d_design(design_name)
+                elif solution_type == 'eigenmode':
+                    adesign = self.pinfo.project.new_em_design(design_name)
+                elif solution_type == 'drivenmodal':
+                    adesign = self.pinfo.project.new_dm_design(design_name)
+                else:
+                    self.logger.error(
+                        f'The solution_type = {solution_type} is not supported by this renderer'
+                    )
+            except AttributeError:
+                if self.pinfo.project is None:
+                    self.logger.error('Project not found')
+                else:
+                    self.logger.error(
+                        'Please install a more recent version of pyEPR (>=0.8.4.4)'
+                    )
+                raise
+            if connect:
+                self.connect_ansys_design(adesign.name)
+            return adesign
+        else:
+            self.logger.info("You have to first connect to Ansys and to a project " \
+                            "before creating a new design. You can use renderer.connect_ansys()")
+
+    def activate_ansys_design(self,
+                              design_name: str,
+                              solution_type: str = None):
+        """Select a design with the given name from the open project.
+        If the design exists, that will be added WITHOUT altering the suffix of the design name.
+
+        Args:
+            name (str): Name of the new Ansys design
+        """
+
+        if self.pinfo:
+            if self.pinfo.project:
+                try:
+                    names_in_design = self.pinfo.project.get_design_names()
+                except AttributeError:
+                    self.logger.error(
+                        'Please install a more recent version of pyEPR (>=0.8.4.5)'
+                    )
+
+                if design_name in names_in_design:
+                    self.pinfo.connect_design(design_name)
+                    oDesktop = self.pinfo.design.parent.parent._desktop  # self.pinfo.design does not work
+                    oProject = oDesktop.SetActiveProject(
+                        self.pinfo.project_name)
+                    oDesign = oProject.SetActiveDesign(design_name)
+                    current_solution_type = self.pinfo.design.solution_type.lower(
+                    )
+                    if current_solution_type == 'q3d':
+                        current_solution_type = 'capacitive'
+                    if current_solution_type != solution_type and solution_type is not None:
+                        self.logger.warning(
+                            f'The design_name={design_name} already exists, but it has solution_type=='
+                            f'{current_solution_type}, which is different from the requested=={solution_type}. '
+                            f'If you want a design with solution type=={solution_type}, please change the name '
+                            'requested for your design to one that does not exist. Alternatively, manually modify '
+                            f'the solution_type for design {design_name} from the Ansys GUI.'
+                        )
+                else:
+                    self.logger.warning(
+                        f'The design_name={design_name} was not in active project.  '
+                        f'Designs in active project are: \n{names_in_design}.  '
+                        'A new design will be added to the project.  ')
+                    if solution_type is not None:
+                        adesign = self.new_ansys_design(
+                            design_name=design_name,
+                            solution_type=solution_type,
+                            connect=True)
+                    else:
+                        self.logger.error(
+                            'Please specify the solution_type, to determine what design to create'
+                        )
+            else:
+                self.logger.warning(
+                    "Project not found, have you opened a project?")
+        else:
+            self.logger.warning(
+                "Have you run start()?  Cannot find a reference to Ansys in QRenderer."
+            )
+
+    def new_ansys_setup(self, name: str, **other_setup):
+        """Determines the appropriate setup to be created based on the pinfo.design.solution_type.
+        make sure to set this variable before executing this method
+
+        Args:
+            name (str): name to give to the new setup
+
+        Returns:
+            pyEPR.ansys.HfssEMSetup: Pointer to the ansys setup object
+        """
+        #TODO: only use activate_ansys_setup?
+        if self.pinfo:
+            if self.pinfo.design:
+                if 'reuse_setup' in other_setup:
+                    if other_setup['reuse_setup']:
+                        # delete_setup will check if setup exists, before deleting.
+                        self.pinfo.design.delete_setup(name)
+
+                if self.pinfo.design.solution_type == 'Eigenmode':
+                    setup = self.add_eigenmode_setup(name, **other_setup)
+                elif self.pinfo.design.solution_type == 'DrivenModal':
+                    setup = self.add_drivenmodal_setup(name, **other_setup)
+                elif self.pinfo.design.solution_type == 'Q3D':
+                    setup = self.add_q3d_setup(name, **other_setup)
+        return setup
+
+    def initialize_cap_extract(self, **kwargs):
+        """Any task that needs to occur before running a simulation, such as creating a setup
+
+        Returns:
+            str: Name of the setup that has been updated
+        """
+
+        setup = self.new_ansys_setup(**kwargs)  #TODO: activate_ansys_setup?
+        return setup.name
+
+    def initialize_eigenmode(self, vars: Dict = {}, **kwargs):
+        """Any task that needs to occur before running a simulation, such as creating a setup
+
+        Args:
+            vars (Dict, optional): list of parametric variables to set in the renderer.
+                Defaults to {}.
+
+        Returns:
+            str: Name of the setup that has been updated
+        """
+        self.set_variables(vars)
+        setup = self.new_ansys_setup(**kwargs)  #TODO: activate_ansys_setup?
+        return setup.name
+
+    def initialize_drivenmodal(self,
+                               sweep_setup: Dict,
+                               vars: Dict = {},
+                               **kwargs):
+        """Any task that needs to occur before running a simulation, such as creating a setup
+
+        Args:
+            sweep_setup (Dict): list of parametric variables to set the frequency sweep.
+            vars (Dict, optional): list of parametric variables to set in the renderer.
+                Defaults to {}.
+
+        Returns:
+            str: Name of the setup that has been updated
+        """
+        self.set_variables(vars)
+        setup = self.new_ansys_setup(**kwargs)  #TODO: activate_ansys_setup?
+        sweep = self.add_sweep(setup.name, **sweep_setup)
+        return setup.name, sweep.name
+
+    def activate_ansys_setup(self, setup_name: str):
+        """For active design, either get existing setup, make new setup with name,
+        or make new setup with default name.
+
+        Args:
+            setup_name (str, optional): If name exists for setup, then have pinfo reference it.
+              If name for setup does not exist, create a new setup with the name.
+              If name is None, create a new setup with default name.
+        """
+        if self.pinfo:
+            if self.pinfo.project:
+                if self.pinfo.design:
+                    # look for setup name, if not there, then add a new one
+                    if setup_name:
+                        all_setup_names = self.pinfo.design.get_setup_names()
+                        self.pinfo.setup_name = setup_name
+                        if setup_name in all_setup_names:
+                            # When name is given and in design. So have pinfo reference existing setup.
+                            self.pinfo.setup = self.pinfo.get_setup(setup_name)
+                        else:
+                            # When name is given, but not in design. So make a new setup with given name.
+                            self.logger.warning(
+                                f'The setup_name={setup_name} was not in active design.  '
+                                f'Setups in active design are: \n{all_setup_names}.  '
+                                'A new setup will default values will be added to the design.  '
+                            )
+                            self.pinfo.setup = self.new_ansys_setup(
+                                name=setup_name)
+                    else:
+                        self.logger.warning(f'Please specify a setup_name.')
+                else:
+                    self.logger.warning(
+                        "Design not found in selected project, have you opened a design?"
+                    )
+            else:
+                self.logger.warning(
+                    "Project not found, have you opened a project?")
+        else:
+            self.logger.warning(
+                "Have you run connect_ansys()?  Cannot find a reference to Ansys in QRenderer."
+            )
+
+    def render_design(self,
+                      selection: Union[list, None] = None,
+                      open_pins: Union[list, None] = None,
+                      box_plus_buffer: bool = True):
+        """Initiate rendering of components in design contained in selection,
+        assuming they're valid. Components are rendered before the chips they
+        reside on, and subtraction of negative shapes is performed at the very
+        end.
+
+        First obtain a list of IDs of components to render and a corresponding case, denoted by self.qcomp_ids
+        and self.case, respectively. If self.case == 1, all components in QDesign are to be rendered.
+        If self.case == 0, a strict subset of components in QDesign are to be rendered. Otherwise, if
+        self.case == 2, one or more component names in selection cannot be found in QDesign.
+
+        Chip_subtract_dict consists of component names (keys) and a set of all elements within each component that
+        will eventually be subtracted from the ground plane. Add objects that are perfect conductors and/or have
+        meshing to self.assign_perfE and self.assign_mesh, respectively; both are initialized as empty lists. Similarly,
+        if the object is a port in an eigenmode simulation, add it to self.assign_port_mesh, which is initialized
+        as an empty list. Note that these objects are "refreshed" each time render_design is called (as opposed to
+        in the init function) to clear QAnsysRenderer of any leftover items from the last call to render_design.
+
+        Among the components selected for export, there may or may not be unused (unconnected) pins.
+        The second parameter, open_pins, contains tuples of the form (component_name, pin_name) that
+        specify exactly which pins should be open rather than shorted during the simulation. Both the
+        component and pin name must be specified because the latter could be shared by multiple
+        components. All pins in this list are rendered with an additional endcap in the form of a
+        rectangular cutout, to be subtracted from its respective plane.
+
+        The final parameter, box_plus_buffer, determines how the chip is drawn. When set to True, it takes the
+        minimum rectangular bounding box of all rendered components and adds a buffer of x_buffer_width_mm and
+        y_buffer_width_mm horizontally and vertically, respectively, to the chip size. The center of the chip
+        lies at the midpoint x/y coordinates of the minimum rectangular bounding box and may change depending
+        on which components are rendered and how they're positioned. If box_plus_buffer is False, however, the
+        chip position and dimensions are taken from the chip info dictionary found in QDesign, irrespective
+        of what's being rendered. While this latter option is faster because it doesn't require calculating a
+        bounding box, it runs the risk of rendered components being too close to the edge of the chip or even
+        falling outside its boundaries.
+
+        Args:
+            selection (Union[list, None], optional): List of components to render. Defaults to None.
+            open_pins (Union[list, None], optional): List of tuples of pins that are open. Defaults to None.
+            box_plus_buffer (bool): Either calculate a bounding box based on the location of rendered geometries
+                                     or use chip size from design class.
+        """
+        self.qcomp_ids, self.case = self.get_unique_component_ids(selection)
+
+        if self.case == 2:
+            self.logger.warning(
+                'Unable to proceed with rendering. Please check selection.')
+            return
+
+        self.chip_subtract_dict = defaultdict(set)
+        self.assign_perfE = []
+        self.assign_mesh = []
+        self.assign_port_mesh = []
+
+        self.render_tables()
+        self.add_endcaps(open_pins)
+
+        self.render_chips(box_plus_buffer=box_plus_buffer)
+        self.subtract_from_ground()
+        self.add_mesh()
+
+    def render_chip(self):
+        pass
+
+    def render_component(self):
+        pass
+
+    def render_tables(self, skip_junction: bool = False):
+        """
+        Render components in design grouped by table type (path, poly, or junction).
+        """
+        for table_type in self.design.qgeometry.get_element_types():
+            if table_type != 'junction' or not skip_junction:
+                self.render_components(table_type)
+
+    def render_components(self, table_type: str):
+        """
+        Render components by breaking them down into individual elements.
+
+        Args:
+            table_type (str): Table type (poly, path, or junction).
+        """
+        table = self.design.qgeometry.tables[table_type]
+
+        if self.case == 0:  # Render a subset of components using mask
+            mask = table['component'].isin(self.qcomp_ids)
+            table = table[mask]
+
+        for _, qgeom in table.iterrows():
+            self.render_element(qgeom, bool(table_type == 'junction'))
+
+        if table_type == 'path':
+            self.auto_wirebonds(table)
+
+    def render_element(self, qgeom: pd.Series, is_junction: bool):
+        """Render an individual shape whose properties are listed in a row of
+        QGeometry table. Junction elements are handled separately from non-
+        junction elements, as the former consist of two rendered shapes, not
+        just one.
+
+        Args:
+            qgeom (pd.Series): GeoSeries of element properties.
+            is_junction (bool): Whether or not qgeom belongs to junction table.
+        """
+        qc_shapely = qgeom.geometry
+        if is_junction:
+            self.render_element_junction(qgeom)
+        else:
+            if isinstance(qc_shapely, shapely.geometry.Polygon):
+                self.render_element_poly(qgeom)
+            elif isinstance(qc_shapely, shapely.geometry.LineString):
+                self.render_element_path(qgeom)
+
+    def render_element_junction(self, qgeom: pd.Series):
+        """
+        Render a Josephson junction consisting of
+            1. A rectangle of length pad_gap and width inductor_width. Defines lumped element
+               RLC boundary condition.
+            2. A line that is later used to calculate the voltage in post-processing analysis.
+
+        Args:
+            qgeom (pd.Series): GeoSeries of element properties.
+        """
+        ansys_options = dict(transparency=0.0)
+
+        qc_name = 'Lj_' + str(qgeom['component'])
+        qc_elt = get_clean_name(qgeom['name'])
+        qc_shapely = qgeom.geometry
+        qc_chip_z = parse_units(self.design.get_chip_z(qgeom.chip))
+        qc_width = parse_units(qgeom.width)
+
+        name = f'{qc_name}{QAnsysRenderer.NAME_DELIM}{qc_elt}'
+
+        endpoints = parse_units(list(qc_shapely.coords))
+        endpoints_3d = to_vec3D(endpoints, qc_chip_z)
+        x0, y0, z0 = endpoints_3d[0]
+        x1, y1, z0 = endpoints_3d[1]
+        if abs(y1 - y0) > abs(x1 - x0):
+            # Junction runs vertically up/down
+            x_min, x_max = x0 - qc_width / 2, x0 + qc_width / 2
+            y_min, y_max = min(y0, y1), max(y0, y1)
+        else:
+            # Junction runs horizontally left/right
+            x_min, x_max = min(x0, x1), max(x0, x1)
+            y_min, y_max = y0 - qc_width / 2, y0 + qc_width / 2
+
+        # Draw rectangle
+        self.logger.debug(f'Drawing a rectangle: {name}')
+        poly_ansys = self.modeler.draw_rect_corner([x_min, y_min, qc_chip_z],
+                                                   x_max - x_min, y_max - y_min,
+                                                   qc_chip_z, **ansys_options)
+        axis = 'x' if abs(x1 - x0) > abs(y1 - y0) else 'y'
+        self.modeler.rename_obj(poly_ansys, 'JJ_rect_' + name)
+        self.assign_mesh.append('JJ_rect_' + name)
+
+        # Draw line
+        poly_jj = self.modeler.draw_polyline([endpoints_3d[0], endpoints_3d[1]],
+                                             closed=False,
+                                             **dict(color=(128, 0, 128)))
+        poly_jj = poly_jj.rename('JJ_' + name + '_')
+        poly_jj.show_direction = True
+
+    def render_element_poly(self, qgeom: pd.Series):
+        """Render a closed polygon.
+
+        Args:
+            qgeom (pd.Series): GeoSeries of element properties.
+        """
+        ansys_options = dict(transparency=0.0)
+
+        qc_name = self.design._components[qgeom['component']].name
+        qc_elt = get_clean_name(qgeom['name'])
+
+        qc_shapely = qgeom.geometry  # shapely geom
+        qc_chip_z = parse_units(self.design.get_chip_z(qgeom.chip))
+        qc_fillet = round(qgeom.fillet, 7)
+
+        name = f'{qc_elt}{QAnsysRenderer.NAME_DELIM}{qc_name}'
+
+        points = parse_units(list(
+            qc_shapely.exterior.coords))  # list of 2d point tuples
+        points_3d = to_vec3D(points, qc_chip_z)
+
+        if is_rectangle(qc_shapely):  # Draw as rectangle
+            self.logger.debug(f'Drawing a rectangle: {name}')
+            x_min, y_min, x_max, y_max = qc_shapely.bounds
+            poly_ansys = self.modeler.draw_rect_corner(
+                *parse_units([[x_min, y_min, qc_chip_z], x_max - x_min,
+                              y_max - y_min, qc_chip_z]), **ansys_options)
+            self.modeler.rename_obj(poly_ansys, name)
+
+        else:
+            # Draw general closed poly
+            poly_ansys = self.modeler.draw_polyline(points_3d[:-1],
+                                                    closed=True,
+                                                    **ansys_options)
+            # rename: handle bug if the name of the cut already exits and is used to make a cut
+            poly_ansys = poly_ansys.rename(name)
+
+        qc_fillet = round(qgeom.fillet, 7)
+        if qc_fillet > 0:
+            qc_fillet = parse_units(qc_fillet)
+            idxs_to_fillet = good_fillet_idxs(
+                points,
+                qc_fillet,
+                precision=self.design._template_options.PRECISION,
+                isclosed=True)
+            if idxs_to_fillet:
+                self.modeler._fillet(qc_fillet, idxs_to_fillet, poly_ansys)
+
+        # Subtract interior shapes, if any
+        if len(qc_shapely.interiors) > 0:
+            for i, x in enumerate(qc_shapely.interiors):
+                interior_points_3d = to_vec3D(parse_units(list(x.coords)),
+                                              qc_chip_z)
+                inner_shape = self.modeler.draw_polyline(
+                    interior_points_3d[:-1], closed=True)
+                self.modeler.subtract(name, [inner_shape])
+
+        # Input chip info into self.chip_subtract_dict
+        if qgeom.chip not in self.chip_subtract_dict:
+            self.chip_subtract_dict[qgeom.chip] = set()
+
+        if qgeom['subtract']:
+            self.chip_subtract_dict[qgeom.chip].add(name)
+
+        # Potentially add to list of elements to metallize
+        elif not qgeom['helper']:
+            self.assign_perfE.append(name)
+
+    def render_element_path(self, qgeom: pd.Series):
+        """Render a path-type element.
+
+        Args:
+            qgeom (pd.Series): GeoSeries of element properties.
+        """
+        ansys_options = dict(transparency=0.0)
+
+        qc_name = self.design._components[qgeom['component']].name
+        qc_elt = get_clean_name(qgeom['name'])
+
+        qc_shapely = qgeom.geometry  # shapely geom
+        qc_chip_z = parse_units(self.design.get_chip_z(qgeom.chip))
+
+        name = f'{qc_elt}{QAnsysRenderer.NAME_DELIM}{qc_name}'
+
+        qc_width = parse_units(qgeom.width)
+
+        points = parse_units(list(qc_shapely.coords))
+        points_3d = to_vec3D(points, qc_chip_z)
+
+        try:
+            poly_ansys = self.modeler.draw_polyline(points_3d,
+                                                    closed=False,
+                                                    **ansys_options)
+        except AttributeError:
+            if self.modeler is None:
+                self.logger.error(
+                    'No modeler was found. Are you connected to an active Ansys Design?'
+                )
+            raise
+
+        poly_ansys = poly_ansys.rename(name)
+
+        qc_fillet = round(qgeom.fillet, 7)
+        if qc_fillet > 0:
+            qc_fillet = parse_units(qc_fillet)
+            idxs_to_fillet = good_fillet_idxs(
+                points,
+                qc_fillet,
+                precision=self.design._template_options.PRECISION,
+                isclosed=False)
+            if idxs_to_fillet:
+                self.modeler._fillet(qc_fillet, idxs_to_fillet, poly_ansys)
+
+        if qc_width:
+            x0, y0 = points[0]
+            x1, y1 = points[1]
+            vlen = math.sqrt((x1 - x0)**2 + (y1 - y0)**2)
+            p0 = np.array([
+                x0, y0, 0
+            ]) + qc_width / (2 * vlen) * np.array([y0 - y1, x1 - x0, 0])
+            p1 = np.array([
+                x0, y0, 0
+            ]) + qc_width / (2 * vlen) * np.array([y1 - y0, x0 - x1, 0])
+            shortline = self.modeler.draw_polyline([p0, p1],
+                                                   closed=False)  # sweepline
+            import pythoncom
+            try:
+                self.modeler._sweep_along_path(shortline, poly_ansys)
+            except pythoncom.com_error as error:
+                print("com_error: ", error)
+                hr, msg, exc, arg = error.args
+                if msg == "Exception occurred." and hr == -2147352567:
+                    self.logger.error(
+                        "We cannot find a writable design. \n  Either you are trying to use a Ansys "
+                        "design that is not empty, in which case please clear it manually or with the "
+                        "renderer method clean_active_design(). \n  Or you accidentally deleted "
+                        "the design in Ansys, in which case please create a new one."
+                    )
+                raise error
+
+        if qgeom.chip not in self.chip_subtract_dict:
+            self.chip_subtract_dict[qgeom.chip] = set()
+
+        if qgeom['subtract']:
+            self.chip_subtract_dict[qgeom.chip].add(name)
+
+        elif qgeom['width'] and (not qgeom['helper']):
+            self.assign_perfE.append(name)
+
+    def add_endcaps(self, open_pins: Union[list, None] = None):
+        """Create endcaps (rectangular cutouts) for all pins in the list
+        open_pins and add them to chip_subtract_dict. Each element in open_pins
+        takes on the form (component_name, pin_name) and corresponds to a
+        single pin.
+
+        Args:
+            open_pins (Union[list, None], optional): List of tuples of pins that are open. Defaults to None.
+        """
+        open_pins = open_pins if open_pins else []
+
+        for comp, pin in open_pins:
+            pin_dict = self.design.components[comp].pins[pin]
+            width, gap = parse_units([pin_dict['width'], pin_dict['gap']])
+            mid, normal = parse_units(pin_dict['middle']), pin_dict['normal']
+            rect_mid = np.append(mid + normal * gap / 2, [0])
+            # Assumption: pins only point in x or y directions
+            # If this assumption is not satisfied, draw_rect_center no longer works -> must use draw_polyline
+            endcap_name = f'endcap_{comp}_{pin}'
+            if abs(normal[0]) > abs(normal[1]):
+                self.modeler.draw_rect_center(rect_mid,
+                                              x_size=gap,
+                                              y_size=width + 2 * gap,
+                                              name=endcap_name)
+            else:
+                self.modeler.draw_rect_center(rect_mid,
+                                              x_size=width + 2 * gap,
+                                              y_size=gap,
+                                              name=endcap_name)
+            self.chip_subtract_dict[pin_dict['chip']].add(endcap_name)
+
+    def get_chip_names(self) -> List[str]:
+        """
+        Obtain a list of chips on which the selection of components, if valid, resides.
+
+        Returns:
+            List[str]: Chips to render.
+        """
+        if self.case == 2:  # One or more components not in QDesign.
+            self.logger.warning('One or more components not found.')
+            return []
+        chip_names = set()
+        if self.case == 1:  # All components rendered.
+            comps = self.design.components
+            for qcomp in comps:
+                if 'chip' not in comps[qcomp].options:
+                    self.chip_designation_error()
+                    return []
+                elif comps[qcomp].options.chip != 'main':
+                    self.chip_not_main()
+                    return []
+                chip_names.add(comps[qcomp].options.chip)
+        else:  # Strict subset rendered.
+            icomps = self.design._components
+            for qcomp_id in self.qcomp_ids:
+                if 'chip' not in icomps[qcomp_id].options:
+                    self.chip_designation_error()
+                    return []
+                elif icomps[qcomp_id].options.chip != 'main':
+                    self.chip_not_main()
+                    return []
+                chip_names.add(icomps[qcomp_id].options.chip)
+        return list(chip_names)
+
+    def chip_designation_error(self):
+        """
+        Warning message that appears when the Ansys renderer fails to locate a component's chip designation.
+        Provides instructions for a temporary workaround until the layer stack is finalized.
+        """
+        self.logger.warning(
+            "This component currently lacks a chip designation. Please add chip='main' to the component's default_options dictionary, restart the kernel, and try again."
+        )
+
+    def chip_not_main(self):
+        """
+        Warning message that appears when a component's chip designation is not 'main'.
+        As of 05/10/21, all chip designations should be 'main' until the layer stack is finalized.
+        Provides instructions for a temporary workaround until the layer stack is finalized.
+        """
+        self.logger.warning(
+            "The chip designation for this component is not 'main'. Please set chip='main' in its default_options dictionary, restart the kernel, and try again."
+        )
+
+    def get_min_bounding_box(self) -> Tuple[float]:
+        """
+        Determine the max/min x/y coordinates of the smallest rectangular, axis-aligned
+        bounding box that will enclose a selection of components to render, given by
+        self.qcomp_ids. This method is only used when box_plus_buffer is True.
+
+        Returns:
+            Tuple[float]: min x, min y, max x, and max y coordinates of bounding box.
+        """
+        min_x_main = min_y_main = float('inf')
+        max_x_main = max_y_main = float('-inf')
+        if self.case == 2:  # One or more components not in QDesign.
+            self.logger.warning('One or more components not found.')
+        elif self.case == 1:  # All components rendered.
+            for qcomp in self.design.components:
+                min_x, min_y, max_x, max_y = self.design.components[
+                    qcomp].qgeometry_bounds()
+                min_x_main = min(min_x, min_x_main)
+                min_y_main = min(min_y, min_y_main)
+                max_x_main = max(max_x, max_x_main)
+                max_y_main = max(max_y, max_y_main)
+        else:  # Strict subset rendered.
+            for qcomp_id in self.qcomp_ids:
+                min_x, min_y, max_x, max_y = self.design._components[
+                    qcomp_id].qgeometry_bounds()
+                min_x_main = min(min_x, min_x_main)
+                min_y_main = min(min_y, min_y_main)
+                max_x_main = max(max_x, max_x_main)
+                max_y_main = max(max_y, max_y_main)
+        return min_x_main, min_y_main, max_x_main, max_y_main
+
+    def render_chips(self,
+                     draw_sample_holder: bool = True,
+                     box_plus_buffer: bool = True):
+        """
+        Render all chips containing components in self.qcomp_ids.
+
+        Args:
+            draw_sample_holder (bool, optional): Option to draw vacuum box around chip. Defaults to True.
+            box_plus_buffer (bool, optional): Whether or not to use a box plus buffer. Defaults to True.
+        """
+        chip_list = self.get_chip_names()
+        if box_plus_buffer:  # Get bounding box of components first
+            min_x_main, min_y_main, max_x_main, max_y_main = parse_units(
+                self.get_min_bounding_box())
+            self.cw_x = max_x_main - min_x_main  # chip width along x
+            self.cw_y = max_y_main - min_y_main  # chip width along y
+            self.cw_x += 2 * parse_units(self._options['x_buffer_width_mm'])
+            self.cw_y += 2 * parse_units(self._options['y_buffer_width_mm'])
+            self.cc_x = (max_x_main + min_x_main) / 2  # x coord of chip center
+            self.cc_y = (max_y_main + min_y_main) / 2  # y coord of chip center
+        else:  # Adhere to chip placement and dimensions in QDesign
+            p = self.design.get_chip_size(
+                'main')  # x/y center/width same for all chips
+            self.cw_x, self.cw_y, _ = parse_units(
+                [p['size_x'], p['size_y'], p['size_z']])
+            self.cc_x, self.cc_y, _ = parse_units(
+                [p['center_x'], p['center_y'], p['center_z']])
+        for chip_name in chip_list:
+            self.render_chip(chip_name, draw_sample_holder and
+                             (chip_name == 'main'))
+
+    def render_chip(self, chip_name: str, draw_sample_holder: bool):
+        """
+        Render individual chips.
+
+        Args:
+            chip_name (str): Name of chip.
+            draw_sample_holder (bool): Option to draw vacuum box around chip.
+        """
+        ansys_options = dict(transparency=0.0)
+        ops = self.design._chips[chip_name]
+        p = self.design.get_chip_size(chip_name)
+        z_coord, height = parse_units([p['center_z'], p['size_z']])
+        plane = self.modeler.draw_rect_center([self.cc_x, self.cc_y, z_coord],
+                                              x_size=self.cw_x,
+                                              y_size=self.cw_y,
+                                              name=f'ground_{chip_name}_plane',
+                                              **ansys_options)
+        whole_chip = self.modeler.draw_box_center(
+            [self.cc_x, self.cc_y, height / 2], [self.cw_x, self.cw_y, -height],
+            name=chip_name,
+            material=ops['material'],
+            color=(186, 186, 205),
+            transparency=0.2,
+            wireframe=False)
+        if draw_sample_holder:  # HFSS
+            vac_height = parse_units(
+                [p['sample_holder_top'], p['sample_holder_bottom']])
+            vacuum_box = self.modeler.draw_box_center(
+                [self.cc_x, self.cc_y, (vac_height[0] - vac_height[1]) / 2],
+                [self.cw_x, self.cw_y, sum(vac_height)],
+                name='sample_holder')
+        if self.chip_subtract_dict[chip_name]:
+            # Any layer which has subtract=True qgeometries will have a ground plane
+            # TODO: Material property assignment may become layer-dependent.
+            self.assign_perfE.append(f'ground_{chip_name}_plane')
+
+    def subtract_from_ground(self):
+        """For each chip, subtract all "negative" shapes residing on its
+        surface if any such shapes exist."""
+        for chip, shapes in self.chip_subtract_dict.items():
+            if shapes:
+                import pythoncom
+                try:
+                    self.modeler.subtract(f'ground_{chip}_plane', list(shapes))
+                except pythoncom.com_error as error:
+                    print("com_error: ", error)
+                    hr, msg, exc, arg = error.args
+                    if msg == "Exception occurred." and hr == -2147352567:
+                        self.logger.error(
+                            "This error might indicate that a component was not correctly rendered in Ansys. \n"
+                            "This might have been caused by floating point numerical corrections. \n For example "
+                            "Ansys will inconsistently render (or not) routing that has 180deg jogs with the two "
+                            "adjacent segments spaced 'exactly' twice the fillet radius (U shaped routing). \n"
+                            "In this example, changing your fillet radius to a smaller number would solve the issue."
+                        )
+                    raise error
+
+    def add_mesh(self):
+        """Add mesh to all elements in self.assign_mesh and self.assign_port_mesh."""
+        if self.assign_mesh:
+            self.modeler.mesh_length(
+                'small_mesh',
+                self.assign_mesh,
+                MaxLength=self._options['max_mesh_length_jj'])
+
+        if self.assign_port_mesh:
+            self.modeler.mesh_length(
+                'port_mesh',
+                self.assign_port_mesh,
+                MaxLength=self._options['max_mesh_length_port'])
+
+    #Still implementing
+    def auto_wirebonds(self, table):
+        """
+        Adds wirebonds to the Ansys model for path elements where;
+        subtract = True and wire_bonds = True.
+
+        Uses render options for determining of the:
+        * wb_threshold -- the minimum distance between two vertices of a path for a
+        wirebond to be added.
+        * wb_offset -- offset distance for wirebond placement (along the direction
+        of the cpw)
+        * wb_size -- controls the width of the wirebond (wb_size * path['width'])
+        """
+        norm_z = np.array([0, 0, 1])
+
+        wb_threshold = parse_units(self._options['wb_threshold'])
+        wb_offset = parse_units(self._options['wb_offset'])
+
+        #selecting only the qgeometry which meet criteria
+        wb_table = table.loc[table['hfss_wire_bonds'] == True]
+        wb_table2 = wb_table.loc[wb_table['subtract'] == True]
+
+        #looping through each qgeometry
+        for _, row in wb_table2.iterrows():
+            geom = row['geometry']
+            width = row['width']
+            #looping through the linestring of the path to determine where WBs should be
+            for index, i_p in enumerate(geom.coords[:-1], start=0):
+                j_p = np.asarray(geom.coords[:][index + 1])
+                vert_distance = parse_units(distance.euclidean(i_p, j_p))
+                if vert_distance > wb_threshold:
+                    #Gets number of wirebonds to fit in section of path
+                    wb_count = int(vert_distance // wb_threshold)
+                    #finds the position vector
+                    wb_pos = (j_p - i_p) / (wb_count + 1)
+                    #gets the norm vector for finding the orthonormal of path
+                    wb_vec = wb_pos / np.linalg.norm(wb_pos)
+                    #finds the orthonormal (for orientation)
+                    wb_perp = np.cross(norm_z, wb_vec)[:2]
+                    #finds the first wirebond to place (rest are in the loop)
+                    wb_pos_step = parse_units(wb_pos + i_p) + (wb_vec *
+                                                               wb_offset)
+                    #Other input values could be modified, kept to minimal selection for automation
+                    #for the time being. Loops to place N wirebonds based on length of path section.
+                    for wb_i in range(wb_count):
+                        self.modeler.draw_wirebond(
+                            pos=wb_pos_step + parse_units(wb_pos * wb_i),
+                            ori=wb_perp,
+                            width=parse_units(width * self._options['wb_size']),
+                            height=parse_units(width *
+                                               self._options['wb_size']),
+                            z=0,
+                            wire_diameter='0.015mm',
+                            NumSides=6,
+                            name='g_wb',
+                            material='pec',
+                            solve_inside=False)
+
+    def clean_active_design(self):
+        """Remove all elements from Ansys Modeler."""
+        if self.pinfo:
+            if self.pinfo.get_all_object_names():
+                project_name = self.pinfo.project_name
+                design_name = self.pinfo.design_name
+                select_all = ','.join(self.pinfo.get_all_object_names())
+
+                # self.pinfo.design does not work, thus the following line
+                oDesktop = self.pinfo.design.parent.parent._desktop
+                oProject = oDesktop.SetActiveProject(project_name)
+                oDesign = oProject.SetActiveDesign(design_name)
+
+                # The available editors: "Layout", "3D Modeler", "SchematicEditor"
+                oEditor = oDesign.SetActiveEditor("3D Modeler")
+
+                oEditor.Delete(["NAME:Selections", "Selections:=", select_all])
+
+    def set_variables(self, variables: Dict):
+        """Fixes the junction properties before setup. This is necessary becasue the eigenmode
+        analysis only considers the junction as a lumped CL element.
+
+        Args:
+            variables (Dict): dictionary of variables to set in Ansys. For example it could
+                contain 'Lj': '10 nH'
+        """
+        if self.pinfo:
+            if self.pinfo.design:
+                for k, v in variables.items():
+                    self.pinfo.design.set_variable(k, v)
+            else:
+                self.logger.warning(
+                    'Please create a design before setting variables, otherwise all variables will be set to 0 during rendering by default.'
+                )
+
+    # TODO: epr methods below should not be in the renderer, but in the analysis files.
+    #  Thus needs to remove the dependency from pinfo, which is Ansys-specific.
+
+    def epr_start(self, junctions: dict = None, dissipatives: dict = None):
+        """Use to initialize the epr analysis package by first identifying which are the junctions,
+        their electrical properties and their reference plane; then initialize the
+        DistributedAnalysis package, which can execute microwave analysis on eigenmode results.
+
+        Args:
+            junctions (dict, optional): Each element of this dictionary describes one junction.
+                Defaults to dict().
+            dissipatives (dict, optional): Each element of this dictionary describes one dissipative.
+                Defaults to dict().
+        """
+        if self.pinfo:
+            if junctions:
+                for k, v in junctions.items():
+                    self.pinfo.junctions[k] = v
+                # Check that valid names of variables and objects have been supplied
+                self.pinfo.validate_junction_info()
+            if dissipatives:
+                for k, v in dissipatives.items():
+                    self.pinfo.dissipative[k] = v
+
+            # Class handling microwave analysis on eigenmode solutions
+            self.epr_distributed_analysis = epr.DistributedAnalysis(self.pinfo)
+
+    def epr_get_stored_energy(self,
+                              junctions: dict = None,
+                              dissipatives: dict = None):
+        """Computes the energy stored in the system
+        pinfo must have a valid list of junctions and dissipatives to compute the energy stored
+        in the system. So please provide them here, or using epr_start()
+
+        Args:
+            junctions (dict, optional): Each element of this dictionary describes one junction.
+                Defaults to dict().
+            dissipatives (dict, optional): Each element of this dictionary describes one dissipative.
+                Defaults to dict().
+
+        Returns:
+            (float, float, float): energy_elec, energy_elec_substrate, energy_mag
+        """
+        if junctions is not None or dissipatives is not None:
+            self.epr_start(junctions, dissipatives)
+        elif self.epr_distributed_analysis is None:
+            self.epr_start()
+
+        if self.pinfo.dissipative['dielectrics_bulk'] is not None:
+            eprd = self.epr_distributed_analysis
+            energy_elec = eprd.calc_energy_electric()
+            energy_elec_substrate = eprd.calc_energy_electric(
+                None, self.pinfo.dissipative['dielectrics_bulk'][0])
+            energy_mag = eprd.calc_energy_magnetic()
+
+            return energy_elec, energy_elec_substrate, energy_mag
+        self.logger.error('dielectrics_bulk needs to be defined')
+
+    def epr_run_analysis(self,
+                         junctions: dict = None,
+                         dissipatives: dict = None):
+        """Executes the EPR analysis
+        pinfo must have a valid list of junctions and dissipatives to compute the energy stored
+        in the system. So please provide them here, or using epr_start()
+
+        Args:
+            junctions (dict, optional): Each element of this dictionary describes one junction.
+                Defaults to dict().
+            dissipatives (dict, optional): Each element of this dictionary describes one dissipative.
+                Defaults to dict().
+        """
+        if junctions is not None or dissipatives is not None:
+            self.epr_start(junctions, dissipatives)
+        self.epr_distributed_analysis.do_EPR_analysis()
+
+    def epr_spectrum_analysis(self, cos_trunc: int = 8, fock_trunc: int = 7):
+        """Core epr analysis method.
+
+        Args:
+            cos_trunc (int, optional): truncation of the cosine. Defaults to 8.
+            fock_trunc (int, optional): truncation of the fock. Defaults to 7.
+        """
+        self.epr_quantum_analysis = epr.QuantumAnalysis(
+            self.epr_distributed_analysis.data_filename)
+        self.epr_quantum_analysis.analyze_all_variations(cos_trunc=cos_trunc,
+                                                         fock_trunc=fock_trunc)
+
+    def epr_report_hamiltonian(self,
+                               swp_variable: str = 'variation',
+                               numeric=True):
+        """Reports in a markdown friendly table the hamiltonian results.
+
+        Args:
+            swp_variable (str, optional): Variable against which we swept. Defaults to 'variation'.
+        """
+        self.epr_quantum_analysis.plot_hamiltonian_results(
+            swp_variable=swp_variable)
+        self.epr_quantum_analysis.report_results(swp_variable=swp_variable,
+                                                 numeric=numeric)
+
+    def epr_get_frequencies(self,
+                            junctions: dict = None,
+                            dissipatives: dict = None) -> pd.DataFrame:
+        """Returns all frequencies and quality factors vs a variation.
+        It also initializes the systems for the epr analysis in terms of junctions and dissipatives
+
+        Args:
+            junctions (dict, optional): Each element of this dictionary describes one junction.
+                Defaults to dict().
+            dissipatives (dict, optional): Each element of this dictionary describes one dissipative.
+                Defaults to dict().
+
+        Returns:
+            pd.DataFrame: multi-index, frequency and quality factors for each variation point.
+        """
+        # TODO: do I need to reset self.pinfo.junctions (does it keep the older analysis one)
+        self.epr_start(junctions, dissipatives)
+        return self.epr_distributed_analysis.get_ansys_frequencies_all()
diff --git a/qiskit_metal/renderers/renderer_ansys/hfss_renderer.py b/qiskit_metal/renderers/renderer_ansys/hfss_renderer.py
index 6406d302f..b8ee5c5ce 100644
--- a/qiskit_metal/renderers/renderer_ansys/hfss_renderer.py
+++ b/qiskit_metal/renderers/renderer_ansys/hfss_renderer.py
@@ -1,1066 +1,1078 @@
-# -*- coding: utf-8 -*-
-
-# This code is part of Qiskit.
-#
-# (C) Copyright IBM 2017, 2021.
-#
-# This code is licensed under the Apache License, Version 2.0. You may
-# obtain a copy of this license in the LICENSE.txt file in the root directory
-# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
-#
-# Any modifications or derivative works of this code must retain this
-# copyright notice, and modified files need to carry a notice indicating
-# that they have been altered from the originals.
-
-# modified by Chalmers/SK/20210621
-
-from collections import defaultdict
-from pathlib import Path
-from typing import Union, Tuple
-
-import logging
-import matplotlib as mpl
-import matplotlib.pyplot as plt
-import numpy as np
-import pandas as pd
-import pyEPR as epr
-from pyEPR.ansys import set_property, parse_units
-
-from qiskit_metal import Dict
-from qiskit_metal.draw.utility import to_vec3D
-from qiskit_metal.renderers.renderer_ansys.ansys_renderer import (
-    QAnsysRenderer, get_clean_name)
-
-
-class QHFSSRenderer(QAnsysRenderer):
-    """Subclass of QAnsysRenderer for running HFSS simulations.
-
-    QAnsysRenderer Default Options:
-        * Lj: '10nH' -- Lj has units of nanoHenries (nH)
-        * Cj: 0 -- Cj *must* be 0 for pyEPR analysis! Cj has units of femtofarads (fF)
-        * _Rj: 0 -- _Rj *must* be 0 for pyEPR analysis! _Rj has units of Ohms
-        * max_mesh_length_jj: '7um' -- Maximum mesh length for Josephson junction elements
-        * project_path: None -- Default project path; if None --> get active
-        * project_name: None -- Default project name
-        * design_name: None -- Default design name
-        * ansys_file_extension: '.aedt' -- Ansys file extension for 2016 version and newer
-        * x_buffer_width_mm: 0.2 -- Buffer between max/min x and edge of ground plane, in mm
-        * y_buffer_width_mm: 0.2 -- Buffer between max/min y and edge of ground plane, in mm
-    """
-
-    name = 'hfss'
-    """Name"""
-
-    hfss_options = Dict(
-        port_inductor_gap=
-        '10um'  # spacing between port and inductor if junction is drawn both ways
-    )
-    """HFSS Options"""
-
-    def __init__(self, design: 'QDesign', initiate=True, options: Dict = None):
-        """Create a QRenderer for HFSS simulations, subclassed from QAnsysRenderer.
-
-        Args:
-            design (QDesign): Use QGeometry within QDesign to obtain elements for Ansys.
-            initiate (bool, optional): True to initiate the renderer. Defaults to True.
-            options (Dict, optional):  Used to override all options. Defaults to None.
-        """
-        super().__init__(design=design, initiate=initiate, options=options)
-
-        self.current_sweep = None
-
-        QHFSSRenderer.load()
-
-    def render_design(self,
-                      selection: Union[list, None] = None,
-                      open_pins: Union[list, None] = None,
-                      port_list: Union[list, None] = None,
-                      jj_to_port: Union[list, None] = None,
-                      ignored_jjs: Union[list, None] = None,
-                      box_plus_buffer: bool = True):
-        """Initiate rendering of components in design contained in selection,
-        assuming they're valid. Components are rendered before the chips they
-        reside on, and subtraction of negative shapes is performed at the very
-        end. Add the metalize() method here to turn objects in
-        self.assign_perfE (see init in QAnsysRenderer class) into perfect
-        electrical conductors. Create lumped ports as needed.
-
-        First obtain a list of IDs of components to render and a corresponding
-        case, denoted by self.qcomp_ids and self.case, respectively. If
-        self.case == 1, all components in QDesign are to be rendered.  If
-        self.case == 0, a strict subset of components in QDesign are to be
-        rendered. Otherwise, if self.case == 2, one or more component names in
-        selection cannot be found in QDesign.
-
-        Among the components selected for export, there may or may not be
-        unused (unconnected) pins. The second parameter, open_pins, contains
-        tuples of the form (component_name, pin_name) that specify exactly
-        which pins should be open rather than shorted during the simulation.
-        Both the component and pin name must be specified because the latter
-        could be shared by multiple components. All pins in this list are
-        rendered with an additional endcap in the form of a rectangular
-        cutout, to be subtracted from its respective plane.
-
-        In driven modal solutions, the Ansys design must include one or more
-        ports. This is done by adding all port designations and their
-        respective impedances in Ohms as (qcomp, pin, impedance) to port_list.
-        Note that an open endcap must separate the two sides of each pin before
-        inserting a lumped port in between, so behind the scenes all pins in
-        port_list are also added to open_pins. Practically, however, port_list
-        and open_pins are inputted as mutually exclusive lists.
-
-        Also in driven modal solutions, one may want to render junctions as
-        lumped ports and/or inductors, or omit them altogether. To do so,
-        tuples of the form (component_name, element_name, impedance, draw_ind)
-        are added to the list jj_to_port. For example,
-        ('Q1', 'rect_jj', 70, True) indicates that rect_jj of component Q1 is
-        to be rendered as both a lumped port with an impedance of 70 Ohms as
-        well as an inductor whose properties are given in default_options.
-        Replacing the last entry of this 4-element tuple with False would
-        indicate that only the port is to be drawn, not the inductor.
-        Alternatively for driven modal solutions, one may want to disregard
-        select junctions in the Metal design altogether to simulate the
-        capacitive effect while keeping the qubit in an "off" state. Such
-        junctions are specified in the form (component_name, element_name)
-        in the list ignored_jjs.
-
-        The final parameter, box_plus_buffer, determines how the chip is drawn.
-        When set to True, it takes the minimum rectangular bounding box of all
-        rendered components and adds a buffer of x_buffer_width_mm and
-        y_buffer_width_mm horizontally and vertically, respectively, to the
-        chip size. The center of the chip lies at the midpoint x/y coordinates
-        of the minimum rectangular bounding box and may change depending on
-        which components are rendered and how they're positioned. If
-        box_plus_buffer is False, however, the chip position and dimensions
-        are taken from the chip info dictionary found in self.design,
-        irrespective of what's being rendered. While this latter option is
-        faster because it doesn't require calculating a bounding box, it runs
-        the risk of rendered components being too close to the edge of the chip
-        or even falling outside its boundaries.
-
-        Args:
-            selection (Union[list, None], optional): List of components to
-                                        render. Defaults to None.
-            open_pins (Union[list, None], optional): List of tuples of pins
-                                        that are open. Defaults to None.
-            port_list (Union[list, None], optional): List of tuples of pins to
-                                        be rendered as ports. Defaults to None.
-            jj_to_port (Union[list, None], optional): List of tuples of jj's to
-                                        be rendered as ports. Defaults to None.
-            ignored_jjs (Union[list, None], optional): List of tuples of jj's
-                                        that shouldn't be rendered.
-                                        Defaults to None.
-            box_plus_buffer (bool): Either calculate a bounding box based on
-                                        the location of rendered geometries
-                                        or use chip size from design class.
-        """
-        self.qcomp_ids, self.case = self.get_unique_component_ids(selection)
-
-        if self.case == 2:
-            self.logger.warning(
-                'Unable to proceed with rendering. Please check selection.')
-            return
-
-        self.chip_subtract_dict = defaultdict(set)
-        self.assign_perfE = []
-        self.assign_mesh = []
-        self.jj_lumped_ports = {}
-        self.jj_to_ignore = set()
-
-        if jj_to_port:
-            self.jj_lumped_ports = {
-                (qcomp, elt): [impedance, draw_ind]
-                for qcomp, elt, impedance, draw_ind in jj_to_port
-            }
-
-        if ignored_jjs:
-            self.jj_to_ignore = {(qcomp, qelt) for qcomp, qelt in ignored_jjs}
-
-        self.render_tables()
-        if port_list:
-            self.add_endcaps(open_pins +
-                             [(qcomp, pin) for qcomp, pin, _ in port_list])
-        else:
-            self.add_endcaps(open_pins)
-
-        self.render_chips(box_plus_buffer=box_plus_buffer)
-        self.subtract_from_ground()
-        self.add_mesh()
-        self.metallize()
-        if port_list:
-            self.create_ports(port_list)
-
-    def create_ports(self, port_list: list):
-        """Add ports and their respective impedances in Ohms to designated pins
-        in port_list. Port_list is formatted as [(qcomp_0, pin_0, impedance_0),
-        (qcomp_1, pin_1, impedance_1), ...].
-
-        Args:
-            port_list (list): List of tuples of pins to be rendered as ports.
-        """
-        for qcomp, pin, impedance in port_list:
-            port_name = f'Port_{qcomp}_{pin}'
-            pdict = self.design.components[qcomp].pins[pin]
-            midpt, gap_size, norm_vec, width = pdict['middle'], pdict['gap'], \
-                                               pdict['normal'], pdict['width']
-            width = parse_units(width)
-            endpoints = parse_units([midpt, midpt + gap_size * norm_vec])
-            endpoints_3d = to_vec3D(endpoints, 0)  # Set z height to 0
-            x0, y0 = endpoints_3d[0][:2]
-            x1, y1 = endpoints_3d[1][:2]
-            if abs(y1 - y0) > abs(x1 - x0):
-                # Junction runs vertically up/down
-                x_min, x_max = x0 - width / 2, x0 + width / 2
-                y_min, y_max = min(y0, y1), max(y0, y1)
-            else:
-                # Junction runs horizontally left/right
-                x_min, x_max = min(x0, x1), max(x0, x1)
-                y_min, y_max = y0 - width / 2, y0 + width / 2
-
-            # Draw rectangle
-            self.logger.debug(f'Drawing a rectangle: {port_name}')
-            poly_ansys = self.modeler.draw_rect_corner([x_min, y_min, 0],
-                                                       x_max - x_min,
-                                                       y_max - y_min, 0,
-                                                       **dict(transparency=0.0))
-            axis = 'x' if abs(x1 - x0) > abs(y1 - y0) else 'y'
-            poly_ansys.make_lumped_port(axis,
-                                        z0=str(impedance) + 'ohm',
-                                        name=f'LumpPort_{qcomp}_{pin}')
-            self.modeler.rename_obj(poly_ansys, port_name)
-
-            # Draw line
-            lump_line = self.modeler.draw_polyline(
-                [endpoints_3d[0], endpoints_3d[1]],
-                closed=False,
-                **dict(color=(128, 0, 128)))
-            lump_line = lump_line.rename(f'voltage_line_{port_name}')
-            lump_line.show_direction = True
-
-    def render_element_junction(self, qgeom: pd.Series):
-        """
-        Render a Josephson junction depending on the solution type.
-
-        If in HFSS eigenmode, junctions are rendered as inductors consisting of
-            1. A rectangle of length pad_gap and width inductor_width. Defines lumped element RLC
-               boundary condition.
-            2. A line that is later used to calculate the voltage in post-processing analysis.
-
-        If in HFSS driven modal, junctions can be inductors, lumped ports, both inductors
-        and lumped ports, or omitted altogether. Ports are characterized by an impedance
-        value given in the list jj_to_port when render_design() is called.
-
-        Args:
-            qgeom (pd.Series): GeoSeries of element properties.
-        """
-        qcomp = self.design._components[qgeom['component']].name
-        qc_elt = get_clean_name(qgeom['name'])
-
-        if (qcomp, qc_elt) not in self.jj_to_ignore:
-            qc_shapely = qgeom.geometry
-            qc_chip_z = parse_units(self.design.get_chip_z(qgeom.chip))
-            qc_width = parse_units(qgeom.width)
-
-            endpoints = parse_units(list(qc_shapely.coords))
-            endpoints_3d = to_vec3D(endpoints, qc_chip_z)
-            x0, y0, z0 = endpoints_3d[0]
-            x1, y1, z0 = endpoints_3d[1]
-            if abs(y1 - y0) > abs(x1 - x0):
-                # Junction runs vertically up/down
-                axis = 'y'
-                x_min, x_max = x0 - qc_width / 2, x0 + qc_width / 2
-                y_min, y_max = min(y0, y1), max(y0, y1)
-            else:
-                # Junction runs horizontally left/right
-                axis = 'x'
-                x_min, x_max = min(x0, x1), max(x0, x1)
-                y_min, y_max = y0 - qc_width / 2, y0 + qc_width / 2
-
-            if (qcomp, qc_elt) in self.jj_lumped_ports:
-                if self.jj_lumped_ports[(qcomp, qc_elt)][1]:
-                    # Draw both port and inductor side by side with small gap in between
-                    gap = parse_units(self.hfss_options['port_inductor_gap'])
-                    x_mid, y_mid = (x_min + x_max) / 2, (y_min + y_max) / 2
-                    if axis == 'x':
-                        y_mid_hi = y_mid + gap / 2
-                        y_mid_lo = y_mid - gap / 2
-                        self.render_junction_port(qgeom, x_min, x_max, y_mid_hi,
-                                                  y_max, qc_chip_z, axis)
-                        self.render_junction_inductor(qgeom, x_min, x_max,
-                                                      y_min, y_mid_lo,
-                                                      qc_chip_z, axis)
-                    elif axis == 'y':
-                        x_mid_lo = x_mid - gap / 2
-                        x_mid_hi = x_mid + gap / 2
-                        self.render_junction_port(qgeom, x_mid_hi, x_max, y_min,
-                                                  y_max, qc_chip_z, axis)
-                        self.render_junction_inductor(qgeom, x_min, x_mid_lo,
-                                                      y_min, y_max, qc_chip_z,
-                                                      axis)
-                else:
-                    # Only draw port
-                    self.render_junction_port(qgeom, x_min, x_max, y_min, y_max,
-                                              qc_chip_z, axis)
-            else:
-                # Only draw inductor
-                self.render_junction_inductor(qgeom, x_min, x_max, y_min, y_max,
-                                              qc_chip_z, axis)
-
-    def render_junction_port(self, qgeom: pd.Series, xmin: float, xmax: float,
-                             ymin: float, ymax: float, z: float, axis: str):
-        """Render a junction as a port with a bounding box given by xmin/xmax
-        and ymin/ymax, a height z, and a horizontal or vertical axis.
-
-        Args:
-            qgeom (pd.Series): GeoSeries of element properties.
-            xmin (float): Smallest x coordinate
-            xmax (float): Largest x coordinate
-            ymin (float): Smallest y coordinate
-            ymax (float): Largest y coordinate
-            z (float): z coordinate
-            axis (str): Orientation, either 'x' or 'y'
-        """
-        ansys_options = dict(transparency=0.0)
-        qcomp = self.design._components[qgeom['component']].name
-        qc_elt = get_clean_name(qgeom['name'])
-        port_name = f'Port_{qcomp}_{qc_elt}'
-        impedance = self.jj_lumped_ports[(qcomp, qc_elt)][0]
-        # Draw rectangle for lumped port.
-        self.logger.debug(f'Drawing a rectangle: {port_name}')
-        poly_ansys = self.modeler.draw_rect_corner([xmin, ymin, z], xmax - xmin,
-                                                   ymax - ymin, z,
-                                                   **ansys_options)
-        poly_ansys.make_lumped_port(axis,
-                                    z0=str(impedance) + 'ohm',
-                                    name=f'LumpPort_{qcomp}_{qc_elt}')
-        self.modeler.rename_obj(poly_ansys, port_name)
-        # Draw line for lumped port.
-        if axis == 'x':
-            ymid = (ymin + ymax) / 2
-            start, end = [xmin, ymid, z], [xmax, ymid, z]
-        elif axis == 'y':
-            xmid = (xmin + xmax) / 2
-            start, end = [xmid, ymin, z], [xmid, ymax, z]
-        lump_line = self.modeler.draw_polyline([start, end],
-                                               closed=False,
-                                               **dict(color=(128, 0, 128)))
-        lump_line = lump_line.rename(f'voltage_line_{port_name}')
-        lump_line.show_direction = True
-
-    def render_junction_inductor(self, qgeom: pd.Series, xmin: float,
-                                 xmax: float, ymin: float, ymax: float,
-                                 z: float, axis: str):
-        """Render a junction as an inductor with a bounding box given by
-        xmin/xmax and ymin/ymax, a height z, and a horizontal or vertical axis.
-
-        Args:
-            qgeom (pd.Series): GeoSeries of element properties.
-            xmin (float): Smallest x coordinate
-            xmax (float): Largest x coordinate
-            ymin (float): Smallest y coordinate
-            ymax (float): Largest y coordinate
-            z (float): z coordinate
-            axis (str): Orientation, either 'x' or 'y'
-        """
-        ansys_options = dict(transparency=0.0)
-        qcomp = self.design._components[qgeom['component']].name
-        qc_elt = get_clean_name(qgeom['name'])
-        qc_name = 'Lj_' + qcomp
-        inductor_name = f'{qc_name}{QAnsysRenderer.NAME_DELIM}{qc_elt}'
-        # Draw rectangle for inductor.
-        self.logger.debug(f'Drawing a rectangle: {inductor_name}')
-        poly_ansys = self.modeler.draw_rect_corner([xmin, ymin, z], xmax - xmin,
-                                                   ymax - ymin, 0,
-                                                   **ansys_options)
-        poly_ansys.make_rlc_boundary(axis,
-                                     l=qgeom['hfss_inductance'],
-                                     c=qgeom['hfss_capacitance'],
-                                     r=qgeom['hfss_resistance'],
-                                     name='Lj_' + inductor_name)
-        self.modeler.rename_obj(poly_ansys, 'JJ_rect_' + inductor_name)
-        self.assign_mesh.append('JJ_rect_' + inductor_name)
-        # Draw line for inductor.
-        if axis == 'x':
-            ymid = (ymin + ymax) / 2
-            start, end = [xmin, ymid, z], [xmax, ymid, z]
-        elif axis == 'y':
-            xmid = (xmin + xmax) / 2
-            start, end = [xmid, ymin, z], [xmid, ymax, z]
-        induc_line = self.modeler.draw_polyline([start, end],
-                                                closed=False,
-                                                **dict(color=(128, 0, 128)))
-        induc_line = induc_line.rename('JJ_' + inductor_name + '_')
-        induc_line.show_direction = True
-
-    def metallize(self):
-        """Assign metallic property to all shapes in self.assign_perfE list."""
-        self.modeler.assign_perfect_E(self.assign_perfE)
-
-    def add_drivenmodal_design(self, name: str, connect: bool = True):
-        """
-        (deprecated) use new_ansys_design()
-        """
-        self.logger.warning(
-            'This method is deprecated. Change your scripts to use new_ansys_design()'
-        )
-        self.new_ansys_design(name, 'drivenmodal', connect)
-
-    def activate_drivenmodal_design(self, name: str = "MetalHFSSDrivenModal"):
-        """
-        (deprecated) use activate_ansys_design()
-        """
-        self.logger.warning(
-            'This method is deprecated. Change your scripts to use activate_ansys_design()'
-        )
-        self.activate_ansys_design(name, 'drivenmodal')
-
-    def activate_drivenmodal_setup(self, setup_name_activate: str = None):
-        """
-        (deprecated) use activate_ansys_setup()
-        """
-        self.logger.warning(
-            'This method is deprecated. Change your scripts to use activate_ansys_setup()'
-        )
-        self.activate_ansys_setup(setup_name_activate)
-
-    def add_drivenmodal_setup(self,
-                              name: str = None,
-                              freq_ghz: int = None,
-                              max_delta_s: float = None,
-                              max_passes: int = None,
-                              min_passes: int = None,
-                              min_converged: int = None,
-                              pct_refinement: int = None,
-                              basis_order: int = None,
-                              *args,
-                              **kwargs):
-        """Create a solution setup in Ansys HFSS Driven Modal. If user does
-        not provide arguments, they will be obtained from default_setup dict.  
-        
-        Args:
-            name (str, optional): Name of driven modal setup. Defaults to None.
-            freq_ghz (int, optional): Frequency in GHz. Defaults to None.
-            max_delta_s (float, optional): Absolute value of maximum difference in scattering parameter S. Defaults to None.
-            max_passes (int, optional): Maximum number of passes. Defaults to None.
-            min_passes (int, optional): Minimum number of passes. Defaults to None.
-            min_converged (int, optional): Minimum number of converged passes. Defaults to None.
-            pct_refinement (int, optional): Percent refinement. Defaults to None.
-            basis_order (int, optional): Basis order. Defaults to None.
-        """
-        dsu = self.default_setup.drivenmodal
-
-        if not name:
-            name = self.parse_value(dsu['name'])
-        if not freq_ghz:
-            freq_ghz = int(self.parse_value(dsu['freq_ghz']))
-        if not max_delta_s:
-            max_delta_s = float(self.parse_value(dsu['max_delta_s']))
-        if not max_passes:
-            max_passes = int(self.parse_value(dsu['max_passes']))
-        if not min_passes:
-            min_passes = int(self.parse_value(dsu['min_passes']))
-        if not min_converged:
-            min_converged = int(self.parse_value(dsu['min_converged']))
-        if not pct_refinement:
-            pct_refinement = int(self.parse_value(dsu['pct_refinement']))
-        if not basis_order:
-            basis_order = int(self.parse_value(dsu['basis_order']))
-
-        if self.pinfo:
-            if self.pinfo.design:
-                return self.pinfo.design.create_dm_setup(
-                    freq_ghz=freq_ghz,
-                    name=name,
-                    max_delta_s=max_delta_s,
-                    max_passes=max_passes,
-                    min_passes=min_passes,
-                    min_converged=min_converged,
-                    pct_refinement=pct_refinement,
-                    basis_order=basis_order)
-
-    def add_eigenmode_design(self, name: str, connect: bool = True):
-        """
-        (deprecated) use new_ansys_design()
-        """
-        self.logger.warning(
-            'This method is deprecated. Change your scripts to use new_ansys_design()'
-        )
-        self.new_ansys_design(name, 'eigenmode', connect)
-
-    def activate_eigenmode_design(self, name: str = "MetalHFSSEigenmode"):
-        """
-        (deprecated) use activate_ansys_design()
-        """
-        self.logger.warning(
-            'This method is deprecated. Change your scripts to use activate_ansys_design()'
-        )
-        self.activate_ansys_design(name, 'eigenmode')
-
-    def activate_eigenmode_setup(self, setup_name_activate: str = None):
-        """
-        (deprecated) use activate_ansys_setup()
-        """
-        self.logger.warning(
-            'This method is deprecated. Change your scripts to use activate_ansys_setup()'
-        )
-        self.activate_ansys_setup(setup_name_activate)
-
-    def add_eigenmode_setup(self,
-                            name: str = None,
-                            min_freq_ghz: int = None,
-                            n_modes: int = None,
-                            max_delta_f: float = None,
-                            max_passes: int = None,
-                            min_passes: int = None,
-                            min_converged: int = None,
-                            pct_refinement: int = None,
-                            basis_order: int = None,
-                            *args,
-                            **kwargs):
-        """Create a solution setup in Ansys HFSS Eigenmode.  If user does not
-        provide arguments, they will be obtained from hfss_options dict.
-
-        Args:
-            name (str, optional): Name of eigenmode setup. Defaults to "Setup".
-            min_freq_ghz (int, optional): Minimum frequency in GHz. Defaults to 1.
-            n_modes (int, optional): Number of modes. Defaults to 1.
-            max_delta_f (float, optional): Maximum difference in freq between consecutive passes. Defaults to 0.5.
-            max_passes (int, optional): Maximum number of passes. Defaults to 10.
-            min_passes (int, optional): Minimum number of passes. Defaults to 1.
-            min_converged (int, optional): Minimum number of converged passes. Defaults to 1.
-            pct_refinement (int, optional): Percent refinement. Defaults to 30.
-            basis_order (int, optional): Basis order. Defaults to -1.
-        """
-        esu = self.default_setup.eigenmode
-
-        if not name:
-            name = self.parse_value(esu['name'])
-        if not min_freq_ghz:
-            min_freq_ghz = int(self.parse_value(esu['min_freq_ghz']))
-        if not n_modes:
-            n_modes = int(self.parse_value(esu['n_modes']))
-        if not max_delta_f:
-            max_delta_f = float(self.parse_value(esu['max_delta_f']))
-        if not max_passes:
-            max_passes = int(self.parse_value(esu['max_passes']))
-        if not min_passes:
-            min_passes = int(self.parse_value(esu['min_passes']))
-        if not min_converged:
-            min_converged = int(self.parse_value(esu['min_converged']))
-        if not pct_refinement:
-            pct_refinement = int(self.parse_value(esu['pct_refinement']))
-        if not basis_order:
-            basis_order = int(self.parse_value(esu['basis_order']))
-
-        if self.pinfo:
-            if self.pinfo.design:
-                return self.pinfo.design.create_em_setup(
-                    name=name,
-                    min_freq_ghz=min_freq_ghz,
-                    n_modes=n_modes,
-                    max_delta_f=max_delta_f,
-                    max_passes=max_passes,
-                    min_passes=min_passes,
-                    min_converged=min_converged,
-                    pct_refinement=pct_refinement,
-                    basis_order=basis_order)
-
-    def edit_eigenmode_setup(self, setup_args: Dict):
-        """User can pass key/values to edit the setup for active eigenmode setup.
-
-        Args:
-            setup_args (Dict): a Dict with possible keys/values.
-
-        **setup_args** dict contents:
-            * name (str, optional): Name of eigenmode setup. Defaults to "Setup".
-            * min_freq_ghz (int, optional): Minimum frequency in GHz. Defaults to 1.
-            * n_modes (int, optional): Number of modes. Defaults to 1.
-            * max_delta_f (float, optional): Maximum difference in freq between consecutive passes. Defaults to 0.5.
-            * max_passes (int, optional): Maximum number of passes. Defaults to 10.
-            * pct_refinement (int, optional): Percent refinement. Defaults to 30.
-            * basis_order (int, optional): Basis order. Defaults to -1.
-
-            Note, that these two are currently NOT implemented:
-            Ansys API named EditSetup not documented for HFSS, and
-            self.pinfo.setup does not have all the property variables used for Setup.
-            * min_passes (int, optional): Minimum number of passes. Defaults to 1.
-            * min_converged (int, optional): Minimum number of converged passes. Defaults to 1.
-        """
-
-        if self.pinfo:
-            if self.pinfo.project:
-                if self.pinfo.design:
-                    if self.pinfo.design.solution_type == 'Eigenmode':
-                        if self.pinfo.setup_name != setup_args.name:
-                            self.design.logger.warning(
-                                f'The name of active setup={self.pinfo.setup_name} does not match'
-                                f'the name of of setup_args.name={setup_args.name}. '
-                                f'To use this method, activate the desired Setup before editing it. The '
-                                f'setup_args was not used to update the active Setup.'
-                            )
-                            return
-
-                        for key, value in setup_args.items():
-                            if key == "name":
-                                continue  #Checked for above.
-                            if key == "n_modes":
-                                #EditSetup  not documented, this is just attempt to use.
-                                #args_editsetup = ["NAME:" + setup_args.name,["NumModes:=", setup_args.n_modes]]
-                                #self.pinfo.setup._setup_module.EditSetup([setup_args.name, args_editsetup])
-                                if value < 0 or value > 20 or not isinstance(
-                                        value, int):
-                                    self.logger.warning(
-                                        f'Value of n_modes={value} must be integer from 1 to 20.'
-                                    )
-                                else:
-                                    self.pinfo.setup.n_modes = value
-                                    continue
-                            if key == "min_freq_ghz":
-                                if not isinstance(value, int):
-                                    self.logger.warning(
-                                        'The value for min_freq_ghz should be an int. '
-                                        f'The present value is {value}.')
-                                else:
-                                    self.pinfo.setup.min_freq = f'{value}GHz'
-                                    continue
-                            if key == 'max_delta_f':
-                                if not isinstance(value, float):
-                                    self.logger.warning(
-                                        'The value for max_delta_f should be float. '
-                                        f'The present value is {value}.')
-                                else:
-                                    self.pinfo.setup.delta_f = value
-                                    continue
-                            if key == 'max_passes':
-                                if not isinstance(value, int):
-                                    self.logger.warning(
-                                        'The value for max_passes should be an int. '
-                                        f'The present value is {value}.')
-                                else:
-                                    self.pinfo.setup.passes = value
-                                    continue
-                            if key == 'pct_refinement':
-                                if not isinstance(value, int):
-                                    self.logger.warning(
-                                        'The value for pct_refinement should be an int. '
-                                        f'The present value is {value}.')
-                                else:
-                                    self.pinfo.setup.pct_refinement = value
-                                    continue
-                            if key == 'basis_order':
-                                if not isinstance(value, int):
-                                    self.logger.warning(
-                                        'The value for basis_order should be an int. '
-                                        f'The present value is {value}.')
-                                else:
-                                    self.pinfo.setup.basis_order = value
-                                    continue
-
-                            self.design.logger.warning(
-                                f'In setup_args, key={key}, value={value} is not in pinfo.setup, '
-                                'the key/value pair from setup_args not added to Setup in Ansys.'
-                            )
-
-                    else:
-                        self.logger.warning(
-                            'The design does not have solution type as "Eigenmode". The Setup not updated.'
-                        )
-                else:
-                    self.logger.warning(
-                        'A design is not in active project. The Setup not updated.'
-                    )
-            else:
-                self.logger.warning(
-                    "Project not available, have you opened a project? Setup not updated."
-                )
-        else:
-            self.logger.warning(
-                "Have you run connect_ansys()?  "
-                "Cannot find a reference to Ansys in QRenderer. Setup not updated. "
-            )
-
-    def edit_drivenmodal_setup(self, setup_args: Dict):
-        """User can pass key/values to edit the setup for active driven modal setup.
-
-        Args:
-            setup_args (Dict): a Dict with possible keys/values.
-
-        **setup_args** dict contents:
-            * name (str, optional): Name of eigenmode setup. Defaults to "Setup".
-            * freq_ghz (int, optional): Minimum frequency in GHz. Defaults to 1.
-            * max_passes (int, optional): Maximum number of passes. Defaults to 10.
-            * pct_refinement (int, optional): Percent refinement. Defaults to 30.
-            * basis_order (int, optional): Basis order. Defaults to -1 (1 is "Mixed Order").
-
-            Note, that these three are currently NOT implemented:
-            Ansys API named EditSetup not documented for HFSS, and
-            self.pinfo.setup does not have all the property variables used for Setup.
-            * max_delta_s (float, optional): Absolute value of maximum difference in scattering parameter S. Defaults to 0.1.
-            * min_passes (int, optional): Minimum number of passes. Defaults to 1.
-            * min_converged (int, optional): Minimum number of converged passes. Defaults to 1.
-        """
-
-        if self.pinfo:
-            if self.pinfo.project:
-                if self.pinfo.design:
-                    if self.pinfo.design.solution_type == 'DrivenModal':
-                        if self.pinfo.setup_name != setup_args.name:
-                            self.design.logger.warning(
-                                f'The name of active setup={self.pinfo.setup_name} does not match'
-                                f'the name of of setup_args.name={setup_args.name}. '
-                                f'To use this method, activate the desired Setup before editing it. The '
-                                f'setup_args was not used to update the active Setup.'
-                            )
-                            return
-
-                        for key, value in setup_args.items():
-                            if key == "name":
-                                continue  #Checked for above.
-                            if key == "freq_ghz":
-                                if not isinstance(value, float):
-                                    self.logger.warning(
-                                        'The value for freq_ghz should be an float. '
-                                        f'The present value is {value}.')
-                                else:
-                                    self.pinfo.setup.solution_freq = f'{value}GHz'
-                                    continue
-                            if key == 'max_passes':
-                                if not isinstance(value, int):
-                                    self.logger.warning(
-                                        'The value for passes should be an int. '
-                                        f'The present value is {value}.')
-                                else:
-                                    self.pinfo.setup.passes = value
-                                    continue
-                            if key == 'pct_refinement':
-                                if not isinstance(value, int):
-                                    self.logger.warning(
-                                        'The value for pct_refinement should be an int. '
-                                        f'The present value is {value}.')
-                                else:
-                                    self.pinfo.setup.pct_refinement = value
-                                    continue
-                            if key == 'basis_order':
-                                if not isinstance(value, int):
-                                    self.logger.warning(
-                                        'The value for basis_order should be an int. '
-                                        f'The present value is {value}.')
-                                else:
-                                    self.pinfo.setup.basis_order = value
-                                    continue
-
-                            self.design.logger.warning(
-                                f'In setup_args, key={key}, value={value} is not in pinfo.setup, '
-                                'the key/value pair from setup_args not added to Setup in Ansys.'
-                            )
-
-                    else:
-                        self.logger.warning(
-                            'The design does not have solution type as "Driven Modal". The Setup not updated.'
-                        )
-                else:
-                    self.logger.warning(
-                        'A design is not in active project. The Setup not updated.'
-                    )
-            else:
-                self.logger.warning(
-                    "Project not available, have you opened a project? Setup not updated."
-                )
-        else:
-            self.logger.warning(
-                "Have you run connect_ansys()?  "
-                "Cannot find a reference to Ansys in QRenderer. Setup not updated. "
-            )
-
-    def set_mode(self, mode: int, setup_name: str):
-        """Set the eigenmode in pyEPR for a design with solution_type set to
-        Eigenmode.
-
-        Args:
-            mode (int): Identify a mode from 1 to n_modes.
-            setup_name (str): Select a setup from the active design.
-        """
-        if self.pinfo:
-            if self.pinfo.project:
-                if self.pinfo.design:
-                    # double parent, becasue self.pinfo.design does not work
-                    o_desktop = self.pinfo.design.parent.parent._desktop
-                    o_project = o_desktop.SetActiveProject(
-                        self.pinfo.project_name)
-                    o_design = o_project.GetActiveDesign()
-                    if o_design.GetSolutionType() == 'Eigenmode':
-                        # The set_mode() method is in HfssEMDesignSolutions
-                        #  class in pyEPR.
-                        # The class HfssEMDesignSolutions is instantiated by
-                        #  get_setup() and create_em_setup().
-                        setup = self.pinfo.get_setup(setup_name)
-                        if 0 < int(mode) <= int(setup.n_modes):
-                            setup_solutions = setup.get_solutions()
-                            if setup_solutions:
-                                setup_solutions.set_mode(mode)
-                            else:
-                                self.logger.warning(
-                                    'Not able to get setup_solutions, '
-                                    'the mode was not set.')
-                        else:
-                            self.logger.warning(
-                                f'The requested mode={mode} is not a valid '
-                                f'(1 to {setup.n_modes}) selection. '
-                                'The mode was not set.')
-                    else:
-                        self.logger.warning(
-                            'The design does not have solution type as '
-                            '"Eigenmode". The mode was not set.')
-                else:
-                    self.logger.warning('A design is not in active project. '
-                                        'The mode was not set.')
-            else:
-                self.logger.warning(
-                    "Project not available, have you opened a project? "
-                    "The mode was not set.")
-        else:
-            self.logger.warning(
-                "Have you run connect_ansys()?  "
-                "Cannot find a reference to Ansys in QRenderer.  "
-                "The mode was not set.")
-
-    def analyze_setup(self, setup_name: str):
-        """Run a specific solution setup in Ansys HFSS.
-
-        Args:
-            setup_name (str): Name of setup.
-        """
-        if self.pinfo:
-            setup = self.pinfo.get_setup(setup_name)
-            setup.analyze(setup_name)
-
-    def add_sweep(self,
-                  setup_name="Setup",
-                  start_ghz=2.0,
-                  stop_ghz=8.0,
-                  count=101,
-                  step_ghz=None,
-                  name="Sweep",
-                  type="Fast",
-                  save_fields=False):
-        """Add a frequency sweep to a driven modal setup.
-
-        Args:
-            setup_name (str, optional): Name of driven modal simulation setup.
-                                    Defaults to "Setup".
-            start_ghz (float, optional): Starting frequency of sweep in GHz.
-                                    Defaults to 2.0.
-            stop_ghz (float, optional): Ending frequency of sweep in GHz.
-                                    Defaults to 8.0.
-            count (int, optional): Total number of frequencies.
-                                    Defaults to 101.
-            step_ghz (float, optional): Difference between adjacent
-                                    frequencies. Defaults to None.
-            name (str, optional): Name of sweep. Defaults to "Sweep".
-            type (str, optional): Type of sweep. Defaults to "Fast".
-            save_fields (bool, optional): Whether or not to save fields.
-                                Defaults to False.
-        """
-        if self.pinfo:
-            setup = self.pinfo.get_setup(setup_name)
-            return setup.insert_sweep(start_ghz=start_ghz,
-                                      stop_ghz=stop_ghz,
-                                      count=count,
-                                      step_ghz=step_ghz,
-                                      name=name,
-                                      type=type,
-                                      save_fields=save_fields)
-
-    def analyze_sweep(self, sweep_name: str, setup_name: str):
-        """Analyze a single sweep within the setup.
-
-        Args:
-            sweep_name (str): Name of sweep to analyze.
-            setup_name (str): Name of setup to analyze.
-        """
-        if self.pinfo:
-            setup = self.pinfo.get_setup(setup_name)
-            sweep = setup.get_sweep(sweep_name)
-            sweep.analyze_sweep()
-            self.current_sweep = sweep
-
-    def get_params(self, param_name: Union[list, None] = None):
-        """Get one or more parameters (S, Y, or Z) as a function of frequency.
-
-        Args:
-            param_name (Union[list, None], optional): Parameters to obtain. Defaults to None.
-        """
-        if self.current_sweep:
-
-            freqs, Pcurves = self.current_sweep.get_network_data(param_name)
-            Pparams = pd.DataFrame(Pcurves,
-                                   columns=freqs / 1e9,
-                                   index=param_name).transpose()
-        return freqs, Pcurves, Pparams
-
-    # yapf: disable
-    def get_all_Pparms_matrices(self, matrix_size: int) -> Tuple[
-            Union[pd.core.frame.DataFrame, None],
-            Union[pd.core.frame.DataFrame, None],
-            Union[pd.core.frame.DataFrame, None]]:
-        #yapf: enable
-        '''
-        S = scattering matrix, Y = Admittance, Z= impedance.
-
-        matrix_size should be 1 or larger.
-        This method will get the entire Scattering matrix based on matrix_size.
-
-        Example:'S21'
-        S matrix: SAB means, excite B, measure A
-        '''
-        s_param_name = []
-        y_param_name = []
-        z_param_name = []
-        if matrix_size < 1:
-            return None, None, None
-        for excite in range(1, matrix_size + 1):
-            for measure in range(1, matrix_size + 1):
-                s_param_name.append(f'S{measure}{excite}')
-                y_param_name.append(f'Y{measure}{excite}')
-                z_param_name.append(f'Z{measure}{excite}')
-        dummy_freqs, dummy_Pcurves, S_Pparams = self.get_params(s_param_name)
-        dummy_freqs, dummy_Pcurves, Y_Pparams = self.get_params(y_param_name)
-        dummy_freqs, dummy_Pcurves, Z_Pparams = self.get_params(z_param_name)
-
-        return S_Pparams, Y_Pparams, Z_Pparams
-
-    def plot_params(self, param_name: Union[list, None] = None):
-        """Plot one or more parameters (S, Y, or Z) as a function of frequency.
-        S = scattering matrix, Y = Admittance, Z= impedance.
-
-        Args:
-            param_name (Union[list, None], optional): Parameters to plot. Defaults to None.
-        """
-        freqs, Pcurves, Pparams = self.get_params(param_name)
-        if Pparams is not None:
-            fig, axs = plt.subplots(1, 2, figsize=(10, 6))
-            Pparams.apply(lambda x: 20 * np.log10(np.abs(x))).plot(ax=axs[0])
-            axs[0].set_xlabel('Frequency (GHz)')
-            axs[0].set_ylabel('Amplitude (dB)')
-            Pparams.apply(lambda x: np.angle(x)).plot(ax=axs[1])
-            axs[1].set_xlabel('Frequency (GHz)')
-            axs[1].set_ylabel('Phase (rad.)')
-            for ax in axs:
-                ax.autoscale()
-        return Pparams, fig
-
-    def distributed_analysis(self):
-        """Returns class containing info on Hamiltonian parameters from HFSS
-        simulation.
-
-        Returns:
-            DistributedAnalysis: A  class from pyEPR which does DISTRIBUTED ANALYSIS of layout
-            and microwave results.  It is the main computation class & interface with HFSS.
-            This class defines a DistributedAnalysis object which calculates
-            and saves Hamiltonian parameters from an HFSS simulation.
-            It allows one to calculate dissipation.
-        """
-        if self.pinfo:
-            return epr.DistributedAnalysis(self.pinfo)
-
-    def get_convergences(self, variation: str = None):
-        """Get convergence for convergence_t, convergence_f, and text from GUI for solution data.
-
-        Args:
-            variation (str, optional):  Information from pyEPR; variation should be in the form
-            variation = "scale_factor='1.2001'". Defaults to None.
-
-        Returns:
-            tuple[pandas.DataFrame, pandas.DataFrame, str]:
-            1st DataFrame: Convergence_t
-            2nd DataFrame: Convergence_f
-            3rd str: Text from GUI of solution data.
-        """
-        if self.pinfo:
-            convergence_t, text = self.pinfo.setup.get_convergence(variation)
-            convergence_f = self.get_f_convergence([])  # TODO; Fix variation []
-            return convergence_t, convergence_f, text
-
-    def plot_convergences(self,
-                          variation: str = None,
-                          fig: mpl.figure.Figure = None):
-        """
-        (deprecated) use EPRanalysis.plot_convergences()
-        """
-        print(
-            'This method is deprecated. Change your scripts to use the equivalent method '
-            'plot_convergence() that has been moved inside the EPRanalysis class.'
-        )
-
-    def get_f_convergence(self, variation: str = None, save_csv: bool = True):
-        """Create a report inside HFSS to plot the converge of frequency and
-        style it. Saves report to csv file.
-
-        .. code-block:: text
-
-                re(Mode(1)) [g]	re(Mode(2)) [g]	re(Mode(3)) [g]
-            Pass []
-            1	4.643101	4.944204	5.586289
-            2	5.114490	5.505828	6.242423
-            3	5.278594	5.604426	6.296777
-
-        Args:
-            variation ('str', optional): Information from pyEPR; variation should be in the form
-                variation = "scale_factor='1.2001'". Defaults to None.
-            save_csv (bool, optional): Save to file? Defaults to True.
-
-        Returns:
-            pd.DataFrame: Returns a convergence vs pass number of the eigenemode frequencies.
-        """
-        if self.pinfo:
-            o_design = self.pinfo.design
-            setup = self.pinfo.setup
-
-            if not o_design.solution_type == 'Eigenmode':
-                return None
-
-            report = o_design._reporter  # reporter OModule for Ansys
-
-            # Create report
-            n_modes = int(setup.n_modes)
-            ycomp = [f"re(Mode({i}))" for i in range(1, 1 + n_modes)]
-
-            params = ["Pass:=", ["All"]] + variation
-            report_name = "Freq. vs. pass"
-            if report_name in report.GetAllReportNames():
-                report.DeleteReports([report_name])
-
-            solutions = setup.get_solutions()
-            solutions.create_report(report_name,
-                                    "Pass",
-                                    ycomp,
-                                    params,
-                                    pass_name='AdaptivePass')
-
-            # Properties of lines
-            curves = [
-                f"{report_name}:re(Mode({i})):Curve1"
-                for i in range(1, 1 + n_modes)
-            ]
-            set_property(report, 'Attributes', curves, 'Line Width', 3)
-            set_property(report, 'Scaling', f"{report_name}:AxisY1",
-                         'Auto Units', False)
-            set_property(report, 'Scaling', f"{report_name}:AxisY1", 'Units',
-                         'g')
-            set_property(report, 'Legend', f"{report_name}:Legend",
-                         'Show Solution Name', False)
-
-            if save_csv:  # Save
-                try:
-                    path = Path().absolute(
-                    ) / 'hfss_eig_f_convergence.csv'  # TODO: Determine better path
-                    report.ExportToFile(report_name, path)
-                    self.logger.info(f'Saved convergences to {path}')
-                    return pd.read_csv(path, index_col=0)
-                except Exception as e:
-                    self.logger.error(
-                        f"Error could not save and export hfss plot to {path}.\
-                                Is the plot made in HFSS with the correct name.\
-                                Check the HFSS error window. \t Error =  {e}")
-
-            return None
+# -*- coding: utf-8 -*-
+
+# This code is part of Qiskit.
+#
+# (C) Copyright IBM 2017, 2021.
+#
+# This code is licensed under the Apache License, Version 2.0. You may
+# obtain a copy of this license in the LICENSE.txt file in the root directory
+# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+#
+# Any modifications or derivative works of this code must retain this
+# copyright notice, and modified files need to carry a notice indicating
+# that they have been altered from the originals.
+
+# modified by Chalmers/SK/20210621
+# modified by Samarth Hawaldar
+
+from collections import defaultdict
+from pathlib import Path
+from typing import Union, Tuple
+
+import logging
+import matplotlib as mpl
+import matplotlib.pyplot as plt
+import numpy as np
+import pandas as pd
+import pyEPR as epr
+from pyEPR.ansys import set_property, parse_units
+
+from qiskit_metal import Dict
+from qiskit_metal.draw.utility import to_vec3D
+from qiskit_metal.renderers.renderer_ansys.ansys_renderer import (
+    QAnsysRenderer, get_clean_name)
+
+
+class QHFSSRenderer(QAnsysRenderer):
+    """Subclass of QAnsysRenderer for running HFSS simulations.
+
+    QAnsysRenderer Default Options:
+        * Lj: '10nH' -- Lj has units of nanoHenries (nH)
+        * Cj: 0 -- Cj *must* be 0 for pyEPR analysis! Cj has units of femtofarads (fF)
+        * _Rj: 0 -- _Rj *must* be 0 for pyEPR analysis! _Rj has units of Ohms
+        * max_mesh_length_jj: '7um' -- Maximum mesh length for Josephson junction elements
+        * max_mesh_length_port: '7um' -- Maximum mesh length for Ports in Eigenmode Simulations
+        * project_path: None -- Default project path; if None --> get active
+        * project_name: None -- Default project name
+        * design_name: None -- Default design name
+        * ansys_file_extension: '.aedt' -- Ansys file extension for 2016 version and newer
+        * x_buffer_width_mm: 0.2 -- Buffer between max/min x and edge of ground plane, in mm
+        * y_buffer_width_mm: 0.2 -- Buffer between max/min y and edge of ground plane, in mm
+    """
+
+    name = 'hfss'
+    """Name"""
+
+    hfss_options = Dict(
+        port_inductor_gap=
+        '10um'  # spacing between port and inductor if junction is drawn both ways
+    )
+    """HFSS Options"""
+
+    def __init__(self, design: 'QDesign', initiate=True, options: Dict = None):
+        """Create a QRenderer for HFSS simulations, subclassed from QAnsysRenderer.
+
+        Args:
+            design (QDesign): Use QGeometry within QDesign to obtain elements for Ansys.
+            initiate (bool, optional): True to initiate the renderer. Defaults to True.
+            options (Dict, optional):  Used to override all options. Defaults to None.
+        """
+        super().__init__(design=design, initiate=initiate, options=options)
+
+        self.current_sweep = None
+
+        QHFSSRenderer.load()
+
+    def render_design(self,
+                      selection: Union[list, None] = None,
+                      open_pins: Union[list, None] = None,
+                      port_list: Union[list, None] = None,
+                      jj_to_port: Union[list, None] = None,
+                      ignored_jjs: Union[list, None] = None,
+                      box_plus_buffer: bool = True):
+        """Initiate rendering of components in design contained in selection,
+        assuming they're valid. Components are rendered before the chips they
+        reside on, and subtraction of negative shapes is performed at the very
+        end. Add the metalize() method here to turn objects in
+        self.assign_perfE (see init in QAnsysRenderer class) into perfect
+        electrical conductors. Create lumped ports as needed.
+
+        First obtain a list of IDs of components to render and a corresponding
+        case, denoted by self.qcomp_ids and self.case, respectively. If
+        self.case == 1, all components in QDesign are to be rendered.  If
+        self.case == 0, a strict subset of components in QDesign are to be
+        rendered. Otherwise, if self.case == 2, one or more component names in
+        selection cannot be found in QDesign.
+
+        Among the components selected for export, there may or may not be
+        unused (unconnected) pins. The second parameter, open_pins, contains
+        tuples of the form (component_name, pin_name) that specify exactly
+        which pins should be open rather than shorted during the simulation.
+        Both the component and pin name must be specified because the latter
+        could be shared by multiple components. All pins in this list are
+        rendered with an additional endcap in the form of a rectangular
+        cutout, to be subtracted from its respective plane.
+
+        In driven modal solutions, the Ansys design must include one or more
+        ports. This is done by adding all port designations and their
+        respective impedances in Ohms as (qcomp, pin, impedance) to port_list.
+        Note that an open endcap must separate the two sides of each pin before
+        inserting a lumped port in between, so behind the scenes all pins in
+        port_list are also added to open_pins. Practically, however, port_list
+        and open_pins are inputted as mutually exclusive lists.
+
+        Also in driven modal solutions, one may want to render junctions as
+        lumped ports and/or inductors, or omit them altogether. To do so,
+        tuples of the form (component_name, element_name, impedance, draw_ind)
+        are added to the list jj_to_port. For example,
+        ('Q1', 'rect_jj', 70, True) indicates that rect_jj of component Q1 is
+        to be rendered as both a lumped port with an impedance of 70 Ohms as
+        well as an inductor whose properties are given in default_options.
+        Replacing the last entry of this 4-element tuple with False would
+        indicate that only the port is to be drawn, not the inductor.
+        Alternatively for driven modal solutions, one may want to disregard
+        select junctions in the Metal design altogether to simulate the
+        capacitive effect while keeping the qubit in an "off" state. Such
+        junctions are specified in the form (component_name, element_name)
+        in the list ignored_jjs.
+
+        The final parameter, box_plus_buffer, determines how the chip is drawn.
+        When set to True, it takes the minimum rectangular bounding box of all
+        rendered components and adds a buffer of x_buffer_width_mm and
+        y_buffer_width_mm horizontally and vertically, respectively, to the
+        chip size. The center of the chip lies at the midpoint x/y coordinates
+        of the minimum rectangular bounding box and may change depending on
+        which components are rendered and how they're positioned. If
+        box_plus_buffer is False, however, the chip position and dimensions
+        are taken from the chip info dictionary found in self.design,
+        irrespective of what's being rendered. While this latter option is
+        faster because it doesn't require calculating a bounding box, it runs
+        the risk of rendered components being too close to the edge of the chip
+        or even falling outside its boundaries.
+
+        Args:
+            selection (Union[list, None], optional): List of components to
+                                        render. Defaults to None.
+            open_pins (Union[list, None], optional): List of tuples of pins
+                                        that are open. Defaults to None.
+            port_list (Union[list, None], optional): List of tuples of pins to
+                                        be rendered as ports. Defaults to None.
+            jj_to_port (Union[list, None], optional): List of tuples of jj's to
+                                        be rendered as ports. Defaults to None.
+            ignored_jjs (Union[list, None], optional): List of tuples of jj's
+                                        that shouldn't be rendered.
+                                        Defaults to None.
+            box_plus_buffer (bool): Either calculate a bounding box based on
+                                        the location of rendered geometries
+                                        or use chip size from design class.
+        """
+        self.qcomp_ids, self.case = self.get_unique_component_ids(selection)
+
+        if self.case == 2:
+            self.logger.warning(
+                'Unable to proceed with rendering. Please check selection.')
+            return
+
+        self.chip_subtract_dict = defaultdict(set)
+        self.assign_perfE = []
+        self.assign_mesh = []
+        self.assign_port_mesh = []
+        self.jj_lumped_ports = {}
+        self.jj_to_ignore = set()
+
+        if jj_to_port:
+            self.jj_lumped_ports = {
+                (qcomp, elt): [impedance, draw_ind]
+                for qcomp, elt, impedance, draw_ind in jj_to_port
+            }
+
+        if ignored_jjs:
+            self.jj_to_ignore = {(qcomp, qelt) for qcomp, qelt in ignored_jjs}
+
+        self.render_tables()
+        if port_list:
+            self.add_endcaps(open_pins +
+                             [(qcomp, pin) for qcomp, pin, _ in port_list])
+        else:
+            self.add_endcaps(open_pins)
+
+        self.render_chips(box_plus_buffer=box_plus_buffer)
+        self.subtract_from_ground()
+        if port_list:
+            self.create_ports(port_list)
+        self.add_mesh()
+        self.metallize()
+
+    def create_ports(self, port_list: list):
+        """Add ports and their respective impedances in Ohms to designated pins
+        in port_list. Port_list is formatted as [(qcomp_0, pin_0, impedance_0),
+        (qcomp_1, pin_1, impedance_1), ...].
+
+        Args:
+            port_list (list): List of tuples of pins to be rendered as ports.
+        """
+        for qcomp, pin, impedance in port_list:
+            port_name = f'Port_{qcomp}_{pin}'
+            pdict = self.design.components[qcomp].pins[pin]
+            midpt, gap_size, norm_vec, width = pdict['middle'], pdict['gap'], \
+                                               pdict['normal'], pdict['width']
+            width = parse_units(width)
+            endpoints = parse_units([midpt, midpt + gap_size * norm_vec])
+            endpoints_3d = to_vec3D(endpoints, 0)  # Set z height to 0
+            x0, y0 = endpoints_3d[0][:2]
+            x1, y1 = endpoints_3d[1][:2]
+            if abs(y1 - y0) > abs(x1 - x0):
+                # Junction runs vertically up/down
+                x_min, x_max = x0 - width / 2, x0 + width / 2
+                y_min, y_max = min(y0, y1), max(y0, y1)
+            else:
+                # Junction runs horizontally left/right
+                x_min, x_max = min(x0, x1), max(x0, x1)
+                y_min, y_max = y0 - width / 2, y0 + width / 2
+
+            # Draw rectangle
+            self.logger.debug(f'Drawing a rectangle: {port_name}')
+            poly_ansys = self.modeler.draw_rect_corner([x_min, y_min, 0],
+                                                       x_max - x_min,
+                                                       y_max - y_min, 0,
+                                                       **dict(transparency=0.0))
+            axis = 'x' if abs(x1 - x0) > abs(y1 - y0) else 'y'
+
+            # Add port. Adds RLC port if it is an eigenmode simulation. Otherwise, adds a lumped port.
+            if self.solution_type != 'eigenmode':
+                poly_ansys.make_lumped_port(axis,
+                                            z0=str(impedance) + 'ohm',
+                                            name=f'LumpPort_{qcomp}_{pin}')
+                self.modeler.rename_obj(poly_ansys, port_name)
+            else:
+                poly_ansys.make_rlc_boundary(axis,
+                                             r=str(impedance) + 'ohm',
+                                             name=f'RLCBoundary_{qcomp}_{pin}')
+                self.modeler.rename_obj(poly_ansys, port_name)
+                self.assign_port_mesh.append(port_name)
+
+            # Draw line
+            lump_line = self.modeler.draw_polyline(
+                [endpoints_3d[0], endpoints_3d[1]],
+                closed=False,
+                **dict(color=(128, 0, 128)))
+            lump_line = lump_line.rename(f'voltage_line_{port_name}')
+            lump_line.show_direction = True
+
+    def render_element_junction(self, qgeom: pd.Series):
+        """
+        Render a Josephson junction depending on the solution type.
+
+        If in HFSS eigenmode, junctions are rendered as inductors consisting of
+            1. A rectangle of length pad_gap and width inductor_width. Defines lumped element RLC
+               boundary condition.
+            2. A line that is later used to calculate the voltage in post-processing analysis.
+
+        If in HFSS driven modal, junctions can be inductors, lumped ports, both inductors
+        and lumped ports, or omitted altogether. Ports are characterized by an impedance
+        value given in the list jj_to_port when render_design() is called.
+
+        Args:
+            qgeom (pd.Series): GeoSeries of element properties.
+        """
+        qcomp = self.design._components[qgeom['component']].name
+        qc_elt = get_clean_name(qgeom['name'])
+
+        if (qcomp, qc_elt) not in self.jj_to_ignore:
+            qc_shapely = qgeom.geometry
+            qc_chip_z = parse_units(self.design.get_chip_z(qgeom.chip))
+            qc_width = parse_units(qgeom.width)
+
+            endpoints = parse_units(list(qc_shapely.coords))
+            endpoints_3d = to_vec3D(endpoints, qc_chip_z)
+            x0, y0, z0 = endpoints_3d[0]
+            x1, y1, z0 = endpoints_3d[1]
+            if abs(y1 - y0) > abs(x1 - x0):
+                # Junction runs vertically up/down
+                axis = 'y'
+                x_min, x_max = x0 - qc_width / 2, x0 + qc_width / 2
+                y_min, y_max = min(y0, y1), max(y0, y1)
+            else:
+                # Junction runs horizontally left/right
+                axis = 'x'
+                x_min, x_max = min(x0, x1), max(x0, x1)
+                y_min, y_max = y0 - qc_width / 2, y0 + qc_width / 2
+
+            if (qcomp, qc_elt) in self.jj_lumped_ports:
+                if self.jj_lumped_ports[(qcomp, qc_elt)][1]:
+                    # Draw both port and inductor side by side with small gap in between
+                    gap = parse_units(self.hfss_options['port_inductor_gap'])
+                    x_mid, y_mid = (x_min + x_max) / 2, (y_min + y_max) / 2
+                    if axis == 'x':
+                        y_mid_hi = y_mid + gap / 2
+                        y_mid_lo = y_mid - gap / 2
+                        self.render_junction_port(qgeom, x_min, x_max, y_mid_hi,
+                                                  y_max, qc_chip_z, axis)
+                        self.render_junction_inductor(qgeom, x_min, x_max,
+                                                      y_min, y_mid_lo,
+                                                      qc_chip_z, axis)
+                    elif axis == 'y':
+                        x_mid_lo = x_mid - gap / 2
+                        x_mid_hi = x_mid + gap / 2
+                        self.render_junction_port(qgeom, x_mid_hi, x_max, y_min,
+                                                  y_max, qc_chip_z, axis)
+                        self.render_junction_inductor(qgeom, x_min, x_mid_lo,
+                                                      y_min, y_max, qc_chip_z,
+                                                      axis)
+                else:
+                    # Only draw port
+                    self.render_junction_port(qgeom, x_min, x_max, y_min, y_max,
+                                              qc_chip_z, axis)
+            else:
+                # Only draw inductor
+                self.render_junction_inductor(qgeom, x_min, x_max, y_min, y_max,
+                                              qc_chip_z, axis)
+
+    def render_junction_port(self, qgeom: pd.Series, xmin: float, xmax: float,
+                             ymin: float, ymax: float, z: float, axis: str):
+        """Render a junction as a port with a bounding box given by xmin/xmax
+        and ymin/ymax, a height z, and a horizontal or vertical axis.
+
+        Args:
+            qgeom (pd.Series): GeoSeries of element properties.
+            xmin (float): Smallest x coordinate
+            xmax (float): Largest x coordinate
+            ymin (float): Smallest y coordinate
+            ymax (float): Largest y coordinate
+            z (float): z coordinate
+            axis (str): Orientation, either 'x' or 'y'
+        """
+        ansys_options = dict(transparency=0.0)
+        qcomp = self.design._components[qgeom['component']].name
+        qc_elt = get_clean_name(qgeom['name'])
+        port_name = f'Port_{qcomp}_{qc_elt}'
+        impedance = self.jj_lumped_ports[(qcomp, qc_elt)][0]
+        # Draw rectangle for lumped port.
+        self.logger.debug(f'Drawing a rectangle: {port_name}')
+        poly_ansys = self.modeler.draw_rect_corner([xmin, ymin, z], xmax - xmin,
+                                                   ymax - ymin, z,
+                                                   **ansys_options)
+        poly_ansys.make_lumped_port(axis,
+                                    z0=str(impedance) + 'ohm',
+                                    name=f'LumpPort_{qcomp}_{qc_elt}')
+        self.modeler.rename_obj(poly_ansys, port_name)
+        # Draw line for lumped port.
+        if axis == 'x':
+            ymid = (ymin + ymax) / 2
+            start, end = [xmin, ymid, z], [xmax, ymid, z]
+        elif axis == 'y':
+            xmid = (xmin + xmax) / 2
+            start, end = [xmid, ymin, z], [xmid, ymax, z]
+        lump_line = self.modeler.draw_polyline([start, end],
+                                               closed=False,
+                                               **dict(color=(128, 0, 128)))
+        lump_line = lump_line.rename(f'voltage_line_{port_name}')
+        lump_line.show_direction = True
+
+    def render_junction_inductor(self, qgeom: pd.Series, xmin: float,
+                                 xmax: float, ymin: float, ymax: float,
+                                 z: float, axis: str):
+        """Render a junction as an inductor with a bounding box given by
+        xmin/xmax and ymin/ymax, a height z, and a horizontal or vertical axis.
+
+        Args:
+            qgeom (pd.Series): GeoSeries of element properties.
+            xmin (float): Smallest x coordinate
+            xmax (float): Largest x coordinate
+            ymin (float): Smallest y coordinate
+            ymax (float): Largest y coordinate
+            z (float): z coordinate
+            axis (str): Orientation, either 'x' or 'y'
+        """
+        ansys_options = dict(transparency=0.0)
+        qcomp = self.design._components[qgeom['component']].name
+        qc_elt = get_clean_name(qgeom['name'])
+        qc_name = 'Lj_' + qcomp
+        inductor_name = f'{qc_name}{QAnsysRenderer.NAME_DELIM}{qc_elt}'
+        # Draw rectangle for inductor.
+        self.logger.debug(f'Drawing a rectangle: {inductor_name}')
+        poly_ansys = self.modeler.draw_rect_corner([xmin, ymin, z], xmax - xmin,
+                                                   ymax - ymin, z,
+                                                   **ansys_options)
+        poly_ansys.make_rlc_boundary(axis,
+                                     l=qgeom['hfss_inductance'],
+                                     c=qgeom['hfss_capacitance'],
+                                     r=qgeom['hfss_resistance'],
+                                     name='Lj_' + inductor_name)
+        self.modeler.rename_obj(poly_ansys, 'JJ_rect_' + inductor_name)
+        self.assign_mesh.append('JJ_rect_' + inductor_name)
+        # Draw line for inductor.
+        if axis == 'x':
+            ymid = (ymin + ymax) / 2
+            start, end = [xmin, ymid, z], [xmax, ymid, z]
+        elif axis == 'y':
+            xmid = (xmin + xmax) / 2
+            start, end = [xmid, ymin, z], [xmid, ymax, z]
+        induc_line = self.modeler.draw_polyline([start, end],
+                                                closed=False,
+                                                **dict(color=(128, 0, 128)))
+        induc_line = induc_line.rename('JJ_' + inductor_name + '_')
+        induc_line.show_direction = True
+
+    def metallize(self):
+        """Assign metallic property to all shapes in self.assign_perfE list."""
+        self.modeler.assign_perfect_E(self.assign_perfE)
+
+    def add_drivenmodal_design(self, name: str, connect: bool = True):
+        """
+        (deprecated) use new_ansys_design()
+        """
+        self.logger.warning(
+            'This method is deprecated. Change your scripts to use new_ansys_design()'
+        )
+        self.new_ansys_design(name, 'drivenmodal', connect)
+
+    def activate_drivenmodal_design(self, name: str = "MetalHFSSDrivenModal"):
+        """
+        (deprecated) use activate_ansys_design()
+        """
+        self.logger.warning(
+            'This method is deprecated. Change your scripts to use activate_ansys_design()'
+        )
+        self.activate_ansys_design(name, 'drivenmodal')
+
+    def activate_drivenmodal_setup(self, setup_name_activate: str = None):
+        """
+        (deprecated) use activate_ansys_setup()
+        """
+        self.logger.warning(
+            'This method is deprecated. Change your scripts to use activate_ansys_setup()'
+        )
+        self.activate_ansys_setup(setup_name_activate)
+
+    def add_drivenmodal_setup(self,
+                              name: str = None,
+                              freq_ghz: int = None,
+                              max_delta_s: float = None,
+                              max_passes: int = None,
+                              min_passes: int = None,
+                              min_converged: int = None,
+                              pct_refinement: int = None,
+                              basis_order: int = None,
+                              *args,
+                              **kwargs):
+        """Create a solution setup in Ansys HFSS Driven Modal. If user does
+        not provide arguments, they will be obtained from default_setup dict.
+
+        Args:
+            name (str, optional): Name of driven modal setup. Defaults to None.
+            freq_ghz (int, optional): Frequency in GHz. Defaults to None.
+            max_delta_s (float, optional): Absolute value of maximum difference in scattering parameter S. Defaults to None.
+            max_passes (int, optional): Maximum number of passes. Defaults to None.
+            min_passes (int, optional): Minimum number of passes. Defaults to None.
+            min_converged (int, optional): Minimum number of converged passes. Defaults to None.
+            pct_refinement (int, optional): Percent refinement. Defaults to None.
+            basis_order (int, optional): Basis order. Defaults to None.
+        """
+        dsu = self.default_setup.drivenmodal
+
+        if not name:
+            name = self.parse_value(dsu['name'])
+        if not freq_ghz:
+            freq_ghz = int(self.parse_value(dsu['freq_ghz']))
+        if not max_delta_s:
+            max_delta_s = float(self.parse_value(dsu['max_delta_s']))
+        if not max_passes:
+            max_passes = int(self.parse_value(dsu['max_passes']))
+        if not min_passes:
+            min_passes = int(self.parse_value(dsu['min_passes']))
+        if not min_converged:
+            min_converged = int(self.parse_value(dsu['min_converged']))
+        if not pct_refinement:
+            pct_refinement = int(self.parse_value(dsu['pct_refinement']))
+        if not basis_order:
+            basis_order = int(self.parse_value(dsu['basis_order']))
+
+        if self.pinfo:
+            if self.pinfo.design:
+                return self.pinfo.design.create_dm_setup(
+                    freq_ghz=freq_ghz,
+                    name=name,
+                    max_delta_s=max_delta_s,
+                    max_passes=max_passes,
+                    min_passes=min_passes,
+                    min_converged=min_converged,
+                    pct_refinement=pct_refinement,
+                    basis_order=basis_order)
+
+    def add_eigenmode_design(self, name: str, connect: bool = True):
+        """
+        (deprecated) use new_ansys_design()
+        """
+        self.logger.warning(
+            'This method is deprecated. Change your scripts to use new_ansys_design()'
+        )
+        self.new_ansys_design(name, 'eigenmode', connect)
+
+    def activate_eigenmode_design(self, name: str = "MetalHFSSEigenmode"):
+        """
+        (deprecated) use activate_ansys_design()
+        """
+        self.logger.warning(
+            'This method is deprecated. Change your scripts to use activate_ansys_design()'
+        )
+        self.activate_ansys_design(name, 'eigenmode')
+
+    def activate_eigenmode_setup(self, setup_name_activate: str = None):
+        """
+        (deprecated) use activate_ansys_setup()
+        """
+        self.logger.warning(
+            'This method is deprecated. Change your scripts to use activate_ansys_setup()'
+        )
+        self.activate_ansys_setup(setup_name_activate)
+
+    def add_eigenmode_setup(self,
+                            name: str = None,
+                            min_freq_ghz: int = None,
+                            n_modes: int = None,
+                            max_delta_f: float = None,
+                            max_passes: int = None,
+                            min_passes: int = None,
+                            min_converged: int = None,
+                            pct_refinement: int = None,
+                            basis_order: int = None,
+                            *args,
+                            **kwargs):
+        """Create a solution setup in Ansys HFSS Eigenmode.  If user does not
+        provide arguments, they will be obtained from hfss_options dict.
+
+        Args:
+            name (str, optional): Name of eigenmode setup. Defaults to "Setup".
+            min_freq_ghz (int, optional): Minimum frequency in GHz. Defaults to 1.
+            n_modes (int, optional): Number of modes. Defaults to 1.
+            max_delta_f (float, optional): Maximum difference in freq between consecutive passes. Defaults to 0.5.
+            max_passes (int, optional): Maximum number of passes. Defaults to 10.
+            min_passes (int, optional): Minimum number of passes. Defaults to 1.
+            min_converged (int, optional): Minimum number of converged passes. Defaults to 1.
+            pct_refinement (int, optional): Percent refinement. Defaults to 30.
+            basis_order (int, optional): Basis order. Defaults to -1.
+        """
+        esu = self.default_setup.eigenmode
+
+        if not name:
+            name = self.parse_value(esu['name'])
+        if not min_freq_ghz:
+            min_freq_ghz = int(self.parse_value(esu['min_freq_ghz']))
+        if not n_modes:
+            n_modes = int(self.parse_value(esu['n_modes']))
+        if not max_delta_f:
+            max_delta_f = float(self.parse_value(esu['max_delta_f']))
+        if not max_passes:
+            max_passes = int(self.parse_value(esu['max_passes']))
+        if not min_passes:
+            min_passes = int(self.parse_value(esu['min_passes']))
+        if not min_converged:
+            min_converged = int(self.parse_value(esu['min_converged']))
+        if not pct_refinement:
+            pct_refinement = int(self.parse_value(esu['pct_refinement']))
+        if not basis_order:
+            basis_order = int(self.parse_value(esu['basis_order']))
+
+        if self.pinfo:
+            if self.pinfo.design:
+                return self.pinfo.design.create_em_setup(
+                    name=name,
+                    min_freq_ghz=min_freq_ghz,
+                    n_modes=n_modes,
+                    max_delta_f=max_delta_f,
+                    max_passes=max_passes,
+                    min_passes=min_passes,
+                    min_converged=min_converged,
+                    pct_refinement=pct_refinement,
+                    basis_order=basis_order)
+
+    def edit_eigenmode_setup(self, setup_args: Dict):
+        """User can pass key/values to edit the setup for active eigenmode setup.
+
+        Args:
+            setup_args (Dict): a Dict with possible keys/values.
+
+        **setup_args** dict contents:
+            * name (str, optional): Name of eigenmode setup. Defaults to "Setup".
+            * min_freq_ghz (int, optional): Minimum frequency in GHz. Defaults to 1.
+            * n_modes (int, optional): Number of modes. Defaults to 1.
+            * max_delta_f (float, optional): Maximum difference in freq between consecutive passes. Defaults to 0.5.
+            * max_passes (int, optional): Maximum number of passes. Defaults to 10.
+            * pct_refinement (int, optional): Percent refinement. Defaults to 30.
+            * basis_order (int, optional): Basis order. Defaults to -1.
+
+            Note, that these two are currently NOT implemented:
+            Ansys API named EditSetup not documented for HFSS, and
+            self.pinfo.setup does not have all the property variables used for Setup.
+            * min_passes (int, optional): Minimum number of passes. Defaults to 1.
+            * min_converged (int, optional): Minimum number of converged passes. Defaults to 1.
+        """
+
+        if self.pinfo:
+            if self.pinfo.project:
+                if self.pinfo.design:
+                    if self.pinfo.design.solution_type == 'Eigenmode':
+                        if self.pinfo.setup_name != setup_args.name:
+                            self.design.logger.warning(
+                                f'The name of active setup={self.pinfo.setup_name} does not match'
+                                f'the name of of setup_args.name={setup_args.name}. '
+                                f'To use this method, activate the desired Setup before editing it. The '
+                                f'setup_args was not used to update the active Setup.'
+                            )
+                            return
+
+                        for key, value in setup_args.items():
+                            if key == "name":
+                                continue  #Checked for above.
+                            if key == "n_modes":
+                                #EditSetup  not documented, this is just attempt to use.
+                                #args_editsetup = ["NAME:" + setup_args.name,["NumModes:=", setup_args.n_modes]]
+                                #self.pinfo.setup._setup_module.EditSetup([setup_args.name, args_editsetup])
+                                if value < 0 or value > 20 or not isinstance(
+                                        value, int):
+                                    self.logger.warning(
+                                        f'Value of n_modes={value} must be integer from 1 to 20.'
+                                    )
+                                else:
+                                    self.pinfo.setup.n_modes = value
+                                    continue
+                            if key == "min_freq_ghz":
+                                if not isinstance(value, int):
+                                    self.logger.warning(
+                                        'The value for min_freq_ghz should be an int. '
+                                        f'The present value is {value}.')
+                                else:
+                                    self.pinfo.setup.min_freq = f'{value}GHz'
+                                    continue
+                            if key == 'max_delta_f':
+                                if not isinstance(value, float):
+                                    self.logger.warning(
+                                        'The value for max_delta_f should be float. '
+                                        f'The present value is {value}.')
+                                else:
+                                    self.pinfo.setup.delta_f = value
+                                    continue
+                            if key == 'max_passes':
+                                if not isinstance(value, int):
+                                    self.logger.warning(
+                                        'The value for max_passes should be an int. '
+                                        f'The present value is {value}.')
+                                else:
+                                    self.pinfo.setup.passes = value
+                                    continue
+                            if key == 'pct_refinement':
+                                if not isinstance(value, int):
+                                    self.logger.warning(
+                                        'The value for pct_refinement should be an int. '
+                                        f'The present value is {value}.')
+                                else:
+                                    self.pinfo.setup.pct_refinement = value
+                                    continue
+                            if key == 'basis_order':
+                                if not isinstance(value, int):
+                                    self.logger.warning(
+                                        'The value for basis_order should be an int. '
+                                        f'The present value is {value}.')
+                                else:
+                                    self.pinfo.setup.basis_order = value
+                                    continue
+
+                            self.design.logger.warning(
+                                f'In setup_args, key={key}, value={value} is not in pinfo.setup, '
+                                'the key/value pair from setup_args not added to Setup in Ansys.'
+                            )
+
+                    else:
+                        self.logger.warning(
+                            'The design does not have solution type as "Eigenmode". The Setup not updated.'
+                        )
+                else:
+                    self.logger.warning(
+                        'A design is not in active project. The Setup not updated.'
+                    )
+            else:
+                self.logger.warning(
+                    "Project not available, have you opened a project? Setup not updated."
+                )
+        else:
+            self.logger.warning(
+                "Have you run connect_ansys()?  "
+                "Cannot find a reference to Ansys in QRenderer. Setup not updated. "
+            )
+
+    def edit_drivenmodal_setup(self, setup_args: Dict):
+        """User can pass key/values to edit the setup for active driven modal setup.
+
+        Args:
+            setup_args (Dict): a Dict with possible keys/values.
+
+        **setup_args** dict contents:
+            * name (str, optional): Name of eigenmode setup. Defaults to "Setup".
+            * freq_ghz (int, optional): Minimum frequency in GHz. Defaults to 1.
+            * max_passes (int, optional): Maximum number of passes. Defaults to 10.
+            * pct_refinement (int, optional): Percent refinement. Defaults to 30.
+            * basis_order (int, optional): Basis order. Defaults to -1 (1 is "Mixed Order").
+
+            Note, that these three are currently NOT implemented:
+            Ansys API named EditSetup not documented for HFSS, and
+            self.pinfo.setup does not have all the property variables used for Setup.
+            * max_delta_s (float, optional): Absolute value of maximum difference in scattering parameter S. Defaults to 0.1.
+            * min_passes (int, optional): Minimum number of passes. Defaults to 1.
+            * min_converged (int, optional): Minimum number of converged passes. Defaults to 1.
+        """
+
+        if self.pinfo:
+            if self.pinfo.project:
+                if self.pinfo.design:
+                    if self.pinfo.design.solution_type == 'DrivenModal':
+                        if self.pinfo.setup_name != setup_args.name:
+                            self.design.logger.warning(
+                                f'The name of active setup={self.pinfo.setup_name} does not match'
+                                f'the name of of setup_args.name={setup_args.name}. '
+                                f'To use this method, activate the desired Setup before editing it. The '
+                                f'setup_args was not used to update the active Setup.'
+                            )
+                            return
+
+                        for key, value in setup_args.items():
+                            if key == "name":
+                                continue  #Checked for above.
+                            if key == "freq_ghz":
+                                if not isinstance(value, float):
+                                    self.logger.warning(
+                                        'The value for freq_ghz should be an float. '
+                                        f'The present value is {value}.')
+                                else:
+                                    self.pinfo.setup.solution_freq = f'{value}GHz'
+                                    continue
+                            if key == 'max_passes':
+                                if not isinstance(value, int):
+                                    self.logger.warning(
+                                        'The value for passes should be an int. '
+                                        f'The present value is {value}.')
+                                else:
+                                    self.pinfo.setup.passes = value
+                                    continue
+                            if key == 'pct_refinement':
+                                if not isinstance(value, int):
+                                    self.logger.warning(
+                                        'The value for pct_refinement should be an int. '
+                                        f'The present value is {value}.')
+                                else:
+                                    self.pinfo.setup.pct_refinement = value
+                                    continue
+                            if key == 'basis_order':
+                                if not isinstance(value, int):
+                                    self.logger.warning(
+                                        'The value for basis_order should be an int. '
+                                        f'The present value is {value}.')
+                                else:
+                                    self.pinfo.setup.basis_order = value
+                                    continue
+
+                            self.design.logger.warning(
+                                f'In setup_args, key={key}, value={value} is not in pinfo.setup, '
+                                'the key/value pair from setup_args not added to Setup in Ansys.'
+                            )
+
+                    else:
+                        self.logger.warning(
+                            'The design does not have solution type as "Driven Modal". The Setup not updated.'
+                        )
+                else:
+                    self.logger.warning(
+                        'A design is not in active project. The Setup not updated.'
+                    )
+            else:
+                self.logger.warning(
+                    "Project not available, have you opened a project? Setup not updated."
+                )
+        else:
+            self.logger.warning(
+                "Have you run connect_ansys()?  "
+                "Cannot find a reference to Ansys in QRenderer. Setup not updated. "
+            )
+
+    def set_mode(self, mode: int, setup_name: str):
+        """Set the eigenmode in pyEPR for a design with solution_type set to
+        Eigenmode.
+
+        Args:
+            mode (int): Identify a mode from 1 to n_modes.
+            setup_name (str): Select a setup from the active design.
+        """
+        if self.pinfo:
+            if self.pinfo.project:
+                if self.pinfo.design:
+                    # double parent, becasue self.pinfo.design does not work
+                    o_desktop = self.pinfo.design.parent.parent._desktop
+                    o_project = o_desktop.SetActiveProject(
+                        self.pinfo.project_name)
+                    o_design = o_project.GetActiveDesign()
+                    if o_design.GetSolutionType() == 'Eigenmode':
+                        # The set_mode() method is in HfssEMDesignSolutions
+                        #  class in pyEPR.
+                        # The class HfssEMDesignSolutions is instantiated by
+                        #  get_setup() and create_em_setup().
+                        setup = self.pinfo.get_setup(setup_name)
+                        if 0 < int(mode) <= int(setup.n_modes):
+                            setup_solutions = setup.get_solutions()
+                            if setup_solutions:
+                                setup_solutions.set_mode(mode)
+                            else:
+                                self.logger.warning(
+                                    'Not able to get setup_solutions, '
+                                    'the mode was not set.')
+                        else:
+                            self.logger.warning(
+                                f'The requested mode={mode} is not a valid '
+                                f'(1 to {setup.n_modes}) selection. '
+                                'The mode was not set.')
+                    else:
+                        self.logger.warning(
+                            'The design does not have solution type as '
+                            '"Eigenmode". The mode was not set.')
+                else:
+                    self.logger.warning('A design is not in active project. '
+                                        'The mode was not set.')
+            else:
+                self.logger.warning(
+                    "Project not available, have you opened a project? "
+                    "The mode was not set.")
+        else:
+            self.logger.warning(
+                "Have you run connect_ansys()?  "
+                "Cannot find a reference to Ansys in QRenderer.  "
+                "The mode was not set.")
+
+    def analyze_setup(self, setup_name: str):
+        """Run a specific solution setup in Ansys HFSS.
+
+        Args:
+            setup_name (str): Name of setup.
+        """
+        if self.pinfo:
+            setup = self.pinfo.get_setup(setup_name)
+            setup.analyze(setup_name)
+
+    def add_sweep(self,
+                  setup_name="Setup",
+                  start_ghz=2.0,
+                  stop_ghz=8.0,
+                  count=101,
+                  step_ghz=None,
+                  name="Sweep",
+                  type="Fast",
+                  save_fields=False):
+        """Add a frequency sweep to a driven modal setup.
+
+        Args:
+            setup_name (str, optional): Name of driven modal simulation setup.
+                                    Defaults to "Setup".
+            start_ghz (float, optional): Starting frequency of sweep in GHz.
+                                    Defaults to 2.0.
+            stop_ghz (float, optional): Ending frequency of sweep in GHz.
+                                    Defaults to 8.0.
+            count (int, optional): Total number of frequencies.
+                                    Defaults to 101.
+            step_ghz (float, optional): Difference between adjacent
+                                    frequencies. Defaults to None.
+            name (str, optional): Name of sweep. Defaults to "Sweep".
+            type (str, optional): Type of sweep. Defaults to "Fast".
+            save_fields (bool, optional): Whether or not to save fields.
+                                Defaults to False.
+        """
+        if self.pinfo:
+            setup = self.pinfo.get_setup(setup_name)
+            return setup.insert_sweep(start_ghz=start_ghz,
+                                      stop_ghz=stop_ghz,
+                                      count=count,
+                                      step_ghz=step_ghz,
+                                      name=name,
+                                      type=type,
+                                      save_fields=save_fields)
+
+    def analyze_sweep(self, sweep_name: str, setup_name: str):
+        """Analyze a single sweep within the setup.
+
+        Args:
+            sweep_name (str): Name of sweep to analyze.
+            setup_name (str): Name of setup to analyze.
+        """
+        if self.pinfo:
+            setup = self.pinfo.get_setup(setup_name)
+            sweep = setup.get_sweep(sweep_name)
+            sweep.analyze_sweep()
+            self.current_sweep = sweep
+
+    def get_params(self, param_name: Union[list, None] = None):
+        """Get one or more parameters (S, Y, or Z) as a function of frequency.
+
+        Args:
+            param_name (Union[list, None], optional): Parameters to obtain. Defaults to None.
+        """
+        if self.current_sweep:
+
+            freqs, Pcurves = self.current_sweep.get_network_data(param_name)
+            Pparams = pd.DataFrame(Pcurves,
+                                   columns=freqs / 1e9,
+                                   index=param_name).transpose()
+        return freqs, Pcurves, Pparams
+
+    # yapf: disable
+    def get_all_Pparms_matrices(self, matrix_size: int) -> Tuple[
+            Union[pd.core.frame.DataFrame, None],
+            Union[pd.core.frame.DataFrame, None],
+            Union[pd.core.frame.DataFrame, None]]:
+        #yapf: enable
+        '''
+        S = scattering matrix, Y = Admittance, Z= impedance.
+
+        matrix_size should be 1 or larger.
+        This method will get the entire Scattering matrix based on matrix_size.
+
+        Example:'S21'
+        S matrix: SAB means, excite B, measure A
+        '''
+        s_param_name = []
+        y_param_name = []
+        z_param_name = []
+        if matrix_size < 1:
+            return None, None, None
+        for excite in range(1, matrix_size + 1):
+            for measure in range(1, matrix_size + 1):
+                s_param_name.append(f'S{measure}{excite}')
+                y_param_name.append(f'Y{measure}{excite}')
+                z_param_name.append(f'Z{measure}{excite}')
+        dummy_freqs, dummy_Pcurves, S_Pparams = self.get_params(s_param_name)
+        dummy_freqs, dummy_Pcurves, Y_Pparams = self.get_params(y_param_name)
+        dummy_freqs, dummy_Pcurves, Z_Pparams = self.get_params(z_param_name)
+
+        return S_Pparams, Y_Pparams, Z_Pparams
+
+    def plot_params(self, param_name: Union[list, None] = None):
+        """Plot one or more parameters (S, Y, or Z) as a function of frequency.
+        S = scattering matrix, Y = Admittance, Z= impedance.
+
+        Args:
+            param_name (Union[list, None], optional): Parameters to plot. Defaults to None.
+        """
+        freqs, Pcurves, Pparams = self.get_params(param_name)
+        if Pparams is not None:
+            fig, axs = plt.subplots(1, 2, figsize=(10, 6))
+            Pparams.apply(lambda x: 20 * np.log10(np.abs(x))).plot(ax=axs[0])
+            axs[0].set_xlabel('Frequency (GHz)')
+            axs[0].set_ylabel('Amplitude (dB)')
+            Pparams.apply(lambda x: np.angle(x)).plot(ax=axs[1])
+            axs[1].set_xlabel('Frequency (GHz)')
+            axs[1].set_ylabel('Phase (rad.)')
+            for ax in axs:
+                ax.autoscale()
+        return Pparams, fig
+
+    def distributed_analysis(self):
+        """Returns class containing info on Hamiltonian parameters from HFSS
+        simulation.
+
+        Returns:
+            DistributedAnalysis: A  class from pyEPR which does DISTRIBUTED ANALYSIS of layout
+            and microwave results.  It is the main computation class & interface with HFSS.
+            This class defines a DistributedAnalysis object which calculates
+            and saves Hamiltonian parameters from an HFSS simulation.
+            It allows one to calculate dissipation.
+        """
+        if self.pinfo:
+            return epr.DistributedAnalysis(self.pinfo)
+
+    def get_convergences(self, variation: str = None):
+        """Get convergence for convergence_t, convergence_f, and text from GUI for solution data.
+
+        Args:
+            variation (str, optional):  Information from pyEPR; variation should be in the form
+            variation = "scale_factor='1.2001'". Defaults to None.
+
+        Returns:
+            tuple[pandas.DataFrame, pandas.DataFrame, str]:
+            1st DataFrame: Convergence_t
+            2nd DataFrame: Convergence_f
+            3rd str: Text from GUI of solution data.
+        """
+        if self.pinfo:
+            convergence_t, text = self.pinfo.setup.get_convergence(variation)
+            convergence_f = self.get_f_convergence([])  # TODO; Fix variation []
+            return convergence_t, convergence_f, text
+
+    def plot_convergences(self,
+                          variation: str = None,
+                          fig: mpl.figure.Figure = None):
+        """
+        (deprecated) use EPRanalysis.plot_convergences()
+        """
+        print(
+            'This method is deprecated. Change your scripts to use the equivalent method '
+            'plot_convergence() that has been moved inside the EPRanalysis class.'
+        )
+
+    def get_f_convergence(self, variation: str = None, save_csv: bool = True):
+        """Create a report inside HFSS to plot the converge of frequency and
+        style it. Saves report to csv file.
+
+        .. code-block:: text
+
+                re(Mode(1)) [g]	re(Mode(2)) [g]	re(Mode(3)) [g]
+            Pass []
+            1	4.643101	4.944204	5.586289
+            2	5.114490	5.505828	6.242423
+            3	5.278594	5.604426	6.296777
+
+        Args:
+            variation ('str', optional): Information from pyEPR; variation should be in the form
+                variation = "scale_factor='1.2001'". Defaults to None.
+            save_csv (bool, optional): Save to file? Defaults to True.
+
+        Returns:
+            pd.DataFrame: Returns a convergence vs pass number of the eigenemode frequencies.
+        """
+        if self.pinfo:
+            o_design = self.pinfo.design
+            setup = self.pinfo.setup
+
+            if not o_design.solution_type == 'Eigenmode':
+                return None
+
+            report = o_design._reporter  # reporter OModule for Ansys
+
+            # Create report
+            n_modes = int(setup.n_modes)
+            ycomp = [f"re(Mode({i}))" for i in range(1, 1 + n_modes)]
+
+            params = ["Pass:=", ["All"]] + variation
+            report_name = "Freq. vs. pass"
+            if report_name in report.GetAllReportNames():
+                report.DeleteReports([report_name])
+
+            solutions = setup.get_solutions()
+            solutions.create_report(report_name,
+                                    "Pass",
+                                    ycomp,
+                                    params,
+                                    pass_name='AdaptivePass')
+
+            # Properties of lines
+            curves = [
+                f"{report_name}:re(Mode({i})):Curve1"
+                for i in range(1, 1 + n_modes)
+            ]
+            set_property(report, 'Attributes', curves, 'Line Width', 3)
+            set_property(report, 'Scaling', f"{report_name}:AxisY1",
+                         'Auto Units', False)
+            set_property(report, 'Scaling', f"{report_name}:AxisY1", 'Units',
+                         'g')
+            set_property(report, 'Legend', f"{report_name}:Legend",
+                         'Show Solution Name', False)
+
+            if save_csv:  # Save
+                try:
+                    path = Path().absolute(
+                    ) / 'hfss_eig_f_convergence.csv'  # TODO: Determine better path
+                    report.ExportToFile(report_name, path)
+                    self.logger.info(f'Saved convergences to {path}')
+                    return pd.read_csv(path, index_col=0)
+                except Exception as e:
+                    self.logger.error(
+                        f"Error could not save and export hfss plot to {path}.\
+                                Is the plot made in HFSS with the correct name.\
+                                Check the HFSS error window. \t Error =  {e}")
+
+            return None
diff --git a/tutorials/4 Analysis/B. Advanced - Direct use of the renderers/4.18 Analyse a Resonator with Ports.ipynb b/tutorials/4 Analysis/B. Advanced - Direct use of the renderers/4.18 Analyse a Resonator with Ports.ipynb
new file mode 100644
index 000000000..696d6503a
--- /dev/null
+++ b/tutorials/4 Analysis/B. Advanced - Direct use of the renderers/4.18 Analyse a Resonator with Ports.ipynb
@@ -0,0 +1,368 @@
+{
+   "cells": [
+      {
+         "cell_type": "markdown",
+         "id": "35d27b44",
+         "metadata": {},
+         "source": [
+            "# Eigenmode simulation with Ports\n",
+            "\n",
+            "Authors: Samarth Hawaldar, Arvind Mamgain\n",
+            "\n",
+            "Adapted from tutorial 4.16\n"
+         ]
+      },
+      {
+         "cell_type": "markdown",
+         "id": "abc2b76d",
+         "metadata": {},
+         "source": []
+      },
+      {
+         "cell_type": "code",
+         "execution_count": null,
+         "id": "0b0da17a",
+         "metadata": {},
+         "outputs": [],
+         "source": [
+            "# Import useful packages\n",
+            "import qiskit_metal as metal\n",
+            "from qiskit_metal import designs, draw\n",
+            "from qiskit_metal import MetalGUI, Dict, open_docs\n",
+            "\n",
+            "# Packages for the simple design\n",
+            "from qiskit_metal.qlibrary.tlines.meandered import RouteMeander\n",
+            "from qiskit_metal.qlibrary.tlines.pathfinder import RoutePathfinder\n",
+            "from qiskit_metal.qlibrary.terminations.launchpad_wb_driven import LaunchpadWirebondDriven\n",
+            "from qiskit_metal.qlibrary.terminations.open_to_ground import OpenToGround\n",
+            "from qiskit_metal.qlibrary.couplers.coupled_line_tee import CoupledLineTee\n",
+            "# Analysis\n",
+            "from qiskit_metal.analyses.quantization import EPRanalysis"
+         ]
+      },
+      {
+         "cell_type": "markdown",
+         "id": "c6e244f8",
+         "metadata": {},
+         "source": [
+            "# Set up the design"
+         ]
+      },
+      {
+         "cell_type": "code",
+         "execution_count": null,
+         "id": "3983eac9",
+         "metadata": {},
+         "outputs": [],
+         "source": [
+            "# Set up chip dimensions \n",
+            "design = designs.DesignPlanar()\n",
+            "design._chips['main']['size']['size_x'] = '2mm'\n",
+            "design._chips['main']['size']['size_y'] = '2mm'\n",
+            "design._chips['main']['size']['size_z'] = '-280um'\n",
+            "# Resonator and feedline gap width (W) and center conductor width (S) from reference 2\n",
+            "design.variables['cpw_width'] = '10 um' #S from reference 2\n",
+            "design.variables['cpw_gap'] = '6 um' #W from reference 2\n",
+            "\n",
+            "\n",
+            "design.overwrite_enabled = True\n",
+            "\n",
+            "hfss = design.renderers.hfss\n",
+            "\n",
+            "# Open GUI\n",
+            "gui = MetalGUI(design)"
+         ]
+      },
+      {
+         "cell_type": "markdown",
+         "id": "055c0bef",
+         "metadata": {},
+         "source": [
+            "# Define the geometry\n",
+            "\n",
+            "Here we will have a single feedline couple to a single CPW resonator.\n",
+            "\n",
+            "The lauchpad should be included in the driven model simulations.\n",
+            "\n",
+            "For that reason, we use the LaunchpadWirebondDriven component which has an extra pin for input/output"
+         ]
+      },
+      {
+         "cell_type": "code",
+         "execution_count": null,
+         "id": "ef570ed8",
+         "metadata": {},
+         "outputs": [],
+         "source": [
+            "###################\n",
+            "# Single feedline #\n",
+            "###################\n",
+            "\n",
+            "# Driven Lauchpad 1\n",
+            "x = '-0.5mm'\n",
+            "y = '0.5mm'\n",
+            "launch_options = dict(chip='main', pos_x=x, pos_y=y, orientation='360', lead_length='30um')\n",
+            "LP1 = LaunchpadWirebondDriven(design, 'LP1', options = launch_options)\n",
+            "\n",
+            "# Driven Launchpad 2\n",
+            "x = '0.5mm'\n",
+            "y = '0.5mm'\n",
+            "launch_options = dict(chip='main', pos_x=x, pos_y=y, orientation='180', lead_length='30um')\n",
+            "LP2 = LaunchpadWirebondDriven(design, 'LP2', options = launch_options)\n",
+            "\n",
+            "# coupling resonator to feedline \n",
+            "q_read = CoupledLineTee(design,'Q_Read_T', options=dict(pos_x = '0.0mm', pos_y = '0.5mm', \n",
+            "                                                        orientation = '0',\n",
+            "                                                        coupling_space = '6um',                                                         \n",
+            "                                                        coupling_length = '300um',\n",
+            "                                                        open_termination = False))\n",
+            "\n",
+            "\n",
+            "gui.rebuild()\n"
+         ]
+      },
+      {
+         "cell_type": "code",
+         "execution_count": null,
+         "id": "239f2d12",
+         "metadata": {},
+         "outputs": [],
+         "source": [
+            "\n",
+            "# Using path finder to connect the two launchpads\n",
+            "TL_LP1_T = RoutePathfinder(design, 'TL_LP1_T', options = dict(chip='main', trace_width ='10um',\n",
+            "        trace_gap ='6um',\n",
+            "                                            fillet='99um',\n",
+            "                                                                  \n",
+            "        hfss_wire_bonds = True,\n",
+            "                                            lead=dict(end_straight='0.1mm'),\n",
+            "                                            pin_inputs=Dict(\n",
+            "                                                start_pin=Dict(\n",
+            "                                                    component='LP1',\n",
+            "                                                    pin='tie'),\n",
+            "                                                end_pin=Dict(\n",
+            "                                                    component='Q_Read_T',\n",
+            "                                                    pin='prime_start')\n",
+            "                                            )))\n",
+            "\n",
+            "TL_T_LP2 = RoutePathfinder(design, 'TL_T_LP2', options = dict(chip='main', trace_width ='10um',\n",
+            "        trace_gap ='6um',\n",
+            "                                            fillet='99um',\n",
+            "                                                                  \n",
+            "        hfss_wire_bonds = True,\n",
+            "                                            lead=dict(end_straight='0.1mm'),\n",
+            "                                            pin_inputs=Dict(\n",
+            "                                                start_pin=Dict(\n",
+            "                                                    component='Q_Read_T',\n",
+            "                                                    pin='prime_end'),\n",
+            "                                                end_pin=Dict(\n",
+            "                                                    component='LP2',\n",
+            "                                                    pin='tie')\n",
+            "                                            )))\n",
+            "# # Rebuild the GUI"
+         ]
+      },
+      {
+         "cell_type": "code",
+         "execution_count": null,
+         "id": "92b1570a",
+         "metadata": {},
+         "outputs": [],
+         "source": [
+            "######################\n",
+            "# lambda/4 resonator #\n",
+            "######################\n",
+            "\n",
+            "# First we define the two end-points\n",
+            "otg = OpenToGround(design, 'otg', options=dict(chip='main', pos_x='0.0mm',  pos_y='-0.7mm', orientation='-90'))\n",
+            "\n",
+            "# Use RouteMeander to fix the total length of the resonator\n",
+            "rt_meander = RouteMeander(design, 'meander',  Dict(\n",
+            "        trace_width ='10um',\n",
+            "        trace_gap ='6um',\n",
+            "        total_length='3.7mm',\n",
+            "        hfss_wire_bonds = True,\n",
+            "        fillet='99 um',\n",
+            "        lead = dict(start_straight='250um'),\n",
+            "        pin_inputs=Dict(\n",
+            "            start_pin=Dict(component='otg', pin='open'),\n",
+            "            end_pin=Dict(component='Q_Read_T', pin='second_end')), ))\n",
+            "\n",
+            "# rebuild the GUI\n",
+            "gui.rebuild()"
+         ]
+      },
+      {
+         "cell_type": "code",
+         "execution_count": null,
+         "id": "9956dd38",
+         "metadata": {},
+         "outputs": [],
+         "source": [
+            "gui.autoscale()\n",
+            "gui.screenshot()"
+         ]
+      },
+      {
+         "cell_type": "markdown",
+         "id": "7070c19a",
+         "metadata": {},
+         "source": [
+            "# Perfoming Eigenmode Simulation"
+         ]
+      },
+      {
+         "cell_type": "code",
+         "execution_count": null,
+         "id": "5691146b",
+         "metadata": {},
+         "outputs": [],
+         "source": [
+            "from qiskit_metal.analyses.quantization import EPRanalysis\n",
+            "eig_qb = EPRanalysis(design, \"hfss\")\n",
+            "hfss = eig_qb.sim.renderer\n",
+            "hfss.start()"
+         ]
+      },
+      {
+         "cell_type": "code",
+         "execution_count": null,
+         "id": "4cfc6a55",
+         "metadata": {},
+         "outputs": [],
+         "source": [
+            "em_p = eig_qb.sim.setup\n",
+            "em_p.name = 'HangerReso'\n",
+            "em_p.min_freq_ghz = 1\n",
+            "em_p.n_modes = 1\n",
+            "em_p.max_passes = 15\n",
+            "em_p.max_delta_f = 0.1\n",
+            "em_p.min_converged = 2\n",
+            "em_p.basis_order = 2\n",
+            "# Design variables can also be added in for direct simulation sweeps.\n",
+            "em_p.vars = Dict({'Lj': '0nH', 'Cj': '0 fF'})"
+         ]
+      },
+      {
+         "cell_type": "code",
+         "execution_count": null,
+         "id": "bf6b16cf",
+         "metadata": {},
+         "outputs": [],
+         "source": [
+            "eig_qb.sim._render(name=\"HangerReso\",\n",
+            "                selection = [],\n",
+            "                solution_type='eigenmode',\n",
+            "                vars_to_initialize=em_p.vars, \n",
+            "                open_pins=[], \n",
+            "                port_list=[('LP1', 'in','50'), ('LP2', 'in','50')],\n",
+            "                box_plus_buffer = False)"
+         ]
+      },
+      {
+         "cell_type": "code",
+         "execution_count": null,
+         "id": "cb9ff9d6",
+         "metadata": {},
+         "outputs": [],
+         "source": [
+            "eig_qb.sim._analyze()"
+         ]
+      },
+      {
+         "cell_type": "code",
+         "execution_count": null,
+         "id": "0f91c976",
+         "metadata": {},
+         "outputs": [],
+         "source": [
+            "eig_qb.get_frequencies()"
+         ]
+      },
+      {
+         "cell_type": "code",
+         "execution_count": null,
+         "id": "d8d20499",
+         "metadata": {},
+         "outputs": [],
+         "source": [
+            "hfss.close()"
+         ]
+      },
+      {
+         "cell_type": "code",
+         "execution_count": null,
+         "id": "8e116383",
+         "metadata": {},
+         "outputs": [],
+         "source": [
+            "# Data obtained during testing\n",
+            "# Data ANSYS:\n",
+            "# Mesh Size = 50um, Q = 18875, f = 7.3167\n",
+            "# Mesh Size = 15um, Q = 4269, f = 7.3031\n",
+            "# Mesh Size = 7um, Q = 14943, f = 7.3316\n",
+            "# Data COMSOL:\n",
+            "# Mesh = Normal, Q = 18858, f = 7.1315\n",
+            "# Mesh = Fine, Q = 18584, f = 7.1176\n",
+            "# Mesh = Finer, Q = 17042, f = 7.1426\n",
+            "# Mesh = Extra Fine, Q = 16986, f = 7.2079\n",
+            "# Mesh = Extremely Fine, Q = 14591, f = 7.2092"
+         ]
+      }
+   ],
+   "metadata": {
+      "interpreter": {
+         "hash": "02a7f130499a2b83a6c839d5429524d9237546653577440f6602b9b30b8b4215"
+      },
+      "kernelspec": {
+         "display_name": "Python [conda env:metal]",
+         "language": "python",
+         "name": "python3"
+      },
+      "language_info": {
+         "codemirror_mode": {
+            "name": "ipython",
+            "version": 3
+         },
+         "file_extension": ".py",
+         "mimetype": "text/x-python",
+         "name": "python",
+         "nbconvert_exporter": "python",
+         "pygments_lexer": "ipython3",
+         "version": "3.9.12"
+      },
+      "latex_envs": {
+         "LaTeX_envs_menu_present": true,
+         "autoclose": false,
+         "autocomplete": true,
+         "bibliofile": "biblio.bib",
+         "cite_by": "apalike",
+         "current_citInitial": 1,
+         "eqLabelWithNumbers": true,
+         "eqNumInitial": 1,
+         "hotkeys": {
+            "equation": "Ctrl-E",
+            "itemize": "Ctrl-I"
+         },
+         "labels_anchors": false,
+         "latex_user_defs": false,
+         "report_style_numbering": false,
+         "user_envs_cfg": false
+      },
+      "toc": {
+         "base_numbering": 1,
+         "nav_menu": {},
+         "number_sections": true,
+         "sideBar": true,
+         "skip_h1_title": false,
+         "title_cell": "Table of Contents",
+         "title_sidebar": "Contents",
+         "toc_cell": false,
+         "toc_position": {},
+         "toc_section_display": true,
+         "toc_window_display": false
+      }
+   },
+   "nbformat": 4,
+   "nbformat_minor": 5
+}


Test patch:
diff --git a/qiskit_metal/tests/test_renderers.py b/qiskit_metal/tests/test_renderers.py
index 9ce5f1f7d..2435c102b 100644
--- a/qiskit_metal/tests/test_renderers.py
+++ b/qiskit_metal/tests/test_renderers.py
@@ -1,467 +1,468 @@
-# -*- coding: utf-8 -*-
-
-# This code is part of Qiskit.
-#
-# (C) Copyright IBM 2017, 2021.
-#
-# This code is licensed under the Apache License, Version 2.0. You may
-# obtain a copy of this license in the LICENSE.txt file in the root directory
-# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
-#
-# Any modifications or derivative works of this code must retain this
-# copyright notice, and modified files need to carry a notice indicating
-# that they have been altered from the originals.
-
-# pylint: disable-msg=unnecessary-pass
-# pylint: disable-msg=broad-except
-# pylint: disable-msg=too-many-public-methods
-# pylint: disable-msg=import-error
-# pylint: disable-msg=protected-access
-"""Qiskit Metal unit tests analyses functionality."""
-
-import unittest
-import matplotlib.pyplot as _plt
-
-from qiskit_metal import designs
-from qiskit_metal.renderers import setup_default
-from qiskit_metal.renderers.renderer_ansys.ansys_renderer import QAnsysRenderer
-from qiskit_metal.renderers.renderer_ansys.q3d_renderer import QQ3DRenderer
-from qiskit_metal.renderers.renderer_ansys.hfss_renderer import QHFSSRenderer
-from qiskit_metal.renderers.renderer_base.renderer_base import QRenderer
-from qiskit_metal.renderers.renderer_base.renderer_gui_base import QRendererGui
-from qiskit_metal.renderers.renderer_gds.gds_renderer import QGDSRenderer
-from qiskit_metal.renderers.renderer_mpl.mpl_interaction import MplInteraction
-
-from qiskit_metal.renderers.renderer_ansys import ansys_renderer
-
-from qiskit_metal.qgeometries.qgeometries_handler import QGeometryTables
-from qiskit_metal.qlibrary.qubits.transmon_pocket import TransmonPocket
-from qiskit_metal import draw
-
-
-class TestRenderers(unittest.TestCase):
-    """Unit test class."""
-
-    def setUp(self):
-        """Setup unit test."""
-        pass
-
-    def tearDown(self):
-        """Tie any loose ends."""
-        pass
-
-    def test_renderer_instanitate_qansys_renderer(self):
-        """Test instantiation of QAnsysRenderer in ansys_renderer.py"""
-        design = designs.DesignPlanar()
-        try:
-            QAnsysRenderer(design, initiate=False)
-        except Exception:
-            self.fail("QAnsysRenderer() failed")
-
-    def test_renderer_instantiate_gdsrender(self):
-        """Test instantiation of QGDSRenderer in gds_renderer.py."""
-        design = designs.DesignPlanar()
-        try:
-            QGDSRenderer(design)
-        except Exception:
-            self.fail("QGDSRenderer(design) failed")
-
-        try:
-            QGDSRenderer(design, initiate=False)
-        except Exception:
-            self.fail("QGDSRenderer(design, initiate=False) failed")
-
-        try:
-            QGDSRenderer(design, initiate=False, render_template={})
-        except Exception:
-            self.fail(
-                "QGDSRenderer(design, initiate=False, render_template={}) failed"
-            )
-
-        try:
-            QGDSRenderer(design, initiate=False, render_options={})
-        except Exception:
-            self.fail(
-                "QGDSRenderer(design, initiate=False, render_options={}) failed"
-            )
-
-    def test_renderer_instantiate_mplinteraction(self):
-        """Test instantiation of MplInteraction in mpl_interaction.py."""
-        try:
-            MplInteraction(_plt)
-        except Exception:
-            self.fail("MplInteraction(None) failed")
-
-    def test_renderer_instantiate_qq3d_renderer(self):
-        """Test instantiation of QQ3DRenderer in q3d_render.py."""
-        design = designs.DesignPlanar()
-        try:
-            QQ3DRenderer(design, initiate=False)
-        except Exception:
-            self.fail("QQ3DRenderer failed")
-
-    def test_renderer_instantiate_qhfss_renderer(self):
-        """Test instantiation of QHFSSRenderer in q3d_render.py."""
-        design = designs.DesignPlanar()
-        try:
-            QHFSSRenderer(design, initiate=False)
-        except Exception:
-            self.fail("QHFSSRenderer failed")
-
-    def test_renderer_qansys_renderer_options(self):
-        """Test that defaults in QAnsysRenderer were not accidentally changed."""
-        design = designs.DesignPlanar()
-        renderer = QAnsysRenderer(design, initiate=False)
-        options = renderer.default_options
-
-        self.assertEqual(len(options), 13)
-        self.assertEqual(options['Lj'], '10nH')
-        self.assertEqual(options['Cj'], 0)
-        self.assertEqual(options['_Rj'], 0)
-        self.assertEqual(options['max_mesh_length_jj'], '7um')
-        self.assertEqual(options['project_path'], None)
-        self.assertEqual(options['project_name'], None)
-        self.assertEqual(options['design_name'], None)
-        self.assertEqual(options['x_buffer_width_mm'], 0.2)
-        self.assertEqual(options['y_buffer_width_mm'], 0.2)
-        self.assertEqual(options['wb_threshold'], '400um')
-        self.assertEqual(options['wb_offset'], '0um')
-        self.assertEqual(options['wb_size'], 5)
-
-        self.assertEqual(len(options['plot_ansys_fields_options']), 13)
-        self.assertEqual(options['plot_ansys_fields_options']['name'],
-                         "NAME:Mag_E1")
-        self.assertEqual(
-            options['plot_ansys_fields_options']['UserSpecifyName'], '0')
-        self.assertEqual(
-            options['plot_ansys_fields_options']['UserSpecifyFolder'], '0')
-        self.assertEqual(options['plot_ansys_fields_options']['QuantityName'],
-                         "Mag_E")
-        self.assertEqual(options['plot_ansys_fields_options']['PlotFolder'],
-                         "E Field")
-        self.assertEqual(options['plot_ansys_fields_options']['StreamlinePlot'],
-                         "False")
-        self.assertEqual(
-            options['plot_ansys_fields_options']['AdjacentSidePlot'], "False")
-        self.assertEqual(options['plot_ansys_fields_options']['FullModelPlot'],
-                         "False")
-        self.assertEqual(options['plot_ansys_fields_options']['IntrinsicVar'],
-                         "Phase=\'0deg\'")
-        self.assertEqual(options['plot_ansys_fields_options']['PlotGeomInfo_0'],
-                         "1")
-        self.assertEqual(options['plot_ansys_fields_options']['PlotGeomInfo_1'],
-                         "Surface")
-        self.assertEqual(options['plot_ansys_fields_options']['PlotGeomInfo_2'],
-                         "FacesList")
-        self.assertEqual(options['plot_ansys_fields_options']['PlotGeomInfo_3'],
-                         "1")
-
-    def test_renderer_qansysrenderer_default_setup(self):
-        """Test that default_setup in QAnsysRenderer have not been accidentally changed."""
-        default_setup = QAnsysRenderer.default_setup
-
-        self.assertEqual(len(default_setup), 4)
-        self.assertEqual(len(default_setup['drivenmodal']), 8)
-        self.assertEqual(len(default_setup['eigenmode']), 9)
-        self.assertEqual(len(default_setup['q3d']), 12)
-        self.assertEqual(default_setup['port_inductor_gap'], '10um')
-
-        self.assertEqual(default_setup['drivenmodal']['name'], "Setup")
-        self.assertEqual(default_setup['drivenmodal']['freq_ghz'], '5.0')
-        self.assertEqual(default_setup['drivenmodal']['max_delta_s'], '0.1')
-        self.assertEqual(default_setup['drivenmodal']['max_passes'], '10')
-        self.assertEqual(default_setup['drivenmodal']['min_passes'], '1')
-        self.assertEqual(default_setup['drivenmodal']['min_converged'], '1')
-        self.assertEqual(default_setup['drivenmodal']['pct_refinement'], '30')
-        self.assertEqual(default_setup['drivenmodal']['basis_order'], '1')
-
-        self.assertEqual(default_setup['eigenmode']['name'], "Setup")
-        self.assertEqual(default_setup['eigenmode']['min_freq_ghz'], '1')
-        self.assertEqual(default_setup['eigenmode']['n_modes'], '1')
-        self.assertEqual(default_setup['eigenmode']['max_delta_f'], '0.5')
-        self.assertEqual(default_setup['eigenmode']['max_passes'], '10')
-        self.assertEqual(default_setup['eigenmode']['min_passes'], '1')
-        self.assertEqual(default_setup['eigenmode']['min_converged'], '1')
-        self.assertEqual(default_setup['eigenmode']['pct_refinement'], '30')
-        self.assertEqual(default_setup['eigenmode']['basis_order'], '-1')
-
-        self.assertEqual(default_setup['q3d']['name'], 'Setup')
-        self.assertEqual(default_setup['q3d']['freq_ghz'], '5.0')
-        self.assertEqual(default_setup['q3d']['save_fields'], 'False')
-        self.assertEqual(default_setup['q3d']['enabled'], 'True')
-        self.assertEqual(default_setup['q3d']['max_passes'], '15')
-        self.assertEqual(default_setup['q3d']['min_passes'], '2')
-        self.assertEqual(default_setup['q3d']['min_converged_passes'], '2')
-        self.assertEqual(default_setup['q3d']['percent_error'], '0.5')
-        self.assertEqual(default_setup['q3d']['percent_refinement'], '30')
-        self.assertEqual(default_setup['q3d']['auto_increase_solution_order'],
-                         'True')
-        self.assertEqual(default_setup['q3d']['solution_order'], 'High')
-        self.assertEqual(default_setup['q3d']['solver_type'], 'Iterative')
-
-    def test_renderer_qq3d_render_options(self):
-        """Test that defaults in QQ3DRenderer were not accidentally changed."""
-        design = designs.DesignPlanar()
-        renderer = QQ3DRenderer(design, initiate=False)
-        options = renderer.q3d_options
-
-        self.assertEqual(renderer.name, 'q3d')
-
-        self.assertEqual(len(options), 2)
-        self.assertEqual(options['material_type'], 'pec')
-        self.assertEqual(options['material_thickness'], '200nm')
-
-    def test_renderer_hfss_render_options(self):
-        """Test that defaults in QHFSSRender were not accidentally changed."""
-        design = designs.DesignPlanar()
-        renderer = QHFSSRenderer(design, initiate=False)
-        options = renderer.hfss_options
-
-        self.assertEqual(renderer.name, 'hfss')
-        self.assertEqual(len(options), 1)
-        self.assertEqual(options['port_inductor_gap'], '10um')
-
-    def test_renderer_gdsrenderer_options(self):
-        """Test that default_options in QGDSRenderer were not accidentally
-        changed."""
-        design = designs.DesignPlanar()
-        renderer = QGDSRenderer(design)
-        options = renderer.default_options
-
-        self.assertEqual(len(options), 17)
-        self.assertEqual(options['short_segments_to_not_fillet'], 'True')
-        self.assertEqual(options['check_short_segments_by_scaling_fillet'],
-                         '2.0')
-        self.assertEqual(options['gds_unit'], '1')
-        self.assertEqual(options['ground_plane'], 'True')
-        self.assertEqual(options['negative_mask']['main'], [])
-        self.assertEqual(options['corners'], 'circular bend')
-        self.assertEqual(options['tolerance'], '0.00001')
-        self.assertEqual(options['precision'], '0.000000001')
-        self.assertEqual(options['width_LineString'], '10um')
-        self.assertEqual(options['path_filename'],
-                         '../resources/Fake_Junctions.GDS')
-        self.assertEqual(options['junction_pad_overlap'], '5um')
-        self.assertEqual(options['max_points'], '199')
-        self.assertEqual(options['bounding_box_scale_x'], '1.2')
-        self.assertEqual(options['bounding_box_scale_y'], '1.2')
-
-        self.assertEqual(options['fabricate'], 'False')
-
-        self.assertEqual(len(options['cheese']), 9)
-        self.assertEqual(len(options['no_cheese']), 5)
-
-        self.assertEqual(options['cheese']['datatype'], '100')
-        self.assertEqual(options['cheese']['shape'], '0')
-        self.assertEqual(options['cheese']['cheese_0_x'], '25um')
-        self.assertEqual(options['cheese']['cheese_0_y'], '25um')
-        self.assertEqual(options['cheese']['cheese_1_radius'], '100um')
-        self.assertEqual(options['cheese']['delta_x'], '100um')
-        self.assertEqual(options['cheese']['delta_y'], '100um')
-        self.assertEqual(options['cheese']['edge_nocheese'], '200um')
-
-        self.assertEqual(options['no_cheese']['datatype'], '99')
-        self.assertEqual(options['no_cheese']['buffer'], '25um')
-        self.assertEqual(options['no_cheese']['cap_style'], '2')
-        self.assertEqual(options['no_cheese']['join_style'], '2')
-
-        self.assertEqual(len(options['cheese']['view_in_file']), 1)
-        self.assertEqual(len(options['cheese']['view_in_file']['main']), 1)
-        self.assertEqual(options['cheese']['view_in_file']['main'][1], True)
-
-        self.assertEqual(len(options['no_cheese']['view_in_file']), 1)
-        self.assertEqual(len(options['no_cheese']['view_in_file']['main']), 1)
-        self.assertEqual(options['no_cheese']['view_in_file']['main'][1], True)
-
-    def test_renderer_ansys_renderer_get_clean_name(self):
-        """Test get_clean_name in ansys_renderer.py"""
-        self.assertEqual(ansys_renderer.get_clean_name('name12'), 'name12')
-        self.assertEqual(ansys_renderer.get_clean_name('12name12'), 'name12')
-        self.assertEqual(ansys_renderer.get_clean_name('name!'), 'name')
-
-    def test_renderer_ansys_renderer_name_delim(self):
-        """Test NAME_DELIM in QAnsysRenderer."""
-        design = designs.DesignPlanar()
-        renderer = QAnsysRenderer(design, initiate=False)
-        self.assertEqual(renderer.NAME_DELIM, '_')
-
-    def test_renderer_ansys_renderer_name(self):
-        """Test name in QAnsysRenderer."""
-        design = designs.DesignPlanar()
-        renderer = QAnsysRenderer(design, initiate=False)
-        self.assertEqual(renderer.name, 'ansys')
-
-    def test_renderer_renderer_base_name(self):
-        """Test name in QRenderer."""
-        renderer = QRenderer
-        self.assertEqual(renderer.name, 'base')
-
-    def test_renderer_renderer_gui_base_name(self):
-        """Test name in QRenderer."""
-        renderer = QRendererGui
-        self.assertEqual(renderer.name, 'guibase')
-
-    def test_renderer_gdsrenderer_inclusive_bound(self):
-        """Test functionality of inclusive_bound in gds_renderer.py."""
-        design = designs.DesignPlanar()
-        renderer = QGDSRenderer(design)
-
-        my_list = []
-        my_list.append([1, 1, 2, 2])
-        my_list.append([3, 3, 5, 5])
-        my_list.append([2.2, 2.3, 4.4, 4.9])
-        self.assertEqual(renderer._inclusive_bound(my_list), (1, 1, 5, 5))
-
-    def test_renderer_scale_max_bounds(self):
-        """Test functionality of scale_max_bounds in gds_renderer.py."""
-        design = designs.DesignPlanar()
-        renderer = QGDSRenderer(design)
-
-        actual = renderer._scale_max_bounds('main', [(1, 1, 3, 3)])
-        self.assertEqual(len(actual), 2)
-        self.assertEqual(actual[0], (0.8, 0.8, 3.2, 3.2))
-        self.assertEqual(actual[1], (1, 1, 3, 3))
-
-    def test_renderer_get_chip_names(self):
-        """Test functionality of get_chip_names in gds_renderer.py."""
-        design = designs.DesignPlanar()
-        renderer = QGDSRenderer(design)
-
-        qgt = QGeometryTables(design)
-        qgt.clear_all_tables()
-
-        transmon_pocket_1 = TransmonPocket(design, 'my_id')
-        transmon_pocket_1.make()
-        transmon_pocket_1.get_template_options(design)
-
-        a_linestring = draw.LineString([[0, 0], [0, 1]])
-        a_poly = draw.rectangle(2, 2, 0, 0)
-        qgt.add_qgeometry('path',
-                          'my_id', {'n_sprial': a_linestring},
-                          width=4000)
-        qgt.add_qgeometry('poly',
-                          'my_id', {'n_spira_etch': a_poly},
-                          subtract=True)
-
-        result = renderer._get_chip_names()
-        self.assertEqual(result, {'main': {}})
-
-    def test_renderer_setup_renderers(self):
-        """Test setup_renderers in setup_defauts.py."""
-        actual = setup_default.setup_renderers()
-        self.assertEqual(actual, {})
-
-    def test_renderer_renderer_base_element_table_data(self):
-        """Test element_table_data in QRenderer."""
-        renderer = QRenderer
-        etd = renderer.element_table_data
-
-        self.assertEqual(len(etd), 0)
-
-    def test_renderer_ansys_renderer_element_table_data(self):
-        """Test element_table_data in QAnsysRenderer."""
-        design = designs.DesignPlanar()
-        renderer = QAnsysRenderer(design, initiate=False)
-        etd = renderer.element_table_data
-
-        self.assertEqual(len(etd), 2)
-        self.assertEqual(len(etd['path']), 1)
-        self.assertEqual(len(etd['junction']), 4)
-
-        self.assertEqual(etd['path']['wire_bonds'], False)
-        self.assertEqual(etd['junction']['inductance'], '10nH')
-        self.assertEqual(etd['junction']['capacitance'], 0)
-        self.assertEqual(etd['junction']['resistance'], 0)
-        self.assertEqual(etd['junction']['mesh_kw_jj'], 7e-06)
-
-    def test_renderer_gdsrenderer_high_level(self):
-        """Test that high level defaults were not accidentally changed in
-        gds_renderer.py."""
-        design = designs.DesignPlanar()
-        renderer = QGDSRenderer(design)
-
-        self.assertEqual(renderer.name, 'gds')
-        element_table_data = renderer.element_table_data
-        self.assertEqual(len(element_table_data), 1)
-        self.assertEqual(len(element_table_data['junction']), 1)
-        self.assertEqual(element_table_data['junction']['cell_name'],
-                         'my_other_junction')
-
-    def test_renderer_gdsrenderer_update_units(self):
-        """Test update_units in gds_renderer.py."""
-        design = designs.DesignPlanar()
-        renderer = QGDSRenderer(design)
-
-        renderer.options['gds_unit'] = 12345
-        self.assertEqual(renderer.options['gds_unit'], 12345)
-
-        renderer._update_units()
-        self.assertEqual(renderer.options['gds_unit'], 0.001)
-
-    def test_renderer_gdsrenderer_midpoint_xy(self):
-        """Test midpoint_xy in gds_renderer.py."""
-        actual = QGDSRenderer._midpoint_xy(10, 15, 20, 30)
-        self.assertEqual(len(actual), 2)
-        self.assertEqual(actual[0], 15.0)
-        self.assertEqual(actual[1], 22.5)
-
-    # pylint: disable-msg=unused-variable
-    def test_renderer_gdsrenderer_check_qcomps(self):
-        """Test check_qcomps in gds_renderer.py."""
-        design = designs.DesignPlanar()
-        renderer = QGDSRenderer(design)
-
-        actual = []
-        actual.append(renderer._check_qcomps([]))
-        actual.append(renderer._check_qcomps(['Q1', 'Q2', 'Q3']))
-        actual.append(renderer._check_qcomps(['Q1', 'Q2', 'Q3', 'Q1']))
-
-        expected = []
-        expected.append(([], 0))
-        expected.append((['Q2', 'Q3', 'Q1'], 1))
-        expected.append((['Q2', 'Q3', 'Q1'], 1))
-
-        for x in range(3):
-            my_length = len(actual[x][0])
-            self.assertEqual(my_length, len(expected[x][0]))
-            self.assertEqual(actual[x][1], expected[x][1])
-
-            for y, _ in enumerate(expected[x][0]):
-                self.assertTrue(_ in actual[x][0])
-
-    def test_renderer_mpl_interaction_disconnect(self):
-        """Test disconnect in MplInteraction in mpl_interaction.py."""
-        mpl = MplInteraction(_plt)
-        mpl.disconnect()
-        self.assertEqual(mpl.figure, None)
-
-    def test_renderer_gds_check_cheese(self):
-        """Test check_cheese in gds_renderer.py."""
-        design = designs.DesignPlanar()
-        renderer = QGDSRenderer(design)
-
-        self.assertEqual(renderer._check_cheese('main', 0), 4)
-        self.assertEqual(renderer._check_cheese('main', 1), 1)
-        self.assertEqual(renderer._check_cheese('fake', 0), 3)
-
-    def test_renderer_gds_check_no_cheese(self):
-        """Test check_no_cheese in gds_renderer.py."""
-        design = designs.DesignPlanar()
-        renderer = QGDSRenderer(design)
-
-        self.assertEqual(renderer._check_no_cheese('main', 0), 4)
-        self.assertEqual(renderer._check_no_cheese('main', 1), 1)
-        self.assertEqual(renderer._check_no_cheese('fake', 0), 3)
-
-    def test_renderer_gds_check_either_cheese(self):
-        """Test check_either_cheese in gds_renderer.py."""
-        design = designs.DesignPlanar()
-        renderer = QGDSRenderer(design)
-
-        self.assertEqual(renderer._check_either_cheese('main', 0), 6)
-        self.assertEqual(renderer._check_either_cheese('main', 1), 1)
-        self.assertEqual(renderer._check_either_cheese('fake', 0), 5)
-
-
-if __name__ == '__main__':
-    unittest.main(verbosity=2)
+# -*- coding: utf-8 -*-
+
+# This code is part of Qiskit.
+#
+# (C) Copyright IBM 2017, 2021.
+#
+# This code is licensed under the Apache License, Version 2.0. You may
+# obtain a copy of this license in the LICENSE.txt file in the root directory
+# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+#
+# Any modifications or derivative works of this code must retain this
+# copyright notice, and modified files need to carry a notice indicating
+# that they have been altered from the originals.
+
+# pylint: disable-msg=unnecessary-pass
+# pylint: disable-msg=broad-except
+# pylint: disable-msg=too-many-public-methods
+# pylint: disable-msg=import-error
+# pylint: disable-msg=protected-access
+"""Qiskit Metal unit tests analyses functionality."""
+
+import unittest
+import matplotlib.pyplot as _plt
+
+from qiskit_metal import designs
+from qiskit_metal.renderers import setup_default
+from qiskit_metal.renderers.renderer_ansys.ansys_renderer import QAnsysRenderer
+from qiskit_metal.renderers.renderer_ansys.q3d_renderer import QQ3DRenderer
+from qiskit_metal.renderers.renderer_ansys.hfss_renderer import QHFSSRenderer
+from qiskit_metal.renderers.renderer_base.renderer_base import QRenderer
+from qiskit_metal.renderers.renderer_base.renderer_gui_base import QRendererGui
+from qiskit_metal.renderers.renderer_gds.gds_renderer import QGDSRenderer
+from qiskit_metal.renderers.renderer_mpl.mpl_interaction import MplInteraction
+
+from qiskit_metal.renderers.renderer_ansys import ansys_renderer
+
+from qiskit_metal.qgeometries.qgeometries_handler import QGeometryTables
+from qiskit_metal.qlibrary.qubits.transmon_pocket import TransmonPocket
+from qiskit_metal import draw
+
+
+class TestRenderers(unittest.TestCase):
+    """Unit test class."""
+
+    def setUp(self):
+        """Setup unit test."""
+        pass
+
+    def tearDown(self):
+        """Tie any loose ends."""
+        pass
+
+    def test_renderer_instanitate_qansys_renderer(self):
+        """Test instantiation of QAnsysRenderer in ansys_renderer.py"""
+        design = designs.DesignPlanar()
+        try:
+            QAnsysRenderer(design, initiate=False)
+        except Exception:
+            self.fail("QAnsysRenderer() failed")
+
+    def test_renderer_instantiate_gdsrender(self):
+        """Test instantiation of QGDSRenderer in gds_renderer.py."""
+        design = designs.DesignPlanar()
+        try:
+            QGDSRenderer(design)
+        except Exception:
+            self.fail("QGDSRenderer(design) failed")
+
+        try:
+            QGDSRenderer(design, initiate=False)
+        except Exception:
+            self.fail("QGDSRenderer(design, initiate=False) failed")
+
+        try:
+            QGDSRenderer(design, initiate=False, render_template={})
+        except Exception:
+            self.fail(
+                "QGDSRenderer(design, initiate=False, render_template={}) failed"
+            )
+
+        try:
+            QGDSRenderer(design, initiate=False, render_options={})
+        except Exception:
+            self.fail(
+                "QGDSRenderer(design, initiate=False, render_options={}) failed"
+            )
+
+    def test_renderer_instantiate_mplinteraction(self):
+        """Test instantiation of MplInteraction in mpl_interaction.py."""
+        try:
+            MplInteraction(_plt)
+        except Exception:
+            self.fail("MplInteraction(None) failed")
+
+    def test_renderer_instantiate_qq3d_renderer(self):
+        """Test instantiation of QQ3DRenderer in q3d_render.py."""
+        design = designs.DesignPlanar()
+        try:
+            QQ3DRenderer(design, initiate=False)
+        except Exception:
+            self.fail("QQ3DRenderer failed")
+
+    def test_renderer_instantiate_qhfss_renderer(self):
+        """Test instantiation of QHFSSRenderer in q3d_render.py."""
+        design = designs.DesignPlanar()
+        try:
+            QHFSSRenderer(design, initiate=False)
+        except Exception:
+            self.fail("QHFSSRenderer failed")
+
+    def test_renderer_qansys_renderer_options(self):
+        """Test that defaults in QAnsysRenderer were not accidentally changed."""
+        design = designs.DesignPlanar()
+        renderer = QAnsysRenderer(design, initiate=False)
+        options = renderer.default_options
+
+        self.assertEqual(len(options), 14)
+        self.assertEqual(options['Lj'], '10nH')
+        self.assertEqual(options['Cj'], 0)
+        self.assertEqual(options['_Rj'], 0)
+        self.assertEqual(options['max_mesh_length_jj'], '7um')
+        self.assertEqual(options['max_mesh_length_port'], '7um')
+        self.assertEqual(options['project_path'], None)
+        self.assertEqual(options['project_name'], None)
+        self.assertEqual(options['design_name'], None)
+        self.assertEqual(options['x_buffer_width_mm'], 0.2)
+        self.assertEqual(options['y_buffer_width_mm'], 0.2)
+        self.assertEqual(options['wb_threshold'], '400um')
+        self.assertEqual(options['wb_offset'], '0um')
+        self.assertEqual(options['wb_size'], 5)
+
+        self.assertEqual(len(options['plot_ansys_fields_options']), 13)
+        self.assertEqual(options['plot_ansys_fields_options']['name'],
+                         "NAME:Mag_E1")
+        self.assertEqual(
+            options['plot_ansys_fields_options']['UserSpecifyName'], '0')
+        self.assertEqual(
+            options['plot_ansys_fields_options']['UserSpecifyFolder'], '0')
+        self.assertEqual(options['plot_ansys_fields_options']['QuantityName'],
+                         "Mag_E")
+        self.assertEqual(options['plot_ansys_fields_options']['PlotFolder'],
+                         "E Field")
+        self.assertEqual(options['plot_ansys_fields_options']['StreamlinePlot'],
+                         "False")
+        self.assertEqual(
+            options['plot_ansys_fields_options']['AdjacentSidePlot'], "False")
+        self.assertEqual(options['plot_ansys_fields_options']['FullModelPlot'],
+                         "False")
+        self.assertEqual(options['plot_ansys_fields_options']['IntrinsicVar'],
+                         "Phase=\'0deg\'")
+        self.assertEqual(options['plot_ansys_fields_options']['PlotGeomInfo_0'],
+                         "1")
+        self.assertEqual(options['plot_ansys_fields_options']['PlotGeomInfo_1'],
+                         "Surface")
+        self.assertEqual(options['plot_ansys_fields_options']['PlotGeomInfo_2'],
+                         "FacesList")
+        self.assertEqual(options['plot_ansys_fields_options']['PlotGeomInfo_3'],
+                         "1")
+
+    def test_renderer_qansysrenderer_default_setup(self):
+        """Test that default_setup in QAnsysRenderer have not been accidentally changed."""
+        default_setup = QAnsysRenderer.default_setup
+
+        self.assertEqual(len(default_setup), 4)
+        self.assertEqual(len(default_setup['drivenmodal']), 8)
+        self.assertEqual(len(default_setup['eigenmode']), 9)
+        self.assertEqual(len(default_setup['q3d']), 12)
+        self.assertEqual(default_setup['port_inductor_gap'], '10um')
+
+        self.assertEqual(default_setup['drivenmodal']['name'], "Setup")
+        self.assertEqual(default_setup['drivenmodal']['freq_ghz'], '5.0')
+        self.assertEqual(default_setup['drivenmodal']['max_delta_s'], '0.1')
+        self.assertEqual(default_setup['drivenmodal']['max_passes'], '10')
+        self.assertEqual(default_setup['drivenmodal']['min_passes'], '1')
+        self.assertEqual(default_setup['drivenmodal']['min_converged'], '1')
+        self.assertEqual(default_setup['drivenmodal']['pct_refinement'], '30')
+        self.assertEqual(default_setup['drivenmodal']['basis_order'], '1')
+
+        self.assertEqual(default_setup['eigenmode']['name'], "Setup")
+        self.assertEqual(default_setup['eigenmode']['min_freq_ghz'], '1')
+        self.assertEqual(default_setup['eigenmode']['n_modes'], '1')
+        self.assertEqual(default_setup['eigenmode']['max_delta_f'], '0.5')
+        self.assertEqual(default_setup['eigenmode']['max_passes'], '10')
+        self.assertEqual(default_setup['eigenmode']['min_passes'], '1')
+        self.assertEqual(default_setup['eigenmode']['min_converged'], '1')
+        self.assertEqual(default_setup['eigenmode']['pct_refinement'], '30')
+        self.assertEqual(default_setup['eigenmode']['basis_order'], '-1')
+
+        self.assertEqual(default_setup['q3d']['name'], 'Setup')
+        self.assertEqual(default_setup['q3d']['freq_ghz'], '5.0')
+        self.assertEqual(default_setup['q3d']['save_fields'], 'False')
+        self.assertEqual(default_setup['q3d']['enabled'], 'True')
+        self.assertEqual(default_setup['q3d']['max_passes'], '15')
+        self.assertEqual(default_setup['q3d']['min_passes'], '2')
+        self.assertEqual(default_setup['q3d']['min_converged_passes'], '2')
+        self.assertEqual(default_setup['q3d']['percent_error'], '0.5')
+        self.assertEqual(default_setup['q3d']['percent_refinement'], '30')
+        self.assertEqual(default_setup['q3d']['auto_increase_solution_order'],
+                         'True')
+        self.assertEqual(default_setup['q3d']['solution_order'], 'High')
+        self.assertEqual(default_setup['q3d']['solver_type'], 'Iterative')
+
+    def test_renderer_qq3d_render_options(self):
+        """Test that defaults in QQ3DRenderer were not accidentally changed."""
+        design = designs.DesignPlanar()
+        renderer = QQ3DRenderer(design, initiate=False)
+        options = renderer.q3d_options
+
+        self.assertEqual(renderer.name, 'q3d')
+
+        self.assertEqual(len(options), 2)
+        self.assertEqual(options['material_type'], 'pec')
+        self.assertEqual(options['material_thickness'], '200nm')
+
+    def test_renderer_hfss_render_options(self):
+        """Test that defaults in QHFSSRender were not accidentally changed."""
+        design = designs.DesignPlanar()
+        renderer = QHFSSRenderer(design, initiate=False)
+        options = renderer.hfss_options
+
+        self.assertEqual(renderer.name, 'hfss')
+        self.assertEqual(len(options), 1)
+        self.assertEqual(options['port_inductor_gap'], '10um')
+
+    def test_renderer_gdsrenderer_options(self):
+        """Test that default_options in QGDSRenderer were not accidentally
+        changed."""
+        design = designs.DesignPlanar()
+        renderer = QGDSRenderer(design)
+        options = renderer.default_options
+
+        self.assertEqual(len(options), 17)
+        self.assertEqual(options['short_segments_to_not_fillet'], 'True')
+        self.assertEqual(options['check_short_segments_by_scaling_fillet'],
+                         '2.0')
+        self.assertEqual(options['gds_unit'], '1')
+        self.assertEqual(options['ground_plane'], 'True')
+        self.assertEqual(options['negative_mask']['main'], [])
+        self.assertEqual(options['corners'], 'circular bend')
+        self.assertEqual(options['tolerance'], '0.00001')
+        self.assertEqual(options['precision'], '0.000000001')
+        self.assertEqual(options['width_LineString'], '10um')
+        self.assertEqual(options['path_filename'],
+                         '../resources/Fake_Junctions.GDS')
+        self.assertEqual(options['junction_pad_overlap'], '5um')
+        self.assertEqual(options['max_points'], '199')
+        self.assertEqual(options['bounding_box_scale_x'], '1.2')
+        self.assertEqual(options['bounding_box_scale_y'], '1.2')
+
+        self.assertEqual(options['fabricate'], 'False')
+
+        self.assertEqual(len(options['cheese']), 9)
+        self.assertEqual(len(options['no_cheese']), 5)
+
+        self.assertEqual(options['cheese']['datatype'], '100')
+        self.assertEqual(options['cheese']['shape'], '0')
+        self.assertEqual(options['cheese']['cheese_0_x'], '25um')
+        self.assertEqual(options['cheese']['cheese_0_y'], '25um')
+        self.assertEqual(options['cheese']['cheese_1_radius'], '100um')
+        self.assertEqual(options['cheese']['delta_x'], '100um')
+        self.assertEqual(options['cheese']['delta_y'], '100um')
+        self.assertEqual(options['cheese']['edge_nocheese'], '200um')
+
+        self.assertEqual(options['no_cheese']['datatype'], '99')
+        self.assertEqual(options['no_cheese']['buffer'], '25um')
+        self.assertEqual(options['no_cheese']['cap_style'], '2')
+        self.assertEqual(options['no_cheese']['join_style'], '2')
+
+        self.assertEqual(len(options['cheese']['view_in_file']), 1)
+        self.assertEqual(len(options['cheese']['view_in_file']['main']), 1)
+        self.assertEqual(options['cheese']['view_in_file']['main'][1], True)
+
+        self.assertEqual(len(options['no_cheese']['view_in_file']), 1)
+        self.assertEqual(len(options['no_cheese']['view_in_file']['main']), 1)
+        self.assertEqual(options['no_cheese']['view_in_file']['main'][1], True)
+
+    def test_renderer_ansys_renderer_get_clean_name(self):
+        """Test get_clean_name in ansys_renderer.py"""
+        self.assertEqual(ansys_renderer.get_clean_name('name12'), 'name12')
+        self.assertEqual(ansys_renderer.get_clean_name('12name12'), 'name12')
+        self.assertEqual(ansys_renderer.get_clean_name('name!'), 'name')
+
+    def test_renderer_ansys_renderer_name_delim(self):
+        """Test NAME_DELIM in QAnsysRenderer."""
+        design = designs.DesignPlanar()
+        renderer = QAnsysRenderer(design, initiate=False)
+        self.assertEqual(renderer.NAME_DELIM, '_')
+
+    def test_renderer_ansys_renderer_name(self):
+        """Test name in QAnsysRenderer."""
+        design = designs.DesignPlanar()
+        renderer = QAnsysRenderer(design, initiate=False)
+        self.assertEqual(renderer.name, 'ansys')
+
+    def test_renderer_renderer_base_name(self):
+        """Test name in QRenderer."""
+        renderer = QRenderer
+        self.assertEqual(renderer.name, 'base')
+
+    def test_renderer_renderer_gui_base_name(self):
+        """Test name in QRenderer."""
+        renderer = QRendererGui
+        self.assertEqual(renderer.name, 'guibase')
+
+    def test_renderer_gdsrenderer_inclusive_bound(self):
+        """Test functionality of inclusive_bound in gds_renderer.py."""
+        design = designs.DesignPlanar()
+        renderer = QGDSRenderer(design)
+
+        my_list = []
+        my_list.append([1, 1, 2, 2])
+        my_list.append([3, 3, 5, 5])
+        my_list.append([2.2, 2.3, 4.4, 4.9])
+        self.assertEqual(renderer._inclusive_bound(my_list), (1, 1, 5, 5))
+
+    def test_renderer_scale_max_bounds(self):
+        """Test functionality of scale_max_bounds in gds_renderer.py."""
+        design = designs.DesignPlanar()
+        renderer = QGDSRenderer(design)
+
+        actual = renderer._scale_max_bounds('main', [(1, 1, 3, 3)])
+        self.assertEqual(len(actual), 2)
+        self.assertEqual(actual[0], (0.8, 0.8, 3.2, 3.2))
+        self.assertEqual(actual[1], (1, 1, 3, 3))
+
+    def test_renderer_get_chip_names(self):
+        """Test functionality of get_chip_names in gds_renderer.py."""
+        design = designs.DesignPlanar()
+        renderer = QGDSRenderer(design)
+
+        qgt = QGeometryTables(design)
+        qgt.clear_all_tables()
+
+        transmon_pocket_1 = TransmonPocket(design, 'my_id')
+        transmon_pocket_1.make()
+        transmon_pocket_1.get_template_options(design)
+
+        a_linestring = draw.LineString([[0, 0], [0, 1]])
+        a_poly = draw.rectangle(2, 2, 0, 0)
+        qgt.add_qgeometry('path',
+                          'my_id', {'n_sprial': a_linestring},
+                          width=4000)
+        qgt.add_qgeometry('poly',
+                          'my_id', {'n_spira_etch': a_poly},
+                          subtract=True)
+
+        result = renderer._get_chip_names()
+        self.assertEqual(result, {'main': {}})
+
+    def test_renderer_setup_renderers(self):
+        """Test setup_renderers in setup_defauts.py."""
+        actual = setup_default.setup_renderers()
+        self.assertEqual(actual, {})
+
+    def test_renderer_renderer_base_element_table_data(self):
+        """Test element_table_data in QRenderer."""
+        renderer = QRenderer
+        etd = renderer.element_table_data
+
+        self.assertEqual(len(etd), 0)
+
+    def test_renderer_ansys_renderer_element_table_data(self):
+        """Test element_table_data in QAnsysRenderer."""
+        design = designs.DesignPlanar()
+        renderer = QAnsysRenderer(design, initiate=False)
+        etd = renderer.element_table_data
+
+        self.assertEqual(len(etd), 2)
+        self.assertEqual(len(etd['path']), 1)
+        self.assertEqual(len(etd['junction']), 4)
+
+        self.assertEqual(etd['path']['wire_bonds'], False)
+        self.assertEqual(etd['junction']['inductance'], '10nH')
+        self.assertEqual(etd['junction']['capacitance'], 0)
+        self.assertEqual(etd['junction']['resistance'], 0)
+        self.assertEqual(etd['junction']['mesh_kw_jj'], 7e-06)
+
+    def test_renderer_gdsrenderer_high_level(self):
+        """Test that high level defaults were not accidentally changed in
+        gds_renderer.py."""
+        design = designs.DesignPlanar()
+        renderer = QGDSRenderer(design)
+
+        self.assertEqual(renderer.name, 'gds')
+        element_table_data = renderer.element_table_data
+        self.assertEqual(len(element_table_data), 1)
+        self.assertEqual(len(element_table_data['junction']), 1)
+        self.assertEqual(element_table_data['junction']['cell_name'],
+                         'my_other_junction')
+
+    def test_renderer_gdsrenderer_update_units(self):
+        """Test update_units in gds_renderer.py."""
+        design = designs.DesignPlanar()
+        renderer = QGDSRenderer(design)
+
+        renderer.options['gds_unit'] = 12345
+        self.assertEqual(renderer.options['gds_unit'], 12345)
+
+        renderer._update_units()
+        self.assertEqual(renderer.options['gds_unit'], 0.001)
+
+    def test_renderer_gdsrenderer_midpoint_xy(self):
+        """Test midpoint_xy in gds_renderer.py."""
+        actual = QGDSRenderer._midpoint_xy(10, 15, 20, 30)
+        self.assertEqual(len(actual), 2)
+        self.assertEqual(actual[0], 15.0)
+        self.assertEqual(actual[1], 22.5)
+
+    # pylint: disable-msg=unused-variable
+    def test_renderer_gdsrenderer_check_qcomps(self):
+        """Test check_qcomps in gds_renderer.py."""
+        design = designs.DesignPlanar()
+        renderer = QGDSRenderer(design)
+
+        actual = []
+        actual.append(renderer._check_qcomps([]))
+        actual.append(renderer._check_qcomps(['Q1', 'Q2', 'Q3']))
+        actual.append(renderer._check_qcomps(['Q1', 'Q2', 'Q3', 'Q1']))
+
+        expected = []
+        expected.append(([], 0))
+        expected.append((['Q2', 'Q3', 'Q1'], 1))
+        expected.append((['Q2', 'Q3', 'Q1'], 1))
+
+        for x in range(3):
+            my_length = len(actual[x][0])
+            self.assertEqual(my_length, len(expected[x][0]))
+            self.assertEqual(actual[x][1], expected[x][1])
+
+            for y, _ in enumerate(expected[x][0]):
+                self.assertTrue(_ in actual[x][0])
+
+    def test_renderer_mpl_interaction_disconnect(self):
+        """Test disconnect in MplInteraction in mpl_interaction.py."""
+        mpl = MplInteraction(_plt)
+        mpl.disconnect()
+        self.assertEqual(mpl.figure, None)
+
+    def test_renderer_gds_check_cheese(self):
+        """Test check_cheese in gds_renderer.py."""
+        design = designs.DesignPlanar()
+        renderer = QGDSRenderer(design)
+
+        self.assertEqual(renderer._check_cheese('main', 0), 4)
+        self.assertEqual(renderer._check_cheese('main', 1), 1)
+        self.assertEqual(renderer._check_cheese('fake', 0), 3)
+
+    def test_renderer_gds_check_no_cheese(self):
+        """Test check_no_cheese in gds_renderer.py."""
+        design = designs.DesignPlanar()
+        renderer = QGDSRenderer(design)
+
+        self.assertEqual(renderer._check_no_cheese('main', 0), 4)
+        self.assertEqual(renderer._check_no_cheese('main', 1), 1)
+        self.assertEqual(renderer._check_no_cheese('fake', 0), 3)
+
+    def test_renderer_gds_check_either_cheese(self):
+        """Test check_either_cheese in gds_renderer.py."""
+        design = designs.DesignPlanar()
+        renderer = QGDSRenderer(design)
+
+        self.assertEqual(renderer._check_either_cheese('main', 0), 6)
+        self.assertEqual(renderer._check_either_cheese('main', 1), 1)
+        self.assertEqual(renderer._check_either_cheese('fake', 0), 5)
+
+
+if __name__ == '__main__':
+    unittest.main(verbosity=2)