Skip to content

Frequently Asked Questions: Photonic Canvas

Download notebook (.zip)

Photonic Canvas keeps your design, your PDK version, your simulation results and your measured data together in one project, with versions and traceability.

Photonic Canvas projects need a Flexcompute account. Photonic Canvas is part of PhotonForge Enterprise.

In this notebook, you will learn how to:

  • make a project, open it again, and add your components, your PCell code and a PDK,
  • keep simulation files and measurement data with a component, and a DRC deck with the technology,
  • give a component, a technology or the whole project a numbered version,
  • share the project, and see when a colleague changes a component,
  • go back to an earlier version or to an earlier save,
  • see the whole project, its history and its files at one time.

Run the questions in sequence, from top to bottom. Each question uses the results of the questions before it. The notebook runs no simulations. It takes approximately two minutes.

  • Install PhotonForge and set your Flexcompute API key.
  • Put the folder misc/ next to this notebook. It contains the PCell file and the small files that the questions attach: waveguide_bend.py, design_review.txt, bend_sweep.py, basic_technology.drc, basic_technology_v2.drc and bend_loss.csv.
  • Each run makes a new project in your account. The last section tells you how to remove it.

A Photonic Canvas project works like a shared folder in a cloud drive:

A shared cloud drive compared with a Photonic Canvas project

There are two differences:

  • Your notebook works on its own copy of the project. Other people do not type into your copy live. Before you save, look for saves from other people with changed_since() (question 14).
  • A file that you keep in the project does not change. To change a file, attach a new file.

For photonics, this gives one place for all the parts of a tape-out, with their history: every save, and the versions that you name.

One project holds the technology, PCells, components, simulation files and measurement data, with numbered versions

TermMeaning
projectThe shared place for one piece of work, for example a tape-out. The project is on the server. You open it with its id.
componentA layout in the project, for example a bend or a reticle. You find a component by its name.
cellA component that is placed in another component, for example one of the nine gratings on a reticle.
technologyThe process stack that a component uses: layers, thicknesses and waveguide types.
PCellA Python function that makes a component from its parameters, for example waveguide_bend(radius=3.0).
sourceThe Python files in the project that contain your PCells. add_module_file() adds a file. A colleague runs import_module(globals()) one time, and the PCells are ready to use. A change to these files counts only after you save it. The tree shows unsaved changes in orange.
libraryA version of another project that you add to your project, for example a foundry PDK. You can then use its cells and its technology.
fileAnything that you keep in the project, for example a simulation script, a GDS file or measurement data. A file does not change after you attach it.
targetThe component or technology that a file belongs to. Use its name or the object. With no target, the file belongs to the whole project.
tagA label on a file, for example simulation or measurement. You use tags to find files again.
version tagThe tag version:1.0.0 on a file. It tells which version the file belongs to.
saveOne add() or update(). The server gets it immediately and keeps it.
versionA number that you give to one save, for example 1.0.0, with a message. The history records who made it and when. Anyone can rebuild it later.
markerThe point where a component was when you looked at it. Later, changed_since(marker) tells you if the component changed.

These commands only read. You can run them at any time.

To seeRun
what is in the projectproject.tree(), or print(project.tree()) for text
one component, with all its cells and filesproject.tree(target="bend")
all versions, with their datesproject.version_history()
which file belongs to which versionproject.traceability_matrix()
every save of a componentproject.edit_log("bend")
the saves after you lookedproject.changed_since(marker)

Import PhotonForge and its project interface, pda. Use the demo technology of PhotonForge as the default technology.

The questions use 6 small files. Download them into a folder named misc next to this notebook: basic_technology.drc, basic_technology_v2.drc, bend_loss.csv, bend_sweep.py, design_review.txt, waveguide_bend.py.

import subprocess
import sys
import tempfile
import time
from pathlib import Path
import photonforge as pf
from photonforge import pda
# the demo process stack of PhotonForge
pf.config.default_technology = pf.basic_technology()

Two short functions help in the questions below:

  • compare() shows components next to each other, at the same scale.
  • run_colleague() acts as a colleague. It starts a second Python program. That program opens the project and saves changes, as a person on a different computer does.
def compare(*pairs):
"""Show components next to each other, at the same scale, with titles.
Example: compare(("now", a), ("at 1.0.0", b))
"""
heights = [c.bounds()[1][1] - c.bounds()[0][1] for _, c in pairs]
text_size = 0.08 * max(heights) + 0.4
picture = pf.Component("compare")
x = 0.0
for title, component in pairs:
(x0, y0), (x1, y1) = component.bounds()
# align the tops of the components
picture.add(pf.Reference(component.copy(), origin=(x - x0, -y1)))
picture.add("METAL", *pf.text(title, text_size, (x, 1.5 * text_size)))
x += (x1 - x0) + 4 * text_size
return picture
COLLEAGUE = '''
import sys, time
from photonforge import pda
project = pda.load_project(project_id=sys.argv[1]) # open the project
project.import_module(globals()) # get its PCell code
name = sys.argv[2]
for change in sys.argv[3:]: # save each change alone
key, value = change.split("=")
component = project.component(name)
component.update(**{key: float(value)})
print("saved at", time.time(), flush=True)
project.update(component)
time.sleep(6)
'''
def run_colleague(project_id, name, *changes):
"""Run the program of the colleague, and wait until it stops.
Each change is one save, for example "radius=5.0".
Returns the times at which the colleague sent the saves.
"""
folder = Path(tempfile.mkdtemp()) # a different folder: a different computer
script = folder / "colleague.py"
script.write_text(COLLEAGUE)
done = subprocess.run(
[sys.executable, str(script), project_id, name, *changes],
cwd=folder, capture_output=True, text=True, timeout=300,
)
if done.returncode != 0:
raise RuntimeError(done.stderr[-2000:])
return [float(line.split()[-1]) for line in done.stdout.splitlines()
if line.startswith("saved at")]

