MySafeCode commited on
Commit
39806b8
·
verified ·
1 Parent(s): 06a5ef5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +165 -41
app.py CHANGED
@@ -7,19 +7,20 @@ import struct
7
  import logging
8
  from pathlib import Path
9
  from typing import Tuple
 
10
 
11
  # Configure logging
12
  logging.basicConfig(level=logging.INFO)
13
  logger = logging.getLogger(__name__)
14
 
15
  class VoxParser:
16
- """Parse MagicaVoxel .vox files"""
17
 
18
  def __init__(self, file_path):
19
  self.file_path = file_path
20
 
21
  def parse(self) -> dict:
22
- """Parse the .vox file and extract voxel data"""
23
  try:
24
  with open(self.file_path, 'rb') as f:
25
  data = f.read()
@@ -102,7 +103,7 @@ class VoxParser:
102
  return colors
103
 
104
  class VoxToGlbConverter:
105
- """Convert MagicaVoxel files to GLB format"""
106
 
107
  def __init__(self):
108
  self.voxel_size = 1.0
@@ -110,32 +111,29 @@ class VoxToGlbConverter:
110
  def vox_to_glb(self, vox_file_path: str) -> Tuple[str, str]:
111
  """Convert .vox file to .glb file"""
112
  try:
113
- # Parse the .vox file
114
  parser = VoxParser(vox_file_path)
115
  voxel_data = parser.parse()
116
 
117
  if not voxel_data['voxels']:
118
  return "", "No voxels found in the file"
119
 
120
- # Create mesh from voxels
121
  mesh = self.create_mesh_from_voxels(voxel_data)
122
 
123
- # Save as GLB
124
  output_path = str(Path(tempfile.gettempdir()) / f"converted_model.glb")
125
  mesh.export(output_path)
126
 
127
- return output_path, f"Successfully converted {len(voxel_data['voxels'])} voxels to GLB format"
 
128
 
129
  except Exception as e:
130
  logger.error(f"Conversion error: {e}")
131
  return "", f"Error converting file: {str(e)}"
132
 
133
  def create_mesh_from_voxels(self, voxel_data: dict) -> trimesh.Trimesh:
134
- """Create a trimesh from voxel data"""
135
  voxels = voxel_data['voxels']
136
  palette = voxel_data['palette']
137
 
138
- # Group voxels by color
139
  color_groups = {}
140
  for voxel in voxels:
141
  color_idx = voxel['color_index']
@@ -173,64 +171,190 @@ class VoxToGlbConverter:
173
  return trimesh.creation.box(extents=[self.voxel_size, self.voxel_size, self.voxel_size])
174
 
175
  def process_vox_file(vox_file) -> Tuple[str, str]:
176
- """Process uploaded .vox file and convert to .glb - FIXED for Error 21"""
177
  if vox_file is None:
178
  return "", "Please upload a .vox file"
179
 
180
  try:
181
  converter = VoxToGlbConverter()
182
 
183
- # PROPER FIX FOR ERROR 21:
184
- # Create temp file and copy the actual uploaded file
185
- with tempfile.NamedTemporaryFile(delete=False, suffix='.vox') as tmp_file:
186
- # Get the actual file path from Gradio's file object
187
- if hasattr(vox_file, 'name'):
188
- real_file_path = vox_file.name
189
-
190
- # Verify it's a file, not a directory
191
- if os.path.isfile(real_file_path):
192
- # Copy the uploaded file to temp location
193
- import shutil
194
- shutil.copy(real_file_path, tmp_file.name)
195
- temp_vox_path = tmp_file.name
196
- else:
197
- return "", "Could not access uploaded file - may be a directory"
198
- else:
199
- return "", "Invalid file format"
200
-
201
- # Convert using the temp file path
202
- glb_path, message = converter.vox_to_glb(temp_vox_path)
203
-
204
- # Clean up temp file
205
- if os.path.exists(temp_vox_path):
206
- os.unlink(temp_vox_path)
207
 
208
- return glb_path, message
 
 
 
 
 
 
209
 
210
  except Exception as e:
211
  return "", f"Error: {str(e)}"
212
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  def create_gradio_interface():
214
- with gr.Blocks(title="VOX to GLB Converter", theme=gr.themes.Soft()) as app:
215
  gr.Markdown("""
216
- # 🧊 MagicaVoxel to GLB Converter
217
- Convert your MagicaVoxel `.vox` files to `.glb` format
218
  """)
219
 
220
  with gr.Row():
221
  with gr.Column():
222
  vox_input = gr.File(label="Upload VOX File", file_types=[".vox"], file_count="single")
223
- convert_btn = gr.Button("Convert to GLB", variant="primary")
224
  status_output = gr.Textbox(label="Status", interactive=False, placeholder="Ready...")
225
 
226
  with gr.Column():
227
  glb_output = gr.File(label="Download GLB File", file_types=[".glb"], interactive=False)
