nyancad 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nyancad-0.1.0/PKG-INFO +101 -0
- nyancad-0.1.0/README.md +79 -0
- nyancad-0.1.0/nyancad/__init__.py +9 -0
- nyancad-0.1.0/nyancad/anywidget.py +50 -0
- nyancad-0.1.0/nyancad/netlist.py +332 -0
- nyancad-0.1.0/nyancad.egg-info/PKG-INFO +101 -0
- nyancad-0.1.0/nyancad.egg-info/SOURCES.txt +10 -0
- nyancad-0.1.0/nyancad.egg-info/dependency_links.txt +1 -0
- nyancad-0.1.0/nyancad.egg-info/requires.txt +2 -0
- nyancad-0.1.0/nyancad.egg-info/top_level.txt +1 -0
- nyancad-0.1.0/pyproject.toml +37 -0
- nyancad-0.1.0/setup.cfg +4 -0
nyancad-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nyancad
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: NyanCAD Python library for schematic editor with anywidget integration
|
|
5
|
+
Author-email: Pepijn de Vos <me@pepijndevos.nl>
|
|
6
|
+
License: MPL-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/NyanCAD/Mosaic
|
|
8
|
+
Project-URL: Repository, https://github.com/NyanCAD/Mosaic
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: anywidget>=0.9.0
|
|
21
|
+
Requires-Dist: traitlets
|
|
22
|
+
|
|
23
|
+
# NyanCAD Python Library
|
|
24
|
+
|
|
25
|
+
A Python library for the [NyanCAD schematic editor](https://github.com/NyanCAD/Mosaic) with anywidget integration. Provides live access to schematic data from marimo notebooks.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install nyancad
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
### Basic Usage
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
import marimo as mo
|
|
39
|
+
from nyancad import SchematicBridge
|
|
40
|
+
|
|
41
|
+
# Create a schematic bridge widget
|
|
42
|
+
bridge = SchematicBridge()
|
|
43
|
+
|
|
44
|
+
# Display the widget (shows connection status)
|
|
45
|
+
bridge
|
|
46
|
+
|
|
47
|
+
# Access live schematic data
|
|
48
|
+
print(bridge.schematic_data)
|
|
49
|
+
|
|
50
|
+
# Send simulation data back to the editor
|
|
51
|
+
bridge.simulation_data = {
|
|
52
|
+
"results": [1.2, 3.4, 5.6],
|
|
53
|
+
"timestamp": "2024-01-01T00:00:00Z"
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Integration with Marimo
|
|
58
|
+
|
|
59
|
+
The SchematicBridge widget automatically connects to the NyanCAD editor running in the same marimo session. Any changes made in the schematic editor will be immediately reflected in the Python widget.
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
# In a marimo cell
|
|
63
|
+
bridge = SchematicBridge()
|
|
64
|
+
bridge # This will show the connection status
|
|
65
|
+
|
|
66
|
+
# In another marimo cell - access the live data
|
|
67
|
+
mo.md(f"""
|
|
68
|
+
## Schematic Analysis
|
|
69
|
+
Raw schematic data: {len(bridge.schematic_data)} items
|
|
70
|
+
""")
|
|
71
|
+
|
|
72
|
+
# View the actual schematic data structure
|
|
73
|
+
bridge.schematic_data
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Features
|
|
77
|
+
|
|
78
|
+
- **Live Sync**: Real-time synchronization with NyanCAD schematic editor via PouchDB
|
|
79
|
+
- **Bidirectional Communication**: Send simulation data back to the editor from Python
|
|
80
|
+
- **Zero Configuration**: Automatically detects and connects to the active schematic
|
|
81
|
+
- **Raw Data Access**: Direct access to the complete schematic data structure
|
|
82
|
+
- **Marimo Integration**: Seamless integration with marimo notebooks
|
|
83
|
+
|
|
84
|
+
## API Reference
|
|
85
|
+
|
|
86
|
+
### SchematicBridge
|
|
87
|
+
|
|
88
|
+
The main widget class that provides bidirectional communication with the Mosaic editor.
|
|
89
|
+
|
|
90
|
+
#### Properties
|
|
91
|
+
|
|
92
|
+
- `schematic_data` (dict): Raw schematic data from the Mosaic editor, automatically synced
|
|
93
|
+
- `simulation_data` (dict): Simulation data to send to the Mosaic editor. Setting this will store the data with a timestamp in the editor's database
|
|
94
|
+
|
|
95
|
+
## Development
|
|
96
|
+
|
|
97
|
+
This package is part of the [NyanCAD](https://github.com/NyanCAD/Mosaic) project. The anywidget integration uses a ClojureScript bridge that compiles to an ESM module, allowing seamless data sharing between the schematic editor and Python environment.
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
This project is licensed under the Mozilla Public License 2.0 - see the [LICENSE](../../LICENSE) file for details.
|
nyancad-0.1.0/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# NyanCAD Python Library
|
|
2
|
+
|
|
3
|
+
A Python library for the [NyanCAD schematic editor](https://github.com/NyanCAD/Mosaic) with anywidget integration. Provides live access to schematic data from marimo notebooks.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install nyancad
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Basic Usage
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
import marimo as mo
|
|
17
|
+
from nyancad import SchematicBridge
|
|
18
|
+
|
|
19
|
+
# Create a schematic bridge widget
|
|
20
|
+
bridge = SchematicBridge()
|
|
21
|
+
|
|
22
|
+
# Display the widget (shows connection status)
|
|
23
|
+
bridge
|
|
24
|
+
|
|
25
|
+
# Access live schematic data
|
|
26
|
+
print(bridge.schematic_data)
|
|
27
|
+
|
|
28
|
+
# Send simulation data back to the editor
|
|
29
|
+
bridge.simulation_data = {
|
|
30
|
+
"results": [1.2, 3.4, 5.6],
|
|
31
|
+
"timestamp": "2024-01-01T00:00:00Z"
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Integration with Marimo
|
|
36
|
+
|
|
37
|
+
The SchematicBridge widget automatically connects to the NyanCAD editor running in the same marimo session. Any changes made in the schematic editor will be immediately reflected in the Python widget.
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
# In a marimo cell
|
|
41
|
+
bridge = SchematicBridge()
|
|
42
|
+
bridge # This will show the connection status
|
|
43
|
+
|
|
44
|
+
# In another marimo cell - access the live data
|
|
45
|
+
mo.md(f"""
|
|
46
|
+
## Schematic Analysis
|
|
47
|
+
Raw schematic data: {len(bridge.schematic_data)} items
|
|
48
|
+
""")
|
|
49
|
+
|
|
50
|
+
# View the actual schematic data structure
|
|
51
|
+
bridge.schematic_data
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Features
|
|
55
|
+
|
|
56
|
+
- **Live Sync**: Real-time synchronization with NyanCAD schematic editor via PouchDB
|
|
57
|
+
- **Bidirectional Communication**: Send simulation data back to the editor from Python
|
|
58
|
+
- **Zero Configuration**: Automatically detects and connects to the active schematic
|
|
59
|
+
- **Raw Data Access**: Direct access to the complete schematic data structure
|
|
60
|
+
- **Marimo Integration**: Seamless integration with marimo notebooks
|
|
61
|
+
|
|
62
|
+
## API Reference
|
|
63
|
+
|
|
64
|
+
### SchematicBridge
|
|
65
|
+
|
|
66
|
+
The main widget class that provides bidirectional communication with the Mosaic editor.
|
|
67
|
+
|
|
68
|
+
#### Properties
|
|
69
|
+
|
|
70
|
+
- `schematic_data` (dict): Raw schematic data from the Mosaic editor, automatically synced
|
|
71
|
+
- `simulation_data` (dict): Simulation data to send to the Mosaic editor. Setting this will store the data with a timestamp in the editor's database
|
|
72
|
+
|
|
73
|
+
## Development
|
|
74
|
+
|
|
75
|
+
This package is part of the [NyanCAD](https://github.com/NyanCAD/Mosaic) project. The anywidget integration uses a ClojureScript bridge that compiles to an ESM module, allowing seamless data sharing between the schematic editor and Python environment.
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
This project is licensed under the Mozilla Public License 2.0 - see the [LICENSE](../../LICENSE) file for details.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Anywidget integration for Mosaic schematic editor.
|
|
3
|
+
|
|
4
|
+
Provides a live bridge to schematic data from the Mosaic editor via PouchDB.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import anywidget
|
|
8
|
+
import traitlets
|
|
9
|
+
import marimo as mo
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SchematicBridge(anywidget.AnyWidget):
|
|
13
|
+
"""
|
|
14
|
+
An anywidget that provides live bidirectional communication with the Mosaic editor.
|
|
15
|
+
|
|
16
|
+
The widget displays a simple status indicator and automatically syncs schematic
|
|
17
|
+
data from the Mosaic editor's PouchDB storage into the Python model, while also
|
|
18
|
+
enabling Python to send simulation data back to the editor.
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
schematic_data (dict): Live schematic data from the Mosaic editor
|
|
22
|
+
simulation_data (dict): Simulation data to send to the Mosaic editor.
|
|
23
|
+
Setting this will store the data with a timestamp in the editor's database.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
# ESM shim that dynamically imports the Shadow CLJS compiled anywidget module
|
|
27
|
+
# Uses window.location.origin to resolve the full URL since ESM runs from data: URL
|
|
28
|
+
_esm = """
|
|
29
|
+
const { render } = await import(`${window.location.origin}/js/anywidget.js`);
|
|
30
|
+
export default { render };
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
# Schematic data that gets synced from the ClojureScript side
|
|
34
|
+
schematic_data = traitlets.Dict().tag(sync=True)
|
|
35
|
+
|
|
36
|
+
# Simulation data that gets synced to the ClojureScript side
|
|
37
|
+
simulation_data = traitlets.Dict().tag(sync=True)
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def name(self):
|
|
41
|
+
return mo.query_params()["schem"]
|
|
42
|
+
|
|
43
|
+
def schematic_bridge():
|
|
44
|
+
"""
|
|
45
|
+
Create a SchematicBridge widget for use in Marimo notebooks.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
SchematicBridge: An instance of the SchematicBridge widget.
|
|
49
|
+
"""
|
|
50
|
+
return mo.ui.anywidget(SchematicBridge())
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2022 Pepijn de Vos
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
"""
|
|
5
|
+
This module communicates with CouchDB to fetch schematics, and generate SPICE netlists out of them.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from collections import deque, namedtuple
|
|
10
|
+
|
|
11
|
+
class SchemId(namedtuple("SchemId", ["cell", "model", "device"])):
|
|
12
|
+
@classmethod
|
|
13
|
+
def from_string(cls, id):
|
|
14
|
+
schem, dev, *_= id.split(':') + [None]
|
|
15
|
+
cell, model = schem.split('$')
|
|
16
|
+
return cls(cell, model, dev)
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def schem(self):
|
|
20
|
+
return f"{self.cell}${self.model}"
|
|
21
|
+
|
|
22
|
+
def shape_ports(shape):
|
|
23
|
+
for y, s in enumerate(shape):
|
|
24
|
+
for x, c in enumerate(s):
|
|
25
|
+
if c != ' ':
|
|
26
|
+
yield x, y, c
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
mosfet_shape = list(shape_ports([
|
|
30
|
+
" D ",
|
|
31
|
+
"GB ",
|
|
32
|
+
" S ",
|
|
33
|
+
]))
|
|
34
|
+
|
|
35
|
+
bjt_shape = list(shape_ports([
|
|
36
|
+
" C ",
|
|
37
|
+
"B ",
|
|
38
|
+
" E ",
|
|
39
|
+
]))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
twoport_shape = list(shape_ports([
|
|
43
|
+
" P ",
|
|
44
|
+
" ",
|
|
45
|
+
" N ",
|
|
46
|
+
]))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def rotate(shape, transform, devx, devy):
|
|
50
|
+
a, b, c, d, e, f = transform
|
|
51
|
+
width = max(max(x, y) for x, y, _ in shape)+1
|
|
52
|
+
mid = width/2-0.5
|
|
53
|
+
res = {}
|
|
54
|
+
for px, py, p in shape:
|
|
55
|
+
x = px-mid
|
|
56
|
+
y = py-mid
|
|
57
|
+
nx = a*x+c*y+e
|
|
58
|
+
ny = b*x+d*y+f
|
|
59
|
+
res[round(devx+nx+mid), round(devy+ny+mid)] = p
|
|
60
|
+
return res
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def getports(doc, models):
|
|
64
|
+
cell = doc['cell']
|
|
65
|
+
x = doc['x']
|
|
66
|
+
y = doc['y']
|
|
67
|
+
tr = doc.get('transform', [1, 0, 0, 1, 0, 0])
|
|
68
|
+
if cell == 'wire':
|
|
69
|
+
rx = doc['rx']
|
|
70
|
+
ry = doc['ry']
|
|
71
|
+
return {(x, y): None,
|
|
72
|
+
(x+rx, y+ry): None}
|
|
73
|
+
elif cell == 'text':
|
|
74
|
+
return {}
|
|
75
|
+
elif cell == 'port':
|
|
76
|
+
return {(x, y): doc['name']}
|
|
77
|
+
elif cell in {'nmos', 'pmos'}:
|
|
78
|
+
return rotate(mosfet_shape, tr, x, y)
|
|
79
|
+
elif cell in {'npn', 'pnp'}:
|
|
80
|
+
return rotate(bjt_shape, tr, x, y)
|
|
81
|
+
elif cell in {'resistor', 'capacitor', 'inductor', 'vsource', 'isource', 'diode'}:
|
|
82
|
+
return rotate(twoport_shape, tr, x, y)
|
|
83
|
+
else:
|
|
84
|
+
return rotate(models[f"models:{cell}"]['conn'], tr, x, y)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def port_index(docs, models):
|
|
88
|
+
wire_index = {}
|
|
89
|
+
device_index = {}
|
|
90
|
+
for doc in docs.values():
|
|
91
|
+
cell = doc['cell']
|
|
92
|
+
for (x, y), p in getports(doc, models).items():
|
|
93
|
+
if cell in {'wire', 'port'}:
|
|
94
|
+
wire_index.setdefault((x, y), []).append(doc)
|
|
95
|
+
else:
|
|
96
|
+
device_index.setdefault((x, y), []).append((p, doc))
|
|
97
|
+
# add a dummy net so two devices can connect directly
|
|
98
|
+
wire_index.setdefault((x, y), []).append({"cell": "wire", "x": x, "y": y, "rx": 0, "ry": 0})
|
|
99
|
+
return device_index, wire_index
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def wire_net(wireid, docs, models):
|
|
103
|
+
device_index, wire_index = port_index(docs, models)
|
|
104
|
+
netname = None
|
|
105
|
+
net = deque([docs[wireid]]) # all the wires on this net
|
|
106
|
+
while net:
|
|
107
|
+
doc = net.popleft() # take a wire from the net
|
|
108
|
+
cell = doc['cell']
|
|
109
|
+
if cell == 'wire':
|
|
110
|
+
wirename = doc.get('name')
|
|
111
|
+
if netname == None and wirename != None:
|
|
112
|
+
netname = wirename
|
|
113
|
+
for ploc in getports(doc, models).keys(): # get the wire ends
|
|
114
|
+
# if the wire connects to another wire,
|
|
115
|
+
# that we have not seen, add it to the net
|
|
116
|
+
if ploc in wire_index:
|
|
117
|
+
net.extend(wire_index.pop(ploc))
|
|
118
|
+
elif cell == 'port':
|
|
119
|
+
netname = doc.get('name')
|
|
120
|
+
else:
|
|
121
|
+
raise ValueError(cell)
|
|
122
|
+
return netname
|
|
123
|
+
|
|
124
|
+
def netlist(docs, models):
|
|
125
|
+
"""
|
|
126
|
+
Turn a collection of documents as returned by `get_docs` into a netlist structure.
|
|
127
|
+
Returns a dictionary of device ID: {port: net}
|
|
128
|
+
Usage:
|
|
129
|
+
```
|
|
130
|
+
async with SchematicService("http://localhost:5984/offline") as service:
|
|
131
|
+
name = "top$top"
|
|
132
|
+
seq, docs = await service.get_all_schem_docs(name)
|
|
133
|
+
print(netlist(docs[name], models))
|
|
134
|
+
```
|
|
135
|
+
"""
|
|
136
|
+
device_index, wire_index = port_index(docs, models)
|
|
137
|
+
nl = {}
|
|
138
|
+
netnum = 0
|
|
139
|
+
while wire_index: # while there are wires left
|
|
140
|
+
loc, locwires = wire_index.popitem() # take one
|
|
141
|
+
netname = None
|
|
142
|
+
net = deque(locwires) # all the wires on this net
|
|
143
|
+
netdevs = {} # all the devices on this net
|
|
144
|
+
while net:
|
|
145
|
+
doc = net.popleft() # take a wire from the net
|
|
146
|
+
cell = doc['cell']
|
|
147
|
+
if cell == 'wire':
|
|
148
|
+
wirename = doc.get('name')
|
|
149
|
+
if netname == None and wirename != None:
|
|
150
|
+
netname = wirename
|
|
151
|
+
for ploc in getports(doc, models).keys(): # get the wire ends
|
|
152
|
+
# if the wire connects to another wire,
|
|
153
|
+
# that we have not seen, add it to the net
|
|
154
|
+
if ploc in wire_index:
|
|
155
|
+
net.extend(wire_index.pop(ploc))
|
|
156
|
+
# if the wire connect to a device, add its port to netdevs
|
|
157
|
+
if ploc in device_index:
|
|
158
|
+
for p, dev in device_index[ploc]:
|
|
159
|
+
netdevs.setdefault(dev['_id'], []).append(p)
|
|
160
|
+
elif cell == 'port':
|
|
161
|
+
netname = doc.get('name')
|
|
162
|
+
else:
|
|
163
|
+
raise ValueError(cell)
|
|
164
|
+
if netname == None:
|
|
165
|
+
netname = f"net{netnum}"
|
|
166
|
+
netnum += 1
|
|
167
|
+
for k, v in netdevs.items():
|
|
168
|
+
nl.setdefault(netname, {}).setdefault(k, []).extend(v)
|
|
169
|
+
inl = {}
|
|
170
|
+
for net, devs in nl.items():
|
|
171
|
+
for dev, pts in devs.items():
|
|
172
|
+
for port in pts:
|
|
173
|
+
inl.setdefault(dev, {})[port] = net
|
|
174
|
+
return inl
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def print_props(props):
|
|
178
|
+
prs = []
|
|
179
|
+
for k, v in props.items():
|
|
180
|
+
if k == "model":
|
|
181
|
+
prs.insert(0, v)
|
|
182
|
+
elif k == "spice":
|
|
183
|
+
prs.append(v)
|
|
184
|
+
else:
|
|
185
|
+
prs.append(f"{k}={v}")
|
|
186
|
+
return " ".join(prs)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def circuit_spice(docs, models, declarations, corner, sim):
|
|
190
|
+
nl = netlist(docs, models)
|
|
191
|
+
cir = []
|
|
192
|
+
for id, ports in nl.items():
|
|
193
|
+
dev = docs[id]
|
|
194
|
+
cell = dev['cell']
|
|
195
|
+
mname = dev.get('props', {}).get('model', '')
|
|
196
|
+
name = dev.get('name') or id
|
|
197
|
+
# print(ports)
|
|
198
|
+
def p(p): return ports[p]
|
|
199
|
+
propstr = print_props(dev.get('props', {}))
|
|
200
|
+
if cell == "resistor":
|
|
201
|
+
ports = ' '.join(p(c) for c in ['P', 'N'])
|
|
202
|
+
templ = "R{name} {ports} {properties}"
|
|
203
|
+
elif cell == "capacitor":
|
|
204
|
+
ports = ' '.join(p(c) for c in ['P', 'N'])
|
|
205
|
+
templ = "C{name} {ports} {properties}"
|
|
206
|
+
elif cell == "inductor":
|
|
207
|
+
ports = ' '.join(p(c) for c in ['P', 'N'])
|
|
208
|
+
templ = "L{name} {ports} {properties}"
|
|
209
|
+
elif cell == "diode":
|
|
210
|
+
ports = ' '.join(p(c) for c in ['P', 'N'])
|
|
211
|
+
templ = "D{name} {ports} {properties}"
|
|
212
|
+
elif cell == "vsource":
|
|
213
|
+
ports = ' '.join(p(c) for c in ['P', 'N'])
|
|
214
|
+
templ = "V{name} {ports} {properties}"
|
|
215
|
+
elif cell == "isource":
|
|
216
|
+
ports = ' '.join(p(c) for c in ['P', 'N'])
|
|
217
|
+
templ = "I{name} {ports} {properties}"
|
|
218
|
+
elif cell in {"pmos", "nmos"}:
|
|
219
|
+
ports = ' '.join(p(c) for c in ['D', 'G', 'S', 'B'])
|
|
220
|
+
templ = "M{name} {ports} {properties}"
|
|
221
|
+
elif cell in {"npn", "pnp"}:
|
|
222
|
+
ports = ' '.join(p(c) for c in ['C', 'B', 'E'])
|
|
223
|
+
templ = "Q{name} {ports} {properties}"
|
|
224
|
+
else: # subcircuit
|
|
225
|
+
m = models[f"models:{cell}"]
|
|
226
|
+
ports = ' '.join(p(c[2]) for c in m['conn'])
|
|
227
|
+
templ = "X{name} {ports} {properties}"
|
|
228
|
+
|
|
229
|
+
# a spice type model can overwrite its reference
|
|
230
|
+
# for example if the mosfet is really a subcircuit
|
|
231
|
+
try:
|
|
232
|
+
m = models[f"models:{cell}"]["models"][mname][sim]
|
|
233
|
+
templ = m['reftempl']
|
|
234
|
+
declarations.add(m['decltempl'].format(corner=corner))
|
|
235
|
+
except KeyError:
|
|
236
|
+
pass
|
|
237
|
+
|
|
238
|
+
cir.append(templ.format(name=name, ports=ports, properties=propstr))
|
|
239
|
+
return '\n'.join(cir)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def spice_netlist(name, schem, extra="", corner='tt', temp=None, sim="NgSpice", **params):
|
|
243
|
+
"""
|
|
244
|
+
Generate a spice netlist, taking a dictionary of schematic documents, and the name of the top level schematic.
|
|
245
|
+
It is possible to pass extra SPICE code and specify the simulation corner.
|
|
246
|
+
"""
|
|
247
|
+
models = schem["models"]
|
|
248
|
+
declarations = set()
|
|
249
|
+
for subname, docs in schem.items():
|
|
250
|
+
if subname in {name, "models"}: continue
|
|
251
|
+
_id = SchemId.from_string(subname)
|
|
252
|
+
mod = models[f"models:{_id.cell}"]
|
|
253
|
+
ports = ' '.join(c[2] for c in mod['conn'])
|
|
254
|
+
body = circuit_spice(docs, models, declarations, corner, sim)
|
|
255
|
+
declarations.add(f".subckt {_id.model} {ports}\n{body}\n.ends {_id.model}") # parameters??
|
|
256
|
+
|
|
257
|
+
body = circuit_spice(schem[name], models, declarations, corner, sim)
|
|
258
|
+
ckt = []
|
|
259
|
+
ckt.append(f"* {name}")
|
|
260
|
+
ckt.extend(declarations)
|
|
261
|
+
ckt.append(body)
|
|
262
|
+
ckt.append(extra)
|
|
263
|
+
ckt.append(".end\n")
|
|
264
|
+
|
|
265
|
+
return "\n".join(ckt)
|
|
266
|
+
|
|
267
|
+
default_device_vectors = {
|
|
268
|
+
'resistor': ['i'],
|
|
269
|
+
'capacitor': ['i'],
|
|
270
|
+
'inductor': ['i'],
|
|
271
|
+
'vsource': ['i'],
|
|
272
|
+
'isource': [],
|
|
273
|
+
'diode': [],
|
|
274
|
+
'nmos': ['gm', 'id', 'vdsat'],
|
|
275
|
+
'pmos': ['gm', 'id', 'vdsat'],
|
|
276
|
+
'npn': ['gm', 'ic', 'ib'],
|
|
277
|
+
'pnp': ['gm', 'ic', 'ib'],
|
|
278
|
+
|
|
279
|
+
}
|
|
280
|
+
device_prefix = {
|
|
281
|
+
'resistor': 'r',
|
|
282
|
+
'capacitor': 'c',
|
|
283
|
+
'inductor': 'l',
|
|
284
|
+
'vsource': 'v',
|
|
285
|
+
'isource': 'i',
|
|
286
|
+
'diode': 'd',
|
|
287
|
+
'nmos': 'm',
|
|
288
|
+
'pmos': 'm',
|
|
289
|
+
'npn': 'q',
|
|
290
|
+
'pnp': 'q',
|
|
291
|
+
|
|
292
|
+
}
|
|
293
|
+
# @m.xx1.xmc1.msky130_fd_pr__nfet_01v8[gm]
|
|
294
|
+
def ngspice_vectors(name, schem, path=()):
|
|
295
|
+
"""
|
|
296
|
+
Extract all the relevant vectors from the schematic,
|
|
297
|
+
and format them in NgSpice syntax.
|
|
298
|
+
Saves label/port net names, and vectors indicated on spice models.
|
|
299
|
+
"""
|
|
300
|
+
models = schem["models"]
|
|
301
|
+
vectors = []
|
|
302
|
+
for id, elem in schem[name].items():
|
|
303
|
+
if elem['cell'] == 'port' and elem['name'].lower() != 'gnd':
|
|
304
|
+
vectors.append(('.'.join(path + (elem['name'],))).lower())
|
|
305
|
+
continue
|
|
306
|
+
m = models.get("models:"+elem['cell'], {})
|
|
307
|
+
n = m.get('models', {}).get(elem.get('props', {}).get('model'), {})
|
|
308
|
+
if n.get('type') == 'spice':
|
|
309
|
+
vex = n.get('NgSpice', {}).get('vectors', [])
|
|
310
|
+
comp = n.get('NgSpice', {}).get('component')
|
|
311
|
+
reftempl = n.get('NgSpice', {}).get('reftempl')
|
|
312
|
+
typ = (comp or reftempl or 'X')[0]
|
|
313
|
+
dtyp = (reftempl or 'X')[0]
|
|
314
|
+
if comp:
|
|
315
|
+
full = typ + '.' + '.'.join(path + (dtyp+elem['name'], comp))
|
|
316
|
+
elif path:
|
|
317
|
+
full = typ + '.' + '.'.join(path + (dtyp+elem['name'],))
|
|
318
|
+
else:
|
|
319
|
+
full = typ+elem['name']
|
|
320
|
+
vectors.extend(f"@{full}[{v}]".lower() for v in vex)
|
|
321
|
+
elif n.get('type') == 'schematic':
|
|
322
|
+
name = elem['cell']+"$"+elem['props']['model']
|
|
323
|
+
vectors.extend(ngspice_vectors(name, schem, path+("X"+elem['name'],)))
|
|
324
|
+
elif elem['cell'] in default_device_vectors: # no model specified
|
|
325
|
+
vex = default_device_vectors[elem['cell']]
|
|
326
|
+
typ = device_prefix.get(elem['cell'], 'x')
|
|
327
|
+
if path:
|
|
328
|
+
full = typ + '.' + '.'.join(path + (typ+elem['name'],))
|
|
329
|
+
else:
|
|
330
|
+
full = typ+elem['name']
|
|
331
|
+
vectors.extend(f"@{full}[{v}]".lower() for v in vex)
|
|
332
|
+
return vectors
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nyancad
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: NyanCAD Python library for schematic editor with anywidget integration
|
|
5
|
+
Author-email: Pepijn de Vos <me@pepijndevos.nl>
|
|
6
|
+
License: MPL-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/NyanCAD/Mosaic
|
|
8
|
+
Project-URL: Repository, https://github.com/NyanCAD/Mosaic
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: anywidget>=0.9.0
|
|
21
|
+
Requires-Dist: traitlets
|
|
22
|
+
|
|
23
|
+
# NyanCAD Python Library
|
|
24
|
+
|
|
25
|
+
A Python library for the [NyanCAD schematic editor](https://github.com/NyanCAD/Mosaic) with anywidget integration. Provides live access to schematic data from marimo notebooks.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install nyancad
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
### Basic Usage
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
import marimo as mo
|
|
39
|
+
from nyancad import SchematicBridge
|
|
40
|
+
|
|
41
|
+
# Create a schematic bridge widget
|
|
42
|
+
bridge = SchematicBridge()
|
|
43
|
+
|
|
44
|
+
# Display the widget (shows connection status)
|
|
45
|
+
bridge
|
|
46
|
+
|
|
47
|
+
# Access live schematic data
|
|
48
|
+
print(bridge.schematic_data)
|
|
49
|
+
|
|
50
|
+
# Send simulation data back to the editor
|
|
51
|
+
bridge.simulation_data = {
|
|
52
|
+
"results": [1.2, 3.4, 5.6],
|
|
53
|
+
"timestamp": "2024-01-01T00:00:00Z"
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Integration with Marimo
|
|
58
|
+
|
|
59
|
+
The SchematicBridge widget automatically connects to the NyanCAD editor running in the same marimo session. Any changes made in the schematic editor will be immediately reflected in the Python widget.
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
# In a marimo cell
|
|
63
|
+
bridge = SchematicBridge()
|
|
64
|
+
bridge # This will show the connection status
|
|
65
|
+
|
|
66
|
+
# In another marimo cell - access the live data
|
|
67
|
+
mo.md(f"""
|
|
68
|
+
## Schematic Analysis
|
|
69
|
+
Raw schematic data: {len(bridge.schematic_data)} items
|
|
70
|
+
""")
|
|
71
|
+
|
|
72
|
+
# View the actual schematic data structure
|
|
73
|
+
bridge.schematic_data
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Features
|
|
77
|
+
|
|
78
|
+
- **Live Sync**: Real-time synchronization with NyanCAD schematic editor via PouchDB
|
|
79
|
+
- **Bidirectional Communication**: Send simulation data back to the editor from Python
|
|
80
|
+
- **Zero Configuration**: Automatically detects and connects to the active schematic
|
|
81
|
+
- **Raw Data Access**: Direct access to the complete schematic data structure
|
|
82
|
+
- **Marimo Integration**: Seamless integration with marimo notebooks
|
|
83
|
+
|
|
84
|
+
## API Reference
|
|
85
|
+
|
|
86
|
+
### SchematicBridge
|
|
87
|
+
|
|
88
|
+
The main widget class that provides bidirectional communication with the Mosaic editor.
|
|
89
|
+
|
|
90
|
+
#### Properties
|
|
91
|
+
|
|
92
|
+
- `schematic_data` (dict): Raw schematic data from the Mosaic editor, automatically synced
|
|
93
|
+
- `simulation_data` (dict): Simulation data to send to the Mosaic editor. Setting this will store the data with a timestamp in the editor's database
|
|
94
|
+
|
|
95
|
+
## Development
|
|
96
|
+
|
|
97
|
+
This package is part of the [NyanCAD](https://github.com/NyanCAD/Mosaic) project. The anywidget integration uses a ClojureScript bridge that compiles to an ESM module, allowing seamless data sharing between the schematic editor and Python environment.
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
This project is licensed under the Mozilla Public License 2.0 - see the [LICENSE](../../LICENSE) file for details.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
nyancad
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "nyancad"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "NyanCAD Python library for schematic editor with anywidget integration"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = {text = "MPL-2.0"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Pepijn de Vos", email = "me@pepijndevos.nl"},
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.8",
|
|
21
|
+
"Programming Language :: Python :: 3.9",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"anywidget>=0.9.0",
|
|
28
|
+
"traitlets",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/NyanCAD/Mosaic"
|
|
33
|
+
Repository = "https://github.com/NyanCAD/Mosaic"
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.packages.find]
|
|
36
|
+
where = ["."]
|
|
37
|
+
include = ["nyancad*"]
|
nyancad-0.1.0/setup.cfg
ADDED