A project is the shared place for one piece of work, for example a tape-out. It contains your components, their technology, the PCell code, the files that you keep with them, and their versions. The project is on the server. Your notebook works on a copy of it.

project = pda.create_project("FAQ project") # make a new private project
print("name:", project.name)
print("id :", project.id) # you and your colleagues open the project with it
name: FAQ project
id : T5Qb3m8XRcTQfEq79437hHXSaDb

Use its id. Each id is unique, but two projects can have the same name. Keep the id with your work, for example in a file next to the notebook.

Path("project_id.txt").write_text(project.id) # keep the id in a file
# open the project again, today or next week
same = pda.load_project(project_id=Path("project_id.txt").read_text())
print("opened:", same.name)
opened: FAQ project

pda.list_projects() can also find a project by its name. But it opens every project that you can see, so it is slow when you have many projects.

Make the component, give it a name, and add it with add(). Later calls find the component by this name.

The server gets each save immediately. add() and update() are the saves. There is no separate upload step. Each person who opens the project gets your save.

straight = pf.Component("straight_wg") # its name in the project
straight.add("WG_CORE", pf.Rectangle(center=(0, 0), size=(10, 0.5))) # 10 um
project.add(straight) # save it in the project
# Watch out: add() works one time for each name.
# To save a changed component later, use update().
project.component("straight_wg") # get it back by its name

Now the project has its technology and one component. Each box shows its kind in small letters, above a line. Each kind has its own color: technology (blue), library (purple), source (gray), component (green) and file (orange).

project.tree()
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e4efe9", "primaryBorderColor": "#00643c", "primaryTextColor": "#13231b", "lineColor": "#4a5b54", "tagLabelColor": "#13231b", "tagLabelBackground": "#ffffff", "tagLabelBorder": "#00643c", "commitLabelColor": "#13231b", "commitLabelBackground": "#ffffff", "git0": "#00643c", "gitBranchLabel0": "#ffffff", "gitInv0": "#00643c", "git1": "#6b4cc4", "gitBranchLabel1": "#ffffff", "gitInv1": "#6b4cc4", "git2": "#b3541e", "gitBranchLabel2": "#ffffff", "gitInv2": "#b3541e", "git3": "#1f6f8b", "gitBranchLabel3": "#ffffff", "gitInv3": "#1f6f8b", "git4": "#8a5209", "gitBranchLabel4": "#ffffff", "gitInv4": "#8a5209", "git5": "#7a3e9d", "gitBranchLabel5": "#ffffff", "gitInv5": "#7a3e9d", "git6": "#2f6b3f", "gitBranchLabel6": "#ffffff", "gitInv6": "#2f6b3f", "git7": "#a03d5f", "gitBranchLabel7": "#ffffff", "gitInv7": "#a03d5f"}}}%%
flowchart TB
  n0(["<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>PROJECT</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>FAQ project"]);
  n1["<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>TECHNOLOGY</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>Basic Technology"];
  n2["<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>COMPONENT</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>straight_wg<br/><b>not versioned</b>"];
  n0 --> n1
  n0 --> n2
  classDef project fill:#e4efe9,stroke:#00643c
  class n0 project
  classDef component fill:#e4efe9,stroke:#00643c
  class n2 component
  classDef technology fill:#e9f0f8,stroke:#3a6ea5
  class n1 technology

A PCell is Python code. To change a component that a PCell made, you need that code. Without the code, a colleague can see the bend but cannot set a new radius. So put the code in the project. add_module_file() copies your .py file into the project and gives it back to you, ready to use.

Our PCell is in misc/waveguide_bend.py. It makes a 90 degree bend with two parameters, radius and width:

print(Path("misc/waveguide_bend.py").read_text())
"""A waveguide bend PCell with two parameters: radius and width."""
import photonforge as pf
import photonforge.typing as pft
__version__ = "1.0"
@pf.parametric_component
def waveguide_bend(
*, radius: pft.PositiveDimension = 3.0, width: pft.PositiveDimension = 0.5
):
"""A 90 degree bend: a quarter of a ring, so radius and width show."""
c = pf.Component("bend") # the name of the component in the project
ring = pf.Circle(
radius=radius + width / 2, inner_radius=radius - width / 2, sector=(0, 90)
)
c.add("WG_CORE", ring)
return c
# Watch out: if a colleague changed the project code after you opened the
# project, this call stops with an error. Then call project.load_latest()
# first, so that you do not write over their files.
# add the PCell file to the project
bend_code = project.add_module_file("misc/waveguide_bend.py")
bend = bend_code.waveguide_bend(radius=3.0) # make one bend with the PCell
project.add(bend) # save it: its name is "bend"
time.sleep(3) # add() returns a moment before the server has the bend
bend

A colleague uses the code. The colleague opens the project and runs import_module(globals()) one time. This gets the project code and makes it ready to use in their notebook. Now the colleague can change the parameters of the bend.

colleague = pda.load_project(project_id=project.id) # a colleague opens it
# Watch out: import_module(globals()) adds a module with the name of each
# project ("faq_project" here). A variable with the same name is replaced.
colleague.import_module(globals()) # get the project code, one time per session
theirs = colleague.component("bend")
theirs.update(radius=5.0) # a new radius: this needs the PCell code
theirs # their own copy: nothing is saved

Now the project also has the bend, and the source that makes it.

