MySafeCode commited on
Commit
1578be5
·
verified ·
1 Parent(s): 2ab2d08

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +116 -83
app.py CHANGED
@@ -7,6 +7,7 @@ from pathlib import Path
7
  import logging
8
  from typing import Tuple, Optional
9
  import struct
 
10
 
11
  # Configure logging
12
  logging.basicConfig(level=logging.INFO)
@@ -23,87 +24,95 @@ class VoxParser:
23
  def parse(self) -> dict:
24
  """Parse the .vox file and extract voxel data"""
25
  try:
26
- with open(self.file_path, 'rb') as f:
27
- # Read header
28
- header = f.read(4).decode('ascii')
29
- if header != 'VOX ':
30
- raise ValueError("Invalid VOX file header")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
- version = struct.unpack('<I', f.read(4))[0]
 
 
 
 
 
 
 
33
 
34
- # Parse chunks
35
- while True:
36
- chunk_data = f.read(8)
37
- if len(chunk_data) < 8:
38
- break
39
-
40
- chunk_id = chunk_data[:4].decode('ascii')
41
- chunk_size, child_size = struct.unpack('<II', chunk_data[4:])
42
-
43
- chunk_content = f.read(chunk_size)
44
-
45
- if chunk_id == 'SIZE':
46
- self.parse_size(chunk_content)
47
- elif chunk_id == 'XYZI':
48
- self.parse_voxels(chunk_content)
49
- elif chunk_id == 'RGBA':
50
- self.parse_palette(chunk_content)
51
- else:
52
- # Skip unknown chunks
53
- if chunk_size > 0:
54
- f.seek(chunk_size, 1)
55
 
56
- return {
57
- 'voxels': self.voxels,
58
- 'palette': self.palette or self._default_palette(),
59
- 'size': self.size
60
- }
61
  except Exception as e:
62
  logger.error(f"Error parsing VOX file: {e}")
63
  raise
64
 
65
- def parse_size(self, data: bytes):
66
  """Parse size chunk"""
67
- self.size = {
68
- 'x': struct.unpack('<I', data[:4])[0],
69
- 'y': struct.unpack('<I', data[4:8])[0],
70
- 'z': struct.unpack('<I', data[8:12])[0]
71
- }
 
72
 
73
- def parse_voxels(self, data: bytes):
74
  """Parse voxel data"""
75
- num_voxels = struct.unpack('<I', data[:4])[0]
76
- offset = 4
77
-
78
- for i in range(num_voxels):
79
- if offset + 4 <= len(data):
80
- x, y, z, color_index = struct.unpack('BBBB', data[offset:offset+4])
81
- self.voxels.append({
82
- 'x': x,
83
- 'y': y,
84
- 'z': z,
85
- 'color_index': color_index
86
- })
87
- offset += 4
88
 
89
- def parse_palette(self, data: bytes):
90
  """Parse palette data"""
91
- offset = 0
92
  for i in range(256):
93
- if offset + 4 <= len(data):
94
- r, g, b, a = struct.unpack('BBBB', data[offset:offset+4])
95
  self.palette.append({
96
  'r': r,
97
  'g': g,
98
  'b': b,
99
  'a': a
100
  })
101
- offset += 4
102
 
103
  def _default_palette(self) -> list:
104
  """Default MagicaVoxel palette if none found"""
105
  colors = []
106
- # Create a simple gradient palette
107
  for i in range(256):
108
  intensity = i / 255.0
109
  colors.append({
@@ -114,39 +123,69 @@ class VoxParser:
114
  })
115
  return colors
116
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  class VoxToGlbConverter:
118
  """Convert MagicaVoxel files to GLB format"""
119
 
120
  def __init__(self):
121
  self.voxel_size = 1.0
122
 
123
- def vox_to_glb(self, vox_file_path: str) -> Tuple[str, str]:
124
  """
125
  Convert .vox file to .glb file
126
 
127
  Args:
128
- vox_file_path: Path to the .vox file
129
 
130
  Returns:
131
  Tuple of (glb_file_path, status_message)
132
  """
133
  try:
134
- # Parse the .vox file
135
- parser = VoxParser(vox_file_path)
136
- voxel_data = parser.parse()
137
-
138
- if not voxel_data['voxels']:
139
- return "", "No voxels found in the file"
140
-
141
- # Create mesh from voxels
142
- mesh = self.create_mesh_from_voxels(voxel_data)
143
-
144
- # Save as GLB
145
- output_path = str(Path(vox_file_path).with_suffix('.glb'))
146
- mesh.export(output_path)
147
-
148
- return output_path, f"Successfully converted {len(voxel_data['voxels'])} voxels to GLB format"
149
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
  except Exception as e:
151
  logger.error(f"Conversion error: {e}")
152
  return "", f"Error converting file: {str(e)}"
@@ -206,7 +245,7 @@ def process_vox_file(vox_file) -> Tuple[str, str]:
206
 
207
  try:
208
  converter = VoxToGlbConverter()
209
- glb_path, message = converter.vox_to_glb(vox_file.name)
210
  return glb_path, message
211
  except Exception as e:
212
  return "", f"Error: {str(e)}"
@@ -264,7 +303,6 @@ def create_gradio_interface():
264
  outputs=[glb_output, status_output]
265
  )
266
 
267
- # Examples
268
  gr.Markdown("### 📁 Example Usage")
269
  gr.Markdown("""
270
  **How to use:**
@@ -272,11 +310,6 @@ def create_gradio_interface():
272
  2. Click "Convert to GLB"
273
  3. Download the converted `.glb` file
274
  4. Preview in any 3D viewer or use in your projects
275
-
276
- **Supported formats:**
277
- - MagicaVoxel .vox files (all versions)
278
- - Preserves colors and voxel positions
279
- - Optimized mesh output
280
  """)
281
 
282
  return app
 
7
  import logging
8
  from typing import Tuple, Optional
9
  import struct
10
+ import shutil
11
 
12
  # Configure logging
13
  logging.basicConfig(level=logging.INFO)
 
24
  def parse(self) -> dict:
25
  """Parse the .vox file and extract voxel data"""
26
  try:
27
+ # Handle both file paths and file-like objects
28
+ if hasattr(self.file_path, 'read'):
29
+ # File-like object from Gradio
30
+ data = self.file_path.read()
31
+ else:
32
+ # File path
33
+ with open(self.file_path, 'rb') as f:
34
+ data = f.read()
35
+
36
+ self.buffer = data
37
+ self.view = memoryview(data)
38
+ self.offset = 0
39
+
40
+ # Read header
41
+ header = self.read_string(4)
42
+ if header != 'VOX ':
43
+ raise ValueError("Invalid VOX file header")
44
+
45
+ version = self.read_int32()
46
+
47
+ # Parse chunks
48
+ while self.offset < len(data):
49
+ chunk_id = self.read_string(4)
50
+ chunk_size = self.read_int32()
51
+ child_size = self.read_int32()
52
 
53
+ if chunk_id == 'SIZE':
54
+ self.parse_size(chunk_size)
55
+ elif chunk_id == 'XYZI':
56
+ self.parse_voxels(chunk_size)
57
+ elif chunk_id == 'RGBA':
58
+ self.parse_palette(chunk_size)
59
+ else:
60
+ self.offset += chunk_size
61
 
62
+ # Safety check
63
+ if self.offset >= len(data):
64
+ break
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
+ return {
67
+ 'voxels': self.voxels,
68
+ 'palette': self.palette or self._default_palette(),
69
+ 'size': self.size
70
+ }
71
  except Exception as e:
72
  logger.error(f"Error parsing VOX file: {e}")
73
  raise
74
 
75
+ def parse_size(self, size: int):
76
  """Parse size chunk"""
77
+ if self.offset + 12 <= len(self.buffer):
78
+ self.size = {
79
+ 'x': self.read_int32(),
80
+ 'y': self.read_int32(),
81
+ 'z': self.read_int32()
82
+ }
83
 
84
+ def parse_voxels(self, size: int):
85
  """Parse voxel data"""
