phg-vis 1.2.1__py3-none-any.whl → 1.3.1__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 CHANGED
@@ -1,4 +1,113 @@
1
- # phg/__init__.py
1
+ """
2
+ PHG - Python Hypergraphics Library
3
+ ===================================
4
+
5
+ A powerful graphics library for procedural 3D visualization using PHG scripts.
6
+
7
+ Main Features:
8
+ - PHG script visualization via vis.exe
9
+ - Shader-based rendering with GLSL conversion
10
+ - Pipe system visualization with world/local coordinate systems
11
+ - Real-time and image rendering capabilities
12
+
13
+ Version: 0.2.0
14
+ Author: PanGuoJun
15
+ License: MIT
16
+ """
17
+
2
18
  from .visphg import vis, image
19
+ from .shader_render import ShaderRenderer, render_shader
20
+ from .phg_to_shader import PHGToShaderConverter, phg_to_shader
21
+ from .pipe_string_phg import (
22
+ world_pipe_to_phg,
23
+ local_pipe_to_phg,
24
+ world_pipestr_vis,
25
+ local_pipestr_vis
26
+ )
27
+
28
+ # Extend ShaderRenderer class to support direct PHG rendering
29
+ class PHGShaderRenderer(ShaderRenderer):
30
+ """Shader renderer that supports direct PHG rendering"""
31
+
32
+ def render_phg(self, phg_script: str, duration=0, interactive=True):
33
+ """
34
+ Render PHG script
35
+
36
+ Parameters:
37
+ phg_script: PHG script code
38
+ duration: Render duration
39
+ interactive: Whether to allow interaction
40
+
41
+ Returns:
42
+ success: Whether rendering was successful
43
+ """
44
+ converter = PHGToShaderConverter()
45
+ shader_code = converter.generate_shader_code(phg_script)
46
+
47
+ if not shader_code:
48
+ print("Error: PHG conversion failed")
49
+ return False
50
+
51
+ return self.render_shader(shader_code, duration, interactive)
52
+
53
+ # Convenience functions
54
+ def render_phg(phg_script: str, width=800, height=600, duration=0, title="PHG Renderer"):
55
+ """
56
+ Directly render PHG script
57
+
58
+ Parameters:
59
+ phg_script: PHG script code
60
+ width: Window width
61
+ height: Window height
62
+ duration: Render duration
63
+ title: Window title
64
+ """
65
+ renderer = PHGShaderRenderer(width, height, title)
66
+ success = renderer.render_phg(phg_script, duration)
67
+ renderer.close()
68
+ return success
69
+
70
+ def convert_phg_to_shader(phg_script: str) -> str:
71
+ """
72
+ Convert PHG script to GLSL shader code
73
+
74
+ Parameters:
75
+ phg_script: PHG script
76
+
77
+ Returns:
78
+ shader_code: GLSL shader code
79
+ """
80
+ return phg_to_shader(phg_script)
81
+
82
+ __all__ = [
83
+ # Core visualization functions
84
+ 'vis',
85
+ 'image',
86
+
87
+ # Shader rendering
88
+ 'ShaderRenderer',
89
+ 'render_shader',
90
+ 'render_phg',
91
+ 'PHGShaderRenderer',
92
+
93
+ # PHG conversion
94
+ 'PHGToShaderConverter',
95
+ 'phg_to_shader',
96
+ 'convert_phg_to_shader',
97
+
98
+ # Pipe visualization
99
+ 'world_pipe_to_phg',
100
+ 'local_pipe_to_phg',
101
+ 'world_pipestr_vis',
102
+ 'local_pipestr_vis',
103
+ ]
104
+
105
+ __version__ = "1.3.1"
106
+ __author__ = "PanGuoJun"
107
+ __description__ = "Python Hypergraphics Library"
3
108
 
4
- __all__ = ['vis', 'image']
109
+ # Package initialization information
110
+ print(f"PHG {__version__} - {__description__}")
111
+ print("[OK] Visualization module loaded")
112
+ print("[OK] Shader converter loaded")
113
+ print("[OK] Pipe visualization loaded")
phg/phg_to_shader.py ADDED
@@ -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)