project.tree()
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e4efe9", "primaryBorderColor": "#00643c", "primaryTextColor": "#13231b", "lineColor": "#4a5b54", "tagLabelColor": "#13231b", "tagLabelBackground": "#ffffff", "tagLabelBorder": "#00643c", "commitLabelColor": "#13231b", "commitLabelBackground": "#ffffff", "git0": "#00643c", "gitBranchLabel0": "#ffffff", "gitInv0": "#00643c", "git1": "#6b4cc4", "gitBranchLabel1": "#ffffff", "gitInv1": "#6b4cc4", "git2": "#b3541e", "gitBranchLabel2": "#ffffff", "gitInv2": "#b3541e", "git3": "#1f6f8b", "gitBranchLabel3": "#ffffff", "gitInv3": "#1f6f8b", "git4": "#8a5209", "gitBranchLabel4": "#ffffff", "gitInv4": "#8a5209", "git5": "#7a3e9d", "gitBranchLabel5": "#ffffff", "gitInv5": "#7a3e9d", "git6": "#2f6b3f", "gitBranchLabel6": "#ffffff", "gitInv6": "#2f6b3f", "git7": "#a03d5f", "gitBranchLabel7": "#ffffff", "gitInv7": "#a03d5f"}}}%%
flowchart TB
  n0(["<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>PROJECT</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>FAQ project"]);
  n1["<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>TECHNOLOGY</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>Basic Technology"];
  n2[["<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>SOURCE</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>1 file<br/>waveguide_bend.py"]];
  n3["<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>COMPONENT</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>straight_wg<br/><b>not versioned</b>"];
  n4["<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>COMPONENT</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>bend<br/><b>not versioned</b>"];
  n0 --> n1
  n0 --> n2
  n0 --> n3
  n0 --> n4
  classDef project fill:#e4efe9,stroke:#00643c
  class n0 project
  classDef component fill:#e4efe9,stroke:#00643c
  class n3,n4 component
  classDef technology fill:#e9f0f8,stroke:#3a6ea5
  class n1 technology
  classDef source fill:#eef1f3,stroke:#4a5b64
  class n2 source

A foundry PDK is published as a library: a version of another project. Add the library to your project. Then you can use its cells and its technology by their names, next to your own components.

# Find the library by its name, then add it with its id.
# Watch out: a search by name opens every project that you can see, so it
# can be slow. Do the search one time and keep the id.
versions = pda.list_libraries(name="SiEPIC EBeam", latest=False) # all versions
siepic_id = next(v["id"] for v in versions if v["version"] == "1.2.2")
project.add_library(library_id=siepic_id) # add the PDK to the project
project.import_module(globals()) # get the code of the PDK
gc = project.component("ebeam_gc_te1550") # a cell of the PDK, by its name
print("cell:", gc.name, "| technology:", gc.technology.name)
gc
cell: ebeam_gc_te1550 | technology: SiEPIC EBeam Si
Layout
Schematic

Now the library is next to our own technology, source and components. The cells of the library stay in the library. The project uses them by their names.

project.tree()
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e4efe9", "primaryBorderColor": "#00643c", "primaryTextColor": "#13231b", "lineColor": "#4a5b54", "tagLabelColor": "#13231b", "tagLabelBackground": "#ffffff", "tagLabelBorder": "#00643c", "commitLabelColor": "#13231b", "commitLabelBackground": "#ffffff", "git0": "#00643c", "gitBranchLabel0": "#ffffff", "gitInv0": "#00643c", "git1": "#6b4cc4", "gitBranchLabel1": "#ffffff", "gitInv1": "#6b4cc4", "git2": "#b3541e", "gitBranchLabel2": "#ffffff", "gitInv2": "#b3541e", "git3": "#1f6f8b", "gitBranchLabel3": "#ffffff", "gitInv3": "#1f6f8b", "git4": "#8a5209", "gitBranchLabel4": "#ffffff", "gitInv4": "#8a5209", "git5": "#7a3e9d", "gitBranchLabel5": "#ffffff", "gitInv5": "#7a3e9d", "git6": "#2f6b3f", "gitBranchLabel6": "#ffffff", "gitInv6": "#2f6b3f", "git7": "#a03d5f", "gitBranchLabel7": "#ffffff", "gitInv7": "#a03d5f"}}}%%
flowchart TB
  n0(["<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>PROJECT</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>FAQ project"]);
  n1["<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>TECHNOLOGY</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>Basic Technology"];
  n2("<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>LIBRARY</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>SiEPIC EBeam");
  n3[["<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>SOURCE</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>1 file<br/>waveguide_bend.py"]];
  n4["<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>COMPONENT</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>straight_wg<br/><b>not versioned</b>"];
  n5["<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>COMPONENT</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>bend<br/><b>not versioned</b>"];
  n0 --> n1
  n0 --> n2
  n0 --> n3
  n0 --> n4
  n0 --> n5
  classDef project fill:#e4efe9,stroke:#00643c
  class n0 project
  classDef component fill:#e4efe9,stroke:#00643c
  class n4,n5 component
  classDef technology fill:#e9f0f8,stroke:#3a6ea5
  class n1 technology
  classDef library fill:#f0ebf8,stroke:#6b4cc4
  class n2 library
  classDef source fill:#eef1f3,stroke:#4a5b64
  class n3 source

Use attach() with no target. The file then belongs to the whole project. This is correct for a file about the whole project, for example a design review.

# no target: the file belongs to the whole project
review = project.attach("misc/design_review.txt", tags=["review"])
print(review["filename"], review["tags"])
design_review.txt ['review']

Use two arguments:

  • target tells which component the file belongs to. Use the name of the component. Question 10 uses a technology as the target. With no target, as in question 6, the file belongs to the whole project.
  • tags is a list of your own labels. You use them to find the file again. Use words that your team agrees on, for example simulation, measurement or gds. One tag has a special meaning: the version tag version:1.0.0 tells which version the file belongs to (question 8).
# The file belongs to the bend. Its tag is "simulation".
project.attach("misc/bend_sweep.py", target="bend", tags=["simulation"])
# find the file again by its tag
for f in project.attachments(target="bend", tags=["simulation"]):
print(f["filename"], f["tags"])
bend_sweep.py ['simulation']

Now there is one file on the project and one file on the bend. Neither file belongs to a version yet (“no version”).

