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.
Files changed (33) hide show
  1. zeon_project_scaffold-0.1.0/LICENSE +21 -0
  2. zeon_project_scaffold-0.1.0/PKG-INFO +31 -0
  3. zeon_project_scaffold-0.1.0/README.md +13 -0
  4. zeon_project_scaffold-0.1.0/pyproject.toml +38 -0
  5. zeon_project_scaffold-0.1.0/setup.cfg +4 -0
  6. zeon_project_scaffold-0.1.0/src/project_scaffold/__init__.py +40 -0
  7. zeon_project_scaffold-0.1.0/src/project_scaffold/_scaffold.py +339 -0
  8. zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/CLAUDE.md +1 -0
  9. zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/objects/.gitkeep +0 -0
  10. zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/objects/bottle/bottle.object_model.yaml +17 -0
  11. zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/objects/bottle/bottle.urdf +4 -0
  12. zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/objects/bowl/bowl.object_model.yaml +17 -0
  13. zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/objects/bowl/bowl.urdf +4 -0
  14. zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/skills/.gitkeep +0 -0
  15. zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/skills/drop_object/metadata.yaml +40 -0
  16. zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/skills/drop_object/modules.py +1 -0
  17. zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/skills/drop_object/robotic_code.py +47 -0
  18. zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/skills/grab_object/metadata.yaml +34 -0
  19. zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/skills/grab_object/modules.py +1 -0
  20. zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/skills/grab_object/robotic_code.py +56 -0
  21. zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/skills/move_object/metadata.yaml +33 -0
  22. zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/skills/move_object/modules.py +1 -0
  23. zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/skills/move_object/robotic_code.py +17 -0
  24. zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/workflows/.gitkeep +0 -0
  25. zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/workflows/pick_place.json +71 -0
  26. zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/worlds/.gitkeep +0 -0
  27. zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/worlds/bowl_bottle/blox_voxels.npz +0 -0
  28. zeon_project_scaffold-0.1.0/src/project_scaffold/templates/default/worlds/bowl_bottle/world_state.json +469 -0
  29. zeon_project_scaffold-0.1.0/src/zeon_project_scaffold.egg-info/PKG-INFO +31 -0
  30. zeon_project_scaffold-0.1.0/src/zeon_project_scaffold.egg-info/SOURCES.txt +31 -0
  31. zeon_project_scaffold-0.1.0/src/zeon_project_scaffold.egg-info/dependency_links.txt +1 -0
  32. zeon_project_scaffold-0.1.0/src/zeon_project_scaffold.egg-info/top_level.txt +1 -0
  33. 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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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,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,4 @@
1
+ <?xml version='1.0' encoding='utf-8'?>
2
+ <robot name="bottle">
3
+ <link name="base" />
4
+ </robot>
@@ -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]
@@ -0,0 +1,4 @@
1
+ <?xml version='1.0' encoding='utf-8'?>
2
+ <robot name="bowl">
3
+ <link name="base" />
4
+ </robot>
@@ -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
@@ -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}
@@ -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)
@@ -0,0 +1 @@
1
+ from execution.skill_editing.execution_functions import *