Install
openclaw skills install @clarezoe/3mf-print-editorGuideline for directly editing 3D-printing project files (.3mf), verified against BambuStudio specifically: cutting or otherwise modifying mesh geometry, wiring objects into the 3MF XML correctly, arranging multiple print plates in BambuStudio's shared multi-plate coordinate system, and setting print/project parameters. Use whenever a user asks to edit, cut, split, merge, reposition, rearrange, or reconfigure a BambuStudio .3mf project file by hand instead of through the slicer GUI. Other slicers (OrcaSlicer, PrusaSlicer, etc.) use similar-looking .3mf files but are NOT verified to follow the same plate-offset math or config schema — treat this skill's multi-plate guidance as BambuStudio-specific.
openclaw skills install @clarezoe/3mf-print-editorGuideline for hand-editing 3D-printing files — Bambu Lab's .3mf project files, as opened/sliced by BambuStudio
— when the user wants a model cut, split across plates, repositioned, merged, or reconfigured programmatically
rather than through the slicer GUI. Covers three things end to end: editing the mesh correctly,
building/arranging plates correctly, and setting print parameters correctly. Read references/3mf-structure.md
and references/plate-coordinate-system.md before editing a multi-plate project — the plate math is not obvious and
is the single most common source of silent failure (see Pitfall #1).
Scope disclaimer: this skill is verified against BambuStudio only. Everything here — the
Metadata/model_settings.configschema, the multi-plate shared-scene offset formula in §4, and theproject_settings.configkeys in §5 — was reverse-engineered from BambuStudio's own source (bambulab/BambuStudio) and confirmed against real BambuStudio behavior. OrcaSlicer, PrusaSlicer, and other tools also read/write.3mf, and OrcaSlicer's is close (it's a BambuStudio fork), but their exact plate-offset constants, config keys, or metadata schema are not guaranteed to match and have not been verified here. If the user's target application isn't BambuStudio, say so explicitly, and re-derive/verify the relevant constants against that application's own source or behavior before applying this skill's formulas — do not assume they transfer as-is.
.3mf as a zip and hand-editing its XML/config instead of using the slicer UIDo NOT use this skill just to view or slice a file — only when the file itself needs to be programmatically modified and re-saved.
Mesh editing needs trimesh plus its optional dependencies for boolean ops, capping, and polygon repair. On macOS
with a Homebrew Python, the interpreter that actually has these packages may differ from the default python3
(check with python3 -c "import sys; print(sys.executable)" vs which -a python3.*). Install once per environment:
python3 -m pip install trimesh numpy scipy shapely networkx rtree manifold3d
Verify:
python3 -c "import trimesh, numpy, scipy, shapely, networkx, rtree; print('ok', trimesh.__version__)"
If pip3 and python3 resolve to different interpreters (common on macOS with multiple Homebrew Python versions),
install and run with the exact interpreter path that has the packages, not just python3.
A .3mf is a standard OPC (Open Packaging Conventions) zip. Always extract and inspect first:
mkdir -p /tmp/3mf_work && cd /tmp/3mf_work
unzip -o "/path/to/model.3mf" -d extracted
find extracted -type f
Read references/3mf-structure.md for the full file-by-file breakdown. Key files:
| File | Purpose |
|---|---|
3D/3dmodel.model | Top-level resources (component references) + <build> list of placed instances |
3D/Objects/object_N.model | Actual mesh geometry (vertices/triangles) for object id N |
3D/_rels/3dmodel.model.rels | OPC relationships — must list every Objects/object_N.model referenced |
Metadata/model_settings.config | Bambu/Orca project metadata: object names, per-object part metadata, <plate> blocks (which objects belong to which plate), <assemble> block |
Metadata/project_settings.config | Full print/printer/filament settings as flat JSON (bed shape, height, temps, speeds, etc.) |
Metadata/cut_information.xml | Bambu's native "Cut tool" state — only relevant if you want the GUI cut-tool to recognize a cut; safe to leave empty (<objects></objects>) for programmatic edits |
Metadata/plate_N.png etc. | Thumbnails — cosmetic only, stale thumbnails do not break loading |
Never assume a schema detail — when unsure, verify against the actual BambuStudio source
(src/libslic3r/Format/bbs_3mf.cpp, src/slic3r/GUI/PartPlate.cpp in bambulab/BambuStudio on GitHub) rather than
guessing. This skill's plate-offset math (Pitfall #1) was reverse-engineered from that source after a first attempt
silently failed.
For real geometry edits (cutting, boolean, hollowing, etc.), never hand-edit vertex/triangle XML — parse it into a proper mesh library, edit, then re-serialize.
3D/Objects/object_N.model (or the sole object file when there's no
multi-part split) using xml.etree.ElementTree, build a trimesh.Trimesh(vertices=..., faces=..., process=False).mesh.bounds and mesh.extents first. For "cut in half", pick the plane through the midpoint of the longest
horizontal extent (or the axis the user specifies), and prefer a plane that lands on an already-symmetric midpoint
(e.g. bounds like [-127, 127]) since that's almost always the model's natural centerline.half_a = trimesh.intersections.slice_mesh_plane(mesh, plane_normal=[0,1,0], plane_origin=[0,0,0], cap=True)
half_b = trimesh.intersections.slice_mesh_plane(mesh, plane_normal=[0,-1,0], plane_origin=[0,0,0], cap=True)
half_a.is_watertight and half_b.is_watertight must both be True.abs(half_a.volume + half_b.volume - mesh.volume) < 1e-3 (volume conservation — proves no geometry was lost
or duplicated at the cut).scripts/mesh_tools.py (split_mesh_plane) to do steps 3–4 with the checks built in instead of re-deriving
this from scratch each time.See references/mesh-editing-notes.md for capping caveats, common trimesh dependency errors and fixes
(scipy/shapely/networkx/rtree ImportErrors surface one at a time — install all five up front), and how to
handle non-watertight source meshes.
When a cut/split produces N new sub-meshes that must become N separate placeable objects:
3D/Objects/object_<id>.model file with a unique numeric object id, following the
existing file's template (<model> root with the same namespaces, one <object type="model"><mesh>... per file).
Keep IDs unique across the whole package — IDs are global, not per-file.3D/3dmodel.model, give each new sub-mesh a wrapping top-level <object type="model"> with a <component>
pointing at its .model file, then add one <item objectid="..."> per object to <build> with the placement
transform (see §4 for the coordinates that transform must use).<Relationship Target="/3D/Objects/object_<id>.model" .../> for every new object file to
3D/_rels/3dmodel.model.rels — a missing relationship can cause silent load failures in strict parsers.Metadata/model_settings.config: one <object id="..."> block per top-level object (with a nested
<part> for the mesh + mesh_stat face_count=...), one <model_instance> per plate assignment inside the
matching <plate> block, and matching <assemble_item> entries in <assemble> using the same transform as the
build item.Metadata/cut_information.xml (<objects></objects>) when you replace geometry outside the GUI's
own cut tool, so stale connector/cut metadata doesn't get misapplied to the new geometry.[Content_Types].xml normally needs no change (it declares Default Extension="model" which covers any new
.model file automatically) — only touch it if you introduce a genuinely new file extension.BambuStudio-specific. The constants and behavior below (
LOGICAL_PART_PLATE_GAP = 0.2, the shared-scene layout) are taken directly from BambuStudio's source and confirmed in BambuStudio. They are likely similar in OrcaSlicer (a BambuStudio fork) but have not been verified there, and are not guaranteed at all for PrusaSlicer or any other.3mfconsumer. Do not present this formula as universal to "3D printing" or "3MF" in general — it is a property of this specific application's renderer/loader.
This is the part that silently fails if you skip it. BambuStudio renders every plate in one shared 3D scene, not as independent origin-0 canvases. Every plate after the first is offset along X (and wraps to a new row after the configured column count) by:
stride_x = bed_width * 1.2 # LOGICAL_PART_PLATE_GAP = 0.2, i.e. a 20% gap
stride_y = bed_depth * 1.2
col = plate_index % cols # plate_index is 0-based; cols defaults such that plates fill one row unless very many
row = plate_index // cols
plate_origin = (col * stride_x, -row * stride_y)
Read the bed size from Metadata/project_settings.config's printable_area (four "XxY" corner strings) and
printable_height. For the common 256×256 bed, stride_x = stride_y = 307.2.
Every object's build-item transform (3D/3dmodel.model <item transform="...">) and its matching
<assemble_item transform="..."> in model_settings.config must be in this shared global space: local
plate-centered position plus that plate's plate_origin offset. If you center every object at its own plate's
local origin without adding the offset, every plate's objects land on top of each other at plate 0's location — the
first plate looks "whole" (multiple pieces stacked back together) and every other plate renders empty. This exact
bug is documented with full derivation in references/plate-coordinate-system.md; read it once, then use
scripts/mesh_tools.py's plate_origin(plate_index, bed_width, bed_depth, cols=...) helper so you never
re-derive it by hand.
Assigning which plate an object belongs to (separately from its coordinates) is done via
Metadata/model_settings.config: each <plate> block has a <model_instance> child per object/instance
(object_id, instance_id, identify_id) that belongs on that plate. identify_id can be any positive integer;
it does not need to match anything else in the file (BambuStudio falls back to internal IDs when it's absent/≤0).
Metadata/project_settings.config is flat JSON (not XML) with every printer/filament/print setting as a
key → value (often the value is itself a stringified array/percentage). Common keys to look for:
printable_area, printable_height — bed shape/size (see §4, drives plate offsets)*_plate_temp, *_plate_temp_initial_layer — bed temperature by plate type (cool/eng/hot/supertack/textured)When a user asks to "改打印设置" (change print settings), find the exact key(s) in this JSON, change only those values, and leave the rest of the (very large) file untouched — do not reformat or reorder it.
Metadata/slice_info.config and Metadata/filament_sequence.json are usually safe to leave as-is unless the user
is specifically changing slicing/AMS filament-sequencing behavior.
Always run these checks on the rebuilt package before handing it back:
.model/.config/.rels XML file (xml.etree.ElementTree.parse).Objects/object_N.model, confirm vertex/triangle counts are sane and
(for solid parts) trimesh reports is_watertight.[Content_Types].xml at minimum should be present; ordering isn't strict but keep
the file complete) and re-extract it fresh to confirm the zip itself isn't corrupt.Use scripts/mesh_tools.py's validate_package(dir) to run 1–3 automatically over an extracted tree.
Never overwrite the user's original file in place. Write the result to a new file (e.g. name_edited.3mf or
name_split.3mf) alongside the original, tell the user exactly what changed, and only overwrite the original if
they explicitly ask you to.
ModuleNotFoundError cascade when calling trimesh.intersections.slice_mesh_plane(..., cap=True) — trimesh
lazily imports scipy, then shapely, then networkx, then rtree one at a time; install all four (plus
manifold3d for booleans) up front instead of iterating on import errors.python3 without the packages — pip install and python3 can point at different interpreters on
multi-Python-version macOS setups; always verify with the same interpreter you'll run edits with.mesh.bounds/mesh.extents first; don't assume which
axis is "the middle" of an asymmetric or unfamiliar model.3D/_rels/3dmodel.model.rels when adding new Objects/object_N.model files.cut_information.xml pointing at old part/connector IDs after replacing geometry outside the
GUI cut tool.project_settings.config by reformatting the whole file — it's huge; touch only the specific keys
requested.references/3mf-structure.md — full file-by-file breakdown of a Bambu/Orca .3mf packagereferences/plate-coordinate-system.md — full derivation of the multi-plate offset formula, with BambuStudio
source citationsreferences/mesh-editing-notes.md — trimesh capping/watertight/dependency notesscripts/mesh_tools.py — reusable helpers: extract/repackage a 3mf, split a mesh with validation, compute a
plate's origin offset, validate an extracted package