project.tree()
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e4efe9", "primaryBorderColor": "#00643c", "primaryTextColor": "#13231b", "lineColor": "#4a5b54", "tagLabelColor": "#13231b", "tagLabelBackground": "#ffffff", "tagLabelBorder": "#00643c", "commitLabelColor": "#13231b", "commitLabelBackground": "#ffffff", "git0": "#00643c", "gitBranchLabel0": "#ffffff", "gitInv0": "#00643c", "git1": "#6b4cc4", "gitBranchLabel1": "#ffffff", "gitInv1": "#6b4cc4", "git2": "#b3541e", "gitBranchLabel2": "#ffffff", "gitInv2": "#b3541e", "git3": "#1f6f8b", "gitBranchLabel3": "#ffffff", "gitInv3": "#1f6f8b", "git4": "#8a5209", "gitBranchLabel4": "#ffffff", "gitInv4": "#8a5209", "git5": "#7a3e9d", "gitBranchLabel5": "#ffffff", "gitInv5": "#7a3e9d", "git6": "#2f6b3f", "gitBranchLabel6": "#ffffff", "gitInv6": "#2f6b3f", "git7": "#a03d5f", "gitBranchLabel7": "#ffffff", "gitInv7": "#a03d5f"}}}%%
flowchart TB
  n0(["<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>PROJECT</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>FAQ project"]);
  n1["<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>TECHNOLOGY</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>Basic Technology"];
  n2("<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>LIBRARY</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>SiEPIC EBeam");
  n3[["<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>SOURCE</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>1 file<br/>waveguide_bend.py"]];
  n4>"design_review.txt<br/>no version"];
  n5["<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>COMPONENT</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>bend<br/><b>not versioned · 1 file</b>"];
  n6>"bend_sweep.py<br/>no version"];
  n7["<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>COMPONENT</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>straight_wg<br/><b>not versioned</b>"];
  n0 --> n1
  n0 --> n2
  n0 --> n3
  n0 -.- n4
  n0 --> n5
  n5 -.- n6
  n0 --> n7
  classDef project fill:#e4efe9,stroke:#00643c
  class n0 project
  classDef component fill:#e4efe9,stroke:#00643c
  class n5,n7 component
  classDef technology fill:#e9f0f8,stroke:#3a6ea5
  class n1 technology
  classDef library fill:#f0ebf8,stroke:#6b4cc4
  class n2 library
  classDef source fill:#eef1f3,stroke:#4a5b64
  class n3 source
  classDef file fill:#fdede6,stroke:#fc7a4c
  class n4,n6 file

add_version() gives the current save of the component a number and a message. It also puts the version tag on each file of the component that has no version tag yet. So attach the files first, then make the version.

The printouts say that the version “labels” a file: it puts the version tag on the file. The tree as text shows the result, before and after. The sweep script changes from “no version” to “1.0.0”.

print(project.tree(target="bend")) # before
# also tags the files of the bend that have no version tag
project.add_version("1.0.0", target="bend", message="first bend, radius 3 um")
print(project.tree(target="bend")) # after
FAQ project
└── component: bend · not versioned · 1 file · radius=3, width=0.5
└── file: bend_sweep.py (no version)
FAQ project
└── component: bend · 1.0.0 · 1 file · radius=3, width=0.5
└── file: bend_sweep.py (1.0.0)

The history now shows one lane for each component and one dot for each version.

project.version_history()
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e4efe9", "primaryBorderColor": "#00643c", "primaryTextColor": "#13231b", "lineColor": "#4a5b54", "tagLabelColor": "#13231b", "tagLabelBackground": "#ffffff", "tagLabelBorder": "#00643c", "commitLabelColor": "#13231b", "commitLabelBackground": "#ffffff", "fontSize": "15px", "tagLabelFontSize": "13px", "git0": "#00643c", "gitBranchLabel0": "#ffffff", "gitInv0": "#00643c", "git1": "#6b4cc4", "gitBranchLabel1": "#ffffff", "gitInv1": "#6b4cc4", "git2": "#b3541e", "gitBranchLabel2": "#ffffff", "gitInv2": "#b3541e", "git3": "#1f6f8b", "gitBranchLabel3": "#ffffff", "gitInv3": "#1f6f8b", "git4": "#8a5209", "gitBranchLabel4": "#ffffff", "gitInv4": "#8a5209", "git5": "#7a3e9d", "gitBranchLabel5": "#ffffff", "gitInv5": "#7a3e9d", "git6": "#2f6b3f", "gitBranchLabel6": "#ffffff", "gitInv6": "#2f6b3f", "git7": "#a03d5f", "gitBranchLabel7": "#ffffff", "gitInv7": "#a03d5f"}, "gitGraph": {"mainBranchName": "project", "showCommitLabel": false}}}%%
gitGraph
  commit id: "FAQ project"
  branch bend
  commit id: "bend 1.0.0" tag: "1.0.0"

9. How do I give the whole project a version?

Section titled “9. How do I give the whole project a version?”

Do not give a target. A project version keeps every component, the technology, the source and the libraries as they are now. It tags only the files that belong to the project itself. You can add a project that has a version to another project as a library, as question 5 adds a PDK.

