#15752·OrcaSlicer

CLI: --arrange forces align_to_y_axis on i3 printers, so --allow-rotations=0 rotates every object 90 degrees

Author: GuyH77Created Sep 18, 2026Updated Sep 18, 2026

Is there an existing issue for this problem?

  • I have searched the existing issues (#12612 and #13087 are about --orient; this is --arrange)

OrcaSlicer Version

2.4.2 release (Windows). The code is unchanged on main (src/OrcaSlicer.cpp, the arrange_cfg.align_to_y_axis = (printer_structure_opt->value == PrinterStructure::psI3); lines).

Operating System (OS)

Windows

OS Version

11

Printer

Bambu Lab A1 (any printer whose profile has printer_structure = i3)

How to reproduce

A project with six 200 x 60 x 5 mm two-colour slabs stacked on plate 1 (they overlap, so --arrange has to lay them out). Generator script below, no models needed.

orca-slicer.exe --arrange 1 --outputdir out1 --export-3mf out.3mf stack6.3mf
orca-slicer.exe --arrange 1 --allow-rotations=0 --outputdir out2 --export-3mf out.3mf stack6.3mf

Then read the build item transforms out of each out.3mf (3D/3dmodel.model).

Actual results

command rotation of every slab layout
--arrange 1 (rotations allowed, the default) 0 deg 4 on plate 1 as rows, 2 on plate 2
--arrange 1 --allow-rotations=0 (rotations forbidden) 90 deg 4 on plate 1 as columns, 2 on plate 2

Forbidding rotation turns every object 90 degrees. Allowing it leaves them alone.

Cause: the CLI forces "Align to Y axis" on for i3 printers and there is no option to turn it off:

src/OrcaSlicer.cpp (2.4.2 lines 4779 and 5229, both --arrange paths):

cpp
if (auto printer_structure_opt = m_print_config.option<ConfigOptionEnum<PrinterStructure>>("printer_structure")) {
    arrange_cfg.align_to_y_axis = (printer_structure_opt->value == PrinterStructure::psI3);
}

src/libslic3r/Arrange.cpp:158 update_selected_items_axis_align then pre-rotates every selected object so its principal axis lies along Y. With rotations allowed the nester tries {0, 45, 90, 135} on top of that and can turn the slab back to 0 (it did). With --allow-rotations=0 it tries {0} only, so the pre-rotation is the result: every object 90 degrees from how it was loaded.

The GUI reads the same flag from the arrange dialog checkbox (src/slic3r/GUI/Jobs/ArrangeJob.cpp:782, params.align_to_y_axis = settings.align_to_y_axis), so a GUI user can arrange an A1 plate at 0 degrees; a CLI user cannot.

Why it matters: objects arranged at 90 degrees have their top-surface and infill lines running across the part at a different angle from neighbours arranged at 0, which is visible on flat parts. --allow-rotations=0 is the switch a CLI user reaches for to keep the loaded orientation, and it does the opposite.

Two smaller things seen on the way:

  • --allow-rotations 0 (space) is not accepted: 0 is taken as an input file name and the CLI exits -3 (file not found). Only --allow-rotations=0 parses. The --help text does not say so.
  • With --allow-rotations=0 the objects are also re-ordered onto plates differently from the allowed case (columns instead of rows), which is expected once they are 90 degrees, mentioned so nobody chases it.

Expected results

--allow-rotations=0 keeps every object at the orientation it was loaded with. Either

  • do not force align_to_y_axis in the CLI (default it off, as the GUI checkbox defaults), or
  • expose it: --align-to-y-axis=0/1, so a CLI caller can choose like a GUI user can.

Reproducer

make_boxes_3mf.py <project_settings.config> stack6.3mf 6 --one-plate writes the project. <project_settings.config> is the Metadata/project_settings.config from any project saved by the GUI for a Bambu A1 0.4 nozzle, 0.20mm Standard, two Generic PLA filaments (that is the only thing the script does not make up: the printer/process/filament settings).

make_boxes_3mf.py
python
"""Write a design-free multi-plate Orca project: N two-colour slabs, one per plate.

    python make_boxes_3mf.py <settings.json from any A1 project> out.3mf [N] [--one-plate]

Each object = a 200 x 60 x 4 mm base (filament 1) with a 180 x 40 x 1 mm slab on top
(filament 2), so the plate is two-colour and keeps its prime tower. Plates sit on Orca's
grid (cols = ceil(sqrt(N)), gap 1/5 of the bed). --one-plate stacks every object at the
centre of plate 1 (they overlap: something --arrange has to fix)."""
import json, math, sys, uuid, zipfile

BW, BD = 256.0, 256.0
GAP = 0.2


def box(x0, y0, z0, x1, y1, z1):
    v = [(x0, y0, z0), (x1, y0, z0), (x1, y1, z0), (x0, y1, z0),
         (x0, y0, z1), (x1, y0, z1), (x1, y1, z1), (x0, y1, z1)]
    t = [(0, 2, 1), (0, 3, 2), (4, 5, 6), (4, 6, 7), (0, 1, 5), (0, 5, 4),
         (1, 2, 6), (1, 6, 5), (2, 3, 7), (2, 7, 6), (3, 0, 4), (3, 4, 7)]
    vs = "".join('<vertex x="%g" y="%g" z="%g"/>' % p for p in v)
    ts = "".join('<triangle v1="%d" v2="%d" v3="%d"/>' % q for q in t)
    return "<mesh><vertices>%s</vertices><triangles>%s</triangles></mesh>" % (vs, ts)


def origin(k, n):
    cols = math.isqrt(n - 1) + 1
    return (k % cols) * BW * (1 + GAP), -(k // cols) * BD * (1 + GAP)


def main(settings_path, out, n=6, one_plate=False):
    cfg = json.load(open(settings_path, encoding="utf-8"))
    parts, objs, items, ms_objs, plates = [], [], [], [], []
    for k in range(n):
        base, top = 1000 * (k + 1) + 1, 1000 * (k + 1) + 2
        parts.append('<object id="%d" p:UUID="%s" type="model">%s</object>' % (base, uuid.uuid4(), box(-100, -30, 0, 100, 30, 4)))
        parts.append('<object id="%d" p:UUID="%s" type="model">%s</object>' % (top, uuid.uuid4(), box(-90, -20, 4, 90, 20, 5)))
        oid = 100 + k
        objs.append('<object id="%d" p:UUID="%s" type="model"><components>'
                    '<component p:path="/3D/Objects/parts.model" objectid="%d" p:UUID="%s" transform="1 0 0 0 1 0 0 0 1 0 0 0"/>'
                    '<component p:path="/3D/Objects/parts.model" objectid="%d" p:UUID="%s" transform="1 0 0 0 1 0 0 0 1 0 0 0"/>'
                    '</components></object>' % (oid, uuid.uuid4(), base, uuid.uuid4(), top, uuid.uuid4()))
        ox, oy = (0.0, 0.0) if one_plate else origin(k, n)
        items.append('<item objectid="%d" p:UUID="%s" transform="1 0 0 0 1 0 0 0 1 %g %g 0" printable="1"/>'
                     % (oid, uuid.uuid4(), BW / 2 + ox, BD / 2 + oy))
        ms_objs.append('  <object id="%d">\n    <metadata key="name" value="slab"/>\n    <metadata key="extruder" value="1"/>\n'
                       '    <part id="%d" subtype="normal_part">\n      <metadata key="name" value="base"/>\n'
                       '      <metadata key="matrix" value="1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1"/>\n      <metadata key="extruder" value="1"/>\n    </part>\n'
                       '    <part id="%d" subtype="normal_part">\n      <metadata key="name" value="top"/>\n'
                       '      <metadata key="matrix" value="1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1"/>\n      <metadata key="extruder" value="2"/>\n    </part>\n'
                       '  </object>' % (oid, base, top))
        pk = 1 if one_plate else k + 1
        plates.append(pk)
    plate_xml = ""
    for pk in sorted(set(plates)):
        inst = "".join('    <model_instance>\n      <metadata key="object_id" value="%d"/>\n      <metadata key="instance_id" value="0"/>\n    </model_instance>\n'
                       % (100 + k) for k in range(n) if plates[k] == pk)
        plate_xml += '  <plate>\n    <metadata key="plater_id" value="%d"/>\n    <metadata key="plater_name" value=""/>\n    <metadata key="locked" value="false"/>\n%s  </plate>\n' % (pk, inst)
    NS = ('xmlns="http://schemas.microsoft.com/3dmanufacturing/core/2015/02" xmlns:BambuStudio="http://schemas.bambulab.com/package/2021" '
          'xmlns:p="http://schemas.microsoft.com/3dmanufacturing/production/2015/06" requiredextensions="p"')
    model = ('<?xml version="1.0" encoding="UTF-8"?>\n<model unit="millimeter" xml:lang="en-US" %s>\n'
             ' <metadata name="Application">OrcaSlicer-2.4.2</metadata>\n <metadata name="BambuStudio:3mfVersion">1</metadata>\n'
             ' <resources>\n%s\n </resources>\n <build p:UUID="%s">\n%s\n </build>\n</model>\n'
             % (NS, "\n".join(objs), uuid.uuid4(), "\n".join(items)))
    parts_model = ('<?xml version="1.0" encoding="UTF-8"?>\n<model unit="millimeter" xml:lang="en-US" %s>\n'
                   ' <metadata name="BambuStudio:3mfVersion">1</metadata>\n <resources>\n%s\n </resources>\n <build/>\n</model>\n'
                   % (NS, "\n".join(parts)))
    modset = '<?xml version="1.0" encoding="UTF-8"?>\n<config>\n%s\n%s</config>\n' % ("\n".join(ms_objs), plate_xml)
    # per-plate lists sized to the plate count
    for key in ("wipe_tower_x", "wipe_tower_y"):
        if key in cfg:
            cfg[key] = [cfg[key][0]] * len(set(plates))
    with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as z:
        z.writestr("[Content_Types].xml", '<?xml version="1.0" encoding="UTF-8"?>\n<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">\n'
                   ' <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>\n'
                   ' <Default Extension="model" ContentType="application/vnd.ms-package.3dmanufacturing-3dmodel+xml"/>\n'
                   ' <Default Extension="config" ContentType="application/xml"/>\n</Types>\n')
        z.writestr("_rels/.rels", '<?xml version="1.0" encoding="UTF-8"?>\n<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">\n'
                   ' <Relationship Target="/3D/3dmodel.model" Id="rel-1" Type="http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"/>\n</Relationships>\n')
        z.writestr("3D/_rels/3dmodel.model.rels", '<?xml version="1.0" encoding="UTF-8"?>\n<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">\n'
                   ' <Relationship Target="/3D/Objects/parts.model" Id="rel-1" Type="http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"/>\n</Relationships>\n')
        z.writestr("Metadata/project_settings.config", json.dumps(cfg, indent=4))
        z.writestr("3D/Objects/parts.model", parts_model)
        z.writestr("3D/3dmodel.model", model)
        z.writestr("Metadata/model_settings.config", modset)
    print("wrote", out, "objects", n, "plates", len(set(plates)))


if __name__ == "__main__":
    a = [x for x in sys.argv[1:] if not x.startswith("--")]
    main(a[0], a[1], int(a[2]) if len(a) > 2 else 6, one_plate="--one-plate" in sys.argv)