autoforge 1.3.4__tar.gz → 1.4.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.
- {autoforge-1.3.4 → autoforge-1.4.0}/PKG-INFO +7 -2
- {autoforge-1.3.4 → autoforge-1.4.0}/README.md +5 -1
- {autoforge-1.3.4 → autoforge-1.4.0}/pyproject.toml +3 -2
- {autoforge-1.3.4 → autoforge-1.4.0}/src/AutoForge.egg-info/PKG-INFO +7 -2
- {autoforge-1.3.4 → autoforge-1.4.0}/src/AutoForge.egg-info/SOURCES.txt +7 -2
- {autoforge-1.3.4 → autoforge-1.4.0}/src/AutoForge.egg-info/requires.txt +1 -0
- autoforge-1.4.0/src/autoforge/Gui/Views/backend_database.py +205 -0
- autoforge-1.4.0/src/autoforge/Gui/Views/filament_editor.py +216 -0
- autoforge-1.4.0/src/autoforge/Gui/app.py +202 -0
- autoforge-1.4.0/src/autoforge/Gui/main.py +29 -0
- {autoforge-1.3.4 → autoforge-1.4.0}/src/autoforge/Helper/FilamentHelper.py +5 -2
- autoforge-1.4.0/src/autoforge/Helper/ImageHelper.py +164 -0
- {autoforge-1.3.4 → autoforge-1.4.0}/src/autoforge/Helper/OptimizerHelper.py +3 -3
- {autoforge-1.3.4 → autoforge-1.4.0}/src/autoforge/Helper/PruningHelper.py +2 -1
- {autoforge-1.3.4/src/autoforge → autoforge-1.4.0/src/autoforge/Helper}/filamentcolors_library.py +45 -11
- autoforge-1.4.0/src/autoforge/Loss/LossFunctions.py +104 -0
- {autoforge-1.3.4 → autoforge-1.4.0}/src/autoforge/Misc/color_fit_all.py +23 -9
- {autoforge-1.3.4 → autoforge-1.4.0}/src/autoforge/Modules/Optimizer.py +69 -4
- autoforge-1.4.0/src/autoforge/Modules/__init__.py +0 -0
- autoforge-1.4.0/src/autoforge/__init__.py +0 -0
- {autoforge-1.3.4 → autoforge-1.4.0}/src/autoforge/auto_forge.py +44 -7
- autoforge-1.3.4/src/autoforge/Helper/ImageHelper.py +0 -22
- autoforge-1.3.4/src/autoforge/Loss/LossFunctions.py +0 -100
- autoforge-1.3.4/src/autoforge/auto_forge_gradio.py +0 -495
- {autoforge-1.3.4 → autoforge-1.4.0}/setup.cfg +0 -0
- {autoforge-1.3.4 → autoforge-1.4.0}/src/AutoForge.egg-info/dependency_links.txt +0 -0
- {autoforge-1.3.4 → autoforge-1.4.0}/src/AutoForge.egg-info/entry_points.txt +0 -0
- {autoforge-1.3.4 → autoforge-1.4.0}/src/AutoForge.egg-info/top_level.txt +0 -0
- {autoforge-1.3.4/src/autoforge/Helper → autoforge-1.4.0/src/autoforge/Gui/Views}/__init__.py +0 -0
- {autoforge-1.3.4/src/autoforge/Loss → autoforge-1.4.0/src/autoforge/Gui}/__init__.py +0 -0
- {autoforge-1.3.4 → autoforge-1.4.0}/src/autoforge/Helper/OutputHelper.py +0 -0
- {autoforge-1.3.4/src/autoforge/Misc → autoforge-1.4.0/src/autoforge/Helper}/__init__.py +0 -0
- {autoforge-1.3.4 → autoforge-1.4.0}/src/autoforge/Loss/PerceptionLoss.py +0 -0
- {autoforge-1.3.4/src/autoforge/Modules → autoforge-1.4.0/src/autoforge/Loss}/__init__.py +0 -0
- {autoforge-1.3.4/src/autoforge → autoforge-1.4.0/src/autoforge/Misc}/__init__.py +0 -0
- {autoforge-1.3.4 → autoforge-1.4.0}/src/autoforge/__main__.py +0 -0
- {autoforge-1.3.4 → autoforge-1.4.0}/src/autoforge/old_jax_files.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: AutoForge
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: AutoForge is a Python tool for generating 3D printed layered models from an input image.
|
|
5
5
|
Author-email: Hendric Voss <hvoss@techfak.uni-bielefeld.de>
|
|
6
6
|
License: CC BY-NC-SA 4.0
|
|
@@ -17,6 +17,7 @@ Requires-Dist: tqdm>=4.50.0
|
|
|
17
17
|
Requires-Dist: pandas>=1.1.0
|
|
18
18
|
Requires-Dist: scikit-image
|
|
19
19
|
Requires-Dist: scikit-learn
|
|
20
|
+
Requires-Dist: tensorboard
|
|
20
21
|
|
|
21
22
|
# AutoForge
|
|
22
23
|
|
|
@@ -103,6 +104,7 @@ autoforge --input_image path/to/input_image.jpg --csv_file path/to/materials.csv
|
|
|
103
104
|
- `--layer_height`: Layer thickness in millimeters (default: 0.04).
|
|
104
105
|
- `--max_layers`: Maximum number of layers (default: 75). \
|
|
105
106
|
**Note:** This is about 3mm + the background height
|
|
107
|
+
- `--min_layers`: Minimum number of layers (default: 0). Used to limit height of pruning
|
|
106
108
|
- `--background_height`: Height of the background in millimeters (default: 0.4). \
|
|
107
109
|
**Note:** The background height must be divisible by the layer height.
|
|
108
110
|
- `--background_color`: Background color in hexadecimal format (default: `#000000` aka Black). \
|
|
@@ -110,8 +112,11 @@ autoforge --input_image path/to/input_image.jpg --csv_file path/to/materials.csv
|
|
|
110
112
|
- `--output_size`: Maximum dimension for target image (default: 1024).
|
|
111
113
|
- `--solver_size`: Maximum dimension for solver (fast) image (default: 128). \
|
|
112
114
|
**Note:** We solve on a smaller size as this is many times faster, but also a bit less accurate. Increase if you need more accuracy.
|
|
113
|
-
- `--
|
|
115
|
+
- `--init_tau`: Initial tau value for Gumbel-Softmax (default: 1.0).
|
|
116
|
+
- `--final_tau`: Final tau value for the Gumbel-Softmax formulation (default: 0.01).
|
|
114
117
|
- `--visualize`: Flag to enable live visualization of the composite image during optimization.
|
|
118
|
+
- `--tensorboard`: Enable TensorBoard logging
|
|
119
|
+
- `--run-name`: Name of the run used for TensorBoard logging (optional).
|
|
115
120
|
- `--perform_pruning`: Perform pruning after optimization (default: True). \
|
|
116
121
|
**Note:** This is highly recommended even if you don't have a color/color swap limit, as it actually increases the quality of the output.
|
|
117
122
|
- `--pruning_max_colors`: Max number of colors allowed after pruning (default: 100).
|
|
@@ -83,6 +83,7 @@ autoforge --input_image path/to/input_image.jpg --csv_file path/to/materials.csv
|
|
|
83
83
|
- `--layer_height`: Layer thickness in millimeters (default: 0.04).
|
|
84
84
|
- `--max_layers`: Maximum number of layers (default: 75). \
|
|
85
85
|
**Note:** This is about 3mm + the background height
|
|
86
|
+
- `--min_layers`: Minimum number of layers (default: 0). Used to limit height of pruning
|
|
86
87
|
- `--background_height`: Height of the background in millimeters (default: 0.4). \
|
|
87
88
|
**Note:** The background height must be divisible by the layer height.
|
|
88
89
|
- `--background_color`: Background color in hexadecimal format (default: `#000000` aka Black). \
|
|
@@ -90,8 +91,11 @@ autoforge --input_image path/to/input_image.jpg --csv_file path/to/materials.csv
|
|
|
90
91
|
- `--output_size`: Maximum dimension for target image (default: 1024).
|
|
91
92
|
- `--solver_size`: Maximum dimension for solver (fast) image (default: 128). \
|
|
92
93
|
**Note:** We solve on a smaller size as this is many times faster, but also a bit less accurate. Increase if you need more accuracy.
|
|
93
|
-
- `--
|
|
94
|
+
- `--init_tau`: Initial tau value for Gumbel-Softmax (default: 1.0).
|
|
95
|
+
- `--final_tau`: Final tau value for the Gumbel-Softmax formulation (default: 0.01).
|
|
94
96
|
- `--visualize`: Flag to enable live visualization of the composite image during optimization.
|
|
97
|
+
- `--tensorboard`: Enable TensorBoard logging
|
|
98
|
+
- `--run-name`: Name of the run used for TensorBoard logging (optional).
|
|
95
99
|
- `--perform_pruning`: Perform pruning after optimization (default: True). \
|
|
96
100
|
**Note:** This is highly recommended even if you don't have a color/color swap limit, as it actually increases the quality of the output.
|
|
97
101
|
- `--pruning_max_colors`: Max number of colors allowed after pruning (default: 100).
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "AutoForge"
|
|
7
|
-
version="1.
|
|
7
|
+
version="1.4.0"
|
|
8
8
|
description = "AutoForge is a Python tool for generating 3D printed layered models from an input image."
|
|
9
9
|
authors = [{ name = "Hendric Voss", email = "hvoss@techfak.uni-bielefeld.de" }]
|
|
10
10
|
readme = "README.md"
|
|
@@ -21,7 +21,8 @@ dependencies = [
|
|
|
21
21
|
"tqdm>=4.50.0",
|
|
22
22
|
"pandas>=1.1.0",
|
|
23
23
|
"scikit-image",
|
|
24
|
-
"scikit-learn"
|
|
24
|
+
"scikit-learn",
|
|
25
|
+
"tensorboard"
|
|
25
26
|
]
|
|
26
27
|
|
|
27
28
|
[tool.setuptools]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: AutoForge
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: AutoForge is a Python tool for generating 3D printed layered models from an input image.
|
|
5
5
|
Author-email: Hendric Voss <hvoss@techfak.uni-bielefeld.de>
|
|
6
6
|
License: CC BY-NC-SA 4.0
|
|
@@ -17,6 +17,7 @@ Requires-Dist: tqdm>=4.50.0
|
|
|
17
17
|
Requires-Dist: pandas>=1.1.0
|
|
18
18
|
Requires-Dist: scikit-image
|
|
19
19
|
Requires-Dist: scikit-learn
|
|
20
|
+
Requires-Dist: tensorboard
|
|
20
21
|
|
|
21
22
|
# AutoForge
|
|
22
23
|
|
|
@@ -103,6 +104,7 @@ autoforge --input_image path/to/input_image.jpg --csv_file path/to/materials.csv
|
|
|
103
104
|
- `--layer_height`: Layer thickness in millimeters (default: 0.04).
|
|
104
105
|
- `--max_layers`: Maximum number of layers (default: 75). \
|
|
105
106
|
**Note:** This is about 3mm + the background height
|
|
107
|
+
- `--min_layers`: Minimum number of layers (default: 0). Used to limit height of pruning
|
|
106
108
|
- `--background_height`: Height of the background in millimeters (default: 0.4). \
|
|
107
109
|
**Note:** The background height must be divisible by the layer height.
|
|
108
110
|
- `--background_color`: Background color in hexadecimal format (default: `#000000` aka Black). \
|
|
@@ -110,8 +112,11 @@ autoforge --input_image path/to/input_image.jpg --csv_file path/to/materials.csv
|
|
|
110
112
|
- `--output_size`: Maximum dimension for target image (default: 1024).
|
|
111
113
|
- `--solver_size`: Maximum dimension for solver (fast) image (default: 128). \
|
|
112
114
|
**Note:** We solve on a smaller size as this is many times faster, but also a bit less accurate. Increase if you need more accuracy.
|
|
113
|
-
- `--
|
|
115
|
+
- `--init_tau`: Initial tau value for Gumbel-Softmax (default: 1.0).
|
|
116
|
+
- `--final_tau`: Final tau value for the Gumbel-Softmax formulation (default: 0.01).
|
|
114
117
|
- `--visualize`: Flag to enable live visualization of the composite image during optimization.
|
|
118
|
+
- `--tensorboard`: Enable TensorBoard logging
|
|
119
|
+
- `--run-name`: Name of the run used for TensorBoard logging (optional).
|
|
115
120
|
- `--perform_pruning`: Perform pruning after optimization (default: True). \
|
|
116
121
|
**Note:** This is highly recommended even if you don't have a color/color swap limit, as it actually increases the quality of the output.
|
|
117
122
|
- `--pruning_max_colors`: Max number of colors allowed after pruning (default: 100).
|
|
@@ -9,15 +9,20 @@ src/AutoForge.egg-info/top_level.txt
|
|
|
9
9
|
src/autoforge/__init__.py
|
|
10
10
|
src/autoforge/__main__.py
|
|
11
11
|
src/autoforge/auto_forge.py
|
|
12
|
-
src/autoforge/auto_forge_gradio.py
|
|
13
|
-
src/autoforge/filamentcolors_library.py
|
|
14
12
|
src/autoforge/old_jax_files.py
|
|
13
|
+
src/autoforge/Gui/__init__.py
|
|
14
|
+
src/autoforge/Gui/app.py
|
|
15
|
+
src/autoforge/Gui/main.py
|
|
16
|
+
src/autoforge/Gui/Views/__init__.py
|
|
17
|
+
src/autoforge/Gui/Views/backend_database.py
|
|
18
|
+
src/autoforge/Gui/Views/filament_editor.py
|
|
15
19
|
src/autoforge/Helper/FilamentHelper.py
|
|
16
20
|
src/autoforge/Helper/ImageHelper.py
|
|
17
21
|
src/autoforge/Helper/OptimizerHelper.py
|
|
18
22
|
src/autoforge/Helper/OutputHelper.py
|
|
19
23
|
src/autoforge/Helper/PruningHelper.py
|
|
20
24
|
src/autoforge/Helper/__init__.py
|
|
25
|
+
src/autoforge/Helper/filamentcolors_library.py
|
|
21
26
|
src/autoforge/Loss/LossFunctions.py
|
|
22
27
|
src/autoforge/Loss/PerceptionLoss.py
|
|
23
28
|
src/autoforge/Loss/__init__.py
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import uuid
|
|
4
|
+
|
|
5
|
+
import streamlit as st
|
|
6
|
+
import sqlite3
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
DATABASE = "swatches.db"
|
|
10
|
+
JSON_FILE = "../swatches.json"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_db_connection():
|
|
14
|
+
conn = sqlite3.connect(DATABASE)
|
|
15
|
+
conn.row_factory = sqlite3.Row
|
|
16
|
+
return conn
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def init_db():
|
|
20
|
+
conn = get_db_connection()
|
|
21
|
+
cursor = conn.cursor()
|
|
22
|
+
cursor.execute("""
|
|
23
|
+
CREATE TABLE IF NOT EXISTS swatches (
|
|
24
|
+
Uuid TEXT PRIMARY KEY,
|
|
25
|
+
Brand TEXT,
|
|
26
|
+
Name TEXT,
|
|
27
|
+
TD REAL,
|
|
28
|
+
HexColor TEXT
|
|
29
|
+
)
|
|
30
|
+
""")
|
|
31
|
+
cursor.execute("""
|
|
32
|
+
CREATE TABLE IF NOT EXISTS owned_filaments (
|
|
33
|
+
Uuid TEXT PRIMARY KEY,
|
|
34
|
+
Brand TEXT,
|
|
35
|
+
Name TEXT,
|
|
36
|
+
TD REAL,
|
|
37
|
+
HexColor TEXT
|
|
38
|
+
)
|
|
39
|
+
""")
|
|
40
|
+
cursor.execute("""
|
|
41
|
+
CREATE TABLE IF NOT EXISTS meta (
|
|
42
|
+
key TEXT PRIMARY KEY,
|
|
43
|
+
value TEXT
|
|
44
|
+
)
|
|
45
|
+
""")
|
|
46
|
+
conn.commit()
|
|
47
|
+
conn.close()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def get_db_connection():
|
|
51
|
+
conn = sqlite3.connect(DATABASE)
|
|
52
|
+
conn.row_factory = sqlite3.Row
|
|
53
|
+
return conn
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_items():
|
|
57
|
+
conn = get_db_connection()
|
|
58
|
+
cursor = conn.cursor()
|
|
59
|
+
cursor.execute("SELECT * FROM swatches")
|
|
60
|
+
rows = cursor.fetchall()
|
|
61
|
+
conn.close()
|
|
62
|
+
return [dict(row) for row in rows]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def add_item(item):
|
|
66
|
+
new_uuid = str(uuid.uuid4())
|
|
67
|
+
hex_color = (
|
|
68
|
+
item["HexColor"] if item["HexColor"].startswith("#") else "#" + item["HexColor"]
|
|
69
|
+
)
|
|
70
|
+
conn = get_db_connection()
|
|
71
|
+
cursor = conn.cursor()
|
|
72
|
+
cursor.execute(
|
|
73
|
+
"INSERT INTO swatches (Uuid, Brand, Name, TD, HexColor) VALUES (?, ?, ?, ?, ?)",
|
|
74
|
+
(new_uuid, item["Brand"], item["Name"], item["TD"], hex_color),
|
|
75
|
+
)
|
|
76
|
+
conn.commit()
|
|
77
|
+
conn.close()
|
|
78
|
+
return {
|
|
79
|
+
"Uuid": new_uuid,
|
|
80
|
+
"Brand": item["Brand"],
|
|
81
|
+
"Name": item["Name"],
|
|
82
|
+
"TD": item["TD"],
|
|
83
|
+
"HexColor": hex_color,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def update_item(uuid_val, update_data):
|
|
88
|
+
conn = get_db_connection()
|
|
89
|
+
cursor = conn.cursor()
|
|
90
|
+
cursor.execute("SELECT * FROM swatches WHERE Uuid = ?", (uuid_val,))
|
|
91
|
+
row = cursor.fetchone()
|
|
92
|
+
if not row:
|
|
93
|
+
conn.close()
|
|
94
|
+
st.error("Item not found")
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
updated_brand = update_data.get("Brand") or row["Brand"]
|
|
98
|
+
updated_name = update_data.get("Name") or row["Name"]
|
|
99
|
+
updated_td = (
|
|
100
|
+
update_data.get("TD") if update_data.get("TD") is not None else row["TD"]
|
|
101
|
+
)
|
|
102
|
+
updated_hex = update_data.get("HexColor") or row["HexColor"]
|
|
103
|
+
if not updated_hex.startswith("#"):
|
|
104
|
+
updated_hex = "#" + updated_hex
|
|
105
|
+
|
|
106
|
+
cursor.execute(
|
|
107
|
+
"UPDATE swatches SET Brand = ?, Name = ?, TD = ?, HexColor = ? WHERE Uuid = ?",
|
|
108
|
+
(updated_brand, updated_name, updated_td, updated_hex, uuid_val),
|
|
109
|
+
)
|
|
110
|
+
conn.commit()
|
|
111
|
+
cursor.execute("SELECT * FROM swatches WHERE Uuid = ?", (uuid_val,))
|
|
112
|
+
updated_row = cursor.fetchone()
|
|
113
|
+
conn.close()
|
|
114
|
+
return dict(updated_row)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def delete_item(uuid_val):
|
|
118
|
+
conn = get_db_connection()
|
|
119
|
+
cursor = conn.cursor()
|
|
120
|
+
cursor.execute("DELETE FROM swatches WHERE Uuid = ?", (uuid_val,))
|
|
121
|
+
if cursor.rowcount == 0:
|
|
122
|
+
conn.close()
|
|
123
|
+
st.error("Item not found")
|
|
124
|
+
return False
|
|
125
|
+
conn.commit()
|
|
126
|
+
conn.close()
|
|
127
|
+
return True
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def get_owned_filaments():
|
|
131
|
+
conn = get_db_connection()
|
|
132
|
+
cursor = conn.cursor()
|
|
133
|
+
cursor.execute("SELECT * FROM owned_filaments")
|
|
134
|
+
rows = cursor.fetchall()
|
|
135
|
+
conn.close()
|
|
136
|
+
return [dict(row) for row in rows]
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def add_owned_filament(item):
|
|
140
|
+
new_uuid = str(uuid.uuid4())
|
|
141
|
+
hex_color = (
|
|
142
|
+
item["HexColor"] if item["HexColor"].startswith("#") else "#" + item["HexColor"]
|
|
143
|
+
)
|
|
144
|
+
conn = get_db_connection()
|
|
145
|
+
cursor = conn.cursor()
|
|
146
|
+
cursor.execute(
|
|
147
|
+
"INSERT INTO owned_filaments (Uuid, Brand, Name, TD, HexColor) VALUES (?, ?, ?, ?, ?)",
|
|
148
|
+
(new_uuid, item["Brand"], item["Name"], item["TD"], hex_color),
|
|
149
|
+
)
|
|
150
|
+
conn.commit()
|
|
151
|
+
conn.close()
|
|
152
|
+
return {
|
|
153
|
+
"Uuid": new_uuid,
|
|
154
|
+
"Brand": item["Brand"],
|
|
155
|
+
"Name": item["Name"],
|
|
156
|
+
"TD": item["TD"],
|
|
157
|
+
"HexColor": hex_color,
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def delete_owned_filament(uuid_val):
|
|
162
|
+
conn = get_db_connection()
|
|
163
|
+
cursor = conn.cursor()
|
|
164
|
+
cursor.execute("DELETE FROM owned_filaments WHERE Uuid = ?", (uuid_val,))
|
|
165
|
+
if cursor.rowcount == 0:
|
|
166
|
+
conn.close()
|
|
167
|
+
st.error("Owned filament not found")
|
|
168
|
+
return False
|
|
169
|
+
conn.commit()
|
|
170
|
+
conn.close()
|
|
171
|
+
return True
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def import_json_data():
|
|
175
|
+
if not os.path.exists(JSON_FILE):
|
|
176
|
+
return
|
|
177
|
+
mtime = os.path.getmtime(JSON_FILE)
|
|
178
|
+
conn = get_db_connection()
|
|
179
|
+
cursor = conn.cursor()
|
|
180
|
+
cursor.execute("SELECT value FROM meta WHERE key = ?", ("json_mtime",))
|
|
181
|
+
row = cursor.fetchone()
|
|
182
|
+
stored_mtime = float(row["value"]) if row else 0
|
|
183
|
+
|
|
184
|
+
if mtime > stored_mtime:
|
|
185
|
+
with open(JSON_FILE, "r", encoding="utf-8") as f:
|
|
186
|
+
data = json.load(f)
|
|
187
|
+
for item in data:
|
|
188
|
+
cursor.execute("SELECT 1 FROM swatches WHERE Uuid = ?", (item["Uuid"],))
|
|
189
|
+
if not cursor.fetchone():
|
|
190
|
+
cursor.execute(
|
|
191
|
+
"INSERT INTO swatches (Uuid, Brand, Name, TD, HexColor) VALUES (?, ?, ?, ?, ?)",
|
|
192
|
+
(
|
|
193
|
+
item["Uuid"],
|
|
194
|
+
item["Brand"],
|
|
195
|
+
item["Name"],
|
|
196
|
+
item["TD"],
|
|
197
|
+
item["HexColor"],
|
|
198
|
+
),
|
|
199
|
+
)
|
|
200
|
+
cursor.execute(
|
|
201
|
+
"INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)",
|
|
202
|
+
("json_mtime", str(mtime)),
|
|
203
|
+
)
|
|
204
|
+
conn.commit()
|
|
205
|
+
conn.close()
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
|
|
3
|
+
import streamlit as st
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import hashlib
|
|
6
|
+
|
|
7
|
+
from st_aggrid import AgGrid, GridOptionsBuilder, GridUpdateMode, JsCode
|
|
8
|
+
|
|
9
|
+
from autoforge.Gui.Views.backend_database import (
|
|
10
|
+
get_items,
|
|
11
|
+
get_owned_filaments,
|
|
12
|
+
add_owned_filament,
|
|
13
|
+
delete_owned_filament,
|
|
14
|
+
update_item,
|
|
15
|
+
add_item,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_grid_key(df):
|
|
20
|
+
# Create a hash from the DataFrame contents
|
|
21
|
+
hash_obj = hashlib.sha256(
|
|
22
|
+
pd.util.hash_pandas_object(df, index=True).values.tobytes()
|
|
23
|
+
)
|
|
24
|
+
return hash_obj.hexdigest()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
hex_renderer = JsCode("""
|
|
28
|
+
class ColorCellEditor {
|
|
29
|
+
init(params) {
|
|
30
|
+
this.eInput = document.createElement('input');
|
|
31
|
+
this.eInput.type = 'color';
|
|
32
|
+
this.eInput.value = params.value;
|
|
33
|
+
this.eInput.style.width = '60px';
|
|
34
|
+
this.eInput.style.height = '25px';
|
|
35
|
+
this.eInput.style.padding = '0';
|
|
36
|
+
this.eInput.style.margin = '0';
|
|
37
|
+
this.eInput.style.border = 'none';
|
|
38
|
+
this.eInput.style.outline = 'none';
|
|
39
|
+
this.eInput.style.borderRadius = '0';
|
|
40
|
+
this.eInput.style.boxShadow = 'none';
|
|
41
|
+
this.eInput.style.appearance = 'none';
|
|
42
|
+
this.eInput.style.webkitAppearance = 'none';
|
|
43
|
+
this.eInput.style.MozAppearance = 'none';
|
|
44
|
+
}
|
|
45
|
+
getGui() {
|
|
46
|
+
return this.eInput;
|
|
47
|
+
}
|
|
48
|
+
afterGuiAttached() {
|
|
49
|
+
this.eInput.focus();
|
|
50
|
+
}
|
|
51
|
+
getValue() {
|
|
52
|
+
return this.eInput.value;
|
|
53
|
+
}
|
|
54
|
+
destroy() {}
|
|
55
|
+
isPopup() {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
""")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def show_filament_editor():
|
|
63
|
+
st.title("Filament Editor")
|
|
64
|
+
|
|
65
|
+
cols_order = ["Brand", "Name", "TD", "HexColor", "Uuid"]
|
|
66
|
+
# Always load fresh data from the database
|
|
67
|
+
swatches_data = pd.DataFrame(get_items())[cols_order]
|
|
68
|
+
owned_data = pd.DataFrame(get_owned_filaments())[cols_order]
|
|
69
|
+
|
|
70
|
+
# Configure AgGrid for swatches
|
|
71
|
+
gb_main = GridOptionsBuilder.from_dataframe(swatches_data)
|
|
72
|
+
gb_main.configure_default_column(editable=True)
|
|
73
|
+
gb_main.configure_column("Uuid", hide=True)
|
|
74
|
+
gb_main.configure_selection("multiple", use_checkbox=True)
|
|
75
|
+
gb_main.configure_column(
|
|
76
|
+
"HexColor", cellRenderer=hex_renderer, cellEditor=hex_renderer
|
|
77
|
+
)
|
|
78
|
+
gridOptions_main = gb_main.build()
|
|
79
|
+
|
|
80
|
+
# Configure AgGrid for owned filaments
|
|
81
|
+
if not owned_data.empty:
|
|
82
|
+
gb_owned = GridOptionsBuilder.from_dataframe(owned_data)
|
|
83
|
+
else:
|
|
84
|
+
gb_owned = GridOptionsBuilder.from_dataframe(
|
|
85
|
+
pd.DataFrame(columns=swatches_data.columns)
|
|
86
|
+
)
|
|
87
|
+
gb_owned.configure_selection("multiple", use_checkbox=True)
|
|
88
|
+
gb_owned.configure_column("Uuid", hide=True)
|
|
89
|
+
gb_owned.configure_column(
|
|
90
|
+
"HexColor", cellRenderer=hex_renderer, cellEditor=hex_renderer
|
|
91
|
+
)
|
|
92
|
+
gridOptions_owned = gb_owned.build()
|
|
93
|
+
|
|
94
|
+
# Generate a dynamic key for the AgGrid based on current swatches_data
|
|
95
|
+
grid_key = get_grid_key(swatches_data)
|
|
96
|
+
|
|
97
|
+
col1, col2 = st.columns(2)
|
|
98
|
+
|
|
99
|
+
with col1:
|
|
100
|
+
st.subheader("All Filaments")
|
|
101
|
+
grid_response = AgGrid(
|
|
102
|
+
swatches_data,
|
|
103
|
+
gridOptions=gridOptions_main,
|
|
104
|
+
update_mode=GridUpdateMode.SELECTION_CHANGED,
|
|
105
|
+
reload_data=True,
|
|
106
|
+
allow_unsafe_jscode=True,
|
|
107
|
+
height=400,
|
|
108
|
+
key=grid_key, # dynamic key forces re-render on data change
|
|
109
|
+
)
|
|
110
|
+
if st.button("Add Selected to Owned"):
|
|
111
|
+
selected_rows = grid_response.get("selected_rows", [])
|
|
112
|
+
current_owned = pd.DataFrame(get_owned_filaments())
|
|
113
|
+
added_count = 0
|
|
114
|
+
for row in selected_rows.to_dict("records"):
|
|
115
|
+
exists = False
|
|
116
|
+
if not current_owned.empty:
|
|
117
|
+
dup = current_owned[
|
|
118
|
+
(current_owned["Brand"] == row["Brand"])
|
|
119
|
+
& (current_owned["Name"] == row["Name"])
|
|
120
|
+
& (current_owned["TD"] == row["TD"])
|
|
121
|
+
& (current_owned["HexColor"] == row["HexColor"])
|
|
122
|
+
]
|
|
123
|
+
if not dup.empty:
|
|
124
|
+
exists = True
|
|
125
|
+
if not exists:
|
|
126
|
+
add_owned_filament(
|
|
127
|
+
{
|
|
128
|
+
"Brand": row["Brand"],
|
|
129
|
+
"Name": row["Name"],
|
|
130
|
+
"TD": row["TD"],
|
|
131
|
+
"HexColor": row["HexColor"],
|
|
132
|
+
}
|
|
133
|
+
)
|
|
134
|
+
added_count += 1
|
|
135
|
+
if added_count > 0:
|
|
136
|
+
st.success(f"Added {added_count} filament(s) to owned filaments.")
|
|
137
|
+
st.rerun()
|
|
138
|
+
else:
|
|
139
|
+
st.warning("No new filaments were added to owned.")
|
|
140
|
+
|
|
141
|
+
with col2:
|
|
142
|
+
st.subheader("Owned Filaments")
|
|
143
|
+
if not owned_data.empty:
|
|
144
|
+
owned_grid = AgGrid(
|
|
145
|
+
owned_data,
|
|
146
|
+
gridOptions=gridOptions_owned,
|
|
147
|
+
update_mode=GridUpdateMode.SELECTION_CHANGED,
|
|
148
|
+
reload_data=True,
|
|
149
|
+
allow_unsafe_jscode=True,
|
|
150
|
+
height=400,
|
|
151
|
+
)
|
|
152
|
+
if st.button("Remove Selected from Owned"):
|
|
153
|
+
selected_owned = owned_grid.get("selected_rows", [])
|
|
154
|
+
removed_count = 0
|
|
155
|
+
for row in selected_owned.to_dict("records"):
|
|
156
|
+
if delete_owned_filament(row["Uuid"]):
|
|
157
|
+
removed_count += 1
|
|
158
|
+
if removed_count > 0:
|
|
159
|
+
st.success(
|
|
160
|
+
f"Removed {removed_count} filament(s) from owned filaments."
|
|
161
|
+
)
|
|
162
|
+
st.rerun()
|
|
163
|
+
else:
|
|
164
|
+
st.warning("No filaments selected for removal.")
|
|
165
|
+
else:
|
|
166
|
+
st.write("No owned filaments yet.")
|
|
167
|
+
|
|
168
|
+
st.markdown("### Save Changes")
|
|
169
|
+
if st.button("Save Changes"):
|
|
170
|
+
# The "data" returned by AgGrid can sometimes be a list of strings or a list of dicts
|
|
171
|
+
updated_data = grid_response["data"]
|
|
172
|
+
if (
|
|
173
|
+
isinstance(updated_data, list)
|
|
174
|
+
and updated_data
|
|
175
|
+
and isinstance(updated_data[0], str)
|
|
176
|
+
):
|
|
177
|
+
try:
|
|
178
|
+
updated_data = [ast.literal_eval(row) for row in updated_data]
|
|
179
|
+
except Exception as e:
|
|
180
|
+
st.error(f"Error converting row data: {e}")
|
|
181
|
+
updated_df = pd.DataFrame(updated_data)
|
|
182
|
+
for index, row in updated_df.iterrows():
|
|
183
|
+
uuid_val = row["Uuid"]
|
|
184
|
+
update_data = {
|
|
185
|
+
"Brand": row["Brand"],
|
|
186
|
+
"Name": row["Name"],
|
|
187
|
+
"TD": row["TD"],
|
|
188
|
+
"HexColor": row["HexColor"],
|
|
189
|
+
}
|
|
190
|
+
updated = update_item(uuid_val, update_data)
|
|
191
|
+
if not updated:
|
|
192
|
+
st.error(f"Error updating row {uuid_val}")
|
|
193
|
+
st.success("Changes saved.")
|
|
194
|
+
st.rerun()
|
|
195
|
+
|
|
196
|
+
st.markdown("### Add New Filament")
|
|
197
|
+
with st.form("add_swatch_form"):
|
|
198
|
+
new_brand = st.text_input("Brand")
|
|
199
|
+
new_name = st.text_input("Name")
|
|
200
|
+
new_td = st.number_input("TD", value=0.0, step=0.1)
|
|
201
|
+
new_hex = st.color_picker("Hex Color", "#ffffff")
|
|
202
|
+
submitted = st.form_submit_button("Add filament")
|
|
203
|
+
if submitted:
|
|
204
|
+
new_data = {
|
|
205
|
+
"Brand": new_brand,
|
|
206
|
+
"Name": new_name,
|
|
207
|
+
"TD": new_td,
|
|
208
|
+
"HexColor": new_hex,
|
|
209
|
+
}
|
|
210
|
+
added = add_item(new_data)
|
|
211
|
+
add_owned_filament(new_data)
|
|
212
|
+
if added:
|
|
213
|
+
st.success("Filament added.")
|
|
214
|
+
st.rerun()
|
|
215
|
+
else:
|
|
216
|
+
st.error("Error adding filament.")
|