Update app.py
Browse files
app.py
CHANGED
@@ -75,16 +75,43 @@ print(f"Output directory: {LOG_PATH}")
|
|
75 |
|
76 |
def normalize_mesh(mesh):
|
77 |
"""Normalize mesh to fit in a unit cube centered at origin"""
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
88 |
|
89 |
def create_rotation_animation(mesh, duration=3.0, fps=30):
|
90 |
"""Create a rotation animation around the Y axis"""
|
@@ -98,15 +125,33 @@ def create_rotation_animation(mesh, duration=3.0, fps=30):
|
|
98 |
t = frame_idx / (num_frames - 1) # Normalized time [0, 1]
|
99 |
angle = t * 2 * math.pi # Full rotation
|
100 |
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
110 |
|
111 |
return frames
|
112 |
|
@@ -120,17 +165,30 @@ def create_float_animation(mesh, duration=3.0, fps=30, amplitude=0.2):
|
|
120 |
|
121 |
for frame_idx in range(num_frames):
|
122 |
t = frame_idx / (num_frames - 1) # Normalized time [0, 1]
|
123 |
-
|
124 |
-
# Create a copy of the mesh to animate
|
125 |
-
animated_mesh = mesh.copy()
|
126 |
-
|
127 |
-
# Apply floating motion (sinusoidal)
|
128 |
y_offset = amplitude * math.sin(2 * math.pi * t)
|
129 |
-
translation_matrix = tf.translation_matrix([0, y_offset, 0])
|
130 |
-
animated_mesh.apply_transform(translation_matrix)
|
131 |
|
132 |
-
|
133 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
134 |
|
135 |
return frames
|
136 |
|
@@ -142,64 +200,98 @@ def create_explode_animation(mesh, duration=3.0, fps=30):
|
|
142 |
# Normalize the mesh for consistent animation
|
143 |
mesh, original_center, original_scale = normalize_mesh(mesh)
|
144 |
|
145 |
-
#
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
# If splitting fails, work with the original mesh
|
153 |
-
components = None
|
154 |
-
|
155 |
-
for frame_idx in range(num_frames):
|
156 |
-
t = frame_idx / (num_frames - 1) # Normalized time [0, 1]
|
157 |
-
|
158 |
-
if components:
|
159 |
-
# Create a scene to hold all components
|
160 |
-
scene = trimesh.Scene()
|
161 |
|
162 |
-
#
|
163 |
-
for
|
164 |
-
|
165 |
-
animated_component = component.copy()
|
166 |
|
167 |
-
#
|
168 |
-
|
|
|
|
|
|
|
169 |
if np.linalg.norm(direction) < 1e-10:
|
170 |
-
#
|
171 |
direction = np.random.rand(3) - 0.5
|
172 |
|
173 |
direction = direction / np.linalg.norm(direction)
|
174 |
|
175 |
-
#
|
176 |
-
translation = direction * t * 0.5 #
|
177 |
translation_matrix = tf.translation_matrix(translation)
|
178 |
-
|
179 |
|
180 |
-
#
|
181 |
-
|
182 |
|
183 |
-
|
184 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
185 |
else:
|
186 |
-
#
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
203 |
|
204 |
return frames
|
205 |
|
@@ -263,23 +355,29 @@ def generate_gif_from_frames(frames, output_path, fps=30, resolution=(640, 480),
|
|
263 |
|
264 |
for frame in frames:
|
265 |
# Create a scene with the frame
|
266 |
-
|
|
|
|
|
|
|
267 |
|
268 |
-
#
|
269 |
try:
|
270 |
-
|
271 |
-
scene.camera_transform = scene.camera_transform
|
272 |
except:
|
273 |
-
#
|
274 |
-
scene.
|
275 |
|
|
|
|
|
|
|
276 |
# Render the frame
|
277 |
try:
|
278 |
-
|
279 |
-
|
|
|
280 |
except Exception as e:
|
281 |
print(f"Error rendering frame: {str(e)}")
|
282 |
-
#
|
283 |
gif_frames.append(Image.new('RGB', resolution, (255, 255, 255)))
|
284 |
|
285 |
# Save as GIF
|
@@ -300,7 +398,30 @@ def create_animation_mesh(input_mesh_path, animation_type='rotate', duration=3.0
|
|
300 |
"""Create animation from input mesh based on animation type"""
|
301 |
# Load the mesh
|
302 |
try:
|
303 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
304 |
except Exception as e:
|
305 |
print(f"Error loading mesh: {str(e)}")
|
306 |
return None, None
|
|
|
75 |
|
76 |
def normalize_mesh(mesh):
|
77 |
"""Normalize mesh to fit in a unit cube centered at origin"""
|
78 |
+
if isinstance(mesh, trimesh.Scene):
|
79 |
+
# Scene ๊ฐ์ฒด ์ฒ๋ฆฌ
|
80 |
+
# ์ฌ์์ ๋ชจ๋ ๋ฉ์ ์ถ์ถ
|
81 |
+
meshes = []
|
82 |
+
for geometry in mesh.geometry.values():
|
83 |
+
if isinstance(geometry, trimesh.Trimesh):
|
84 |
+
meshes.append(geometry)
|
85 |
+
|
86 |
+
if not meshes:
|
87 |
+
raise ValueError("No meshes found in scene")
|
88 |
+
|
89 |
+
# ๋ชจ๋ ๋ฉ์์ ์ ์ ์ ๊ฒฐํฉํ์ฌ ๊ฒฝ๊ณ ์์ ๊ณ์ฐ
|
90 |
+
all_vertices = np.vstack([m.vertices for m in meshes])
|
91 |
+
bounds = np.array([all_vertices.min(axis=0), all_vertices.max(axis=0)])
|
92 |
+
center = (bounds[0] + bounds[1]) / 2
|
93 |
+
scale = 1.0 / (bounds[1] - bounds[0]).max()
|
94 |
+
|
95 |
+
# ๊ฐ ๋ฉ์๋ฅผ ์ ๊ทํํ์ฌ ์ ์ฌ ์์ฑ
|
96 |
+
normalized_scene = trimesh.Scene()
|
97 |
+
for mesh_obj in meshes:
|
98 |
+
normalized_mesh = mesh_obj.copy()
|
99 |
+
normalized_mesh.vertices = (normalized_mesh.vertices - center) * scale
|
100 |
+
normalized_scene.add_geometry(normalized_mesh)
|
101 |
+
|
102 |
+
return normalized_scene, center, scale
|
103 |
+
else:
|
104 |
+
# ์ผ๋ฐ Trimesh ๊ฐ์ฒด ์ฒ๋ฆฌ
|
105 |
+
vertices = mesh.vertices
|
106 |
+
bounds = np.array([vertices.min(axis=0), vertices.max(axis=0)])
|
107 |
+
center = (bounds[0] + bounds[1]) / 2
|
108 |
+
scale = 1.0 / (bounds[1] - bounds[0]).max()
|
109 |
+
|
110 |
+
# ๋ณต์ฌ๋ณธ ์์ฑํ์ฌ ์๋ณธ ๋ณ๊ฒฝ ๋ฐฉ์ง
|
111 |
+
normalized_mesh = mesh.copy()
|
112 |
+
normalized_mesh.vertices = (vertices - center) * scale
|
113 |
+
|
114 |
+
return normalized_mesh, center, scale
|
115 |
|
116 |
def create_rotation_animation(mesh, duration=3.0, fps=30):
|
117 |
"""Create a rotation animation around the Y axis"""
|
|
|
125 |
t = frame_idx / (num_frames - 1) # Normalized time [0, 1]
|
126 |
angle = t * 2 * math.pi # Full rotation
|
127 |
|
128 |
+
if isinstance(mesh, trimesh.Scene):
|
129 |
+
# Scene ๊ฐ์ฒด์ธ ๊ฒฝ์ฐ ๊ฐ ๋ฉ์์ ํ์ ์ ์ฉ
|
130 |
+
frame_scene = trimesh.Scene()
|
131 |
+
|
132 |
+
for node_name, transform, geometry_name in mesh.graph.nodes_geometry:
|
133 |
+
# ๋ฉ์๋ฅผ ๋ณต์ฌํ๊ณ ํ์ ์ ์ฉ
|
134 |
+
mesh_copy = mesh.geometry[geometry_name].copy()
|
135 |
+
|
136 |
+
# ๋ฉ์์ ์ค์ฌ์ ๊ณ์ฐ
|
137 |
+
center_point = mesh_copy.centroid if hasattr(mesh_copy, 'centroid') else np.zeros(3)
|
138 |
+
|
139 |
+
# ํ์ ํ๋ ฌ ์์ฑ
|
140 |
+
rotation_matrix = tf.rotation_matrix(angle, [0, 1, 0], center_point)
|
141 |
+
|
142 |
+
# ๋ณํ ์ ์ฉ
|
143 |
+
mesh_copy.apply_transform(rotation_matrix)
|
144 |
+
|
145 |
+
# ์ฌ์ ์ถ๊ฐ
|
146 |
+
frame_scene.add_geometry(mesh_copy, node_name=node_name)
|
147 |
+
|
148 |
+
frames.append(frame_scene)
|
149 |
+
else:
|
150 |
+
# ์ผ๋ฐ Trimesh ๊ฐ์ฒด์ธ ๊ฒฝ์ฐ ์ง์ ํ์ ์ ์ฉ
|
151 |
+
animated_mesh = mesh.copy()
|
152 |
+
rotation_matrix = tf.rotation_matrix(angle, [0, 1, 0])
|
153 |
+
animated_mesh.apply_transform(rotation_matrix)
|
154 |
+
frames.append(animated_mesh)
|
155 |
|
156 |
return frames
|
157 |
|
|
|
165 |
|
166 |
for frame_idx in range(num_frames):
|
167 |
t = frame_idx / (num_frames - 1) # Normalized time [0, 1]
|
|
|
|
|
|
|
|
|
|
|
168 |
y_offset = amplitude * math.sin(2 * math.pi * t)
|
|
|
|
|
169 |
|
170 |
+
if isinstance(mesh, trimesh.Scene):
|
171 |
+
# Scene ๊ฐ์ฒด์ธ ๊ฒฝ์ฐ
|
172 |
+
frame_scene = trimesh.Scene()
|
173 |
+
|
174 |
+
for node_name, transform, geometry_name in mesh.graph.nodes_geometry:
|
175 |
+
# ๋ฉ์๋ฅผ ๋ณต์ฌ
|
176 |
+
mesh_copy = mesh.geometry[geometry_name].copy()
|
177 |
+
|
178 |
+
# ๋ณํ ์ ์ฉ
|
179 |
+
translation_matrix = tf.translation_matrix([0, y_offset, 0])
|
180 |
+
mesh_copy.apply_transform(translation_matrix)
|
181 |
+
|
182 |
+
# ์ฌ์ ์ถ๊ฐ
|
183 |
+
frame_scene.add_geometry(mesh_copy, node_name=node_name)
|
184 |
+
|
185 |
+
frames.append(frame_scene)
|
186 |
+
else:
|
187 |
+
# ์ผ๋ฐ Trimesh ๊ฐ์ฒด์ธ ๊ฒฝ์ฐ
|
188 |
+
animated_mesh = mesh.copy()
|
189 |
+
translation_matrix = tf.translation_matrix([0, y_offset, 0])
|
190 |
+
animated_mesh.apply_transform(translation_matrix)
|
191 |
+
frames.append(animated_mesh)
|
192 |
|
193 |
return frames
|
194 |
|
|
|
200 |
# Normalize the mesh for consistent animation
|
201 |
mesh, original_center, original_scale = normalize_mesh(mesh)
|
202 |
|
203 |
+
# ์ฌ์ธ ๊ฒฝ์ฐ ๋ถ๋ถ๋ณ ์ฒ๋ฆฌ
|
204 |
+
if isinstance(mesh, trimesh.Scene):
|
205 |
+
for frame_idx in range(num_frames):
|
206 |
+
t = frame_idx / (num_frames - 1) # Normalized time [0, 1]
|
207 |
+
|
208 |
+
# ์ ์ฌ ์์ฑ
|
209 |
+
frame_scene = trimesh.Scene()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
210 |
|
211 |
+
# ๊ฐ ๋
ธ๋๋ณ ํญ๋ฐ ์ ๋๋ฉ์ด์
์ ์ฉ
|
212 |
+
for node_name, transform, geometry_name in mesh.graph.nodes_geometry:
|
213 |
+
mesh_copy = mesh.geometry[geometry_name].copy()
|
|
|
214 |
|
215 |
+
# ๋
ธ๋์ ์ค์ฌ์ ๊ณ์ฐ
|
216 |
+
center_point = mesh_copy.centroid if hasattr(mesh_copy, 'centroid') else np.zeros(3)
|
217 |
+
|
218 |
+
# ๋ฐฉํฅ ๋ฒกํฐ (์์ ์์ ๊ฐ์ฒด ์ค์ฌ๊น์ง)
|
219 |
+
direction = center_point
|
220 |
if np.linalg.norm(direction) < 1e-10:
|
221 |
+
# ์ค์ฌ์ ์ด ์์ ๊ณผ ๋๋ฌด ๊ฐ๊น์ฐ๋ฉด ๋๋ค ๋ฐฉํฅ ์ ํ
|
222 |
direction = np.random.rand(3) - 0.5
|
223 |
|
224 |
direction = direction / np.linalg.norm(direction)
|
225 |
|
226 |
+
# ํญ๋ฐ ์ด๋ ์ ์ฉ
|
227 |
+
translation = direction * t * 0.5 # ํญ๋ฐ ๊ฐ๋ ์กฐ์
|
228 |
translation_matrix = tf.translation_matrix(translation)
|
229 |
+
mesh_copy.apply_transform(translation_matrix)
|
230 |
|
231 |
+
# ์ฌ์ ์ถ๊ฐ
|
232 |
+
frame_scene.add_geometry(mesh_copy, node_name=f"{node_name}_{frame_idx}")
|
233 |
|
234 |
+
frames.append(frame_scene)
|
235 |
+
else:
|
236 |
+
# ๋ฉ์๋ฅผ ์ฌ๋ฌ ๋ถ๋ถ์ผ๋ก ๋ถํ
|
237 |
+
try:
|
238 |
+
components = mesh.split(only_watertight=False)
|
239 |
+
if len(components) <= 1:
|
240 |
+
# ์ปดํฌ๋ํธ๊ฐ ํ๋๋ฟ์ด๋ฉด ์ ์ ๊ธฐ๋ฐ ํญ๋ฐ ์ ๋๋ฉ์ด์
์ฌ์ฉ
|
241 |
+
raise ValueError("Mesh cannot be split into components")
|
242 |
+
except:
|
243 |
+
# ๋ถํ ์ด ์คํจํ๋ฉด ์ ์ ๊ธฐ๋ฐ ํญ๋ฐ ์ ๋๋ฉ์ด์
์ฌ์ฉ
|
244 |
+
for frame_idx in range(num_frames):
|
245 |
+
t = frame_idx / (num_frames - 1) # Normalized time [0, 1]
|
246 |
+
|
247 |
+
# ๋ฉ์ ๋ณต์ฌ
|
248 |
+
animated_mesh = mesh.copy()
|
249 |
+
vertices = animated_mesh.vertices.copy()
|
250 |
+
|
251 |
+
# ๊ฐ ์ ์ ์ ์ค์ฌ์์ ๋ฐ๊นฅ์ชฝ์ผ๋ก ์ด๋
|
252 |
+
directions = vertices.copy()
|
253 |
+
norms = np.linalg.norm(directions, axis=1, keepdims=True)
|
254 |
+
mask = norms > 1e-10
|
255 |
+
directions[mask] = directions[mask] / norms[mask]
|
256 |
+
directions[~mask] = np.random.rand(np.sum(~mask), 3) - 0.5
|
257 |
+
|
258 |
+
# ํญ๋ฐ ๊ณ์ ์ ์ฉ
|
259 |
+
vertices += directions * t * 0.3
|
260 |
+
animated_mesh.vertices = vertices
|
261 |
+
|
262 |
+
frames.append(animated_mesh)
|
263 |
else:
|
264 |
+
# ์ปดํฌ๋ํธ ๊ธฐ๋ฐ ํญ๋ฐ ์ ๋๋ฉ์ด์
|
265 |
+
for frame_idx in range(num_frames):
|
266 |
+
t = frame_idx / (num_frames - 1) # Normalized time [0, 1]
|
267 |
+
|
268 |
+
# ์ ์ฌ ์์ฑ
|
269 |
+
scene = trimesh.Scene()
|
270 |
+
|
271 |
+
# ๊ฐ ์ปดํฌ๋ํธ๋ฅผ ์ค์ฌ์์ ๋ฐ๊นฅ์ชฝ์ผ๋ก ์ด๋
|
272 |
+
for i, component in enumerate(components):
|
273 |
+
# ์ปดํฌ๋ํธ ๋ณต์ฌ
|
274 |
+
animated_component = component.copy()
|
275 |
+
|
276 |
+
# ์ปดํฌ๋ํธ ์ค์ฌ์ ์์ ๋ฐฉํฅ ๊ณ์ฐ
|
277 |
+
direction = animated_component.centroid
|
278 |
+
if np.linalg.norm(direction) < 1e-10:
|
279 |
+
# ์ค์ฌ์ ์ด ์์ ๊ณผ ๋๋ฌด ๊ฐ๊น์ฐ๋ฉด ๋๋ค ๋ฐฉํฅ ์ ํ
|
280 |
+
direction = np.random.rand(3) - 0.5
|
281 |
+
|
282 |
+
direction = direction / np.linalg.norm(direction)
|
283 |
+
|
284 |
+
# ํญ๋ฐ ์ด๋ ์ ์ฉ
|
285 |
+
translation = direction * t * 0.5
|
286 |
+
translation_matrix = tf.translation_matrix(translation)
|
287 |
+
animated_component.apply_transform(translation_matrix)
|
288 |
+
|
289 |
+
# ์ฌ์ ์ถ๊ฐ
|
290 |
+
scene.add_geometry(animated_component, node_name=f"component_{i}")
|
291 |
+
|
292 |
+
# ์ฌ์ ๋จ์ผ ๋ฉ์๋ก ๋ณํ (๊ทผ์ฌ์น)
|
293 |
+
animated_mesh = trimesh.util.concatenate(scene.dump())
|
294 |
+
frames.append(animated_mesh)
|
295 |
|
296 |
return frames
|
297 |
|
|
|
355 |
|
356 |
for frame in frames:
|
357 |
# Create a scene with the frame
|
358 |
+
if not isinstance(frame, trimesh.Scene):
|
359 |
+
scene = trimesh.Scene(frame)
|
360 |
+
else:
|
361 |
+
scene = frame
|
362 |
|
363 |
+
# ์๋ ์นด๋ฉ๋ผ ์ค์ (์ ์ฒด ์ฅ๋ฉด์ด ๋ณด์ด๋๋ก)
|
364 |
try:
|
365 |
+
scene.camera
|
|
|
366 |
except:
|
367 |
+
# ๊ธฐ๋ณธ ์นด๋ฉ๋ผ๊ฐ ์์ผ๋ฉด ์์ฑ
|
368 |
+
scene.set_camera()
|
369 |
|
370 |
+
# ๋ชจ๋ ๊ฐ์ฒด๊ฐ ๋ณด์ด๋๋ก ์นด๋ฉ๋ผ ์์น ์กฐ์
|
371 |
+
scene.camera_transform = scene.camera_transform
|
372 |
+
|
373 |
# Render the frame
|
374 |
try:
|
375 |
+
rendered_img = scene.save_image(resolution=resolution, background=background_color)
|
376 |
+
pil_img = Image.open(rendered_img)
|
377 |
+
gif_frames.append(pil_img)
|
378 |
except Exception as e:
|
379 |
print(f"Error rendering frame: {str(e)}")
|
380 |
+
# ๋ ๋๋ง ์คํจ ์ ๋น ์ด๋ฏธ์ง ์์ฑ
|
381 |
gif_frames.append(Image.new('RGB', resolution, (255, 255, 255)))
|
382 |
|
383 |
# Save as GIF
|
|
|
398 |
"""Create animation from input mesh based on animation type"""
|
399 |
# Load the mesh
|
400 |
try:
|
401 |
+
# ๋จผ์ Scene์ผ๋ก ๋ก๋ ์๋
|
402 |
+
loaded_obj = trimesh.load(input_mesh_path)
|
403 |
+
|
404 |
+
# Scene์ธ ๊ฒฝ์ฐ ๋จ์ผ ๋ฉ์๋ก ๋ณํ ์๋
|
405 |
+
if isinstance(loaded_obj, trimesh.Scene):
|
406 |
+
print("Loaded a scene, extracting meshes...")
|
407 |
+
# ์ฌ์์ ๋ชจ๋ ๋ฉ์ ์ถ์ถ
|
408 |
+
meshes = []
|
409 |
+
for geometry in loaded_obj.geometry.values():
|
410 |
+
if isinstance(geometry, trimesh.Trimesh):
|
411 |
+
meshes.append(geometry)
|
412 |
+
|
413 |
+
if not meshes:
|
414 |
+
print("No meshes found in scene")
|
415 |
+
return None, None
|
416 |
+
|
417 |
+
# ๋ชจ๋ ๋ฉ์๋ฅผ ํ๋๋ก ๊ฒฐํฉ
|
418 |
+
mesh = trimesh.util.concatenate(meshes)
|
419 |
+
elif isinstance(loaded_obj, trimesh.Trimesh):
|
420 |
+
mesh = loaded_obj
|
421 |
+
else:
|
422 |
+
print(f"Unsupported object type: {type(loaded_obj)}")
|
423 |
+
return None, None
|
424 |
+
|
425 |
except Exception as e:
|
426 |
print(f"Error loading mesh: {str(e)}")
|
427 |
return None, None
|