diff --git a/_unittest/test_00_EDB.py b/_unittest/test_00_EDB.py index 07d8b91afe3..9b7de90c094 100644 --- a/_unittest/test_00_EDB.py +++ b/_unittest/test_00_EDB.py @@ -10,7 +10,7 @@ test_project_name = "Galileo_edb" bom_example = "bom_example.csv" -from _unittest.conftest import config, desktop_version, local_path, scratch_path +from _unittest.conftest import config, desktop_version, local_path, scratch_path, is_ironpython try: import pytest @@ -532,3 +532,9 @@ def test_68_flip_layer_stackup(self): def test_69_create_solder_balls_on_component(self): assert self.edbapp.core_components.set_solder_ball("U2A5") + + @pytest.mark.skipif(is_ironpython, reason="This Test uses Ironpython") + def test_70_plot_on_matplotlib(self): + local_png = os.path.join(self.local_scratch.path, "test.png") + self.edbapp.core_nets.plot(None, None, save_plot=local_png) + assert os.path.exists(local_png) diff --git a/examples/00-EDB/05_Plot_nets.py b/examples/00-EDB/05_Plot_nets.py new file mode 100644 index 00000000000..2e868c852af --- /dev/null +++ b/examples/00-EDB/05_Plot_nets.py @@ -0,0 +1,56 @@ +""" +Plot Nets with Matplotlib +------------------------- +This example shows how to use EDB Class to plot a net or a layout. +""" + +############################################################################### +# Import Section +# ~~~~~~~~~~~~~~ +import shutil +import os +import tempfile +from pyaedt import generate_unique_name, examples, Edb + +############################################################################### +# File download +# ~~~~~~~~~~~~~ +# In this section the aedb file will be downloaded and copied in Temporary Folder. + +tmpfold = tempfile.gettempdir() +temp_folder = os.path.join(tmpfold, generate_unique_name("Example")) +if not os.path.exists(temp_folder): + os.makedirs(temp_folder) +example_path = examples.download_aedb() +targetfolder = os.path.join(temp_folder, "Galileo.aedb") +if os.path.exists(targetfolder): + shutil.rmtree(targetfolder) +shutil.copytree(example_path[:-8], targetfolder) + +############################################################################### +# Launch EDB +# ~~~~~~~~~~ +# This example launches the :class:`pyaedt.Edb` class. +# This example uses EDB 2021.2 and uses SI units. + +edb = Edb(edbpath=targetfolder, edbversion="2021.2") + +############################################################################### +# Plot a custom set of nets colored by Nets. + +edb.core_nets.plot(["VREF", "V3P3_S0"], color_by_layer=False) + +############################################################################### +# Plot a custom set of nets colored by Layer. + +edb.core_nets.plot("V3P3_S0", color_by_layer=True) + +############################################################################### +# Plot all nets on a layer colored by Nets. + +edb.core_nets.plot(None, ["TOP"], color_by_layer=False) + +############################################################################### +# Close Db + +edb.close_edb() diff --git a/examples/02-Maxwell/Maxwell3DTeam3.py b/examples/02-Maxwell/Maxwell3DTeam3.py index ac0174ce203..c8d0709e776 100644 --- a/examples/02-Maxwell/Maxwell3DTeam3.py +++ b/examples/02-Maxwell/Maxwell3DTeam3.py @@ -157,4 +157,4 @@ ############################################################################### # The electronics desktop is released from the script engine, we leave the desktop and project open. -M3D.release_desktop(False, False) +M3D.release_desktop(True, True) diff --git a/pyaedt/edb_core/nets.py b/pyaedt/edb_core/nets.py index a633a746f08..ac177b4816a 100644 --- a/pyaedt/edb_core/nets.py +++ b/pyaedt/edb_core/nets.py @@ -1,7 +1,20 @@ from __future__ import absolute_import +import warnings +import random +import time +from pyaedt.generic.general_methods import is_ironpython from pyaedt.generic.general_methods import aedt_exception_handler, generate_unique_name +try: + from matplotlib import pyplot as plt +except ImportError: + if not is_ironpython: + warnings.warn( + "The Matplotlib module is required to run some functionalities.\n" "Install with \npip install matplotlib" + ) + pass + class EdbNets(object): """Manages EDB functionalities for nets. @@ -100,6 +113,145 @@ def power_nets(self): nets[net] = value return nets + @aedt_exception_handler + def plot(self, nets, layers=None, color_by_layer=True, save_plot=None, outline=None): + """Plot a Net to Matplotlib 2D Chart. + + Parameters + ---------- + nets : str, list + Name of the net or list of nets to plot. If `None` all nets will be plotted. + layers : str, list + Name of the layers on which plot. If `None` all the signal layers will be considered. + color_by_layer : bool + If `True` then the plot will be colored by layer. + If `False` the plot will be colored by net. + save_plot : str, optional + If `None` the plot will be shown. + If a path is entered the plot will be saved to such path. + outline : list, optional + List of points of the outline to plot. + """ + start_time = time.time() + if is_ironpython: + self._logger.warning("Plot functionalities are enabled only in CPython.") + return False + if not layers: + layers = list(self._pedb.core_stackup.signal_layers.keys()) + if not nets: + nets = list(self.nets.keys()) + labels = {} + fig, ax = plt.subplots(figsize=(20, 10)) + if outline: + x1 = [i[0] for i in outline] + y1 = [i[1] for i in outline] + plt.fill(x1, y1, c="b", label="Outline", alpha=0.3) + + if isinstance(nets, str): + nets = [nets] + for path in self._pedb.core_primitives.paths: + net_name = path.GetNet().GetName() + layer_name = path.GetLayer().GetName() + if net_name in nets and layer_name in layers: + my_net_points = path.GetPolygonData().Points + x = [] + y = [] + for point in list(my_net_points): + if point.Y.ToDouble() < 1e100: + x.append(point.X.ToDouble()) + y.append(point.Y.ToDouble()) + if not x: + continue + if color_by_layer: + label = "Layer " + layer_name + if label not in labels: + color = path.GetLayer().GetColor() + try: + c = tuple([color.Item1 / 255, color.Item2 / 255, color.Item3 / 255]) + except: + c = "b" + labels[label] = c + plt.fill(x, y, c=labels[label], label=label, alpha=0.3) + else: + plt.fill(x, y, c=labels[label], alpha=0.3) + else: + label = "Net " + net_name + if label not in labels: + labels[label] = tuple( + [ + round(random.uniform(0, 1), 3), + round(random.uniform(0, 1), 3), + round(random.uniform(0, 1), 3), + ] + ) + plt.fill(x, y, c=labels[label], label=label, alpha=0.3) + else: + plt.fill(x, y, c=labels[label], alpha=0.3) + + for poly in self._pedb.core_primitives.polygons: + net_name = poly.GetNet().GetName() + layer_name = poly.GetLayer().GetName() + if net_name in nets and layer_name in layers: + + my_net_points = poly.GetPolygonData().Points + x = [] + y = [] + for point in list(my_net_points): + if point.Y.ToDouble() < 1e100: + x.append(point.X.ToDouble()) + y.append(point.Y.ToDouble()) + if not x: + continue + if color_by_layer: + label = "Layer " + layer_name + if label not in labels: + color = poly.GetLayer().GetColor() + try: + c = tuple([color.Item1 / 255, color.Item2 / 255, color.Item3 / 255]) + except: + c = "b" + labels[label] = c + plt.fill(x, y, c=labels[label], label=label, alpha=0.3) + else: + plt.fill(x, y, c=labels[label], alpha=0.3) + else: + label = "Net " + net_name + if label not in labels: + labels[label] = tuple( + [ + round(random.uniform(0, 1), 3), + round(random.uniform(0, 1), 3), + round(random.uniform(0, 1), 3), + ] + ) + plt.fill(x, y, c=labels[label], label=label, alpha=0.3) + else: + plt.fill(x, y, c=labels[label], alpha=0.3) + + for void in poly.Voids: + v_points = void.GetPolygonData().Points + x1 = [] + y1 = [] + for point in list(v_points): + if point.Y.ToDouble() < 1e100: + x1.append(point.X.ToDouble()) + y1.append(point.Y.ToDouble()) + if x1: + if "Voids" not in labels: + labels["Voids"] = "black" + plt.fill(x1, y1, c="black", alpha=1, label="Voids") + else: + plt.fill(x1, y1, c="black", alpha=1) + + ax.set(xlabel="X (m)", ylabel="Y (m)", title=self._pedb.active_cell.GetName()) + ax.legend() + end_time = time.time() - start_time + self._logger.info("Plot Generation time %s seconds", round(end_time, 3)) + if save_plot: + plt.savefig(save_plot) + else: + plt.show() + @aedt_exception_handler def is_power_gound_net(self, netname_list): """Determine if one of the nets in a list is power or ground.