86
+ if self.offset + 4 <= len(self.buffer):
87
+ num_voxels = self.read_int32()
88
+
89
+ for i in range(num_voxels):
90
+ if self.offset + 4 <= len(self.buffer):
91
+ x, y, z, color_index = struct.unpack('BBBB', self.buffer[self.offset:self.offset+4])
92
+ self.voxels.append({
93
+ 'x': x,
94
+ 'y': y,
95
+ 'z': z,
96
+ 'color_index': color_index
97
+ })
98
+ self.offset += 4
99
 
100
+ def parse_palette(self, size: int):
101
  """Parse palette data"""
 
102
  for i in range(256):
103
+ if self.offset + 4 <= len(self.buffer):
104
+ r, g, b, a = struct.unpack('BBBB', self.buffer[self.offset:self.offset+4])
105
  self.palette.append({
106
  'r': r,
107
  'g': g,
108
  'b': b,
109
  'a': a
110
  })
111
+ self.offset += 4
112
 
113
  def _default_palette(self) -> list:
114
  """Default MagicaVoxel palette if none found"""
115
  colors = []
 
116
  for i in range(256):
117
  intensity = i / 255.0
118
  colors.append({
 
123
  })
124
  return colors
125
 
126
+ def read_string(self, length: int) -> str:
127
+ """Read string from buffer"""
128
+ if self.offset + length <= len(self.buffer):
129
+ result = self.buffer[self.offset:self.offset+length].tobytes().decode('ascii', errors='ignore')
130
+ self.offset += length
131
+ return result
132
+ return ""
133
+
134
+ def read_int32(self) -> int:
135
+ """Read 32-bit integer"""
136
+ if self.offset + 4 <= len(self.buffer):
137
+ result = struct.unpack('<I', self.buffer[self.offset:self.offset+4])[0]
138
+ self.offset += 4
139
+ return result
140
+ return 0
141
+
142
  class VoxToGlbConverter:
143
  """Convert MagicaVoxel files to GLB format"""
144
 
145
  def __init__(self):
146
  self.voxel_size = 1.0
147
 
148
+ def vox_to_glb(self, vox_file) -> Tuple[str, str]:
149
  """
150
  Convert .vox file to .glb file
151
 
152
  Args:
153
+ vox_file: Gradio file object or file path
154
 
155
  Returns:
156
  Tuple of (glb_file_path, status_message)
157
  """
158
  try:
159
+ # Create a temporary file to handle the upload
160
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.vox') as tmp_file:
161
+ if hasattr(vox_file, 'name'):
162
+ # Gradio file object - copy to temp file
163
+ shutil.copy(vox_file.name, tmp_file.name)
164
+ temp_vox_path = tmp_file.name
165
+ else:
166
+ # Direct file path
167
+ temp_vox_path = vox_file
168
+
169
+ # Parse the .vox file
170
+ parser = VoxParser(temp_vox_path)
171
+ voxel_data = parser.parse()
172
+
173
+ if not voxel_data['voxels']:
174
+ return "", "No voxels found in the file"
175
+
176
+ # Create mesh from voxels
177
+ mesh = self.create_mesh_from_voxels(voxel_data)
178
+
179
+ # Save as GLB to a permanent location
180
+ output_path = str(Path(tempfile.gettempdir()) / f"converted_{os.path.basename(temp_vox_path)}.glb")
181
+ mesh.export(output_path)
182
+
183
+ # Clean up temp file
184
+ if temp_vox_path != vox_file:
185
+ os.unlink(temp_vox_path)
186
+
187
+ return output_path, f"Successfully converted {len(voxel_data['voxels'])} voxels to GLB format"
188
+
189
  except Exception as e:
190
  logger.error(f"Conversion error: {e}")
191
  return "", f"Error converting file: {str(e)}"
 
245
 
246
  try:
247
  converter = VoxToGlbConverter()
248
+ glb_path, message = converter.vox_to_glb(vox_file)
249
  return glb_path, message
250
  except Exception as e:
251
  return "", f"Error: {str(e)}"
 
303
  outputs=[glb_output, status_output]
304
  )
305
 
 
306
  gr.Markdown("### 📁 Example Usage")
307
  gr.Markdown("""
308
  **How to use:**
 
310
  2. Click "Convert to GLB"
311
  3. Download the converted `.glb` file
312
  4. Preview in any 3D viewer or use in your projects
 
 
 
 
 
313
  """)
314
 
315
  return app