siliconcompiler 0.35.1__py3-none-any.whl → 0.35.3__py3-none-any.whl
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.
- siliconcompiler/_metadata.py +1 -1
- siliconcompiler/apps/sc_install.py +1 -1
- siliconcompiler/apps/sc_issue.py +8 -16
- siliconcompiler/apps/smake.py +106 -100
- siliconcompiler/checklist.py +349 -91
- siliconcompiler/design.py +8 -1
- siliconcompiler/flowgraph.py +419 -130
- siliconcompiler/flows/showflow.py +1 -2
- siliconcompiler/library.py +6 -5
- siliconcompiler/package/https.py +10 -5
- siliconcompiler/project.py +87 -37
- siliconcompiler/remote/client.py +17 -6
- siliconcompiler/scheduler/scheduler.py +284 -59
- siliconcompiler/scheduler/schedulernode.py +154 -102
- siliconcompiler/schema/__init__.py +3 -2
- siliconcompiler/schema/_metadata.py +1 -1
- siliconcompiler/schema/baseschema.py +210 -93
- siliconcompiler/schema/namedschema.py +21 -13
- siliconcompiler/schema/parameter.py +8 -1
- siliconcompiler/schema/safeschema.py +18 -7
- siliconcompiler/schema_support/dependencyschema.py +23 -3
- siliconcompiler/schema_support/filesetschema.py +10 -4
- siliconcompiler/schema_support/option.py +37 -34
- siliconcompiler/schema_support/pathschema.py +7 -2
- siliconcompiler/schema_support/record.py +5 -4
- siliconcompiler/targets/asap7_demo.py +4 -1
- siliconcompiler/tool.py +100 -8
- siliconcompiler/tools/__init__.py +10 -7
- siliconcompiler/tools/bambu/convert.py +19 -0
- siliconcompiler/tools/builtin/__init__.py +3 -2
- siliconcompiler/tools/builtin/filter.py +108 -0
- siliconcompiler/tools/builtin/importfiles.py +154 -0
- siliconcompiler/tools/execute/exec_input.py +4 -3
- siliconcompiler/tools/gtkwave/show.py +6 -2
- siliconcompiler/tools/icarus/compile.py +1 -0
- siliconcompiler/tools/klayout/scripts/klayout_show.py +1 -1
- siliconcompiler/tools/klayout/show.py +17 -5
- siliconcompiler/tools/openroad/screenshot.py +0 -1
- siliconcompiler/tools/openroad/scripts/common/screenshot.tcl +1 -1
- siliconcompiler/tools/openroad/scripts/common/write_images.tcl +2 -0
- siliconcompiler/tools/openroad/show.py +10 -0
- siliconcompiler/tools/surfer/show.py +7 -2
- siliconcompiler/tools/verilator/compile.py +2 -2
- siliconcompiler/tools/yosys/prepareLib.py +7 -2
- siliconcompiler/tools/yosys/syn_asic.py +20 -2
- siliconcompiler/toolscripts/_tools.json +5 -5
- siliconcompiler/toolscripts/rhel9/{install-yosys-wildebeest.sh → install-wildebeest.sh} +5 -5
- siliconcompiler/toolscripts/ubuntu22/{install-yosys-wildebeest.sh → install-wildebeest.sh} +5 -5
- siliconcompiler/toolscripts/ubuntu24/{install-yosys-wildebeest.sh → install-wildebeest.sh} +5 -5
- siliconcompiler/utils/__init__.py +1 -2
- siliconcompiler/utils/issue.py +38 -45
- {siliconcompiler-0.35.1.dist-info → siliconcompiler-0.35.3.dist-info}/METADATA +4 -4
- {siliconcompiler-0.35.1.dist-info → siliconcompiler-0.35.3.dist-info}/RECORD +57 -55
- {siliconcompiler-0.35.1.dist-info → siliconcompiler-0.35.3.dist-info}/WHEEL +0 -0
- {siliconcompiler-0.35.1.dist-info → siliconcompiler-0.35.3.dist-info}/entry_points.txt +0 -0
- {siliconcompiler-0.35.1.dist-info → siliconcompiler-0.35.3.dist-info}/licenses/LICENSE +0 -0
- {siliconcompiler-0.35.1.dist-info → siliconcompiler-0.35.3.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from siliconcompiler import Flowgraph
|
|
2
2
|
from siliconcompiler import ShowTask
|
|
3
|
-
from siliconcompiler.tools import get_task
|
|
4
3
|
|
|
5
4
|
|
|
6
5
|
class ShowFlow(Flowgraph):
|
|
@@ -31,5 +30,5 @@ class ShowFlow(Flowgraph):
|
|
|
31
30
|
##################################################
|
|
32
31
|
if __name__ == "__main__":
|
|
33
32
|
from siliconcompiler import ShowTask
|
|
34
|
-
flow = ShowFlow(get_task(
|
|
33
|
+
flow = ShowFlow(ShowTask.get_task("gds"))
|
|
35
34
|
flow.write_flowgraph(f"{flow.name}.png")
|
siliconcompiler/library.py
CHANGED
|
@@ -7,7 +7,7 @@ from siliconcompiler.schema_support.filesetschema import FileSetSchema
|
|
|
7
7
|
from siliconcompiler.schema_support.pathschema import PathSchema
|
|
8
8
|
from siliconcompiler.schema import NamedSchema, BaseSchema
|
|
9
9
|
|
|
10
|
-
from siliconcompiler.schema import EditableSchema, Parameter, Scope, PerNode
|
|
10
|
+
from siliconcompiler.schema import EditableSchema, Parameter, Scope, PerNode, LazyLoad
|
|
11
11
|
from siliconcompiler.schema.utils import trim
|
|
12
12
|
|
|
13
13
|
|
|
@@ -99,7 +99,8 @@ class ToolLibrarySchema(LibrarySchema):
|
|
|
99
99
|
|
|
100
100
|
def _from_dict(self, manifest: Dict,
|
|
101
101
|
keypath: Union[List[str], Tuple[str, ...]],
|
|
102
|
-
version: Optional[Tuple[int, ...]] = None
|
|
102
|
+
version: Optional[Tuple[int, ...]] = None,
|
|
103
|
+
lazyload: LazyLoad = LazyLoad.ON) \
|
|
103
104
|
-> Tuple[Set[Tuple[str, ...]], Set[Tuple[str, ...]]]:
|
|
104
105
|
"""
|
|
105
106
|
Constructs a schema from a dictionary.
|
|
@@ -112,7 +113,7 @@ class ToolLibrarySchema(LibrarySchema):
|
|
|
112
113
|
Returns:
|
|
113
114
|
dict: The constructed dictionary.
|
|
114
115
|
"""
|
|
115
|
-
if "tool" in manifest:
|
|
116
|
+
if not lazyload.is_enforced and "tool" in manifest:
|
|
116
117
|
# collect tool keys
|
|
117
118
|
tool_keys = self.allkeys("tool")
|
|
118
119
|
|
|
@@ -136,7 +137,7 @@ class ToolLibrarySchema(LibrarySchema):
|
|
|
136
137
|
if not manifest["tool"]:
|
|
137
138
|
del manifest["tool"]
|
|
138
139
|
|
|
139
|
-
return super()._from_dict(manifest, keypath, version)
|
|
140
|
+
return super()._from_dict(manifest, keypath, version=version, lazyload=lazyload)
|
|
140
141
|
|
|
141
142
|
def _generate_doc(self, doc,
|
|
142
143
|
ref_root: str = "",
|
|
@@ -196,7 +197,7 @@ class ToolLibrarySchema(LibrarySchema):
|
|
|
196
197
|
return None
|
|
197
198
|
|
|
198
199
|
|
|
199
|
-
class StdCellLibrary(
|
|
200
|
+
class StdCellLibrary(DependencySchema, ToolLibrarySchema):
|
|
200
201
|
"""
|
|
201
202
|
A class for managing standard cell library schemas.
|
|
202
203
|
"""
|
siliconcompiler/package/https.py
CHANGED
|
@@ -114,11 +114,16 @@ class HTTPResolver(RemoteResolver):
|
|
|
114
114
|
except tarfile.ReadError:
|
|
115
115
|
fileobj.seek(0)
|
|
116
116
|
try:
|
|
117
|
-
with
|
|
118
|
-
|
|
119
|
-
except
|
|
120
|
-
|
|
121
|
-
|
|
117
|
+
with tarfile.open(fileobj=fileobj, mode='r:bz2') as tar_ref:
|
|
118
|
+
tar_ref.extractall(path=self.cache_path)
|
|
119
|
+
except tarfile.ReadError:
|
|
120
|
+
fileobj.seek(0)
|
|
121
|
+
try:
|
|
122
|
+
with zipfile.ZipFile(fileobj) as zip_ref:
|
|
123
|
+
zip_ref.extractall(path=self.cache_path)
|
|
124
|
+
except zipfile.BadZipFile:
|
|
125
|
+
raise TypeError(f"Could not extract file from {data_url}. "
|
|
126
|
+
"File is not a valid tar.gz or zip archive.")
|
|
122
127
|
|
|
123
128
|
# --- GitHub-specific directory flattening ---
|
|
124
129
|
# GitHub archives often have a single top-level directory like 'repo-v1.0'.
|
siliconcompiler/project.py
CHANGED
|
@@ -5,10 +5,11 @@ import uuid
|
|
|
5
5
|
|
|
6
6
|
import os.path
|
|
7
7
|
|
|
8
|
-
from typing import Union, List, Tuple, TextIO
|
|
8
|
+
from typing import Union, List, Tuple, TextIO, Optional, Dict, Set
|
|
9
9
|
|
|
10
10
|
from siliconcompiler.schema import BaseSchema, NamedSchema, EditableSchema, Parameter, Scope, \
|
|
11
|
-
__version__ as schema_version
|
|
11
|
+
__version__ as schema_version, \
|
|
12
|
+
LazyLoad
|
|
12
13
|
|
|
13
14
|
from siliconcompiler import Design
|
|
14
15
|
from siliconcompiler import Flowgraph
|
|
@@ -32,7 +33,6 @@ from siliconcompiler.utils import get_file_ext
|
|
|
32
33
|
from siliconcompiler.utils.multiprocessing import MPManager
|
|
33
34
|
from siliconcompiler.utils.paths import jobdir, workdir
|
|
34
35
|
from siliconcompiler.flows.showflow import ShowFlow
|
|
35
|
-
from siliconcompiler.tools import get_task
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
class Project(PathSchemaBase, CommandLineSchema, BaseSchema):
|
|
@@ -43,7 +43,7 @@ class Project(PathSchemaBase, CommandLineSchema, BaseSchema):
|
|
|
43
43
|
and reporting.
|
|
44
44
|
"""
|
|
45
45
|
|
|
46
|
-
def __init__(self, design: Union[Design, str] = None):
|
|
46
|
+
def __init__(self, design: Optional[Union[Design, str]] = None):
|
|
47
47
|
"""
|
|
48
48
|
Initializes a new Project instance.
|
|
49
49
|
|
|
@@ -108,7 +108,7 @@ class Project(PathSchemaBase, CommandLineSchema, BaseSchema):
|
|
|
108
108
|
is not intended for external use."""))
|
|
109
109
|
|
|
110
110
|
schema.insert("checklist", "default", Checklist())
|
|
111
|
-
schema.insert("library",
|
|
111
|
+
schema.insert("library", _ProjectLibrary())
|
|
112
112
|
schema.insert("flowgraph", "default", Flowgraph())
|
|
113
113
|
schema.insert("metric", MetricSchema())
|
|
114
114
|
schema.insert("record", RecordSchema())
|
|
@@ -290,26 +290,11 @@ class Project(PathSchemaBase, CommandLineSchema, BaseSchema):
|
|
|
290
290
|
"""
|
|
291
291
|
return Project.__name__
|
|
292
292
|
|
|
293
|
-
def
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
Args:
|
|
300
|
-
obj (DependencySchema, optional): An optional dependency object to
|
|
301
|
-
reset and populate. If None, all existing library dependencies
|
|
302
|
-
in the project are processed. Defaults to None.
|
|
303
|
-
"""
|
|
304
|
-
if obj:
|
|
305
|
-
obj._reset_deps()
|
|
306
|
-
dep_map = {name: self.get("library", name, field="schema")
|
|
307
|
-
for name in self.getkeys("library")}
|
|
308
|
-
for obj in dep_map.values():
|
|
309
|
-
if isinstance(obj, DependencySchema):
|
|
310
|
-
obj._populate_deps(dep_map)
|
|
311
|
-
|
|
312
|
-
def _from_dict(self, manifest, keypath, version=None):
|
|
293
|
+
def _from_dict(self, manifest: Dict,
|
|
294
|
+
keypath: Union[List[str], Tuple[str, ...]],
|
|
295
|
+
version: Optional[Tuple[int, ...]] = None,
|
|
296
|
+
lazyload: LazyLoad = LazyLoad.ON) \
|
|
297
|
+
-> Tuple[Set[Tuple[str, ...]], Set[Tuple[str, ...]]]:
|
|
313
298
|
"""
|
|
314
299
|
Populates the project's schema from a dictionary representation.
|
|
315
300
|
|
|
@@ -326,15 +311,13 @@ class Project(PathSchemaBase, CommandLineSchema, BaseSchema):
|
|
|
326
311
|
Returns:
|
|
327
312
|
Any: The result of the superclass's `_from_dict` method.
|
|
328
313
|
"""
|
|
329
|
-
ret = super()._from_dict(manifest, keypath, version)
|
|
330
|
-
|
|
331
|
-
# Restore dependencies
|
|
332
|
-
self.__populate_deps()
|
|
314
|
+
ret = super()._from_dict(manifest, keypath, version=version, lazyload=lazyload)
|
|
333
315
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
316
|
+
if not lazyload.is_enforced:
|
|
317
|
+
# Preserve logger in history
|
|
318
|
+
for history in self.getkeys("history"):
|
|
319
|
+
hist: "Project" = self.get("history", history, field="schema")
|
|
320
|
+
hist.__logger = self.__logger
|
|
338
321
|
|
|
339
322
|
return ret
|
|
340
323
|
|
|
@@ -393,7 +376,7 @@ class Project(PathSchemaBase, CommandLineSchema, BaseSchema):
|
|
|
393
376
|
self.add_dep(dep)
|
|
394
377
|
|
|
395
378
|
# Rebuild dependencies to ensure instances are correct
|
|
396
|
-
self.
|
|
379
|
+
self.get("library", field="schema")._populate_deps(obj)
|
|
397
380
|
|
|
398
381
|
def __import_flow(self, flow: Flowgraph):
|
|
399
382
|
"""
|
|
@@ -665,6 +648,9 @@ class Project(PathSchemaBase, CommandLineSchema, BaseSchema):
|
|
|
665
648
|
# Pass along manager address
|
|
666
649
|
state["__manager__"] = MPManager._get_manager_address()
|
|
667
650
|
|
|
651
|
+
# Pass along logger level
|
|
652
|
+
state["__loglevel__"] = self.logger.level
|
|
653
|
+
|
|
668
654
|
return state
|
|
669
655
|
|
|
670
656
|
def __setstate__(self, state):
|
|
@@ -677,6 +663,10 @@ class Project(PathSchemaBase, CommandLineSchema, BaseSchema):
|
|
|
677
663
|
Args:
|
|
678
664
|
state (dict): The deserialized state of the object.
|
|
679
665
|
"""
|
|
666
|
+
# Retrieve log level
|
|
667
|
+
loglevel = state["__loglevel__"]
|
|
668
|
+
del state["__loglevel__"]
|
|
669
|
+
|
|
680
670
|
# Retrieve manager address
|
|
681
671
|
MPManager._set_manager_address(state["__manager__"])
|
|
682
672
|
del state["__manager__"]
|
|
@@ -685,6 +675,7 @@ class Project(PathSchemaBase, CommandLineSchema, BaseSchema):
|
|
|
685
675
|
|
|
686
676
|
# Reinitialize logger on restore
|
|
687
677
|
self.__init_logger()
|
|
678
|
+
self.logger.setLevel(loglevel)
|
|
688
679
|
|
|
689
680
|
# Restore callbacks
|
|
690
681
|
self.__init_option_callbacks()
|
|
@@ -1155,7 +1146,7 @@ class Project(PathSchemaBase, CommandLineSchema, BaseSchema):
|
|
|
1155
1146
|
'''
|
|
1156
1147
|
tool_cls = ScreenshotTask if screenshot else ShowTask
|
|
1157
1148
|
|
|
1158
|
-
sc_jobname = self.
|
|
1149
|
+
sc_jobname = self.option.get_jobname()
|
|
1159
1150
|
sc_step, sc_index = None, None
|
|
1160
1151
|
|
|
1161
1152
|
has_filename = filename is not None
|
|
@@ -1220,12 +1211,13 @@ class Project(PathSchemaBase, CommandLineSchema, BaseSchema):
|
|
|
1220
1211
|
return None
|
|
1221
1212
|
|
|
1222
1213
|
# Create copy of project to avoid changing user project
|
|
1223
|
-
proj = self.copy()
|
|
1214
|
+
proj: Project = self.copy()
|
|
1224
1215
|
proj.set_flow(ShowFlow(task))
|
|
1225
1216
|
|
|
1226
1217
|
# Setup options:
|
|
1227
1218
|
for option, value in [
|
|
1228
1219
|
("track", False),
|
|
1220
|
+
("remote", False),
|
|
1229
1221
|
("hash", False),
|
|
1230
1222
|
("nodisplay", False),
|
|
1231
1223
|
("continue", True),
|
|
@@ -1242,7 +1234,7 @@ class Project(PathSchemaBase, CommandLineSchema, BaseSchema):
|
|
|
1242
1234
|
proj.set("option", "jobname", jobname)
|
|
1243
1235
|
|
|
1244
1236
|
# Setup in task variables
|
|
1245
|
-
task: ShowTask =
|
|
1237
|
+
task: ShowTask = task.find_task(proj)
|
|
1246
1238
|
task.set_showfilepath(filename)
|
|
1247
1239
|
task.set_showfiletype(filetype)
|
|
1248
1240
|
task.set_shownode(jobname=sc_jobname, step=sc_step, index=sc_index)
|
|
@@ -1277,3 +1269,61 @@ class Lint(Project):
|
|
|
1277
1269
|
@classmethod
|
|
1278
1270
|
def _getdict_type(cls) -> str:
|
|
1279
1271
|
return Lint.__name__
|
|
1272
|
+
|
|
1273
|
+
|
|
1274
|
+
class _ProjectLibrary(BaseSchema):
|
|
1275
|
+
def _from_dict(self, manifest: Dict,
|
|
1276
|
+
keypath: Union[List[str], Tuple[str, ...]],
|
|
1277
|
+
version: Optional[Tuple[int, ...]] = None,
|
|
1278
|
+
lazyload: LazyLoad = LazyLoad.ON) \
|
|
1279
|
+
-> Tuple[Set[Tuple[str, ...]], Set[Tuple[str, ...]]]:
|
|
1280
|
+
"""
|
|
1281
|
+
Populates the project's schema from a dictionary representation.
|
|
1282
|
+
|
|
1283
|
+
This method is typically used during deserialization or when loading
|
|
1284
|
+
a project state from a manifest. After loading the data, it ensures
|
|
1285
|
+
that internal dependencies are correctly re-established.
|
|
1286
|
+
|
|
1287
|
+
Args:
|
|
1288
|
+
manifest (dict): The dictionary containing the schema data.
|
|
1289
|
+
keypath (list): The current keypath being processed (used internally
|
|
1290
|
+
for recursive loading).
|
|
1291
|
+
version (str, optional): The schema version of the manifest. Defaults to None.
|
|
1292
|
+
|
|
1293
|
+
Returns:
|
|
1294
|
+
Any: The result of the superclass's `_from_dict` method.
|
|
1295
|
+
"""
|
|
1296
|
+
ret = super()._from_dict(manifest, keypath, version=version, lazyload=lazyload)
|
|
1297
|
+
|
|
1298
|
+
if not lazyload.is_enforced:
|
|
1299
|
+
# Restore dependencies
|
|
1300
|
+
self._populate_deps(complete=True)
|
|
1301
|
+
|
|
1302
|
+
return ret
|
|
1303
|
+
|
|
1304
|
+
def _populate_deps(self, obj: Optional[DependencySchema] = None, complete: bool = False):
|
|
1305
|
+
"""
|
|
1306
|
+
Ensures that all loaded dependencies (like libraries) within the project
|
|
1307
|
+
contain correct internal pointers back to the project's libraries.
|
|
1308
|
+
This is crucial for maintaining a consistent and navigable schema graph.
|
|
1309
|
+
|
|
1310
|
+
Args:
|
|
1311
|
+
obj (DependencySchema, optional): An optional dependency object to
|
|
1312
|
+
reset and populate. If None, all existing library dependencies
|
|
1313
|
+
in the project are processed. Defaults to None.
|
|
1314
|
+
complete (bool, optional): If True, performs a full reset of all
|
|
1315
|
+
DependencySchema objects before populating dependencies. This
|
|
1316
|
+
ensures a clean state during manifest deserialization. Defaults to False.
|
|
1317
|
+
"""
|
|
1318
|
+
if obj:
|
|
1319
|
+
obj._reset_deps()
|
|
1320
|
+
dep_map = {name: self.get(name, field="schema") for name in self.getkeys()}
|
|
1321
|
+
|
|
1322
|
+
if complete:
|
|
1323
|
+
for obj in dep_map.values():
|
|
1324
|
+
if isinstance(obj, DependencySchema):
|
|
1325
|
+
obj._reset_deps()
|
|
1326
|
+
|
|
1327
|
+
for obj in dep_map.values():
|
|
1328
|
+
if isinstance(obj, DependencySchema):
|
|
1329
|
+
obj._populate_deps(dep_map)
|
siliconcompiler/remote/client.py
CHANGED
|
@@ -18,7 +18,8 @@ from siliconcompiler import NodeStatus as SCNodeStatus
|
|
|
18
18
|
from siliconcompiler._metadata import default_server
|
|
19
19
|
from siliconcompiler.flowgraph import RuntimeFlowgraph
|
|
20
20
|
from siliconcompiler.scheduler import Scheduler
|
|
21
|
-
from siliconcompiler.schema import Journal
|
|
21
|
+
from siliconcompiler.schema import Journal, Parameter
|
|
22
|
+
from siliconcompiler.package import PythonPathResolver, FileResolver, KeyPathResolver
|
|
22
23
|
|
|
23
24
|
from siliconcompiler.utils.logging import get_console_formatter
|
|
24
25
|
from siliconcompiler.utils.curation import collect
|
|
@@ -573,19 +574,29 @@ service, provided by SiliconCompiler, is not intended to process proprietary IP.
|
|
|
573
574
|
|
|
574
575
|
# Ensure dataroots with python sources are copied
|
|
575
576
|
for key in self.__project.allkeys():
|
|
576
|
-
|
|
577
|
+
if key[0] == "history":
|
|
578
|
+
continue
|
|
579
|
+
|
|
580
|
+
param: Parameter = self.__project.get(*key, field=None)
|
|
581
|
+
key_type: str = param.get(field="type")
|
|
577
582
|
|
|
578
583
|
if 'dir' in key_type or 'file' in key_type:
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
584
|
+
schema_obj = self.__project.get(*key[:-1], field="schema")
|
|
585
|
+
dataroot_objs = schema_obj._find_files_dataroot_resolvers(True)
|
|
586
|
+
|
|
587
|
+
for value, step, index in param.getvalues():
|
|
588
|
+
if not value:
|
|
589
|
+
continue
|
|
590
|
+
dataroots = param.get(field='dataroot', step=step, index=index)
|
|
582
591
|
if not isinstance(dataroots, list):
|
|
583
592
|
dataroots = [dataroots]
|
|
584
593
|
force_copy = False
|
|
585
594
|
for dataroot in dataroots:
|
|
586
595
|
if not dataroot:
|
|
587
596
|
continue
|
|
588
|
-
|
|
597
|
+
dataroot_resolver = dataroot_objs.get(dataroot, None)
|
|
598
|
+
if isinstance(dataroot_resolver,
|
|
599
|
+
(PythonPathResolver, FileResolver, KeyPathResolver)):
|
|
589
600
|
force_copy = True
|
|
590
601
|
if force_copy:
|
|
591
602
|
self.__project.set(*key, True, field='copy', step=step, index=index)
|