phg-vis 1.2.0__py3-none-any.whl → 1.3.0__py3-none-any.whl
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.
- phg/__init__.py +80 -2
- phg/phg_to_shader.py +433 -0
- phg/shader_render.py +425 -0
- phg/vis/GCU.dll +0 -0
- phg/vis/vis.exe +0 -0
- phg_vis-1.3.0.dist-info/METADATA +607 -0
- {phg_vis-1.2.0.dist-info → phg_vis-1.3.0.dist-info}/RECORD +10 -8
- phg_vis-1.2.0.dist-info/METADATA +0 -136
- {phg_vis-1.2.0.dist-info → phg_vis-1.3.0.dist-info}/LICENSE +0 -0
- {phg_vis-1.2.0.dist-info → phg_vis-1.3.0.dist-info}/WHEEL +0 -0
- {phg_vis-1.2.0.dist-info → phg_vis-1.3.0.dist-info}/top_level.txt +0 -0
phg/shader_render.py
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import pygame
|
|
2
|
+
import numpy as np
|
|
3
|
+
from OpenGL.GL import *
|
|
4
|
+
from OpenGL.GL.shaders import compileProgram, compileShader
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
class Camera:
|
|
9
|
+
"""Camera class with mouse interaction controls"""
|
|
10
|
+
|
|
11
|
+
def __init__(self):
|
|
12
|
+
# Start further back to see the whole scene
|
|
13
|
+
self.position = np.array([0.0, 3.0, 12.0], dtype=np.float32) # Camera position - further back
|
|
14
|
+
self.target = np.array([0.0, 0.0, 0.0], dtype=np.float32) # Look at target
|
|
15
|
+
self.up = np.array([0.0, 1.0, 0.0], dtype=np.float32) # Up direction
|
|
16
|
+
|
|
17
|
+
# Camera parameters
|
|
18
|
+
self.fov = 45.0 # Field of view
|
|
19
|
+
self.zoom_speed = 0.5 # Increased zoom speed
|
|
20
|
+
self.rotate_speed = 0.01 # Increased rotate speed
|
|
21
|
+
self.pan_speed = 0.02 # Increased pan speed
|
|
22
|
+
|
|
23
|
+
# Mouse state
|
|
24
|
+
self.last_mouse_pos = None
|
|
25
|
+
self.is_rotating = False
|
|
26
|
+
self.is_panning = False
|
|
27
|
+
|
|
28
|
+
def zoom(self, delta):
|
|
29
|
+
"""Zoom control"""
|
|
30
|
+
direction = self.target - self.position
|
|
31
|
+
distance = np.linalg.norm(direction)
|
|
32
|
+
|
|
33
|
+
# Limit min and max distance
|
|
34
|
+
min_distance = 1.0
|
|
35
|
+
max_distance = 100.0
|
|
36
|
+
|
|
37
|
+
new_distance = distance * (1.0 - delta * self.zoom_speed)
|
|
38
|
+
new_distance = np.clip(new_distance, min_distance, max_distance)
|
|
39
|
+
|
|
40
|
+
self.position = self.target - direction * (new_distance / distance)
|
|
41
|
+
print(f"Zoom: position={self.position}, distance={new_distance}")
|
|
42
|
+
|
|
43
|
+
def rotate(self, delta_x, delta_y):
|
|
44
|
+
"""Rotation control"""
|
|
45
|
+
# Calculate vector from target to camera (inverse of camera to target)
|
|
46
|
+
direction = self.position - self.target
|
|
47
|
+
distance = np.linalg.norm(direction)
|
|
48
|
+
|
|
49
|
+
if distance < 0.001:
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
# Spherical coordinates rotation
|
|
53
|
+
# Convert to spherical coordinates
|
|
54
|
+
theta = np.arctan2(direction[0], direction[2]) # Horizontal angle (around Y axis)
|
|
55
|
+
phi = np.arctan2(direction[1], np.sqrt(direction[0]**2 + direction[2]**2)) # Vertical angle
|
|
56
|
+
|
|
57
|
+
# Apply rotation (inverted for more intuitive control)
|
|
58
|
+
theta -= delta_x * self.rotate_speed
|
|
59
|
+
phi += delta_y * self.rotate_speed # Inverted Y for more natural control
|
|
60
|
+
|
|
61
|
+
# Limit vertical angle (avoid flipping)
|
|
62
|
+
phi = np.clip(phi, -np.pi/2 + 0.1, np.pi/2 - 0.1)
|
|
63
|
+
|
|
64
|
+
# Convert back to Cartesian coordinates
|
|
65
|
+
new_x = distance * np.sin(theta) * np.cos(phi)
|
|
66
|
+
new_y = distance * np.sin(phi)
|
|
67
|
+
new_z = distance * np.cos(theta) * np.cos(phi)
|
|
68
|
+
|
|
69
|
+
self.position = self.target + np.array([new_x, new_y, new_z])
|
|
70
|
+
|
|
71
|
+
print(f"Rotate: position={self.position}")
|
|
72
|
+
|
|
73
|
+
def pan(self, delta_x, delta_y):
|
|
74
|
+
"""Pan control"""
|
|
75
|
+
# Calculate camera coordinate system
|
|
76
|
+
forward = normalize(self.target - self.position)
|
|
77
|
+
right = normalize(np.cross(forward, self.up))
|
|
78
|
+
up = normalize(np.cross(right, forward))
|
|
79
|
+
|
|
80
|
+
# Apply pan (inverted for more intuitive control)
|
|
81
|
+
pan_distance = 0.1
|
|
82
|
+
pan_vector = -delta_x * right * self.pan_speed + delta_y * up * self.pan_speed
|
|
83
|
+
|
|
84
|
+
self.position += pan_vector
|
|
85
|
+
self.target += pan_vector
|
|
86
|
+
|
|
87
|
+
print(f"Pan: position={self.position}, target={self.target}")
|
|
88
|
+
|
|
89
|
+
def normalize(v):
|
|
90
|
+
"""Normalize a vector"""
|
|
91
|
+
norm = np.linalg.norm(v)
|
|
92
|
+
if norm == 0:
|
|
93
|
+
return v
|
|
94
|
+
return v / norm
|
|
95
|
+
|
|
96
|
+
class ShaderRenderer:
|
|
97
|
+
"""
|
|
98
|
+
Shader renderer class for visualizing GLSL shaders
|
|
99
|
+
|
|
100
|
+
Usage:
|
|
101
|
+
renderer = ShaderRenderer()
|
|
102
|
+
renderer.render_shader(fragment_shader_code)
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def __init__(self, width=800, height=600, title="Shader Renderer"):
|
|
106
|
+
"""
|
|
107
|
+
Initialize Shader renderer
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
width: Window width
|
|
111
|
+
height: Window height
|
|
112
|
+
title: Window title
|
|
113
|
+
"""
|
|
114
|
+
self.width = width
|
|
115
|
+
self.height = height
|
|
116
|
+
self.title = title
|
|
117
|
+
self.is_running = False
|
|
118
|
+
self.shader_program = None
|
|
119
|
+
self.start_time = 0
|
|
120
|
+
self.camera = Camera()
|
|
121
|
+
self.show_help = True
|
|
122
|
+
|
|
123
|
+
self.vertex_shader_src = """
|
|
124
|
+
#version 330 core
|
|
125
|
+
layout (location = 0) in vec3 aPos;
|
|
126
|
+
layout (location = 1) in vec2 aTexCoord;
|
|
127
|
+
|
|
128
|
+
out vec2 TexCoord;
|
|
129
|
+
|
|
130
|
+
void main()
|
|
131
|
+
{
|
|
132
|
+
gl_Position = vec4(aPos, 1.0);
|
|
133
|
+
TexCoord = aTexCoord;
|
|
134
|
+
}
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
self._init_pygame()
|
|
138
|
+
self._init_opengl()
|
|
139
|
+
self._create_geometry()
|
|
140
|
+
self._init_font()
|
|
141
|
+
|
|
142
|
+
def _init_pygame(self):
|
|
143
|
+
"""Initialize Pygame and OpenGL context"""
|
|
144
|
+
pygame.init()
|
|
145
|
+
self.screen = pygame.display.set_mode((self.width, self.height), pygame.OPENGL | pygame.DOUBLEBUF)
|
|
146
|
+
pygame.display.set_caption(self.title)
|
|
147
|
+
|
|
148
|
+
def _init_opengl(self):
|
|
149
|
+
"""Initialize OpenGL settings"""
|
|
150
|
+
glClearColor(0.0, 0.0, 0.0, 1.0)
|
|
151
|
+
glEnable(GL_BLEND)
|
|
152
|
+
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
|
153
|
+
|
|
154
|
+
def _init_font(self):
|
|
155
|
+
"""Initialize font for text rendering"""
|
|
156
|
+
self.font = pygame.font.Font(None, 24)
|
|
157
|
+
self.small_font = pygame.font.Font(None, 18)
|
|
158
|
+
|
|
159
|
+
def _create_geometry(self):
|
|
160
|
+
"""Create geometry required for rendering"""
|
|
161
|
+
# Vertex data: position + texture coordinates
|
|
162
|
+
self.vertices = np.array([
|
|
163
|
+
# Position # Texture coordinates
|
|
164
|
+
-1.0, -1.0, 0.0, 0.0, 0.0,
|
|
165
|
+
1.0, -1.0, 0.0, 1.0, 0.0,
|
|
166
|
+
1.0, 1.0, 0.0, 1.0, 1.0,
|
|
167
|
+
-1.0, 1.0, 0.0, 0.0, 1.0
|
|
168
|
+
], dtype=np.float32)
|
|
169
|
+
|
|
170
|
+
self.indices = np.array([
|
|
171
|
+
0, 1, 2,
|
|
172
|
+
2, 3, 0
|
|
173
|
+
], dtype=np.uint32)
|
|
174
|
+
|
|
175
|
+
# Create VAO, VBO, EBO
|
|
176
|
+
self.VAO = glGenVertexArrays(1)
|
|
177
|
+
self.VBO = glGenBuffers(1)
|
|
178
|
+
self.EBO = glGenBuffers(1)
|
|
179
|
+
|
|
180
|
+
glBindVertexArray(self.VAO)
|
|
181
|
+
|
|
182
|
+
glBindBuffer(GL_ARRAY_BUFFER, self.VBO)
|
|
183
|
+
glBufferData(GL_ARRAY_BUFFER, self.vertices.nbytes, self.vertices, GL_STATIC_DRAW)
|
|
184
|
+
|
|
185
|
+
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.EBO)
|
|
186
|
+
glBufferData(GL_ELEMENT_ARRAY_BUFFER, self.indices.nbytes, self.indices, GL_STATIC_DRAW)
|
|
187
|
+
|
|
188
|
+
# Position attribute
|
|
189
|
+
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * self.vertices.itemsize, ctypes.c_void_p(0))
|
|
190
|
+
glEnableVertexAttribArray(0)
|
|
191
|
+
|
|
192
|
+
# Texture coordinate attribute
|
|
193
|
+
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * self.vertices.itemsize, ctypes.c_void_p(3 * self.vertices.itemsize))
|
|
194
|
+
glEnableVertexAttribArray(1)
|
|
195
|
+
|
|
196
|
+
glBindBuffer(GL_ARRAY_BUFFER, 0)
|
|
197
|
+
glBindVertexArray(0)
|
|
198
|
+
|
|
199
|
+
def _compile_shader(self, fragment_src):
|
|
200
|
+
"""
|
|
201
|
+
Compile shader program
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
fragment_src: Fragment shader code
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
shader_program: Compiled shader program, None if failed
|
|
208
|
+
"""
|
|
209
|
+
try:
|
|
210
|
+
vertex_shader = compileShader(self.vertex_shader_src, GL_VERTEX_SHADER)
|
|
211
|
+
fragment_shader = compileShader(fragment_src, GL_FRAGMENT_SHADER)
|
|
212
|
+
shader_program = compileProgram(vertex_shader, fragment_shader)
|
|
213
|
+
return shader_program
|
|
214
|
+
except Exception as e:
|
|
215
|
+
print(f"Shader compilation error: {e}")
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
def _render_help_text(self):
|
|
219
|
+
"""Render help text on screen"""
|
|
220
|
+
# Switch to 2D rendering for text
|
|
221
|
+
glDisable(GL_DEPTH_TEST)
|
|
222
|
+
|
|
223
|
+
# Create a semi-transparent background for better readability
|
|
224
|
+
help_lines = [
|
|
225
|
+
"=== CAMERA CONTROLS ===",
|
|
226
|
+
"LEFT CLICK + DRAG: Rotate Camera",
|
|
227
|
+
"MIDDLE CLICK + DRAG: Pan Camera",
|
|
228
|
+
"MOUSE WHEEL: Zoom In/Out",
|
|
229
|
+
"R: Reset Camera Position",
|
|
230
|
+
"H: Toggle This Help",
|
|
231
|
+
"ESC: Exit Renderer",
|
|
232
|
+
"",
|
|
233
|
+
"Camera debugging enabled - check console"
|
|
234
|
+
]
|
|
235
|
+
|
|
236
|
+
y_offset = 20
|
|
237
|
+
for i, line in enumerate(help_lines):
|
|
238
|
+
if i == 0: # Header
|
|
239
|
+
text_surface = self.font.render(line, True, (255, 255, 0))
|
|
240
|
+
else:
|
|
241
|
+
text_surface = self.small_font.render(line, True, (255, 255, 255))
|
|
242
|
+
self.screen.blit(text_surface, (20, y_offset))
|
|
243
|
+
y_offset += 22 if i == 0 else 18
|
|
244
|
+
|
|
245
|
+
# Camera position info
|
|
246
|
+
cam_info = [
|
|
247
|
+
f"Camera Pos: ({self.camera.position[0]:.1f}, {self.camera.position[1]:.1f}, {self.camera.position[2]:.1f})",
|
|
248
|
+
f"Camera Target: ({self.camera.target[0]:.1f}, {self.camera.target[1]:.1f}, {self.camera.target[2]:.1f})"
|
|
249
|
+
]
|
|
250
|
+
|
|
251
|
+
y_offset += 10
|
|
252
|
+
for info in cam_info:
|
|
253
|
+
text_surface = self.small_font.render(info, True, (0, 255, 255))
|
|
254
|
+
self.screen.blit(text_surface, (20, y_offset))
|
|
255
|
+
y_offset += 18
|
|
256
|
+
|
|
257
|
+
glEnable(GL_DEPTH_TEST)
|
|
258
|
+
|
|
259
|
+
def render_shader(self, fragment_shader, duration=0, interactive=True):
|
|
260
|
+
"""
|
|
261
|
+
Render specified shader
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
fragment_shader: Fragment shader code string
|
|
265
|
+
duration: Render duration in seconds, 0 for infinite
|
|
266
|
+
interactive: Allow interaction (ESC to exit)
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
success: Whether rendering was successful
|
|
270
|
+
"""
|
|
271
|
+
# Compile shader
|
|
272
|
+
self.shader_program = self._compile_shader(fragment_shader)
|
|
273
|
+
if not self.shader_program:
|
|
274
|
+
return False
|
|
275
|
+
|
|
276
|
+
# Get uniform locations
|
|
277
|
+
time_loc = glGetUniformLocation(self.shader_program, "time")
|
|
278
|
+
resolution_loc = glGetUniformLocation(self.shader_program, "resolution")
|
|
279
|
+
mouse_loc = glGetUniformLocation(self.shader_program, "mouse")
|
|
280
|
+
camera_pos_loc = glGetUniformLocation(self.shader_program, "cameraPosition")
|
|
281
|
+
camera_target_loc = glGetUniformLocation(self.shader_program, "cameraTarget")
|
|
282
|
+
camera_up_loc = glGetUniformLocation(self.shader_program, "cameraUp")
|
|
283
|
+
camera_fov_loc = glGetUniformLocation(self.shader_program, "cameraFov")
|
|
284
|
+
|
|
285
|
+
self.is_running = True
|
|
286
|
+
self.start_time = time.time()
|
|
287
|
+
clock = pygame.time.Clock()
|
|
288
|
+
|
|
289
|
+
mouse_pos = [0.0, 0.0]
|
|
290
|
+
|
|
291
|
+
print("Renderer started! Camera controls:")
|
|
292
|
+
print("- Left mouse button + drag: Rotate")
|
|
293
|
+
print("- Middle mouse button + drag: Pan")
|
|
294
|
+
print("- Mouse wheel: Zoom")
|
|
295
|
+
print("- R: Reset camera")
|
|
296
|
+
print("- H: Toggle help")
|
|
297
|
+
|
|
298
|
+
while self.is_running:
|
|
299
|
+
current_time = time.time() - self.start_time
|
|
300
|
+
|
|
301
|
+
# Handle events
|
|
302
|
+
for event in pygame.event.get():
|
|
303
|
+
if event.type == pygame.QUIT:
|
|
304
|
+
self.is_running = False
|
|
305
|
+
elif event.type == pygame.KEYDOWN:
|
|
306
|
+
if event.key == pygame.K_ESCAPE and interactive:
|
|
307
|
+
self.is_running = False
|
|
308
|
+
elif event.key == pygame.K_r: # R key to reset camera
|
|
309
|
+
self.camera = Camera()
|
|
310
|
+
print("Camera reset to default position")
|
|
311
|
+
elif event.key == pygame.K_h: # H key to toggle help
|
|
312
|
+
self.show_help = not self.show_help
|
|
313
|
+
print(f"Help display: {'ON' if self.show_help else 'OFF'}")
|
|
314
|
+
|
|
315
|
+
# Handle mouse events for camera control
|
|
316
|
+
if interactive:
|
|
317
|
+
if event.type == pygame.MOUSEBUTTONDOWN:
|
|
318
|
+
if event.button == 1: # Left button - rotate
|
|
319
|
+
self.camera.is_rotating = True
|
|
320
|
+
self.camera.last_mouse_pos = event.pos
|
|
321
|
+
print("Rotation started")
|
|
322
|
+
elif event.button == 2: # Middle button - pan
|
|
323
|
+
self.camera.is_panning = True
|
|
324
|
+
self.camera.last_mouse_pos = event.pos
|
|
325
|
+
print("Panning started")
|
|
326
|
+
elif event.button == 4: # Wheel up - zoom in
|
|
327
|
+
self.camera.zoom(1.0)
|
|
328
|
+
elif event.button == 5: # Wheel down - zoom out
|
|
329
|
+
self.camera.zoom(-1.0)
|
|
330
|
+
|
|
331
|
+
elif event.type == pygame.MOUSEBUTTONUP:
|
|
332
|
+
if event.button == 1: # Left button release
|
|
333
|
+
self.camera.is_rotating = False
|
|
334
|
+
print("Rotation ended")
|
|
335
|
+
elif event.button == 2: # Middle button release
|
|
336
|
+
self.camera.is_panning = False
|
|
337
|
+
print("Panning ended")
|
|
338
|
+
self.camera.last_mouse_pos = None
|
|
339
|
+
|
|
340
|
+
elif event.type == pygame.MOUSEMOTION:
|
|
341
|
+
# Update mouse position for shader
|
|
342
|
+
mouse_pos[0] = event.pos[0] / self.width
|
|
343
|
+
mouse_pos[1] = 1.0 - event.pos[1] / self.height
|
|
344
|
+
|
|
345
|
+
# Handle camera movement
|
|
346
|
+
if self.camera.last_mouse_pos is not None:
|
|
347
|
+
delta_x = event.pos[0] - self.camera.last_mouse_pos[0]
|
|
348
|
+
delta_y = event.pos[1] - self.camera.last_mouse_pos[1]
|
|
349
|
+
|
|
350
|
+
if self.camera.is_rotating:
|
|
351
|
+
self.camera.rotate(delta_x, delta_y)
|
|
352
|
+
elif self.camera.is_panning:
|
|
353
|
+
self.camera.pan(delta_x, delta_y)
|
|
354
|
+
|
|
355
|
+
self.camera.last_mouse_pos = event.pos
|
|
356
|
+
|
|
357
|
+
# Check duration
|
|
358
|
+
if duration > 0 and current_time >= duration:
|
|
359
|
+
break
|
|
360
|
+
|
|
361
|
+
# Render
|
|
362
|
+
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
|
363
|
+
|
|
364
|
+
glUseProgram(self.shader_program)
|
|
365
|
+
|
|
366
|
+
# Set uniforms
|
|
367
|
+
if time_loc != -1:
|
|
368
|
+
glUniform1f(time_loc, current_time)
|
|
369
|
+
if resolution_loc != -1:
|
|
370
|
+
glUniform2f(resolution_loc, self.width, self.height)
|
|
371
|
+
if mouse_loc != -1:
|
|
372
|
+
glUniform2f(mouse_loc, mouse_pos[0], mouse_pos[1])
|
|
373
|
+
if camera_pos_loc != -1:
|
|
374
|
+
glUniform3f(camera_pos_loc,
|
|
375
|
+
self.camera.position[0],
|
|
376
|
+
self.camera.position[1],
|
|
377
|
+
self.camera.position[2])
|
|
378
|
+
if camera_target_loc != -1:
|
|
379
|
+
glUniform3f(camera_target_loc,
|
|
380
|
+
self.camera.target[0],
|
|
381
|
+
self.camera.target[1],
|
|
382
|
+
self.camera.target[2])
|
|
383
|
+
if camera_up_loc != -1:
|
|
384
|
+
glUniform3f(camera_up_loc,
|
|
385
|
+
self.camera.up[0],
|
|
386
|
+
self.camera.up[1],
|
|
387
|
+
self.camera.up[2])
|
|
388
|
+
if camera_fov_loc != -1:
|
|
389
|
+
glUniform1f(camera_fov_loc, self.camera.fov)
|
|
390
|
+
|
|
391
|
+
glBindVertexArray(self.VAO)
|
|
392
|
+
glDrawElements(GL_TRIANGLES, len(self.indices), GL_UNSIGNED_INT, None)
|
|
393
|
+
glBindVertexArray(0)
|
|
394
|
+
|
|
395
|
+
# Render help text if enabled
|
|
396
|
+
if self.show_help:
|
|
397
|
+
self._render_help_text()
|
|
398
|
+
|
|
399
|
+
pygame.display.flip()
|
|
400
|
+
clock.tick(60)
|
|
401
|
+
|
|
402
|
+
return True
|
|
403
|
+
|
|
404
|
+
def close(self):
|
|
405
|
+
"""Clean up resources"""
|
|
406
|
+
if self.shader_program:
|
|
407
|
+
glDeleteProgram(self.shader_program)
|
|
408
|
+
pygame.quit()
|
|
409
|
+
|
|
410
|
+
# Convenience function
|
|
411
|
+
def render_shader(fragment_shader, width=800, height=600, duration=0, title="Shader Renderer"):
|
|
412
|
+
"""
|
|
413
|
+
Convenience function: Directly render shader
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
fragment_shader: Fragment shader code
|
|
417
|
+
width: Window width
|
|
418
|
+
height: Window height
|
|
419
|
+
duration: Render duration in seconds
|
|
420
|
+
title: Window title
|
|
421
|
+
"""
|
|
422
|
+
renderer = ShaderRenderer(width, height, title)
|
|
423
|
+
success = renderer.render_shader(fragment_shader, duration)
|
|
424
|
+
renderer.close()
|
|
425
|
+
return success
|
phg/vis/GCU.dll
CHANGED
|
Binary file
|
phg/vis/vis.exe
CHANGED
|
Binary file
|