phg-vis 1.3.1__py3-none-any.whl → 1.4.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 CHANGED
@@ -8,22 +8,36 @@ Main Features:
8
8
  - PHG script visualization via vis.exe
9
9
  - Shader-based rendering with GLSL conversion
10
10
  - Pipe system visualization with world/local coordinate systems
11
+ - Multiple pipe visualization support
11
12
  - Real-time and image rendering capabilities
12
13
 
13
- Version: 0.2.0
14
- Author: PanGuoJun
15
- License: MIT
16
- """
17
-
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
- )
14
+ Version: 1.4.0
15
+ Author: PanGuoJun
16
+ License: MIT
17
+ """
18
+
19
+ import os
20
+ import tempfile
21
+
22
+ from .visphg import vis, image
23
+ from .shader_render import ShaderRenderer, render_shader
24
+ from .phg_to_shader import PHGToShaderConverter, phg_to_shader
25
+ from .pipe_string_phg import (
26
+ world_to_local_pipe_str,
27
+ local_pipe_to_phg,
28
+ world_pipe_to_phg,
29
+ world_pipestr_vis,
30
+ local_pipestr_vis,
31
+ multiple_pipes_vis
32
+ )
33
+ from .ai_bridge import (
34
+ compile_ai_phg,
35
+ vis_ai,
36
+ image_ai,
37
+ export_ai_obj,
38
+ web_view_ai,
39
+ )
40
+ from .web_three import write_three_view_html, serve_three_view
27
41
 
28
42
  # Extend ShaderRenderer class to support direct PHG rendering
29
43
  class PHGShaderRenderer(ShaderRenderer):
@@ -50,8 +64,8 @@ class PHGShaderRenderer(ShaderRenderer):
50
64
 
51
65
  return self.render_shader(shader_code, duration, interactive)
52
66
 
53
- # Convenience functions
54
- def render_phg(phg_script: str, width=800, height=600, duration=0, title="PHG Renderer"):
67
+ # Convenience functions
68
+ def render_phg(phg_script: str, width=800, height=600, duration=0, title="PHG Renderer"):
55
69
  """
56
70
  Directly render PHG script
57
71
 
@@ -77,37 +91,310 @@ def convert_phg_to_shader(phg_script: str) -> str:
77
91
  Returns:
78
92
  shader_code: GLSL shader code
79
93
  """
80
- return phg_to_shader(phg_script)
94
+ return phg_to_shader(phg_script)
81
95
 
82
- __all__ = [
83
- # Core visualization functions
84
- 'vis',
85
- 'image',
96
+ def visualize_pipe_variants(pipe_variants, start_position=None, colors=None, coordinate_system='world'):
97
+ """
98
+ Visualize multiple pipe variants for comparison
99
+
100
+ Parameters:
101
+ pipe_variants: List of pipe strings or configuration dictionaries
102
+ start_position: Starting coordinate (optional)
103
+ colors: List of colors for each pipe variant
104
+ coordinate_system: 'world' or 'local' coordinate system
105
+
106
+ Returns:
107
+ PHG script string
108
+ """
109
+ from coordinate_system import vec3, quat, coord3
110
+
111
+ # Handle start position
112
+ if start_position is None:
113
+ start_c = coord3(vec3(0, 0, 0), quat(1, 0, 0, 0))
114
+ else:
115
+ start_c = start_position
116
+
117
+ # Handle different input formats
118
+ if isinstance(pipe_variants[0], str):
119
+ # Simple list of pipe strings
120
+ if colors is None:
121
+ # Generate distinct colors for each variant
122
+ import colorsys
123
+ colors = []
124
+ for i in range(len(pipe_variants)):
125
+ hue = i / len(pipe_variants)
126
+ rgb = colorsys.hsv_to_rgb(hue, 0.8, 0.9)
127
+ colors.append(tuple(int(c * 255) for c in rgb))
128
+
129
+ if coordinate_system == 'world':
130
+ return world_pipestr_vis(pipe_variants, start_c, colors)
131
+ else:
132
+ return local_pipestr_vis(pipe_variants, start_c, colors)
133
+
134
+ else:
135
+ # List of configuration dictionaries
136
+ pipe_configs = []
137
+ for i, variant in enumerate(pipe_variants):
138
+ if isinstance(variant, str):
139
+ config = {
140
+ 'pipe_str': variant,
141
+ 'start_c': start_c,
142
+ 'color': colors[i] if colors else None,
143
+ 'radius': 0.3
144
+ }
145
+ else:
146
+ config = variant
147
+ if 'start_c' not in config:
148
+ config['start_c'] = start_c
149
+
150
+ pipe_configs.append(config)
151
+
152
+ return multiple_pipes_vis(pipe_configs, coordinate_system)
153
+
154
+ def create_pipe_comparison_grid(pipe_strings, grid_size=(2, 2), start_position=None,
155
+ base_color=(0, 55, 255), coordinate_system='world'):
156
+ """
157
+ Create a grid layout for comparing multiple pipe paths
158
+
159
+ Parameters:
160
+ pipe_strings: List of pipe strings to compare
161
+ grid_size: (rows, cols) for grid layout
162
+ start_position: Base starting position
163
+ base_color: Base color for pipes
164
+ coordinate_system: 'world' or 'local'
165
+
166
+ Returns:
167
+ PHG script string
168
+ """
169
+ from coordinate_system import vec3, quat, coord3
170
+
171
+ if start_position is None:
172
+ start_position = coord3(vec3(0, 0, 0), quat(1, 0, 0, 0))
173
+
174
+ rows, cols = grid_size
175
+ spacing = 5.0 # Space between pipes in grid
176
+
177
+ pipe_configs = []
178
+
179
+ for i, pipe_str in enumerate(pipe_strings):
180
+ if i >= rows * cols:
181
+ break
182
+
183
+ row = i // cols
184
+ col = i % cols
185
+
186
+ # Calculate position in grid
187
+ x_offset = col * spacing
188
+ z_offset = row * spacing
189
+
190
+ # Create individual start position for this pipe
191
+ individual_start = coord3(
192
+ vec3(
193
+ start_position.o.x + x_offset,
194
+ start_position.o.y,
195
+ start_position.o.z + z_offset
196
+ ),
197
+ start_position.Q()
198
+ )
199
+
200
+ # Generate color variation
201
+ color_variation = (
202
+ min(255, base_color[0] + (i * 30) % 100),
203
+ min(255, base_color[1] + (i * 50) % 100),
204
+ min(255, base_color[2] + (i * 70) % 100)
205
+ )
206
+
207
+ pipe_configs.append({
208
+ 'pipe_str': pipe_str,
209
+ 'start_c': individual_start,
210
+ 'color': color_variation,
211
+ 'radius': 0.25 + (i * 0.05) # Vary radius slightly
212
+ })
213
+
214
+ return multiple_pipes_vis(pipe_configs, coordinate_system)
215
+
216
+ def generate_pipe_variants_from_rules(base_pipe, num_variants=5, apply_rules=None):
217
+ """
218
+ Generate pipe variants using transformation rules
219
+
220
+ Parameters:
221
+ base_pipe: Base pipe string
222
+ num_variants: Number of variants to generate
223
+ apply_rules: List of rules to apply ('swap', 'insert', 'cancel')
224
+
225
+ Returns:
226
+ List of pipe variant strings
227
+ """
228
+ if apply_rules is None:
229
+ apply_rules = ['swap', 'insert', 'cancel']
230
+
231
+ variants = [base_pipe]
232
+
233
+ # Import the pipe transformer if available
234
+ try:
235
+ from .pipe_string_phg import PipeStringTransformer
236
+ transformer = PipeStringTransformer()
237
+
238
+ for _ in range(num_variants - 1):
239
+ current_pipe = base_pipe
240
+
241
+ # Apply random transformations
242
+ import random
243
+ for _ in range(random.randint(1, 3)):
244
+ rule = random.choice(apply_rules)
245
+
246
+ if rule == 'swap' and len(current_pipe) >= 2:
247
+ i, j = random.sample(range(len(current_pipe)), 2)
248
+ current_pipe = transformer.swap_positions(current_pipe, i, j)
249
+
250
+ elif rule == 'insert':
251
+ position = random.randint(0, len(current_pipe))
252
+ direction = random.choice(list(transformer.CANCEL_PAIRS.keys()))
253
+ current_pipe = transformer.insert_cancel_pair(current_pipe, position, direction)
254
+
255
+ elif rule == 'cancel':
256
+ current_pipe = transformer.cancel_adjacent_pairs(current_pipe)
257
+
258
+ # Simplify and add if valid
259
+ simplified = transformer.simplify_path(current_pipe)
260
+ if simplified not in variants:
261
+ variants.append(simplified)
262
+
263
+ except ImportError:
264
+ # Fallback: simple variant generation
265
+ import random
266
+ for i in range(num_variants - 1):
267
+ # Simple character shuffling
268
+ pipe_list = list(base_pipe)
269
+ random.shuffle(pipe_list)
270
+ variant = ''.join(pipe_list)
271
+ if variant not in variants:
272
+ variants.append(variant)
273
+
274
+ return variants[:num_variants]
275
+
276
+ # Enhanced pipe visualization with transformation support
277
+ def visualize_pipe_with_transformations(pipe_string, start_position=None,
278
+ transformations=None, show_variants=3):
279
+ """
280
+ Visualize a pipe string along with its transformed variants
281
+
282
+ Parameters:
283
+ pipe_string: Base pipe string
284
+ start_position: Starting coordinate
285
+ transformations: List of transformation rules to apply
286
+ show_variants: Number of variants to show
287
+
288
+ Returns:
289
+ PHG script string
290
+ """
291
+ # Generate variants
292
+ variants = generate_pipe_variants_from_rules(
293
+ pipe_string,
294
+ num_variants=show_variants + 1, # +1 for original
295
+ apply_rules=transformations
296
+ )
297
+
298
+ # Create comparison visualization
299
+ return visualize_pipe_variants(variants, start_position)
300
+
301
+
302
+ def web_view(script: str, out_html: str = "phg_view.html", out_obj: str = None,
303
+ ai_root: str = None, title: str = "PHG Web Viewer") -> str:
304
+ """
305
+ Export OBJ via AI parser and write a three.js HTML viewer.
306
+ """
307
+ return web_view_ai(script, out_html=out_html, out_obj=out_obj, ai_root=ai_root, title=title)
308
+
309
+
310
+ def web_serve(script: str, host: str = "127.0.0.1", port: int = 8766,
311
+ ai_root: str = None, title: str = "PHG Web Viewer") -> str:
312
+ """
313
+ Export OBJ via AI parser, write HTML, and serve with a local HTTP server.
314
+ """
315
+ temp_dir = tempfile.mkdtemp(prefix="phg_web_")
316
+ out_obj = os.path.join(temp_dir, "scene.obj")
317
+ out_html = os.path.join(temp_dir, "viewer.html")
318
+ web_view_ai(script, out_html=out_html, out_obj=out_obj, ai_root=ai_root, title=title)
319
+ return serve_three_view(out_obj, html_path=out_html, host=host, port=port, title=title)
320
+
321
+
322
+ def visualize(script: str, mode: str = "vis", ai: bool = False, **kwargs):
323
+ """
324
+ Unified visualization entrypoint.
325
+ mode: vis | image | shader | web
326
+ ai: use AI parser (DGE++) to normalize/expand before visualization
327
+ """
328
+ mode = (mode or "vis").lower()
329
+ ai_root = kwargs.pop("ai_root", None)
330
+
331
+ if mode in ("vis", "native", "exe"):
332
+ return vis_ai(script, ai_root=ai_root) if ai else vis(script)
333
+
334
+ if mode in ("image", "img", "png"):
335
+ filename = kwargs.pop("filename", "shot.png")
336
+ return image_ai(script, filename=filename, ai_root=ai_root) if ai else image(script, filename=filename)
337
+
338
+ if mode in ("shader", "ray", "rt"):
339
+ phg_script = compile_ai_phg(script, ai_root=ai_root, include_setup=True) if ai else script
340
+ return render_phg(phg_script, **kwargs)
341
+
342
+ if mode in ("web", "three"):
343
+ out_html = kwargs.pop("out_html", "phg_view.html")
344
+ out_obj = kwargs.pop("out_obj", None)
345
+ title = kwargs.pop("title", "PHG Web Viewer")
346
+ return web_view_ai(script, out_html=out_html, out_obj=out_obj, ai_root=ai_root, title=title)
347
+
348
+ raise ValueError(f"Unknown mode: {mode}")
349
+
350
+ __all__ = [
351
+ # Core visualization functions
352
+ 'vis',
353
+ 'image',
354
+ 'visualize',
86
355
 
87
356
  # Shader rendering
88
357
  'ShaderRenderer',
89
358
  'render_shader',
90
359
  'render_phg',
91
- 'PHGShaderRenderer',
360
+ 'PHGShaderRenderer',
92
361
 
93
362
  # PHG conversion
94
363
  'PHGToShaderConverter',
95
- 'phg_to_shader',
96
- 'convert_phg_to_shader',
364
+ 'phg_to_shader',
365
+ 'convert_phg_to_shader',
366
+ 'compile_ai_phg',
367
+ 'vis_ai',
368
+ 'image_ai',
369
+ 'export_ai_obj',
370
+ 'web_view',
371
+ 'web_view_ai',
372
+ 'web_serve',
373
+ 'write_three_view_html',
374
+ 'serve_three_view',
97
375
 
98
376
  # Pipe visualization
99
377
  'world_pipe_to_phg',
100
378
  'local_pipe_to_phg',
101
379
  'world_pipestr_vis',
102
380
  'local_pipestr_vis',
103
- ]
104
-
105
- __version__ = "1.3.1"
106
- __author__ = "PanGuoJun"
107
- __description__ = "Python Hypergraphics Library"
108
-
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")
381
+ 'multiple_pipes_vis',
382
+
383
+ # Enhanced pipe visualization
384
+ 'visualize_pipe_variants',
385
+ 'create_pipe_comparison_grid',
386
+ 'generate_pipe_variants_from_rules',
387
+ 'visualize_pipe_with_transformations',
388
+ ]
389
+
390
+ __version__ = "1.4.0"
391
+ __author__ = "PanGuoJun"
392
+ __description__ = "Python Hypergraphics Library"
393
+
394
+ if os.environ.get("PHG_VERBOSE"):
395
+ print(f"PHG {__version__} - {__description__}")
396
+ print("[OK] Visualization module loaded")
397
+ print("[OK] Shader converter loaded")
398
+ print("[OK] Pipe visualization loaded")
399
+ print("[OK] Multiple pipe visualization support loaded")
400
+ print("[OK] Pipe transformation utilities loaded")
phg/ai_bridge.py ADDED
@@ -0,0 +1,174 @@
1
+ """
2
+ AI bridge for PHG (DGE++ parser -> normalized PHG -> vis.exe / web).
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import json
8
+ import os
9
+ import subprocess
10
+ import sys
11
+ import tempfile
12
+ from typing import Any, Dict, Iterable, List, Optional
13
+
14
+ from .visphg import vis as _vis, image as _image
15
+ from .web_three import write_three_view_html
16
+
17
+ DEFAULT_AI_ROOT = r"C:\Users\18858\Documents\_AILab\DGE++\PHG"
18
+
19
+
20
+ def resolve_ai_root(ai_root: Optional[str] = None) -> str:
21
+ root = ai_root or os.environ.get("PHG_AI_ROOT") or DEFAULT_AI_ROOT
22
+ if not os.path.isdir(root):
23
+ raise FileNotFoundError(
24
+ f"AI PHG root not found: {root}. "
25
+ "Set PHG_AI_ROOT or pass ai_root explicitly."
26
+ )
27
+ return root
28
+
29
+
30
+ def _run_ai_cli(args: List[str], ai_root: Optional[str] = None, timeout: int = 120) -> subprocess.CompletedProcess:
31
+ root = resolve_ai_root(ai_root)
32
+ cmd = [sys.executable, "-m", "phg"] + args
33
+ return subprocess.run(
34
+ cmd,
35
+ cwd=root,
36
+ capture_output=True,
37
+ text=True,
38
+ timeout=timeout,
39
+ check=False,
40
+ )
41
+
42
+
43
+ def _write_temp_phg(script: str) -> str:
44
+ fd, path = tempfile.mkstemp(suffix=".phg")
45
+ os.close(fd)
46
+ with open(path, "w", encoding="utf-8") as f:
47
+ f.write(script)
48
+ return path
49
+
50
+
51
+ def dump_scene(script: str, ai_root: Optional[str] = None) -> Dict[str, Any]:
52
+ """
53
+ Use AI PHG (DGE++) to parse script and dump scene JSON.
54
+ """
55
+ phg_path = _write_temp_phg(script)
56
+ fd, json_path = tempfile.mkstemp(suffix=".json")
57
+ os.close(fd)
58
+ try:
59
+ proc = _run_ai_cli(["dump", phg_path, "--out", json_path], ai_root=ai_root)
60
+ if proc.returncode != 0:
61
+ raise RuntimeError(proc.stderr.strip() or proc.stdout.strip() or "AI dump failed")
62
+ with open(json_path, "r", encoding="utf-8") as f:
63
+ return json.load(f)
64
+ finally:
65
+ for p in (phg_path, json_path):
66
+ try:
67
+ os.remove(p)
68
+ except OSError:
69
+ pass
70
+
71
+
72
+ def _format_value(value: Any) -> str:
73
+ if value is None:
74
+ return ""
75
+ if isinstance(value, bool):
76
+ return "1" if value else "0"
77
+ if isinstance(value, (int, float)):
78
+ return str(value)
79
+ if isinstance(value, (list, tuple)):
80
+ return ",".join(str(v) for v in value)
81
+ return str(value)
82
+
83
+
84
+ def _emit_props(props: Dict[str, Any], indent: str) -> List[str]:
85
+ lines = []
86
+ for key, value in props.items():
87
+ val = _format_value(value)
88
+ if val == "":
89
+ lines.append(f"{indent}{key};")
90
+ else:
91
+ lines.append(f"{indent}{key}:{val};")
92
+ return lines
93
+
94
+
95
+ def _emit_node(node: Dict[str, Any], indent: str = " ") -> List[str]:
96
+ name = node.get("name", "node")
97
+ props = node.get("props", {}) or {}
98
+ children = node.get("children", []) or []
99
+
100
+ lines = [f"{indent}{name}{{"]
101
+ lines.extend(_emit_props(props, indent + " "))
102
+ for child in children:
103
+ lines.extend(_emit_node(child, indent + " "))
104
+ lines.append(f"{indent}}}")
105
+ return lines
106
+
107
+
108
+ def scene_to_phg(scene: Dict[str, Any], include_setup: bool = True) -> str:
109
+ """
110
+ Convert AI scene JSON (dump) to normalized PHG script.
111
+ """
112
+ root_props = scene.get("props", {}) or {}
113
+ root_children = scene.get("children", []) or []
114
+
115
+ lines = ["{"]
116
+ if root_props:
117
+ # Preserve root-level props by creating a synthetic node.
118
+ root_node = {
119
+ "name": scene.get("name", "root"),
120
+ "props": root_props,
121
+ "children": root_children,
122
+ }
123
+ lines.extend(_emit_node(root_node, indent=" "))
124
+ else:
125
+ for child in root_children:
126
+ lines.extend(_emit_node(child, indent=" "))
127
+ lines.append("}")
128
+ if include_setup:
129
+ lines.append("setup_draw();")
130
+ return "\n".join(lines)
131
+
132
+
133
+ def compile_ai_phg(script: str, ai_root: Optional[str] = None, include_setup: bool = True) -> str:
134
+ scene = dump_scene(script, ai_root=ai_root)
135
+ return scene_to_phg(scene, include_setup=include_setup)
136
+
137
+
138
+ def vis_ai(script: str, ai_root: Optional[str] = None) -> None:
139
+ phg_script = compile_ai_phg(script, ai_root=ai_root, include_setup=True)
140
+ _vis(phg_script)
141
+
142
+
143
+ def image_ai(script: str, filename: str = "shot.png", ai_root: Optional[str] = None) -> None:
144
+ phg_script = compile_ai_phg(script, ai_root=ai_root, include_setup=True)
145
+ _image(phg_script, filename=filename)
146
+
147
+
148
+ def export_ai_obj(script: str, out_obj: str, ai_root: Optional[str] = None) -> str:
149
+ phg_path = _write_temp_phg(script)
150
+ try:
151
+ proc = _run_ai_cli(["export", phg_path, "--out", out_obj], ai_root=ai_root)
152
+ if proc.returncode != 0:
153
+ raise RuntimeError(proc.stderr.strip() or proc.stdout.strip() or "AI export failed")
154
+ finally:
155
+ try:
156
+ os.remove(phg_path)
157
+ except OSError:
158
+ pass
159
+ return out_obj
160
+
161
+
162
+ def web_view_ai(
163
+ script: str,
164
+ out_html: str,
165
+ out_obj: Optional[str] = None,
166
+ ai_root: Optional[str] = None,
167
+ title: str = "PHG Web Viewer",
168
+ ) -> str:
169
+ if out_obj is None:
170
+ base, _ = os.path.splitext(out_html)
171
+ out_obj = base + ".obj"
172
+ export_ai_obj(script, out_obj, ai_root=ai_root)
173
+ write_three_view_html(out_obj, out_html, title=title)
174
+ return out_html