phg-vis 1.2.1__tar.gz → 1.3.0__tar.gz

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.
Files changed (27) hide show
  1. {phg_vis-1.2.1/phg_vis.egg-info → phg_vis-1.3.0}/PKG-INFO +1 -1
  2. phg_vis-1.3.0/phg/__init__.py +82 -0
  3. phg_vis-1.3.0/phg/phg_to_shader.py +433 -0
  4. phg_vis-1.3.0/phg/shader_render.py +425 -0
  5. phg_vis-1.3.0/phg/vis/GCU.dll +0 -0
  6. {phg_vis-1.2.1 → phg_vis-1.3.0}/phg/vis/vis.exe +0 -0
  7. {phg_vis-1.2.1 → phg_vis-1.3.0/phg_vis.egg-info}/PKG-INFO +1 -1
  8. {phg_vis-1.2.1 → phg_vis-1.3.0}/phg_vis.egg-info/SOURCES.txt +2 -0
  9. {phg_vis-1.2.1 → phg_vis-1.3.0}/setup.py +1 -1
  10. phg_vis-1.2.1/phg/__init__.py +0 -4
  11. phg_vis-1.2.1/phg/vis/GCU.dll +0 -0
  12. {phg_vis-1.2.1 → phg_vis-1.3.0}/LICENSE +0 -0
  13. {phg_vis-1.2.1 → phg_vis-1.3.0}/MANIFEST.in +0 -0
  14. {phg_vis-1.2.1 → phg_vis-1.3.0}/README.md +0 -0
  15. {phg_vis-1.2.1 → phg_vis-1.3.0}/phg/vis/dragpad.exe +0 -0
  16. {phg_vis-1.2.1 → phg_vis-1.3.0}/phg/vis/dragpad_config.ini +0 -0
  17. {phg_vis-1.2.1 → phg_vis-1.3.0}/phg/vis/freeglut.dll +0 -0
  18. {phg_vis-1.2.1 → phg_vis-1.3.0}/phg/vis/imgui/main.lua +0 -0
  19. {phg_vis-1.2.1 → phg_vis-1.3.0}/phg/vis/lua.dll +0 -0
  20. {phg_vis-1.2.1 → phg_vis-1.3.0}/phg/vis/script.phg +0 -0
  21. {phg_vis-1.2.1 → phg_vis-1.3.0}/phg/vis/sqlite3.dll +0 -0
  22. {phg_vis-1.2.1 → phg_vis-1.3.0}/phg/vis/vis.ini +0 -0
  23. {phg_vis-1.2.1 → phg_vis-1.3.0}/phg/vis/zlib1.dll +0 -0
  24. {phg_vis-1.2.1 → phg_vis-1.3.0}/phg/visphg.py +0 -0
  25. {phg_vis-1.2.1 → phg_vis-1.3.0}/phg_vis.egg-info/dependency_links.txt +0 -0
  26. {phg_vis-1.2.1 → phg_vis-1.3.0}/phg_vis.egg-info/top_level.txt +0 -0
  27. {phg_vis-1.2.1 → phg_vis-1.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: phg_vis
3
- Version: 1.2.1
3
+ Version: 1.3.0
4
4
  Summary: A package for the PHG modeling language and 3D visualization tool.
5
5
  Home-page: https://github.com/panguojun/Coordinate-System
6
6
  Author: romeosoft
@@ -0,0 +1,82 @@
1
+ """
2
+ __init__.py
3
+ PHG主库 - 集成PHG到Shader转换功能
4
+ """
5
+
6
+ from .visphg import vis, image
7
+ from .shader_render import ShaderRenderer, render_shader
8
+ from .phg_to_shader import PHGToShaderConverter, phg_to_shader
9
+
10
+ # 扩展ShaderRenderer类以支持PHG直接渲染
11
+ class PHGShaderRenderer(ShaderRenderer):
12
+ """支持PHG直接渲染的Shader渲染器"""
13
+
14
+ def render_phg(self, phg_script: str, duration=0, interactive=True):
15
+ """
16
+ 渲染PHG脚本
17
+
18
+ 参数:
19
+ phg_script: PHG脚本代码
20
+ duration: 渲染持续时间
21
+ interactive: 是否允许交互
22
+
23
+ 返回:
24
+ success: 是否成功渲染
25
+ """
26
+ converter = PHGToShaderConverter()
27
+ shader_code = converter.generate_shader_code(phg_script)
28
+
29
+ if not shader_code:
30
+ print("错误: PHG转换失败")
31
+ return False
32
+
33
+ return self.render_shader(shader_code, duration, interactive)
34
+
35
+ # 便捷函数
36
+ def render_phg(phg_script: str, width=800, height=600, duration=0, title="PHG Renderer"):
37
+ """
38
+ 直接渲染PHG脚本
39
+
40
+ 参数:
41
+ phg_script: PHG脚本代码
42
+ width: 窗口宽度
43
+ height: 窗口高度
44
+ duration: 渲染持续时间
45
+ title: 窗口标题
46
+ """
47
+ renderer = PHGShaderRenderer(width, height, title)
48
+ success = renderer.render_phg(phg_script, duration)
49
+ renderer.close()
50
+ return success
51
+
52
+ def convert_phg_to_shader(phg_script: str) -> str:
53
+ """
54
+ 将PHG脚本转换为GLSL着色器代码
55
+
56
+ 参数:
57
+ phg_script: PHG脚本
58
+
59
+ 返回:
60
+ shader_code: GLSL着色器代码
61
+ """
62
+ return phg_to_shader(phg_script)
63
+
64
+ __all__ = [
65
+ 'vis',
66
+ 'image',
67
+ 'ShaderRenderer',
68
+ 'render_shader',
69
+ 'PHGToShaderConverter',
70
+ 'PHGShaderRenderer',
71
+ 'render_phg',
72
+ 'convert_phg_to_shader',
73
+ 'phg_to_shader'
74
+ ]
75
+
76
+ __version__ = "0.2.0"
77
+ __author__ = "PHG Development Team"
78
+ __description__ = "Python Graphics Library with PHG to Shader conversion"
79
+
80
+ # 包初始化信息
81
+ print(f"PHG {__version__} - {__description__}")
82
+ print("PHG to Shader转换器已加载")
@@ -0,0 +1,433 @@
1
+ """
2
+ phg_to_shader.py
3
+ PHG to Shader Converter Core Library
4
+ """
5
+
6
+ import re
7
+ import math
8
+ from typing import Dict, List, Any, Tuple
9
+
10
+ class PHGToShaderConverter:
11
+ """PHG to Shader converter"""
12
+
13
+ def __init__(self):
14
+ self.nodes = {}
15
+ self.geometry_types = {
16
+ 'cylinder': 'CYLINDER',
17
+ 'sphere': 'SPHERE',
18
+ 'cube': 'BOX',
19
+ 'box': 'BOX',
20
+ 'cone': 'CONE',
21
+ 'torus': 'TORUS',
22
+ 'plane': 'PLANE'
23
+ }
24
+ self.primitive_count = 0
25
+
26
+ def parse_phg(self, phg_script: str) -> Dict[str, Any]:
27
+ """Parse PHG script and extract node information"""
28
+ # Remove comments
29
+ phg_clean = re.sub(r'#.*$', '', phg_script, flags=re.MULTILINE)
30
+
31
+ # Extract node definitions
32
+ node_pattern = r'(\w+)\s*\{([^}]*)\}'
33
+ nodes = {}
34
+
35
+ for match in re.finditer(node_pattern, phg_clean):
36
+ node_name = match.group(1)
37
+ node_content = match.group(2)
38
+
39
+ # Parse node properties
40
+ properties = self._parse_node_properties(node_content)
41
+ nodes[node_name] = properties
42
+
43
+ return nodes
44
+
45
+ def _parse_node_properties(self, content: str) -> Dict[str, Any]:
46
+ """Parse node properties"""
47
+ properties = {}
48
+
49
+ # Property pattern: property:value1,value2;
50
+ prop_pattern = r'(\w+):([^;]+);'
51
+
52
+ for match in re.finditer(prop_pattern, content):
53
+ prop_name = match.group(1)
54
+ prop_value = match.group(2).strip()
55
+
56
+ # Handle different property types
57
+ if prop_name == 'md':
58
+ # Geometry definition
59
+ properties['geometry'] = self._parse_geometry(prop_value)
60
+ elif prop_name in ['xyz', 'sxyz', 'hpr', 'rot']:
61
+ # Vector values
62
+ properties[prop_name] = self._parse_vector(prop_value)
63
+ elif prop_name in ['x', 'y', 'z', 'rx', 'ry', 'rz', 's']:
64
+ # Scalar values
65
+ properties[prop_name] = float(prop_value)
66
+ elif prop_name == 'rgb':
67
+ # Color values
68
+ properties['color'] = self._parse_color(prop_value)
69
+
70
+ return properties
71
+
72
+ def _parse_geometry(self, geom_str: str) -> Dict[str, Any]:
73
+ """Parse geometry definition"""
74
+ parts = geom_str.strip().split()
75
+ if not parts:
76
+ return {}
77
+
78
+ geom_type = parts[0]
79
+
80
+ geometry = {'type': geom_type}
81
+
82
+ try:
83
+ if geom_type == 'cylinder':
84
+ geometry['radius'] = float(parts[1])
85
+ geometry['height'] = float(parts[2])
86
+ elif geom_type == 'sphere':
87
+ geometry['radius'] = float(parts[1])
88
+ elif geom_type == 'cube':
89
+ geometry['size'] = [float(parts[1])] * 3
90
+ elif geom_type == 'box':
91
+ if len(parts) >= 4:
92
+ geometry['size'] = [float(parts[1]), float(parts[2]), float(parts[3])]
93
+ else:
94
+ geometry['size'] = [1.0, 1.0, 1.0]
95
+ elif geom_type == 'cone':
96
+ geometry['radius1'] = float(parts[1])
97
+ geometry['radius2'] = float(parts[2]) if len(parts) > 2 else 0.0
98
+ geometry['height'] = float(parts[3]) if len(parts) > 3 else 1.0
99
+ elif geom_type == 'torus':
100
+ geometry['radius1'] = float(parts[1])
101
+ geometry['radius2'] = float(parts[2]) if len(parts) > 2 else 0.3
102
+ elif geom_type == 'plane':
103
+ geometry['normal'] = [float(parts[1]), float(parts[2]), float(parts[3])] if len(parts) > 3 else [0, 1, 0]
104
+ except (IndexError, ValueError):
105
+ print(f"Warning: Geometry parameter parsing error: {geom_str}")
106
+
107
+ return geometry
108
+
109
+ def _parse_vector(self, vec_str: str) -> List[float]:
110
+ """Parse vector values"""
111
+ try:
112
+ return [float(x.strip()) for x in vec_str.split(',')]
113
+ except ValueError:
114
+ return [0.0, 0.0, 0.0]
115
+
116
+ def _parse_color(self, color_str: str) -> List[float]:
117
+ """Parse color values and normalize to 0-1"""
118
+ try:
119
+ rgb = [float(x.strip()) for x in color_str.split(',')]
120
+ return [c / 255.0 for c in rgb]
121
+ except ValueError:
122
+ return [0.7, 0.7, 0.7]
123
+
124
+ def geometry_to_sdf_params(self, geometry: Dict[str, Any], transform: Dict[str, Any]) -> List[str]:
125
+ """Convert geometry to SDF parameter array"""
126
+ geom_type = geometry.get('type', 'sphere')
127
+ color = transform.get('color', [0.7, 0.7, 0.7])
128
+ position = transform.get('xyz', [0, 0, 0])
129
+
130
+ # Determine SDF type
131
+ sdf_type = self.geometry_types.get(geom_type, 'SPHERE')
132
+
133
+ params = []
134
+
135
+ # params[0]: position + type
136
+ params.append(f"vec4({position[0]:.3f}, {position[1]:.3f}, {position[2]:.3f}, float({sdf_type}))")
137
+
138
+ # params[1]: size parameters
139
+ if geom_type == 'cylinder':
140
+ radius = geometry.get('radius', 1.0)
141
+ height = geometry.get('height', 2.0)
142
+ params.append(f"vec4({radius:.3f}, {height:.3f}, 0.0, 0.0)")
143
+ elif geom_type == 'sphere':
144
+ radius = geometry.get('radius', 1.0)
145
+ params.append(f"vec4({radius:.3f}, 0.0, 0.0, 0.0)")
146
+ elif geom_type in ['cube', 'box']:
147
+ size = geometry.get('size', [1.0, 1.0, 1.0])
148
+ params.append(f"vec4({size[0]:.3f}, {size[1]:.3f}, {size[2]:.3f}, 0.0)")
149
+ elif geom_type == 'cone':
150
+ r1 = geometry.get('radius1', 1.0)
151
+ r2 = geometry.get('radius2', 0.0)
152
+ h = geometry.get('height', 2.0)
153
+ params.append(f"vec4({r1:.3f}, {h:.3f}, {r2:.3f}, 0.0)")
154
+ elif geom_type == 'torus':
155
+ r1 = geometry.get('radius1', 1.0)
156
+ r2 = geometry.get('radius2', 0.3)
157
+ params.append(f"vec4({r1:.3f}, {r2:.3f}, 0.0, 0.0)")
158
+ elif geom_type == 'plane':
159
+ normal = geometry.get('normal', [0, 1, 0])
160
+ params.append(f"vec4({normal[0]:.1f}, {normal[1]:.1f}, {normal[2]:.1f}, 0.0)")
161
+ else:
162
+ params.append("vec4(1.0, 0.0, 0.0, 0.0)")
163
+
164
+ # params[2]: rotation/direction
165
+ rotation = transform.get('hpr', transform.get('rot', [0, 0, 0]))
166
+ params.append(f"vec4({rotation[0]:.1f}, {rotation[1]:.1f}, {rotation[2]:.1f}, 0.0)")
167
+
168
+ # params[3]: color
169
+ params.append(f"vec4({color[0]:.3f}, {color[1]:.3f}, {color[2]:.3f}, 0.0)")
170
+
171
+ return params
172
+
173
+ def generate_shader_code(self, phg_script: str) -> str:
174
+ """Generate complete GLSL shader code"""
175
+ self.nodes = self.parse_phg(phg_script)
176
+
177
+ # Collect all geometries
178
+ primitives = []
179
+ for node_name, properties in self.nodes.items():
180
+ if 'geometry' in properties:
181
+ primitives.append((node_name, properties))
182
+
183
+ self.primitive_count = len(primitives)
184
+
185
+ if self.primitive_count == 0:
186
+ return self._create_empty_shader()
187
+
188
+ # Generate shader code
189
+ shader_code = self._create_shader_template()
190
+ shader_code = self._insert_primitives(shader_code, primitives)
191
+
192
+ return shader_code
193
+
194
+ def _create_empty_shader(self) -> str:
195
+ """Create shader for empty scene"""
196
+ return """
197
+ #version 330 core
198
+ out vec4 fragColor;
199
+ uniform float time;
200
+ uniform vec2 resolution;
201
+ uniform vec2 mouse;
202
+ uniform vec3 cameraPosition;
203
+ uniform vec3 cameraTarget;
204
+ uniform vec3 cameraUp;
205
+ uniform float cameraFov;
206
+
207
+ void main() {
208
+ vec2 uv = gl_FragCoord.xy / resolution.xy;
209
+ vec3 color = vec3(0.1, 0.2, 0.4);
210
+ fragColor = vec4(color, 1.0);
211
+ }
212
+ """
213
+
214
+ def _create_shader_template(self) -> str:
215
+ """Create shader template with camera support"""
216
+ return """
217
+ #version 330 core
218
+ out vec4 fragColor;
219
+
220
+ uniform float time;
221
+ uniform vec2 resolution;
222
+ uniform vec2 mouse;
223
+ uniform vec3 cameraPosition;
224
+ uniform vec3 cameraTarget;
225
+ uniform vec3 cameraUp;
226
+ uniform float cameraFov;
227
+
228
+ #define MAX_STEPS 100
229
+ #define MAX_DIST 200.0
230
+ #define SURF_DIST 0.001
231
+
232
+ // Primitive type enum
233
+ #define SPHERE 0
234
+ #define BOX 1
235
+ #define CYLINDER 2
236
+ #define TORUS 3
237
+ #define PLANE 4
238
+ #define CONE 5
239
+
240
+ // Primitive parameter definitions
241
+ {primitive_definitions}
242
+
243
+ // SDF function definitions
244
+ float sdfSphere(vec3 p, vec4 params) {
245
+ return length(p) - params.x;
246
+ }
247
+
248
+ float sdfBox(vec3 p, vec4 params) {
249
+ vec3 b = params.xyz;
250
+ vec3 q = abs(p) - b;
251
+ return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0);
252
+ }
253
+
254
+ float sdfCylinder(vec3 p, vec4 params) {
255
+ vec2 d = abs(vec2(length(p.xz), p.y)) - vec2(params.x, params.y);
256
+ return min(max(d.x, d.y), 0.0) + length(max(d, 0.0));
257
+ }
258
+
259
+ float sdfTorus(vec3 p, vec4 params) {
260
+ vec2 q = vec2(length(p.xz) - params.x, p.y);
261
+ return length(q) - params.y;
262
+ }
263
+
264
+ float sdfPlane(vec3 p, vec4 params) {
265
+ return dot(p, normalize(params.xyz));
266
+ }
267
+
268
+ float sdfCone(vec3 p, vec4 params) {
269
+ // params.x = base radius, params.y = height, params.z = top radius
270
+ vec2 q = vec2(length(p.xz), -p.y);
271
+ float d = length(q);
272
+ float angle = atan(params.z - params.x, params.y);
273
+ float c = cos(angle);
274
+ float s = sin(angle);
275
+ return max(dot(q, vec2(c, s)), -p.y - params.y);
276
+ }
277
+
278
+ // Unified SDF dispatcher function
279
+ float mapPrimitive(vec3 p, vec4 params[4]) {
280
+ int type = int(params[0].w);
281
+
282
+ if (type == SPHERE) return sdfSphere(p - params[0].xyz, params[1]);
283
+ if (type == BOX) return sdfBox(p - params[0].xyz, params[1]);
284
+ if (type == CYLINDER) return sdfCylinder(p - params[0].xyz, params[1]);
285
+ if (type == TORUS) return sdfTorus(p - params[0].xyz, params[1]);
286
+ if (type == PLANE) return sdfPlane(p - params[0].xyz, params[1]);
287
+ if (type == CONE) return sdfCone(p - params[0].xyz, params[1]);
288
+
289
+ return 1000.0;
290
+ }
291
+
292
+ // Scene SDF
293
+ float mapScene(vec3 p) {
294
+ float minDist = 1000.0;
295
+
296
+ {primitive_calls}
297
+
298
+ return minDist;
299
+ }
300
+
301
+ // Normal calculation
302
+ vec3 calculateNormal(vec3 p) {
303
+ vec2 e = vec2(0.001, 0.0);
304
+ return normalize(vec3(
305
+ mapScene(p + e.xyy) - mapScene(p - e.xyy),
306
+ mapScene(p + e.yxy) - mapScene(p - e.yxy),
307
+ mapScene(p + e.yyx) - mapScene(p - e.yyx)
308
+ ));
309
+ }
310
+
311
+ // Ray marching
312
+ float rayMarch(vec3 ro, vec3 rd) {
313
+ float depth = 0.0;
314
+
315
+ for (int i = 0; i < MAX_STEPS; i++) {
316
+ vec3 p = ro + depth * rd;
317
+ float dist = mapScene(p);
318
+ depth += dist;
319
+
320
+ if (dist < SURF_DIST || depth > MAX_DIST) break;
321
+ }
322
+
323
+ return depth;
324
+ }
325
+
326
+ // Get hit object color
327
+ vec3 getObjectColor(vec3 p) {
328
+ float minDist = 1000.0;
329
+ vec3 color = vec3(0.5);
330
+
331
+ {color_calls}
332
+
333
+ return color;
334
+ }
335
+
336
+ // Calculate camera ray direction using camera uniforms
337
+ vec3 getCameraRay(vec2 uv, vec3 camPos, vec3 camTarget, vec3 camUp, float fov) {
338
+ vec3 forward = normalize(camTarget - camPos);
339
+ vec3 right = normalize(cross(forward, camUp));
340
+ vec3 up = normalize(cross(right, forward));
341
+
342
+ float tanFov = tan(radians(fov) * 0.5);
343
+ vec3 rayDir = normalize(forward + right * uv.x * tanFov * (resolution.x / resolution.y) + up * uv.y * tanFov);
344
+
345
+ return rayDir;
346
+ }
347
+
348
+ void main() {
349
+ vec2 uv = (gl_FragCoord.xy - 0.5 * resolution.xy) / resolution.y;
350
+
351
+ // Use camera uniforms from Python for ray direction
352
+ vec3 ro = cameraPosition;
353
+ vec3 rd = getCameraRay(uv, cameraPosition, cameraTarget, cameraUp, cameraFov);
354
+
355
+ // Ray marching
356
+ float dist = rayMarch(ro, rd);
357
+
358
+ // Shading
359
+ vec3 color = vec3(0.1, 0.2, 0.4);
360
+
361
+ if (dist < MAX_DIST) {
362
+ vec3 p = ro + rd * dist;
363
+ vec3 normal = calculateNormal(p);
364
+ vec3 objColor = getObjectColor(p);
365
+
366
+ // Simple lighting
367
+ vec3 lightDir = normalize(vec3(1.0, 3.0, 2.0));
368
+ float diff = max(dot(normal, lightDir), 0.2);
369
+
370
+ color = objColor * diff;
371
+
372
+ // Add some ambient occlusion
373
+ float ao = 1.0 - (1.0 / (1.0 + dist * 0.1));
374
+ color *= ao;
375
+ }
376
+
377
+ fragColor = vec4(color, 1.0);
378
+ }
379
+ """
380
+
381
+ def _insert_primitives(self, template: str, primitives: List[Tuple[str, Dict]]) -> str:
382
+ """Insert primitive definitions and call code"""
383
+ primitive_definitions = []
384
+ primitive_calls = []
385
+ color_calls = []
386
+
387
+ for i, (node_name, properties) in enumerate(primitives):
388
+ geometry = properties['geometry']
389
+ params = self.geometry_to_sdf_params(geometry, properties)
390
+
391
+ # Generate primitive parameter definitions
392
+ primitive_definitions.append(f"// {node_name}")
393
+ for j, param in enumerate(params):
394
+ primitive_definitions.append(f"vec4 prim_{i}_{j} = {param};")
395
+ primitive_definitions.append("")
396
+
397
+ # Generate SDF calls
398
+ primitive_calls.append(f" // {node_name}")
399
+ primitive_calls.append(f" vec4 prim_{i}[4] = vec4[4](prim_{i}_0, prim_{i}_1, prim_{i}_2, prim_{i}_3);")
400
+ primitive_calls.append(f" float dist_{i} = mapPrimitive(p, prim_{i});")
401
+ primitive_calls.append(f" minDist = min(minDist, dist_{i});")
402
+ primitive_calls.append("")
403
+
404
+ # Generate color calls
405
+ color_calls.append(f" // {node_name}")
406
+ color_calls.append(f" vec4 color_prim_{i}[4] = vec4[4](prim_{i}_0, prim_{i}_1, prim_{i}_2, prim_{i}_3);")
407
+ color_calls.append(f" float color_dist_{i} = mapPrimitive(p, color_prim_{i});")
408
+ color_calls.append(f" if (color_dist_{i} < SURF_DIST && color_dist_{i} < minDist) {{")
409
+ color_calls.append(f" minDist = color_dist_{i};")
410
+ color_calls.append(f" color = prim_{i}_3.xyz;")
411
+ color_calls.append(f" }}")
412
+ color_calls.append("")
413
+
414
+ # Replace placeholders in template
415
+ shader_code = template.replace("{primitive_definitions}", "\n".join(primitive_definitions))
416
+ shader_code = shader_code.replace("{primitive_calls}", "\n".join(primitive_calls))
417
+ shader_code = shader_code.replace("{color_calls}", "\n".join(color_calls))
418
+
419
+ return shader_code
420
+
421
+ # Convenient conversion function
422
+ def phg_to_shader(phg_script: str) -> str:
423
+ """
424
+ Convert PHG script to GLSL shader code
425
+
426
+ Args:
427
+ phg_script: PHG script string
428
+
429
+ Returns:
430
+ shader_code: GLSL shader code
431
+ """
432
+ converter = PHGToShaderConverter()
433
+ return converter.generate_shader_code(phg_script)
@@ -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
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: phg_vis
3
- Version: 1.2.1
3
+ Version: 1.3.0
4
4
  Summary: A package for the PHG modeling language and 3D visualization tool.
5
5
  Home-page: https://github.com/panguojun/Coordinate-System
6
6
  Author: romeosoft
@@ -4,6 +4,8 @@ README.md
4
4
  setup.py
5
5
  phg/VisPHG.py
6
6
  phg/__init__.py
7
+ phg/phg_to_shader.py
8
+ phg/shader_render.py
7
9
  phg/visphg.py
8
10
  phg/vis/GCU.dll
9
11
  phg/vis/dragpad.exe
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name='phg_vis',
5
- version='1.2.1',
5
+ version='1.3.0',
6
6
  packages=find_packages(),
7
7
  include_package_data=True,
8
8
  description='A package for the PHG modeling language and 3D visualization tool.',
@@ -1,4 +0,0 @@
1
- # phg/__init__.py
2
- from .visphg import vis, image
3
-
4
- __all__ = ['vis', 'image']
Binary file
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes