zeon-project-scaffold 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.
- zeon_project_scaffold-0.1.0/LICENSE +21 -0
- zeon_project_scaffold-0.1.0/PKG-INFO +31 -0
- zeon_project_scaffold-0.1.0/README.md +13 -0
- zeon_project_scaffold-0.1.0/pyproject.toml +38 -0
- zeon_project_scaffold-0.1.0/setup.cfg +4 -0
- zeon_project_scaffold-0.1.0/src/project_scaffold/__init__.py +40 -0
- zeon_project_scaffold-0.1.0/src/project_scaffold/_scaffold.py +339 -0
- zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/CLAUDE.md +1 -0
- zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/objects/.gitkeep +0 -0
- zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/objects/bottle/bottle.object_model.yaml +17 -0
- zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/objects/bottle/bottle.urdf +4 -0
- zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/objects/bowl/bowl.object_model.yaml +17 -0
- zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/objects/bowl/bowl.urdf +4 -0
- zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/skills/.gitkeep +0 -0
- zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/skills/drop_object/metadata.yaml +40 -0
- zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/skills/drop_object/modules.py +1 -0
- zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/skills/drop_object/robotic_code.py +47 -0
- zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/skills/grab_object/metadata.yaml +34 -0
- zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/skills/grab_object/modules.py +1 -0
- zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/skills/grab_object/robotic_code.py +56 -0
- zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/skills/move_object/metadata.yaml +33 -0
- zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/skills/move_object/modules.py +1 -0
- zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/skills/move_object/robotic_code.py +17 -0
- zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/workflows/.gitkeep +0 -0
- zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/workflows/pick_place.json +71 -0
- zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/worlds/.gitkeep +0 -0
- zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/worlds/bowl_bottle/blox_voxels.npz +0 -0
- zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/worlds/bowl_bottle/world_state.json +469 -0
- zeon_project_scaffold-0.1.0/src/zeon_project_scaffold.egg-info/PKG-INFO +31 -0
- zeon_project_scaffold-0.1.0/src/zeon_project_scaffold.egg-info/SOURCES.txt +31 -0
- zeon_project_scaffold-0.1.0/src/zeon_project_scaffold.egg-info/dependency_links.txt +1 -0
- zeon_project_scaffold-0.1.0/src/zeon_project_scaffold.egg-info/top_level.txt +1 -0
- zeon_project_scaffold-0.1.0/tests/test_scaffold.py +236 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zeon Systems
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zeon-project-scaffold
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Canonical Zeon project layout — shared by CLI `zeon init` and sync-service POST /projects.
|
|
5
|
+
Author-email: Zeon Team <team@zeonsystems.app>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://zeonsystems.app
|
|
8
|
+
Project-URL: Repository, https://github.com/ZeonSystems/everything-prototype-containers
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# Zeon Project Scaffold
|
|
20
|
+
|
|
21
|
+
Canonical project layout and templates for Zeon projects.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install zeon-project-scaffold
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
This library is primarily used by the `zeon` CLI and the Zeon Sync service to ensure consistent project structures.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Zeon Project Scaffold
|
|
2
|
+
|
|
3
|
+
Canonical project layout and templates for Zeon projects.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install zeon-project-scaffold
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
This library is primarily used by the `zeon` CLI and the Zeon Sync service to ensure consistent project structures.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "zeon-project-scaffold"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Canonical Zeon project layout — shared by CLI `zeon init` and sync-service POST /projects."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [{ name = "Zeon Team", email = "team@zeonsystems.app" }]
|
|
11
|
+
license = "MIT"
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Programming Language :: Python :: 3.10",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
]
|
|
20
|
+
dependencies = []
|
|
21
|
+
|
|
22
|
+
[project.urls]
|
|
23
|
+
Homepage = "https://zeonsystems.app"
|
|
24
|
+
Repository = "https://github.com/ZeonSystems/everything-prototype-containers"
|
|
25
|
+
|
|
26
|
+
[tool.setuptools.packages.find]
|
|
27
|
+
where = ["src"]
|
|
28
|
+
include = ["project_scaffold*"]
|
|
29
|
+
|
|
30
|
+
[tool.setuptools.package-dir]
|
|
31
|
+
"" = "src"
|
|
32
|
+
|
|
33
|
+
[tool.setuptools.package-data]
|
|
34
|
+
project_scaffold = [
|
|
35
|
+
"templates/default/**/*",
|
|
36
|
+
"templates/default/.gitkeep",
|
|
37
|
+
"templates/default/**/.gitkeep",
|
|
38
|
+
]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Canonical Zeon project layout.
|
|
2
|
+
|
|
3
|
+
A single source of truth for the file structure produced when a new project
|
|
4
|
+
is created — used by both the CLI (`zeon init` writes to local disk) and
|
|
5
|
+
the sync service (POST /projects seeds the initial commit's tree).
|
|
6
|
+
|
|
7
|
+
Public API:
|
|
8
|
+
- ``iter_default_project_files()`` yields every file in the default scaffold
|
|
9
|
+
as ``(relative_path, bytes)`` pairs. The path uses forward slashes and is
|
|
10
|
+
project-relative.
|
|
11
|
+
- ``item_template(kind, name)`` yields the file map for a single new typed
|
|
12
|
+
item (``"skill"``, ``"workflow"``, ``"world"``, or ``"object"``). The
|
|
13
|
+
``name`` must be a valid identifier — see :func:`validate_item_name`.
|
|
14
|
+
- ``ITEM_KINDS`` is the canonical set of typed item kinds.
|
|
15
|
+
|
|
16
|
+
The templates themselves live as packaged resources under
|
|
17
|
+
``project_scaffold/templates/default/``. Binary assets (``.npz`` and the
|
|
18
|
+
like) are returned as raw bytes; text files use the on-disk encoding
|
|
19
|
+
(UTF-8 in practice).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from ._scaffold import (
|
|
25
|
+
ITEM_KINDS,
|
|
26
|
+
ItemKind,
|
|
27
|
+
item_template,
|
|
28
|
+
iter_default_project_files,
|
|
29
|
+
primary_path_for,
|
|
30
|
+
validate_item_name,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"ITEM_KINDS",
|
|
35
|
+
"ItemKind",
|
|
36
|
+
"item_template",
|
|
37
|
+
"iter_default_project_files",
|
|
38
|
+
"primary_path_for",
|
|
39
|
+
"validate_item_name",
|
|
40
|
+
]
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
"""Implementation of the public scaffold API.
|
|
2
|
+
|
|
3
|
+
Two surfaces:
|
|
4
|
+
|
|
5
|
+
1. Default project — :func:`iter_default_project_files` walks the packaged
|
|
6
|
+
``templates/default/`` tree and yields every file. This is what backs
|
|
7
|
+
``zeon init`` and the sync service's initial-commit seed.
|
|
8
|
+
|
|
9
|
+
2. Typed items — :func:`item_template` returns the file map for a new
|
|
10
|
+
skill / workflow / world / object by rendering minimal in-code templates.
|
|
11
|
+
These are kept deliberately small so a fresh item is a clean canvas; the
|
|
12
|
+
bundled examples (``grab_object``, ``pick_place``, ``bowl_bottle``) are
|
|
13
|
+
the *reference* of what a richer item looks like. The object kind emits
|
|
14
|
+
only empty URDF + YAML placeholders — the generator that fills them
|
|
15
|
+
lives elsewhere and is wired in later.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from importlib import resources
|
|
21
|
+
import json
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
import re
|
|
24
|
+
from typing import Iterator, Literal
|
|
25
|
+
|
|
26
|
+
ItemKind = Literal["skill", "workflow", "world", "object"]
|
|
27
|
+
ITEM_KINDS: tuple[ItemKind, ...] = ("skill", "workflow", "world", "object")
|
|
28
|
+
|
|
29
|
+
# Identifier rules: lowercase letter or underscore start, then any of
|
|
30
|
+
# ``[a-z0-9_-]``. 1–64 chars. Both dashes and underscores are accepted in the
|
|
31
|
+
# folder/file name — but skill Python function names get dashes mapped to
|
|
32
|
+
# underscores via :func:`_py_identifier` since dashes are illegal in Python
|
|
33
|
+
# identifiers.
|
|
34
|
+
_NAME_RE = re.compile(r"^[a-z_][a-z0-9_-]{0,63}$")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def validate_item_name(name: str) -> str:
|
|
38
|
+
"""Return ``name`` unchanged if valid, else raise ``ValueError``.
|
|
39
|
+
|
|
40
|
+
The rules: lowercase, starts with ``[a-z_]``, then any of ``[a-z0-9_-]``,
|
|
41
|
+
length 1–64. Spaces, uppercase letters, and other punctuation are
|
|
42
|
+
rejected — callers should slug-ify user input before calling.
|
|
43
|
+
"""
|
|
44
|
+
if not isinstance(name, str) or not _NAME_RE.match(name):
|
|
45
|
+
raise ValueError(
|
|
46
|
+
f"invalid item name {name!r}: must match {_NAME_RE.pattern} "
|
|
47
|
+
"(lowercase, alphanumerics + underscore + hyphen, 1–64 chars)"
|
|
48
|
+
)
|
|
49
|
+
return name
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _py_identifier(name: str) -> str:
|
|
53
|
+
"""Convert a slug to a legal Python identifier.
|
|
54
|
+
|
|
55
|
+
Skills compile to a Python function whose name must match ``[a-zA-Z_]\\w*``,
|
|
56
|
+
so dashes in user-chosen slugs are rewritten to underscores for the
|
|
57
|
+
function definition while the folder and ``skill_id`` keep the original.
|
|
58
|
+
"""
|
|
59
|
+
return name.replace("-", "_")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# ---------------------------------------------------------------------------
|
|
63
|
+
# Default project scaffold
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _template_root() -> Path:
|
|
68
|
+
"""Return the on-disk path of the bundled default-template directory."""
|
|
69
|
+
pkg = resources.files("project_scaffold").joinpath("templates/default")
|
|
70
|
+
return Path(str(pkg))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def iter_default_project_files() -> Iterator[tuple[str, bytes]]:
|
|
74
|
+
"""Yield every file in the default project scaffold.
|
|
75
|
+
|
|
76
|
+
Each pair is ``(relative_path, content)`` where ``relative_path`` is
|
|
77
|
+
project-rooted with forward-slash separators and ``content`` is the raw
|
|
78
|
+
file bytes. The default scaffold currently includes:
|
|
79
|
+
|
|
80
|
+
- ``CLAUDE.md`` (project notes)
|
|
81
|
+
- ``skills/.gitkeep`` + the example skills (grab/move/drop object)
|
|
82
|
+
- ``workflows/.gitkeep`` + the example workflow (pick_place)
|
|
83
|
+
- ``worlds/.gitkeep`` + the example world (bowl_bottle)
|
|
84
|
+
- ``objects/.gitkeep`` + placeholder objects (bowl, bottle) — empty
|
|
85
|
+
URDF + YAML; the heavy mesh assets live in the global mesh-database
|
|
86
|
+
namespace served by the sync-service, not in the project tree.
|
|
87
|
+
|
|
88
|
+
Order within the iterator is unspecified; callers that need a stable
|
|
89
|
+
order should sort by path.
|
|
90
|
+
"""
|
|
91
|
+
root = _template_root()
|
|
92
|
+
for path in _walk_files(root):
|
|
93
|
+
rel = path.relative_to(root).as_posix()
|
|
94
|
+
yield rel, path.read_bytes()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _walk_files(root: Path) -> Iterator[Path]:
|
|
98
|
+
"""Depth-first walk yielding every file under ``root``."""
|
|
99
|
+
for entry in sorted(root.iterdir()):
|
|
100
|
+
if entry.is_dir():
|
|
101
|
+
yield from _walk_files(entry)
|
|
102
|
+
elif entry.is_file():
|
|
103
|
+
yield entry
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# ---------------------------------------------------------------------------
|
|
107
|
+
# Typed item templates
|
|
108
|
+
# ---------------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def item_template(
|
|
112
|
+
kind: str, name: str, *, global_object: str | None = None
|
|
113
|
+
) -> Iterator[tuple[str, bytes]]:
|
|
114
|
+
"""Yield files for a newly-created typed item.
|
|
115
|
+
|
|
116
|
+
``kind`` is one of :data:`ITEM_KINDS`. ``name`` must pass
|
|
117
|
+
:func:`validate_item_name`. Returned paths are project-relative.
|
|
118
|
+
|
|
119
|
+
``global_object`` is only meaningful when ``kind == "object"``. When
|
|
120
|
+
provided, the rendered ``<name>.object_model.yaml`` carries a single
|
|
121
|
+
``global_object: <global_object>`` line at the top — a hint to the
|
|
122
|
+
future populator that this project-bound object should draw its
|
|
123
|
+
geometry from the named mesh-database row. The URDF stays empty;
|
|
124
|
+
anchors/parts/articulations stay empty. The link is metadata only,
|
|
125
|
+
not content (D3 of the plan).
|
|
126
|
+
|
|
127
|
+
Passing ``global_object`` for a non-object kind is a programming
|
|
128
|
+
error — raises ``ValueError`` rather than silently dropping it.
|
|
129
|
+
"""
|
|
130
|
+
validate_item_name(name)
|
|
131
|
+
if global_object is not None:
|
|
132
|
+
# Reuse the same name regex — global-object names must follow the
|
|
133
|
+
# same rules as project-bound names (they're mesh-database `name`
|
|
134
|
+
# PKs, also validated by `validate_item_name` on the server).
|
|
135
|
+
validate_item_name(global_object)
|
|
136
|
+
if kind == "skill":
|
|
137
|
+
if global_object is not None:
|
|
138
|
+
raise ValueError(
|
|
139
|
+
"global_object is only valid for kind='object'"
|
|
140
|
+
)
|
|
141
|
+
yield from _skill_files(name)
|
|
142
|
+
elif kind == "workflow":
|
|
143
|
+
if global_object is not None:
|
|
144
|
+
raise ValueError(
|
|
145
|
+
"global_object is only valid for kind='object'"
|
|
146
|
+
)
|
|
147
|
+
yield from _workflow_files(name)
|
|
148
|
+
elif kind == "world":
|
|
149
|
+
if global_object is not None:
|
|
150
|
+
raise ValueError(
|
|
151
|
+
"global_object is only valid for kind='object'"
|
|
152
|
+
)
|
|
153
|
+
yield from _world_files(name)
|
|
154
|
+
elif kind == "object":
|
|
155
|
+
yield from _object_files(name, global_object=global_object)
|
|
156
|
+
else:
|
|
157
|
+
raise ValueError(
|
|
158
|
+
f"unknown item kind: {kind!r}; expected one of {ITEM_KINDS}"
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def primary_path_for(kind: str, name: str) -> str:
|
|
163
|
+
"""Return the file path a freshly-created item is expected to be opened at.
|
|
164
|
+
|
|
165
|
+
The CLI prints this as an "edit <path> to get started" hint after writing
|
|
166
|
+
the template files; the sync service surfaces it in the IDE's scaffold
|
|
167
|
+
response so Monaco lands on the right file. Kept here so every consumer
|
|
168
|
+
agrees on which file is the "primary" one for each kind:
|
|
169
|
+
|
|
170
|
+
- skill → ``skills/{name}/robotic_code.py`` (the implementation file)
|
|
171
|
+
- workflow → ``workflows/{name}.json`` (the graph)
|
|
172
|
+
- world → ``worlds/{name}/world_state.json`` (the scene state)
|
|
173
|
+
- object → ``objects/{name}/{name}.object_model.yaml`` (anchors/parts/articulations)
|
|
174
|
+
"""
|
|
175
|
+
validate_item_name(name)
|
|
176
|
+
if kind == "skill":
|
|
177
|
+
return f"skills/{name}/robotic_code.py"
|
|
178
|
+
if kind == "workflow":
|
|
179
|
+
return f"workflows/{name}.json"
|
|
180
|
+
if kind == "world":
|
|
181
|
+
return f"worlds/{name}/world_state.json"
|
|
182
|
+
if kind == "object":
|
|
183
|
+
return f"objects/{name}/{name}.object_model.yaml"
|
|
184
|
+
raise ValueError(
|
|
185
|
+
f"unknown item kind: {kind!r}; expected one of {ITEM_KINDS}"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _skill_files(name: str) -> Iterator[tuple[str, bytes]]:
|
|
190
|
+
base = f"skills/{name}"
|
|
191
|
+
py_name = _py_identifier(name)
|
|
192
|
+
yield f"{base}/metadata.yaml", _SKILL_METADATA.format(name=name).encode()
|
|
193
|
+
yield (
|
|
194
|
+
f"{base}/robotic_code.py",
|
|
195
|
+
_SKILL_ROBOTIC_CODE.format(name=name, py_name=py_name).encode(),
|
|
196
|
+
)
|
|
197
|
+
yield f"{base}/modules.py", _SKILL_MODULES.encode()
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _workflow_files(name: str) -> Iterator[tuple[str, bytes]]:
|
|
201
|
+
body = {
|
|
202
|
+
"graph_id": name,
|
|
203
|
+
"name": name,
|
|
204
|
+
"description": "",
|
|
205
|
+
"nodes": [
|
|
206
|
+
{"node_id": "start", "node_type": "start", "metadata": {"label": "Start"}},
|
|
207
|
+
{"node_id": "end", "node_type": "end", "metadata": {"label": "End"}},
|
|
208
|
+
],
|
|
209
|
+
"edges": [
|
|
210
|
+
{
|
|
211
|
+
"edge_id": "e0",
|
|
212
|
+
"from_node": "start",
|
|
213
|
+
"to_node": "end",
|
|
214
|
+
"condition": "unconditional",
|
|
215
|
+
}
|
|
216
|
+
],
|
|
217
|
+
}
|
|
218
|
+
yield f"workflows/{name}.json", (json.dumps(body, indent=2) + "\n").encode()
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _object_files(
|
|
222
|
+
name: str, *, global_object: str | None = None
|
|
223
|
+
) -> Iterator[tuple[str, bytes]]:
|
|
224
|
+
base = f"objects/{name}"
|
|
225
|
+
yield f"{base}/{name}.urdf", _OBJECT_URDF.format(name=name).encode()
|
|
226
|
+
# When linked to a mesh-database row, prepend a `global_object: <name>`
|
|
227
|
+
# line to the YAML. This is metadata only — anchors/parts/articulations
|
|
228
|
+
# stay empty; the URDF stays the same empty placeholder.
|
|
229
|
+
if global_object is not None:
|
|
230
|
+
yaml_body = (
|
|
231
|
+
f"global_object: {global_object}\n"
|
|
232
|
+
+ _OBJECT_YAML.format(name=name)
|
|
233
|
+
)
|
|
234
|
+
else:
|
|
235
|
+
yaml_body = _OBJECT_YAML.format(name=name)
|
|
236
|
+
yield f"{base}/{name}.object_model.yaml", yaml_body.encode()
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _world_files(name: str) -> Iterator[tuple[str, bytes]]:
|
|
240
|
+
body = {
|
|
241
|
+
"version": "2.0",
|
|
242
|
+
"config": {
|
|
243
|
+
"tensor_device": "cuda",
|
|
244
|
+
"enable_blox": False,
|
|
245
|
+
"create_workspace_boundaries": False,
|
|
246
|
+
},
|
|
247
|
+
"objects": {},
|
|
248
|
+
"metadata": {
|
|
249
|
+
"name": name,
|
|
250
|
+
"description": "",
|
|
251
|
+
"source": "manual",
|
|
252
|
+
},
|
|
253
|
+
}
|
|
254
|
+
yield (
|
|
255
|
+
f"worlds/{name}/world_state.json",
|
|
256
|
+
(json.dumps(body, indent=2) + "\n").encode(),
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
# ---------------------------------------------------------------------------
|
|
261
|
+
# Templates kept as constants so the package has no runtime branching when
|
|
262
|
+
# rendering. Indentation matches what a user would expect to see on disk.
|
|
263
|
+
# ---------------------------------------------------------------------------
|
|
264
|
+
|
|
265
|
+
_SKILL_METADATA = """\
|
|
266
|
+
# Skill: {name}
|
|
267
|
+
skill_id: {name}
|
|
268
|
+
version: "1.0.0"
|
|
269
|
+
description: ""
|
|
270
|
+
|
|
271
|
+
parameters: []
|
|
272
|
+
|
|
273
|
+
preconditions: {{}}
|
|
274
|
+
postconditions: {{}}
|
|
275
|
+
"""
|
|
276
|
+
|
|
277
|
+
_SKILL_ROBOTIC_CODE = '''\
|
|
278
|
+
from .modules import *
|
|
279
|
+
|
|
280
|
+
OBJECT_VARIABLES = {{}}
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def {py_name}():
|
|
284
|
+
"""Skill implementation for {name}."""
|
|
285
|
+
# TODO: Implement skill logic here.
|
|
286
|
+
return {{"success": True}}
|
|
287
|
+
'''
|
|
288
|
+
|
|
289
|
+
_SKILL_MODULES = """\
|
|
290
|
+
from execution.skill_editing.execution_functions import *
|
|
291
|
+
"""
|
|
292
|
+
|
|
293
|
+
# Object templates are deliberately minimal placeholders — just enough for
|
|
294
|
+
# the zeon_robotics `load_object_model()` schema to validate without raising.
|
|
295
|
+
# The future populator (lives in another part of the codebase) fills in real
|
|
296
|
+
# kinematics, anchors, parts, and articulation presets. Until then, a
|
|
297
|
+
# project-bound object renders as an invisible identity-frame at the root.
|
|
298
|
+
#
|
|
299
|
+
# Three things the schema requires we MUST emit:
|
|
300
|
+
# 1. `urdf: <name>.urdf` — yaml's link to its companion file
|
|
301
|
+
# 2. `articulations.default` — even with empty joints (matches a
|
|
302
|
+
# URDF that has zero actuated joints)
|
|
303
|
+
# 3. `anchors.object` — REQUIRED_ANCHORS = {"object"} on the
|
|
304
|
+
# schema; identity transform on the URDF's
|
|
305
|
+
# single `base` link
|
|
306
|
+
#
|
|
307
|
+
# Anything beyond this is content (anchors with real poses, parts that
|
|
308
|
+
# reference geometry-bearing links, etc.) and is the populator's job.
|
|
309
|
+
#
|
|
310
|
+
# Verified live: load_object_model against this pair returns an ObjectModel
|
|
311
|
+
# with parts=[], articulations=['default'], anchors=['object']. Verifying
|
|
312
|
+
# regression test lives at
|
|
313
|
+
# services/execution/src/execution/mesh_database/tests/test_scaffold_compat.py.
|
|
314
|
+
_OBJECT_URDF = """\
|
|
315
|
+
<?xml version='1.0' encoding='utf-8'?>
|
|
316
|
+
<robot name="{name}">
|
|
317
|
+
<link name="base" />
|
|
318
|
+
</robot>
|
|
319
|
+
"""
|
|
320
|
+
|
|
321
|
+
_OBJECT_YAML = """\
|
|
322
|
+
# {name} object model — populated later
|
|
323
|
+
urdf: {name}.urdf
|
|
324
|
+
|
|
325
|
+
parts: {{}}
|
|
326
|
+
|
|
327
|
+
articulations:
|
|
328
|
+
default:
|
|
329
|
+
description: "Empty default articulation"
|
|
330
|
+
joints: {{}}
|
|
331
|
+
|
|
332
|
+
anchors:
|
|
333
|
+
object:
|
|
334
|
+
parent_link: base
|
|
335
|
+
description: "Object reference frame"
|
|
336
|
+
link_T_anchor:
|
|
337
|
+
xyz: [0.0, 0.0, 0.0]
|
|
338
|
+
wxyz: [1.0, 0.0, 0.0, 0.0]
|
|
339
|
+
"""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Project notes
|
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# bottle object model — populated later
|
|
2
|
+
urdf: bottle.urdf
|
|
3
|
+
|
|
4
|
+
parts: {}
|
|
5
|
+
|
|
6
|
+
articulations:
|
|
7
|
+
default:
|
|
8
|
+
description: "Empty default articulation"
|
|
9
|
+
joints: {}
|
|
10
|
+
|
|
11
|
+
anchors:
|
|
12
|
+
object:
|
|
13
|
+
parent_link: base
|
|
14
|
+
description: "Object reference frame"
|
|
15
|
+
link_T_anchor:
|
|
16
|
+
xyz: [0.0, 0.0, 0.0]
|
|
17
|
+
wxyz: [1.0, 0.0, 0.0, 0.0]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# bowl object model — populated later
|
|
2
|
+
urdf: bowl.urdf
|
|
3
|
+
|
|
4
|
+
parts: {}
|
|
5
|
+
|
|
6
|
+
articulations:
|
|
7
|
+
default:
|
|
8
|
+
description: "Empty default articulation"
|
|
9
|
+
joints: {}
|
|
10
|
+
|
|
11
|
+
anchors:
|
|
12
|
+
object:
|
|
13
|
+
parent_link: base
|
|
14
|
+
description: "Object reference frame"
|
|
15
|
+
link_T_anchor:
|
|
16
|
+
xyz: [0.0, 0.0, 0.0]
|
|
17
|
+
wxyz: [1.0, 0.0, 0.0, 0.0]
|
|
File without changes
|
zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/skills/drop_object/metadata.yaml
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Skill Metadata: Drop Object
|
|
2
|
+
# Manipulation skill for releasing a grasped object at a destination
|
|
3
|
+
|
|
4
|
+
skill_id: drop_object
|
|
5
|
+
version: "1.0.0"
|
|
6
|
+
description: "Release a grasped object at a specified destination and open gripper"
|
|
7
|
+
|
|
8
|
+
parameters:
|
|
9
|
+
- name: destination
|
|
10
|
+
type: object
|
|
11
|
+
description: "Destination to drop the object to"
|
|
12
|
+
required: true
|
|
13
|
+
|
|
14
|
+
- name: object
|
|
15
|
+
type: object
|
|
16
|
+
description: "Object to be dropped"
|
|
17
|
+
required: true
|
|
18
|
+
|
|
19
|
+
preconditions:
|
|
20
|
+
object_grasped: true
|
|
21
|
+
gripper_closed: true
|
|
22
|
+
destination_accessible: true
|
|
23
|
+
|
|
24
|
+
postconditions:
|
|
25
|
+
object_released: true
|
|
26
|
+
gripper_open: true
|
|
27
|
+
object_at_destination: true
|
|
28
|
+
|
|
29
|
+
safety_rules:
|
|
30
|
+
- "Minimum release height: 0.1m above destination"
|
|
31
|
+
- "Gripper must fully open before retreat"
|
|
32
|
+
- "Collision detection must be enabled"
|
|
33
|
+
- "Emergency stop button must be accessible"
|
|
34
|
+
- "Object detachment must be synchronized"
|
|
35
|
+
|
|
36
|
+
tags:
|
|
37
|
+
- manipulation
|
|
38
|
+
- gripper
|
|
39
|
+
- pick-and-place
|
|
40
|
+
- release
|
zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/skills/drop_object/modules.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from execution.skill_editing.execution_functions import *
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
from execution.skill_editing.execution_functions import (
|
|
4
|
+
capture_image,
|
|
5
|
+
detach_object_from_arm,
|
|
6
|
+
move_arm,
|
|
7
|
+
set_gripper,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def drop_object():
|
|
12
|
+
destination = globals().get("OBJECT_VARIABLES", {}).get("destination", [0.5, -0.1, 0.15])
|
|
13
|
+
destination_position = [destination[0], destination[1], destination[2]]
|
|
14
|
+
|
|
15
|
+
object_id = globals().get("OBJECT_IDS", {}).get("object", "object")
|
|
16
|
+
|
|
17
|
+
move_arm(
|
|
18
|
+
arm="left_arm",
|
|
19
|
+
position=[destination_position[0], destination_position[1], destination_position[2] + 0.15],
|
|
20
|
+
orientation=[2.792021, -1.158447, -1.477334],
|
|
21
|
+
safe=False,
|
|
22
|
+
speed=100,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Step 2: Detach object from arm and sync world
|
|
26
|
+
detach_object_from_arm(object_id)
|
|
27
|
+
|
|
28
|
+
# Step 3: Open gripper
|
|
29
|
+
set_gripper(arm="left_arm", width_m=0.08)
|
|
30
|
+
time.sleep(2)
|
|
31
|
+
|
|
32
|
+
# Step 4: Move back up
|
|
33
|
+
move_arm(
|
|
34
|
+
arm="left_arm",
|
|
35
|
+
position=[destination_position[0], destination_position[1], destination_position[2] + 0.2],
|
|
36
|
+
orientation=[1.085106, -1.490891, 0.235473],
|
|
37
|
+
safe=False,
|
|
38
|
+
speed=100,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Step 5: Close gripper
|
|
42
|
+
set_gripper(arm="left_arm", width_m=0.0)
|
|
43
|
+
|
|
44
|
+
# Step 6: Capture image with right arm for testing
|
|
45
|
+
capture_image(arm="right_arm", capture_name="drop_object_final")
|
|
46
|
+
|
|
47
|
+
return {"success": True}
|
zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/skills/grab_object/metadata.yaml
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Skill Metadata: Grab Object (Pick & Place variant)
|
|
2
|
+
# Pick and place manipulation skill for grasping objects with annotation-based positioning
|
|
3
|
+
|
|
4
|
+
skill_id: grab_object
|
|
5
|
+
version: "1.0.0"
|
|
6
|
+
description: "Grasp an object using annotation-based positioning with the left arm gripper"
|
|
7
|
+
|
|
8
|
+
parameters:
|
|
9
|
+
- name: object
|
|
10
|
+
type: object
|
|
11
|
+
description: "Object to be grabbed"
|
|
12
|
+
required: true
|
|
13
|
+
|
|
14
|
+
preconditions:
|
|
15
|
+
gripper_open: true
|
|
16
|
+
object_visible: true
|
|
17
|
+
left_arm_available: true
|
|
18
|
+
workspace_clear: true
|
|
19
|
+
annotations_loaded: true
|
|
20
|
+
|
|
21
|
+
postconditions:
|
|
22
|
+
object_grasped: true
|
|
23
|
+
gripper_closed: true
|
|
24
|
+
object_attached_to_arm: true
|
|
25
|
+
# world_state_prime:
|
|
26
|
+
# object_positions:
|
|
27
|
+
# bowl: [0.2, -0.3, 0.0, 1.0, 0.0, 0.0, 0.0] # [x, y, z, qw, qx, qy, qz] format matching state_manager.py
|
|
28
|
+
# object_types:
|
|
29
|
+
# bowl: bowl
|
|
30
|
+
# robot_state:
|
|
31
|
+
# left_arm_tcp_pose: [0.250, -0.240, 0.209, -3.130, -0.029, -1.548]
|
|
32
|
+
# right_arm_tcp_pose: [-0.007, -0.387, 0.230, 3.007, -0.949, 0.121]
|
|
33
|
+
# left_arm_gripper_width: 0.08 # Gripper width in meters (open)
|
|
34
|
+
# right_arm_gripper_width: 0.0 # Gripper width in meters (closed)
|
zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/skills/grab_object/modules.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from execution.skill_editing.execution_functions import *
|