project.add_version("0.1.0", message="first draft of the FAQ project")
project.version_history()
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e4efe9", "primaryBorderColor": "#00643c", "primaryTextColor": "#13231b", "lineColor": "#4a5b54", "tagLabelColor": "#13231b", "tagLabelBackground": "#ffffff", "tagLabelBorder": "#00643c", "commitLabelColor": "#13231b", "commitLabelBackground": "#ffffff", "fontSize": "15px", "tagLabelFontSize": "13px", "git0": "#00643c", "gitBranchLabel0": "#ffffff", "gitInv0": "#00643c", "git1": "#6b4cc4", "gitBranchLabel1": "#ffffff", "gitInv1": "#6b4cc4", "git2": "#b3541e", "gitBranchLabel2": "#ffffff", "gitInv2": "#b3541e", "git3": "#1f6f8b", "gitBranchLabel3": "#ffffff", "gitInv3": "#1f6f8b", "git4": "#8a5209", "gitBranchLabel4": "#ffffff", "gitInv4": "#8a5209", "git5": "#7a3e9d", "gitBranchLabel5": "#ffffff", "gitInv5": "#7a3e9d", "git6": "#2f6b3f", "gitBranchLabel6": "#ffffff", "gitInv6": "#2f6b3f", "git7": "#a03d5f", "gitBranchLabel7": "#ffffff", "gitInv7": "#a03d5f"}, "gitGraph": {"mainBranchName": "project", "showCommitLabel": false}}}%%
gitGraph
  commit id: "FAQ project"
  branch bend
  commit id: "bend 1.0.0" tag: "1.0.0"
  checkout project
  commit id: "the project 0.1.0" tag: "0.1.0"

10. How do I keep a DRC rule deck with the technology?

Section titled “10. How do I keep a DRC rule deck with the technology?”

A rule deck belongs to the process, not to one component. So keep the deck with the technology: use the name of the technology as the target. Then give the technology a version, as question 8 does for a component. The deck gets the version tag of that version.

Our components use the demo stack of PhotonForge, “Basic Technology”. Its deck, misc/basic_technology.drc, is a KLayout DRC script. It checks the minimum width and spacing of the waveguide core, the slab width, the metal width, and the distance from metal to waveguide core.

tech_name = pf.config.default_technology.name # "Basic Technology"
# the deck belongs to the technology
project.attach("misc/basic_technology.drc", target=tech_name, tags=["drc"])
# the version also tags the deck
project.add_version(
"1.0.0", target=tech_name, message="demo stack and its first DRC deck"
)
✓ Basic Technology is now version 1.0.0: "demo stack and its first DRC deck"
files labelled 1.0.0basic_technology.drc (it had no version label)
next: project.version_history("Basic Technology")

The fab changes a rule: the minimum core width changes from 0.18 µm to 0.2 µm. A file does not change after you attach it. So the new deck is a new file, and the technology gets a new version. The old deck stays, with the version tag version:1.0.0. Use it to check a component that was taped out with that deck.

# Watch out: attach the new deck BEFORE you make the new version.
# If not, the deck has no version tag.
project.attach("misc/basic_technology_v2.drc", target=tech_name, tags=["drc"])
project.add_version(
"1.1.0", target=tech_name, message="core width rule 0.18 -> 0.2 um"
)
project.tree(target=tech_name) # the technology and its two decks
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e4efe9", "primaryBorderColor": "#00643c", "primaryTextColor": "#13231b", "lineColor": "#4a5b54", "tagLabelColor": "#13231b", "tagLabelBackground": "#ffffff", "tagLabelBorder": "#00643c", "commitLabelColor": "#13231b", "commitLabelBackground": "#ffffff", "git0": "#00643c", "gitBranchLabel0": "#ffffff", "gitInv0": "#00643c", "git1": "#6b4cc4", "gitBranchLabel1": "#ffffff", "gitInv1": "#6b4cc4", "git2": "#b3541e", "gitBranchLabel2": "#ffffff", "gitInv2": "#b3541e", "git3": "#1f6f8b", "gitBranchLabel3": "#ffffff", "gitInv3": "#1f6f8b", "git4": "#8a5209", "gitBranchLabel4": "#ffffff", "gitInv4": "#8a5209", "git5": "#7a3e9d", "gitBranchLabel5": "#ffffff", "gitInv5": "#7a3e9d", "git6": "#2f6b3f", "gitBranchLabel6": "#ffffff", "gitInv6": "#2f6b3f", "git7": "#a03d5f", "gitBranchLabel7": "#ffffff", "gitInv7": "#a03d5f"}}}%%
flowchart TB
  n0(["<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>PROJECT</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>FAQ project"]);
  n1["<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>TECHNOLOGY</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>Basic Technology<br/>1.1.0 · 2 files"];
  n2>"basic_technology_v2.drc<br/>1.1.0"];
  n3>"basic_technology.drc<br/>1.0.0"];
  n0 --> n1
  n1 -.- n2
  n1 -.- n3
  classDef project fill:#e4efe9,stroke:#00643c
  class n0 project
  classDef technology fill:#e9f0f8,stroke:#3a6ea5
  class n1 technology
  classDef file fill:#fdede6,stroke:#fc7a4c
  class n2,n3 file

To run the deck of one version, find the deck by its tags and download it. Then run KLayout on your GDS file:

# the deck of version 1.1.0
deck = project.attachments(target=tech_name, tags=["drc", "version:1.1.0"])[0]
Path("drc_rules.drc").write_bytes(project.download_attachment(deck))
print("downloaded", deck["filename"], "as drc_rules.drc")
# Then, in a terminal:
# klayout -b -r drc_rules.drc -rd input=chip.gds -rd report=drc.lyrdb
downloaded basic_technology_v2.drc as drc_rules.drc

The owner shares the project with one colleague as editor, or with the whole organization as viewer

To share with one colleague, you need their user id, not their email address. The colleague runs pda.user_info() and sends you the id. To share with your whole organization, you need no id. The roles are viewer (read only) and editor.

my_user_id, my_org_id = pda.user_info()
print("my user id:", my_user_id)
# To share with one colleague:
# project.grant_permission(
# visibility="private", grantee_id="<their user id>", role="editor"
# )
# To share with your whole organization:
# project.grant_permission(visibility="organization", role="viewer")
for p in project.list_permissions(): # who can open the project now: only you
print(p["role"], p["visibility"], p["granteeId"])
my user id: user-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
owner private user-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

12. How do I know that a colleague changed a component?

Section titled “12. How do I know that a colleague changed a component?”

