Skip to main content
Glama
code_generator.py11.2 kB
""" Code Generation Module Generate GHPython and C# script component code """ from typing import Optional from dataclasses import dataclass @dataclass class ScriptTemplate: """Template for generated scripts""" language: str code: str inputs: list[dict] outputs: list[dict] description: str # Common GHPython templates GHPYTHON_TEMPLATES = { "basic": '''""" {description} """ import rhinoscriptsyntax as rs import Rhino.Geometry as rg import ghpythonlib.components as ghcomp # Inputs: {input_names} # Outputs: {output_names} {code} ''', "geometry_processing": '''""" {description} Process geometry with Rhino.Geometry """ import Rhino.Geometry as rg from Rhino.Geometry import Point3d, Vector3d, Curve, Surface, Brep # Input: geometry (Geometry) # Output: result (Geometry) def process_geometry(geo): """Process a single geometry object""" # TODO: Add processing logic return geo if isinstance(geometry, list): result = [process_geometry(g) for g in geometry] else: result = process_geometry(geometry) ''', "data_tree": '''""" {description} Work with data trees """ import ghpythonlib.treehelpers as th from Grasshopper import DataTree from Grasshopper.Kernel.Data import GH_Path # Convert tree to nested lists nested_list = th.tree_to_list(x) # Process data processed = [] for branch in nested_list: processed.append([item * 2 for item in branch]) # Example operation # Convert back to tree result = th.list_to_tree(processed) ''', "point_grid": '''""" {description} Generate a grid of points """ import Rhino.Geometry as rg # Inputs: # x_count (int) - Number of points in X # y_count (int) - Number of points in Y # spacing (float) - Distance between points points = [] for i in range(x_count): for j in range(y_count): pt = rg.Point3d(i * spacing, j * spacing, 0) points.append(pt) a = points ''', "attractor": '''""" {description} Attractor point influence on geometry """ import Rhino.Geometry as rg import math # Inputs: # points (Point3d[]) - Points to affect # attractor (Point3d) - Attractor point # max_distance (float) - Maximum influence distance # strength (float) - Effect strength (0-1) result = [] for pt in points: dist = pt.DistanceTo(attractor) if dist < max_distance: # Calculate influence (inverse distance) influence = 1.0 - (dist / max_distance) influence = influence ** 2 # Quadratic falloff # Apply transformation based on influence direction = rg.Vector3d(pt - attractor) direction.Unitize() # Move point away from attractor based on influence new_pt = pt + direction * influence * strength result.append(new_pt) else: result.append(pt) a = result ''', "mesh_analysis": '''""" {description} Analyze mesh properties """ import Rhino.Geometry as rg # Input: mesh (Mesh) # Outputs: vertices, faces, normals, area vertices = [mesh.Vertices[i] for i in range(mesh.Vertices.Count)] faces = mesh.Faces normals = [mesh.Normals[i] for i in range(mesh.Normals.Count)] # Calculate total area area = 0 for i in range(mesh.Faces.Count): face = mesh.Faces[i] if face.IsQuad: # Quad face - split into two triangles tri1 = rg.Mesh() tri1.Vertices.Add(mesh.Vertices[face.A]) tri1.Vertices.Add(mesh.Vertices[face.B]) tri1.Vertices.Add(mesh.Vertices[face.C]) tri1.Faces.AddFace(0, 1, 2) tri2 = rg.Mesh() tri2.Vertices.Add(mesh.Vertices[face.A]) tri2.Vertices.Add(mesh.Vertices[face.C]) tri2.Vertices.Add(mesh.Vertices[face.D]) tri2.Faces.AddFace(0, 1, 2) area += rg.AreaMassProperties.Compute(tri1).Area area += rg.AreaMassProperties.Compute(tri2).Area else: # Triangle face tri = rg.Mesh() tri.Vertices.Add(mesh.Vertices[face.A]) tri.Vertices.Add(mesh.Vertices[face.B]) tri.Vertices.Add(mesh.Vertices[face.C]) tri.Faces.AddFace(0, 1, 2) area += rg.AreaMassProperties.Compute(tri).Area a = vertices b = faces c = normals d = area ''', } # Common C# templates CSHARP_TEMPLATES = { "basic": '''/* {description} */ using System; using System.Collections.Generic; using Rhino; using Rhino.Geometry; using Grasshopper; using Grasshopper.Kernel; // Inputs: {input_names} // Outputs: {output_names} {code} ''', "geometry_processing": '''/* {description} Process geometry with RhinoCommon */ using System; using System.Collections.Generic; using Rhino.Geometry; // Input: x (Geometry) // Output: A (Geometry) private void RunScript(object x, ref object A) {{ if (x is Point3d pt) {{ A = pt; }} else if (x is Curve crv) {{ A = crv; }} else if (x is Brep brep) {{ A = brep; }} }} ''', "point_grid": '''/* {description} Generate a grid of points */ using System; using System.Collections.Generic; using Rhino.Geometry; // Inputs: xCount, yCount, spacing // Output: A (List<Point3d>) private void RunScript(int xCount, int yCount, double spacing, ref object A) {{ var points = new List<Point3d>(); for (int i = 0; i < xCount; i++) {{ for (int j = 0; j < yCount; j++) {{ points.Add(new Point3d(i * spacing, j * spacing, 0)); }} }} A = points; }} ''', } class CodeGenerator: """Generate GHPython and C# code for Grasshopper""" def __init__(self): self.ghpython_templates = GHPYTHON_TEMPLATES self.csharp_templates = CSHARP_TEMPLATES def list_templates(self, language: str = "python") -> list[str]: """List available templates""" if language.lower() in ["python", "ghpython"]: return list(self.ghpython_templates.keys()) elif language.lower() in ["csharp", "c#"]: return list(self.csharp_templates.keys()) return [] def get_template(self, template_name: str, language: str = "python") -> Optional[str]: """Get a specific template""" if language.lower() in ["python", "ghpython"]: return self.ghpython_templates.get(template_name) elif language.lower() in ["csharp", "c#"]: return self.csharp_templates.get(template_name) return None def generate_ghpython( self, description: str, inputs: list[dict], outputs: list[dict], code_body: str ) -> str: """Generate a GHPython script""" input_names = ", ".join(i.get("name", "x") for i in inputs) if inputs else "None" output_names = ", ".join(o.get("name", "a") for o in outputs) if outputs else "None" # Build input comments input_comments = "" for inp in inputs: name = inp.get("name", "x") typ = inp.get("type", "any") desc = inp.get("description", "") input_comments += f"# Input: {name} ({typ}) - {desc}\n" # Build output comments output_comments = "" for out in outputs: name = out.get("name", "a") typ = out.get("type", "any") desc = out.get("description", "") output_comments += f"# Output: {name} ({typ}) - {desc}\n" return f'''""" {description} """ import rhinoscriptsyntax as rs import Rhino.Geometry as rg {input_comments}{output_comments} {code_body} ''' def generate_csharp( self, description: str, inputs: list[dict], outputs: list[dict], code_body: str ) -> str: """Generate a C# script""" # Build method signature params = [] for inp in inputs: name = inp.get("name", "x") typ = self._python_to_csharp_type(inp.get("type", "object")) params.append(f"{typ} {name}") for out in outputs: name = out.get("name", "A") params.append(f"ref object {name}") param_str = ", ".join(params) return f'''/* {description} */ using System; using System.Collections.Generic; using Rhino.Geometry; private void RunScript({param_str}) {{ {self._indent_code(code_body, 4)} }} ''' def _python_to_csharp_type(self, python_type: str) -> str: """Convert Python type hint to C# type""" type_map = { "int": "int", "float": "double", "double": "double", "str": "string", "string": "string", "bool": "bool", "Point3d": "Point3d", "Vector3d": "Vector3d", "Curve": "Curve", "Surface": "Surface", "Brep": "Brep", "Mesh": "Mesh", "Plane": "Plane", "Line": "Line", "any": "object", } return type_map.get(python_type, "object") def _indent_code(self, code: str, spaces: int) -> str: """Indent code by given spaces""" indent = " " * spaces lines = code.split("\n") return "\n".join(indent + line if line.strip() else line for line in lines) def generate_from_description(self, description: str, language: str = "python") -> dict: """ Generate code from a natural language description. Returns a dict with generated code and suggestions. """ description_lower = description.lower() # Match description to templates suggestions = [] if "grid" in description_lower or "point" in description_lower: suggestions.append({ "template": "point_grid", "reason": "Description mentions grid or points" }) if "attractor" in description_lower or "influence" in description_lower: suggestions.append({ "template": "attractor", "reason": "Description mentions attractor or influence" }) if "mesh" in description_lower: suggestions.append({ "template": "mesh_analysis", "reason": "Description mentions mesh" }) if "tree" in description_lower or "branch" in description_lower: suggestions.append({ "template": "data_tree", "reason": "Description mentions data tree or branches" }) # Default to basic if no matches if not suggestions: suggestions.append({ "template": "basic", "reason": "Default template" }) # Get the first suggested template template_name = suggestions[0]["template"] template = self.get_template(template_name, language) return { "description": description, "language": language, "suggested_template": template_name, "suggestions": suggestions, "code": template.format(description=description) if template else None } # Singleton instance _generator: Optional[CodeGenerator] = None def get_generator() -> CodeGenerator: """Get or create the code generator""" global _generator if _generator is None: _generator = CodeGenerator() return _generator

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/dongwoosuk/grasshopper-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server