228
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
  convert_btn.click(
230
- fn=process_vox_file,
231
  inputs=[vox_input],
232
- outputs=[glb_output, status_output]
233
  )
 
 
 
 
 
 
 
 
234
 
235
  return app
236
 
 
7
  import logging
8
  from pathlib import Path
9
  from typing import Tuple
10
+ import shutil
11
 
12
  # Configure logging
13
  logging.basicConfig(level=logging.INFO)
14
  logger = logging.getLogger(__name__)
15
 
16
  class VoxParser:
17
+ """MagicaVoxel .vox file parser"""
18
 
19
  def __init__(self, file_path):
20
  self.file_path = file_path
21
 
22
  def parse(self) -> dict:
23
+ """Parse the .vox file structure"""
24
  try:
25
  with open(self.file_path, 'rb') as f:
26
  data = f.read()
 
103
  return colors
104
 
105
  class VoxToGlbConverter:
106
+ """MagicaVoxel to GLB converter"""
107
 
108
  def __init__(self):
109
  self.voxel_size = 1.0
 
111
  def vox_to_glb(self, vox_file_path: str) -> Tuple[str, str]:
112
  """Convert .vox file to .glb file"""
113
  try:
 
114
  parser = VoxParser(vox_file_path)
115
  voxel_data = parser.parse()
116
 
117
  if not voxel_data['voxels']:
118
  return "", "No voxels found in the file"
119
 
 
120
  mesh = self.create_mesh_from_voxels(voxel_data)
121
 
 
122
  output_path = str(Path(tempfile.gettempdir()) / f"converted_model.glb")
123
  mesh.export(output_path)
124
 
125
+ voxel_count = len(voxel_data['voxels'])
126
+ return output_path, f"Converted {voxel_count} voxels to GLB format"
127
 
128
  except Exception as e:
129
  logger.error(f"Conversion error: {e}")
130
  return "", f"Error converting file: {str(e)}"
131
 
132
  def create_mesh_from_voxels(self, voxel_data: dict) -> trimesh.Trimesh:
133
+ """Create mesh from voxel data"""
134
  voxels = voxel_data['voxels']
135
  palette = voxel_data['palette']
136
 
 
137
  color_groups = {}
138
  for voxel in voxels:
139
  color_idx = voxel['color_index']
 
171
  return trimesh.creation.box(extents=[self.voxel_size, self.voxel_size, self.voxel_size])
172
 
173
  def process_vox_file(vox_file) -> Tuple[str, str]:
174
+ """Process uploaded .vox file and convert to .glb"""
175
  if vox_file is None:
176
  return "", "Please upload a .vox file"
177
 
178
  try:
179
  converter = VoxToGlbConverter()
180
 
181
+ if hasattr(vox_file, 'name'):
182
+ real_file_path = vox_file.name
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
 
184
+ if os.path.isfile(real_file_path):
185
+ glb_path, message = converter.vox_to_glb(real_file_path)
186
+ return glb_path, message
187
+ else:
188
+ return "", "Could not access uploaded file"
189
+ else:
190
+ return "", "Invalid file format"
191
 
192
  except Exception as e:
193
  return "", f"Error: {str(e)}"
194
 