Take a marker with latest_head(). Later, changed_since(marker) lists each save after the marker. To get a message at the time of a save, use watch(). It runs a function of yours for each save.

Here the colleague is a real second Python program, as on a different computer (run_colleague() from the setup). It opens the project and saves the bend two times, each time with a larger width. This notebook watches.

marker = project.latest_head("bend") # where the bend is now
told = [] # the times at which we get a message
def tell_me(changes):
"""Runs in the background for each save to the bend. Keep it short."""
told.append(time.time())
# start to watch the bend, then let the colleague save two times
watcher = project.watch("bend", on_change=tell_me, fields="parameters")
saved = run_colleague(project.id, "bend", "width=0.7", "width=0.9")
time.sleep(3)
watcher.cancel() # stop the watcher
for saved_at, told_at in zip(saved, told):
print(f"the colleague saved; we were told {told_at - saved_at:.1f} s later")
the colleague saved; we were told 2.9 s later
the colleague saved; we were told 2.3 s later

The marker finds each save after it, also the saves that nobody watched. fields="parameters" counts only changes to the parameters of the PCell.

project.changed_since(marker, fields="parameters")
! bend changed since your marker: 2 saves
sessions6d01
nowradius 3.0, width 0.9
savesessionfieldchangenote
26d01width0.5 (default) -> 0.7
36d01width0.7 -> 0.9
next: project.load_latest(), then look before you save

edit_log() lists every save, oldest first. It also shows the values that later saves wrote over. It tells what changed and in which sequence. It does not tell who saved or when: a “session” is one run of Python, not a person.

project.edit_log("bend", fields="parameters")
Every save to bend, oldest first
sessions1267 (this session), 6d01
nowradius 3.0, width 0.9
savesessionfieldchangenote
11267parametersset to {}
26d01width0.5 (default) -> 0.7
36d01width0.7 -> 0.9
A session is one run of Python, not a person: the log has no names or times.
next: project.component("bend", save=<n>) rebuilds it as it was after save n

14. What if two people change the same parameter?

Section titled “14. What if two people change the same parameter?”

Alice sets the bend radius to 5 µm. Bob works on an older copy and sets the radius to 10 µm. The last save wins, and the save gives no warning. But nothing is lost. Alice’s 5.0 stays in the edit log, and Bob can see her save with changed_since() or edit_log().

Alice and Bob save the same parameter; the last save wins, and the earlier save stays in the log

In this example, this notebook is Bob. Alice is the second program again.

project.load_latest() # Bob gets the latest saves of everyone
marker = project.latest_head("bend") # and takes a marker
bobs_copy = project.component("bend")
run_colleague(project.id, "bend", "radius=5.0") # Alice saves radius 5.0
bobs_copy.update(radius=10.0) # Bob sets 10.0 on his older copy
project.update(bobs_copy) # and saves: the last save wins, with no warning
time.sleep(3)
project.changed_since(marker, fields="parameters") # both saves are in the log
! bend changed since your marker: 2 saves
sessionsdb30, 1267 (this session)
nowradius 10.0, width 0.9
savesessionfieldchangenote
4db30radius3.0 (default) -> 5.0
51267radius5.0 -> 10.0
next: project.load_latest(), then look before you save

You can rebuild each save with component(name, save=...). These two lines show what each save left:

# the two saves after the marker
alices, bobs = project.changed_since(marker, fields="parameters")
print("after Alice's save:",
project.component("bend", save=alices).parametric_kwargs)
print("after Bob's save :",
project.component("bend", save=bobs).parametric_kwargs)
after Alice's save: {'radius': 5.0, 'width': 0.9}
after Bob's save : {'radius': 10.0, 'width': 0.9}

To prevent this, look for new saves immediately before you save:

if project.changed_since(marker, fields="parameters"):
print("someone saved after you looked: call load_latest(), then look again")

Also, update() saves your whole copy. If your copy is old, update() also writes back the old values that you did not change. So call load_latest() before you edit.

Rebuild the component as it was at that version. Then save it as the current component. The later saves stay in the edit log. Nothing is deleted.

project.load_latest() # the latest saves of everyone
now = project.component("bend")
back = project.component("bend", version="1.0.0") # the bend at version 1.0.0
compare(("now", now), ("at version 1.0.0", back)) # the two bends, same scale
Layout
Schematic
project.update(back) # save the bend of version 1.0.0 as the current bend
time.sleep(3)
# Watch out: give a version only to what is on the server. Here our copy is
# current, because we saved it above. If not, call project.load_latest() first.
project.add_version("1.1.0", target="bend", message="back to the 1.0.0 bend")
print("the bend now:", project.component("bend").parametric_kwargs)
the bend now: {'radius': 3.0, 'width': 0.5}

Call load_latest() before you give something a version. Then the version is of what is on the server.

You can rebuild any save with component(name, save=...), with or without a version. Here are the bends that Alice and Bob saved in question 14. Neither save has a version:

compare(("Alice's save", project.component("bend", save=alices)),
("Bob's save", project.component("bend", save=bobs)))
Layout
Schematic

There are two meanings, and they can be different:

  • the latest saves of everyone: load_latest(), then component("bend"),
  • the newest version: the newest entry in version_history().
project.load_latest()
print("the latest saves :", project.component("bend").parametric_kwargs)
# the number of the newest version
newest = project.version_history("bend", kinds=["version"], limit=1)[0].label
print(f"version {newest} :",
project.component("bend", version=newest).parametric_kwargs)
the latest saves : {'radius': 3.0, 'width': 0.5}
version 1.1.0 : {'radius': 3.0, 'width': 0.5}

18. How do I keep measurements, and get the files of one version?

Section titled “18. How do I keep measurements, and get the files of one version?”

Attach the measurement data to the component that you measured. In its tags, write the version of the chip that you measured. A file with no version tag gets the next version that you make. That version can be different from the chip that you measured.

