Spaces:
Sleeping
Sleeping
| #!/bin/env python3 | |
| """ | |
| Convert a standard 3mf generated by OpenSCAD with color information | |
| to a MMU‑painted 3mf that PrusaSlicer expects | |
| """ | |
| import argparse | |
| import logging | |
| import zipfile | |
| from pathlib import Path | |
| from xml.etree import ElementTree as ET | |
| # Mapping from extruder index to paint color code used by PrusaSlicer / BambuStudio | |
| PAINT_COLOR_MAP = [ | |
| None, | |
| "8", | |
| "0C", | |
| "1C", | |
| "2C", | |
| "3C", | |
| "4C", | |
| "5C", | |
| "6C", | |
| "7C", | |
| "8C", | |
| "9C", | |
| "AC", | |
| "BC", | |
| "CC", | |
| "DC", | |
| ] | |
| def main(): | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument( | |
| "--loglevel", | |
| type=str, | |
| choices=["INFO", "DEBUG", "WARN", "ERROR"], | |
| default="INFO", | |
| help="Logging Level", | |
| ) | |
| parser.add_argument( | |
| "--force", | |
| default=False, | |
| action="store_true", | |
| help="Force overwriting the output file", | |
| ) | |
| parser.add_argument("openscad_3mf", help="OpenSCAD 3MF input") | |
| parser.add_argument("prusaslicer_3mf", help="PrusaSlicer 3MF output") | |
| args = parser.parse_args() | |
| logging.basicConfig( | |
| format="%(asctime)s %(levelname)8s %(message)s", level=args.loglevel | |
| ) | |
| # register the namespaces we care about | |
| namespaces = { | |
| "xml": "http://www.w3.org/XML/1998/namespace", | |
| "": "http://schemas.microsoft.com/3dmanufacturing/core/2015/02", | |
| "p": "http://schemas.microsoft.com/3dmanufacturing/production/2015/06", | |
| "slic3rpe": "http://schemas.slic3r.org/3mf/2017/06", | |
| } | |
| for k, v in namespaces.items(): | |
| ET.register_namespace(k, v) | |
| os3mf = Path(args.openscad_3mf) | |
| ps3mf = Path(args.prusaslicer_3mf) | |
| if not os3mf.exists(): | |
| logging.error(f"The input file {os3mf} doesn't exist") | |
| exit(1) | |
| if ps3mf.exists() and not args.force: | |
| logging.error( | |
| f"The output file {ps3mf} exists and will not be overwritten without --force" | |
| ) | |
| exit(1) | |
| with zipfile.ZipFile( | |
| ps3mf, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9 | |
| ) as pszip: | |
| with zipfile.ZipFile(os3mf, "r") as oszip: | |
| # copy the files from the openscad 3mf to the prusaslicer 3mf | |
| for file in [ | |
| x for x in oszip.infolist() if x.filename != "3D/3dmodel.model" | |
| ]: | |
| logging.info(f"Copying file {file}") | |
| pszip.writestr(file, oszip.open(file).read()) | |
| model_raw = oszip.read("3D/3dmodel.model") | |
| logging.info("Updating 3dmodel.model") | |
| # Parse the XML | |
| model_root = ET.fromstring(str(model_raw, encoding="utf-8")) | |
| # Remove any existing Application metadata and set to PrusaSlicer | |
| for meta in list(model_root.findall("metadata")): | |
| if meta.get("name") == "Application": | |
| model_root.remove(meta) | |
| # Add Application metadata | |
| app_meta = ET.Element( | |
| "metadata", {"name": "Application", "preserve": "1"} | |
| ) | |
| app_meta.text = "PrusaSlicer" | |
| model_root.append(app_meta) | |
| # add slic3rpe metadata | |
| for k, v in { | |
| "slic3rpe:Version3mf": "1", | |
| "slic3rpe:MmPaintingVersion": "1", | |
| "BambuStudio:3mfVersion": "1", | |
| }.items(): | |
| e = ET.Element("metadata", {"name": k}) | |
| e.text = v | |
| model_root.append(e) | |
| # Set color attributes on triangles | |
| for t in model_root.findall( | |
| "resources/object/mesh/triangles/triangle", namespaces | |
| ): | |
| p1_value = t.get("p1") | |
| if not p1_value or p1_value == "0": | |
| continue | |
| try: | |
| p1_index = int(p1_value) | |
| except ValueError: | |
| logging.warning( | |
| f"Triangle has non‑integer p1 value: {p1_value}" | |
| ) | |
| continue | |
| if p1_index < len(PAINT_COLOR_MAP): | |
| color_code = PAINT_COLOR_MAP[p1_index] | |
| else: | |
| logging.warning( | |
| f"Material number {p1_index} is greater than supported extruders" | |
| ) | |
| color_code = None | |
| if color_code: | |
| t.set("slic3rpe:mmu_segmentation", color_code) | |
| t.set("paint_color", color_code) | |
| model_text = str( | |
| ET.tostring( | |
| model_root, encoding="utf-8", xml_declaration=True | |
| ), | |
| encoding="utf-8", | |
| ) | |
| pszip.writestr("3D/3dmodel.model", model_text) | |
| if __name__ == "__main__": | |
| main() | |