conversor3mf 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.
- conversor3mf-0.1.0/LICENSE +21 -0
- conversor3mf-0.1.0/PKG-INFO +164 -0
- conversor3mf-0.1.0/README.md +140 -0
- conversor3mf-0.1.0/conversor3mf/__init__.py +0 -0
- conversor3mf-0.1.0/conversor3mf/__main__.py +111 -0
- conversor3mf-0.1.0/conversor3mf/ace.py +203 -0
- conversor3mf-0.1.0/conversor3mf/config.py +106 -0
- conversor3mf-0.1.0/conversor3mf/conversor.py +438 -0
- conversor3mf-0.1.0/conversor3mf/cores.py +133 -0
- conversor3mf-0.1.0/conversor3mf/ilhas.py +447 -0
- conversor3mf-0.1.0/conversor3mf/pintura.py +96 -0
- conversor3mf-0.1.0/conversor3mf/projeto.py +220 -0
- conversor3mf-0.1.0/conversor3mf/servidor_mcp.py +151 -0
- conversor3mf-0.1.0/conversor3mf/verificar.py +75 -0
- conversor3mf-0.1.0/conversor3mf.egg-info/PKG-INFO +164 -0
- conversor3mf-0.1.0/conversor3mf.egg-info/SOURCES.txt +22 -0
- conversor3mf-0.1.0/conversor3mf.egg-info/dependency_links.txt +1 -0
- conversor3mf-0.1.0/conversor3mf.egg-info/entry_points.txt +3 -0
- conversor3mf-0.1.0/conversor3mf.egg-info/requires.txt +3 -0
- conversor3mf-0.1.0/conversor3mf.egg-info/top_level.txt +1 -0
- conversor3mf-0.1.0/pyproject.toml +35 -0
- conversor3mf-0.1.0/setup.cfg +4 -0
- conversor3mf-0.1.0/tests/test_conversao.py +136 -0
- conversor3mf-0.1.0/tests/test_pintura.py +48 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Johnny Welton
|
|
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,164 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: conversor3mf
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Convert Bambu Lab / MakerWorld multicolor 3MF projects to the Anycubic Kobra X, in the slot order of your ACE, with an MCP server for Claude
|
|
5
|
+
Author: Johnny Welton
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/JohnnyTrindade/conversor-3mf
|
|
8
|
+
Project-URL: Issues, https://github.com/JohnnyTrindade/conversor-3mf/issues
|
|
9
|
+
Keywords: 3mf,3d-printing,anycubic,kobra-x,ace,bambu-studio,makerworld,mcp,multicolor
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
13
|
+
Classifier: Natural Language :: Portuguese (Brazilian)
|
|
14
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Multimedia :: Graphics :: 3D Modeling
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: mcp<3,>=2.3
|
|
21
|
+
Requires-Dist: numpy>=2
|
|
22
|
+
Requires-Dist: pillow>=11
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# conversor3mf
|
|
26
|
+
|
|
27
|
+
[](https://github.com/JohnnyTrindade/conversor-3mf/actions/workflows/testes.yml)
|
|
28
|
+
|
|
29
|
+
**Print MakerWorld multicolor models on an Anycubic Kobra X without recoloring anything.**
|
|
30
|
+
[Português](README.pt-BR.md)
|
|
31
|
+
|
|
32
|
+
Most multicolor 3MF files on MakerWorld are made for Bambu Lab printers. When you open
|
|
33
|
+
one in Anycubic Slicer Next, the colors come in the file's filament order, not the order
|
|
34
|
+
of the spools in your ACE. After syncing the ACE they end up swapped. Painted models are
|
|
35
|
+
the worst case: the per-object color menu only shows one color, so you end up repainting
|
|
36
|
+
by hand.
|
|
37
|
+
|
|
38
|
+
conversor3mf rewrites the project so that **each color of the model goes to the ACE slot
|
|
39
|
+
where that spool already is**. It also includes an MCP server, so you can ask Claude to do
|
|
40
|
+
it for you.
|
|
41
|
+
|
|
42
|
+
> Not affiliated with Anycubic or Bambu Lab. All trademarks belong to their owners.
|
|
43
|
+
> Respect each model's license: the tool converts files you downloaded for your own use
|
|
44
|
+
> and never redistributes anything.
|
|
45
|
+
|
|
46
|
+
## What it does
|
|
47
|
+
|
|
48
|
+
- **Remaps colors.** Rewrites per-triangle paint, the filament of each part and
|
|
49
|
+
per-layer filament changes to your ACE slot order. It picks the closest color
|
|
50
|
+
(CIEDE2000) and merges identical colors.
|
|
51
|
+
- **Opens as a native Kobra X project.** No "Printer parameter switching" dialog and no
|
|
52
|
+
geometry-only import.
|
|
53
|
+
- **Keeps the designer's choices.** Layer height, walls, infill, supports, brim, ironing
|
|
54
|
+
and fuzzy skin carry over. Speeds and temperatures come from the Kobra X system profile.
|
|
55
|
+
- **Recenters plates.** Objects on every plate are centered on the 260 mm Kobra X bed.
|
|
56
|
+
- **Warns you.** It flags colors with no similar spool, more colors than ACE slots, and
|
|
57
|
+
PLA/PETG mixes.
|
|
58
|
+
- **Recolors whole regions ("islands").** Turns all holly leaves green or all stars red,
|
|
59
|
+
including the side walls of raised details, without touching the paint brush.
|
|
60
|
+
- **Never overwrites anything.** It writes `<name>_KobraX.3mf` next to the original.
|
|
61
|
+
|
|
62
|
+
## Requirements
|
|
63
|
+
|
|
64
|
+
- Windows with **Anycubic Slicer Next** installed. It is detected automatically; if not,
|
|
65
|
+
set `CONVERSOR3MF_SLICER` to its folder.
|
|
66
|
+
- **Python 3.10+**.
|
|
67
|
+
- An Anycubic **Kobra X** with ACE. Other Anycubic printers may work but are untested;
|
|
68
|
+
see [Limitations](#limitations).
|
|
69
|
+
|
|
70
|
+
## Install
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install git+https://github.com/JohnnyTrindade/conversor-3mf
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Use with Claude (MCP)
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
claude mcp add conversor3mf --scope user -- conversor3mf-mcp
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Then just ask, in any language:
|
|
83
|
+
|
|
84
|
+
> "Convert C:\Downloads\coaster.3mf for my Kobra X"
|
|
85
|
+
>
|
|
86
|
+
> "I swapped slot 4 for a yellow PLA"
|
|
87
|
+
>
|
|
88
|
+
> "In the coaster, make the holly leaves green and the stars red"
|
|
89
|
+
|
|
90
|
+
Claude shows where each color will go before converting. When recoloring, it looks at a
|
|
91
|
+
numbered map of the model.
|
|
92
|
+
|
|
93
|
+
## First-time setup: tell it what is in your ACE
|
|
94
|
+
|
|
95
|
+
1. In Anycubic Slicer Next, select the Kobra X, sync the ACE and save any project.
|
|
96
|
+
2. Run this command, or ask Claude to "set up my ACE from that project":
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
conversor3mf ace definir "C:\path\to\saved_project.3mf"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
This saves the printer profile and your slot colors and materials. When you only change a
|
|
103
|
+
spool's color, update it like this:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
conversor3mf ace slot 4 --cor "#FFD700" --tipo PLA
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
For a material that is not in any slot yet (ABS, TPU…), save a new project with it and run
|
|
110
|
+
`ace definir` again.
|
|
111
|
+
|
|
112
|
+
Your data lives in `%APPDATA%\conversor3mf`. Change it with `CONVERSOR3MF_DADOS`.
|
|
113
|
+
|
|
114
|
+
## Command line
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
conversor3mf planejar model.3mf # show which slot each color goes to
|
|
118
|
+
conversor3mf converter model.3mf --mapa 1=3 # convert; force filament 1 into slot 3
|
|
119
|
+
conversor3mf ilhas model_KobraX.3mf --vista cima # numbered map of color regions
|
|
120
|
+
conversor3mf repintar model_KobraX.3mf 32=4,3=3 # recolor islands 32 and 3
|
|
121
|
+
conversor3mf verificar model_KobraX.3mf # reopen in Anycubic Slicer Next and compare
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Command and option names are in Portuguese. Run `conversor3mf --help` for the full list.
|
|
125
|
+
|
|
126
|
+
## How it works
|
|
127
|
+
|
|
128
|
+
- **Color format.** A Bambu/Orca 3MF stores filament numbers, not colors. The paint of each
|
|
129
|
+
triangle is a hex-encoded subdivision tree with a filament number on every leaf. The
|
|
130
|
+
converter decodes that tree, swaps the numbers and encodes it back. Every other part of
|
|
131
|
+
the file is left untouched.
|
|
132
|
+
- **Printer settings.** They come from a project saved by your own Anycubic Slicer Next,
|
|
133
|
+
so no Anycubic profiles are shipped with this package.
|
|
134
|
+
- **Islands.** Connected triangles of the same color, after welding duplicate vertices.
|
|
135
|
+
When a triangle has several colors, recoloring only changes the leaves of that island's
|
|
136
|
+
color, including on the island's border.
|
|
137
|
+
|
|
138
|
+
## Limitations
|
|
139
|
+
|
|
140
|
+
- **Verification of multi-plate projects.** Anycubic Slicer Next 2.0.0.3 crashes from the
|
|
141
|
+
command line on any project with more than one plate. Those projects are only checked
|
|
142
|
+
internally, so open them in the slicer before slicing.
|
|
143
|
+
- **Plate grid spacing.** It is measured only for the Kobra X (314.4 × 328.8 mm). Other
|
|
144
|
+
printers fall back to 1.2 × the bed size.
|
|
145
|
+
- **More colors than slots.** Similar colors get merged. For multi-plate projects you can
|
|
146
|
+
print plate by plate and use `--mapa`.
|
|
147
|
+
- **Mesh-only 3MF files.** Files without a slicer project inside have no colors to
|
|
148
|
+
convert.
|
|
149
|
+
|
|
150
|
+
## Development
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
python -m venv .venv
|
|
154
|
+
.venv\Scripts\pip install -e .
|
|
155
|
+
.venv\Scripts\python -m unittest discover -s tests
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The tests use synthetic 3MF files built in `tests/fixtures.py` and do not need the slicer.
|
|
159
|
+
`ferramentas/testar_na_interface.ps1` opens a file in the slicer GUI, takes a screenshot
|
|
160
|
+
and closes it.
|
|
161
|
+
|
|
162
|
+
## License
|
|
163
|
+
|
|
164
|
+
MIT
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# conversor3mf
|
|
2
|
+
|
|
3
|
+
[](https://github.com/JohnnyTrindade/conversor-3mf/actions/workflows/testes.yml)
|
|
4
|
+
|
|
5
|
+
**Print MakerWorld multicolor models on an Anycubic Kobra X without recoloring anything.**
|
|
6
|
+
[Português](README.pt-BR.md)
|
|
7
|
+
|
|
8
|
+
Most multicolor 3MF files on MakerWorld are made for Bambu Lab printers. When you open
|
|
9
|
+
one in Anycubic Slicer Next, the colors come in the file's filament order, not the order
|
|
10
|
+
of the spools in your ACE. After syncing the ACE they end up swapped. Painted models are
|
|
11
|
+
the worst case: the per-object color menu only shows one color, so you end up repainting
|
|
12
|
+
by hand.
|
|
13
|
+
|
|
14
|
+
conversor3mf rewrites the project so that **each color of the model goes to the ACE slot
|
|
15
|
+
where that spool already is**. It also includes an MCP server, so you can ask Claude to do
|
|
16
|
+
it for you.
|
|
17
|
+
|
|
18
|
+
> Not affiliated with Anycubic or Bambu Lab. All trademarks belong to their owners.
|
|
19
|
+
> Respect each model's license: the tool converts files you downloaded for your own use
|
|
20
|
+
> and never redistributes anything.
|
|
21
|
+
|
|
22
|
+
## What it does
|
|
23
|
+
|
|
24
|
+
- **Remaps colors.** Rewrites per-triangle paint, the filament of each part and
|
|
25
|
+
per-layer filament changes to your ACE slot order. It picks the closest color
|
|
26
|
+
(CIEDE2000) and merges identical colors.
|
|
27
|
+
- **Opens as a native Kobra X project.** No "Printer parameter switching" dialog and no
|
|
28
|
+
geometry-only import.
|
|
29
|
+
- **Keeps the designer's choices.** Layer height, walls, infill, supports, brim, ironing
|
|
30
|
+
and fuzzy skin carry over. Speeds and temperatures come from the Kobra X system profile.
|
|
31
|
+
- **Recenters plates.** Objects on every plate are centered on the 260 mm Kobra X bed.
|
|
32
|
+
- **Warns you.** It flags colors with no similar spool, more colors than ACE slots, and
|
|
33
|
+
PLA/PETG mixes.
|
|
34
|
+
- **Recolors whole regions ("islands").** Turns all holly leaves green or all stars red,
|
|
35
|
+
including the side walls of raised details, without touching the paint brush.
|
|
36
|
+
- **Never overwrites anything.** It writes `<name>_KobraX.3mf` next to the original.
|
|
37
|
+
|
|
38
|
+
## Requirements
|
|
39
|
+
|
|
40
|
+
- Windows with **Anycubic Slicer Next** installed. It is detected automatically; if not,
|
|
41
|
+
set `CONVERSOR3MF_SLICER` to its folder.
|
|
42
|
+
- **Python 3.10+**.
|
|
43
|
+
- An Anycubic **Kobra X** with ACE. Other Anycubic printers may work but are untested;
|
|
44
|
+
see [Limitations](#limitations).
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install git+https://github.com/JohnnyTrindade/conversor-3mf
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Use with Claude (MCP)
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
claude mcp add conversor3mf --scope user -- conversor3mf-mcp
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Then just ask, in any language:
|
|
59
|
+
|
|
60
|
+
> "Convert C:\Downloads\coaster.3mf for my Kobra X"
|
|
61
|
+
>
|
|
62
|
+
> "I swapped slot 4 for a yellow PLA"
|
|
63
|
+
>
|
|
64
|
+
> "In the coaster, make the holly leaves green and the stars red"
|
|
65
|
+
|
|
66
|
+
Claude shows where each color will go before converting. When recoloring, it looks at a
|
|
67
|
+
numbered map of the model.
|
|
68
|
+
|
|
69
|
+
## First-time setup: tell it what is in your ACE
|
|
70
|
+
|
|
71
|
+
1. In Anycubic Slicer Next, select the Kobra X, sync the ACE and save any project.
|
|
72
|
+
2. Run this command, or ask Claude to "set up my ACE from that project":
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
conversor3mf ace definir "C:\path\to\saved_project.3mf"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
This saves the printer profile and your slot colors and materials. When you only change a
|
|
79
|
+
spool's color, update it like this:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
conversor3mf ace slot 4 --cor "#FFD700" --tipo PLA
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
For a material that is not in any slot yet (ABS, TPU…), save a new project with it and run
|
|
86
|
+
`ace definir` again.
|
|
87
|
+
|
|
88
|
+
Your data lives in `%APPDATA%\conversor3mf`. Change it with `CONVERSOR3MF_DADOS`.
|
|
89
|
+
|
|
90
|
+
## Command line
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
conversor3mf planejar model.3mf # show which slot each color goes to
|
|
94
|
+
conversor3mf converter model.3mf --mapa 1=3 # convert; force filament 1 into slot 3
|
|
95
|
+
conversor3mf ilhas model_KobraX.3mf --vista cima # numbered map of color regions
|
|
96
|
+
conversor3mf repintar model_KobraX.3mf 32=4,3=3 # recolor islands 32 and 3
|
|
97
|
+
conversor3mf verificar model_KobraX.3mf # reopen in Anycubic Slicer Next and compare
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Command and option names are in Portuguese. Run `conversor3mf --help` for the full list.
|
|
101
|
+
|
|
102
|
+
## How it works
|
|
103
|
+
|
|
104
|
+
- **Color format.** A Bambu/Orca 3MF stores filament numbers, not colors. The paint of each
|
|
105
|
+
triangle is a hex-encoded subdivision tree with a filament number on every leaf. The
|
|
106
|
+
converter decodes that tree, swaps the numbers and encodes it back. Every other part of
|
|
107
|
+
the file is left untouched.
|
|
108
|
+
- **Printer settings.** They come from a project saved by your own Anycubic Slicer Next,
|
|
109
|
+
so no Anycubic profiles are shipped with this package.
|
|
110
|
+
- **Islands.** Connected triangles of the same color, after welding duplicate vertices.
|
|
111
|
+
When a triangle has several colors, recoloring only changes the leaves of that island's
|
|
112
|
+
color, including on the island's border.
|
|
113
|
+
|
|
114
|
+
## Limitations
|
|
115
|
+
|
|
116
|
+
- **Verification of multi-plate projects.** Anycubic Slicer Next 2.0.0.3 crashes from the
|
|
117
|
+
command line on any project with more than one plate. Those projects are only checked
|
|
118
|
+
internally, so open them in the slicer before slicing.
|
|
119
|
+
- **Plate grid spacing.** It is measured only for the Kobra X (314.4 × 328.8 mm). Other
|
|
120
|
+
printers fall back to 1.2 × the bed size.
|
|
121
|
+
- **More colors than slots.** Similar colors get merged. For multi-plate projects you can
|
|
122
|
+
print plate by plate and use `--mapa`.
|
|
123
|
+
- **Mesh-only 3MF files.** Files without a slicer project inside have no colors to
|
|
124
|
+
convert.
|
|
125
|
+
|
|
126
|
+
## Development
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
python -m venv .venv
|
|
130
|
+
.venv\Scripts\pip install -e .
|
|
131
|
+
.venv\Scripts\python -m unittest discover -s tests
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The tests use synthetic 3MF files built in `tests/fixtures.py` and do not need the slicer.
|
|
135
|
+
`ferramentas/testar_na_interface.ps1` opens a file in the slicer GUI, takes a screenshot
|
|
136
|
+
and closes it.
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
MIT
|
|
File without changes
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Linha de comando: python -m conversor3mf <comando> ..."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from . import ace as modulo_ace
|
|
11
|
+
from . import ilhas
|
|
12
|
+
from .conversor import converter, planejar
|
|
13
|
+
from .projeto import ler_projeto
|
|
14
|
+
from .verificar import verificar
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _mapa(texto: str | None) -> dict[int, int]:
|
|
18
|
+
"""'1=3,2=1' -> {1: 3, 2: 1}"""
|
|
19
|
+
if not texto:
|
|
20
|
+
return {}
|
|
21
|
+
mapa = {}
|
|
22
|
+
for par in texto.split(","):
|
|
23
|
+
origem, _, slot = par.partition("=")
|
|
24
|
+
mapa[int(origem)] = int(slot)
|
|
25
|
+
return mapa
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _mostrar(dados) -> None:
|
|
29
|
+
print(json.dumps(dados, indent=2, ensure_ascii=False))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def main(argv: list[str] | None = None) -> int:
|
|
33
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
34
|
+
p = argparse.ArgumentParser(prog="conversor3mf", description="Converte 3MF da Bambu para a Kobra X na ordem do seu ACE.")
|
|
35
|
+
sub = p.add_subparsers(dest="comando", required=True)
|
|
36
|
+
|
|
37
|
+
s = sub.add_parser("inspecionar", help="mostra filamentos, cores e onde cada um é usado")
|
|
38
|
+
s.add_argument("arquivo")
|
|
39
|
+
|
|
40
|
+
s = sub.add_parser("planejar", help="mostra em qual slot cada cor vai sair, sem gravar nada")
|
|
41
|
+
s.add_argument("arquivo")
|
|
42
|
+
s.add_argument("--mapa", help="força slots, ex.: 1=3,2=1 (filamento do modelo = slot do ACE)")
|
|
43
|
+
|
|
44
|
+
s = sub.add_parser("converter", help="grava o 3MF convertido")
|
|
45
|
+
s.add_argument("arquivo")
|
|
46
|
+
s.add_argument("--mapa", help="força slots, ex.: 1=3,2=1")
|
|
47
|
+
s.add_argument("--saida", help="caminho do arquivo convertido (padrão: <nome>_KobraX.3mf)")
|
|
48
|
+
s.add_argument("--sem-verificar", action="store_true", help="não confere no slicer depois de gravar")
|
|
49
|
+
|
|
50
|
+
s = sub.add_parser("verificar", help="abre o arquivo no Anycubic Slicer Next e confere")
|
|
51
|
+
s.add_argument("arquivo")
|
|
52
|
+
|
|
53
|
+
s = sub.add_parser("ilhas", help="desenha o objeto com o número de cada região de mesma cor")
|
|
54
|
+
s.add_argument("arquivo")
|
|
55
|
+
s.add_argument("--vista", default="cima", choices=["cima", "baixo", "frente", "tras", "esquerda", "direita"])
|
|
56
|
+
s.add_argument("--objeto")
|
|
57
|
+
s.add_argument("--regiao", help="zoom em mm: x0,y0,x1,y1")
|
|
58
|
+
s.add_argument("--png", help="onde salvar a imagem (padrão: <nome>_ilhas_<vista>.png ao lado)")
|
|
59
|
+
|
|
60
|
+
s = sub.add_parser("repintar", help="grava um 3MF novo com ilhas em outro slot")
|
|
61
|
+
s.add_argument("arquivo")
|
|
62
|
+
s.add_argument("mudancas", help="ilha=slot separados por vírgula, ex.: 32=4,3=3")
|
|
63
|
+
s.add_argument("--objeto")
|
|
64
|
+
s.add_argument("--saida")
|
|
65
|
+
|
|
66
|
+
s = sub.add_parser("ace", help="mostra ou altera os slots do ACE")
|
|
67
|
+
acoes = s.add_subparsers(dest="acao", required=True)
|
|
68
|
+
acoes.add_parser("mostrar")
|
|
69
|
+
d = acoes.add_parser("definir", help="usa um projeto salvo no slicer (com o ACE sincronizado) como molde")
|
|
70
|
+
d.add_argument("projeto")
|
|
71
|
+
t = acoes.add_parser("slot", help="troca cor/tipo/nome de um slot")
|
|
72
|
+
t.add_argument("numero", type=int)
|
|
73
|
+
t.add_argument("--cor")
|
|
74
|
+
t.add_argument("--tipo")
|
|
75
|
+
t.add_argument("--nome")
|
|
76
|
+
|
|
77
|
+
a = p.parse_args(argv)
|
|
78
|
+
if a.comando == "inspecionar":
|
|
79
|
+
_mostrar(ler_projeto(a.arquivo).resumo())
|
|
80
|
+
elif a.comando == "planejar":
|
|
81
|
+
_mostrar(planejar(ler_projeto(a.arquivo), modulo_ace.carregar(), _mapa(a.mapa)).resumo())
|
|
82
|
+
elif a.comando == "converter":
|
|
83
|
+
resultado = converter(a.arquivo, a.saida, _mapa(a.mapa))
|
|
84
|
+
dados = resultado.resumo()
|
|
85
|
+
if not a.sem_verificar:
|
|
86
|
+
dados["verificacao"] = verificar(resultado.destino)
|
|
87
|
+
_mostrar(dados)
|
|
88
|
+
elif a.comando == "verificar":
|
|
89
|
+
_mostrar(verificar(a.arquivo))
|
|
90
|
+
elif a.comando == "ilhas":
|
|
91
|
+
regiao = [float(x) for x in a.regiao.split(",")] if a.regiao else None
|
|
92
|
+
png, info = ilhas.mapa_de_ilhas(a.arquivo, a.objeto, a.vista, regiao)
|
|
93
|
+
destino = Path(a.png) if a.png else Path(a.arquivo).with_name(f"{Path(a.arquivo).stem}_ilhas_{a.vista}.png")
|
|
94
|
+
destino.write_bytes(png)
|
|
95
|
+
_mostrar({"imagem": str(destino), **info})
|
|
96
|
+
elif a.comando == "repintar":
|
|
97
|
+
resultado = ilhas.repintar_ilhas(a.arquivo, _mapa(a.mudancas), a.objeto, a.saida)
|
|
98
|
+
resultado["verificacao"] = verificar(resultado["arquivo_repintado"])
|
|
99
|
+
_mostrar(resultado)
|
|
100
|
+
elif a.comando == "ace":
|
|
101
|
+
if a.acao == "mostrar":
|
|
102
|
+
_mostrar(modulo_ace.carregar().resumo())
|
|
103
|
+
elif a.acao == "definir":
|
|
104
|
+
_mostrar(modulo_ace.definir_molde(a.projeto).resumo())
|
|
105
|
+
else:
|
|
106
|
+
_mostrar(modulo_ace.alterar_slot(a.numero, a.cor, a.tipo, a.nome).resumo())
|
|
107
|
+
return 0
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
if __name__ == "__main__":
|
|
111
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""Molde de configuração da impressora e slots do ACE.
|
|
2
|
+
|
|
3
|
+
O molde é tirado de um projeto salvo pelo próprio Anycubic Slicer Next depois de
|
|
4
|
+
sincronizar o ACE: ele já traz impressora, perfis de filamento e a ordem real
|
|
5
|
+
dos carretéis. O arquivo `dados/meu_ace.json` guarda os slots e pode ser
|
|
6
|
+
editado à mão (por exemplo, para trocar a cor de um slot sem salvar outro molde).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import re
|
|
13
|
+
import zipfile
|
|
14
|
+
from dataclasses import asdict, dataclass
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from . import cores
|
|
18
|
+
from .config import pasta_dados, pasta_perfis
|
|
19
|
+
|
|
20
|
+
__all__ = ["Slot", "Ace", "definir_molde", "carregar", "alterar_slot", "aplicar_slots"]
|
|
21
|
+
|
|
22
|
+
ARQUIVO_ACE = "meu_ace.json"
|
|
23
|
+
ARQUIVO_MOLDE = "molde_project_settings.json"
|
|
24
|
+
|
|
25
|
+
# Espaçamento entre placas medido em projetos salvos pelo Anycubic Slicer Next
|
|
26
|
+
ESPACAMENTO_PLACAS = {"Anycubic Kobra X": (314.4, 328.8)}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class Slot:
|
|
31
|
+
slot: int
|
|
32
|
+
cor: str
|
|
33
|
+
tipo: str
|
|
34
|
+
nome: str
|
|
35
|
+
perfil: str
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class Ace:
|
|
40
|
+
slots: list[Slot]
|
|
41
|
+
molde: dict
|
|
42
|
+
origem: str
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def impressora(self) -> str:
|
|
46
|
+
return self.molde.get("printer_model", "?")
|
|
47
|
+
|
|
48
|
+
def resumo(self) -> dict:
|
|
49
|
+
return {
|
|
50
|
+
"impressora": self.impressora,
|
|
51
|
+
"molde_tirado_de": self.origem,
|
|
52
|
+
"slots": [asdict(s) for s in self.slots],
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _chaves_de_filamento() -> set[str]:
|
|
57
|
+
"""Todas as chaves que aparecem em perfis de filamento do slicer."""
|
|
58
|
+
chaves = {"filament_colour", "filament_colour_info", "filament_settings_id", "default_filament_colour"}
|
|
59
|
+
perfis = pasta_perfis()
|
|
60
|
+
for arquivo in (perfis / "filament").glob("*.json") if perfis else []:
|
|
61
|
+
try:
|
|
62
|
+
chaves |= set(json.loads(arquivo.read_text(encoding="utf8")))
|
|
63
|
+
except (OSError, ValueError):
|
|
64
|
+
continue
|
|
65
|
+
return chaves - {"name", "inherits", "from", "type", "setting_id", "instantiation", "version",
|
|
66
|
+
"compatible_printers", "compatible_printers_condition", "is_custom_defined",
|
|
67
|
+
"filament_id"}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def perfil_de_processo(nome: str) -> dict | None:
|
|
71
|
+
perfis = pasta_perfis()
|
|
72
|
+
caminho = perfis / "process" / f"{nome}.json" if perfis else None
|
|
73
|
+
if caminho is None or not caminho.exists():
|
|
74
|
+
return None
|
|
75
|
+
perfil = json.loads(caminho.read_text(encoding="utf8"))
|
|
76
|
+
if perfil.get("inherits"):
|
|
77
|
+
base = perfil_de_processo(perfil["inherits"]) or {}
|
|
78
|
+
perfil = {**base, **perfil}
|
|
79
|
+
return perfil
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
CHAVES_META = {"type", "from", "setting_id", "is_custom_defined", "instantiation", "version",
|
|
83
|
+
"inherits", "name", "compatible_printers", "compatible_printers_condition"}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def definir_molde(projeto_3mf: str | Path) -> Ace:
|
|
87
|
+
"""Lê um projeto salvo pelo Anycubic Slicer Next e grava molde + slots em dados/."""
|
|
88
|
+
projeto_3mf = Path(projeto_3mf)
|
|
89
|
+
with zipfile.ZipFile(projeto_3mf) as arquivo:
|
|
90
|
+
config = json.loads(arquivo.read("Metadata/project_settings.config"))
|
|
91
|
+
principal = arquivo.read("3D/3dmodel.model").decode("utf8")
|
|
92
|
+
embutidos = [n for n in arquivo.namelist()
|
|
93
|
+
if re.match(r"Metadata/(machine|filament|process)_settings_\d+\.config$", n)]
|
|
94
|
+
if not str(config.get("printer_model", "")).startswith("Anycubic"):
|
|
95
|
+
raise ValueError(f"o projeto é de {config.get('printer_model')}, preciso de um salvo para a impressora Anycubic")
|
|
96
|
+
diferencas = config.get("different_settings_to_system") or []
|
|
97
|
+
if diferencas and diferencas[-1]:
|
|
98
|
+
raise ValueError(
|
|
99
|
+
"o perfil de impressora desse projeto foi modificado "
|
|
100
|
+
f"({diferencas[-1][:120]}). Salve um projeto usando o perfil de sistema da impressora."
|
|
101
|
+
)
|
|
102
|
+
if any("machine_settings" in n for n in embutidos):
|
|
103
|
+
# projetos com perfil de impressora embutido podem carregar G-code de outra máquina
|
|
104
|
+
raise ValueError("o projeto traz um perfil de impressora personalizado embutido; use outro projeto")
|
|
105
|
+
|
|
106
|
+
# processo volta ao perfil de fábrica; as escolhas do modelo entram na conversão
|
|
107
|
+
perfil = perfil_de_processo(config.get("print_settings_id", ""))
|
|
108
|
+
if perfil:
|
|
109
|
+
for chave, valor in perfil.items():
|
|
110
|
+
if chave not in CHAVES_META and chave in config:
|
|
111
|
+
config[chave] = valor
|
|
112
|
+
if diferencas:
|
|
113
|
+
diferencas[0] = ""
|
|
114
|
+
app = re.search(r'<metadata name="Application">([^<]*)<', principal)
|
|
115
|
+
config["_aplicativo"] = app.group(1) if app else f"BambuStudio-{config.get('version', '2.0.0.3')}"
|
|
116
|
+
|
|
117
|
+
n = len(config.get("filament_colour") or [])
|
|
118
|
+
slots = [
|
|
119
|
+
Slot(
|
|
120
|
+
slot=i + 1,
|
|
121
|
+
cor=cores.normalizar(config["filament_colour"][i]),
|
|
122
|
+
tipo=config["filament_type"][i],
|
|
123
|
+
nome=cores.nome_da_cor(config["filament_colour"][i]),
|
|
124
|
+
perfil=config["filament_settings_id"][i],
|
|
125
|
+
)
|
|
126
|
+
for i in range(n)
|
|
127
|
+
]
|
|
128
|
+
pasta_dados().mkdir(parents=True, exist_ok=True)
|
|
129
|
+
(pasta_dados() / ARQUIVO_MOLDE).write_text(json.dumps(config, indent=1, ensure_ascii=False), encoding="utf8")
|
|
130
|
+
ace = Ace(slots=slots, molde=config, origem=str(projeto_3mf))
|
|
131
|
+
salvar_slots(ace)
|
|
132
|
+
return ace
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def salvar_slots(ace: Ace) -> None:
|
|
136
|
+
dados = {
|
|
137
|
+
"_leia_me": "Slots do ACE na ordem da impressora. Pode editar cor e nome; "
|
|
138
|
+
"para mudar o tipo use um tipo que já exista em algum slot do molde.",
|
|
139
|
+
"molde_tirado_de": ace.origem,
|
|
140
|
+
"slots": [asdict(s) for s in ace.slots],
|
|
141
|
+
}
|
|
142
|
+
pasta_dados().mkdir(parents=True, exist_ok=True)
|
|
143
|
+
(pasta_dados() / ARQUIVO_ACE).write_text(json.dumps(dados, indent=2, ensure_ascii=False), encoding="utf8")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def carregar() -> Ace:
|
|
147
|
+
caminho_molde = pasta_dados() / ARQUIVO_MOLDE
|
|
148
|
+
caminho_ace = pasta_dados() / ARQUIVO_ACE
|
|
149
|
+
if not caminho_molde.exists() or not caminho_ace.exists():
|
|
150
|
+
raise FileNotFoundError(
|
|
151
|
+
"ainda não há molde do ACE. Salve um projeto no Anycubic Slicer Next com o ACE "
|
|
152
|
+
"sincronizado e rode: python -m conversor3mf ace definir <projeto.3mf>"
|
|
153
|
+
)
|
|
154
|
+
molde = json.loads(caminho_molde.read_text(encoding="utf8"))
|
|
155
|
+
dados = json.loads(caminho_ace.read_text(encoding="utf8"))
|
|
156
|
+
slots = [Slot(**{**s, "cor": cores.normalizar(s["cor"])}) for s in dados["slots"]]
|
|
157
|
+
tipos_no_molde = set(molde.get("filament_type") or [])
|
|
158
|
+
for s in slots:
|
|
159
|
+
if s.tipo not in tipos_no_molde:
|
|
160
|
+
raise ValueError(
|
|
161
|
+
f"slot {s.slot} está como {s.tipo}, mas o molde só tem {sorted(tipos_no_molde)}. "
|
|
162
|
+
"Salve um projeto no slicer com esse filamento e defina o molde de novo."
|
|
163
|
+
)
|
|
164
|
+
return Ace(slots=slots, molde=molde, origem=dados.get("molde_tirado_de", "?"))
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def alterar_slot(numero: int, cor: str | None = None, tipo: str | None = None, nome: str | None = None) -> Ace:
|
|
168
|
+
"""Atualiza um slot depois de trocar o carretel; o tipo precisa existir no molde."""
|
|
169
|
+
ace = carregar()
|
|
170
|
+
slot = next((s for s in ace.slots if s.slot == numero), None)
|
|
171
|
+
if slot is None:
|
|
172
|
+
raise ValueError(f"slot {numero} não existe")
|
|
173
|
+
if tipo:
|
|
174
|
+
tipos = sorted(set(ace.molde.get("filament_type") or []))
|
|
175
|
+
if tipo not in tipos:
|
|
176
|
+
raise ValueError(
|
|
177
|
+
f"o molde só tem {tipos}. Para usar {tipo}, salve um projeto no slicer com esse "
|
|
178
|
+
"filamento em algum slot e defina o molde de novo."
|
|
179
|
+
)
|
|
180
|
+
slot.tipo = tipo
|
|
181
|
+
if cor:
|
|
182
|
+
slot.cor = cores.normalizar(cor)
|
|
183
|
+
slot.nome = cores.nome_da_cor(slot.cor)
|
|
184
|
+
if nome:
|
|
185
|
+
slot.nome = nome
|
|
186
|
+
salvar_slots(ace)
|
|
187
|
+
return ace
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def aplicar_slots(config: dict, ace: Ace) -> None:
|
|
191
|
+
"""Ajusta as listas por filamento do config para refletir os slots (cor e tipo)."""
|
|
192
|
+
chaves = _chaves_de_filamento()
|
|
193
|
+
n = len(ace.slots)
|
|
194
|
+
tipos_molde = list(config.get("filament_type") or [])
|
|
195
|
+
original = {k: list(v) for k, v in config.items() if k in chaves and isinstance(v, list) and len(v) == n}
|
|
196
|
+
for i, slot in enumerate(ace.slots):
|
|
197
|
+
if i < len(tipos_molde) and tipos_molde[i] != slot.tipo:
|
|
198
|
+
fonte = tipos_molde.index(slot.tipo)
|
|
199
|
+
for chave, valores in original.items():
|
|
200
|
+
config[chave][i] = valores[fonte]
|
|
201
|
+
config["filament_colour"][i] = slot.cor
|
|
202
|
+
if "filament_colour_info" in config and i < len(config["filament_colour_info"]):
|
|
203
|
+
config["filament_colour_info"][i] = slot.cor
|