# Watch out: without "version:1.0.0" here, the next add_version() gives
# this file the wrong version tag.
project.attach(
"misc/bend_loss.csv", target="bend", tags=["measurement", "version:1.0.0"]
)
# Download all files of version 1.0.0
folder = Path("downloads/bend-1.0.0")
folder.mkdir(parents=True, exist_ok=True)
for f in project.attachments(target="bend", tags=["version:1.0.0"]):
(folder / f["filename"]).write_bytes(project.download_attachment(f))
print("downloaded", f["filename"])
downloaded bend_loss.csv
downloaded bend_sweep.py

traceability_matrix() shows which file belongs to which version. ✓ is the version that a file belongs to. · is a version of its component or technology that the file does not belong to.

project.traceability_matrix()
Which version each file belongs to, in "FAQ project"
filetarget0.1.01.0.01.1.0note
design_review.txtthe project✓
bend_loss.csvbend✓·
bend_sweep.pybend✓·
basic_technology_v2.drcBasic Technology·✓
basic_technology.drcBasic Technology✓·
✓ labelled for that version; · its target has that version, the file is not labelled for it; blank: its target has no such version.
next: project.trace(<file>) rebuilds the component a file belongs to

version_history() draws all the versions. .table() gives the same history as a table, with dates and messages. .mermaid() gives the source of the drawing, which you can paste into a wiki or a Markdown page.

project.version_history()
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e4efe9", "primaryBorderColor": "#00643c", "primaryTextColor": "#13231b", "lineColor": "#4a5b54", "tagLabelColor": "#13231b", "tagLabelBackground": "#ffffff", "tagLabelBorder": "#00643c", "commitLabelColor": "#13231b", "commitLabelBackground": "#ffffff", "fontSize": "15px", "tagLabelFontSize": "13px", "git0": "#00643c", "gitBranchLabel0": "#ffffff", "gitInv0": "#00643c", "git1": "#6b4cc4", "gitBranchLabel1": "#ffffff", "gitInv1": "#6b4cc4", "git2": "#b3541e", "gitBranchLabel2": "#ffffff", "gitInv2": "#b3541e", "git3": "#1f6f8b", "gitBranchLabel3": "#ffffff", "gitInv3": "#1f6f8b", "git4": "#8a5209", "gitBranchLabel4": "#ffffff", "gitInv4": "#8a5209", "git5": "#7a3e9d", "gitBranchLabel5": "#ffffff", "gitInv5": "#7a3e9d", "git6": "#2f6b3f", "gitBranchLabel6": "#ffffff", "gitInv6": "#2f6b3f", "git7": "#a03d5f", "gitBranchLabel7": "#ffffff", "gitInv7": "#a03d5f"}, "gitGraph": {"mainBranchName": "project", "showCommitLabel": false}}}%%
gitGraph
  commit id: "FAQ project"
  branch bend
  commit id: "bend 1.0.0" tag: "1.0.0"
  checkout project
  commit id: "the project 0.1.0" tag: "0.1.0"
  branch Basic_Technology
  commit id: "Basic Technology 1.0.0" tag: "1.0.0"
  commit id: "Basic Technology 1.1.0" tag: "1.1.0"
  checkout bend
  commit id: "bend 1.1.0" tag: "1.1.0"
project.version_history().table()
Version history of "FAQ project", newest first
whentargetversionmessagefiles labelledby
06 Nov 2026 14:02 (1 min ago)bend1.1.0back to the 1.0.0 bend-you
06 Nov 2026 14:01 (1 min ago)Basic Technology1.1.0core width rule 0.18 -> 0.2 umbasic_technology_v2.drcyou
06 Nov 2026 14:01 (1 min ago)Basic Technology1.0.0demo stack and its first DRC deckbasic_technology.drcyou
06 Nov 2026 14:01 (1 min ago)the project0.1.0first draft of the FAQ projectdesign_review.txtyou
06 Nov 2026 14:01 (1 min ago)bend1.0.0first bend, radius 3 umbend_loss.csv, bend_sweep.pyyou
Saves made between versions: project.edit_log("<component>").
next: project.load("bend", version="1.1.0") rebuilds a version

20. How do I see everything in the project at one time?

Section titled “20. How do I see everything in the project at one time?”

tree() shows the technology with its DRC decks, the library, the source, each component with its newest version, and each file with the version that it belongs to.

project.tree()
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e4efe9", "primaryBorderColor": "#00643c", "primaryTextColor": "#13231b", "lineColor": "#4a5b54", "tagLabelColor": "#13231b", "tagLabelBackground": "#ffffff", "tagLabelBorder": "#00643c", "commitLabelColor": "#13231b", "commitLabelBackground": "#ffffff", "git0": "#00643c", "gitBranchLabel0": "#ffffff", "gitInv0": "#00643c", "git1": "#6b4cc4", "gitBranchLabel1": "#ffffff", "gitInv1": "#6b4cc4", "git2": "#b3541e", "gitBranchLabel2": "#ffffff", "gitInv2": "#b3541e", "git3": "#1f6f8b", "gitBranchLabel3": "#ffffff", "gitInv3": "#1f6f8b", "git4": "#8a5209", "gitBranchLabel4": "#ffffff", "gitInv4": "#8a5209", "git5": "#7a3e9d", "gitBranchLabel5": "#ffffff", "gitInv5": "#7a3e9d", "git6": "#2f6b3f", "gitBranchLabel6": "#ffffff", "gitInv6": "#2f6b3f", "git7": "#a03d5f", "gitBranchLabel7": "#ffffff", "gitInv7": "#a03d5f"}}}%%
flowchart TB
  n0(["<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>PROJECT</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>FAQ project"]);
  n1["<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>TECHNOLOGY</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>Basic Technology<br/>1.1.0 · 2 files"];
  n2>"basic_technology_v2.drc<br/>1.1.0"];
  n3>"basic_technology.drc<br/>1.0.0"];
  n4("<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>LIBRARY</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>SiEPIC EBeam");
  n5[["<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>SOURCE</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>1 file<br/>waveguide_bend.py"]];
  n6>"design_review.txt<br/>0.1.0"];
  n7["<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>COMPONENT</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>bend<br/><b>1.1.0 · 2 files</b>"];
  n8>"bend_loss.csv<br/>1.0.0"];
  n9>"bend_sweep.py<br/>1.0.0"];
  n10["<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>COMPONENT</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>straight_wg<br/><b>not versioned</b>"];
  n0 --> n1
  n1 -.- n2
  n1 -.- n3
  n0 --> n4
  n0 --> n5
  n0 -.- n6
  n0 --> n7
  n7 -.- n8
  n7 -.- n9
  n0 --> n10
  classDef project fill:#e4efe9,stroke:#00643c
  class n0 project
  classDef component fill:#e4efe9,stroke:#00643c
  class n7,n10 component
  classDef technology fill:#e9f0f8,stroke:#3a6ea5
  class n1 technology
  classDef library fill:#f0ebf8,stroke:#6b4cc4
  class n4 library
  classDef source fill:#eef1f3,stroke:#4a5b64
  class n5 source
  classDef file fill:#fdede6,stroke:#fc7a4c
  class n2,n3,n6,n8,n9 file