195
+ # GLB Preview Component
196
+ def create_glb_preview(glb_file):
197
+ """Create HTML preview for GLB file"""
198
+ if glb_file is None:
199
+ return "<p>No GLB file to preview</p>"
200
+
201
+ if hasattr(glb_file, 'name'):
202
+ glb_path = glb_file.name
203
+ else:
204
+ glb_path = str(glb_file)
205
+
206
+ if not os.path.exists(glb_path):
207
+ return "<p>GLB file not found</p>"
208
+
209
+ # Create HTML with Three.js viewer
210
+ html_content = f"""
211
+ <div style="width: 100%; height: 400px; position: relative; background: #1a1a1a; border-radius: 8px; overflow: hidden;">
212
+ <div id="preview-container" style="width: 100%; height: 100%;"></div>
213
+ <div style="position: absolute; top: 10px; left: 10px; color: white; font-size: 12px; background: rgba(0,0,0,0.7); padding: 5px 10px; border-radius: 4px;">
214
+ 🎮 Drag to rotate • Scroll to zoom • Right-click to pan
215
+ </div>
216
+ </div>
217
+
218
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script>
219
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js"></script>
220
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/GLTFLoader.js"></script>
221
+
222
+ <script>
223
+ // Initialize Three.js scene
224
+ const container = document.getElementById('preview-container');
225
+ const scene = new THREE.Scene();
226
+ scene.background = new THREE.Color(0x2a2a2a);
227
+
228
+ const camera = new THREE.PerspectiveCamera(75, container.clientWidth / container.clientHeight, 0.1, 1000);
229
+ camera.position.set(5, 5, 5);
230
+
231
+ const renderer = new THREE.WebGLRenderer({ antialias: true });
232
+ renderer.setSize(container.clientWidth, container.clientHeight);
233
+ renderer.setPixelRatio(window.devicePixelRatio);
234
+ renderer.shadowMap.enabled = true;
235
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
236
+ container.appendChild(renderer.domElement);
237
+
238
+ // Controls
239
+ const controls = new THREE.OrbitControls(camera, renderer.domElement);
240
+ controls.enableDamping = true;
241
+ controls.dampingFactor = 0.05;
242
+ controls.target.set(0, 0, 0);
243
+
244
+ // Lights
245
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
246
+ scene.add(ambientLight);
247
+
248
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
249
+ directionalLight.position.set(10, 10, 10);
250
+ directionalLight.castShadow = true;
251
+ scene.add(directionalLight);
252
+
253
+ // Grid helper
254
+ const gridHelper = new THREE.GridHelper(20, 20, 0x444444, 0x333333);
255
+ scene.add(gridHelper);
256
+
257
+ // Load GLB file
258
+ const loader = new THREE.GLTFLoader();
259
+ const glbPath = '{glb_path}';
260
+
261
+ loader.load(
262
+ glbPath,
263
+ function (gltf) {
264
+ const model = gltf.scene;
265
+
266
+ // Center and scale the model
267
+ const box = new THREE.Box3().setFromObject(model);
268
+ const center = box.getCenter(new THREE.Vector3());
269
+ const size = box.getSize(new THREE.Vector3());
270
+
271
+ // Center the model
272
+ model.position.sub(center);
273
+
274
+ // Auto-scale to fit view
275
+ const maxDim = Math.max(size.x, size.y, size.z);
276
+ const scale = 10 / maxDim;
277
+ model.scale.multiplyScalar(scale);
278
+
279
+ scene.add(model);
280
+
281
+ // Auto-rotate camera
282
+ function animate() {
283
+ requestAnimationFrame(animate);
284
+ controls.update();
285
+
286
+ // Gentle auto-rotation
287
+ const time = Date.now() * 0.001;
288
+ camera.position.x = Math.cos(time * 0.1) * 8;
289
+ camera.position.z = Math.sin(time * 0.1) * 8;
290
+ camera.lookAt(0, 0, 0);
291
+
292
+ renderer.render(scene, camera);
293
+ }
294
+ animate();
295
+ },
296
+ function (progress) {
297
+ console.log('Loading:', progress);
298
+ },
299
+ function (error) {
300
+ console.error('Error loading GLB:', error);
301
+ container.innerHTML = '<p style="color: white; text-align: center; padding: 20px;">Error loading GLB file</p>';
302
+ }
303
+ );
304
+
305
+ // Handle resize
306
+ window.addEventListener('resize', function() {
307
+ camera.aspect = container.clientWidth / container.clientHeight;
308
+ camera.updateProjectionMatrix();
309
+ renderer.setSize(container.clientWidth, container.clientHeight);
310
+ });
311
+ </script>
312
+ """
313
+
314
+ return html_content
315
+
316
  def create_gradio_interface():
317
+ with gr.Blocks(title="VOX to GLB Converter with Preview", theme=gr.themes.Soft()) as app:
318
  gr.Markdown("""
319
+ # 🧊 MagicaVoxel VOX to GLB Converter with 3D Preview
320
+ Convert your MagicaVoxel `.vox` files to `.glb` format and preview them in 3D
321
  """)
322
 
323
  with gr.Row():
324
  with gr.Column():
325
  vox_input = gr.File(label="Upload VOX File", file_types=[".vox"], file_count="single")
326
+ convert_btn = gr.Button("🔄 Convert to GLB", variant="primary")
327
  status_output = gr.Textbox(label="Status", interactive=False, placeholder="Ready...")
328
 
329
  with gr.Column():
330
  glb_output = gr.File(label="Download GLB File", file_types=[".glb"], interactive=False)
331
 
332
+ # 3D Preview Section
333
+ gr.Markdown("### 🎮 3D Preview")
334
+ preview_html = gr.HTML(label="GLB Preview")
335
+
336
+ # Connect conversion to preview
337
+ def convert_with_preview(vox_file):
338
+ glb_path, message = process_vox_file(vox_file)
339
+ if glb_path:
340
+ preview = create_glb_preview(glb_path)
341
+ return glb_path, message, preview
342
+ else:
343
+ return None, message, "<p>❌ Conversion failed - no preview available</p>"
344
+
345
  convert_btn.click(
346
+ fn=convert_with_preview,
347
  inputs=[vox_input],
348
+ outputs=[glb_output, status_output, preview_html]
349
  )
350
+
351
+ gr.Markdown("""
352
+ ### 📋 How to Use
353
+ 1. Upload your `.vox` file
354
+ 2. Click "Convert to GLB"
355
+ 3. Download the GLB file
356
+ 4. Preview your voxel model in 3D above
357
+ """)
358
 
359
  return app
360