Skip to content

Commit 8f143e8

Browse files
committed
Added config to GUI
1 parent 78afe94 commit 8f143e8

5 files changed

Lines changed: 297 additions & 7 deletions

File tree

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ For controlled rendering of 2 selected objects, navigate to the `render_single.s
102102

103103
_This script is useful for **testing and fine-tuning** object placements before batch rendering._
104104

105-
### **Adding AI-generated Backgrounds (EXTRA)**
105+
### **AI-generated Backgrounds (EXPERIMENTAL)**
106106

107107
All the images rendered are just 2 objects on a white surface with a light grey "sky". If you would like to add a custom (more realistic) background to each image in your image output folder, you can navigate to the `generate_backgrounds.sh` script.
108108

@@ -114,7 +114,7 @@ All the images rendered are just 2 objects on a white surface with a light grey
114114

115115
_The goal is to enable comparisons between a fine-tuned spatial reasoning model's performance on the original dataset versus a version with more realistic backgrounds._
116116

117-
## Web GUI Interface
117+
## Web GUI (NEW)
118118

119119
FORG3D now includes a **web-based graphical user interface (GUI)** that provides an intuitive way to configure and execute rendering tasks without using command-line scripts. The GUI offers real-time output streaming, interactive forms, and process control capabilities.
120120

@@ -126,6 +126,8 @@ The web interface provides three main modes:
126126
- **Multiple Renders**: Batch rendering interface for generating multiple image combinations
127127
- **Background Generation**: AI-powered background generation using Stable Diffusion XL inpainting
128128

129+
Additionally, the GUI allows you to **configure the `config.json` settings** directly through the web interface, eliminating the need to manually edit configuration files.
130+
129131
### Setup and Launch
130132

131133
1. **Navigate to the GUI directory**:

gui/app.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
SCRIPTS_DIR = os.path.join(os.path.dirname(__file__), "..", "scripts")
1717
# Path to the data directory for properties.json
1818
DATA_DIR = os.path.join(os.path.dirname(__file__), "..", "data", "objaverse")
19+
# Path to the config.json file
20+
CONFIG_PATH = os.path.join(os.path.dirname(__file__), "..", "src", "config.json")
1921

2022
running_processes = {}
2123

@@ -160,6 +162,58 @@ def get_objects():
160162
except Exception as e:
161163
return jsonify({"success": False, "error": str(e)}), 500
162164

165+
@app.route('/api/config', methods=['GET'])
166+
def get_config():
167+
"""Get current configuration from config.json."""
168+
try:
169+
with open(CONFIG_PATH, 'r') as f:
170+
config_data = json.load(f)
171+
return jsonify({"success": True, "config": config_data})
172+
except FileNotFoundError:
173+
return jsonify({"success": False, "error": "Config file not found"}), 404
174+
except json.JSONDecodeError:
175+
return jsonify({"success": False, "error": "Invalid config file format"}), 500
176+
except Exception as e:
177+
return jsonify({"success": False, "error": str(e)}), 500
178+
179+
@app.route('/api/config', methods=['POST'])
180+
def update_config():
181+
"""Update configuration in config.json."""
182+
try:
183+
# Get the new config data from request
184+
new_config = request.get_json()
185+
186+
if not new_config:
187+
return jsonify({"success": False, "error": "No configuration data provided"}), 400
188+
189+
# Read current config to preserve any fields not being updated
190+
try:
191+
with open(CONFIG_PATH, 'r') as f:
192+
current_config = json.load(f)
193+
except FileNotFoundError:
194+
current_config = {}
195+
196+
customizable_fields = [
197+
'properties_json', 'base_scene_blendfile', 'shape_dir', 'material_dir',
198+
'output_image_dir', 'output_scene_dir', 'output_scene_file', 'masks_dir',
199+
'enhanced_image_dir', 'use_gpu', 'width', 'height', 'render_tile_size'
200+
]
201+
202+
for field in customizable_fields:
203+
if field in new_config:
204+
current_config[field] = new_config[field]
205+
206+
# Write updated config back to file
207+
with open(CONFIG_PATH, 'w') as f:
208+
json.dump(current_config, f, indent=2)
209+
210+
return jsonify({"success": True, "message": "Configuration updated successfully"})
211+
212+
except json.JSONDecodeError:
213+
return jsonify({"success": False, "error": "Invalid JSON data"}), 400
214+
except Exception as e:
215+
return jsonify({"success": False, "error": f"Error updating config: {str(e)}"}), 500
216+
163217
@socketio.on('connect')
164218
def handle_connect():
165219
"""Handle WebSocket connection."""

gui/static/script.js

Lines changed: 122 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
1+
// Configuration field mapping
2+
const CONFIG_FIELDS = {
3+
// File paths
4+
'properties-json': 'properties_json',
5+
'base-scene-blendfile': 'base_scene_blendfile',
6+
'output-scene-file': 'output_scene_file',
7+
8+
// Directories
9+
'shape-dir': 'shape_dir',
10+
'material-dir': 'material_dir',
11+
'output-image-dir': 'output_image_dir',
12+
'output-scene-dir': 'output_scene_dir',
13+
'masks-dir': 'masks_dir',
14+
'enhanced-image-dir': 'enhanced_image_dir',
15+
16+
// Rendering settings
17+
'use-gpu': 'use_gpu',
18+
'width': 'width',
19+
'height': 'height',
20+
'render-tile-size': 'render_tile_size'
21+
}
22+
123
// Object Selector Functionality
224
let objectsData = {}
325
let selectedObjects = {
@@ -25,6 +47,81 @@ async function initObjectSelectors() {
2547
}
2648
}
2749

50+
// Configuration Management Functions
51+
async function loadConfiguration() {
52+
try {
53+
const response = await fetch('/api/config')
54+
const data = await response.json()
55+
56+
if (data.success) {
57+
populateConfigurationForm(data.config)
58+
appendOutput("\n✅ Configuration loaded successfully\n")
59+
} else {
60+
console.error('Failed to load configuration:', data.error)
61+
appendOutput(`\n❌ Failed to load configuration: ${data.error}\n`)
62+
}
63+
} catch (error) {
64+
console.error('Error fetching configuration:', error)
65+
appendOutput(`\n❌ Error fetching configuration: ${error.message}\n`)
66+
}
67+
}
68+
69+
function populateConfigurationForm(config) {
70+
// Iterate through all configured fields and populate them
71+
Object.entries(CONFIG_FIELDS).forEach(([fieldId, configKey]) => {
72+
const field = document.getElementById(fieldId)
73+
if (field && config[configKey] !== undefined) {
74+
if (field.type === 'checkbox') {
75+
field.checked = config[configKey]
76+
} else {
77+
field.value = config[configKey]
78+
}
79+
}
80+
})
81+
}
82+
83+
async function saveConfiguration(formData) {
84+
try {
85+
// Convert form data to config object using the mapping
86+
const configData = {}
87+
88+
Object.entries(CONFIG_FIELDS).forEach(([fieldId, configKey]) => {
89+
const formKey = fieldId.replace(/-/g, '_')
90+
const value = formData[formKey]
91+
92+
if (configKey === 'use_gpu') {
93+
configData[configKey] = value === 'on'
94+
} else if (value !== undefined && value !== '') {
95+
if (['width', 'height', 'render_tile_size'].includes(configKey)) {
96+
configData[configKey] = parseInt(value)
97+
} else {
98+
configData[configKey] = value
99+
}
100+
}
101+
})
102+
103+
const response = await fetch('/api/config', {
104+
method: 'POST',
105+
headers: {
106+
'Content-Type': 'application/json',
107+
},
108+
body: JSON.stringify(configData)
109+
})
110+
111+
const data = await response.json()
112+
113+
if (data.success) {
114+
appendOutput("✅ Configuration saved successfully\n")
115+
} else {
116+
console.error('Failed to save configuration:', data.error)
117+
appendOutput(`❌ Failed to save configuration: ${data.error}\n`)
118+
}
119+
} catch (error) {
120+
console.error('Error saving configuration:', error)
121+
appendOutput(`❌ Error saving configuration: ${error.message}\n`)
122+
}
123+
}
124+
28125
function showObjectLoadError() {
29126
const singleToggle = document.getElementById('objects-single-toggle')
30127
const multipleToggle = document.getElementById('objects-multiple-toggle')
@@ -224,11 +321,14 @@ const renderForms = document.querySelectorAll(".render-form")
224321
const singleForm = document.getElementById("single-render-form")
225322
const multipleForm = document.getElementById("multiple-render-form")
226323
const backgroundForm = document.getElementById("background-generation-form")
324+
const configurationForm = document.getElementById("configuration-form")
227325
const outputDiv = document.getElementById("output")
228326
const outputStatus = document.getElementById("output-status")
229327
const singleSubmitBtn = document.getElementById("single-submit-btn")
230328
const multipleSubmitBtn = document.getElementById("multiple-submit-btn")
231329
const backgroundSubmitBtn = document.getElementById("background-submit-btn")
330+
331+
const saveConfigBtn = document.getElementById("save-config-btn")
232332
const stopBtn = document.getElementById("stop-btn")
233333

234334
// WebSocket connection
@@ -505,6 +605,27 @@ backgroundForm.addEventListener("submit", (e) => {
505605
}
506606
})
507607

608+
// Configuration form event listeners
609+
configurationForm.addEventListener("submit", async (e) => {
610+
e.preventDefault()
611+
612+
const submitBtn = e.target.querySelector('button[type="submit"]')
613+
const originalHTML = submitBtn.innerHTML
614+
615+
// Show loading state
616+
submitBtn.disabled = true
617+
submitBtn.innerHTML = '<span class="loading"></span>Saving configuration...'
618+
619+
const formData = new FormData(e.target)
620+
const data = Object.fromEntries(formData.entries())
621+
622+
await saveConfiguration(data)
623+
624+
// Re-enable submit button
625+
submitBtn.disabled = false
626+
submitBtn.innerHTML = originalHTML
627+
})
628+
508629
// Add input event listeners for real-time validation feedback
509630
document.querySelectorAll("input, select").forEach((field) => {
510631
field.addEventListener("input", () => {
@@ -558,6 +679,5 @@ stopBtn.addEventListener("click", () => {
558679
document.addEventListener('DOMContentLoaded', () => {
559680
initWebSocket()
560681
initObjectSelectors()
682+
loadConfiguration()
561683
})
562-
563-
console.log("🎬 Blender Render Control initialized successfully!")

gui/static/styles.css

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ body {
146146

147147
.render-selector {
148148
display: grid;
149-
grid-template-columns: 1fr 1fr 1fr;
149+
grid-template-columns: 1fr 1fr 1fr 1fr;
150150
gap: .5rem;
151151
margin-bottom: 1rem
152152
}
@@ -497,7 +497,7 @@ body {
497497
}
498498

499499
.render-selector {
500-
grid-template-columns: 1fr 1fr 1fr;
500+
grid-template-columns: 1fr 1fr 1fr 1fr;
501501
gap: .5rem
502502
}
503503

@@ -532,10 +532,15 @@ body {
532532
}
533533

534534
.main-layout {
535-
grid-template-columns: 1fr 1fr;
535+
grid-template-columns: 3fr 1fr;
536536
gap: .75rem
537537
}
538538

539+
.render-selector {
540+
grid-template-columns: 1fr 1fr;
541+
gap: .3rem
542+
}
543+
539544
.render-tab {
540545
padding: .3rem
541546
}

0 commit comments

Comments
 (0)