The same tree as text. You can search it, copy it, or paste it into an email:

print(project.tree())
FAQ project
├── technology: Basic Technology · 1.1.0 · 2 files
│ ├── file: basic_technology_v2.drc (1.1.0)
│ └── file: basic_technology.drc (1.0.0)
├── library: SiEPIC EBeam
├── source: 1 file (waveguide_bend.py)
├── file: design_review.txt (0.1.0)
├── component: bend · 1.1.0 · 2 files
│ ├── file: bend_loss.csv (1.0.0)
│ └── file: bend_sweep.py (1.0.0)
└── component: straight_wg · not versioned

In a large project, the tree shows the files and the cells of a component as one row, for example “9 cells”. The row gives the call that lists them. To see all of one component, use target:

project.tree(target="bend")
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e4efe9", "primaryBorderColor": "#00643c", "primaryTextColor": "#13231b", "lineColor": "#4a5b54", "tagLabelColor": "#13231b", "tagLabelBackground": "#ffffff", "tagLabelBorder": "#00643c", "commitLabelColor": "#13231b", "commitLabelBackground": "#ffffff", "git0": "#00643c", "gitBranchLabel0": "#ffffff", "gitInv0": "#00643c", "git1": "#6b4cc4", "gitBranchLabel1": "#ffffff", "gitInv1": "#6b4cc4", "git2": "#b3541e", "gitBranchLabel2": "#ffffff", "gitInv2": "#b3541e", "git3": "#1f6f8b", "gitBranchLabel3": "#ffffff", "gitInv3": "#1f6f8b", "git4": "#8a5209", "gitBranchLabel4": "#ffffff", "gitInv4": "#8a5209", "git5": "#7a3e9d", "gitBranchLabel5": "#ffffff", "gitInv5": "#7a3e9d", "git6": "#2f6b3f", "gitBranchLabel6": "#ffffff", "gitInv6": "#2f6b3f", "git7": "#a03d5f", "gitBranchLabel7": "#ffffff", "gitInv7": "#a03d5f"}}}%%
flowchart TB
  n0(["<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>PROJECT</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>FAQ project"]);
  n1["<span style='font-size:0.78em;letter-spacing:0.04em;color:#4a5b54'>COMPONENT</span><hr style='margin:3px 0;border:0;border-top:1px solid #4a5b54'/>bend<br/><b>1.1.0 · 2 files · radius=3, width=0.5</b>"];
  n2>"bend_loss.csv<br/>1.0.0"];
  n3>"bend_sweep.py<br/>1.0.0"];
  n0 --> n1
  n1 -.- n2
  n1 -.- n3
  classDef project fill:#e4efe9,stroke:#00643c
  class n0 project
  classDef component fill:#e4efe9,stroke:#00643c
  class n1 component
  classDef file fill:#fdede6,stroke:#fc7a4c
  class n2,n3 file
  1. The server gets each save immediately. add() and update() are the saves. There is no separate upload step.
  2. add() returns a moment before the server has the component. Wait a moment before you read the component back.
  3. add() works one time for each name. Use update() for later changes.
  4. Each component needs its own name. A PCell must give each variant a name that it makes from its parameters.
  5. Attach the files first, then make the version. add_version() tags each file that has no version tag.
  6. Give measurement data the version tag of the chip that you measured, for example version:1.0.0. If not, the file gets the next version that you make.
  7. update() saves your whole copy. An old copy writes back values that you did not change. Call load_latest() before you edit.
  8. The last save wins, with no warning. Look at changed_since(marker) before you save.
  9. Call load_latest() before you make a version, so that the version is of what is on the server.
  10. add_module_file() stops with an error if the project code changed after you opened the project. Call load_latest() first.
  11. import_module(globals()) adds a module with the name of each project, for example faq_project. If you have a variable with the same name, the module replaces it.
  12. Open a project with its id. A search by name opens every project that you can see, so it is slow. Find a library by its name one time, and keep its id.
  13. A file does not change after you attach it. For a new edition of a file, attach a new file, with a tag that tells the two files apart.
  14. A change to the project code counts only after you save it, as for a component. add_module_file() saves a new file. save_module() saves changed and deleted files. The tree shows unsaved changes in orange.
  15. A target is a component, a technology, or nothing. Use the name or the object. A placed reference is not a target: use reference.component.

Each run of this notebook makes a new project in your account. To remove the project, retire it:

pda.retire_project(project_id=project.id)

To bring it back, use pda.restore_project(project_id=...).